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
+13 -5
View File
@@ -8,6 +8,7 @@
#include <godot_cpp/templates/hash_map.hpp>
#include <godot_cpp/variant/array.hpp>
#include <godot_cpp/variant/aabb.hpp>
#include <godot_cpp/variant/dictionary.hpp>
#include <godot_cpp/variant/packed_string_array.hpp>
#include <godot_cpp/variant/string.hpp>
@@ -81,6 +82,11 @@ public:
// Explicit per-surface texture path override (index -> absolute .dds path).
void set_surface_texture(int surface, const godot::String &path);
// Reference SetMaterialImagePointer(part, SourceSkin, TargetSkin): replace
// every GR2 material whose texture basename matches source, rather than
// assuming that an armour skin always belongs to surface 0.
void set_skin_texture(const godot::String &source, const godot::String &target);
void clear_skin_textures();
// When gr2_path is a .msm: [{index, model (abs .gr2), target_skin}, ...].
godot::Array get_hair_options() const { return hair_options; }
@@ -160,6 +166,8 @@ public:
// libgr2 sanity probe (kept from M0').
godot::String probe_gr2(const godot::String &path) const;
static void clear_texture_cache();
protected:
static void _bind_methods();
@@ -176,6 +184,7 @@ private:
godot::Ref<godot::Texture2D> sphere_map; // shared sphere map, lazy-loaded
godot::Ref<godot::Texture2D> _load_sphere_map(); // "ymir work/special/spheremap.jpg"
godot::PackedStringArray surface_tex_override;
godot::Dictionary skin_tex_override; // lowercase source basename -> target path
godot::String resolved_gr2_dir; // dir of the actually-loaded .gr2 (for .msm)
godot::Array hair_options; // from .msm HairData
@@ -196,10 +205,10 @@ private:
std::shared_ptr<std::optional<gr2::File>> weapon_file; // loaded weapon gr2
godot::MeshInstance3D *weapon_mi = nullptr; // child of this node
int weapon_bone_idx = -1; // base skeleton index of weapon_bone
// weapon's own bone[0] grip transform (invWorld · local); folded in so the mesh
// aligns to the hand even when it is authored offset from its bone (client:
// GrannySampleModelAnimationsAccelerated on the weapon skeleton). Identity when
// the weapon has no skeleton or a ~identity bone.
// Weapon root rigid-mesh composite (invWorld · local),
// folded in before the parent hand matrix exactly as Granny does for an attached
// model. This may contain a large translation which cancels an authored vertex
// offset (notably 03150), so it must not be clamped.
gr2::Mat4 weapon_pre{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 };
godot::String shield_gr2;
godot::String shield_bone = "Bip01 L Hand"; // PC skeletons have no equip_left_hand
@@ -220,7 +229,6 @@ private:
godot::MeshInstance3D *mi = nullptr;
godot::String last_info;
double ground_offset = 0.0;
godot::HashMap<godot::String, godot::Ref<godot::ImageTexture>> tex_cache;
// §2.10 LOD
bool lod_enabled = true;