- 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
195 lines
9.5 KiB
PHP
195 lines
9.5 KiB
PHP
// PORT: dungeon information from installed quests; original NPC admission remains authoritative.
|
|
static const char c_szDungeonScript[] = R"PY(# -*- coding: utf-8 -*-
|
|
import app
|
|
import chat
|
|
import game
|
|
import grp
|
|
import ui
|
|
import wndMgr
|
|
import mt_feature_service
|
|
import mt_wiki
|
|
from mt_wiki import B
|
|
from mt_biolog import Action, Text, L
|
|
|
|
g_window=None
|
|
|
|
|
|
def Duration(seconds):
|
|
seconds=max(0,int(seconds))
|
|
return '%d天 %02d:%02d:%02d'%(seconds//86400,(seconds//3600)%24,(seconds//60)%60,seconds%60) if seconds>=86400 else '%02d:%02d:%02d'%(seconds//3600,(seconds//60)%60,seconds%60)
|
|
|
|
|
|
class DungeonWindow(ui.Window):
|
|
def __init__(self):
|
|
ui.Window.__init__(self);self.SetWindowName('DungeonInformationWindow');self.AddFlag('float');self.AddFlag('movable')
|
|
self.values=None;self.pending=False;self.generation=0;self.received=0;self.serverTime=0;self.nextQuery=0
|
|
self.lastTick=app.GetTime();self.reminder=None;self.tab=0;self.dimensions=None;self.stale=True
|
|
self.heading=Text(self,18,18,'副本信息',0xffffd68b)
|
|
self.closeButton=Action(self,0,6,60,'关闭',self.Hide)
|
|
self.lairButton=Action(self,18,52,150,'龙穴',lambda:self.Select(0))
|
|
self.weeklyButton=Action(self,180,52,150,'龙穴周任务',lambda:self.Select(1))
|
|
self.refreshButton=Action(self,342,52,80,'刷新',self.Query)
|
|
self.status=Text(self,18,108,'正在读取任务状态')
|
|
self.rows=[Text(self,24,142+32*i,'',0xffefdfc3) for i in range(6)]
|
|
self.remindButton=Action(self,18,0,146,'到期提醒:关',self.Remind)
|
|
self.entryButton=Action(self,180,0,146,'查看入口',self.Entry)
|
|
self.travelButton=Action(self,342,0,146,'传送到入口',self.Travel)
|
|
self.note=Text(self,18,0,'当前仅接入已核实的龙穴及相关周任务。',0xffb2aa9a)
|
|
self.Reflow();self.Hide()
|
|
|
|
def Open(self):
|
|
if mt_wiki.g_window:mt_wiki.g_window.Load()
|
|
self.Reflow();self.SetCenterPosition();self.Show();self.SetTop();self.Query()
|
|
|
|
def Select(self,tab):
|
|
self.tab=tab;self.RenderRows()
|
|
|
|
def Query(self):
|
|
if self.pending:return
|
|
self.pending=True;self.generation+=1;generation=self.generation
|
|
self.status.SetText(L('正在同步服务器任务…'));self.refreshButton.SetEnabled(False)
|
|
def received(data,error):
|
|
if generation!=self.generation:return
|
|
self.pending=False;self.refreshButton.SetEnabled(True);self.nextQuery=app.GetTime()+15
|
|
if error:
|
|
self.stale=True;self.values=None
|
|
if error=='unsupported':self.reminder=None
|
|
self.status.SetText(L('服务端未启用此副本查询' if error=='unsupported' else '状态未同步,请稍后刷新'))
|
|
else:
|
|
self.values=dict(data['rows']);self.received=app.GetTime();self.serverTime=data['time'];self.stale=False
|
|
self.status.SetText(L('已同步任务状态;入场仍由原 NPC 检查'))
|
|
self.CheckReminder()
|
|
self.RenderRows()
|
|
if mt_feature_service.g_service:mt_feature_service.g_service.Request('dungeons',0,0,received)
|
|
else:received(None,'unsupported')
|
|
|
|
def Now(self):
|
|
return self.serverTime+max(0,app.GetTime()-self.received)
|
|
|
|
def RenderRows(self):
|
|
lines=[];v=self.values
|
|
if v is not None and not self.stale:
|
|
now=self.Now()
|
|
if self.tab==0:
|
|
if now<=v['lair_join_end']:state='队伍加入窗口(需要口令)'
|
|
elif now<=v['lair_end']:state='场次占用中'
|
|
else:state='可向入口 NPC 申请新场次'
|
|
catalogue=mt_wiki.g_window.catalogue if mt_wiki.g_window else None
|
|
ticket=catalogue.FindItem(v['lair_ticket']) if catalogue else None
|
|
name=B(ticket['name']) if ticket else '门票'
|
|
lines=['状态:'+state,'新场次等待:'+Duration(v['lair_end']+1-now),
|
|
'加入窗口:'+Duration(v['lair_join_end']+1-now),
|
|
'%s:需要 %d,持有 %d'%(name,v['lair_tickets'],v['lair_owned']),
|
|
'场次为全服共享;不是个人通关冷却。','入场口令、门票及 NPC 锁由原任务校验。']
|
|
elif 'weekly_end' in v:
|
|
lines=['个人周任务等待:'+Duration(v['weekly_end']+1-now),
|
|
'当前任务:'+('收集中' if v.get('weekly_active') else '未在收集阶段'),
|
|
'收集剩余:'+(Duration(v['weekly_hunt_end']-now) if v.get('weekly_active') else '—'),
|
|
'周任务与龙穴入场计时独立。','接取时开始计时,完成后不会重置冷却。','资格、收集材料和奖励由原任务检查。']
|
|
else:lines=['服务端未安装或未提供周任务查询。']
|
|
for i,row in enumerate(self.rows):
|
|
if i<len(lines):row.SetText(L(lines[i]));row.Show()
|
|
else:row.Hide()
|
|
self.remindButton.SetText('到期提醒:'+('开' if self.reminder is not None and self.reminder[0]==self.tab else '关'))
|
|
self.remindButton.SetEnabled(v is not None and not self.stale and (self.tab==0 or 'weekly_end' in v))
|
|
self.entryButton.SetEnabled(v is not None and not self.stale and (self.tab==0 or 'weekly_npc' in v))
|
|
service=mt_feature_service.g_service
|
|
if self.tab==0 and service and service.mask is not None and service.mask&512:self.travelButton.Show()
|
|
else:self.travelButton.Hide()
|
|
self.travelButton.SetEnabled(v is not None and not self.stale)
|
|
|
|
def Travel(self):
|
|
if self.values is None or self.stale or self.tab!=0:return
|
|
import mt_boss
|
|
mt_boss.Travel(2493)
|
|
|
|
def Entry(self):
|
|
if self.values is None or self.stale:return
|
|
v=self.values
|
|
text='请前往流放者洞窟深处,与修罗之魂对话进入龙穴。' if self.tab==0 else '请前往流放者洞窟二层,与猛将之魂对话接取周任务。'
|
|
self.status.SetText(L(text))
|
|
|
|
def Remind(self):
|
|
if self.values is None or self.stale:return
|
|
if self.reminder is not None and self.reminder[0]==self.tab:self.reminder=None
|
|
else:
|
|
end=self.values.get('lair_end' if self.tab==0 else 'weekly_end',0)
|
|
if end<self.Now():return
|
|
self.reminder=(self.tab,end)
|
|
self.RenderRows()
|
|
|
|
def CheckReminder(self):
|
|
if self.reminder is None or self.values is None or self.stale:return
|
|
tab,old=self.reminder;key='lair_end' if tab==0 else 'weekly_end'
|
|
if key not in self.values:self.reminder=None;return
|
|
end=self.values[key]
|
|
# A later run/quest extends the authoritative deadline; never announce the old one.
|
|
self.reminder=(tab,end)
|
|
if self.Now()>end:
|
|
chat.AppendChat(chat.CHAT_TYPE_INFO,L('龙穴新场次等待已结束,请向入口申请。' if tab==0 else '龙穴周任务等待已结束,请向 NPC 确认资格。'))
|
|
self.reminder=None
|
|
|
|
def Reflow(self):
|
|
dims=(wndMgr.GetScreenWidth(),wndMgr.GetScreenHeight())
|
|
if dims==self.dimensions:return
|
|
self.dimensions=dims;width=min(660,max(500,dims[0]-16));height=min(450,max(300,dims[1]-16));self.SetSize(width,height)
|
|
self.closeButton.SetPosition(width-76,6)
|
|
self.travelButton.SetPosition(342,height-74)
|
|
spacing=min(32,max(16,(height-250)//5))
|
|
for i,row in enumerate(self.rows):row.SetPosition(24,132+i*spacing)
|
|
self.remindButton.SetPosition(18,height-74);self.entryButton.SetPosition(180,height-74);self.note.SetPosition(18,height-18)
|
|
self.SetCenterPosition()
|
|
|
|
def OnUpdate(self):
|
|
now=app.GetTime();gap=now-self.lastTick;self.lastTick=now
|
|
if gap>5 or gap<0:self.stale=True;self.nextQuery=0
|
|
active=self.IsShow() or self.reminder is not None
|
|
if active and not self.pending and now>=self.nextQuery and (self.values is not None or self.reminder is not None or self.stale and self.nextQuery==0):self.Query()
|
|
if self.IsShow():self.Reflow();self.RenderRows()
|
|
# Reminders only fire after a fresh server query confirms expiry.
|
|
if self.reminder is not None and not self.pending and self.values is not None and self.Now()>self.reminder[1] and now>=self.nextQuery:self.Query()
|
|
|
|
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())
|
|
|
|
def Hide(self):
|
|
ui.Window.Hide(self)
|
|
|
|
def OnPressEscapeKey(self):self.Hide();return True
|
|
|
|
def Destroy(self):
|
|
self.generation+=1;self.reminder=None;self.values=None;self.Hide()
|
|
for b in (self.closeButton,self.lairButton,self.weeklyButton,self.refreshButton,self.remindButton,self.entryButton,self.travelButton):b.event=None
|
|
|
|
|
|
class ReminderTick(ui.Window):
|
|
def __init__(self):
|
|
ui.Window.__init__(self);self.AddFlag('not_pick');self.SetSize(0,0);self.Show()
|
|
def OnUpdate(self):
|
|
if g_window and not g_window.IsShow():g_window.OnUpdate()
|
|
|
|
|
|
def Toggle():
|
|
if g_window:
|
|
if g_window.IsShow():g_window.Hide()
|
|
else:g_window.Open()
|
|
|
|
|
|
_open=game.GameWindow.Open
|
|
_close=game.GameWindow.Close
|
|
g_tick=None
|
|
|
|
def Open(self):
|
|
global g_window,g_tick
|
|
_open(self);g_window=DungeonWindow();g_tick=ReminderTick()
|
|
|
|
def Close(self):
|
|
global g_window,g_tick
|
|
if g_tick:g_tick.Hide();g_tick=None
|
|
if g_window:g_window.Destroy();g_window=None
|
|
_close(self)
|
|
|
|
game.GameWindow.Open=Open
|
|
game.GameWindow.Close=Close
|
|
)PY";
|