weapon system rework

This commit is contained in:
Rendo 2025-11-26 16:32:09 +05:00
commit 30b01100f0
27 changed files with 352 additions and 190 deletions

View file

@ -26,6 +26,8 @@ const MAX_HP = 100
@export var TOGGLE_CROUCH: bool = true
@export var WALK_MODIFIER: float = 0.5
@export var crouched: bool = false:
set(value):
if value != crouched and stand_up_area.has_overlapping_bodies() == false:
@ -36,6 +38,8 @@ const MAX_HP = 100
get:
return crouched
@onready var TEMP_start_pos = global_position
var potential_crouched: bool = crouched
func _enter_tree() -> void:
@ -89,4 +93,5 @@ func _input(event: InputEvent) -> void:
crouched = false
func die() -> void:
queue_free()
global_position = TEMP_start_pos
hp = MAX_HP

View file

@ -1,6 +1,5 @@
extends Node
class_name StateMachine
@export var current_state: State
@ -19,18 +18,36 @@ func on_transition_required(to: StringName):
push_warning("Incorrect state request: " + to)
return
change_state(states[to])
func change_state(to_state: State) -> void:
if current_state != null:
current_state.exit()
current_state = to_state
current_state.enter()
update_remote_machines.rpc(to_state.name)
@rpc("authority","call_local","unreliable")
func clear_state():
if current_state == null:
return
current_state.exit()
current_state = null
@rpc("authority","call_remote","unreliable")
func update_remote_machines(to: StringName) -> void:
if current_state != null:
current_state.exit()
current_state = states[to]
current_state.enter()
func _process(delta: float) -> void:
if current_state == null:
push_error("State is not set")
return
current_state.update(delta)
func _physics_process(delta: float) -> void:
if current_state == null:
push_error("State is not set")
return
current_state.physics_update(delta)

View file

@ -1,12 +1,13 @@
@abstract
extends Node
class_name State
signal transition(to: StringName)
@abstract func enter() -> void
@abstract func exit() -> void
@abstract func update(delta: float) -> void
@abstract func physics_update(delta: float) -> void
func update(delta: float) -> void:
pass
func physics_update(delta: float) -> void:
pass

View file

@ -0,0 +1,27 @@
extends StateMachine
class_name SubStateMachine
@export var enter_state: State
func enter() -> void:
change_state(enter_state)
func exit() -> void:
clear_state.rpc()
func update(delta: float) -> void:
if current_state == null:
return
current_state.update(delta)
func physics_update(delta: float) -> void:
if current_state == null:
return
current_state.physics_update(delta)
func _process(_delta: float) -> void:
pass
func _physics_process(_delta: float) -> void:
pass

View file

@ -0,0 +1 @@
uid://btc5vyvccdqfp

View file

@ -1,93 +0,0 @@
extends Usable
@export var max_ammo: int
@export var semi_auto: bool
@export var emptyable: bool
@export var damage: int
@export var firerate: float
@export var prefix: String
@export var raycast: RayCast3D
var ammo_amount: int
var fire_timer: Timer
var state_locked: bool
func _ready() -> void:
fire_timer = Timer.new()
fire_timer.wait_time = firerate
ammo_amount = max_ammo
add_child(fire_timer)
if not semi_auto:
fire_timer.timeout.connect(fire)
@rpc("authority","call_local","reliable")
func use_begin() -> void:
if fire_timer.is_stopped() == false:
return
fire_timer.start()
fire_timer.one_shot = true if semi_auto else false
fire()
@rpc("authority","call_local","reliable")
func use_end() -> void:
fire_timer.one_shot = true
func fire() -> void:
if ammo_amount == 0 or state_locked:
if not semi_auto:
fire_timer.stop()
return
ammo_amount -= 1
system.animation_player.stop()
system.animation_player.play(prefix + with_empty_suffix("_shoot"))
system.animation_player.queue(prefix + with_empty_suffix("_idle"))
if raycast.is_colliding():
raycast.get_collider().hp -= damage
func alternate_state() -> void:
pass
func switch_mode() -> void:
pass
func enter() -> void:
system.animation_player.animation_changed.connect(on_animation_changed)
state_locked = true
system.animation_player.stop()
system.animation_player.play(prefix+with_empty_suffix("_intro"))
system.animation_player.queue(prefix+with_empty_suffix("_idle"))
func exit() -> void:
system.animation_player.animation_changed.disconnect(on_animation_changed)
func on_animation_changed(old_animation: StringName,_new_animation: StringName) -> void:
if old_animation == prefix + with_empty_suffix("_reload"):
state_locked = false
ammo_amount = max_ammo
if old_animation == prefix + with_empty_suffix("_intro"):
state_locked = false
func _input(event: InputEvent) -> void:
if not system.is_multiplayer_authority() or not in_use: return
if event.is_action_pressed("plr_reload"):
init_reload.rpc()
@rpc("call_local","authority","reliable")
func init_reload():
if ammo_amount == max_ammo or state_locked:
return
state_locked = true
system.animation_player.play(prefix + with_empty_suffix("_reload"))
system.animation_player.queue(prefix + "_idle")
ammo_amount = max_ammo
func with_empty_suffix(animation):
return (animation+"_empty") if emptyable and ammo_amount == 0 else animation

