- 连击类型由 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
187 lines
7.4 KiB
GDScript
187 lines
7.4 KiB
GDScript
# player_move_test —— §3.1 Src/Dst 移动模型(player_controller.gd)headless 自检。
|
||
# 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) CanSkipCollision(skip_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()
|