Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8164c02709 |
@@ -0,0 +1,364 @@
|
||||
// PORT: battle pass (战令), from AVA2's uibattlepassex.py. The server runs monthly seasons of up to ten missions
|
||||
// counted from real events (kills, fishing, refines, levels, NPC trades, play time); each mission has a reward and
|
||||
// a final reward follows once all are done. This window asks with /mt_bp, shows progress live while open and
|
||||
// claims rewards. Entry points: F6 and a "战令" button by the minimap. Protocol: metin2-server
|
||||
// docs/FEATURE-BATTLEPASS.md.
|
||||
static const char c_szBattlePassScript[] = R"PY(# -*- coding: utf-8 -*-
|
||||
import app
|
||||
import chat
|
||||
import game
|
||||
import grp
|
||||
import item
|
||||
import net
|
||||
import nonplayer
|
||||
import ui
|
||||
import weakref
|
||||
from mt_biolog import Action, Text, L
|
||||
|
||||
g_window=None
|
||||
g_entry=None
|
||||
SEND_GAP=.4
|
||||
TIMEOUT=3.
|
||||
PAGE=5
|
||||
ROW=56
|
||||
WIDTH=620
|
||||
MAX_SLOT=10
|
||||
# type: (name without sub, name with a sub vnum: kind of the vnum)
|
||||
TYPES={1:('击杀怪物','击杀 ','mob'),2:('击杀 Boss','击杀 Boss ','mob'),3:('摧毁陨石','摧毁 ','mob'),4:('累计在线',None,None),
|
||||
5:('钓鱼','钓到 ','item'),6:('成功强化装备','强化出 ','item'),7:('提升等级',None,None),8:('在 NPC 商店消费金币',None,None),
|
||||
9:(None,'向 NPC 出售 ','item')}
|
||||
ERRORS={'loading':'数据加载中,请稍后再试','slot':'没有这个任务','claimed':'奖励已经领取过了','incomplete':'任务尚未完成',
|
||||
'space':'背包空间不足,请整理后再领取','config':'奖励配置有误,请联系管理员','db':'服务器繁忙,请稍后再试',
|
||||
'rate':'操作过快,请稍后再试','invalid':'参数无效'}
|
||||
|
||||
|
||||
def Number(text,low=0,high=10**12):
|
||||
if not text.isdigit() or len(text)>13:raise ValueError(text)
|
||||
value=int(text)
|
||||
if not low<=value<=high:raise ValueError(text)
|
||||
return value
|
||||
|
||||
|
||||
def Rewards(words):
|
||||
if len(words)%2:raise ValueError(words)
|
||||
return [(Number(words[i],1,2**32-1),Number(words[i+1],1,10000)) for i in range(0,len(words),2)]
|
||||
|
||||
|
||||
def Grouped(value):
|
||||
text=str(int(value));parts=[]
|
||||
while len(text)>3:parts.insert(0,text[-3:]);text=text[:-3]
|
||||
return ','.join([text]+parts)
|
||||
|
||||
|
||||
def Amount(kind,value,target):
|
||||
# Play time is counted in seconds and shown in hours, or in minutes for a target under an hour.
|
||||
if kind==4:
|
||||
unit=3600. if target>=3600 else 60.
|
||||
amount=value/unit
|
||||
return ('%d'%amount) if amount==int(amount) else ('%.1f'%amount)
|
||||
return Grouped(value)
|
||||
|
||||
|
||||
def Unit(kind,target):
|
||||
if kind!=4:return ''
|
||||
return ' 小时' if target>=3600 else ' 分钟'
|
||||
|
||||
|
||||
def SeasonName(season):
|
||||
year,month=divmod(season,100)
|
||||
if 2000<=year<=2999 and 1<=month<=12:return '%d 年 %d 月'%(year,month)
|
||||
return '第 %d 期'%season
|
||||
|
||||
|
||||
def Left(seconds):
|
||||
seconds=int(seconds)
|
||||
if seconds<=0:return '已结束'
|
||||
days,rest=divmod(seconds,86400);hours,rest=divmod(rest,3600)
|
||||
if days:return '剩余 %d 天 %d 小时'%(days,hours)
|
||||
return '剩余 %d 小时 %d 分'%(hours,max(1,rest//60))
|
||||
|
||||
|
||||
class Pass(object):
|
||||
# Protocol state only. send(text) posts a chat command, now() is seconds.
|
||||
def __init__(self,send,now,listener=None):
|
||||
self.send=send;self.now=now;self.listener=listener
|
||||
self.waiting=None;self.nextSend=0;self.unsupported=False
|
||||
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
|
||||
|
||||
def Ready(self):
|
||||
return self.waiting is None or self.now()>=self.waiting
|
||||
|
||||
def Post(self,text,expect=True):
|
||||
now=self.now()
|
||||
if now<self.nextSend:return False
|
||||
self.send(text);self.nextSend=now+SEND_GAP
|
||||
if expect:self.waiting=now+TIMEOUT
|
||||
return True
|
||||
|
||||
def Query(self):
|
||||
self.Tick()
|
||||
if self.waiting is not None:return False
|
||||
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.Post('/mt_bp 1 claim %d'%slot):self.claiming=slot;self.error=None;return True
|
||||
return False
|
||||
|
||||
def Close(self):
|
||||
self.send('/mt_bp 1 close')
|
||||
|
||||
def Tick(self):
|
||||
if self.waiting is not None and self.now()>=self.waiting:
|
||||
self.waiting=None;self.claiming=None
|
||||
if self.season is None:self.unsupported=True
|
||||
self.Notify('timeout')
|
||||
|
||||
def SecondsLeft(self):
|
||||
if not self.season:return 0
|
||||
_,_,end,now,at=self.season
|
||||
return end-(now+(self.now()-at))
|
||||
|
||||
def Notify(self,kind,value=None):
|
||||
if self.listener:self.listener(kind,value)
|
||||
|
||||
def Receive(self,line):
|
||||
if not line.startswith('MTBP1 '):return False
|
||||
words=line.split()
|
||||
try:self.Parse(words[1],words[2:])
|
||||
except (ValueError,IndexError):self.reading=None
|
||||
return True
|
||||
|
||||
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)
|
||||
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),
|
||||
value=Number(args[4]),claimed=Number(args[5],0,1)==1,rewards=Rewards(args[6:]))
|
||||
elif kind=='final' and self.reading is not None and len(args)>=4:
|
||||
self.reading['final']=dict(claimed=Number(args[0],0,1)==1,all=Number(args[1],0,1)==1,rewards=Rewards(args[2:]))
|
||||
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.Answered('info')
|
||||
elif kind=='none' and not args:
|
||||
self.Clear();self.none=True;self.Answered('none')
|
||||
elif kind=='progress' and len(args)==2:
|
||||
slot=Number(args[0],1,MAX_SLOT);m=self.missions.get(slot)
|
||||
if m:m['value']=min(m['target'],Number(args[1]));self.Notify('progress',slot)
|
||||
elif kind=='done' and len(args)==1:
|
||||
slot=Number(args[0],1,MAX_SLOT);m=self.missions.get(slot)
|
||||
if m:m['value']=m['target']
|
||||
self.Notify('done',slot)
|
||||
elif kind=='claimed' and len(args)==1:
|
||||
slot=Number(args[0],0,MAX_SLOT)
|
||||
if slot:
|
||||
if slot in self.missions:self.missions[slot]['claimed']=True
|
||||
elif self.final:self.final['claimed']=True
|
||||
self.claiming=None;self.Notify('claimed',slot) # a full info follows
|
||||
elif kind=='error' and len(args)==1 and args[0] in ERRORS:
|
||||
self.error=args[0];self.claiming=None;self.Answered('error')
|
||||
|
||||
def Answered(self,kind):
|
||||
self.waiting=None;self.unsupported=False;self.Notify(kind)
|
||||
|
||||
def AllDone(self):
|
||||
return bool(self.missions) and all(m['value']>=m['target'] for m in self.missions.values())
|
||||
|
||||
|
||||
def ItemName(vnum):
|
||||
try:
|
||||
item.SelectItem(vnum);name=item.GetItemName()
|
||||
except Exception:name=''
|
||||
return name if name else L('物品 %d'%vnum)
|
||||
|
||||
|
||||
def MobName(vnum):
|
||||
try:name=nonplayer.GetMonsterName(vnum)
|
||||
except Exception:name=''
|
||||
return name if name else L('怪物 %d'%vnum)
|
||||
|
||||
|
||||
def MissionName(m):
|
||||
# Locale text: the utf-8 parts go through L, game names are already in the locale's code page.
|
||||
plain,prefix,kind=TYPES.get(m['type'],('任务',None,None))
|
||||
if m['sub'] and prefix:return L(prefix)+(MobName(m['sub']) if kind=='mob' else ItemName(m['sub']))
|
||||
return L(plain or '任务')
|
||||
|
||||
|
||||
def RewardText(rewards):
|
||||
return L(',').join([ItemName(vnum)+(' x%d'%count) for vnum,count in rewards])
|
||||
|
||||
|
||||
class BattlePassWindow(ui.Window):
|
||||
def __init__(self,state):
|
||||
ui.Window.__init__(self);self.SetWindowName('BattlePassWindow');self.AddFlag('float');self.AddFlag('movable')
|
||||
self.state=state;self.page=0
|
||||
self.SetSize(WIDTH,74+PAGE*ROW+ROW+20+56)
|
||||
self.heading=Text(self,18,18,'战令',0xffffd68b)
|
||||
self.season=Text(self,80,18,'',0xffc3b7a1)
|
||||
self.closeButton=Action(self,WIDTH-76,6,64,'关闭',self.Hide)
|
||||
self.rows=[]
|
||||
for i in range(PAGE):
|
||||
y=64+i*ROW
|
||||
self.rows.append(dict(name=Text(self,18,y+4,''),value=Text(self,330,y+4,''),
|
||||
reward=Text(self,18,y+26,'',0xffb3aa98),button=Action(self,WIDTH-118,y,100,'领取',lambda i=i:self.ClaimRow(i))))
|
||||
y=64+PAGE*ROW+8
|
||||
self.finalName=Text(self,18,y+4,'最终奖励',0xffffd68b)
|
||||
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())
|
||||
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()
|
||||
self.next=Action(self,182,y,80,'下一页',lambda:self.Turn(1))
|
||||
self.note=Text(self,280,y+16,'',0xffc3b7a1)
|
||||
self.second=-1
|
||||
self.Hide()
|
||||
|
||||
def Open(self):
|
||||
self.state.error=None;self.state.Query();self.Refresh();self.SetCenterPosition();ui.Window.Show(self);self.SetTop()
|
||||
|
||||
def Toggle(self):
|
||||
if self.IsShow():self.Hide()
|
||||
else:self.Open()
|
||||
|
||||
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()
|
||||
ui.Window.Hide(self)
|
||||
|
||||
def Slots(self):
|
||||
return sorted(self.state.missions)
|
||||
|
||||
def Pages(self):
|
||||
return max(1,(len(self.Slots())+PAGE-1)//PAGE)
|
||||
|
||||
def Turn(self,delta):
|
||||
self.page=max(0,min(self.Pages()-1,self.page+delta));self.Refresh()
|
||||
|
||||
def ClaimRow(self,i):
|
||||
slots=self.Slots()[self.page*PAGE:]
|
||||
if i<len(slots) and self.state.Claim(slots[i]):self.Refresh()
|
||||
|
||||
def Refresh(self):
|
||||
s=self.state;self.page=min(self.page,self.Pages()-1)
|
||||
slots=self.Slots()[self.page*PAGE:(self.page+1)*PAGE]
|
||||
for i,row in enumerate(self.rows):
|
||||
widgets=(row['name'],row['value'],row['reward'],row['button'])
|
||||
if i>=len(slots):
|
||||
for w in widgets:w.Hide()
|
||||
continue
|
||||
for w in widgets:w.Show()
|
||||
m=s.missions[slots[i]];done=m['value']>=m['target']
|
||||
row['name'].SetText(MissionName(m))
|
||||
row['value'].SetText(Amount(m['type'],m['value'],m['target'])+' / '+Amount(m['type'],m['target'],m['target'])+L(Unit(m['type'],m['target'])))
|
||||
row['value'].SetPackedFontColor(0xff9fe08b if done else 0xffe8dec8)
|
||||
row['reward'].SetText(L('奖励:')+RewardText(m['rewards']) if m['rewards'] else L('无奖励'))
|
||||
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)
|
||||
f=s.final
|
||||
for w in (self.finalName,self.finalState,self.finalReward,self.finalButton):
|
||||
if f:w.Show()
|
||||
else:w.Hide()
|
||||
if f:
|
||||
ready=s.AllDone()
|
||||
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)
|
||||
many=self.Pages()>1
|
||||
for w in (self.prev,self.next,self.pageText):
|
||||
if many and s.season:w.Show()
|
||||
else:w.Hide()
|
||||
self.prev.SetEnabled(self.page>0);self.next.SetEnabled(self.page<self.Pages()-1)
|
||||
self.pageText.SetText('%d / %d'%(self.page+1,self.Pages()))
|
||||
if s.season:self.season.SetText(L(SeasonName(s.season[0])+' '+Left(s.SecondsLeft())))
|
||||
else:self.season.SetText('')
|
||||
if s.unsupported:note='!服务器尚未启用战令'
|
||||
elif s.error:note='!'+ERRORS[s.error]
|
||||
elif s.none:note='当前没有进行中的战令赛季'
|
||||
elif not s.season:note='正在查询……'
|
||||
elif s.claiming is not None:note='正在领取……'
|
||||
else:note='击杀、钓鱼、强化等都会自动计入进度'
|
||||
self.note.SetText(L(note))
|
||||
|
||||
def OnUpdate(self):
|
||||
self.state.Tick()
|
||||
second=int(app.GetTime())
|
||||
if second!=self.second:self.second=second;self.Refresh()
|
||||
|
||||
def OnRender(self):
|
||||
x,y=self.GetGlobalPosition();w=self.GetWidth()
|
||||
grp.SetColor(0xf51b1916);grp.RenderBar(x,y,w,self.GetHeight());grp.SetColor(0xff9b7b48);grp.RenderBox(x,y,w,self.GetHeight())
|
||||
grp.SetColor(0xff4a3d2a)
|
||||
for i in range(PAGE+1):grp.RenderLine(x+12,y+60+i*ROW,w-24,0)
|
||||
|
||||
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
|
||||
for row in self.rows:row['button'].event=None
|
||||
self.state=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('BattlePassEntry');self.OnUpdate()
|
||||
|
||||
def OnUpdate(self):
|
||||
x,y=self.minimap.GetGlobalPosition();self.SetPosition(max(4,x-68),y+460)
|
||||
|
||||
|
||||
def OnPass(kind,value):
|
||||
s=g_pass
|
||||
if kind=='done':
|
||||
m=s.missions.get(value)
|
||||
name=MissionName(m) if m else L('一项任务')
|
||||
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()
|
||||
|
||||
|
||||
g_pass=Pass(net.SendChatPacket,app.GetTime,OnPass)
|
||||
|
||||
|
||||
def Toggle():
|
||||
if g_window:g_window.Toggle()
|
||||
|
||||
|
||||
_open=game.GameWindow.Open
|
||||
_close=game.GameWindow.Close
|
||||
_command=game.GameWindow.BINARY_ServerCommand_Run
|
||||
|
||||
def Open(self):
|
||||
global g_window,g_entry
|
||||
_open(self);g_pass.waiting=None;g_pass.unsupported=False;g_pass.Clear()
|
||||
g_window=BattlePassWindow(g_pass);g_entry=Entry(self)
|
||||
keys=getattr(self,'onPressKeyDict',None)
|
||||
if keys is not None and app.DIK_F6 not in keys:keys[app.DIK_F6]=lambda:Toggle()
|
||||
|
||||
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)
|
||||
|
||||
def Command(self,line):
|
||||
if g_pass.Receive(line):return 1
|
||||
return _command(self,line)
|
||||
|
||||
game.GameWindow.Open=Open
|
||||
game.GameWindow.Close=Close
|
||||
game.GameWindow.BINARY_ServerCommand_Run=Command
|
||||
)PY";
|
||||
@@ -1010,6 +1010,7 @@ namespace PythonBoot
|
||||
#include "BossScript.inc"
|
||||
#include "VipScript.inc"
|
||||
#include "ModelPreviewScript.inc"
|
||||
#include "BattlePassScript.inc"
|
||||
|
||||
static void install_script_module(const char* c_szModule, const char* c_szScript)
|
||||
{
|
||||
@@ -1091,6 +1092,7 @@ bool RunMainScript(const char* lpCmdLine, std::string* error)
|
||||
install_script_module("mt_boss", c_szBossScript);
|
||||
install_script_module("mt_vip", c_szVipScript);
|
||||
install_script_module("mt_preview", c_szModelPreviewScript);
|
||||
install_script_module("mt_battlepass", c_szBattlePassScript);
|
||||
if (g_touch_input)
|
||||
install_script_module("mt_mobile_hud", c_szMobileHUDScript);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT=Path(__file__).resolve().parents[2]
|
||||
|
||||
def script(name):return (ROOT/'src/platform/ScriptLib'/name).read_text().split('R"PY(',1)[1].rsplit(')PY";',1)[0]
|
||||
|
||||
|
||||
class Widget(object):
|
||||
def __init__(self,*a,**k):
|
||||
self.Hide()
|
||||
self.shown=False;self.text='';self.pos=(0,0);self.size=(0,0);self.enabled=True
|
||||
def __getattr__(self,name):return lambda *a,**k:None
|
||||
def Show(self):self.shown=True
|
||||
def Hide(self):self.shown=False
|
||||
def IsShow(self):return self.shown
|
||||
def SetText(self,text):self.text=text
|
||||
def SetPosition(self,x,y):self.pos=(x,y)
|
||||
def GetGlobalPosition(self):return self.pos
|
||||
def SetSize(self,w,h):self.size=(w,h)
|
||||
def GetWidth(self):return self.size[0]
|
||||
def GetHeight(self):return self.size[1]
|
||||
|
||||
|
||||
class Action(Widget):
|
||||
def __init__(self,parent,x,y,width,text,event):
|
||||
Widget.__init__(self);self.pos=(x,y);self.event=event;self.text=text;self.shown=True
|
||||
def SetEnabled(self,enabled):self.enabled=enabled
|
||||
|
||||
|
||||
NAMES={70043:'夺宝手套',71153:'经验药水',27992:'白珍珠',27987:'珍珠蚌'}
|
||||
INFO=['MTBP1 season 202610 1790805600 1793487600 1790900000 6',
|
||||
'MTBP1 mission 1 1 0 20000 150 0 70043 1 71153 5',
|
||||
'MTBP1 mission 2 2 0 30 30 0 25041 2',
|
||||
'MTBP1 mission 3 3 0 300 300 1 50513 3',
|
||||
'MTBP1 mission 4 4 0 172800 5400 0 71094 2',
|
||||
'MTBP1 mission 5 5 27987 100 3 0 27987 5',
|
||||
'MTBP1 mission 9 9 27992 10 0 0',
|
||||
'MTBP1 final 0 0 27992 2 27993 1',
|
||||
'MTBP1 end']
|
||||
|
||||
|
||||
class BattlePassTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.chat=[];self.sent=[];self.clock=100.
|
||||
names=('app','chat','game','grp','item','net','nonplayer','ui','mt_biolog')
|
||||
mods={n:types.ModuleType(n) for n in names}
|
||||
app=mods['app'];app.GetTime=lambda:self.clock;app.DIK_F6=64
|
||||
mods['chat'].CHAT_TYPE_INFO=1;mods['chat'].AppendChat=lambda t,m:self.chat.append(m)
|
||||
mods['game'].GameWindow=type('GameWindow',(),dict(Open=lambda s:None,Close=lambda s:None,BINARY_ServerCommand_Run=lambda s,l:'old'))
|
||||
grp=mods['grp'];grp.SetColor=grp.RenderBar=grp.RenderBox=grp.RenderLine=lambda *a:None
|
||||
self.selected=0
|
||||
def select(v):self.selected=v
|
||||
mods['item'].SelectItem=select;mods['item'].GetItemName=lambda:NAMES.get(self.selected,'')
|
||||
mods['net'].SendChatPacket=self.sent.append
|
||||
mods['nonplayer'].GetMonsterName=lambda v:{691:'狼王'}.get(v,'')
|
||||
mods['ui'].Window=Widget
|
||||
mods['mt_biolog'].Action=Action;mods['mt_biolog'].L=lambda t:t
|
||||
mods['mt_biolog'].Text=lambda p,x,y,t='',c=0:self.text(t)
|
||||
self.previous={n:sys.modules.get(n) for n in mods}
|
||||
sys.modules.update(mods)
|
||||
self.ns={'__name__':'mt_battlepass'};exec(script('BattlePassScript.inc'),self.ns)
|
||||
self.game=mods['game'];self.gw=self.game.GameWindow()
|
||||
self.gw.onPressKeyDict={};self.gw.interface=types.SimpleNamespace(wndMiniMap=Widget())
|
||||
self.game.GameWindow.Open(self.gw)
|
||||
self.w=self.ns['g_window'];self.p=self.ns['g_pass']
|
||||
|
||||
def text(self,t):
|
||||
w=Widget();w.text=t;w.shown=True;return w
|
||||
|
||||
def tearDown(self):
|
||||
for name,value in self.previous.items():
|
||||
if value is None:sys.modules.pop(name,None)
|
||||
else:sys.modules[name]=value
|
||||
|
||||
def feed(self,*lines):
|
||||
for line in lines:self.assertEqual(self.game.GameWindow.BINARY_ServerCommand_Run(self.gw,line),1)
|
||||
|
||||
def open(self):
|
||||
self.gw.onPressKeyDict[64]();self.assertTrue(self.w.IsShow());self.assertEqual(self.sent[-1],'/mt_bp 1 info')
|
||||
self.feed(*INFO)
|
||||
|
||||
def test_other_commands_pass_through(self):
|
||||
self.assertEqual(self.game.GameWindow.BINARY_ServerCommand_Run(self.gw,'MTVIP1 times 0 0 0 0 0 0 0'),'old')
|
||||
self.assertIn(64,self.gw.onPressKeyDict)
|
||||
|
||||
def test_info_and_rows(self):
|
||||
self.open();rows=self.w.rows
|
||||
self.assertEqual(sorted(self.p.missions),[1,2,3,4,5,9]);self.assertEqual(self.w.Pages(),2)
|
||||
self.assertEqual(rows[0]['name'].text,'击杀怪物');self.assertEqual(rows[0]['value'].text,'150 / 20,000')
|
||||
self.assertEqual(rows[0]['reward'].text,'奖励:夺宝手套 x1,经验药水 x5')
|
||||
self.assertFalse(rows[0]['button'].enabled);self.assertEqual(rows[0]['button'].text,'未完成')
|
||||
self.assertTrue(rows[1]['button'].enabled);self.assertEqual(rows[1]['button'].text,'领取')
|
||||
self.assertEqual(rows[2]['button'].text,'已领取');self.assertFalse(rows[2]['button'].enabled)
|
||||
self.assertEqual(rows[3]['value'].text,'1.5 / 48 小时')
|
||||
self.assertEqual(rows[4]['name'].text,'钓到 珍珠蚌')
|
||||
self.assertIn('2026 年 10 月',self.w.season.text);self.assertIn('剩余 29 天 22 小时',self.w.season.text)
|
||||
self.w.next.event();self.assertEqual(self.w.page,1)
|
||||
self.assertEqual(rows[0]['name'].text,'向 NPC 出售 白珍珠');self.assertEqual(rows[0]['reward'].text,'无奖励')
|
||||
self.assertFalse(rows[0]['button'].enabled);self.assertFalse(rows[1]['name'].shown)
|
||||
self.assertFalse(self.w.finalButton.enabled);self.assertEqual(self.w.finalState.text,'完成全部任务后可领取')
|
||||
|
||||
def test_progress_done_and_claim(self):
|
||||
self.open()
|
||||
self.feed('MTBP1 progress 1 151');self.assertEqual(self.w.rows[0]['value'].text,'151 / 20,000')
|
||||
self.feed('MTBP1 done 1');self.assertEqual(self.chat[-1],'战令任务完成:击杀怪物,按 F6 领取奖励')
|
||||
self.assertTrue(self.w.rows[0]['button'].enabled)
|
||||
self.clock+=1;self.w.rows[0]['button'].event();self.assertEqual(self.sent[-1],'/mt_bp 1 claim 1')
|
||||
self.assertEqual(self.w.rows[0]['button'].text,'领取中');self.assertFalse(self.w.rows[1]['button'].enabled)
|
||||
self.w.rows[1]['button'].event();self.assertEqual(self.sent[-1],'/mt_bp 1 claim 1') # one claim at a time
|
||||
self.feed('MTBP1 claimed 1');self.assertEqual(self.chat[-1],'战令奖励已放入背包')
|
||||
self.assertEqual(self.w.rows[0]['button'].text,'已领取')
|
||||
self.feed(*INFO) # the server sends the full state after a claim
|
||||
self.clock+=1;self.w.rows[1]['button'].event();self.feed('MTBP1 error space')
|
||||
self.assertEqual(self.chat[-1],'背包空间不足,请整理后再领取');self.assertIn('背包空间不足',self.w.note.text)
|
||||
self.assertTrue(self.w.rows[1]['button'].enabled)
|
||||
|
||||
def test_final(self):
|
||||
self.open()
|
||||
for slot in (1,4,5,9):self.feed('MTBP1 done %d'%slot)
|
||||
self.assertTrue(self.p.AllDone());self.w.Refresh()
|
||||
self.assertTrue(self.w.finalButton.enabled);self.assertEqual(self.w.finalState.text,'全部任务完成')
|
||||
self.clock+=1;self.w.finalButton.event();self.assertEqual(self.sent[-1],'/mt_bp 1 claim 0')
|
||||
self.feed('MTBP1 claimed 0');self.assertEqual(self.chat[-1],'战令最终奖励已放入背包')
|
||||
self.assertEqual(self.w.finalButton.text,'已领取');self.assertFalse(self.w.finalButton.enabled)
|
||||
|
||||
def test_close_none_and_timeout(self):
|
||||
self.open();self.w.OnPressEscapeKey();self.assertEqual(self.sent[-1],'/mt_bp 1 close');self.assertFalse(self.w.IsShow())
|
||||
self.feed('MTBP1 done 2');self.assertEqual(self.chat[-1],'战令任务完成:击杀 Boss,按 F6 领取奖励')
|
||||
self.clock+=1;self.w.Open();self.feed('MTBP1 none')
|
||||
self.assertIsNone(self.p.season);self.assertEqual(self.w.note.text,'当前没有进行中的战令赛季')
|
||||
self.assertFalse(self.w.rows[0]['name'].shown);self.assertFalse(self.w.finalButton.shown)
|
||||
self.w.Hide();self.assertEqual(self.sent[-1],'/mt_bp 1 info') # no season, nothing to close
|
||||
self.clock+=1;self.w.Open();self.clock+=3.5;self.w.OnUpdate()
|
||||
self.assertTrue(self.p.unsupported);self.assertEqual(self.w.note.text,'!服务器尚未启用战令')
|
||||
|
||||
def test_bad_lines_are_dropped(self):
|
||||
self.feed('MTBP1 season 1 2 3 4 0','MTBP1 end');self.assertIsNone(self.p.season)
|
||||
self.feed(INFO[0],INFO[1],'MTBP1 end');self.assertIsNone(self.p.season) # count mismatch
|
||||
self.feed(INFO[0],'MTBP1 mission 1 1 0 x 0 0',*INFO[2:]);self.assertIsNone(self.p.season)
|
||||
self.feed('MTBP1 mission 1 1 0 5 0 0','MTBP1 end');self.assertIsNone(self.p.season) # no season line
|
||||
self.feed('MTBP1 progress 99 1','MTBP1 error nope','MTBP1 bogus')
|
||||
self.feed(*INFO);self.assertEqual(self.p.season[0],202610)
|
||||
self.assertEqual(self.ns['Grouped'](50000000),'50,000,000');self.assertEqual(self.ns['Amount'](4,45,90),'0.8')
|
||||
self.assertEqual(self.ns['Unit'](4,90),' 分钟');self.assertEqual(self.ns['Amount'](4,7200,172800),'2');self.assertEqual(self.ns['SeasonName'](7),'第 7 期')
|
||||
|
||||
def test_registered(self):
|
||||
boot=(ROOT/'src/platform/ScriptLib/PythonBoot.cpp').read_text()
|
||||
self.assertIn('#include "BattlePassScript.inc"',boot)
|
||||
self.assertLess(boot.index('install_script_module("mt_preview"'),boot.index('install_script_module("mt_battlepass"'))
|
||||
self.game.GameWindow.Close(self.gw);self.assertIsNone(self.ns['g_window'])
|
||||
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
Reference in New Issue
Block a user