Round system

This commit is contained in:
Rendo 2025-11-29 23:46:16 +05:00
commit 3df8247a84
32 changed files with 573 additions and 123 deletions

View file

@ -0,0 +1,18 @@
extends Label
@export var our_side: bool
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
if our_side:
match Lobby.get_team():
Session.TEAMS.DEFENCE:
text = str(Session.defender_score)
Session.TEAMS.ATTACK, Session.TEAMS.SPECTATE, Session.TEAMS.UNASSIGNED:
text = str(Session.attacker_score)
else:
match Lobby.get_team():
Session.TEAMS.DEFENCE:
text = str(Session.attacker_score)
Session.TEAMS.ATTACK, Session.TEAMS.SPECTATE, Session.TEAMS.UNASSIGNED:
text = str(Session.defender_score)

View file

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

View file

@ -0,0 +1,8 @@
extends Label
func _process(_delta: float) -> void:
var seconds_u = int(round(Session.reference_round_time))
var seconds = seconds_u % 60
@warning_ignore("integer_division")
var minutes = seconds_u / 60
text = str(minutes)+":"+str(seconds).pad_zeros(2)

View file

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

View file

@ -3,11 +3,11 @@ extends Node3D
@export var item: PackedScene
func _ready() -> void:
deferred_spawn.call_deferred()
if multiplayer.is_server():
Session.round_started.connect(spawn)
func deferred_spawn():
func spawn():
if multiplayer.is_server():
var node = item.instantiate()
add_sibling(node,true)
node.global_position = global_position
queue_free()

View file

@ -9,10 +9,15 @@ signal lobby_joined
signal lobby_closed
signal update_teams_state
var in_lobby: bool = false
var attack_team: Array[int] = []
var defence_team: Array[int] = []
var specators_team: Array[int] = []
var win_score = 13
var half_rounds = 12
func _ready() -> void:
multiplayer.peer_disconnected.connect(player_left)
multiplayer.server_disconnected.connect(server_disconnected)
@ -36,6 +41,7 @@ func host() -> void:
multiplayer.multiplayer_peer = peer
lobby_created.emit()
specators_team.append(multiplayer.get_unique_id())
in_lobby = true
func join(ip: String) -> Error:
var peer: ENetMultiplayerPeer = ENetMultiplayerPeer.new()
@ -44,6 +50,7 @@ func join(ip: String) -> Error:
return res
multiplayer.multiplayer_peer = peer
lobby_joined.emit()
in_lobby = true
return Error.OK
func leave() -> void:
@ -52,6 +59,7 @@ func leave() -> void:
defence_team.clear()
specators_team.clear()
lobby_closed.emit()
in_lobby = false
func add_and_sync_peer(id: int) -> void:
if multiplayer.is_server() == false:
@ -100,3 +108,16 @@ func team_switch_notification(id: int, team: int) -> void:
@rpc("authority","call_local","reliable")
func start_game() -> void:
get_tree().change_scene_to_file("res://levels/prototype_scene.tscn")
if multiplayer.is_server():
await get_tree().scene_changed
Session.start_session()
func get_team() -> Session.TEAMS:
var id = multiplayer.get_unique_id()
if attack_team.has(id):
return Session.TEAMS.ATTACK
if defence_team.has(id):
return Session.TEAMS.DEFENCE
if specators_team.has(id):
return Session.TEAMS.SPECTATE
return Session.TEAMS.UNASSIGNED

View file

@ -4,3 +4,6 @@ extends Node
func _ready() -> void:
get_parent().visible = (get_multiplayer_authority() != multiplayer.get_unique_id()) == visible_for_others
func reverse() -> void:
get_parent().visible = not get_parent().visible

View file

