Files
mtgodot-poc/project/race_motion_assembly_test.gd
T
shenleiandClaude Opus 5 1933a5ceda fix(combat): 战斗逐项对齐 40250,修复移动 / 技能释放与武器挂点
- 连击类型由 combo_changed 驱动(SetComboSkillFlag,技能 122 等级)
- 命中判定改为 .msa 刀尖 Z 圆柱 vs .msm 防御球(hit_collision.gd 逐行移植)
- _register_hit 改为 map::insert 语义,修正命中上限
- 攻速同时缩放 GetAttackingElapsedTime 与攻击动作速率;按武器动作模式目录绑定攻击段
- 去掉本地连击超时,motion_bound 清命中表 / 连击段号
- __ProcessDataAttackSuccess:InsertDelay 硬直、physics_push.gd 击退 + GetBlendingPosition 同步、
  命中特效、__HitGood / __HitGreate / __HitStone 受击动作链与抖动(ui/hit_reaction.gd)
- ClassicSession::send_use_skill 不再夹带 CG_FLY_TARGETING
- mac 前进 / 后退方向与技能释放修复;武器按职业骨骼挂到手上
- 新增 hit_collision / physics_push / hit_view / net_world_push / weapon_attach 测试;
  gpu_pose_bounds / race_motion_assembly 适配 damage 随机变体
- 文档:CLIENT-GAP.md、CLIENT-GAP-FIX.md §3.5 / §3.7 与 C.5 增量 130

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ft3kXpNdLbKEfg5uDLfqVF
2026-09-12 13:15:28 +09:00

50 lines
2.0 KiB
GDScript

extends SceneTree
const PlayerView = preload("res://ui/player_view.gd")
const EquipModel = preload("res://ui/equip_model.gd")
var failures: Array[String] = []
func _initialize() -> void:
call_deferred("run")
func check(ok: bool, message: String) -> void:
if not ok:
failures.append(message)
printerr(message)
func run() -> void:
for race in 8:
var pv := PlayerView.new()
root.add_child(pv)
if not pv.build(AssetRoot.path(), race):
check(false, "race %d failed to build" % race)
pv.queue_free()
continue
pv.anim.set_process(false)
pv.anim.set("blend_time", 0.0)
var equip := EquipModel.new()
pv.add_child(equip)
equip.setup_remote(null, func() -> Node: return pv, AssetRoot.path(), race, [0, 0, 0, 0])
# MSM is an independent asset authority for the equipped body's gender.
var body_dir := String(pv.model.get("gr2_path")).get_base_dir().to_lower()
check(body_dir == pv.motion_dir.get_base_dir().to_lower(),
"race %d: MSM body and animation directories differ" % race)
check(String(pv.model.get("hair_gr2")).to_lower().begins_with(body_dir + "/"),
"race %d: hair is from a different resource family" % race)
for motion in ["wait", "walk", "run", "attack", "damage", "dead"]:
pv.set_anim_state(motion)
# damage is registered as two equal-weight variants (damage / damage_1).
var variants: Array = PlayerView.REACTION_FILES.get(motion, [motion])
check(String(pv.anim.get("anim_path")).get_file().get_basename() in variants,
"race %d: missing %s motion" % [race, motion])
for fraction in [0.0, 0.5, 1.0]:
pv.anim.set("time", float(pv.anim.call("get_duration")) * fraction)
for mi in pv.model.find_children("*", "MeshInstance3D", true, false):
var bounds: AABB = mi.get_aabb()
check(bounds.position.is_finite() and bounds.size.is_finite() and bounds.size.length() > 0,
"race %d: invalid bounds in %s" % [race, motion])
pv.queue_free()
await process_frame
print("race_motion_assembly_test: failures=", failures.size(), " (8 races, 6 motions, 3 samples)")
quit(0 if failures.is_empty() else 1)