t_p1_hitscan: Add lag-compensated hitscan weapon system
NEW: server/scripts/combat/lag_compensation.gd
- 128-entry ring buffer of player positions per server tick
- rewind_and_raycast(tick, origin, direction, range, exclude):
saves current positions, rewinds to tick-time positions,
performs PhysicsDirectSpaceState3D.intersect_ray(), restores
- Falls back to normal raycast if history missing for tick
- shot_processed signal
NEW: scripts/combat/damage_processor.gd
- Processes WeaponServer hit results: applies damage, tracks kills
- Optional distance-based damage falloff
- player_damaged / player_killed signals
- Health and kill-count queries
MODIFY: server/scripts/weapons/weapon_server.gd
- fire() now takes tick parameter: fire(tick, player_id, ...)
- _perform_hitscan() uses LagCompensation.rewind_and_raycast()
when lag_compensation reference is set and tick >= 0
- Non-compensated fallback path preserved
MODIFY: server/scripts/game_server.gd
- Adds _current_tick counter, incremented each simulation tick
- Creates WeaponServer + LagCompensation + DamageProcessor as children
- record_tick() called before each tick's simulation
- register/unregister_player_node() for player lifecycle
- _on_player_killed() relays kill events via player_damaged signal
MODIFY: scripts/network/server_main.gd
- _spawn_player() registers node with GameServer.register_player_node()
- _despawn_player() unregisters via unregister_player_node()
This commit is contained in:
@@ -43,6 +43,11 @@ var _state: Dictionary = {}
|
||||
## Set automatically if the node enters the tree; can also be injected.
|
||||
var physics_world: World3D = null
|
||||
|
||||
## Optional LagCompensation controller for server-side rewinding.
|
||||
## When set, _perform_hitscan uses lag-compensated raycasting;
|
||||
## otherwise it uses the current-frame physics world directly.
|
||||
var lag_compensation: LagCompensation = null
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Lifecycle
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -208,8 +213,11 @@ func _complete_reload(player_id: int, weapon_id: String, st: Dictionary) -> void
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
## Fire the weapon from the given origin along direction.
|
||||
## Performs a hit-scan raycast through the physics world.
|
||||
## Performs a hit-scan raycast through the physics world with optional
|
||||
## server-side lag compensation rewinding.
|
||||
##
|
||||
## tick: server tick when the shot occurred (used for lag compensation).
|
||||
## Pass -1 to skip lag compensation and use current-frame physics.
|
||||
## hit_result = {
|
||||
## hit: bool,
|
||||
## position: Vector3, # world-space hit point
|
||||
@@ -218,7 +226,7 @@ func _complete_reload(player_id: int, weapon_id: String, st: Dictionary) -> void
|
||||
## weapon_id: String, # the weapon used
|
||||
## }
|
||||
##
|
||||
func fire(player_id: int, weapon_id: String, origin: Vector3, direction: Vector3) -> Dictionary:
|
||||
func fire(tick: int, player_id: int, weapon_id: String, origin: Vector3, direction: Vector3) -> Dictionary:
|
||||
var data := WeaponDefinitions.get_weapon(weapon_id)
|
||||
if data == null:
|
||||
return _miss_result(weapon_id)
|
||||
@@ -241,13 +249,14 @@ func fire(player_id: int, weapon_id: String, origin: Vector3, direction: Vector3
|
||||
st["reload_remaining"] = data.reload_time
|
||||
|
||||
# Perform hit-scan — supports multi-pellet weapons
|
||||
return _perform_hitscan(player_id, weapon_id, data, origin, direction, st)
|
||||
return _perform_hitscan(tick, player_id, weapon_id, data, origin, direction, st)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Internal: hitscan
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func _perform_hitscan(
|
||||
tick: int,
|
||||
player_id: int,
|
||||
weapon_id: String,
|
||||
data: WeaponData,
|
||||
@@ -273,12 +282,26 @@ func _perform_hitscan(
|
||||
|
||||
for i in range(pellets):
|
||||
var dir: Vector3 = _apply_spread(base_direction, spread_rad)
|
||||
var query := PhysicsRayQueryParameters3D.create(origin, origin + dir * max_range)
|
||||
query.exclude = exclude
|
||||
query.collide_with_bodies = true
|
||||
query.collide_with_areas = false
|
||||
|
||||
var result: Dictionary = space_state.intersect_ray(query)
|
||||
# --- Lag-compensated raycast ---
|
||||
# If lag_compensation is available and a valid tick is provided,
|
||||
# rewind to the target tick before raycasting. Otherwise fall
|
||||
# back to the current-frame physics world.
|
||||
var result: Dictionary = {}
|
||||
if lag_compensation != null and tick >= 0:
|
||||
result = lag_compensation.rewind_and_raycast(
|
||||
tick, origin, dir, max_range, exclude
|
||||
)
|
||||
else:
|
||||
# Fallback: no lag compensation — raycast against current positions
|
||||
var query := PhysicsRayQueryParameters3D.create(
|
||||
origin, origin + dir * max_range
|
||||
)
|
||||
query.exclude = exclude
|
||||
query.collide_with_bodies = true
|
||||
query.collide_with_areas = false
|
||||
result = space_state.intersect_ray(query)
|
||||
|
||||
if result.is_empty():
|
||||
continue
|
||||
|
||||
|
||||
Reference in New Issue
Block a user