@ -6,15 +6,186 @@ enum TEAMS {
SPECTATE,
UNASSIGNED
}
enum ROUND_STATES {
NOT_SET = 0,
BUY = 1,
ROUND = 2,
AFTER_ROUND = 3,
AFTER_PLANT = 4,
AFTER_SESSION = 5,
}
const ATTACK_LAYER: int = 0b10000
const DEFENCE_LAYER: int = 0b100000
signal round_started
signal round_state_changed(state: int)
var player_nodes: Dictionary[int,Player] = {}
var object_containers: Array[ObjectContainer]
var dynamic_objects_spawner: MultiplayerSpawner
var plants: Array[PlantSite]
var plant_deadzones: Dictionary[StringName, Area3D]
var current_round: int = 0
var attacker_score: int = 0
var defender_score: int = 0
var attackers_alive: int = 0
var defenders_alive: int = 0
var bomb_timer: Timer
var round_timer: Timer
var buy_timer: Timer
var round_state: ROUND_STATES
var session_started: bool = false
var reference_round_time: float
func _ready() -> void:
if multiplayer.is_server() == false:
return
bomb_timer = Timer.new()
bomb_timer.wait_time = 45.0
bomb_timer.one_shot = true
bomb_timer.timeout.connect(end_round.bind(TEAMS.ATTACK))
round_timer = Timer.new()
round_timer.wait_time = 150
round_timer.one_shot = true
round_timer.timeout.connect(end_round.bind(TEAMS.DEFENCE))
buy_timer = Timer.new()
buy_timer.wait_time = 15
buy_timer.one_shot = true
buy_timer.timeout.connect(begin_main_stage)
add_child(bomb_timer)
add_child(round_timer)
add_child(buy_timer)
func _process(_delta: float) -> void:
if multiplayer.is_server() == false or not session_started:
return
match round_state:
ROUND_STATES.BUY:
reference_round_time = buy_timer.time_left
ROUND_STATES.ROUND:
reference_round_time = round_timer.time_left
ROUND_STATES.AFTER_PLANT:
reference_round_time = bomb_timer.time_left
_:
reference_round_time = 0
update_clock.rpc(reference_round_time)
@rpc("authority","call_remote","unreliable")
func update_clock(time: float):
reference_round_time = time
@rpc("any_peer","call_remote","reliable")
func start_session() -> void:
if not is_server_request():
return
if multiplayer.is_server():
start_session.rpc()
current_round = 0
attacker_score = 0
defender_score = 0
session_started = true
start_round()
@rpc("any_peer","call_remote","reliable")
func end_session() -> void:
if not is_server_request():
return
if multiplayer.is_server():
end_session.rpc()
bomb_timer.stop()
round_timer.stop()
buy_timer.stop()
object_containers.clear()
session_started = false
@rpc("any_peer","call_remote","reliable")
func start_round() -> void:
if not is_server_request():
return
if multiplayer.is_server():
buy_timer.start()
start_round.rpc()
for container in object_containers:
container.despawn()
attackers_alive = 0
defenders_alive = 0
current_round += 1
round_started.emit.call_deferred()
round_state = ROUND_STATES.BUY
round_state_changed.emit.call_deferred(round_state)
@rpc("any_peer","call_remote","reliable")
func end_round(win_team: int) -> void:
if not is_server_request():
return
if multiplayer.is_server():
get_tree().create_timer(5).timeout.connect(start_round)
end_round.rpc(win_team)
if win_team == TEAMS.DEFENCE:
defender_score += 1
elif win_team == TEAMS.ATTACK:
attacker_score += 1
round_state = ROUND_STATES.AFTER_ROUND
round_state_changed.emit(round_state)
@rpc("any_peer","call_remote","reliable")
func begin_main_stage() -> void:
if not is_server_request():
return
if multiplayer.is_server():
round_timer.start()
begin_main_stage.rpc()
round_state = ROUND_STATES.ROUND
round_state_changed.emit(round_state)
@rpc("any_peer","call_remote","reliable")
func begin_bomb_stage() -> void:
if not is_server_request():
return
if multiplayer.is_server():
bomb_timer.start()
round_timer.stop()
begin_bomb_stage.rpc()
round_state = ROUND_STATES.AFTER_PLANT
round_state_changed.emit(round_state)
@rpc("any_peer","call_local","reliable")
func add_dead(team: int):
if multiplayer.is_server() == false:
return
if team == TEAMS.ATTACK:
attackers_alive -= 1
if attackers_alive == 0 and round_state != ROUND_STATES.AFTER_ROUND:
end_round(TEAMS.DEFENCE)
if team == TEAMS.DEFENCE:
defenders_alive -= 1
if defenders_alive == 0 and round_state != ROUND_STATES.AFTER_ROUND:
end_round(TEAMS.ATTACK)
func is_server_request() -> bool:
return multiplayer.is_server() or multiplayer.get_remote_sender_id() == 1
## Spawns dynamic object at game scene [br]
## Dictionary keys: [br]
## (Required) scene - path/uuid to scene [br]

View file

