# 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