View file

@ -1 +0,0 @@
uid://c76n6qgu6o4hn

View file

@ -0,0 +1,26 @@
extends WeaponState
@export var emptyable: bool
func enter() -> void:
machine.animation_player.play(with_morphems("idle"))
func exit() -> void:
pass
func _input(event: InputEvent) -> void:
if not machine.is_multiplayer_authority(): return
if event.is_action_pressed("plr_reload"):
init_reload()
func use_begin() -> void:
transition.emit("Shoot")
func init_reload():
if machine.ammo == machine.max_ammo:
return
transition.emit("Reload")
func with_morphems(animation):
return machine.animation_prefix + (animation+"_empty") if emptyable and machine.ammo == 0 else animation

View file

@ -0,0 +1 @@
uid://ofv4e3dsfe8

View file

@ -0,0 +1,17 @@
extends WeaponState
@export var emptyable: bool
func enter() -> void:
machine.animation_player.play(with_morphems("intro"))
machine.animation_player.animation_finished.connect(on_animation_finished)
func exit() -> void:
machine.animation_player.animation_finished.disconnect(on_animation_finished)
func on_animation_finished(animation):
if animation == with_morphems("intro"):
transition.emit("Idle")
func with_morphems(animation):
return machine.animation_prefix + (animation+"_empty") if emptyable and machine.ammo == 0 else animation

View file

@ -0,0 +1 @@
uid://bmj0bwy2tlian

View file

@ -0,0 +1,18 @@
extends WeaponState
@export var emptyable: bool
func enter() -> void:
machine.animation_player.play(with_morphems("reload"))
machine.animation_player.animation_finished.connect(on_animation_finished)
func exit() -> void:
machine.animation_player.animation_finished.disconnect(on_animation_finished)
func on_animation_finished(animation):
if animation == with_morphems("reload"):
machine.ammo = machine.max_ammo
transition.emit("Idle")
func with_morphems(animation):
return machine.animation_prefix + (animation+"_empty") if emptyable and machine.ammo == 0 else animation

View file

@ -0,0 +1 @@
uid://hmekwp8444ao

View file

@ -0,0 +1,39 @@
extends WeaponState
@export var raycast: RayCast3D
@export var emptyable: bool
@export var damage: int
@export var fire_timer: Timer
func enter() -> void:
fire()
machine.animation_player.animation_finished.connect(on_animation_finished)
func exit() -> void:
machine.animation_player.animation_finished.disconnect(on_animation_finished)
func on_animation_finished(animation):
if animation == with_morphems("shoot"):
transition.emit("Idle")
func use_begin() -> void:
if fire_timer.time_left > 0:
return
fire()
func fire() -> void:
if machine.ammo == 0 or fire_timer.time_left > 0:
return
machine.ammo -= 1
machine.animation_player.stop()
machine.animation_player.play(with_morphems("shoot"))
if raycast.is_colliding():
raycast.get_collider().hp -= damage
fire_timer.start()
func with_morphems(animation):
return machine.animation_prefix + (animation+"_empty") if emptyable and machine.ammo == 0 else animation

View file

@ -0,0 +1 @@
uid://vj13r83l3xyq

View file

@ -1,25 +0,0 @@
@abstract
extends Node
class_name Usable
var system: WeaponSystem
var in_use: bool = false
@rpc("authority","call_local","reliable")
@abstract func use_begin() -> void
@rpc("authority","call_local","reliable")
@abstract func use_end() -> void
@abstract func alternate_state() -> void
# Need to clarify naming; Switch mode like firemode. For different states use
# alternate_state
@abstract func switch_mode() -> void
func enter() -> void:
pass
func exit() -> void:
pass
func update(_delta: float) -> void:
pass
func physics_update(_delta: float) -> void:
pass

View file

@ -1 +0,0 @@
uid://qj4m67w7ps7j

