Files
mtgodot-poc/project/char_status_ui_test.gd

169 lines
7.2 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# char_status_ui_test —— P11 角色状态窗 headless 自检(假 client + 真 uiscript)。
# godot --headless --path project --script char_status_ui_test.gd
# 校验 characterwindow.py 装载、RefreshStatus 数值绑定(逐字对照 uicharacter.py)、
# /stat 加点命令、页签切换。缺 uiscript 资产时优雅跳过。
extends SceneTree
const UiManager = preload("res://ui/ui_manager.gd")
const CharStatusUI = preload("res://ui/char_status_ui.gd")
class FakeClient extends Node:
signal points_changed(points: Dictionary)
signal entity_main_set(vid: int)
var pts: Array = []
var says: Array = []
var _main_vid := 7
func _init() -> void:
pts.resize(255)
pts.fill(0)
pts[1] = 42 # LEVEL
pts[3] = 123456 # EXP
pts[4] = 200000 # NEXT_EXP
pts[5] = 850 # HP
pts[6] = 900 # MAX_HP
pts[7] = 120 # SP
pts[8] = 150 # MAX_SP
pts[12] = 33 # ST
pts[13] = 27 # HT
pts[14] = 18 # DX
pts[15] = 12 # IQ
pts[16] = 250 # ATT_POWER
pts[17] = 145 # ATT_SPEED
pts[18] = 40 # EVADE_RATE
pts[19] = 165 # MOV_SPEED
pts[20] = 88 # DEF_GRADE
pts[21] = 100 # CASTING_SPEED
pts[22] = 55 # MAGIC_ATT_GRADE
pts[23] = 44 # MAGIC_DEF_GRADE
pts[26] = 5 # STAT (남은 포인트)
pts[29] = 210 # MIN_ATK
pts[30] = 210 # MAX_ATK
pts[95] = 15 # ATT_GRADE_BONUS
func get_main_vid() -> int: return _main_vid
func get_points() -> Dictionary:
return {
"points": pts, "hp": pts[5], "max_hp": pts[6], "sp": pts[7], "max_sp": pts[8],
"level": pts[1], "exp": pts[3], "next_exp": pts[4], "gold": 0,
}
func get_entity(vid: int) -> Dictionary:
if vid != _main_vid:
return {}
return {"name": "TestHero", "guild": 9, "race": 5} # race 5 -> job 1 (assassin)
func get_guild_name(gid: int) -> String:
return "銀河聯盟" if gid == 9 else ""
func say(type: int, text: String) -> bool:
says.append([type, text]); return true
var _fail := 0
func _ck(c: bool, m: String) -> void:
if not c:
_fail += 1
printerr("FAIL: " + m)
func _init() -> void:
_run()
if _fail == 0:
print("PASS: char_status_ui_test (layout + RefreshStatus + /stat + tabs)")
quit(0)
else:
printerr("%d check(s) failed" % _fail)
quit(1)
func _txt(cs: Node, nm: String) -> String:
var n: Control = cs._node(nm)
return String(n.text) if n and ("text" in n) else "<missing:%s>" % nm
func _run() -> void:
var assets := AssetRoot.path()
if not FileAccess.file_exists(assets.path_join("uiscript/uiscript/characterwindow.py")):
print(" (skip: no characterwindow.py — assets checkout missing)")
return
var ui: CanvasLayer = UiManager.new()
get_root().add_child(ui)
ui._ready()
var fc := FakeClient.new()
get_root().add_child(fc)
var cs: Node = CharStatusUI.new()
get_root().add_child(cs)
cs.setup(ui, fc, assets)
cs.open()
_ck(cs.is_open(), "character window opened")
if not cs.is_open():
return
# --- RefreshStatus 数值绑定 ---
_ck(_txt(cs, "Level_Value") == "42", "Level_Value = 42 (got %s)" % _txt(cs, "Level_Value"))
_ck(_txt(cs, "Exp_Value") == "123456", "Exp_Value = 123456 (got %s)" % _txt(cs, "Exp_Value"))
_ck(_txt(cs, "RestExp_Value") == "76544", "RestExp_Value = next-exp (got %s)" % _txt(cs, "RestExp_Value"))
_ck(_txt(cs, "HP_Value") == "850/900", "HP_Value = 850/900 (got %s)" % _txt(cs, "HP_Value"))
_ck(_txt(cs, "SP_Value") == "120/150", "SP_Value = 120/150 (got %s)" % _txt(cs, "SP_Value"))
_ck(_txt(cs, "STR_Value") == "33", "STR_Value = 33 (got %s)" % _txt(cs, "STR_Value"))
_ck(_txt(cs, "DEX_Value") == "18", "DEX_Value = 18 (got %s)" % _txt(cs, "DEX_Value"))
_ck(_txt(cs, "HTH_Value") == "27", "HTH_Value = 27 (got %s)" % _txt(cs, "HTH_Value"))
_ck(_txt(cs, "INT_Value") == "12", "INT_Value = 12 (got %s)" % _txt(cs, "INT_Value"))
# ATT: min==max==210, bonus = 95(15) + 91(0) -> 225
_ck(_txt(cs, "ATT_Value") == "225", "ATT_Value = 210+15 (got %s)" % _txt(cs, "ATT_Value"))
_ck(_txt(cs, "DEF_Value") == "88", "DEF_Value = 88 (got %s)" % _txt(cs, "DEF_Value"))
_ck(_txt(cs, "MATT_Value") == "55", "MATT_Value = 55 (no magic-wep) (got %s)" % _txt(cs, "MATT_Value"))
_ck(_txt(cs, "MDEF_Value") == "44", "MDEF_Value = 44 (got %s)" % _txt(cs, "MDEF_Value"))
_ck(_txt(cs, "ASPD_Value") == "145", "ASPD_Value = 145 (got %s)" % _txt(cs, "ASPD_Value"))
_ck(_txt(cs, "MSPD_Value") == "165", "MSPD_Value = 165 (got %s)" % _txt(cs, "MSPD_Value"))
_ck(_txt(cs, "CSPD_Value") == "100", "CSPD_Value = 100 (got %s)" % _txt(cs, "CSPD_Value"))
_ck(_txt(cs, "ER_Value") == "40", "ER_Value = 40 (got %s)" % _txt(cs, "ER_Value"))
_ck(_txt(cs, "Status_Plus_Value") == "5", "Status_Plus_Value = 5 (got %s)" % _txt(cs, "Status_Plus_Value"))
# --- identity ---
_ck(_txt(cs, "Character_Name") == "TestHero", "Character_Name = TestHero (got %s)" % _txt(cs, "Character_Name"))
_ck(_txt(cs, "Guild_Name") == "銀河聯盟", "Guild_Name resolved (got %s)" % _txt(cs, "Guild_Name"))
# --- +/- 按钮:STAT>0 时可见,命令与 uicharacter.statusPlusCommandDict 一致 ---
var hth_plus: Control = cs._node("HTH_Plus")
_ck(hth_plus != null and hth_plus.visible, "HTH_Plus visible while STAT>0")
if hth_plus is BaseButton:
hth_plus.pressed.emit()
_ck(fc.says.size() == 1 and fc.says[0][0] == 0 and fc.says[0][1] == "/stat ht",
"HTH_Plus -> say(0, '/stat ht') (got %s)" % str(fc.says))
var int_minus: Control = cs._node("INT_Minus")
if int_minus is BaseButton:
int_minus.pressed.emit()
_ck(fc.says.size() == 2 and fc.says[1][1] == "/stat- iq",
"INT_Minus -> say(0, '/stat- iq') (got %s)" % str(fc.says))
# --- STAT == 0 -> 加点按钮隐藏 + 刷新走 points_changed ---
fc.pts[26] = 0
fc.points_changed.emit(fc.get_points())
_ck(_txt(cs, "Status_Plus_Value") == "0", "Status_Plus_Value = 0 after change")
var hth_plus2: Control = cs._node("HTH_Plus")
_ck(hth_plus2 != null and not hth_plus2.visible, "HTH_Plus hidden while STAT==0")
# --- 页签切换(uicharacter.SetState ---
var char_page: Control = cs._node("Character_Page")
var skill_page: Control = cs._node("Skill_Page")
_ck(char_page != null and char_page.visible, "STATUS page visible by default")
cs._set_state("SKILL")
_ck(char_page != null and not char_page.visible, "Character_Page hidden after SetState(SKILL)")
_ck(skill_page != null and skill_page.visible, "Skill_Page visible after SetState(SKILL)")
cs._set_state("STATUS")
_ck(char_page.visible, "Character_Page visible again after SetState(STATUS)")
cs.close()
_ck(not cs.is_open(), "character window closed")
# 移动端使用独立横屏视图,不把 PC 固定坐标页面缩小后直接复用。
ui.set_mobile_mode(true)
cs.set_mobile_mode(true)
cs.open("STATUS")
_ck(cs.is_open(), "mobile character window opened")
_ck(cs._mobile_root.size == Vector2(760, 340), "mobile character base size is landscape")
_ck(cs._mobile_status_page.visible and not cs._mobile_emote_page.visible,
"mobile character opens on status/equipment page")
_ck(cs._mobile_stats.has("STR") and cs._mobile_stats["STR"]["value"].text == "33",
"mobile character binds STR")
cs.open("EMOTICON")
_ck(not cs._mobile_status_page.visible and cs._mobile_emote_page.visible,
"mobile character switches to emote page")
cs.close()
_ck(not cs.is_open(), "mobile character window closed")