class_name GameManager extends Node signal wave_started() signal wave_finished() signal base_took_damage(remaining_health: int) signal game_setup signal game_started signal lost_game signal won_game signal rng_seeded signal switch_to_single_player signal switch_to_multi_player signal switch_to_main_menu var root_scene: Node var player_scene: PackedScene = load("res://PCs/hero.tscn") var game_end_scene: PackedScene = load("res://UI/Menus/GameEndScreen/game_end_screen.tscn") var connected_players_nodes: Dictionary = {} var game_active: bool = false var gamemode: GameMode = null var level: Level var enemies: int = 0 var objective_health: int = Data.starting_lives var wave: int var pot: float var UILayer: CanvasLayer var chatbox: Chatbox var wave_limit: int = 20 var shop_chance: float = 0.0 var stats: RoundStats var card_gameplay: bool = false var level_layout: FlowFieldData var level_config: LevelConfig var starting_endless: bool = false var rhythm_notifier: RhythmNotifier #TODO: Create a reference to some generic Lobby object that wraps the multiplayer players list stuff var connected_player_profiles: Dictionary = {} func parse_command(text: String, peer_id: int) -> void: if text.substr(1, 4) == "give": var gift_name: String = text.substr(6) as String var gift: Card = Data.cards[0] for x: Card in Data.cards: if x.display_name == gift_name: gift = x connected_players_nodes[peer_id].hand.add(gift) elif text.substr(1, 2) == "tr": chatbox.append_message("SERVER", Color.TOMATO, "[color=#f7a8b8]t[color=#55cdfc]r[color=#ffffff]a[color=#55cdfc]n[color=#f7a8b8]s [color=#e50000]r[color=#ff8d00]i[color=#ffee00]g[color=#028121]h[color=#004cff]t[color=#760088]s[color=white]!!") elif text.substr(1, 6) == "length": chatbox.append_message("SERVER", Color.TOMATO, str(level.a_star_graph_3d.visualized_path.curve.get_baked_length())) elif text.substr(1, 11) == "random_maze": level.a_star_graph_3d.build_random_maze(50) elif text.substr(1, 13) == "random_towers": level.a_star_graph_3d.place_random_towers(floori(level.a_star_graph_3d.tower_bases.size() / 3.0)) elif text.substr(1, 11) == "set_endless": if is_multiplayer_authority(): networked_set_endless.rpc(true) else: chatbox.append_message("SERVER", Color.TOMATO, "Unable to edit gamemode") elif text.substr(1, 12) == "set_standard": if is_multiplayer_authority(): networked_set_endless.rpc(false) else: chatbox.append_message("SERVER", Color.TOMATO, "Unable to edit gamemode") elif text.substr(1, 11) == "spawn_print": level.printer._on_static_body_3d_button_interacted(0, connected_players_nodes[peer_id].inventory) elif text.substr(1, 10) == "spawn_shop": level.shop.randomize_cards() elif text.substr(1, 7) == "prosper": for id: int in connected_players_nodes: connected_players_nodes[id].currency += 50 elif text.substr(1, 8) == "set_wave": if is_multiplayer_authority(): networked_set_wave.rpc(int(text.substr(10))) else: chatbox.append_message("SERVER", Color.TOMATO, "Unable to set wave") elif text.substr(1, 4) == "seed": chatbox.append_message("SERVER", Color.TOMATO, str(NoiseRandom.noise.seed)) elif text.substr(1, 7) == "unstuck": connected_players_nodes[multiplayer.get_unique_id()].position = level.player_spawns[0].global_position elif text.substr(1, 5) == "blank": connected_players_nodes[multiplayer.get_unique_id()].blank_cassettes += 1 @rpc("reliable", "call_local") func networked_set_wave(wave_number: int) -> void: chatbox.append_message("SERVER", Color.TOMATO, "Set to wave " + str(wave_number)) for player: int in connected_players_nodes: connected_players_nodes[player].hud.set_wave_count(wave_number) wave = wave_number set_upcoming_wave() ##wave_count is number of upcoming waves this function should return func get_upcoming_waves(wave_count: int) -> Array[WaveConfig]: var waves: Array[WaveConfig] = [] waves.append_array(level_config.waves.slice(wave - 1)) if waves.size() < wave_count: var starting_wave: int = wave + waves.size() for x: int in wave_count - waves.size(): var spawn_power: int = WaveManager.calculate_spawn_power(starting_wave + x, connected_players_nodes.size()) var new_wave: WaveConfig = WaveManager.generate_wave(spawn_power, level.enemy_pool, level.enemy_spawns.size()) waves.append(new_wave) return waves func connect_tower_to_music(tower: Tower) -> void: rhythm_notifier.beats(1).connect(tower.shoot) func spawn_level(scene: PackedScene) -> void: level = scene.instantiate() as Level var flow_field: FlowField = FlowField.new() level.flow_field = flow_field level.add_child(flow_field) flow_field.data = FlowFieldTool.load_flow_field_from_disc(level_config.zone.flow_field_data_path) level.load_flow_field() level.game_manager = self for x: EnemySpawner in level.enemy_spawns: x.flow_field = flow_field x.game_manager = self x.enemy_died_callback = enemy_died x.enemy_reached_goal_callback = damage_goal x.enemy_spawned.connect(increase_enemy_count) root_scene.add_child(level) for spawner: EnemySpawner in level.enemy_spawns: spawner.create_path() level.generate_obstacles(level_config.points_blocked) func spawn_players() -> void: var p_i: int = 0 var player_array: Array = connected_player_profiles.keys() player_array.sort() for peer_id: int in player_array: var player: Hero = player_scene.instantiate() as Hero player.placed_tower.connect(connect_tower_to_music) player.name = str(peer_id) level.enemy_goals[0].goal_cleared.connect(player.hud.shield_ui.fade_ui) level.enemy_goals[0].goal_occupied.connect(player.hud.shield_ui.show_ui) player.game_manager = self player.edit_tool.level = level player.hud.map_anchor = level player.blank_cassettes += Data.starting_blanks player.player_name_tag.text = connected_player_profiles[peer_id].display_name player.position = level.player_spawns[p_i].global_position player.profile = connected_player_profiles[peer_id] player.hero_class = Data.characters[connected_player_profiles[peer_id].preferred_class] player.ready_state_changed.connect(ready_player) if peer_id == multiplayer.get_unique_id(): chatbox.opened.connect(player.pause) chatbox.closed.connect(player.unpause) player.set_multiplayer_authority(peer_id) connected_players_nodes[peer_id] = player wave_started.connect(player.enter_fighting_state) wave_finished.connect(player.exit_fighting_state) base_took_damage.connect(player.hud.set_lives_count) root_scene.add_child(player) p_i += 1 func ready_player(player_ready_true: bool) -> void: if !player_ready_true: return var ready_players: int = 0 for key: int in connected_players_nodes: if connected_players_nodes[key].ready_state == false: continue else: ready_players += 1 if ready_players == connected_players_nodes.size(): spawn_enemy_wave() else: chatbox.append_message("SERVER", Color.TOMATO, str(ready_players) + "/" + str(connected_players_nodes.size()) + " Players ready") func spawn_enemy_wave() -> void: level.disable_all_tower_frames() level.flow_field.calculate() for spawn: EnemySpawner in level.enemy_spawns: spawn.visible = false spawn.spawn_wave() wave_started.emit() func set_wave_to_spawners(wave_thing: WaveConfig, wave_number: int) -> void: var spawners: Array[EnemySpawner] = level.enemy_spawns var ground_spawners: Array[EnemySpawner] = [] var air_spawners: Array[EnemySpawner] = [] for spawner: EnemySpawner in spawners: if spawner.type == Data.EnemyType.LAND: ground_spawners.append(spawner) else: air_spawners.append(spawner) var assignment_salt: int = 0 for enemy_group: EnemyGroup in wave_thing.enemy_groups.keys(): assignment_salt += 1 if enemy_group.enemy.target_type == Data.EnemyType.LAND: ground_spawners[wave_thing.enemy_groups[enemy_group]].add_card(enemy_group) else: air_spawners[NoiseRandom.randi_in_range((wave_number * assignment_salt) + assignment_salt, 0, air_spawners.size() - 1)].add_card(enemy_group) func set_upcoming_wave() -> void: if is_multiplayer_authority(): #print(wave) #print(level_config.waves.size()) if wave > level_config.waves.size(): #print("added new wave on top") var spawn_power: int = WaveManager.calculate_spawn_power(wave, connected_players_nodes.size()) var new_wave: WaveConfig = WaveManager.generate_wave(spawn_power, level.enemy_pool, level.enemy_spawns.size()) level_config.waves.append(new_wave) var new_wave: WaveConfig = get_upcoming_waves(1)[0] #print(new_wave) set_wave_to_spawners(new_wave, wave) temp_set_upcoming_wave(new_wave, WaveManager.calculate_pot(wave, connected_players_nodes.size())) func temp_set_upcoming_wave(new_wave: WaveConfig, coins: int) -> void: pot = coins var dict: Dictionary[String, int] = {} for enemy_group: EnemyGroup in new_wave.enemy_groups.keys(): if !dict.has(enemy_group.enemy.title): dict[enemy_group.enemy.title] = 0 dict[enemy_group.enemy.title] += enemy_group.count connected_players_nodes[multiplayer.get_unique_id()].hud.set_upcoming_wave(dict) @rpc("reliable", "call_local") func networked_set_endless(value: bool) -> void: gamemode.endless = value if gamemode.endless: chatbox.append_message("SERVER", Color.TOMATO, "Endless mode enabled!") else: chatbox.append_message("SERVER", Color.TOMATO, "Endless mode disabled!") func increase_enemy_count() -> void: enemies += 1 func enemy_died(enemy: Enemy) -> void: enemies -= 1 for key: int in connected_players_nodes: connected_players_nodes[key].hud.enemy_count_down(enemy) for x: EnemySpawner in level.enemy_spawns: if !x.done_spawning: return if enemies == 0: end_wave() if !gamemode.endless and wave > wave_limit: end(true) func damage_goal(enemy: Enemy, penalty: int) -> void: enemies -= 1 stats.add_enemy_undefeated(wave, enemy) for key: int in connected_players_nodes: connected_players_nodes[key].hud.enemy_count_down(enemy) objective_health -= penalty base_took_damage.emit(objective_health) if objective_health <= 0: end(false) elif enemies == 0: end_wave() if !gamemode.endless and wave > wave_limit: end(true) func end_wave() -> void: #print("wave endedZ") wave += 1 Data.save_data.check_high_score(level_config.display_title, wave, starting_endless) for peer_id: int in connected_players_nodes: var player: Hero = connected_players_nodes[peer_id] as Hero if player.hud.enemy_count < 0: print(level_config.waves[wave - 2]) player.hud.set_wave_count(wave) player.currency += ceili(pot / connected_players_nodes.size()) player.currency += level_config.waves[wave - 2].bonus_cash player.energy = Data.player_energy player.blank_cassettes += 1 if level_config.waves[wave - 2].rewards_blank_cassette else 0 #if wave % 2 == 0: # player.blank_cassettes += 1 if card_gameplay: player.iterate_duration() player.draw_to_hand_size() player.unready_self() for spawn: EnemySpawner in level.enemy_spawns: spawn.visible = true for tower_base: TowerBase in level.walls.values(): if tower_base.has_card: #tower_base.enable_duration_sprites() tower_base.iterate_duration() if is_multiplayer_authority(): if level_config.waves[wave - 2].new_shop: networked_spawn_shop.rpc() #if NoiseRandom.randf_in_range(23 * wave, 0.0, 1.0) <= shop_chance: #networked_spawn_shop.rpc() #shop_chance = 0.0 #else: #shop_chance += 0.09 wave_finished.emit() set_upcoming_wave() @rpc("reliable", "call_local") func networked_spawn_shop() -> void: level.shop.randomize_cards() chatbox.append_message("SERVER", Color.TOMATO, "A shopkeeper has arrived!") func remove_player(peer_id: int) -> void: if connected_players_nodes.has(peer_id): connected_players_nodes[peer_id].queue_free() connected_players_nodes.erase(peer_id) func setup() -> void: #clean up old stuff if level: level.queue_free() for peer_id: int in connected_players_nodes: connected_players_nodes[peer_id].queue_free() connected_players_nodes.clear() #Spawn new stuff spawn_level(level_config.zone.scene) #Set starting parameters game_active = false enemies = 0 objective_health = Data.starting_lives wave = 1 stats = RoundStats.new() wave_limit = level_config.waves.size() rhythm_notifier = RhythmNotifier.new() add_child(rhythm_notifier) rhythm_notifier.bpm = 120 rhythm_notifier.running = true game_setup.emit() @rpc("reliable", "call_local") func set_seed(value: int) -> void: NoiseRandom.set_seed(value) rng_seeded.emit() func start() -> void: if is_multiplayer_authority(): set_seed.rpc(gamemode.rng_seed) else: await rng_seeded #Relies on player list having been decided spawn_players() for peer_id: int in connected_players_nodes: connected_players_nodes[peer_id].currency = ceili(float(Data.starting_cash) / float(connected_players_nodes.size())) if card_gameplay: connected_players_nodes[peer_id].energy = Data.player_energy connected_players_nodes[peer_id].draw_pile.shuffle() connected_players_nodes[peer_id].draw_to_hand_size() #Relies on rng having been seeded set_upcoming_wave() level.flow_field.calculate() level.enemy_spawns[0].update_path() #Start game game_active = true chatbox.append_message("SERVER", Color.TOMATO, "Started with seed: " + str(NoiseRandom.noise.seed)) #networked_spawn_shop.rpc() starting_endless = gamemode.endless game_started.emit() func end(outcome: bool) -> void: if game_active == false: return game_active = false Data.save_data.add_game_outcome(outcome) Data.save_data.save_to_disc() var menu: GameEndScreen = game_end_scene.instantiate() as GameEndScreen menu.game_manager = self match outcome: false: menu.set_outcome_message("You lost...") lost_game.emit() true: menu.won_game() menu.set_outcome_message("You win!") won_game.emit() menu.pressed_continue.connect(continue_with_game) UILayer.add_child(menu) Input.mouse_mode = Input.MOUSE_MODE_VISIBLE connected_players_nodes[multiplayer.get_unique_id()].pause() func continue_with_game() -> void: game_active = true gamemode.endless = true Input.mouse_mode = Input.MOUSE_MODE_CAPTURED connected_players_nodes[multiplayer.get_unique_id()].unpause() #TODO: This shouldn't happen. instead, the wave generator should generate level_config waves #FIXME: this really needs to be changed because otherwise endless mode cant have shit like #stations and shop respawns. it all needs to be part of the one system u know set_upcoming_wave() func quit_to_desktop() -> void: multiplayer.multiplayer_peer.close() multiplayer.multiplayer_peer = null get_tree().quit() func scene_switch_main_menu() -> void: for node: Node in get_children(): node.queue_free() level = null connected_players_nodes.clear() multiplayer.multiplayer_peer.close() multiplayer.multiplayer_peer = null switch_to_main_menu.emit() func scene_switch_to_multiplayer_lobby() -> void: switch_to_multi_player.emit() func scene_switch_to_singleplayer_lobby() -> void: switch_to_single_player.emit()