Add feature queries, dungeon information and event calendar UI

This commit is contained in:
shen
2026-10-15 02:28:01 -07:00
parent 9d9993b007
commit 4e2d39b612
17 changed files with 1248 additions and 11 deletions
+108 -9
View File
@@ -1,5 +1,5 @@
// 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 templates plus negotiated read-only server views; no gameplay mutation or loot RNG here.
static const char c_szWikiScript[] = R"PY(# -*- coding: utf-8 -*-
import app
import game
@@ -10,13 +10,22 @@ import player
import ui
import wndMgr
import weakref
import mt_feature_service
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 ServerCapabilities():
mask=mt_feature_service.g_service.mask if mt_feature_service.g_service else None
return {'schema':1,'static_catalogue':True,'model_preview':False,
'live_drops':None if mask is None else bool(mask&1),
'chest_preview':None if mask is None else bool(mask&2),
'attributes':None if mask is None else bool(mask&4),
'sources':None if mask is None else bool(mask&8),
'dungeons':None if mask is None else bool(mask&16),
'calendar':None if mask is None else bool(mask&32)}
def U(text):
@@ -46,7 +55,11 @@ class WikiWindow(ui.Window):
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.remote=None;self.remoteData=None;self.remoteError=None;self.remoteSerial=0
self.heading=Text(self,18,18,'游戏百科',0xffffd68b)
self.detailsButton=Action(self,130,6,120,'详细属性',self.OpenDetails)
self.dungeonButton=Action(self,258,6,96,'副本信息',self.OpenDungeons)
self.calendarButton=Action(self,362,6,80,'活动日历',self.OpenCalendar)
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)
@@ -72,6 +85,18 @@ class WikiWindow(ui.Window):
self.Reflow()
self.Hide()
def OpenCalendar(self):
import mt_calendar
mt_calendar.Toggle()
def OpenDungeons(self):
import mt_dungeons
mt_dungeons.Toggle()
def OpenDetails(self):
import mt_details
mt_details.Toggle()
def Load(self):
if self.catalogue:return True
try:
@@ -112,6 +137,13 @@ class WikiWindow(ui.Window):
self.page+=step;self.Refresh()
def DetailPage(self,step):
if self.remote and self.remoteData:
pages=max(1,(len(self.links)+self.rowCount-1)//self.rowCount)
kind,subject,offset=self.remote
if step>0 and self.detailPage+1>=pages and offset+len(self.remoteData['rows'])<self.remoteData['total']:
self.QueryRemote(kind,subject,offset+8);return
if step<0 and self.detailPage==0 and offset>0:
self.QueryRemote(kind,subject,max(0,offset-8));return
self.detailPage+=step;self.RefreshDetail()
def Refresh(self):
@@ -132,9 +164,12 @@ class WikiWindow(ui.Window):
def Select(self,mode,row,remember=True):
if remember and self.selected:self.history=(self.history+[self.selected])[-24:]
self.remote=None;self.remoteData=None;self.remoteSerial+=1
self.selected=(mode,row['id']);self.detailPage=0;self.RefreshDetail()
def Back(self):
if self.remote:
self.remote=None;self.remoteData=None;self.remoteSerial+=1;self.detailPage=0;self.RefreshDetail();return
if self.history:
self.selected=self.history.pop();self.detailPage=0;self.RefreshDetail()
@@ -142,6 +177,14 @@ class WikiWindow(ui.Window):
offset=self.detailPage*self.rowCount+index
if offset<len(self.links) and self.links[offset][1]:
vnum=self.links[offset][1]
if isinstance(vnum,tuple):
if vnum[0]=='query':self.QueryRemote(vnum[1],vnum[2],0)
elif vnum[0]=='mobs':
row=self.catalogue.mobs.get(vnum[1])
if row:self.Select('mobs',row)
else:self.QueryRemote('mob',vnum[1],0)
elif vnum[0]=='items':self.Open(vnum[1])
return
row=self.catalogue.FindItem(vnum)
if row:self.Select('items',row)
else:self.status.SetText(L(B('目标物品未收录:#%d'%vnum)))
@@ -149,7 +192,7 @@ class WikiWindow(ui.Window):
def RefreshDetail(self):
for b in self.detailButtons:b.Hide()
self.links=[];self.icon.Hide()
self.backButton.SetEnabled(bool(self.history))
self.backButton.SetEnabled(bool(self.history) or self.remote is not None)
if not self.selected or not self.catalogue:
self.dprev.SetEnabled(False);self.dnext.SetEnabled(False)
return
@@ -189,18 +232,74 @@ class WikiWindow(ui.Window):
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))
lines.append((u'查看获取来源',('query','sources',vnum)))
if row['kind'] in ('ITEM_GIFTBOX','ITEM_TREASURE_BOX'):lines.append((u'宝箱奖励预览',('query','chest',vnum)))
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)]
(u'基础防御:%d 经验:%d'%(row['defense'],row['exp']),0),(u'查看服务端掉落列表',('query','mob',vnum))]
if self.remote:lines=self.RemoteLines()
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)
before=bool(self.remoteData and self.remote and self.remote[2]>0)
after=bool(self.remoteData and self.remote and self.remote[2]+len(self.remoteData['rows'])<self.remoteData['total'])
self.dprev.SetEnabled(self.detailPage>0 or before);self.dnext.SetEnabled(self.detailPage+1<pages or after)
def QueryRemote(self,kind,subject,offset=0):
self.remote=(kind,subject,offset);self.remoteData=None;self.remoteError=None;self.detailPage=0;self.remoteSerial+=1
serial=self.remoteSerial
self.RefreshDetail()
def received(data,error):
if self.remoteSerial!=serial or self.remote!=(kind,subject,offset):return
self.remoteData=data;self.remoteError=error;self.RefreshDetail()
if mt_feature_service.g_service:mt_feature_service.g_service.Request(kind,subject,offset,received)
else:received(None,'unsupported')
def RemoteLines(self):
if self.remoteError:
text={'unsupported':'服务端尚未启用此查询','unsupported_chest':'此道具未接入通用宝箱预览',
'missing':'服务端不存在此目标','timeout':'查询超时,请返回后重试','bad_reply':'查询数据异常,请更新客户端',
'rate':'操作过快,请稍后重试','too_many':'来源过多,请按怪物分别查询'}.get(self.remoteError,'查询失败,请返回后重试')
return [(U(text),0)]
if self.remoteData is None:return [(U('正在向服务器查询…'),0)]
data=self.remoteData;kind,subject,offset=self.remote
lines=[]
if kind=='chest':
lines.append((U('各项独立判定,可能多项或无奖励' if data['mode']==11 else '按权重随机抽取一项'),0))
lines.append((U('仅预览;条件、钥匙与消耗仍由服务器校验'),0))
else:
lines.append((U('已加载常规掉落;任务/活动/矿石额外掉落未全覆盖'),0))
lines.append((U('列出候选及限制,不表示最终中奖率'),0))
for vnum,count,rule,low,high,weight,total,rare in data['rows']:
target=0
if kind=='chest' and vnum<=7:
label={1:U('金币'),2:U('经验'),3:U('召唤怪物'),4:U('减速效果'),5:U('生命损失'),6:U('中毒效果'),7:U('召唤怪物组')}.get(vnum,U('特殊效果'))
label+=u' %d'%count
elif kind=='sources' and rule<20:
row=self.catalogue.mobs.get(vnum);label=row['name'] if row else u'怪物 #%d'%vnum
target=('mobs',vnum);label+=u' ×%d'%count
else:
row=self.catalogue.FindItem(vnum);label=row['name'] if row else u'物品 #%d'%vnum
target=('items',vnum)
if kind!='sources':label+=u' ×%d'%count
lines.append((label,target))
if kind=='chest':
detail=(u'独立判定:%d/100'%min(weight,100)) if rule==11 else (u'权重:%d / %d'%(weight,total))
if rare:detail+=u' 可能附带随机属性'
elif rule>=20:detail=U('宝箱来源;点击可继续查看预览')
elif rule==0:detail=u'通用掉落;角色等级 %d—%d'%(low,high)
elif rule==2:detail=u'击杀组候选权重 %d/%d'%(weight,total)
elif rule==3:detail=u'等级组;角色至少 %d 级'%low
elif rule==4:detail=U('需掉宝手套或对应增益资格')
elif rule==5:detail=U('怪物模板附加掉落')
else:detail=U('怪物独立掉落组')
lines.append((detail,0))
if not data['rows']:lines.append((U('当前查询范围内无配置;不代表所有途径均不存在'),0))
lines.append((U('第 %d—%d 条,共 %d 条')%(offset+1 if data['total'] else 0,offset+len(data['rows']),data['total']),0))
return lines
def Reflow(self):
dims=(wndMgr.GetScreenWidth(),wndMgr.GetScreenHeight())
@@ -243,8 +342,8 @@ class WikiWindow(ui.Window):
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
for b in self.listButtons+self.detailButtons+[self.detailsButton,self.dungeonButton,self.calendarButton,self.closeButton,self.backButton,self.searchButton,self.itemTab,self.mobTab,self.categoryButton,self.prev,self.next,self.dprev,self.dnext]:b.event=None
self.remoteSerial+=1;self.catalogue=None
class Entry(Action):