Files
mtgodot-poc/project/ui/target_ui.gd
T
shenandshen 66d217b313 feat(client): 完成40250客户端核心功能1:1对齐与桥梁高度采样修复
- 桥梁与静态物体高度采样修复:
  - 严格对齐 40250 CMapOutdoor::GetHeight 与 CAttributeInstance::GetHeight
  - 解析 .mdatr 中的 AttributeHeight 网格,使用 is_in_triangle_2d 准确计算桥面多边形平面方程
  - sample_height 查询邻近区块并返回 fMAX(fObjectHeight, fTerrainHeight),彻底解决走上桥面穿透掉入水底/河床的问题
  - 新增 test_bridge_height_parity.gd 自动化对拍测试
- 40250 怪物击杀经验动效:
  - 1:1 实现 FLY_EXP(0) / FLY_HP / FLY_SP 粒子轨迹与爆炸吸附
- 40250 客户端全系统功能对齐(Batches 1-31):
  - 包含公会、交易、骑乘、变身、钓鱼、采矿、商城、信件、结婚、地牢等 134 套对拍系统与自动化回归测试
- 文档沉淀:
  - 新增 docs/CLIENT-PARITY-AUDIT-AND-FIX-GUIDE.md 客户端对拍缺陷发现与修复工程指南
2026-09-19 08:51:25 -07:00

