extends Node3D class_name Level enum GameState { WAITING, # Stop state to be used by default, nothing should react to this TAGGER_GROUNDED, # Tagger is on the ground TAGGER_UNGROUNDED, # Tagger is off the ground TAGGER_CHANGE, # Tagger has caught someone, and new person must go to ground } var game_state: GameState = GameState.WAITING var game_tagger: Player signal state_change(old: GameState, new: GameState) signal state_enter_tagger_grounded signal state_leave_tagger_grounded signal state_enter_tagger_ungrounded signal state_leave_tagger_ungrounded signal state_enter_tagger_change(new_tagger: Player) signal state_leave_tagger_change const MIN_PLAYERS := 2 const global_msg_1 := "Waiting for at least {min_players} players to join" const global_msg_2 := "Game is starting soon, {tagger_username} is it!" const global_msg_3 := "{tagger_username} is it! Run!" const global_msg_4 := "{old_tagger_username} caught {tagger_username}!" const global_msg_5 := "{old_tagger_username} left. {tagger_username} is now it!" @rpc("any_peer", "reliable", "call_local") func push_global_message(msg: String): %GlobalMessage.text = msg print(msg) func set_tagger_grounded(v: bool): if v and game_state == GameState.TAGGER_CHANGE: _change_state(GameState.TAGGER_GROUNDED) return if not (game_state == GameState.TAGGER_GROUNDED or game_state == GameState.TAGGER_UNGROUNDED): return if (not v) and game_state == GameState.TAGGER_GROUNDED: _change_state(GameState.TAGGER_UNGROUNDED) if v and game_state == GameState.TAGGER_UNGROUNDED: _change_state(GameState.TAGGER_GROUNDED) func not_enough_players() -> bool: if %Players.get_children().size() < MIN_PLAYERS: game_tagger = null _change_state(GameState.WAITING) push_global_message.rpc(global_msg_1.format({"min_players": MIN_PLAYERS})) return true return false func change_to_random_tagger(): game_tagger = %Players.get_children().pick_random() _change_state(GameState.TAGGER_CHANGE) func tagger_left_game(): if not_enough_players(): return var old_tagger_username := game_tagger.username change_to_random_tagger() var tagger_username := game_tagger.username push_global_message.rpc(global_msg_5.format({ "old_tagger_username": old_tagger_username, "tagger_username": tagger_username})) func set_tagger(who: Player): game_tagger = who _change_state(GameState.TAGGER_CHANGE) func _change_state(new_state: GameState): var id := -1 if game_tagger != null: id = game_tagger.get_multiplayer_authority() _change_state_full.rpc(new_state, id) @rpc("any_peer", "reliable", "call_local") func _change_state_full(new_state: GameState, new_tagger_id: int): game_tagger = null if new_tagger_id != -1: game_tagger = find_player_by_id(new_tagger_id) if game_state == new_state: return if new_state == GameState.WAITING: game_tagger = null state_change.emit(game_state, new_state) if new_state == GameState.TAGGER_GROUNDED: state_enter_tagger_grounded.emit() if new_state == GameState.TAGGER_UNGROUNDED: state_enter_tagger_ungrounded.emit() if new_state == GameState.TAGGER_CHANGE: state_enter_tagger_change.emit(game_tagger) var old_state = game_state game_state = new_state if old_state == GameState.TAGGER_GROUNDED: state_leave_tagger_grounded.emit() if old_state == GameState.TAGGER_UNGROUNDED: state_leave_tagger_ungrounded.emit() if old_state == GameState.TAGGER_CHANGE: state_leave_tagger_change.emit() func tagging_possible(_tagger: Player, tagee: Player) -> bool: if game_state != GameState.TAGGER_GROUNDED and game_state != GameState.TAGGER_UNGROUNDED: return false return true func _ready(): # Hacky way to start level without going though multiplayer screens if get_parent() == get_tree().root: %PlayerSpawner.spawn(multiplayer.get_unique_id()) func player_ground(v: bool, player: Player): if game_tagger == player: set_tagger_grounded(v) func teleport(who: Player): if not who.is_multiplayer_authority(): push_error("Don't have the authority to teleport") return who.position = Vector3.ZERO func player_tag(who: Player, player: Player): if not tagging_possible(player, who) or game_tagger != player: return var old_tagger_username := game_tagger.username var tagger_username := player.username push_global_message.rpc(global_msg_4.format({ "old_tagger_username": old_tagger_username, "tagger_username": tagger_username})) teleport(player) set_tagger(who) func start_game(): change_to_random_tagger() push_global_message.rpc(global_msg_3.format({"tagger_username" : game_tagger.username})) # Called from the server func server_add_player(id: int): %PlayerSpawner.spawn(id) if not not_enough_players() and game_state == GameState.WAITING: start_game() func server_remove_player(id: int): var player := find_player_by_id(id) var tagger_left := (game_tagger == player) await %PlayerSpawner.despawn_player(id) if not_enough_players(): return if tagger_left: tagger_left_game() func client_add_player(player: Player): if not player.is_multiplayer_authority(): return player.ground.connect(player_ground.bind(player)) player.tag.connect(player_tag.bind(player)) func client_remove_player(_node): pass static func find_level(node: Node) -> Level: while not (node is Level): node = node.get_parent() return node func find_player_by_id(id: int) -> Player: return %Players.get_node(str(id))