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
+103
View File
@@ -0,0 +1,103 @@
# test_chat_hyperlink_dice_parity.gd —— 40250 聊天物品超链接与 /dice 掷骰系统回归测试
extends SceneTree
const SystemClass = preload("res://chat_hyperlink_dice_system.gd")
var _passed: int = 0
var _failed: int = 0
func _assert(cond: bool, msg: String) -> void:
if cond:
_passed += 1
print(" [PASS] %s" % msg)
else:
_failed += 1
printerr(" [FAIL] %s" % msg)
func _init() -> void:
print("\n=== Running test_chat_hyperlink_dice_parity (Batch 27 Direction 3) ===")
_test_item_hyperlink_encode_decode()
_test_tooltip_generation()
_test_broken_hyperlink_handling()
_test_dice_command_variations()
print("\n=== Result: %d Passed, %d Failed ===" % [_passed, _failed])
if _failed == 0:
print("ALL TESTS PASSED!\n")
else:
printerr("SOME TESTS FAILED!\n")
quit(0 if _failed == 0 else 1)
func _test_item_hyperlink_encode_decode() -> void:
print("\n--- Test 1: Item Hyperlink Encode & Decode Round-trip ---")
var original_item = {
"vnum": 189,
"name": "Poison Sword+9",
"flags": 1,
"sockets": [28430, 28431, 28432],
"attributes": [
{ "type": 72, "value": 45 }, # 平均伤害 45%
{ "type": 71, "value": -12 }, # 技能伤害 -12%
{ "type": 17, "value": 10 } # 半人加成 10%
]
}
# 1. Encode
var link_str = SystemClass.encode_item_hyperlink(original_item)
_assert(link_str.begins_with("|cffffc700|Hitem:"), "Link starts with official color and header")
_assert(link_str.ends_with("[Poison Sword+9]|h|r"), "Link ends with brackets and reset tag")
# 2. Decode
var decoded = SystemClass.decode_item_hyperlink(link_str)
_assert(decoded["valid"] == true, "Decoded successfully")
_assert(decoded["vnum"] == 189, "Vnum matches 189")
_assert(decoded["name"] == "Poison Sword+9", "Name matches Poison Sword+9")
_assert(decoded["sockets"][0] == 28430, "Socket 0 matches 28430")
_assert(decoded["sockets"][1] == 28431, "Socket 1 matches 28431")
_assert(decoded["sockets"][2] == 28432, "Socket 2 matches 28432")
_assert(decoded["attributes"].size() == 3, "Decoded 3 attributes")
_assert(decoded["attributes"][0]["type"] == 72 and decoded["attributes"][0]["value"] == 45, "Attr 0 is Type 72 Val 45")
func _test_tooltip_generation() -> void:
print("\n--- Test 2: Tooltip Generation on Link Hover ---")
var decoded = {
"valid": true,
"vnum": 11299,
"name": "Black Steel Armor+9",
"sockets": [28440, 28441, 0],
"attributes": [
{ "type": 1, "value": 2000 } # 最大生命值 +2000
]
}
var tooltip = SystemClass.build_tooltip_text(decoded)
_assert(tooltip.contains("Black Steel Armor+9"), "Tooltip contains item name")
_assert(tooltip.contains("Vnum: 11299"), "Tooltip contains item vnum")
_assert(tooltip.contains("镶嵌宝石 [Vnum 28440]"), "Tooltip contains socket 1 gem")
_assert(tooltip.contains("附加属性: 类型 1 +2000"), "Tooltip contains max HP bonus")
func _test_broken_hyperlink_handling() -> void:
print("\n--- Test 3: Broken Hyperlink Text Safety ---")
var r1 = SystemClass.decode_item_hyperlink("Just normal chat text")
_assert(r1["valid"] == false, "Normal text produces invalid hyperlink result")
var r2 = SystemClass.decode_item_hyperlink("|cffffc700|Hitem:corrupted_data|h")
_assert(r2["valid"] == false, "Corrupted hyperlink handled safely without crash")
func _test_dice_command_variations() -> void:
print("\n--- Test 4: 40250 /dice Command Logic (1:1 do_dice) ---")
# 1. Default /dice (1..100)
var d1 = SystemClass.execute_dice("Player1", [], 77)
_assert(d1["roll"] == 77, "Default dice rolled 77")
_assert(d1["start"] == 1 and d1["end"] == 100, "Default range is 1-100")
_assert(d1["message"].contains("77 (1-100)"), "Message correctly formatted")
# 2. Single arg /dice 20 (1..20)
var d2 = SystemClass.execute_dice("Player2", ["20"], 15)
_assert(d2["roll"] == 15, "Dice rolled 15 in range 1-20")
_assert(d2["start"] == 1 and d2["end"] == 20, "Single arg range is 1-20")
# 3. Two args reversed: /dice 80 20 -> auto min/max to (20..80)
var d3 = SystemClass.execute_dice("Player3", ["80", "20"], 50)
_assert(d3["start"] == 20 and d3["end"] == 80, "Auto swaps reversed range to 20-80")
_assert(d3["roll"] == 50, "Rolled 50 within normalized range")