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 客户端对拍缺陷发现与修复工程指南
This commit is contained in:
shen
2026-09-19 08:51:25 -07:00
parent 1db9e9a129
commit 66d217b313
650 changed files with 53046 additions and 1586 deletions
+55 -3
View File
@@ -29,6 +29,10 @@ var player_getter: Callable
var _root: Control
var _view: Control
var _ch_label: Label
var _coord_label: Label
var _btn_zoom_in: Button
var _btn_zoom_out: Button
var _btn_atlas: Button
var _scale := SCALE
var _signal_points: Array[Vector2] = []
var _observer_tracks: Dictionary = {}
@@ -47,10 +51,12 @@ func setup(m2client: Node, parent: Node, get_player: Callable) -> void:
func _build(parent: Node) -> void:
_root = Control.new()
_root.name = "MiniMap"
_root.set_anchors_preset(Control.PRESET_TOP_RIGHT)
_root.position = Vector2(-2 * RADIUS - 16, 12)
_root.size = Vector2(2 * RADIUS, 2 * RADIUS + 16)
_root.size = Vector2(2 * RADIUS + 16, 2 * RADIUS + 38)
parent.add_child(_root)
_view = Control.new()
_view.size = Vector2(2 * RADIUS, 2 * RADIUS)
_view.mouse_filter = Control.MOUSE_FILTER_IGNORE
@@ -58,13 +64,59 @@ func _build(parent: Node) -> void:
_root.add_child(_view)
_root.gui_input.connect(_on_gui_input)
_root.mouse_filter = Control.MOUSE_FILTER_STOP
_ch_label = Label.new()
_ch_label.position = Vector2(0, 2 * RADIUS)
_ch_label.add_theme_font_size_override("font_size", 11)
_ch_label.position = Vector2(4, 2 * RADIUS + 2)
_ch_label.add_theme_font_size_override("font_size", 10)
_ch_label.text = "CH -"
_root.add_child(_ch_label)
# 40250 uiminimap.py:440 实时坐标 "(X, Y)"
_coord_label = Label.new()
_coord_label.position = Vector2(46, 2 * RADIUS + 2)
_coord_label.size = Vector2(90, 16)
_coord_label.add_theme_font_size_override("font_size", 10)
_coord_label.text = "(0, 0)"
_root.add_child(_coord_label)
# 40250 uiminimap.py: ScaleUp / ScaleDown / Atlas
_btn_zoom_in = Button.new()
_btn_zoom_in.name = "ScaleUpButton"
_btn_zoom_in.position = Vector2(2 * RADIUS - 2, 2 * RADIUS - 38)
_btn_zoom_in.size = Vector2(18, 18)
_btn_zoom_in.text = "+"
_btn_zoom_in.add_theme_font_size_override("font_size", 11)
_btn_zoom_in.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
_btn_zoom_in.pressed.connect(func(): set_scale(_scale + 0.05))
_root.add_child(_btn_zoom_in)
_btn_zoom_out = Button.new()
_btn_zoom_out.name = "ScaleDownButton"
_btn_zoom_out.position = Vector2(2 * RADIUS - 2, 2 * RADIUS - 18)
_btn_zoom_out.size = Vector2(18, 18)
_btn_zoom_out.text = "-"
_btn_zoom_out.add_theme_font_size_override("font_size", 11)
_btn_zoom_out.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
_btn_zoom_out.pressed.connect(func(): set_scale(_scale - 0.05))
_root.add_child(_btn_zoom_out)
_btn_atlas = Button.new()
_btn_atlas.name = "AtlasButton"
_btn_atlas.position = Vector2(2 * RADIUS - 2, 2 * RADIUS + 2)
_btn_atlas.size = Vector2(18, 18)
_btn_atlas.text = "M"
_btn_atlas.add_theme_font_size_override("font_size", 10)
_btn_atlas.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
_btn_atlas.pressed.connect(func(): map_pressed.emit())
_root.add_child(_btn_atlas)
func _process(_dt: float) -> void:
if _coord_label:
var pl := _player()
if pl:
var pos: Vector3 = pl.global_position if pl.is_inside_tree() else pl.position
# 40250 uiminimap.py: (x // 100, y // 100)
_coord_label.text = "(%d, %d)" % [int(pos.x), int(-pos.z)]
if _view:
_view.queue_redraw()