@ -7,6 +7,14 @@ func _ready() -> void:
if not multiplayer.is_server():
queue_free()
return
Session.round_started.connect(spawn)
func _exit_tree() -> void:
if not multiplayer.is_server():
return
Session.round_started.disconnect(spawn)
func spawn():
match team:
Session.TEAMS.ATTACK:
for attacker in Lobby.attack_team:
@ -23,6 +31,10 @@ func spawn_player(id: int) -> void:
var inst: Player = player.instantiate()
Session.player_nodes[id] = inst
inst.name = str(id)
if team == Session.TEAMS.DEFENCE:
Session.defenders_alive += 1
elif team == Session.TEAMS.ATTACK:
Session.attackers_alive += 1
deferred_setup.bind(inst,team).call_deferred()
@ -34,7 +46,7 @@ func spawn_spectator(id: int) -> void:
deferred_setup.bind(inst,Session.TEAMS.SPECTATE).call_deferred()
func deferred_setup(what: Node3D, new_team: Session.TEAMS):
get_parent().add_child(what)
get_parent().add_child(what,true)
var distance = randf_range(0,spawn_radius)
var angle = randf_range(0,TAU)
var new_position = global_position + Vector3.RIGHT.rotated(Vector3.UP,angle) * distance

View file

@ -0,0 +1,16 @@
extends Node
class_name ObjectContainer
@export var exlusion_list: Array[Node]
func _ready() -> void:
if not multiplayer.is_server(): return
Session.object_containers.append(self)
func despawn():
if not multiplayer.is_server(): return
for child in get_children():
if exlusion_list.has(child):
continue
child.queue_free()

View file

@ -0,0 +1 @@
uid://3i00rp8urth7

View file

@ -0,0 +1,36 @@
extends Camera3D
@export var SENSITIVITY = 0.02
@export var SPEED = 10.0
var active: bool
func _ready() -> void:
if not is_multiplayer_authority():
return
func set_active() -> void:
if not is_multiplayer_authority():
return
active = true
current = true
func _process(delta: float) -> void:
if active == false or not is_multiplayer_authority():
return
var xz_plane = Input.get_vector("plr_strafe_l","plr_strafe_r","plr_forward","plr_back")
var y = Input.get_axis("spc_down","spc_up")
var direction = Vector3(xz_plane.x,y,xz_plane.y)
global_position += global_basis * direction * SPEED * delta
func rotate_camera(x,y) -> void:
rotate_y(x)
rotation.x = clamp(rotation.x + y,-PI/2,PI/2)
orthonormalize()
func _input(event: InputEvent) -> void:
if active == false or not is_multiplayer_authority():
return
if event is InputEventMouseMotion:
rotate_camera(-event.relative.x * SENSITIVITY,-event.relative.y * SENSITIVITY)

View file

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

View file

@ -7,6 +7,7 @@ class_name Player
signal spawned
signal health_changed(to: int)
signal died
const MAX_HP = 100
@ -23,8 +24,6 @@ const MAX_HP = 100
get:
return hp
var TEMP_start_pos
func _enter_tree() -> void:
set_multiplayer_authority(str(name).to_int())
@ -37,9 +36,8 @@ func _physics_process(_delta: float) -> void:
func die() -> void:
if (not is_multiplayer_authority()):
return
global_position = TEMP_start_pos
hp = MAX_HP
Session.add_dead.rpc(team)
died.emit()
@rpc("any_peer","call_local","reliable")
func kill_request() -> void:
@ -51,7 +49,6 @@ func kill_request() -> void:
@rpc("any_peer","call_local","reliable")
func set_after_spawn(start_position: Vector3,new_team: int):
global_position = start_position
TEMP_start_pos = global_position
team = new_team as Session.TEAMS
spawned.emit()

View file

@ -8,6 +8,7 @@ class_name PlayerCamera
var vertical_compensation : float
var compensation_tween: Tween
var compensate: bool = false
var disable: bool = false
var compensation_speed: float
@export var compensation_time: float = 1.0
@export var compensation_delay: float = 0.5
@ -20,6 +21,7 @@ func _ready() -> void:
current = true
func _process(delta: float) -> void:
if disable: return
if compensate:
if abs(vertical_compensation) <= 0.001:
vertical_compensation = 0
@ -28,7 +30,7 @@ func _process(delta: float) -> void:
rotate_camera(0,compensation_speed * delta)
func _input(event: InputEvent) -> void:
if not is_multiplayer_authority():
if not is_multiplayer_authority() or disable:
return
if event is InputEventMouseMotion:
rotate_camera(-event.relative.x * SENSITIVITY,-event.relative.y * SENSITIVITY)

View file