View file

@ -0,0 +1,17 @@
@abstract
extends State
class_name WeaponState
var machine: WeaponSubStateMachine
func use_begin() -> void:
pass
func use_end() -> void:
pass
func alternate_state() -> void:
pass
# Need to clarify naming; Switch mode like firemode. For different states use
# alternate_state
func switch_mode() -> void:
pass

View file

@ -0,0 +1 @@
uid://fd4tm5wexdk

View file

@ -0,0 +1,33 @@
extends SubStateMachine
class_name WeaponSubStateMachine
@export var animation_prefix: StringName
@export var max_ammo: int
@onready var ammo: int = max_ammo
var system: WeaponSystem
var animation_player: AnimationPlayer
func _ready() -> void:
for child in get_children():
if child is WeaponState:
states[child.name] = child
child.machine = self
child.transition.connect(on_transition_required)
else:
push_warning("Child of state machine is not state")
@rpc("authority","call_local","reliable")
func use_begin() -> void:
current_state.use_begin()
@rpc("authority","call_local","reliable")
func use_end() -> void:
current_state.use_end()
func alternate_state() -> void:
current_state.alternate_state()
# Need to clarify naming; Switch mode like firemode. For different states use
# alternate_state
func switch_mode() -> void:
current_state.switch_mode()

View file

@ -0,0 +1 @@
uid://e6lqknfl4ngt

View file

@ -1,16 +1,15 @@
extends Node
class_name WeaponSystem
@export var default_pistol: Usable
@export var default_knife: Usable
@export var default_pistol: WeaponSubStateMachine
@export var default_knife: WeaponSubStateMachine
@export var animation_player: AnimationPlayer
var current_usable: Usable
var current_state: WeaponSubStateMachine
var slots: Dictionary[StringName,Usable] = {
var slots: Dictionary[StringName,WeaponSubStateMachine] = {
"primary": null,
"secondary": default_pistol,
"knife": default_knife,
@ -21,61 +20,63 @@ var slots: Dictionary[StringName,Usable] = {
"ultimate": null
}
signal switched_to(usable: Usable)
signal switched_to(state: WeaponSubStateMachine)
func _ready() -> void:
for child in get_children():
if child is Usable:
if child is WeaponSubStateMachine:
child.system = self
child.animation_player = animation_player
else:
push_warning("Child of weapon system is not ability or weapon")
current_usable = default_pistol
current_state = default_pistol
slots["knife"] = default_knife
slots["secondary"] = default_pistol
current_usable.enter()
current_state.enter()
current_state.in_use = true
func can_add(slot: StringName) -> bool:
return slots.has(slot)
func add(usable: Usable, slot: StringName) -> void:
func add(state: WeaponSubStateMachine, slot: StringName) -> void:
if can_add(slot) == false:
return
add_child(usable)
add_child(state)
slots[slot] = usable
usable.system = self
slots[slot] = state
state.system = self
func switch(to: StringName):
if slots.has(to) == false or slots[to] == null or slots[to] == current_usable:
if slots.has(to) == false or slots[to] == null or slots[to] == current_state:
return
current_usable.exit()
current_usable.in_use = false
current_usable = slots[to]
current_usable.enter()
current_usable.in_use = true
current_state.exit()
current_state.in_use = false
current_state = slots[to]
current_state.enter()
current_state.in_use = true
switched_to.emit(current_usable)
switched_to.emit(current_state)
update_remotes.rpc(to)
#update_remotes.rpc(to)
@rpc("authority","call_remote","reliable")
func update_remotes(to: StringName):
switch(to)
func _process(delta: float) -> void:
if current_usable == null:
if current_state == null:
push_error("State is not set")
return
current_usable.update(delta)
current_state.update(delta)
func _physics_process(delta: float) -> void:
if current_usable == null:
if current_state == null:
push_error("State is not set")
return
current_usable.physics_update(delta)
current_state.physics_update(delta)
func _input(event: InputEvent) -> void:
if is_multiplayer_authority() == false: return
@ -98,11 +99,11 @@ func _input(event: InputEvent) -> void:
switch("knife")
if event.is_action_pressed("plr_fire"):
current_usable.use_begin.rpc()
current_state.use_begin.rpc()
if event.is_action_released("plr_fire"):
current_usable.use_end.rpc()
current_state.use_end.rpc()
if event.is_action_pressed("plr_scope"):
current_usable.alternate_state()
current_state.alternate_state()
if event.is_action_pressed("plr_firemode"):
current_usable.switch_mode()
current_state.switch_mode()