Phase 7: netfox + godot-jolt stack upgrade
Stack installed: - netfox v1.35.3 (core + extras + noray + internals) - godot-jolt v0.16.0-stable Architecture: - Server: ENet transport (works headless, no netfox deps) - Client/Editor: netfox rollback (RollbackSynchronizer, TickInterpolator) New/modified: - docs/migration-netfox-plan.md — migration architecture - scripts/network/network_manager.gd — netfox-aware ENet fallback - scripts/network/player.gd — clean base player - client/characters/player_netfox.gd — rollback player w/ WeaponManager - client/characters/input/player_net_input.gd — BaseNetInput subclass - client/characters/character/fps_character_controller.gd — netfox input feed - client/weapons/ — weapon data, registry, TacticalWeaponHitscan, WeaponManager - client/scripts/round_replicator.gd — client-side round state bridge - server/scripts/round_manager.gd — improved state machine - server/scripts/plugin_api/plugin_manager.gd — refined plugin system - config: enemy_tag, ally_tag for meatball targeting Removed: old C++ SimulationServer GDExtension (replaced by netfox rollback)
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
extends PhysicsDriver
|
||||
|
||||
class_name PhysicsDriver2D
|
||||
|
||||
# Physics driver based on netfox ticks
|
||||
# Requires a custom build of Godot with https://github.com/godotengine/godot/pull/76462
|
||||
|
||||
var scene_collision_objects: Array = []
|
||||
var collision_objects_snapshots: Dictionary[int, Dictionary] = {}
|
||||
|
||||
func _init_physics_space() -> void:
|
||||
physics_space = get_viewport().world_2d.space
|
||||
PhysicsServer2D.space_set_active(physics_space, false)
|
||||
|
||||
get_tree().node_added.connect(node_added)
|
||||
get_tree().node_removed.connect(node_removed)
|
||||
scan_tree()
|
||||
|
||||
func _physics_step(delta) -> void:
|
||||
PhysicsServer2D.space_flush_queries(physics_space)
|
||||
PhysicsServer2D.space_step(physics_space, delta)
|
||||
|
||||
func _snapshot_space(tick: int) -> void:
|
||||
var rid_states: Dictionary[RID, Array] = {}
|
||||
for element in scene_collision_objects:
|
||||
if element is CharacterBody2D:
|
||||
element.force_update_transform() # force colliders to update
|
||||
|
||||
var rid = element.get_rid()
|
||||
rid_states[rid] = get_body_states(rid)
|
||||
snapshots[tick] = rid_states
|
||||
|
||||
func _rollback_space(tick) -> void:
|
||||
if snapshots.has(tick):
|
||||
var rid_states = snapshots[tick]
|
||||
for rid in rid_states.keys():
|
||||
set_body_states(rid, rid_states[rid])
|
||||
|
||||
for body in scene_collision_objects:
|
||||
if body is CharacterBody2D or body is AnimatableBody2D:
|
||||
body.force_update_transform() # force colliders to update
|
||||
|
||||
func get_body_states(rid: RID) -> Array:
|
||||
var body_state: Array = [Vector3.ZERO, Quaternion.IDENTITY, Vector3.ZERO, Vector3.ZERO]
|
||||
body_state[0] = PhysicsServer2D.body_get_state(rid, PhysicsServer2D.BODY_STATE_TRANSFORM)
|
||||
body_state[1] = PhysicsServer2D.body_get_state(rid, PhysicsServer2D.BODY_STATE_LINEAR_VELOCITY)
|
||||
body_state[2] = PhysicsServer2D.body_get_state(rid, PhysicsServer2D.BODY_STATE_ANGULAR_VELOCITY)
|
||||
body_state[3] = PhysicsServer2D.body_get_state(rid, PhysicsServer2D.BODY_STATE_SLEEPING)
|
||||
return body_state
|
||||
|
||||
func set_body_states(rid: RID, body_state: Array) -> void:
|
||||
PhysicsServer2D.body_set_state(rid, PhysicsServer2D.BODY_STATE_TRANSFORM, body_state[0])
|
||||
PhysicsServer2D.body_set_state(rid, PhysicsServer2D.BODY_STATE_LINEAR_VELOCITY, body_state[1])
|
||||
PhysicsServer2D.body_set_state(rid, PhysicsServer2D.BODY_STATE_ANGULAR_VELOCITY, body_state[2])
|
||||
PhysicsServer2D.body_set_state(rid, PhysicsServer2D.BODY_STATE_SLEEPING, body_state[3])
|
||||
|
||||
func scan_tree():
|
||||
scene_collision_objects.clear()
|
||||
scene_collision_objects = get_all_children(get_node('/root'))
|
||||
|
||||
func get_all_children(in_node: Node) -> Array:
|
||||
var nodes = []
|
||||
nodes = in_node.find_children("*", "PhysicsBody2D", true, false)
|
||||
return nodes
|
||||
|
||||
func node_added(node: Node) -> void:
|
||||
if node is PhysicsBody2D:
|
||||
scene_collision_objects.append(node)
|
||||
|
||||
func node_removed(node: Node) -> void:
|
||||
if node is PhysicsBody2D:
|
||||
scene_collision_objects.erase(node)
|
||||
@@ -0,0 +1 @@
|
||||
uid://c8p8gymii2y5v
|
||||
@@ -0,0 +1,71 @@
|
||||
extends PhysicsDriver
|
||||
class_name PhysicsDriver3D
|
||||
|
||||
# Physics driver based on netfox ticks
|
||||
# Requires a custom build of Godot with https://github.com/godotengine/godot/pull/76462
|
||||
|
||||
# Maps ticks ( int ) to global snapshots ( Dictionary<RID, Array> )
|
||||
var scene_collision_objects: Array = []
|
||||
|
||||
func _init_physics_space() -> void:
|
||||
physics_space = get_viewport().world_3d.space
|
||||
PhysicsServer3D.space_set_active(physics_space, false)
|
||||
|
||||
get_tree().node_added.connect(node_added)
|
||||
get_tree().node_removed.connect(node_removed)
|
||||
scan_tree()
|
||||
|
||||
func _physics_step(delta) -> void:
|
||||
PhysicsServer3D.space_flush_queries(physics_space)
|
||||
PhysicsServer3D.space_step(physics_space, delta)
|
||||
|
||||
func _snapshot_space(tick: int) -> void:
|
||||
# Maps RIDs to physics state ( Array )
|
||||
var rid_states := {}
|
||||
for element in scene_collision_objects:
|
||||
var rid = element.get_rid()
|
||||
rid_states[rid] = get_body_states(rid)
|
||||
|
||||
snapshots[tick] = rid_states
|
||||
|
||||
func _rollback_space(tick) -> void:
|
||||
if snapshots.has(tick):
|
||||
var rid_states = snapshots[tick]
|
||||
for rid in rid_states.keys():
|
||||
set_body_states(rid, rid_states[rid])
|
||||
|
||||
for body in scene_collision_objects:
|
||||
if body is CharacterBody3D or body is AnimatableBody3D:
|
||||
body.force_update_transform()
|
||||
|
||||
|
||||
func get_body_states(rid: RID) -> Array:
|
||||
var body_state: Array = [Vector3.ZERO, Quaternion.IDENTITY, Vector3.ZERO, Vector3.ZERO]
|
||||
body_state[0] = PhysicsServer3D.body_get_state(rid, PhysicsServer3D.BODY_STATE_TRANSFORM)
|
||||
body_state[1] = PhysicsServer3D.body_get_state(rid, PhysicsServer3D.BODY_STATE_LINEAR_VELOCITY)
|
||||
body_state[2] = PhysicsServer3D.body_get_state(rid, PhysicsServer3D.BODY_STATE_ANGULAR_VELOCITY)
|
||||
body_state[3] = PhysicsServer3D.body_get_state(rid, PhysicsServer3D.BODY_STATE_SLEEPING)
|
||||
return body_state
|
||||
|
||||
func set_body_states(rid: RID, body_state: Array) -> void:
|
||||
PhysicsServer3D.body_set_state(rid, PhysicsServer3D.BODY_STATE_TRANSFORM, body_state[0])
|
||||
PhysicsServer3D.body_set_state(rid, PhysicsServer3D.BODY_STATE_LINEAR_VELOCITY, body_state[1])
|
||||
PhysicsServer3D.body_set_state(rid, PhysicsServer3D.BODY_STATE_ANGULAR_VELOCITY, body_state[2])
|
||||
PhysicsServer3D.body_set_state(rid, PhysicsServer3D.BODY_STATE_SLEEPING, body_state[3])
|
||||
|
||||
func scan_tree():
|
||||
scene_collision_objects.clear()
|
||||
scene_collision_objects = get_all_children(get_node('/root'))
|
||||
|
||||
func get_all_children(in_node: Node) -> Array:
|
||||
var nodes = []
|
||||
nodes = in_node.find_children("*", "PhysicsBody3D", true, false)
|
||||
return nodes
|
||||
|
||||
func node_added(node: Node) -> void:
|
||||
if node is PhysicsBody3D:
|
||||
scene_collision_objects.append(node)
|
||||
|
||||
func node_removed(node: Node) -> void:
|
||||
if node is PhysicsBody3D:
|
||||
scene_collision_objects.erase(node)
|
||||
@@ -0,0 +1 @@
|
||||
uid://wr5ur5dqrkni
|
||||
@@ -0,0 +1,44 @@
|
||||
@icon("res://addons/netfox.extras/icons/network-rigid-body-2d.svg")
|
||||
extends RigidBody2D
|
||||
class_name NetworkRigidBody2D
|
||||
|
||||
## A rollback / state synchronizer class for RigidBody2D.
|
||||
## Set state property path to physics_state to synchronize the state of this body.
|
||||
|
||||
@onready var direct_state = PhysicsServer2D.body_get_direct_state(get_rid())
|
||||
|
||||
var physics_state: Array:
|
||||
get: return get_state()
|
||||
set(v): set_state(v)
|
||||
|
||||
enum {
|
||||
ORIGIN,
|
||||
ROT,
|
||||
LIN_VEL,
|
||||
ANG_VEL,
|
||||
SLEEPING
|
||||
}
|
||||
|
||||
func _notification(notification: int):
|
||||
if notification == NOTIFICATION_READY:
|
||||
add_to_group("network_rigid_body")
|
||||
|
||||
func get_state() -> Array:
|
||||
var body_state: Array = [Vector3.ZERO, Quaternion.IDENTITY, Vector3.ZERO, Vector3.ZERO, false]
|
||||
body_state[ORIGIN] = direct_state.transform.origin
|
||||
body_state[ROT] = direct_state.transform.get_rotation()
|
||||
body_state[LIN_VEL] = direct_state.linear_velocity
|
||||
body_state[ANG_VEL] = direct_state.angular_velocity
|
||||
body_state[SLEEPING] = direct_state.sleeping
|
||||
return body_state
|
||||
|
||||
func set_state(remote_state: Array) -> void:
|
||||
direct_state.transform = Transform2D(remote_state[ROT], remote_state[ORIGIN])
|
||||
direct_state.linear_velocity = remote_state[LIN_VEL]
|
||||
direct_state.angular_velocity = remote_state[ANG_VEL]
|
||||
direct_state.sleeping = remote_state[SLEEPING]
|
||||
|
||||
## Override and apply any logic, forces or impulses to the rigid body as you would in physics_process
|
||||
## The physics engine will run its simulation during rollback_tick with other nodes
|
||||
func _physics_rollback_tick(_delta, _tick):
|
||||
pass
|
||||
@@ -0,0 +1 @@
|
||||
uid://c8hw7ol53m55g
|
||||
@@ -0,0 +1,46 @@
|
||||
@icon("res://addons/netfox.extras/physics/network-rigid-body-3d.gd")
|
||||
extends RigidBody3D
|
||||
class_name NetworkRigidBody3D
|
||||
|
||||
## A rollback / state synchronizer class for RigidBody3D.
|
||||
## Set state property path to physics_state to synchronize the state of this body.
|
||||
|
||||
@onready var direct_state = PhysicsServer3D.body_get_direct_state(get_rid())
|
||||
|
||||
var physics_state: Array:
|
||||
get: return get_state()
|
||||
set(v): set_state(v)
|
||||
|
||||
enum {
|
||||
ORIGIN,
|
||||
QUAT,
|
||||
LIN_VEL,
|
||||
ANG_VEL,
|
||||
SLEEPING
|
||||
}
|
||||
|
||||
func _notification(notification: int):
|
||||
if notification == NOTIFICATION_READY:
|
||||
add_to_group("network_rigid_body")
|
||||
|
||||
func get_state() -> Array:
|
||||
var body_state: Array = [Vector3.ZERO, Quaternion.IDENTITY, Vector3.ZERO, Vector3.ZERO, false]
|
||||
body_state[ORIGIN] = direct_state.transform.origin
|
||||
body_state[QUAT] = direct_state.transform.basis.get_rotation_quaternion()
|
||||
body_state[LIN_VEL] = direct_state.linear_velocity
|
||||
body_state[ANG_VEL] = direct_state.angular_velocity
|
||||
body_state[SLEEPING] = direct_state.sleeping
|
||||
return body_state
|
||||
|
||||
func set_state(remote_state: Array) -> void:
|
||||
direct_state.transform.origin = remote_state[ORIGIN]
|
||||
direct_state.transform.basis = Basis(remote_state[QUAT])
|
||||
direct_state.linear_velocity = remote_state[LIN_VEL]
|
||||
direct_state.angular_velocity = remote_state[ANG_VEL]
|
||||
direct_state.sleeping = remote_state[SLEEPING]
|
||||
|
||||
|
||||
## Override and apply any logic, forces or impulses to the rigid body as you would in physics_process
|
||||
## The physics engine will run its simulation during rollback_tick with other nodes
|
||||
func _physics_rollback_tick(_delta, _tick):
|
||||
pass
|
||||
@@ -0,0 +1 @@
|
||||
uid://bklxcdyxgbjg2
|
||||
@@ -0,0 +1,88 @@
|
||||
extends Object
|
||||
|
||||
class PhysicsDriverToggle:
|
||||
const INACTIVE_SUFFIX := ".off"
|
||||
|
||||
func get_name() -> String:
|
||||
return "???"
|
||||
|
||||
func get_files() -> Array[String]:
|
||||
return []
|
||||
|
||||
func get_error_messages() -> Array[String]:
|
||||
return []
|
||||
|
||||
func is_enabled() -> bool:
|
||||
return get_files().any(func(it): return FileAccess.file_exists(it))
|
||||
|
||||
func toggle() -> Array[String]:
|
||||
var errors := get_error_messages()
|
||||
if not errors.is_empty():
|
||||
return errors
|
||||
|
||||
var enable := not is_enabled()
|
||||
|
||||
var uid_files := get_files().map(func(it): return it + ".uid")
|
||||
var renames = (get_files() + uid_files).map(func(it):
|
||||
if enable: return [it + INACTIVE_SUFFIX, it]
|
||||
else: return [it, it + INACTIVE_SUFFIX]
|
||||
)
|
||||
|
||||
for rename in renames:
|
||||
var result := DirAccess.rename_absolute(rename[0], rename[1])
|
||||
if result != OK:
|
||||
errors.append(
|
||||
"Failed rename \"%s\" -> \"%s\"; reason: %s" %
|
||||
[rename[0], rename[1], error_string(result)]
|
||||
)
|
||||
return errors
|
||||
|
||||
class Rapier2DPhysicsDriverToggle extends PhysicsDriverToggle:
|
||||
func get_name() -> String:
|
||||
return "Rapier2D"
|
||||
|
||||
func get_files() -> Array[String]:
|
||||
return [
|
||||
"res://addons/netfox.extras/physics/rapier_driver_2d.gd",
|
||||
]
|
||||
|
||||
func get_error_messages() -> Array[String]:
|
||||
if not ClassDB.class_exists("RapierPhysicsServer2D"):
|
||||
return ["Rapier physics is not available! Is the extension installed?"]
|
||||
return []
|
||||
|
||||
class Rapier3DPhysicsDriverToggle extends PhysicsDriverToggle:
|
||||
func get_name() -> String:
|
||||
return "Rapier3D"
|
||||
|
||||
func get_files() -> Array[String]:
|
||||
return [
|
||||
"res://addons/netfox.extras/physics/rapier_driver_3d.gd",
|
||||
]
|
||||
|
||||
func get_error_messages() -> Array[String]:
|
||||
if not ClassDB.class_exists("RapierPhysicsServer3D"):
|
||||
return ["Rapier physics is not available! Is the extension installed?"]
|
||||
return []
|
||||
|
||||
class GodotPhysicsDriverToggle extends PhysicsDriverToggle:
|
||||
func get_name() -> String:
|
||||
return "Godot"
|
||||
|
||||
func get_files() -> Array[String]:
|
||||
return [
|
||||
"res://addons/netfox.extras/physics/godot_driver_3d.gd",
|
||||
"res://addons/netfox.extras/physics/godot_driver_2d.gd"
|
||||
]
|
||||
|
||||
func get_error_messages() -> Array[String]:
|
||||
if not PhysicsServer3D.has_method("space_step") or not PhysicsServer2D.has_method("space_step"):
|
||||
return ["Physics stepping is not available! Is this the right Godot build?"]
|
||||
return []
|
||||
|
||||
static func all() -> Array[PhysicsDriverToggle]:
|
||||
return [
|
||||
Rapier2DPhysicsDriverToggle.new(),
|
||||
Rapier3DPhysicsDriverToggle.new(),
|
||||
GodotPhysicsDriverToggle.new()
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
uid://bu4ppfj0ovkbr
|
||||
@@ -0,0 +1,86 @@
|
||||
extends Node
|
||||
|
||||
class_name PhysicsDriver
|
||||
|
||||
# Physics driver based on netfox ticks
|
||||
# Step physics in time with netfox and participates in rollback
|
||||
|
||||
var physics_space: RID
|
||||
var snapshots: Dictionary = {}
|
||||
|
||||
# Number of physics steps to take per network tick
|
||||
@export var physics_factor: int = 2
|
||||
# Snapshot and Rollback entire physics space.
|
||||
@export var rollback_physics_space: bool = true
|
||||
|
||||
func _enter_tree():
|
||||
#regular ticks
|
||||
NetworkTime.before_tick.connect(before_tick)
|
||||
NetworkTime.after_tick_loop.connect(after_tick_loop)
|
||||
|
||||
#rollback ticks
|
||||
if rollback_physics_space:
|
||||
NetworkRollback.on_prepare_tick.connect(on_prepare_tick)
|
||||
NetworkRollback.on_process_tick.connect(on_process_tick)
|
||||
|
||||
func _exit_tree():
|
||||
NetworkTime.before_tick.disconnect(before_tick)
|
||||
NetworkTime.after_tick_loop.disconnect(after_tick_loop)
|
||||
|
||||
#rollback ticks
|
||||
if NetworkRollback.on_prepare_tick.is_connected(on_prepare_tick):
|
||||
NetworkRollback.on_prepare_tick.disconnect(on_prepare_tick)
|
||||
NetworkRollback.on_process_tick.disconnect(on_process_tick)
|
||||
|
||||
func _ready() -> void:
|
||||
_init_physics_space()
|
||||
|
||||
# Emitted before a tick is run.
|
||||
func before_tick(_delta: float, tick: int) -> void:
|
||||
_snapshot_space(tick)
|
||||
step_physics(_delta)
|
||||
|
||||
func on_prepare_tick(tick: int) -> void:
|
||||
if NetworkRollback._rollback_from == tick:
|
||||
# First tick of rollback loop, rewind
|
||||
_rollback_space(tick)
|
||||
else:
|
||||
# Subsequent ticks are re-writing history.
|
||||
_snapshot_space(tick)
|
||||
|
||||
func on_process_tick(_tick: int) -> void:
|
||||
step_physics(NetworkTime.ticktime)
|
||||
|
||||
func after_tick_loop() -> void:
|
||||
# Remove old snapshots
|
||||
for i in snapshots.keys():
|
||||
if i < NetworkRollback.history_start:
|
||||
snapshots.erase(i)
|
||||
|
||||
func step_physics(_delta: float) -> void:
|
||||
# Break up physics into smaller steps if needed
|
||||
var frac_delta = _delta / physics_factor
|
||||
var rollback_participants = get_tree().get_nodes_in_group("network_rigid_body")
|
||||
for i in range(physics_factor):
|
||||
for net_rigid_body in rollback_participants:
|
||||
net_rigid_body._physics_rollback_tick(frac_delta, NetworkTime.tick)
|
||||
|
||||
_physics_step(frac_delta)
|
||||
|
||||
## Override this method to initialize the physics space.
|
||||
func _init_physics_space() -> void:
|
||||
pass
|
||||
|
||||
## Override this method to take one step in the physics space.
|
||||
## [br][br]
|
||||
## It should also flush and update all Godot nodes
|
||||
func _physics_step(_delta) -> void:
|
||||
pass
|
||||
|
||||
## Override this method to record the current state of the physics space.
|
||||
func _snapshot_space(_tick: int) -> void:
|
||||
pass
|
||||
|
||||
## Override this method to restore the physics space to a previous state.
|
||||
func _rollback_space(_tick) -> void:
|
||||
pass
|
||||
@@ -0,0 +1 @@
|
||||
uid://dcws6qk4hxtun
|
||||
@@ -0,0 +1,18 @@
|
||||
extends PhysicsDriver
|
||||
|
||||
class_name RapierDriver2D
|
||||
|
||||
func _init_physics_space() -> void:
|
||||
physics_space = get_viewport().world_2d.space
|
||||
PhysicsServer2D.space_set_active(physics_space, false)
|
||||
|
||||
func _physics_step(delta) -> void:
|
||||
RapierPhysicsServer2D.space_step(physics_space, delta)
|
||||
RapierPhysicsServer2D.space_flush_queries(physics_space)
|
||||
|
||||
func _snapshot_space(tick: int) -> void:
|
||||
snapshots[tick] = RapierPhysicsServer2D.export_binary(physics_space)
|
||||
|
||||
func _rollback_space(tick) -> void:
|
||||
if snapshots.has(tick):
|
||||
RapierPhysicsServer2D.import_binary(physics_space, snapshots[tick])
|
||||
@@ -0,0 +1 @@
|
||||
uid://bcc28dvk0pufe
|
||||
@@ -0,0 +1,18 @@
|
||||
extends PhysicsDriver
|
||||
|
||||
class_name RapierDriver3D
|
||||
|
||||
func _init_physics_space() -> void:
|
||||
physics_space = get_viewport().world_3d.space
|
||||
PhysicsServer3D.space_set_active(physics_space, false)
|
||||
|
||||
func _physics_step(delta) -> void:
|
||||
RapierPhysicsServer3D.space_step(physics_space, delta)
|
||||
RapierPhysicsServer3D.space_flush_queries(physics_space)
|
||||
|
||||
func _snapshot_space(tick: int) -> void:
|
||||
snapshots[tick] = RapierPhysicsServer3D.export_binary(physics_space)
|
||||
|
||||
func _rollback_space(tick) -> void:
|
||||
if snapshots.has(tick):
|
||||
RapierPhysicsServer3D.import_binary(physics_space, snapshots[tick])
|
||||
@@ -0,0 +1 @@
|
||||
uid://bo4lplbeepibj
|
||||
Reference in New Issue
Block a user