feat: implement weapon set system (t_p2_weapons)
- WeaponData resource class with configurable weapon properties and pre-configured constants for rifle, pistol, shotgun, and SMG - WeaponDefinitions singleton/static registry with WEAPONS dictionary - WeaponServer node for server-authoritative weapon logic including fire rate cooldowns, ammo tracking, reload state machine, and multi-pellet hit-scan raycasting - Extended WeaponManager with weapon inventory array, weapon switching, per-weapon state persistence, fire_animation_triggered signal
This commit is contained in:
@@ -35,6 +35,13 @@ signal ammo_changed(current: int, max: int)
|
||||
## Emitted when player is killed.
|
||||
signal player_killed()
|
||||
|
||||
## Emitted when the player switches weapons (for animation/UI).
|
||||
signal weapon_switched(index: int, weapon_id: String)
|
||||
|
||||
## Emitted when the current weapon fires (for animation/visual sync).
|
||||
## Connected by animation players or VFX systems on the weapon model.
|
||||
signal fire_animation_triggered()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Exports — Assault Rifle Config
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -70,6 +77,20 @@ var is_reloading: bool = false
|
||||
var _last_shot_time: float = 0.0
|
||||
var _current_spread: float = 0.0
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Multi-weapon inventory state
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
## Array of weapon IDs the player carries (the inventory).
|
||||
var _weapon_inventory: Array[String] = []
|
||||
|
||||
## Index into _weapon_inventory for the currently equipped weapon.
|
||||
var _current_weapon_index: int = -1
|
||||
|
||||
## Per-weapon persistent state saved when switching away, restored on switch-back.
|
||||
## weapon_id -> { ammo, reserve, is_reloading, last_shot_time, current_spread }
|
||||
var _weapon_states: Dictionary = {}
|
||||
|
||||
## Reference to the parent FPSCharacterController (set in _ready).
|
||||
var _controller: FPSCharacterController = null
|
||||
|
||||
@@ -132,6 +153,9 @@ func try_fire(time: float) -> bool:
|
||||
# Increase spread
|
||||
_current_spread = min(_current_spread + spread_per_shot, max_spread)
|
||||
|
||||
# Persist state back to per-weapon dict
|
||||
_save_current_weapon_state()
|
||||
|
||||
# Emit fire signal (for visual/hit marker)
|
||||
# Direction comes from the controller's look direction
|
||||
if _controller:
|
||||
@@ -141,6 +165,7 @@ func try_fire(time: float) -> bool:
|
||||
weapon_fired.emit(origin, direction)
|
||||
|
||||
ammo_changed.emit(current_ammo, max_ammo)
|
||||
fire_animation_triggered.emit()
|
||||
return true
|
||||
|
||||
## Called every frame to recover spread and handle spread animation.
|
||||
@@ -165,6 +190,7 @@ func start_reload() -> void:
|
||||
is_reloading = false
|
||||
|
||||
ammo_changed.emit(current_ammo, max_ammo)
|
||||
_save_current_weapon_state()
|
||||
|
||||
## Called when a hit is confirmed by the server.
|
||||
func on_hit_confirmed(hit_pos: Vector3, damage: float, killed: bool) -> void:
|
||||
@@ -197,9 +223,126 @@ func get_spread_direction(base_direction: Vector3) -> Vector3:
|
||||
|
||||
## Reset weapon state (for respawn).
|
||||
func reset() -> void:
|
||||
current_ammo = max_ammo
|
||||
reserve_ammo = start_ammo
|
||||
is_reloading = false
|
||||
_current_spread = 0.0
|
||||
_last_shot_time = 0.0
|
||||
# Re-initialise all weapon states in inventory
|
||||
for wid in _weapon_inventory:
|
||||
_initialize_weapon_state(wid)
|
||||
# Apply current weapon (or use defaults if no inventory)
|
||||
if _current_weapon_index >= 0 and _current_weapon_index < _weapon_inventory.size():
|
||||
_apply_current_weapon()
|
||||
else:
|
||||
current_ammo = max_ammo
|
||||
reserve_ammo = start_ammo
|
||||
is_reloading = false
|
||||
_current_spread = 0.0
|
||||
_last_shot_time = 0.0
|
||||
ammo_changed.emit(current_ammo, max_ammo)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Weapon inventory management
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
## Add a weapon to the player's inventory.
|
||||
## If no weapon is currently equipped, this one is automatically selected.
|
||||
func add_weapon(weapon_id: String) -> void:
|
||||
if weapon_id in _weapon_inventory:
|
||||
return
|
||||
_weapon_inventory.append(weapon_id)
|
||||
_initialize_weapon_state(weapon_id)
|
||||
if _current_weapon_index < 0:
|
||||
_current_weapon_index = _weapon_inventory.size() - 1
|
||||
_apply_current_weapon()
|
||||
|
||||
## Switch to a weapon by its inventory index.
|
||||
## Returns true if the switch was performed.
|
||||
func switch_weapon(index: int) -> bool:
|
||||
if index < 0 or index >= _weapon_inventory.size():
|
||||
return false
|
||||
if index == _current_weapon_index:
|
||||
return false
|
||||
_save_current_weapon_state()
|
||||
_current_weapon_index = index
|
||||
_apply_current_weapon()
|
||||
weapon_switched.emit(index, _weapon_inventory[index])
|
||||
return true
|
||||
|
||||
## Return the WeaponData for the currently equipped weapon, or null.
|
||||
func current_weapon_data() -> WeaponData:
|
||||
var wid := get_current_weapon_id()
|
||||
if wid.is_empty():
|
||||
return null
|
||||
return WeaponDefinitions.get_weapon(wid)
|
||||
|
||||
## Return the current weapon's ID string, or empty string.
|
||||
func get_current_weapon_id() -> String:
|
||||
if _current_weapon_index >= 0 and _current_weapon_index < _weapon_inventory.size():
|
||||
return _weapon_inventory[_current_weapon_index]
|
||||
return ""
|
||||
|
||||
## Return the number of weapons in the inventory.
|
||||
func get_weapon_count() -> int:
|
||||
return _weapon_inventory.size()
|
||||
|
||||
## Return the list of weapon IDs in the inventory.
|
||||
func get_inventory() -> Array[String]:
|
||||
return _weapon_inventory.duplicate()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Internal: per-weapon state management
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
## Initialise state dict for a weapon with default values.
|
||||
func _initialize_weapon_state(weapon_id: String) -> void:
|
||||
var data := WeaponDefinitions.get_weapon(weapon_id)
|
||||
if data == null:
|
||||
return
|
||||
_weapon_states[weapon_id] = {
|
||||
ammo = data.mag_size,
|
||||
reserve = data.mag_size * 3,
|
||||
is_reloading = false,
|
||||
last_shot_time = 0.0,
|
||||
current_spread = 0.0,
|
||||
}
|
||||
|
||||
## Save the current weapon's runtime state back into _weapon_states.
|
||||
func _save_current_weapon_state() -> void:
|
||||
var wid := get_current_weapon_id()
|
||||
if wid.is_empty() or not (wid in _weapon_states):
|
||||
return
|
||||
_weapon_states[wid] = {
|
||||
ammo = current_ammo,
|
||||
reserve = reserve_ammo,
|
||||
is_reloading = is_reloading,
|
||||
last_shot_time = _last_shot_time,
|
||||
current_spread = _current_spread,
|
||||
}
|
||||
|
||||
## Load the weapon at _current_weapon_index and apply its data + state
|
||||
## to the live export properties and state variables.
|
||||
func _apply_current_weapon() -> void:
|
||||
var wid := get_current_weapon_id()
|
||||
if wid.is_empty():
|
||||
return
|
||||
var data := WeaponDefinitions.get_weapon(wid)
|
||||
if data == null:
|
||||
return
|
||||
|
||||
# Set export defaults from weapon data
|
||||
max_ammo = data.mag_size
|
||||
start_ammo = data.mag_size * 4 # 1 in mag + 3 reserve = 4x
|
||||
fire_rate = 1.0 / max(data.fire_rate, 0.001)
|
||||
reload_time = data.reload_time
|
||||
spread_per_shot = data.spread_degrees * 0.1
|
||||
max_spread = data.spread_degrees * 3.0
|
||||
spread_recovery = data.spread_degrees * 8.0
|
||||
|
||||
# Restore per-weapon state
|
||||
var st: Dictionary = _weapon_states.get(wid, {})
|
||||
if st.is_empty():
|
||||
_initialize_weapon_state(wid)
|
||||
st = _weapon_states.get(wid, {})
|
||||
current_ammo = st.get("ammo", data.mag_size)
|
||||
reserve_ammo = st.get("reserve", data.mag_size * 3)
|
||||
is_reloading = st.get("is_reloading", false)
|
||||
_last_shot_time = st.get("last_shot_time", 0.0)
|
||||
_current_spread = st.get("current_spread", 0.0)
|
||||
|
||||
Reference in New Issue
Block a user