169 lines
7.2 KiB
GDScript
169 lines
7.2 KiB
GDScript
# 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")
|