- 连击类型由 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
82 lines
3.1 KiB
GDScript
82 lines
3.1 KiB
GDScript
# weapon_attach_test —— 武器挂点按种族 + 左右手(真 PlayerView + 真 gr2)。
|
||
# godot --headless --path project --script weapon_attach_test.gd
|
||
#
|
||
# 参考端 playersettingmodule.py RegisterAttachingBoneName:
|
||
# warrior PART_WEAPON=equip_right_hand (661)
|
||
# assassin PART_WEAPON=equip_right PART_WEAPON_LEFT=equip_left (873-874)
|
||
# sura PART_WEAPON=equip_right (999)
|
||
# shaman PART_WEAPON=equip_right PART_WEAPON_LEFT=equip_left (1190-1191)
|
||
# ActorInstanceAttach.cpp AttachWeapon:GetAttachingBoneName 失败(该职业没登记左手)就不挂。
|
||
# PlayerView 的 shield_gr2 槽 = PART_WEAPON_LEFT(参考端没有盾牌挂件)。
|
||
extends SceneTree
|
||
|
||
const PlayerView = preload("res://ui/player_view.gd")
|
||
|
||
const RIGHT_BONE := ["equip_right_hand", "equip_right", "equip_right", "equip_right"]
|
||
const LEFT_BONE := ["", "equip_left", "", "equip_left"]
|
||
const MAX_GRIP_M := 0.10
|
||
|
||
var _fail := 0
|
||
func _ck(c: bool, m: String) -> void:
|
||
if not c:
|
||
_fail += 1
|
||
printerr("FAIL: " + m)
|
||
|
||
func _init() -> void:
|
||
await _run()
|
||
if _fail == 0:
|
||
print("PASS: weapon_attach_test (race attaching bones + left/right hand)")
|
||
quit(0)
|
||
else:
|
||
printerr("%d check(s) failed" % _fail)
|
||
quit(1)
|
||
|
||
func _run() -> void:
|
||
if not ClassDB.class_exists("Metin2Model"):
|
||
print(" (skip: no Metin2Model extension)")
|
||
return
|
||
var assets := AssetRoot.path()
|
||
var weapon_dir := assets.path_join("item/ymir work/item/weapon")
|
||
var sword := weapon_dir.path_join("00010.gr2")
|
||
var dagger := weapon_dir.path_join("04000.gr2")
|
||
if not FileAccess.file_exists(sword) or not FileAccess.file_exists(dagger):
|
||
print(" (skip: weapon gr2 assets missing)")
|
||
return
|
||
await process_frame
|
||
for race in 8:
|
||
var job := race & 3
|
||
var pv = PlayerView.new()
|
||
get_root().add_child(pv)
|
||
if not pv.build(assets, race):
|
||
print(" (skip race %d: body assets missing)" % race)
|
||
pv.free()
|
||
continue
|
||
pv.set("weapon_gr2", sword)
|
||
pv.set("shield_gr2", dagger)
|
||
for i in 3:
|
||
await process_frame
|
||
var right := pv.model.get_node_or_null("Weapon") as Node3D
|
||
_ck(right != null, "race %d: right-hand weapon attached (%s)" % [race, RIGHT_BONE[job]])
|
||
if right:
|
||
_ck(_near(pv, right, RIGHT_BONE[job]), "race %d: weapon sits on %s" % [race, RIGHT_BONE[job]])
|
||
var left := pv.model.get_node_or_null("Shield") as Node3D
|
||
if LEFT_BONE[job] == "":
|
||
_ck(left == null, "race %d: no PART_WEAPON_LEFT bone registered -> nothing on left hand" % race)
|
||
else:
|
||
_ck(left != null, "race %d: left-hand weapon attached (%s)" % [race, LEFT_BONE[job]])
|
||
if left:
|
||
_ck(_near(pv, left, LEFT_BONE[job]), "race %d: left weapon sits on %s" % [race, LEFT_BONE[job]])
|
||
pv.queue_free()
|
||
await process_frame
|
||
|
||
func _near(pv: Node, node: Node3D, bone: String) -> bool:
|
||
var pose: Dictionary = pv.anim.call("get_effect_bone_pose", bone)
|
||
if pose.is_empty():
|
||
printerr(" bone %s has no pose" % bone)
|
||
return false
|
||
var hand: Vector3 = (pv.model as Node3D).global_transform * (pose["transform"] as Transform3D).origin
|
||
var d := node.global_position.distance_to(hand)
|
||
if d > MAX_GRIP_M:
|
||
printerr(" %s is %.3f m from %s" % [node.name, d, bone])
|
||
return d <= MAX_GRIP_M
|