216 lines
6.8 KiB
GDScript
Raw 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.
# target_ui.gd —— 40250 目标信息板(TargetBoard1:1 实现
# 严格对照:
# root/uitarget.py:14-340 (TargetBoard)
# UserInterface/InstanceBase.cpp (CanViewTargetHP)
# root/localeinfo.py (TARGET_LEVEL_PAWN..KING)
extends Node
const UiAssets = preload("res://ui/ui_assets.gd")
const GRADE_NAMES := {
0: "1阶", # nonplayer.PAWN: TARGET_LEVEL_PAWN
1: "2阶", # nonplayer.S_PAWN: TARGET_LEVEL_S_PAWN
2: "3阶", # nonplayer.KNIGHT: TARGET_LEVEL_KNIGHT
3: "4阶", # nonplayer.S_KNIGHT: TARGET_LEVEL_S_KNIGHT
4: "5阶首领", # nonplayer.BOSS: TARGET_LEVEL_BOSS
5: "6阶王", # nonplayer.KING: TARGET_LEVEL_KING
}
const DEFAULT_BOARD_WIDTH := 250.0
const BOARD_HEIGHT := 40.0
const GAUGE_WIDTH := 130.0
signal whisper_requested(name: String)
signal exchange_requested(vid: int)
signal fight_requested(vid: int)
signal view_equipment_requested(vid: int)
var client: Node
var _root: Control
var _board: Panel
var _name_label: Label
var _hp_gauge_bg: ColorRect
var _hp_gauge_fill: ColorRect
var _hp_text: Label
var _close_button: Button
var _button_container: HBoxContainer
var _action_buttons: Dictionary = {}
var _current_vid := 0
var _current_name := ""
var _current_hp_pct := 100
var _is_pc := false
var _shown := false
var _assets_root := ""
func setup(m2client: Node, parent: Node, assets := "") -> void:
client = m2client
_assets_root = assets
_build(parent)
func is_open() -> bool:
return _shown and _root != null and _root.visible
func get_target_vid() -> int:
return _current_vid
func get_target_name() -> String:
return _current_name
func get_hp_percentage() -> int:
return _current_hp_pct
func open(vid: int, target_name: String, level := -1, grade := -1, is_pc := false) -> void:
_current_vid = vid
_is_pc = is_pc
var display_name := target_name
var prefix := ""
if level > 0:
prefix += "[Lv. %d] " % level
if grade in GRADE_NAMES:
prefix += "(%s) " % GRADE_NAMES[grade]
_current_name = prefix + display_name
_name_label.text = _current_name
# 40250 uitarget.py:301 板面自适应宽度:200 + 7 * nameLength
var calculated_width := maxf(DEFAULT_BOARD_WIDTH, 200.0 + 7.0 * float(_current_name.length()))
_update_layout(calculated_width, is_pc)
_shown = true
_root.visible = true
_update_screen_position()
func set_hp(hp_pct: int) -> void:
_current_hp_pct = clampi(hp_pct, 0, 100)
if _hp_gauge_fill:
_hp_gauge_fill.size.x = GAUGE_WIDTH * (_current_hp_pct / 100.0)
if _hp_text:
_hp_text.text = "%d%%" % _current_hp_pct
if _hp_gauge_bg and not _is_pc:
_hp_gauge_bg.visible = true
func close() -> void:
_shown = false
_current_vid = 0
_current_name = ""
if _root:
_root.visible = false
func _on_close_button_pressed() -> void:
if client and client.has_method("clear_target"):
client.clear_target()
close()
func _update_layout(width: float, is_pc: bool) -> void:
_board.size = Vector2(width, BOARD_HEIGHT)
if is_pc:
_hp_gauge_bg.visible = false
_button_container.visible = true
_button_container.position = Vector2(width - 200, 8)
else:
_hp_gauge_bg.visible = true
_button_container.visible = false
_hp_gauge_bg.position = Vector2(width - 175, 14)
_close_button.position = Vector2(width - 28, 10)
func _update_screen_position() -> void:
if _root == null or not is_open():
return
var screen_w := 800.0
var vp := _root.get_viewport()
if vp:
screen_w = vp.get_visible_rect().size.x
# 40250 uitarget.py:254 UpdatePosition: ScreenWidth // 2 - Width // 2, 10
_root.position = Vector2(screen_w / 2.0 - _board.size.x / 2.0, 10.0)
func _build(parent: Node) -> void:
_root = Control.new()
_root.name = "TargetBoard"
_root.set_anchors_preset(Control.PRESET_TOP_LEFT)
_root.size = Vector2(DEFAULT_BOARD_WIDTH, BOARD_HEIGHT)
_root.visible = false
parent.add_child(_root)
# 经典 ThinBoard 风格
_board = Panel.new()
_board.name = "Board"
_board.size = Vector2(DEFAULT_BOARD_WIDTH, BOARD_HEIGHT)
var bg := StyleBoxFlat.new()
bg.bg_color = Color(0.04, 0.05, 0.07, 0.88)
bg.border_color = Color(0.45, 0.38, 0.22, 0.95)
bg.set_border_width_all(1)
bg.set_corner_radius_all(2)
_board.add_theme_stylebox_override("panel", bg)
_root.add_child(_board)
# 目标名称标签
_name_label = Label.new()
_name_label.name = "Name"
_name_label.position = Vector2(23, 11)
_name_label.size = Vector2(200, 20)
_name_label.add_theme_font_size_override("font_size", 11)
_name_label.add_theme_color_override("font_color", Color(1.0, 0.92, 0.75))
_name_label.add_theme_color_override("font_outline_color", Color(0, 0, 0, 0.9))
_name_label.add_theme_constant_override("outline_size", 2)
_board.add_child(_name_label)
# 红色 HP Gauge40250 130px 经典红槽)
_hp_gauge_bg = ColorRect.new()
_hp_gauge_bg.name = "HPGaugeBG"
_hp_gauge_bg.position = Vector2(DEFAULT_BOARD_WIDTH - 175, 14)
_hp_gauge_bg.size = Vector2(GAUGE_WIDTH, 12)
_hp_gauge_bg.color = Color(0.12, 0.02, 0.02, 0.95)
_board.add_child(_hp_gauge_bg)
_hp_gauge_fill = ColorRect.new()
_hp_gauge_fill.name = "HPGaugeFill"
_hp_gauge_fill.position = Vector2.ZERO
_hp_gauge_fill.size = Vector2(GAUGE_WIDTH, 12)
_hp_gauge_fill.color = Color(0.85, 0.12, 0.12, 1.0)
_hp_gauge_bg.add_child(_hp_gauge_fill)
_hp_text = Label.new()
_hp_text.name = "HPText"
_hp_text.set_anchors_preset(Control.PRESET_FULL_RECT)
_hp_text.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_hp_text.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
_hp_text.add_theme_font_size_override("font_size", 9)
_hp_text.add_theme_color_override("font_color", Color(1, 1, 1, 0.95))
_hp_text.add_theme_color_override("font_outline_color", Color(0, 0, 0, 0.8))
_hp_text.add_theme_constant_override("outline_size", 1)
_hp_text.text = "100%"
_hp_gauge_bg.add_child(_hp_text)
# 关闭按钮 (X)
_close_button = Button.new()
_close_button.name = "CloseButton"
_close_button.position = Vector2(DEFAULT_BOARD_WIDTH - 28, 10)
_close_button.size = Vector2(18, 18)
_close_button.text = "×"
_close_button.add_theme_font_size_override("font_size", 12)
_close_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
_close_button.pressed.connect(_on_close_button_pressed)
_board.add_child(_close_button)
# PC 交互按钮条
_button_container = HBoxContainer.new()
_button_container.name = "ButtonContainer"
_button_container.position = Vector2(DEFAULT_BOARD_WIDTH - 200, 8)
_button_container.visible = false
_board.add_child(_button_container)
_add_action_btn("私聊", func(): whisper_requested.emit(_current_name))
_add_action_btn("交易", func(): exchange_requested.emit(_current_vid))
_add_action_btn("装备", func(): view_equipment_requested.emit(_current_vid))
func _add_action_btn(text: String, callback: Callable) -> void:
var btn := Button.new()
btn.text = text
btn.custom_minimum_size = Vector2(42, 22)
btn.add_theme_font_size_override("font_size", 10)
btn.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
btn.pressed.connect(callback)
_button_container.add_child(btn)