@ -0,0 +1,21 @@
extends Node
class_name PlayerMovement
@export var player: Player
func process_movement(max_speed: float,acceleration: float,deceleration: float,delta: float) -> void:
if is_multiplayer_authority() == false:
return
if Session.round_state == Session.ROUND_STATES.BUY:
player.velocity.x = 0
player.velocity.z = 0
return
var input_dir := Input.get_vector("plr_strafe_r","plr_strafe_l", "plr_back","plr_forward")
var direction := (player.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
if direction:
player.velocity.x = clamp(player.velocity.x + direction.x * acceleration * delta,-max_speed*abs(direction.x),max_speed*abs(direction.x))
player.velocity.z = clamp(player.velocity.z + direction.z * acceleration * delta,-max_speed*abs(direction.z),max_speed*abs(direction.z))
else:
player.velocity.x = move_toward(player.velocity.x, 0, deceleration*delta)
player.velocity.z = move_toward(player.velocity.z, 0, deceleration*delta)

View file

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

View file

@ -14,6 +14,8 @@ func _ready() -> void:
current = true
func _process(delta: float) -> void:
if not is_multiplayer_authority():
return
var xz_plane = Input.get_vector("plr_strafe_l","plr_strafe_r","plr_forward","plr_back")
var y = Input.get_axis("spc_down","spc_up")
@ -26,6 +28,8 @@ func rotate_camera(x,y) -> void:
orthonormalize()
func _input(event: InputEvent) -> void:
if not is_multiplayer_authority():
return
if event is InputEventMouseMotion:
rotate_camera(-event.relative.x * SENSITIVITY,-event.relative.y * SENSITIVITY)

View file

@ -7,6 +7,7 @@ extends State
@export var toggle: bool = false
@export var stand_up_area: Area3D
@export var player: Player
@export var player_movement: PlayerMovement
@export var animation_player: AnimationPlayer
@export var crouch_time: float = 0.1
@export var weapon_system: WeaponSystem
@ -28,15 +29,7 @@ func physics_update(delta: float) -> void:
transition.emit("Fall")
return
var input_dir := Input.get_vector("plr_strafe_r","plr_strafe_l", "plr_back","plr_forward")
var direction := (player.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
var modified_max_speed = max_speed * weapon_system.get_speed_modifier()
if direction:
player.velocity.x = clamp(player.velocity.x + direction.x * acceleration * delta,-modified_max_speed*abs(direction.x),modified_max_speed*abs(direction.x))
player.velocity.z = clamp(player.velocity.z + direction.z * acceleration * delta,-modified_max_speed*abs(direction.z),modified_max_speed*abs(direction.z))
else:
player.velocity.x = move_toward(player.velocity.x, 0, deceleration*delta)
player.velocity.z = move_toward(player.velocity.z, 0, deceleration*delta)
player_movement.process_movement(max_speed * weapon_system.get_speed_modifier(),acceleration,deceleration,delta)
func state_input(event: InputEvent) -> void:
if (toggle == true and event.is_action_pressed("plr_crouch")) or (toggle == false and event.is_action_released("plr_crouch")):

View file

@ -0,0 +1,12 @@
extends State
@export var animation_player: AnimationPlayer
func on_death() -> void:
transition.emit("Death")
func enter() -> void:
animation_player.play("die")
func exit() -> void:
pass

View file

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

View file

@ -1,6 +1,7 @@
extends State
@export var player: Player
@export var player_movement: PlayerMovement
@export var max_speed: float = 5.0
@export var acceleration: float
@export var weapon_system: WeaponSystem
@ -20,11 +21,4 @@ func physics_update(delta: float) -> void:
player.velocity += player.get_gravity() * delta
var input_dir := Input.get_vector("plr_strafe_r","plr_strafe_l", "plr_back","plr_forward")
var direction := (player.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
var modified_max_speed = max_speed * weapon_system.get_speed_modifier()
if direction:
if abs(player.velocity.x + direction.x * acceleration * delta) < abs(modified_max_speed * direction.x):
player.velocity.x += direction.x * acceleration * delta
if abs(player.velocity.z + direction.z * acceleration * delta) < abs(modified_max_speed * direction.z):
player.velocity.z += direction.z * acceleration * delta
player_movement.process_movement(max_speed * weapon_system.get_speed_modifier(),acceleration,0,delta)

View file

@ -5,6 +5,7 @@ extends State
@export var deceleration: float = 200.0
@export var JUMP_VELOCITY: float = 4.5
@export var player: Player
@export var player_movement: PlayerMovement
@export var weapon_system: WeaponSystem
func enter() -> void:
@ -17,7 +18,7 @@ func physics_update(delta: float) -> void:
if not is_multiplayer_authority():
return
if Input.is_action_just_pressed("plr_jump") and player.is_on_floor():
player.velocity.y = JUMP_VELOCITY
player.velocity.y = JUMP_VELOCITY * sign(weapon_system.get_speed_modifier())
transition.emit("Fall")
return
@ -25,15 +26,7 @@ func physics_update(delta: float) -> void:
transition.emit("Fall")
return
var input_dir := Input.get_vector("plr_strafe_r","plr_strafe_l", "plr_back","plr_forward")
var direction := (player.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
var modified_max_speed = max_speed * weapon_system.get_speed_modifier()
if direction:
player.velocity.x = clamp(player.velocity.x + direction.x * acceleration * delta,-modified_max_speed*abs(direction.x),modified_max_speed*abs(direction.x))
player.velocity.z = clamp(player.velocity.z + direction.z * acceleration * delta,-modified_max_speed*abs(direction.z),modified_max_speed*abs(direction.z))
else:
player.velocity.x = move_toward(player.velocity.x, 0, deceleration*delta)
player.velocity.z = move_toward(player.velocity.z, 0, deceleration*delta)
player_movement.process_movement(max_speed * weapon_system.get_speed_modifier(),acceleration,deceleration,delta)
func state_input(event: InputEvent) -> void:
if event.is_action_pressed("plr_crouch"):

View file

@ -5,6 +5,7 @@ extends State
@export var deceleration: float = 100.0
@export var JUMP_VELOCITY: float = 4.5
@export var player: Player
@export var player_movement: PlayerMovement
@export var weapon_system: WeaponSystem
func enter() -> void:
@ -25,15 +26,7 @@ func physics_update(delta: float) -> void:
transition.emit("Fall")
return
var input_dir := Input.get_vector("plr_strafe_r","plr_strafe_l", "plr_back","plr_forward")
var direction := (player.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
var modified_max_speed = max_speed * weapon_system.get_speed_modifier()
if direction:
player.velocity.x = clamp(player.velocity.x + direction.x * acceleration * delta,-modified_max_speed*abs(direction.x),modified_max_speed*abs(direction.x))
player.velocity.z = clamp(player.velocity.z + direction.z * acceleration * delta,-modified_max_speed*abs(direction.z),modified_max_speed*abs(direction.z))
else:
player.velocity.x = move_toward(player.velocity.x, 0, deceleration*delta)
player.velocity.z = move_toward(player.velocity.z, 0, deceleration*delta)
player_movement.process_movement(max_speed * weapon_system.get_speed_modifier(),acceleration,deceleration,delta)
func state_input(event: InputEvent) -> void:
if event.is_action_released("plr_walk"):

View file

@ -4,7 +4,8 @@ var plant: StringName
func _ready() -> void:
if multiplayer.is_server():
$Timer.timeout.connect(on_timeout)
Session.bomb_timer.timeout.connect(on_timeout)
Session.begin_bomb_stage()
func on_timeout():
if multiplayer.is_server() == false:

View file

@ -12,6 +12,8 @@ func exit():
machine.speed_modifier = 1.0
func on_animation_finished(animation: StringName):
if is_multiplayer_authority() == false:
return
if animation == machine.animation_prefix + "plant":
Session.spawn({"scene": active_bomb, "position": machine.player_camera.get_parent().global_position,"plant": Session.get_site().name})
@ -20,5 +22,7 @@ func on_animation_finished(animation: StringName):
return_to_previous.emit()
func state_input(event: InputEvent) -> void:
if is_multiplayer_authority() == false:
return
if event.is_action_released("plr_bomb"):
transition.emit("Idle")

View file

@ -8,6 +8,7 @@ class_name WeaponSystem
var current_state: WeaponSubStateMachine
var last_slot: StringName
var disabled: bool
var slots: Dictionary[StringName,WeaponSubStateMachine] = {
"primary": null,
@ -151,18 +152,21 @@ func check_for_empty() -> void:
func on_ammo_updated() -> void:
ammo_updated.emit(current_state.ammo,current_state.remaining_ammo)
func disable() -> void:
disabled = true
func _process(delta: float) -> void:
if current_state == null:
if current_state == null or disabled:
return
current_state.update(delta)
func _physics_process(delta: float) -> void:
if current_state == null:
if current_state == null or disabled:
return
current_state.physics_update(delta)
func _input(event: InputEvent) -> void:
if is_multiplayer_authority() == false: return
if is_multiplayer_authority() == false or disabled or Session.round_state == Session.ROUND_STATES.BUY: return
if current_state != null:
current_state.state_input(event)