extends Node enum TEAMS { DEFENCE, ATTACK, 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) signal player_stopped_interacting(id: 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 = 1 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) func defuse_win() -> void: if not multiplayer.is_server(): return bomb_timer.stop() end_round(TEAMS.DEFENCE) @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 and round_state != ROUND_STATES.AFTER_PLANT: 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] ## (Optional) position - position to spawn [br] ## (Optional) impulse - impulse to apply [br] ## (Optional but required for each other) [br] ## ammo, remaining_ammo, slot - data for dropped weapon [br] ## for more, see dyn_objects_spawner.gd func spawn(data: Dictionary) -> void: spawn_internal.rpc_id(1,data) @rpc("any_peer","call_local","reliable") func spawn_internal(data: Dictionary) -> void: if multiplayer.is_server() == false: printerr(str(multiplayer.get_remote_sender_id())+" tried to spawn internally on "+str(multiplayer.get_unique_id())) return var object = dynamic_objects_spawner.spawn(data) if data.has("position"): object.global_position = data.position func despawn(path: NodePath): despawn_internal.rpc_id(1,path) @rpc("any_peer","call_local","reliable") func despawn_internal(path: NodePath) -> void: if multiplayer.is_server() == false: printerr(str(multiplayer.get_remote_sender_id())+" tried to despawn internally on "+str(multiplayer.get_unique_id())) return get_node(path).queue_free() func shoot(damage: int,distance: float = 100) -> void: if multiplayer.get_unique_id() == 1: shoot_internal(1,damage,distance) else: shoot_internal.rpc_id(1,multiplayer.get_unique_id(),damage,distance) @rpc("any_peer","call_local","reliable") func shoot_internal(id:int , damage: int, distance: float) -> void: if multiplayer.is_server() == false: return var player: Player = player_nodes[id] var player_camera: Camera3D = player.get_node("Camera3D") var space: PhysicsDirectSpaceState3D = player.get_world_3d().direct_space_state var endpoint: Vector3 = player_camera.global_position - player_camera.global_basis.z * distance var ray = PhysicsRayQueryParameters3D.create(player_camera.global_position,endpoint,1) ray.exclude = [player.get_rid()] ray.collide_with_areas = false match player.team: TEAMS.DEFENCE: ray.collision_mask |= ATTACK_LAYER TEAMS.ATTACK: ray.collision_mask |= DEFENCE_LAYER _: ray.collision_mask |= ATTACK_LAYER | DEFENCE_LAYER var collision = space.intersect_ray(ray) if collision != {} and collision["collider"] is Player: collision["collider"].take_damage.rpc_id(int(collision["collider"].name),damage) func interact() -> void: if multiplayer.get_unique_id() == 1: interact_internal(1) else: interact_internal.rpc_id(1,multiplayer.get_unique_id()) @rpc("any_peer","call_local","reliable") func interact_internal(id:int) -> void: if multiplayer.is_server() == false: return var player: Player = player_nodes[id] var player_camera: Camera3D = player.get_node("Camera3D") var space: PhysicsDirectSpaceState3D = player.get_world_3d().direct_space_state var endpoint: Vector3 = player_camera.global_position - player_camera.global_basis.z * 3 var ray = PhysicsRayQueryParameters3D.create(player_camera.global_position,endpoint,1) ray.exclude = [player.get_rid()] ray.collide_with_areas = false ray.collision_mask |= 64 var collision = space.intersect_ray(ray) if collision != {} and collision["collider"] is Interactible: collision["collider"].interaction_start(id) func stop_interact(): if multiplayer.get_unique_id() == 1: stop_interact_internal(1) else: stop_interact_internal.rpc_id(1,multiplayer.get_unique_id()) @rpc("any_peer","call_local","reliable") func stop_interact_internal(id: int) -> void: if multiplayer.is_server() == false: return player_stopped_interacting.emit(id) func is_on_site() -> bool: for plant in plants: if plant.is_player_on_site(multiplayer.get_unique_id()): return true return false func get_site() -> PlantSite: for plant in plants: if plant.is_player_on_site(multiplayer.get_unique_id()): return plant return null func kill_site(site: StringName) -> void: if multiplayer.is_server() == false: return for body in plant_deadzones[site].get_overlapping_bodies(): if body is Player: body.kill_request.rpc()