diff --git a/src/creeps/creep_spawner.gd b/src/creeps/creep_spawner.gd index e4f489494..53096d713 100644 --- a/src/creeps/creep_spawner.gd +++ b/src/creeps/creep_spawner.gd @@ -38,7 +38,7 @@ var _creep_index: int = 0 func set_player(player: Player): _player = player - _player.get_team().game_lose.connect(_on_game_lose) + _player.game_lose.connect(_on_game_lose) _ground_path = Utils.find_creep_path(player, false) _air_path = Utils.find_creep_path(player, true) diff --git a/src/enums/team_mode.gd b/src/enums/team_mode.gd new file mode 100644 index 000000000..1459ff8a1 --- /dev/null +++ b/src/enums/team_mode.gd @@ -0,0 +1,60 @@ +class_name TeamMode extends Node + + +# This enum defines how players are placed into teams. + + +enum enm { + ONE_PLAYER_PER_TEAM, + TWO_PLAYERS_PER_TEAM, +} + +static var _string_map: Dictionary = { + TeamMode.enm.ONE_PLAYER_PER_TEAM: "ffa", + TeamMode.enm.TWO_PLAYERS_PER_TEAM: "coop", +} + + +static var _display_string_map: Dictionary = { + TeamMode.enm.ONE_PLAYER_PER_TEAM: "FFA", + TeamMode.enm.TWO_PLAYERS_PER_TEAM: "Co-op", +} + + +static var _player_count_max_map: Dictionary = { + TeamMode.enm.ONE_PLAYER_PER_TEAM: 4, + TeamMode.enm.TWO_PLAYERS_PER_TEAM: 8, +} + + +static var _player_count_per_team_map: Dictionary = { + TeamMode.enm.ONE_PLAYER_PER_TEAM: 1, + TeamMode.enm.TWO_PLAYERS_PER_TEAM: 2, +} + + +static func convert_to_string(type: TeamMode.enm): + return _string_map[type] + + +static func from_string(string: String) -> TeamMode.enm: + var key = _string_map.find_key(string) + + if key != null: + return key + else: + push_error("Invalid string: \"%s\". Possible values: %s" % [string, _string_map.values()]) + + return TeamMode.enm.ONE_PLAYER_PER_TEAM + + +static func convert_to_display_string(type: TeamMode.enm): + return _display_string_map[type] + + +static func get_player_count_max(type: TeamMode.enm): + return _player_count_max_map[type] + + +static func get_player_count_per_team(type: TeamMode.enm): + return _player_count_per_team_map[type] diff --git a/src/game_scene/game_scene.gd b/src/game_scene/game_scene.gd index b4b71bc75..6c2decf24 100644 --- a/src/game_scene/game_scene.gd +++ b/src/game_scene/game_scene.gd @@ -281,18 +281,26 @@ func _setup_players(): return # Create teams -# TODO: create an amount of teams which is appropriate for -# the amount of players and selected team mode - var team: Team = Team.make(1) - _team_container.add_team(team) + var team_mode: TeamMode.enm = Globals.get_team_mode() + var player_count_per_team: int = TeamMode.get_player_count_per_team(team_mode) + var player_count: int = peer_id_list.size() + var team_count: int = ceili(player_count * 1.0 / player_count_per_team) + + for i in range(0, team_count): + var team: Team = Team.make(i) + _team_container.add_team(team) var connection_type: Globals.ConnectionType = Globals.get_connect_type() -# TODO: implement different team modes and assign teams -# based on selected team mode for peer_id in peer_id_list: var player_id: int = peer_id_list.find(peer_id) +# NOTE: multiply player id in "1 player per team" mode +# by 2 because the second slot in teams is not +# occupied + if team_mode == TeamMode.enm.ONE_PLAYER_PER_TEAM: + player_id *= 2 + var user_id: String match connection_type: Globals.ConnectionType.ENET: @@ -301,9 +309,21 @@ func _setup_players(): var webrtc_player: OnlineMatch.WebrtcPlayer = OnlineMatch.get_player_by_peer_id(peer_id) user_id = webrtc_player.user_id - var player: Player = team.create_player(player_id, peer_id, user_id) + var player: Player = Player.make(player_id, peer_id, user_id) PlayerManager.add_player(player) +# Assign players to teams + var remaining_player_list: Array[Player] = PlayerManager.get_player_list() + var team_list: Array[Team] = _team_container.get_team_list() + + for team in team_list: + for i in range(0, player_count_per_team): + if remaining_player_list.is_empty(): + break + + var player: Player = remaining_player_list.pop_front() + team.add_player(player) + func _start_game(): _game_start_timer.stop() diff --git a/src/player/player.gd b/src/player/player.gd index e835387c2..db453b5c7 100644 --- a/src/player/player.gd +++ b/src/player/player.gd @@ -12,6 +12,7 @@ signal selected_builder() signal voted_ready() signal roll_was_disabled() signal rolled_starting_towers() +signal game_lose() const STARTING_ELEMENT_COST = 20 @@ -625,6 +626,10 @@ func roll_starting_towers(): rolled_starting_towers.emit() +func emit_game_lose_signal(): + game_lose.emit() + + ######################### ### Private ### ######################### @@ -731,3 +736,20 @@ func _on_selected_unit_tree_exited(unit: Unit): var selected_unit_is_being_removed: bool = _selected_unit == unit if selected_unit_is_being_removed: set_selected_unit(null) + + +# NOTE: either peer_id or user_id has to be defined, +# depending on if connection is Nakama or Enet +static func make(player_id: int, peer_id: int, user_id: String) -> Player: + var player: Player = Preloads.player_scene.instantiate() + player._id = player_id + player._peer_id = peer_id + player._user_id = user_id + +# Add base class Builder as placeholder until the real +# builder is assigned. This builder will have no effects. + var placeholder_builder: Builder = Builder.new() + player._builder = placeholder_builder + player.add_child(placeholder_builder) + + return player diff --git a/src/player/team.gd b/src/player/team.gd index 22dde2ad3..175c106a1 100644 --- a/src/player/team.gd +++ b/src/player/team.gd @@ -66,28 +66,12 @@ func start_next_wave(): _start_wave() -# NOTE: either peer_id or user_id has to be defined, -# depending on if connection is Nakama or Enet -func create_player(player_id: int, peer_id: int, user_id: String) -> Player: - var player: Player = Preloads.player_scene.instantiate() - player._id = player_id +func add_player(player: Player): player._team = self - player._peer_id = peer_id - player._user_id = user_id - _player_list.append(player) - -# Add base class Builder as placeholder until the real -# builder is assigned. This builder will have no effects. - var placeholder_builder: Builder = Builder.new() - player._builder = placeholder_builder - player.add_child(placeholder_builder) - player.wave_spawned.connect(_on_player_wave_spawned) player.wave_finished.connect(_on_player_wave_finished) - return player - func get_id() -> int: return _id @@ -286,6 +270,9 @@ func _do_game_lose(): game_lose.emit() + for player in _player_list: + player.emit_game_lose_signal() + # This function starts the timer only if it's not already # running or if new duration is shorter diff --git a/src/singletons/globals.gd b/src/singletons/globals.gd index 274dc7ddc..f883a1703 100644 --- a/src/singletons/globals.gd +++ b/src/singletons/globals.gd @@ -16,6 +16,7 @@ var _player_mode: PlayerMode.enm = PlayerMode.enm.SINGLEPLAYER var _wave_count: int = 0 var _game_mode: GameMode.enm = GameMode.enm.BUILD var _difficulty: Difficulty.enm = Difficulty.enm.EASY +var _team_mode: TeamMode.enm = TeamMode.enm.ONE_PLAYER_PER_TEAM var _origin_seed: int = 0 var _update_ticks_per_physics_tick: int = 1 var _connection_type: ConnectionType = ConnectionType.ENET @@ -65,6 +66,10 @@ func get_difficulty() -> Difficulty.enm: return _difficulty +func get_team_mode() -> TeamMode.enm: + return _team_mode + + func get_origin_seed() -> int: return _origin_seed diff --git a/src/singletons/player_manager.gd b/src/singletons/player_manager.gd index 0397c5500..e2136656b 100644 --- a/src/singletons/player_manager.gd +++ b/src/singletons/player_manager.gd @@ -63,7 +63,7 @@ func get_player_by_nakama_user_id(user_id: String) -> Player: func get_player_list() -> Array[Player]: - return _player_list + return _player_list.duplicate() func reset(): diff --git a/src/title_screen/configure_singleplayer_menu.gd b/src/title_screen/configure_singleplayer_menu.gd index 9f60f8de5..3fd5a79d4 100644 --- a/src/title_screen/configure_singleplayer_menu.gd +++ b/src/title_screen/configure_singleplayer_menu.gd @@ -24,6 +24,8 @@ func _ready(): _match_config_panel.set_difficulty(cached_difficulty) _match_config_panel.set_game_mode(cached_game_mode) _match_config_panel.set_game_length(cached_game_length) + + _match_config_panel.hide_team_mode_selector() ######################### diff --git a/src/title_screen/lan_match/setup_lan_game.gd b/src/title_screen/lan_match/setup_lan_game.gd index c0cb2a3dd..f3ac1a2eb 100644 --- a/src/title_screen/lan_match/setup_lan_game.gd +++ b/src/title_screen/lan_match/setup_lan_game.gd @@ -175,9 +175,10 @@ func _on_lan_lobby_menu_start_pressed(): var difficulty: Difficulty.enm = _current_match_config.get_difficulty() var game_length: int = _current_match_config.get_game_length() var game_mode: GameMode.enm = _current_match_config.get_game_mode() + var team_mode: TeamMode.enm = _current_match_config.get_team_mode() var origin_seed: int = randi() - _title_screen.start_game.rpc(PlayerMode.enm.MULTIPLAYER, game_length, game_mode, difficulty, origin_seed, Globals.ConnectionType.ENET) + _title_screen.start_game.rpc(PlayerMode.enm.MULTIPLAYER, game_length, game_mode, difficulty, team_mode, origin_seed, Globals.ConnectionType.ENET) func _on_lan_connect_menu_join_pressed(): diff --git a/src/title_screen/match_config.gd b/src/title_screen/match_config.gd index aea864265..cb1561e1e 100644 --- a/src/title_screen/match_config.gd +++ b/src/title_screen/match_config.gd @@ -5,6 +5,7 @@ enum Field { GAME_MODE, DIFFICULTY, GAME_LENGTH, + TEAM_MODE, COUNT, } @@ -14,21 +15,24 @@ enum Field { var _game_mode: GameMode.enm = GameMode.enm.BUILD var _difficulty: Difficulty.enm = Difficulty.enm.BEGINNER var _game_length: int = Constants.WAVE_COUNT_FULL +var _team_mode: TeamMode.enm = TeamMode.enm.ONE_PLAYER_PER_TEAM const KEY_DIFFICULTY: String = "difficulty" const KEY_GAME_MODE: String = "game_mode" const KEY_GAME_LENGTH: String = "game_length" +const KEY_TEAM_MODE: String = "team_mode" ######################### ### Built-in ### ######################### -func _init(game_mode: GameMode.enm, difficulty: Difficulty.enm, game_length: int): +func _init(game_mode: GameMode.enm, difficulty: Difficulty.enm, game_length: int, team_mode: TeamMode.enm): _game_mode = game_mode _difficulty = difficulty _game_length = game_length + _team_mode = team_mode ######################### @@ -47,13 +51,18 @@ func get_game_mode() -> GameMode.enm: return _game_mode +func get_team_mode() -> TeamMode.enm: + return _team_mode + + # TODO: fix this code being duplicated here and in GameStats func get_display_string() -> String: var game_length_string: String = _get_game_length_string(_game_length) var game_mode_string: String = GameMode.convert_to_display_string(_game_mode).capitalize() var difficulty_string: String = Difficulty.convert_to_string(_difficulty).capitalize() + var team_mode_string: String = TeamMode.convert_to_display_string(_team_mode) - var display_string: String = "%s, %s, %s\n" % [game_length_string, game_mode_string, difficulty_string] + var display_string: String = "%s, %s, %s, %s\n" % [game_length_string, game_mode_string, difficulty_string, team_mode_string] return display_string @@ -62,8 +71,9 @@ func get_display_string_rich() -> String: var game_length_string: String = _get_game_length_string(_game_length) var game_mode_string: String = GameMode.convert_to_display_string(_game_mode).capitalize() var difficulty_string: String = Difficulty.convert_to_colored_string(_difficulty) + var team_mode_string: String = TeamMode.convert_to_display_string(_team_mode) - var display_string: String = "[color=GOLD]%s[/color], [color=GOLD]%s[/color], %s\n" % [game_length_string, game_mode_string, difficulty_string] + var display_string: String = "[color=GOLD]%s[/color], [color=GOLD]%s[/color], %s, %s\n" % [game_length_string, game_mode_string, difficulty_string, team_mode_string] return display_string @@ -73,6 +83,7 @@ func convert_to_bytes() -> PackedByteArray: dict[Field.DIFFICULTY] = _difficulty dict[Field.GAME_MODE] = _game_mode dict[Field.GAME_LENGTH] = _game_length + dict[Field.TEAM_MODE] = _team_mode var bytes: PackedByteArray = var_to_bytes(dict) return bytes @@ -88,7 +99,8 @@ static func convert_from_bytes(bytes: PackedByteArray) -> MatchConfig: var game_mode: GameMode.enm = int(dict[Field.GAME_MODE]) as GameMode.enm var difficulty: Difficulty.enm = int(dict[Field.DIFFICULTY]) as Difficulty.enm var game_length: int = int(dict[Field.GAME_LENGTH]) - var match_config: MatchConfig = MatchConfig.new(game_mode, difficulty, game_length) + var team_mode: TeamMode.enm = int(dict[Field.TEAM_MODE]) as TeamMode.enm + var match_config: MatchConfig = MatchConfig.new(game_mode, difficulty, game_length, team_mode) return match_config @@ -98,9 +110,11 @@ func convert_to_dict() -> Dictionary: var difficulty_string: String = Difficulty.convert_to_string(_difficulty) var game_mode_string: String = GameMode.convert_to_string(_game_mode) var game_length_string: String = str(_game_length) + var team_mode_string: String = TeamMode.convert_to_string(_team_mode) dict[KEY_DIFFICULTY] = difficulty_string dict[KEY_GAME_MODE] = game_mode_string dict[KEY_GAME_LENGTH] = game_length_string + dict[KEY_TEAM_MODE] = team_mode_string return dict @@ -111,7 +125,9 @@ static func convert_from_dict(dict: Dictionary) -> MatchConfig: var difficulty_string: String = dict.get(KEY_DIFFICULTY, "") var difficulty: Difficulty.enm = Difficulty.from_string(difficulty_string) var game_length: int = dict.get(KEY_GAME_LENGTH, Constants.WAVE_COUNT_TRIAL) as int - var match_config: MatchConfig = MatchConfig.new(game_mode, difficulty, game_length) + var team_mode_string: String = dict.get(KEY_TEAM_MODE, "") + var team_mode: TeamMode.enm = TeamMode.from_string(team_mode_string) + var match_config: MatchConfig = MatchConfig.new(game_mode, difficulty, game_length, team_mode) return match_config diff --git a/src/title_screen/match_config_panel.gd b/src/title_screen/match_config_panel.gd index 1df3f0147..4ddf232cc 100644 --- a/src/title_screen/match_config_panel.gd +++ b/src/title_screen/match_config_panel.gd @@ -10,6 +10,13 @@ var _combo_index_to_game_length: Dictionary = { @export var _difficulty_combo: OptionButton @export var _game_length_combo: OptionButton @export var _game_mode_combo: OptionButton +@export var _team_mode_label: Label +@export var _team_mode_combo: OptionButton + + +func hide_team_mode_selector(): + _team_mode_label.hide() + _team_mode_combo.hide() func set_difficulty(difficulty: Difficulty.enm): @@ -58,10 +65,18 @@ func get_game_mode() -> GameMode.enm: return game_mode +func get_team_mode() -> TeamMode.enm: + var selected_index: int = _team_mode_combo.get_selected() + var team_mode: TeamMode.enm = selected_index as TeamMode.enm + + return team_mode + + func get_match_config() -> MatchConfig: var game_mode: GameMode.enm = get_game_mode() var difficulty: Difficulty.enm = get_difficulty() var game_length: int = get_game_length() - var match_config: MatchConfig = MatchConfig.new(game_mode, difficulty, game_length) + var team_mode: TeamMode.enm = get_team_mode() + var match_config: MatchConfig = MatchConfig.new(game_mode, difficulty, game_length, team_mode) return match_config diff --git a/src/title_screen/match_config_panel.tscn b/src/title_screen/match_config_panel.tscn index 2202f07e1..cced9665b 100644 --- a/src/title_screen/match_config_panel.tscn +++ b/src/title_screen/match_config_panel.tscn @@ -2,12 +2,14 @@ [ext_resource type="Script" path="res://src/title_screen/match_config_panel.gd" id="1_wksi8"] -[node name="MatchConfigPanel" type="GridContainer" node_paths=PackedStringArray("_difficulty_combo", "_game_length_combo", "_game_mode_combo")] +[node name="MatchConfigPanel" type="GridContainer" node_paths=PackedStringArray("_difficulty_combo", "_game_length_combo", "_game_mode_combo", "_team_mode_label", "_team_mode_combo")] columns = 2 script = ExtResource("1_wksi8") _difficulty_combo = NodePath("DifficultyCombo") _game_length_combo = NodePath("GameLengthCombo") _game_mode_combo = NodePath("GameModeCombo") +_team_mode_label = NodePath("TeamModeLabel") +_team_mode_combo = NodePath("TeamModeCombo") [node name="Label" type="Label" parent="."] layout_mode = 2 @@ -65,3 +67,17 @@ popup/item_1/text = "Random with upgrades" popup/item_1/id = 1 popup/item_2/text = "Totally random" popup/item_2/id = 2 + +[node name="TeamModeLabel" type="Label" parent="."] +layout_mode = 2 +text = "Team mode:" + +[node name="TeamModeCombo" type="OptionButton" parent="."] +layout_mode = 2 +tooltip_text = "Co-op - 2 players per team +FFA - 1 player per team" +selected = 0 +item_count = 2 +popup/item_0/text = "FFA" +popup/item_1/text = "Co-op" +popup/item_1/id = 1 diff --git a/src/title_screen/online/match_card.gd b/src/title_screen/online/match_card.gd index 4202bd317..be5ff52d7 100644 --- a/src/title_screen/online/match_card.gd +++ b/src/title_screen/online/match_card.gd @@ -37,6 +37,7 @@ func get_match_id() -> String: ### Private ### ######################### +# TODO: fix duplication of loading match_config func _get_match_info_text(match_: NakamaAPI.ApiMatch) -> String: var label_string: String = match_.label @@ -58,11 +59,13 @@ func _get_match_info_text(match_: NakamaAPI.ApiMatch) -> String: var game_length_string: String = str(game_length) var game_mode: GameMode.enm = match_config.get_game_mode() var game_mode_string: String = GameMode.convert_to_long_display_string(game_mode).capitalize() + var team_mode: TeamMode.enm = match_config.get_team_mode() + var team_mode_string: String = TeamMode.convert_to_display_string(team_mode) var text: String = "" \ + "%s\n" % difficulty_string \ + "%s waves\n" % game_length_string \ - + "%s\n" % game_mode_string \ + + "%s %s\n" % [game_mode_string, team_mode_string] \ + " \n" \ + "[color=ROYAL_BLUE]Creator: %s[/color]" % host_display_name \ + "" @@ -70,9 +73,23 @@ func _get_match_info_text(match_: NakamaAPI.ApiMatch) -> String: return text +# TODO: fix duplication of loading match_config func _get_player_count_text(match_: NakamaAPI.ApiMatch) -> String: + var label_string: String = match_.label + + var parse_result = JSON.parse_string(label_string) + var parse_failed: bool = parse_result == null + if parse_failed: + return "" + + var label_dict: Dictionary = parse_result + + var match_config: MatchConfig = MatchConfig.convert_from_dict(label_dict) + var team_mode: TeamMode.enm = match_config.get_team_mode() + var player_count_max: int = TeamMode.get_player_count_max(team_mode) + var player_count: int = match_.size - var text: String = "%d/2" % player_count + var text: String = "%d/%d" % [player_count, player_count_max] return text diff --git a/src/title_screen/online/setup_online_game.gd b/src/title_screen/online/setup_online_game.gd index 4305925f1..e0372d59f 100644 --- a/src/title_screen/online/setup_online_game.gd +++ b/src/title_screen/online/setup_online_game.gd @@ -224,13 +224,15 @@ func _on_create_online_match_menu_create_pressed(): var host_display_name: String = Settings.get_setting(Settings.PLAYER_NAME) var creation_time: float = Time.get_unix_time_from_system() var game_version: String = Config.build_version() + var team_mode: TeamMode.enm = _current_match_config.get_team_mode() + var player_count_max: int = TeamMode.get_player_count_max(team_mode) var local_user_id: String = NakamaConnection.get_local_user_id() var match_params_dict: Dictionary = { "host_display_name": host_display_name, "host_user_id": local_user_id, - "player_count_max": 2, + "player_count_max": player_count_max, "is_private": false, "creation_time": creation_time, "game_version": game_version, @@ -390,9 +392,10 @@ func _on_peer_connected(_peer_id: int): var difficulty: Difficulty.enm = _current_match_config.get_difficulty() var game_length: int = _current_match_config.get_game_length() var game_mode: GameMode.enm = _current_match_config.get_game_mode() + var team_mode: TeamMode.enm = _current_match_config.get_team_mode() var origin_seed: int = randi() - _title_screen.start_game.rpc(PlayerMode.enm.MULTIPLAYER, game_length, game_mode, difficulty, origin_seed, Globals.ConnectionType.NAKAMA) + _title_screen.start_game.rpc(PlayerMode.enm.MULTIPLAYER, game_length, game_mode, difficulty, team_mode, origin_seed, Globals.ConnectionType.NAKAMA) func _on_host_created_game_match(game_match_id: String): diff --git a/src/title_screen/title_screen.gd b/src/title_screen/title_screen.gd index c47599115..b8a40dfc4 100644 --- a/src/title_screen/title_screen.gd +++ b/src/title_screen/title_screen.gd @@ -64,13 +64,14 @@ func switch_to_tab(tab: TitleScreen.Tab): # NOTE: this function transitions the game from title screen to game scene. Can be called either by client itself or the host if the game is in multiplayer mode. @rpc("any_peer", "call_local", "reliable") -func start_game(player_mode: PlayerMode.enm, wave_count: int, game_mode: GameMode.enm, difficulty: Difficulty.enm, origin_seed: int, connection_type: Globals.ConnectionType): +func start_game(player_mode: PlayerMode.enm, wave_count: int, game_mode: GameMode.enm, difficulty: Difficulty.enm, team_mode: TeamMode.enm, origin_seed: int, connection_type: Globals.ConnectionType): # NOTE: save game settings into globals so that GameScene # can access them Globals._player_mode = player_mode Globals._difficulty = difficulty Globals._wave_count = wave_count Globals._game_mode = game_mode + Globals._team_mode = team_mode Globals._origin_seed = origin_seed Globals._connection_type = connection_type @@ -111,6 +112,7 @@ func _on_configure_singleplayer_menu_start_button_pressed(): var difficulty: Difficulty.enm = _configure_singleplayer_menu.get_difficulty() var game_length: int = _configure_singleplayer_menu.get_game_length() var game_mode: GameMode.enm = _configure_singleplayer_menu.get_game_mode() + var team_mode: TeamMode.enm = TeamMode.enm.ONE_PLAYER_PER_TEAM var origin_seed: int = randi() var difficulty_string: String = Difficulty.convert_to_string(difficulty) @@ -120,7 +122,7 @@ func _on_configure_singleplayer_menu_start_button_pressed(): Settings.set_setting(Settings.CACHED_GAME_LENGTH, game_length) Settings.flush() - start_game(PlayerMode.enm.SINGLEPLAYER, game_length, game_mode, difficulty, origin_seed, Globals.ConnectionType.ENET) + start_game(PlayerMode.enm.SINGLEPLAYER, game_length, game_mode, difficulty, team_mode, origin_seed, Globals.ConnectionType.ENET) func _on_generic_tab_cancel_pressed():