Add searchable wiki catalogue and biology material navigation

This commit is contained in:
shen
2026-10-15 00:48:50 -07:00
parent 6472d17306
commit 9d9993b007
21 changed files with 7926 additions and 3 deletions
+286
View File
@@ -0,0 +1,286 @@
// PORT: AVA2-inspired encyclopedia, adapted to current server templates and touch controls.
// Gameplay/loot is never computed here. The schema explicitly advertises static data only.
static const char c_szWikiScript[] = R"PY(# -*- coding: utf-8 -*-
import app
import game
import grp
import item
import pack
import player
import ui
import wndMgr
import weakref
from mt_biolog import Action, Text, L
from mt_wiki_data import Catalogue
PATH='wiki/catalogue.txt'
g_window=None
g_entry=None
CAPABILITIES={'schema':1,'static_catalogue':True,'live_drops':False,'chest_preview':False,'model_preview':False}
def U(text):
return text if isinstance(text,unicode) else text.decode('utf-8')
def B(text, limit=0):
text=U(text)
if limit and len(text)>limit:text=text[:limit-1]+u'…'
return text.encode('utf-8')
KINDS=(('', '全部物品'),('ITEM_WEAPON','武器'),('ITEM_ARMOR','防具首饰'),('ITEM_MATERIAL','材料'),
('ITEM_QUEST','任务物品'),('ITEM_USE','消耗品'),('ITEM_GIFTBOX','宝箱'),('ITEM_METIN','宝石'))
MOBKINDS=(('', '全部怪物'),('MONSTER','怪物'),('STONE','矿石'))
TYPE_NAMES=dict(KINDS)
TYPE_NAMES.update({'ITEM_NONE':'特殊道具','ITEM_UNIQUE':'特殊装备','ITEM_TOOL':'工具','ITEM_FISH':'鱼类','ITEM_ROD':'鱼竿','ITEM_PICK':'采矿镐','ITEM_RESOURCE':'资源','ITEM_SKILLBOOK':'技能书','ITEM_SKILLFORGET':'技能忘却书','ITEM_COSTUME':'时装','ITEM_SPECIAL':'特殊道具'})
RANK_NAMES={'PAWN':'普通','S_PAWN':'弱小','KNIGHT':'精英','S_KNIGHT':'强力','BOSS':'首领','KING':'王者'}
class WikiWindow(ui.Window):
def __init__(self):
ui.Window.__init__(self)
self.SetWindowName('WikiWindow')
self.AddFlag('float')
self.AddFlag('movable')
self.mode='items';self.category=0;self.page=0;self.detailPage=0
self.catalogue=None;self.error='';self.selected=None;self.results=[];self.links=[]
self.dimensions=None;self.lastQuery=None;self.history=[]
self.heading=Text(self,18,18,'游戏百科',0xffffd68b)
self.closeButton=Action(self,0,6,52,'关闭',self.Hide)
self.backButton=Action(self,0,6,72,'返回',self.Back)
self.searchBox=ui.SlotBar();self.searchBox.SetParent(self);self.searchBox.SetPosition(18,54)
self.searchBox.SetSize(320,28);self.searchBox.Show()
self.search=ui.EditLine();self.search.SetParent(self.searchBox);self.search.SetPosition(8,6)
self.search.SetSize(302,20);self.search.SetMax(80);self.search.SetReturnEvent(self.Search)
self.search.SetEscapeEvent(self.Hide);self.search.Show()
self.searchButton=Action(self,346,50,72,'搜索',self.Search)
self.itemTab=Action(self,18,100,96,'物品',lambda:self.SetMode('items'))
self.mobTab=Action(self,122,100,96,'怪物',lambda:self.SetMode('mobs'))
self.categoryButton=Action(self,226,100,164,'全部物品',self.NextCategory)
self.status=Text(self,18,85,'输入名称或 ID 搜索',0xffc3b7a1)
self.title=Text(self,0,112,'选择左侧条目',0xffffd68b)
self.icon=ui.ExpandedImageBox();self.icon.SetParent(self);self.icon.AddFlag('not_pick')
self.listButtons=[Action(self,0,0,100,'',lambda i=i:self.SelectResult(i)) for i in range(8)]
self.detailButtons=[Action(self,0,0,100,'',lambda i=i:self.Follow(i)) for i in range(8)]
self.prev=Action(self,18,0,72,'上一页',lambda:self.Page(-1))
self.next=Action(self,0,0,72,'下一页',lambda:self.Page(1))
self.pageText=Text(self,110,0)
self.dprev=Action(self,0,0,72,'详情上页',lambda:self.DetailPage(-1))
self.dnext=Action(self,0,0,72,'详情下页',lambda:self.DetailPage(1))
self.revision=Text(self,430,66,'未加载资料',0xffaaa08b)
self.Reflow()
self.Hide()
def Load(self):
if self.catalogue:return True
try:
if not pack.Exist(PATH):raise ValueError('missing catalogue')
self.catalogue=Catalogue(pack.Get(PATH))
self.revision.SetText(L(B(u'资料版本 '+self.catalogue.version[:12])))
self.error=''
return True
except (ValueError,TypeError,UnicodeError,RuntimeError,IOError):
self.error='百科资料缺失或版本不支持,请更新资源包。'
self.status.SetText(L(B(self.error,24)))
return False
def Open(self,vnum=0):
self.Reflow();self.SetCenterPosition();self.Show();self.SetTop()
if not self.Load():return
self.Search()
if vnum:
row=self.catalogue.FindItem(int(vnum))
if row:self.Select('items',row)
else:self.status.SetText(L(B('当前资料未收录此物品。')))
def Query(self):
value=self.search.GetText()
return player.FeatureLocaleToUTF8(value).decode('utf-8').strip()
def Search(self):
self.page=0;self.lastQuery=self.Query();self.Refresh()
def SetMode(self,mode):
self.mode=mode;self.category=0;self.page=0;self.Refresh()
def NextCategory(self):
choices=KINDS if self.mode=='items' else MOBKINDS
self.category=(self.category+1)%len(choices);self.page=0;self.Refresh()
def Page(self,step):
self.page+=step;self.Refresh()
def DetailPage(self,step):
self.detailPage+=step;self.RefreshDetail()
def Refresh(self):
for button in self.listButtons:button.Hide()
if not self.catalogue:return
choices=KINDS if self.mode=='items' else MOBKINDS
kind,label=choices[self.category]
self.categoryButton.SetText(label)
self.results,count,self.page,pages=self.catalogue.Search(self.mode,self.Query(),kind,self.page,self.rowCount)
for i,row in enumerate(self.results):
self.listButtons[i].SetText(B(row['name'],max(9,(self.split-130)//12))+' #%d'%row['id']);self.listButtons[i].Show()
self.status.SetText(L(B('%d 项;模板资料不保证可获取。'%count)))
self.pageText.SetText(L(B('%d / %d'%(self.page+1,pages))))
self.prev.SetEnabled(self.page>0);self.next.SetEnabled(self.page+1<pages)
def SelectResult(self,index):
if index<len(self.results):self.Select(self.mode,self.results[index])
def Select(self,mode,row,remember=True):
if remember and self.selected:self.history=(self.history+[self.selected])[-24:]
self.selected=(mode,row['id']);self.detailPage=0;self.RefreshDetail()
def Back(self):
if self.history:
self.selected=self.history.pop();self.detailPage=0;self.RefreshDetail()
def Follow(self,index):
offset=self.detailPage*self.rowCount+index
if offset<len(self.links) and self.links[offset][1]:
vnum=self.links[offset][1]
row=self.catalogue.FindItem(vnum)
if row:self.Select('items',row)
else:self.status.SetText(L(B('目标物品未收录:#%d'%vnum)))
def RefreshDetail(self):
for b in self.detailButtons:b.Hide()
self.links=[];self.icon.Hide()
self.backButton.SetEnabled(bool(self.history))
if not self.selected or not self.catalogue:
self.dprev.SetEnabled(False);self.dnext.SetEnabled(False)
return
mode,vnum=self.selected
row=(self.catalogue.items if mode=='items' else self.catalogue.mobs)[vnum]
self.title.SetText(L(B(row['name'],max(8,(self.split-150)//12))+' #%d'%vnum))
lines=[]
if mode=='items':
lines.append((u'类型:'+U(TYPE_NAMES.get(row['kind'],'其他物品')),0))
lines.append((u'最低等级:%d 占格:%d'%(row['level'],row['size']),0))
if row['end']!=vnum:lines.append((u'模板 ID 范围:%d—%d'%(vnum,row['end']),0))
try:
path=item.FeatureGetIconPath(vnum)
if path:self.icon.LoadImage(path);self.icon.Show()
except (RuntimeError,ValueError):pass
target=row['refined'];recipe=self.catalogue.recipes.get(row['recipe'])
if target:
name=self.catalogue.FindItem(target)
lines.append((u'升级 → '+(name['name'] if name else u'#%d'%target),target if name else 0))
if recipe:
lines.append((u'基础配方:%d 金币 / %d%%'%(recipe['cost'],recipe['probability']),0))
for mat,count in recipe['materials']:
ingredient=self.catalogue.FindItem(mat)
lines.append((u'材料:'+(ingredient['name'] if ingredient else u'#%d'%mat)+u' ×%d'%count,mat if ingredient else 0))
lines.append((u'实际费用/概率以精炼窗口为准',0))
else:lines.append((u'升级配方尚未收录',0))
else:lines.append((u'没有配置下一阶升级',0))
import mt_biolog
for stage in sorted(mt_biolog.CATALOG):
bio=mt_biolog.CATALOG[stage]
if vnum in (bio['material'],bio['stone']):
lines.append((u'生物学家:%d 级研究'%bio['level'],0))
if vnum==bio['material']:lines.append((u'阶段所需:%d 个'%bio['count'],0))
offer=next((o for o in mt_biolog.SHOP if o['vnum']==vnum),None)
if offer:
if offer['gold']:lines.append((u'研究商店:%d 金币'%offer['gold'],0))
else:
cost=self.catalogue.FindItem(offer['cost'])
lines.append((u'研究兑换:'+(cost['name'] if cost else u'#%d'%offer['cost'])+u' ×%d'%offer['amount'],offer['cost'] if cost else 0))
lines.append((u'怪物掉落来源:尚未接入',0))
if row['kind']=='ITEM_GIFTBOX':lines.append((u'宝箱奖励预览:尚未接入',0))
else:
lines=[(u'类型:'+U('矿石' if row['kind']=='STONE' else '怪物')+u' / '+U(RANK_NAMES.get(row['rank'],'普通')),0),(u'等级:%d 生命:%d'%(row['level'],row['hp']),0),
(u'基础攻击:%d—%d'%(row['attack_min'],row['attack_max']),0),
(u'基础防御:%d 经验:%d'%(row['defense'],row['exp']),0),(u'掉落列表:尚未接入',0)]
self.links=lines
pages=max(1,(len(lines)+self.rowCount-1)//self.rowCount)
self.detailPage=max(0,min(self.detailPage,pages-1))
for i,(label,link) in enumerate(lines[self.detailPage*self.rowCount:(self.detailPage+1)*self.rowCount]):
self.detailButtons[i].SetText(B(label,max(15,(self.split-40)//12)));self.detailButtons[i].SetEnabled(bool(link));self.detailButtons[i].label.SetPackedFontColor(0xffefdbac if link else 0xffc8bea8);self.detailButtons[i].Show()
self.dprev.SetEnabled(self.detailPage>0);self.dnext.SetEnabled(self.detailPage+1<pages)
def Reflow(self):
dims=(wndMgr.GetScreenWidth(),wndMgr.GetScreenHeight())
if dims==self.dimensions:return
self.dimensions=dims
width=min(760,max(600,dims[0]-16));height=min(480,max(300,dims[1]-16))
self.SetSize(width,height);self.split=width//2
self.categoryButton.SetSize(self.split-234,48);self.categoryButton.label.SetPosition((self.split-234)//2,24)
self.rowCount=min(8,max(2,(height-216)//48))
self.closeButton.SetPosition(width-70,6);self.backButton.SetPosition(width-150,6)
self.title.SetPosition(self.split+48,122);self.icon.SetPosition(self.split+8,112)
for i,b in enumerate(self.listButtons):
b.SetPosition(18,154+i*48);b.SetSize(self.split-30,44);b.label.SetPosition((self.split-30)//2,22)
for i,b in enumerate(self.detailButtons):
b.SetPosition(self.split+8,154+i*48);b.SetSize(self.split-26,44);b.label.SetPosition((self.split-26)//2,22)
self.prev.SetPosition(18,height-54);self.next.SetPosition(self.split-90,height-54);self.pageText.SetPosition(112,height-30)
self.dprev.SetPosition(self.split+8,height-54);self.dnext.SetPosition(width-90,height-54)
self.SetCenterPosition();self.Refresh();self.RefreshDetail()
def OnUpdate(self):
if self.IsShow():self.Reflow()
def OnRender(self):
x,y=self.GetGlobalPosition();grp.SetColor(0xf51b1916);grp.RenderBar(x,y,self.GetWidth(),self.GetHeight())
grp.SetColor(0xff9b7b48);grp.RenderBox(x,y,self.GetWidth(),self.GetHeight())
grp.RenderLine(x+self.split,y+108,0,self.GetHeight()-170)
def OnMouseWheel(self,delta):
x,y=self.GetGlobalPosition();mx,my=wndMgr.GetMousePosition()
if mx>=x+self.split:self.DetailPage(-1 if delta>0 else 1)
else:self.Page(-1 if delta>0 else 1)
return True
def Hide(self):
if hasattr(self,'search'):self.search.KillFocus()
ui.Window.Hide(self)
def OnPressEscapeKey(self):
self.Hide();return True
def Destroy(self):
self.Hide();self.search.SetReturnEvent(None);self.search.SetEscapeEvent(None)
for b in self.listButtons+self.detailButtons+[self.closeButton,self.backButton,self.searchButton,self.itemTab,self.mobTab,self.categoryButton,self.prev,self.next,self.dprev,self.dnext]:b.event=None
self.catalogue=None
class Entry(Action):
def __init__(self,window):
Action.__init__(self,None,0,0,64,'百科',Toggle)
self.minimap=weakref.proxy(window.interface.wndMiniMap);self.SetWindowName('WikiEntry');self.OnUpdate()
def OnUpdate(self):
x,y=self.minimap.GetGlobalPosition();self.SetPosition(max(4,x-68),y+96)
def Toggle():
if g_window:
if g_window.IsShow():g_window.Hide()
else:g_window.Open()
def OpenItem(vnum):
if g_window:g_window.Open(vnum)
_open=game.GameWindow.Open
_close=game.GameWindow.Close
def Open(self):
global g_window,g_entry
_open(self);g_window=WikiWindow();g_entry=Entry(self)
def Close(self):
global g_window,g_entry
if g_entry:g_entry.Hide();g_entry.event=None;g_entry=None
if g_window:g_window.Destroy();g_window=None
_close(self)
game.GameWindow.Open=Open
game.GameWindow.Close=Close
)PY";