# taskbar_dock_test.gd —— 验证右下角功能栏及对应窗口(角色、仓库等)40250 对齐 extends SceneTree const UiManager = preload("res://ui/ui_manager.gd") const CharStatusUI = preload("res://ui/char_status_ui.gd") const SafeboxUI = preload("res://ui/safebox_ui.gd") const Quickbar = preload("res://ui/quickbar.gd") const SkillTable = preload("res://ui/skill_table.gd") const Hud = preload("res://hud.gd") class FakeClient extends Node: signal points_changed(points: Dictionary) signal entity_main_set(vid: int) signal safebox_changed() signal inventory_changed() signal skills_changed() signal quickslots_changed() var _main_vid := 1 var _safebox_open := false var calls := [] func get_main_vid() -> int: return _main_vid func get_points() -> Dictionary: var pts: Array = [] pts.resize(255); pts.fill(0) pts[1] = 50; pts[3] = 1000; pts[4] = 2000; pts[5] = 1000; pts[6] = 1000 pts[7] = 200; pts[8] = 200; pts[12] = 40; pts[13] = 30; pts[14] = 20; pts[15] = 10 pts[26] = 3; pts[27] = 5 return {"points": pts, "hp": 1000, "max_hp": 1000, "sp": 200, "max_sp": 200, "level": 50, "exp": 1000, "next_exp": 2000, "gold": 50000} func get_entity(vid: int) -> Dictionary: return {"name": "WarriorTest", "guild": 0, "race": 0} func get_skill_group() -> int: return 1 func get_skills() -> Array: return [{"id": 1, "level": 15, "master": 0}, {"id": 2, "level": 1, "master": 1}] func get_safebox_items() -> Array: return [{"cell": 0, "vnum": 10, "count": 1}, {"cell": 4, "vnum": 27001, "count": 50}] func get_inventory() -> Array: return [{"cell": 0, "vnum": 11200, "count": 1}, {"cell": 1, "vnum": 27001, "count": 50}] func get_safebox_size() -> int: return 3 func get_safebox_gold() -> int: return 12345 func is_safebox_open() -> bool: return _safebox_open func safebox_close() -> void: _safebox_open = false calls.append("safebox_close") func safebox_checkout(cell: int, win: int, target: int) -> bool: calls.append(["checkout", cell, win, target]) return true func say(type: int, text: String) -> bool: calls.append(["say", type, text]) return true func get_item(window: int, cell: int) -> Dictionary: if cell == 1: return {"vnum": 27001, "count": 50} return {} 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: taskbar_dock_test (dock buttons + CharacterWindow tabs + SafeboxWindow 40250 parity)") quit(0) else: printerr("%d check(s) failed" % _fail) quit(1) func _run() -> void: var assets := AssetRoot.path() var ui: CanvasLayer = UiManager.new() get_root().add_child(ui) ui._ready() var fc := FakeClient.new() get_root().add_child(fc) var sk_tbl := SkillTable.new() var desc := assets.path_join("locale/locale/en/skilldesc.txt") if FileAccess.file_exists(desc): sk_tbl.load_file(desc) # 1. 验证 Quickbar Dock var qb := Quickbar.new() get_root().add_child(qb) qb.setup(fc, sk_tbl, ui, func(): return null, assets) var dock := qb.get_dock() _ck(dock != null and is_instance_valid(dock), "taskbar dock exists") _ck(dock.anchor_left == 1.0 and dock.offset_left == -178 and dock.offset_bottom == 37, "TaskBarDock correctly anchored to top-right (offset_left=-178, offset_bottom=37)") var btn_char := qb.get_button("character") var btn_inv := qb.get_button("inventory") var btn_msg := qb.get_button("messenger") var btn_safe := qb.get_button("safebox") var btn_sys := qb.get_button("system") _ck(btn_char != null and btn_char.position == Vector2(34, 3), "CharacterButton at (34, 3)") _ck(btn_inv != null and btn_inv.position == Vector2(68, 3), "InventoryButton at (68, 3)") _ck(btn_msg != null and btn_msg.position == Vector2(102, 3), "MessengerButton at (102, 3)") _ck(btn_safe != null and btn_safe.position == Vector2(0, 3), "SafeboxButton at (0, 3)") _ck(btn_sys != null and btn_sys.position == Vector2(136, 3), "SystemButton at (136, 3)") # 1.05 验证 40250 CenterBar 居中定位与槽位显示 var cb: Control = qb._root.find_child("CenterBar", true, false) _ck(cb != null and cb.offset_left == -128 and cb.offset_right == 237, "CenterBar horizontally centered at offset_left=-128, offset_right=237") var qb_board: Control = cb.find_child("quickslot_board", true, false) if cb else null _ck(qb_board != null and qb_board.position == Vector2(42, 0), "quickslot_board at (42, 0)") var slot0: Control = qb._slots[0].btn var corner0: TextureRect = slot0.find_child("Corner", true, false) if slot0 else null _ck(corner0 != null and corner0.position == Vector2(3, 3), "Slot 0 corner indicator at (3, 3)") # 验证物品装配后显示真实图标与数量,而非 #id 文本 qb.assign(0, "item", 1, false) _ck(qb._slots[0].icon.visible and qb._slots[0].icon.texture != null, "Slot 0 item icon loaded") _ck(qb._slots[0].lbl.text == "50", "Slot 0 shows item count 50") # 1.1 验证 40250 左下角状态栏 (Gauge_Board + EXP_Gauge_Board) var gb: Control = qb._root.find_child("Gauge_Board", true, false) _ck(gb != null and gb.position == Vector2(0, -10) and gb.size == Vector2(158, 47), "Gauge_Board exists at (0, -10) with size 158x47") var rampage: TextureRect = gb.find_child("RampageGauge", true, false) if gb else null _ck(rampage != null and qb._rampage_frames.size() == 17, "RampageGauge has 17 animation frames") var hp_board: Control = gb.find_child("HPGauge_Board", true, false) if gb else null _ck(hp_board != null and hp_board.size == Vector2(95, 11), "HPGauge_Board exists at 95x11") var hp_rec: ColorRect = hp_board.find_child("HPRecoveryGaugeBar", true, false) if hp_board else null _ck(hp_rec != null, "HPRecoveryGaugeBar exists inside HPGauge_Board") var hp_fill: Control = hp_board.find_child("HPFill", true, false) if hp_board else null _ck(hp_fill != null and hp_fill.clip_contents, "HPFill has clip_contents = true") var hp_img: TextureRect = hp_fill.find_child("HPGauge", true, false) if hp_fill else null _ck(hp_img != null and qb._hp_frames.size() == 7, "HPGauge loaded 7-frame liquid ripple textures") _ck(qb._sp_frames.size() == 7 and qb._st_frames.size() == 7, "SPGauge & STGauge loaded 7-frame textures") # 验证 set_hp / set_sp / set_st / set_exp 与 40250 悬停提示 qb.set_hp(500, 1000) _ck(hp_board.tooltip_text == "HP : 500 / 1000", "HP tooltip matches 40250 (HP : 500 / 1000)") _ck(hp_fill.size.x == 48, "HP fill width clipped to 48px for 50%") qb.set_sp(100, 200) _ck(qb._sp_board.tooltip_text == "SP : 100 / 200", "SP tooltip matches 40250 (SP : 100 / 200)") _ck(qb._sp_fill.size.x == 48, "SP fill width clipped to 48px for 50%") qb.set_st(50, 100) _ck(qb._st_board.tooltip_text == "ST : 50 / 100", "ST tooltip matches 40250 (ST : 50 / 100)") _ck(qb._st_fill.size.x == 48, "ST fill width clipped to 48px for 50%") qb.set_exp(500, 2000) _ck(qb._exp_board.tooltip_text == "EXP : 25.00%", "EXP tooltip matches 40250 (EXP : 25.00%)") _ck(qb._exp_clips[0].visible and qb._exp_clips[0].size.y == 19, "EXP point 1 fully visible") _ck(not qb._exp_clips[1].visible, "EXP point 2 hidden at 25%") # 1.2 验证 Hud 无浮动状态条且正确转发数据至 Quickbar var hud := Hud.new() get_root().add_child(hud) hud._build_status() _ck(hud._status_box == null, "HUD _build_status does not create floating prototype status box") hud.quickbar = qb hud.set_vitals(750, 1000, 180, 200) _ck(qb._cur_hp == 750 and hp_board.tooltip_text == "HP : 750 / 1000", "HUD set_vitals forwarded to Quickbar") hud.set_exp(1000, 2000) _ck(qb._cur_exp == 1000 and qb._exp_board.tooltip_text == "EXP : 50.00%", "HUD set_exp forwarded to Quickbar") hud.queue_free() # 2. 验证 CharacterWindow var char_ui := CharStatusUI.new() get_root().add_child(char_ui) char_ui.setup(ui, fc, assets, sk_tbl) char_ui.open("STATUS") _ck(char_ui.is_open(), "CharacterWindow opened") # 验证 TitleBar 与 CloseButton var char_tb: Control = char_ui._node("Character_TitleBar") _ck(char_tb != null, "Character_TitleBar exists") var close_btn: Button = char_tb.find_child("CloseButton", true, false) if char_tb else null _ck(close_btn != null, "CloseButton on Character_TitleBar exists") # 切换到 SKILL 页签并验证格子与点数 char_ui._set_state("SKILL") var skill_page: Control = char_ui._node("Skill_Page") _ck(skill_page != null and skill_page.visible, "Skill_Page visible") var active_slot: Control = char_ui._node("Skill_Active_Slot") _ck(active_slot != null, "Skill_Active_Slot exists") var s1: Control = active_slot.get_node_or_null("slot_1") if active_slot else null _ck(s1 != null, "Skill slot_1 exists") var s1_btn: TextureButton = s1.find_child("Btn", true, false) if s1 else null _ck(s1_btn != null and s1_btn.texture_normal != null, "Skill slot_1 has valid skill icon") var s1_lvl: Label = s1.find_child("Lvl", true, false) if s1 else null _ck(s1_lvl != null and s1_lvl.text == "15", "Skill slot_1 level is 15") var etc_slot: Control = char_ui._node("Skill_ETC_Slot") _ck(etc_slot != null, "Skill_ETC_Slot exists") var etc_101: Control = etc_slot.get_node_or_null("slot_101") if etc_slot else null _ck(etc_101 != null, "Skill_ETC_Slot slot_101 exists") # 切换到 EMOTICON 页签 char_ui._set_state("EMOTICON") var emote_page: Control = char_ui._node("Emoticon_Page") _ck(emote_page != null and emote_page.visible, "Emoticon_Page visible") var solo_slot: Control = char_ui._node("SoloEmotionSlot") _ck(solo_slot != null, "SoloEmotionSlot exists") var e1: Control = solo_slot.get_node_or_null("slot_1") if solo_slot else null _ck(e1 != null, "Emote slot_1 exists") var e1_btn: TextureButton = e1.find_child("Btn", true, false) if e1 else null _ck(e1_btn != null and e1_btn.texture_normal != null, "Emote slot_1 has emote icon") # 切换到 QUEST 页签 char_ui._set_state("QUEST") var quest_page: Control = char_ui._node("Quest_Page") _ck(quest_page != null and quest_page.visible, "Quest_Page visible") # 测试 TitleBar 关闭按钮 var quest_tb: Control = char_ui._node("Quest_TitleBar") var q_close: Button = quest_tb.find_child("CloseButton", true, false) if quest_tb else null _ck(q_close != null, "Quest_TitleBar has CloseButton") if q_close: q_close.pressed.emit() _ck(not char_ui.is_open(), "CharacterWindow closed by TitleBar CloseButton") # 3. 验证 SafeboxWindow var safe_ui := SafeboxUI.new() get_root().add_child(safe_ui) safe_ui.setup(fc, ui, null, assets) fc._safebox_open = true safe_ui.open() _ck(safe_ui.is_open(), "SafeboxWindow opened") _ck(safe_ui._root.size == Vector2(530, 418), "SafeboxWindow size is 530x418 (got %s)" % str(safe_ui._root.size)) var safe_tb: Control = safe_ui._root.find_child("TitleBar", true, false) _ck(safe_tb != null, "Safebox TitleBar exists") var safe_title: Label = safe_ui._root.find_child("TitleName", true, false) _ck(safe_title != null and safe_title.text.begins_with("仓库"), "Safebox TitleName is 仓库") var safe_close: Button = safe_tb.find_child("CloseButton", true, false) if safe_tb else null _ck(safe_close != null, "Safebox TitleBar has CloseButton") var wnd_item: Control = safe_ui._root.find_child("wndItem", true, false) _ck(wnd_item != null and wnd_item.size == Vector2(160, 288), "wndItem 5x9 grid (160x288) exists") var safe_s0: Control = safe_ui._cells.get(0, null) _ck(safe_s0 != null, "Safebox slot 0 exists") var safe_s0_icon: TextureRect = safe_s0.find_child("Icon", true, false) if safe_s0 else null _ck(safe_s0_icon != null and safe_s0_icon.texture != null, "Safebox slot 0 has item icon") var safe_s4: Control = safe_ui._cells.get(4, null) _ck(safe_s4 != null, "Safebox slot 4 exists") var safe_s4_cnt: Label = safe_s4.find_child("Count", true, false) if safe_s4 else null _ck(safe_s4_cnt != null and safe_s4_cnt.text == "50", "Safebox slot 4 count label is 50") # 右键取出测试 safe_ui._on_cell_input(0, InputEventMouseButton.new()) var right_click := InputEventMouseButton.new() right_click.button_index = MOUSE_BUTTON_RIGHT right_click.pressed = true safe_ui._on_cell_input(0, right_click) _ck(fc.calls.any(func(c): return c is Array and c.size() > 0 and c[0] == "checkout" and c[1] == 0), "Safebox right click triggers checkout") # 分页按钮 (I, II, III) _ck(safe_ui._page_buttons.size() == 3, "Safebox has 3 page buttons") safe_ui._page_buttons[1].pressed.emit() _ck(safe_ui._page == 1, "Switched to page 1 (II)") safe_ui._page_buttons[0].pressed.emit() _ck(safe_ui._page == 0, "Switched back to page 0 (I)") # 修改密码按钮 _ck(safe_ui._change_pw_btn != null and safe_ui._change_pw_btn.text == "修改密码", "ChangePasswordButton exists") # 关闭按钮 _ck(safe_ui._exit_btn != null and safe_ui._exit_btn.text == "关闭", "ExitButton exists") safe_ui._exit_btn.pressed.emit() _ck(not safe_ui.is_open(), "ExitButton closed SafeboxWindow")