Files
metin2-client/tests/wiki/test_battlepass.py
T
shenleiandClaude Opus 5.5 8164c02709 Add the battle pass window (mt_battlepass)
战令 window opened by the 战令 button under the minimap or F6: season and
time left, missions with progress and per-mission claim, final reward,
done notices in chat. Talks to the server over /mt_bp 1 / MTBP1 lines and
reports an unsupported server after 3 s without an answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 09:11:43 +09:00

157 lines
8.9 KiB
Python

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()