- 连击类型由 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
126 lines
4.6 KiB
GDScript
126 lines
4.6 KiB
GDScript
# net_world_push_test —— CLIENT-GAP §3.7 受击击退的场景侧:
|
||
# __PushCircle + IncreaseExternalForce -> net_world.push_victim
|
||
# CActorInstance::PhysicsProcess(每帧 AddMovement(GetX/YMovement))-> 节点偏移
|
||
# GetBlendingPosition(blending 中 = 当前位置 + LastPosition)-> OnHit 的 CG_SYNC_POSITION 坐标
|
||
# godot --headless --path project --script net_world_push_test.gd
|
||
extends SceneTree
|
||
|
||
const NetWorld = preload("res://net_world.gd")
|
||
const PhysicsPush = preload("res://physics_push.gd")
|
||
|
||
class FakeClient extends Node:
|
||
signal entity_spawned(entity: Dictionary)
|
||
signal entity_despawned(vid: int)
|
||
signal entity_moved(vid: int)
|
||
signal entity_main_set(vid: int)
|
||
signal entity_info(vid: int, entity: Dictionary)
|
||
signal chat(type: int, vid: int, text: String)
|
||
signal vitals_changed(vid: int)
|
||
signal entity_dead(vid: int)
|
||
signal damage(vid: int, amount: int, flag: int)
|
||
var ents := {}
|
||
func get_entity(vid: int) -> Dictionary: return ents.get(vid, {})
|
||
func get_entities() -> Array: return ents.values()
|
||
func get_pvp_relations() -> Array: return []
|
||
func get_duel() -> Dictionary: return {}
|
||
func add(vid: int, cm: Vector2, extra := {}) -> Dictionary:
|
||
var d := {"vid": vid, "name": "e%d" % vid, "is_main": false,
|
||
"func": 0, "moving": false, "angle_deg": 0.0,
|
||
"hp": 100, "max_hp": 100, "dead": false, "race": 0,
|
||
"affect_flags": 0, "parts": [0, 0, 0, 0]}
|
||
d.merge(extra, true)
|
||
ents[vid] = d
|
||
place(vid, cm)
|
||
return d
|
||
func place(vid: int, cm: Vector2) -> void:
|
||
ents[vid]["pos"] = Vector3(cm.x * 0.01, 0.0, -cm.y * 0.01)
|
||
ents[vid]["pos_cm"] = Vector3(cm.x, cm.y, 0.0)
|
||
|
||
var _fail := 0
|
||
func _ck(c: bool, m: String) -> void:
|
||
if not c:
|
||
_fail += 1
|
||
printerr("FAIL: " + m)
|
||
|
||
var _done := false
|
||
|
||
func _init() -> void:
|
||
await _run()
|
||
_ck(_done, "test body ran to completion (a SCRIPT ERROR aborted _run)")
|
||
if _fail == 0:
|
||
print("PASS: net_world_push_test (push_victim / PhysicsProcess offset / GetBlendingPosition)")
|
||
quit(0)
|
||
else:
|
||
printerr("%d check(s) failed" % _fail)
|
||
quit(1)
|
||
|
||
func _off(v: Vector2) -> Vector3:
|
||
return Vector3(v.x / 100.0, 0.0, -v.y / 100.0)
|
||
|
||
func _run() -> void:
|
||
var mount := Node3D.new()
|
||
get_root().add_child(mount)
|
||
var fc := FakeClient.new()
|
||
get_root().add_child(fc)
|
||
var nw: Node = NetWorld.new()
|
||
get_root().add_child(nw)
|
||
nw.setup(fc, mount)
|
||
|
||
fc.add(1, Vector2.ZERO, {"is_main": true})
|
||
fc.entity_main_set.emit(1)
|
||
fc.entity_spawned.emit(fc.add(2, Vector2(300, 0)))
|
||
await process_frame
|
||
nw._process(0.02)
|
||
var n: Node3D = nw.node_for(2)
|
||
_ck(n != null, "victim node spawned")
|
||
if n == null:
|
||
return
|
||
_ck(not nw.is_pushing(2), "no push -> not pushing")
|
||
_ck(nw.blending_position(2).is_equal_approx(n.global_position), "not blending -> GetBlendingPosition = current position")
|
||
|
||
# 攻击者在西边:victim - attacker = actor-world (+1, 0)
|
||
nw.push_victim(2, Vector2(1, 0), 3.0)
|
||
var ref := PhysicsPush.new()
|
||
ref.set_direction(Vector2(1, 0))
|
||
ref.increase_external_force(3.0)
|
||
var last := ref.get_last_position()
|
||
_ck(last.x > 0.0, "reference push has a positive displacement")
|
||
_ck(nw.is_pushing(2), "push_victim -> is_pushing")
|
||
_ck(nw.blending_position(2).is_equal_approx(n.global_position + _off(last)),
|
||
"blending -> GetBlendingPosition = current + LastPosition, got %s" % nw.blending_position(2))
|
||
|
||
var acc := Vector2.ZERO
|
||
for i in 60:
|
||
nw._process(0.02)
|
||
ref.update(0.02)
|
||
acc += ref.get_movement()
|
||
var want: Vector3 = nw._grounded(fc.ents[2]["pos"]) + _off(acc)
|
||
_ck(acc.x > 0.0 and n.position.is_equal_approx(want),
|
||
"PhysicsProcess: node follows the accumulated ease-out movement, got %s want %s" % [n.position, want])
|
||
|
||
# blending 中服务器位置变化:偏移叠在新位置上(位移还在进行)
|
||
fc.place(2, Vector2(300, 50))
|
||
nw._process(0.02)
|
||
ref.update(0.02)
|
||
acc += ref.get_movement()
|
||
want = nw._grounded(fc.ents[2]["pos"]) + _off(acc)
|
||
_ck(n.position.is_equal_approx(want), "server move while blending -> offset kept on top, got %s want %s" % [n.position, want])
|
||
|
||
for i in 100:
|
||
nw._process(0.02)
|
||
ref.update(0.02)
|
||
acc += ref.get_movement()
|
||
_ck(not nw.is_pushing(2), "blend over -> not pushing")
|
||
want = nw._grounded(fc.ents[2]["pos"]) + _off(acc)
|
||
_ck(n.position.is_equal_approx(want), "blend over -> actor stays where the push left it, got %s want %s" % [n.position, want])
|
||
|
||
# blend 结束后服务器位置再变:以服务器位置为准(丢弃本地击退偏移)
|
||
fc.place(2, Vector2(320, 50))
|
||
nw._process(0.02)
|
||
_ck(n.position.is_equal_approx(nw._grounded(fc.ents[2]["pos"])), "server position after the blend supersedes the push offset")
|
||
|
||
nw.push_victim(2, Vector2(0, 1), 3.0)
|
||
fc.entity_despawned.emit(2)
|
||
_ck(not nw.is_pushing(2) and not nw._push.has(2), "despawn drops the push state")
|
||
_done = true
|