A tree close to the camera is drawn by RenderPCBlocker, not RenderTree, so background.SetVisiblePart(PART_TREE, 0) left it on screen. Skip tree blockers while PART_TREE is hidden. Live screenshots on the 203 test core confirm the tree, the GM mark and the weapon glow now disappear. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
5.0 KiB
Python
95 lines
5.0 KiB
Python
import os
|
|
import re
|
|
import sys
|
|
import tempfile
|
|
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.shown=False;self.text='';self.pos=(0,0);self.size=(0,0)
|
|
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
|
|
|
|
|
|
class DisplayTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.cwd=os.getcwd();self.tmp=tempfile.TemporaryDirectory();os.chdir(self.tmp.name)
|
|
self.state=dict(effect=None,name=True,damage=True,parts={});self.opened=[]
|
|
mods={n:types.ModuleType(n) for n in ('background','game','grp','systemSetting','ui','mt_biolog','mt_wiki')}
|
|
bg=mods['background'];bg.PART_TREE=3;bg.PART_CLOUD=2;bg.SetVisiblePart=lambda part,on:self.state['parts'].__setitem__(part,on)
|
|
ss=mods['systemSetting'];ss.SetEffectLevel=lambda v:self.state.__setitem__('effect',v)
|
|
ss.IsAlwaysShowName=lambda:self.state['name'];ss.SetAlwaysShowNameFlag=lambda v:self.state.__setitem__('name',bool(v))
|
|
ss.IsShowDamage=lambda:self.state['damage'];ss.SetShowDamageFlag=lambda v:self.state.__setitem__('damage',bool(v))
|
|
mods['game'].GameWindow=type('GameWindow',(),dict(Open=lambda s:self.opened.append(1),Close=lambda s:None))
|
|
grp=mods['grp'];grp.SetColor=grp.RenderBar=grp.RenderBox=lambda *a:None
|
|
mods['ui'].Window=Widget
|
|
mods['mt_biolog'].Action=Action;mods['mt_biolog'].Text=lambda p,x,y,t='',c=0:Widget();mods['mt_biolog'].L=lambda t:t if isinstance(t,str) else t.decode('utf-8')
|
|
mods['mt_wiki'].B=lambda t,limit=0:t
|
|
self.previous={n:sys.modules.get(n) for n in mods}
|
|
sys.modules.update(mods)
|
|
|
|
def load(self):
|
|
self.ns={'__name__':'mt_display'};exec(script('DisplayScript.inc'),self.ns);return self.ns
|
|
|
|
def tearDown(self):
|
|
os.chdir(self.cwd);self.tmp.cleanup()
|
|
for name,value in self.previous.items():
|
|
if value is None:sys.modules.pop(name,None)
|
|
else:sys.modules[name]=value
|
|
|
|
def test_defaults_and_config(self):
|
|
ns=self.load();self.assertEqual(self.state['effect'],0)
|
|
o=ns['Options']();self.assertEqual(o.Dump(),'0 1 1')
|
|
for bad in ('5 1 1','1 2 1','a 1 1','1 1','10 1 1','-1 1 1'):
|
|
o=ns['Options']();o.Load(bad);self.assertEqual(o.Dump(),'0 1 1')
|
|
Path('mt_display.cfg').write_text('3 0 1')
|
|
self.load();self.assertEqual(self.state['effect'],3) # applied at boot, before any map
|
|
|
|
def test_game_window_applies_and_saves(self):
|
|
Path('mt_display.cfg').write_text('2 0 1')
|
|
ns=self.load();game=sys.modules['game'];gw=game.GameWindow();gw.interface=types.SimpleNamespace(wndMiniMap=Widget())
|
|
game.GameWindow.Open(gw);self.assertEqual(self.opened,[1])
|
|
self.assertEqual(self.state['parts'],{3:0,2:1});self.assertEqual(ns['g_entry'].pos,(4,252))
|
|
w=ns['g_window'];w.Open()
|
|
self.assertIn('【',w.levelButtons[2].text);self.assertNotIn('【',w.levelButtons[0].text)
|
|
self.assertNotIn('【',w.treeButton.text);self.assertIn('【',w.cloudButton.text)
|
|
w.levelButtons[4].event();self.assertEqual(self.state['effect'],4)
|
|
w.ToggleTree();w.ToggleCloud();self.assertEqual(self.state['parts'],{3:1,2:0})
|
|
self.assertEqual(Path('mt_display.cfg').read_text(),'4 1 0')
|
|
w.ToggleName();w.ToggleDamage();self.assertEqual((self.state['name'],self.state['damage']),(False,False))
|
|
self.assertNotIn('【',w.nameButton.text)
|
|
game.GameWindow.Close(gw);self.assertIsNone(ns['g_window']);self.assertIsNone(ns['g_entry'])
|
|
|
|
def test_levels_match_native_rule(self):
|
|
ns=self.load();text=(ROOT/'src/port/UserInterface/InstanceBase.cpp').read_text(encoding='latin1')
|
|
body=text.split('void CInstanceBase::__UpdateEffectShown()',1)[1].split('\n}\n',1)[0]
|
|
self.assertEqual(len(ns['LEVELS']),5);self.assertIn('iLevel == 4',body)
|
|
self.assertIn('IsEnemy() || IsStone()',body);self.assertIn('IsPC()',body)
|
|
system=(ROOT/'src/port/UserInterface/PythonSystem.h').read_text(encoding='latin1')
|
|
self.assertIn('iLevel > 4',system)
|
|
render=(ROOT/'src/port/GameLib/MapOutdoorRender.cpp').read_text(encoding='latin1')
|
|
blocker=render.split('void CMapOutdoor::RenderPCBlocker()',1)[1].split('\n}\n',1)[0]
|
|
self.assertIn('TREE_OBJECT != pInstance->GetType()',blocker) # near trees are drawn as PC blockers
|
|
|
|
|
|
if __name__=='__main__':unittest.main()
|