Files
mtgodot-poc/project/player_move_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

187 lines
7.4 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# player_move_test —— §3.1 Src/Dst 移动模型(player_controller.gdheadless 自检。
# godot --headless --path project --script player_move_test.gd
# 覆盖:NEW_Goto 设 Src/Dst/is_going、__IsMovableGroundDistance 太近不动、
# 三门(frozen/lock/moving_skill)、__ReserveClickGround 的 0.1s delay 重试。
extends SceneTree
const PlayerCtl = preload("res://player_controller.gd")
const GameCam = preload("res://game_camera.gd")
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: player_move_test (Src/Dst move model / gates / reserved ground)")
quit(0)
else:
printerr("%d check(s) failed" % _fail)
quit(1)
func _run() -> void:
# SceneTree._init() 早于 root 就绪;等一帧再建 Node3D,避免 !is_inside_tree()。
await process_frame
var pc: Node = PlayerCtl.new()
get_root().add_child(pc)
pc.player = Node3D.new()
get_root().add_child(pc.player)
pc.player.position = Vector3.ZERO
# 1) NEW_Goto:远点 -> 设 Dst + is_going + 面向
var ok: bool = pc._goto(Vector3(10, 0, 0))
_ck(ok, "_goto(far) returns true")
_ck(pc.is_going(), "_goto -> is_going()")
_ck(pc._dst_pos.is_equal_approx(Vector3(10, 0, 0)), "Dst set to click point")
_ck(pc._src_pos.is_equal_approx(Vector3.ZERO), "Src set to current pos")
_ck(is_equal_approx(pc._dst_rot, atan2(10.0, 0.0)), "_dst_rot faces the target")
# 2) __IsMovableGroundDistance:太近(< 1 m)不动
pc._is_going = false
var near_ok: bool = pc._goto(Vector3(0.5, 0, 0))
_ck(near_ok, "_goto(near) returns true (treated as done)")
_ck(not pc.is_going(), "within MOVABLE_GROUND_DISTANCE -> no movement")
# 3) 每帧推进:朝 Dst 走,抵达后 is_going 归零
pc._goto(Vector3(3, 0, 0))
for i in 240:
pc._process(0.05)
if not pc.is_going():
break
_ck(pc.player.position.distance_to(Vector3(3, 0, 0)) < 0.5, "advances to Dst, got %s" % pc.player.position)
_ck(not pc.is_going(), "arrival clears is_going")
# 4) 三门:frozen / lock -> _goto 被挡 -> 预约 + 0.1s 后重试
pc.player.position = Vector3.ZERO
pc._is_going = false
pc.frozen = true
var blocked: bool = pc._goto(Vector3(8, 0, 0))
_ck(not blocked, "_goto while frozen returns false (gated)")
_ck(not pc.is_going(), "frozen -> not going yet")
_ck(pc._reserved_ground != null, "frozen -> click ground reserved")
# 冻结中推进:不平移、不消预约
pc._process(0.05)
_ck(pc.player.position.is_equal_approx(Vector3.ZERO), "frozen -> no translation")
# 解冻:delay 递减到 0 后自动重试预约点
pc.frozen = false
for i in 10:
pc._process(0.02)
_ck(pc.is_going() or pc.player.position.x > 0.1,
"reserved ground fires after delay once unfrozen (going=%s x=%.2f)" % [pc.is_going(), pc.player.position.x])
# 5) moving_skill:只转向不平移
pc.player.position = Vector3.ZERO
pc._is_going = false
pc.moving_skill = true
pc._goto(Vector3(0, 0, 6))
_ck(not pc.is_going(), "_goto while moving_skill is gated (no translate)")
pc.moving_skill = false
# 清掉第 5 组留下的预约点,免得下面各组被它改向。
pc._reserved_ground = null
pc._reserved_delay_time = 0.0
# 6) Actor-Actor 碰撞(TestActorCollision / BlockMovement 近似)——正前方有远端 Actor
# 时走不过去,停在两 body 球相接前;is_going 归零(BlockMovement = 不滑移硬停)。
pc.player.position = Vector3.ZERO
pc.player.rotation.y = 0.0
pc._is_going = false
pc.skip_actor_collision = false
var blocker := Node3D.new()
get_root().add_child(blocker)
blocker.global_position = Vector3(2, 0, 0) # 正前方 2 m
pc.pickables.append(blocker)
pc._goto(Vector3(10, 0, 0)) # 想穿过 blocker
for i in 240:
pc._process(0.05)
if not pc.is_going():
break
_ck(pc.player.position.x > 0.3 and pc.player.position.x < 1.5,
"actor collision halts before overlap (x=%.2f)" % pc.player.position.x)
_ck(not pc.is_going(), "blocked advance clears is_going (BlockMovement)")
# 6b) 死亡的 Actor 不挡(rVictim.IsDead()
pc.player.position = Vector3.ZERO
pc._is_going = false
blocker.set_meta("dead", true)
pc._goto(Vector3(10, 0, 0))
for i in 400:
pc._process(0.05)
if not pc.is_going():
break
_ck(pc.player.position.distance_to(Vector3(10, 0, 0)) < 0.6,
"dead actor does not block (x=%.2f)" % pc.player.position.x)
blocker.set_meta("dead", false)
# 6c) CanSkipCollisionskip_actor_collision)为真 → 整段跳过,可穿过
pc.player.position = Vector3.ZERO
pc._is_going = false
pc.skip_actor_collision = true
pc._goto(Vector3(10, 0, 0))
for i in 400:
pc._process(0.05)
if not pc.is_going():
break
_ck(pc.player.position.distance_to(Vector3(10, 0, 0)) < 0.6,
"skip_actor_collision passes straight through (x=%.2f)" % pc.player.position.x)
pc.skip_actor_collision = false
pc.pickables.erase(blocker)
blocker.free()
# 7) 坐骑转向速度:MountHorse → c_fDefaultHorseRotationSpeed(300),逐帧收敛不瞬转
_ck(is_equal_approx(pc.rotation_speed_deg, pc.ROT_SPEED_DEFAULT_DEG),
"default rotation speed is c_fDefaultRotationSpeed 1200")
pc.rotation_speed_deg = pc.ROT_SPEED_HORSE_DEG
pc.player.position = Vector3.ZERO
pc.player.rotation.y = 0.0
pc._is_going = false
pc._goto(Vector3(0, 0, -6)) # 需要转到 yaw ≈ ±PI
pc._process(0.05)
_ck(absf(pc.player.rotation.y) <= deg_to_rad(pc.ROT_SPEED_HORSE_DEG) * 0.05 + 1e-3,
"mounted: turn clamped to 300 deg/s in one 50ms step (%.3f rad)" % pc.player.rotation.y)
_ck(pc.player.position.z < -0.05, "mounted: still translates toward Dst while turning")
pc.rotation_speed_deg = pc.ROT_SPEED_DEFAULT_DEG
# 8) 相机相对移动:W / 摇杆上 = 远离相机(沿视线),D / 摇杆右 = 屏幕右。
# GameCamera 摆在 head + (sin yaw, ·, cos yaw)*dist 并看向 head,前方取真实相机几何。
var cam: Camera3D = GameCam.new()
get_root().add_child(cam)
cam.target = pc.player
cam.yaw = deg_to_rad(30.0)
pc.camera = cam
pc.player.rotation.y = 0.0
var off: Vector3 = cam._desired_pos(true) - (pc.player.global_position + cam.head_offset)
var away := -Vector3(off.x, 0, off.z).normalized()
var screen_right := away.cross(Vector3.UP)
for c in [["mobile axis up", Vector2(0, -1), away], ["mobile axis right", Vector2(1, 0), screen_right]]:
pc.player.position = Vector3.ZERO
pc._is_going = false
pc.set_mobile_axis(c[1])
pc._process(0.1)
pc.clear_mobile_input()
var moved := Vector3(pc.player.position.x, 0, pc.player.position.z)
_ck(moved.length() > 0.05 and moved.normalized().dot(c[2]) > 0.95,
"%s moves camera-relative (want %s, moved %s)" % [c[0], c[2], moved])
for c in [[KEY_W, "W", away], [KEY_S, "S", -away], [KEY_D, "D", screen_right]]:
pc.player.position = Vector3.ZERO
pc._is_going = false
var down := InputEventKey.new()
down.keycode = c[0]
down.physical_keycode = c[0]
down.pressed = true
Input.parse_input_event(down)
Input.flush_buffered_events()
_ck(Input.is_key_pressed(c[0]), "synthetic %s press reaches Input" % c[1])
pc._process(0.1)
var up := down.duplicate()
up.pressed = false
Input.parse_input_event(up)
Input.flush_buffered_events()
var moved := Vector3(pc.player.position.x, 0, pc.player.position.z)
_ck(moved.length() > 0.05 and moved.normalized().dot(c[2]) > 0.95,
"key %s moves camera-relative (want %s, moved %s)" % [c[1], c[2], moved])
pc.camera = null
cam.free()