feat: add MeshPort overlay networking, item actions window, and AVA2 client enhancements

- add MeshPort peer-to-peer overlay networking SDK, C++ integration, and Python bindings
- add item actions quick use/split/delete/lock overlay window (mt_item_actions)
- add shop name range and NPC name visibility settings to system display options
- support battlepass ticket activation and month validation in mt_battlepass
- support boss tracking teleport destination button in mt_bosses
- support asynchronous account registration cancellation and pending login cancellation
- update AVA2 item conversion for attr lock scrolls and battlepass tickets
- update .gitignore for local build outputs and caches
This commit is contained in:
shen
2026-10-16 01:29:57 -07:00
parent f266072f67
commit 507e687867
556 changed files with 213111 additions and 43 deletions
+65 -9
View File
@@ -11,6 +11,7 @@ import grp
import item
import net
import nonplayer
import player
import ui
import weakref
from mt_biolog import Action, Text, L
@@ -29,7 +30,8 @@ TYPES={1:('击杀怪物','击杀 ','mob'),2:('击杀 Boss','击杀 Boss ','mob')
9:(None,'向 NPC 出售 ','item')}
ERRORS={'loading':'数据加载中,请稍后再试','slot':'没有这个任务','claimed':'奖励已经领取过了','incomplete':'任务尚未完成',
'space':'背包空间不足,请整理后再领取','config':'奖励配置有误,请联系管理员','db':'服务器繁忙,请稍后再试',
'rate':'操作过快,请稍后再试','invalid':'参数无效'}
'rate':'操作过快,请稍后再试','invalid':'参数无效','ticket':'请使用当月战令票激活','month':'这张战令票不属于当前月份',
'activated':'本月战令已经激活过了','busy':'请先结束交易、商店、仓库或传送'}
def Number(text,low=0,high=10**12):
@@ -86,7 +88,7 @@ class Pass(object):
self.Clear()
def Clear(self):
self.season=None;self.missions={};self.final=None;self.reading=None;self.none=False;self.error=None;self.claiming=None
self.season=None;self.missions={};self.final=None;self.reading=None;self.none=False;self.error=None;self.claiming=None;self.access=None;self.month=None
def Ready(self):
return self.waiting is None or self.now()>=self.waiting
@@ -104,10 +106,15 @@ class Pass(object):
return self.Post('/mt_bp 1 info')
def Claim(self,slot):
if self.claiming is not None or not self.season:return False
if self.claiming is not None or not self.season or self.access not in (None,1):return False
if self.Post('/mt_bp 1 claim %d'%slot):self.claiming=slot;self.error=None;return True
return False
def Activate(self,cell,vnum):
if self.access!=0 or self.waiting is not None or not 25150<=vnum<=25161:return False
if player.GetItemIndex(cell)!=vnum:return False
return self.Post('/mt_bp 1 activate %d %d'%(cell,vnum))
def Close(self):
self.send('/mt_bp 1 close')
@@ -135,7 +142,10 @@ class Pass(object):
def Parse(self,kind,args):
if kind=='season' and len(args)==5:
seasonId,start,end,now=[Number(w,0,2**32-1) for w in args[:4]];count=Number(args[4],1,MAX_SLOT)
self.reading=dict(season=(seasonId,start,end,now,self.now()),count=count,missions={},final=None)
self.reading=dict(season=(seasonId,start,end,now,self.now()),count=count,missions={},final=None,access=None,month=None)
elif kind=='access' and len(args) in (1,2) and self.reading is not None:
self.reading['access']=Number(args[0],0,2)
if len(args)==2:self.reading['month']=Number(args[1],1,12)
elif kind=='mission' and self.reading is not None and len(args)>=6:
slot=Number(args[0],1,MAX_SLOT);kind=Number(args[1],1,9)
self.reading['missions'][slot]=dict(slot=slot,type=kind,sub=Number(args[2],0,2**32-1),target=Number(args[3],1),
@@ -145,7 +155,7 @@ class Pass(object):
elif kind=='end' and not args and self.reading is not None:
r=self.reading;self.reading=None
if len(r['missions'])!=r['count'] or r['final'] is None:return
self.season=r['season'];self.missions=r['missions'];self.final=r['final'];self.none=False
self.season=r['season'];self.missions=r['missions'];self.final=r['final'];self.access=r['access'];self.month=r['month'];self.none=False
self.Answered('info')
elif kind=='none' and not args:
self.Clear();self.none=True;self.Answered('none')
@@ -214,6 +224,8 @@ class BattlePassWindow(ui.Window):
self.finalState=Text(self,330,y+4,'')
self.finalReward=Text(self,18,y+26,'',0xffb3aa98)
self.finalButton=Action(self,WIDTH-118,y,100,'领取',lambda:self.state.Claim(0) and self.Refresh())
self.activateButton=Action(self,WIDTH-218,y,200,'使用当月战令票激活',self.AskActivate)
self.activateButton.Hide();self.confirm=None;self.pendingTicket=None
y=self.GetHeight()-56
self.prev=Action(self,18,y,80,'上一页',lambda:self.Turn(-1))
self.pageText=Text(self,140,y+24,'');self.pageText.SetHorizontalAlignCenter()
@@ -232,8 +244,43 @@ class BattlePassWindow(ui.Window):
def Hide(self):
state=self.__dict__.get('state') # ui.Window.__init__ hides the window before state is set
if self.IsShow() and state and state.season:state.Close()
self.pendingTicket=None
self.CloseConfirm()
ui.Window.Hide(self)
def CloseConfirm(self):
confirm=self.__dict__.get('confirm')
if confirm:
confirm.Close();confirm.SetAcceptEvent(None);confirm.SetCancelEvent(None);self.confirm=None
def OfferTicket(self,cell,vnum):
self.pendingTicket=(cell,vnum)
self.Open()
def AskTicket(self,cell,vnum):
if self.state.access!=0 or player.GetItemIndex(cell)!=vnum:return
item.SelectItem(vnum)
if item.GetValue(0)!=self.state.month:self.state.error='month';self.Refresh();return
import uiCommon
self.CloseConfirm();self.confirm=uiCommon.QuestionDialog()
self.confirm.SetText(L('消耗战令票并激活本月战令?'))
self.confirm.SetAcceptEvent(lambda:self.ConfirmActivate(cell,vnum))
self.confirm.SetCancelEvent(self.CloseConfirm);self.confirm.Open()
def AskActivate(self):
if self.state.access!=0:return
for cell in range(player.INVENTORY_PAGE_SIZE*player.INVENTORY_PAGE_COUNT):
vnum=player.GetItemIndex(cell)
if not 25150<=vnum<=25161:continue
item.SelectItem(vnum)
# Display uses server time, not the phone's local month/time zone.
if item.GetValue(0)!=self.state.month:continue
self.AskTicket(cell,vnum);return
self.state.error='ticket';self.Refresh()
def ConfirmActivate(self,cell,vnum):
self.CloseConfirm();self.state.Activate(cell,vnum);self.Refresh()
def Slots(self):
return sorted(self.state.missions)
@@ -264,7 +311,7 @@ class BattlePassWindow(ui.Window):
if m['claimed']:row['button'].SetText('已领取')
elif s.claiming==m['slot']:row['button'].SetText('领取中')
else:row['button'].SetText('领取' if done else '未完成')
row['button'].SetEnabled(done and not m['claimed'] and bool(m['rewards']) and s.claiming is None)
row['button'].SetEnabled(s.access in (None,1) and done and not m['claimed'] and bool(m['rewards']) and s.claiming is None)
f=s.final
for w in (self.finalName,self.finalState,self.finalReward,self.finalButton):
if f:w.Show()
@@ -274,7 +321,11 @@ class BattlePassWindow(ui.Window):
self.finalState.SetText(L('已领取' if f['claimed'] else '全部任务完成' if ready else '完成全部任务后可领取'))
self.finalReward.SetText(L('奖励:')+RewardText(f['rewards']))
self.finalButton.SetText('已领取' if f['claimed'] else '领取中' if s.claiming==0 else '领取')
self.finalButton.SetEnabled(ready and not f['claimed'] and s.claiming is None)
self.finalButton.SetEnabled(s.access in (None,1) and ready and not f['claimed'] and s.claiming is None)
if s.access==0:
self.finalButton.Hide();self.finalState.Hide();self.finalReward.Hide();self.activateButton.Show()
self.activateButton.SetEnabled(s.waiting is None)
else:self.activateButton.Hide()
many=self.Pages()>1
for w in (self.prev,self.next,self.pageText):
if many and s.season:w.Show()
@@ -288,6 +339,8 @@ class BattlePassWindow(ui.Window):
elif s.none:note='当前没有进行中的战令赛季'
elif not s.season:note='正在查询……'
elif s.claiming is not None:note='正在领取……'
elif s.access==0:note='激活后才开始累计进度'
elif s.access==2:note='本月战令已完成'
else:note='击杀、钓鱼、强化等都会自动计入进度'
self.note.SetText(L(note))
@@ -305,7 +358,7 @@ class BattlePassWindow(ui.Window):
def OnPressEscapeKey(self):self.Hide();return True
def Destroy(self):
self.Hide();self.closeButton.event=self.finalButton.event=self.prev.event=self.next.event=None
self.Hide();self.closeButton.event=self.finalButton.event=self.activateButton.event=self.prev.event=self.next.event=None
for row in self.rows:row['button'].event=None
self.state=None
@@ -327,7 +380,10 @@ def OnPass(kind,value):
chat.AppendChat(chat.CHAT_TYPE_INFO,L('战令任务完成:')+name+L(',按 F6 领取奖励'))
elif kind=='claimed':chat.AppendChat(chat.CHAT_TYPE_INFO,L('战令奖励已放入背包' if value else '战令最终奖励已放入背包'))
elif kind=='error':chat.AppendChat(chat.CHAT_TYPE_INFO,L(ERRORS[s.error]))
if g_window and g_window.IsShow():g_window.Refresh()
if g_window and g_window.IsShow():
if kind=='info' and g_window.pendingTicket:
ticket=g_window.pendingTicket;g_window.pendingTicket=None;g_window.AskTicket(*ticket)
g_window.Refresh()
g_pass=Pass(net.SendChatPacket,app.GetTime,OnPass)