Add searchable wiki catalogue and biology material navigation
This commit is contained in:
@@ -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";
|
||||
Reference in New Issue
Block a user