diff --git a/worlds/keymasters_keep/game.py b/worlds/keymasters_keep/game.py index ef4197b3e8ed..735504c1749a 100644 --- a/worlds/keymasters_keep/game.py +++ b/worlds/keymasters_keep/game.py @@ -13,6 +13,7 @@ class AutoGameRegister(type): games: Dict[str, Type[Game]] = dict() metagames: Dict[str, Type[Game]] = dict() + modded_games: Dict[str, Type[Game]] = dict() def __new__(mcs, name: str, bases: Tuple[type, ...], dict_: Dict[str, Any]) -> AutoGameRegister: new_class: Type[Game] = super().__new__(mcs, name, bases, dict_) @@ -22,6 +23,8 @@ def __new__(mcs, name: str, bases: Tuple[type, ...], dict_: Dict[str, Any]) -> A if "is_metagame" in dict_ and dict_["is_metagame"]: mcs.metagames[game_name] = new_class + elif "is_modded_game" in dict_ and dict_["is_modded_game"]: + mcs.modded_games[game_name] = new_class else: mcs.games[game_name] = new_class @@ -36,6 +39,8 @@ class Game(metaclass=AutoGameRegister): platforms_other: Optional[List[KeymastersKeepGamePlatforms]] = None is_metagame: bool = False # Whether the game should be considered a metagame for grouping purposes + is_modded_game: bool = False # Whether the game is a modded version of a base game. For grouping purposes + is_adult_only_or_unrated: bool = True # ESRB AO / PEGI 18 / USK 18 / Unrated? Used for filtering should_autoregister: bool = True # Development flag. Used to prevent AutoGameRegister from registering the game diff --git a/worlds/keymasters_keep/game_objective_generator.py b/worlds/keymasters_keep/game_objective_generator.py index cdd2de1228db..b043e5b04e60 100644 --- a/worlds/keymasters_keep/game_objective_generator.py +++ b/worlds/keymasters_keep/game_objective_generator.py @@ -144,10 +144,19 @@ def _filter_games( game_name: str for game_name in allowable_games: - if game_name not in AutoGameRegister.games and game_name not in AutoGameRegister.metagames: + in_games: bool = game_name in AutoGameRegister.games + in_metagames: bool = game_name in AutoGameRegister.metagames + in_modded_games: bool = game_name in AutoGameRegister.modded_games + + if not in_games and not in_metagames and not in_modded_games: continue - game: Type[Game] = AutoGameRegister.games.get(game_name, AutoGameRegister.metagames.get(game_name)) + game: Type[Game] = AutoGameRegister.games.get( + game_name, AutoGameRegister.metagames.get( + game_name, AutoGameRegister.modded_games.get(game_name) + ) + ) + game_instance: Game = game(archipelago_options=self.archipelago_options) if not include_adult_only_or_unrated_games and game.is_adult_only_or_unrated: diff --git a/worlds/keymasters_keep/games/pokemon_platinum_map_randomizer_game.py b/worlds/keymasters_keep/games/pokemon_platinum_map_randomizer_game.py new file mode 100644 index 000000000000..8a9ab0345212 --- /dev/null +++ b/worlds/keymasters_keep/games/pokemon_platinum_map_randomizer_game.py @@ -0,0 +1,347 @@ +from __future__ import annotations + +from typing import List + +from dataclasses import dataclass + +from ..game import Game +from ..game_objective_template import GameObjectiveTemplate + +from ..enums import KeymastersKeepGamePlatforms + + +@dataclass +class PokemonPlatinumMapRandomizerArchipelagoOptions: + pass + + +class PokemonPlatinumMapRandomizerGame(Game): + name = "Pokémon Platinum Map Randomizer" + platform = KeymastersKeepGamePlatforms.NDS + + platforms_other = None + + is_modded_game = True + is_adult_only_or_unrated = False + + options_cls = PokemonPlatinumMapRandomizerArchipelagoOptions + + def optional_game_constraint_templates(self) -> List[GameObjectiveTemplate]: + return [ + GameObjectiveTemplate( + label="Choose STARTER as your starter", + data={ + "STARTER": (self.starters, 1) + }, + ), + GameObjectiveTemplate( + label="Run only with TYPE type Pokémon in your party (where possible)", + data={ + "TYPE": (self.types, 1) + }, + ), + ] + + def game_objective_templates(self) -> List[GameObjectiveTemplate]: + return [ + GameObjectiveTemplate( + label="Find and beat in order: GYMS", + data={ + "GYMS": (self.gyms, 4) + }, + is_time_consuming=True, + is_difficult=False, + weight=1, + ), + GameObjectiveTemplate( + label="Find and beat in order: ELITE4", + data={ + "ELITE4": (self.elite_4, 3) + }, + is_time_consuming=True, + is_difficult=False, + weight=1, + ), + GameObjectiveTemplate( + label="Find and map: CITIES", + data={ + "CITIES": (self.cities, 4) + }, + is_time_consuming=True, + is_difficult=False, + weight=1, + ), + GameObjectiveTemplate( + label="Find and map: INTERIORS", + data={ + "INTERIORS": (self.interiors, 4) + }, + is_time_consuming=True, + is_difficult=False, + weight=2, + ), + GameObjectiveTemplate( + label="Find and complete: STORY_BEATS", + data={ + "STORY_BEATS": (self.story_beats, 3) + }, + is_time_consuming=True, + is_difficult=False, + weight=2, + ), + GameObjectiveTemplate( + label="Capture a Pokémon at ROUTE", + data={ + "ROUTE": (self.routes, 1) + }, + is_time_consuming=True, + is_difficult=False, + weight=3, + ), + GameObjectiveTemplate( + label="Capture one Pokémon from each of ROUTES and build a team to defeat ELITE4", + data={ + "ROUTES": (self.routes, 3), + "ELITE4": (self.elite_4, 1) + }, + is_time_consuming=True, + is_difficult=True, + weight=1, + ), + GameObjectiveTemplate( + label="Capture one Pokémon from each of ROUTES and build a team to defeat GYM", + data={ + "ROUTES": (self.routes, 3), + "GYM": (self.gyms, 1) + }, + is_time_consuming=True, + is_difficult=True, + weight=1, + ), + GameObjectiveTemplate( + label="Find and unlock HM", + data={ + "HM": (self.hms, 1), + }, + is_time_consuming=True, + is_difficult=False, + weight=1, + ), + ] + + @staticmethod + def cities() -> List[str]: + return [ + "Oreburgh City", + "Floaroma Town", + "Eterna City", + "Hearthome City", + "Solaceon Town", + "Veilstone City", + "Pastoria City", + "Celestic Town", + "Canalave City", + "Snowpoint City", + "SunyShore City", + "Fight Area", + "Survival Area", + "Resort Area", + ] + + @staticmethod + def interiors() -> List[str]: + return [ + "Sandgem PC", + "Jubilife PC", + "Oreburgh PC", + "Floaroma PC", + "Eterna PC", + "Hearthome PC", + "Solaceon PC", + "Veilstone PC", + "Pastoria PC", + "Celestic PC", + "Canalave PC", + "Snowpoint PC", + "SunyShore PC", + "Fight Area PC", + "Survival Area PC", + "Resort Area PC", + "League PC", + "Victory Road PC", + "Sandgem Mart", + "Jubilife Mart", + "Oreburgh Mart", + "Floaroma Mart", + "Eterna Mart", + "Hearthome Mart", + "Solaceon Mart", + "Veilstone Department Store", + "Pastoria Mart", + "Celestic Shop", + "Canalave Mart", + "Snowpoint Mart", + "SunyShore Mart", + "Fight Area Mart", + "Survival Area Mart", + "Resort Area Mart", + "Jubilife TV", + ] + + @staticmethod + def gyms() -> List[str]: + return [ + "Oreburgh", + "Eterna", + "Hearthome", + "Solaceon", + "Veilstone", + "Pastoria", + "Snowpoint", + "Sunyshore", + ] + + @staticmethod + def elite_4() -> List[str]: + return [ + "Aaron", + "Bertha", + "Flint", + "Lucian", + "Cynthia", + ] + + @staticmethod + def starters() -> List[str]: + return [ + "Turtwig", + "Chimchar", + "Piplup", + ] + + @staticmethod + def routes() -> List[str]: + return [ + "Route 201", + "Route 202", + "Route 203", + "Route 204", + "Route 205", + "Route 206", + "Route 207", + "Route 208", + "Route 209", + "Route 210", + "Route 211", + "Route 212", + "Route 213", + "Route 214", + "Route 215", + "Route 216", + "Route 217", + "Route 218", + "Route 219", + "Route 220", + "Route 221", + "Route 222", + "Route 223", + "Route 224", + "Route 225", + "Route 226", + "Route 227", + "Route 228", + "Route 229", + "Route 230", + "Lake Verity (Lakefront)", + "Oreburgh Gate", + "Oreburgh Mine", + "Ravaged Path", + "Valley Windworks", + "Eterna Forest", + "Old Chateau", + "Wayward Cave", + "Mount Coronet", + "Lost Tower", + "Solaceon Ruins", + "Maniac Tunnel", + "Lake Valor (Lakefront)", + "Great Marsh", + "Trophy Garden", + "Fuego Ironworks", + "Iron Island", + "Lake Acuity (Lakefront)", + "Victory Road", + "Stark Mountain", + "Snowpoint Temple", + "Sendoff Spring", + ] + + @staticmethod + def story_beats() -> List[str]: + return [ + "Rival Fight 2 (After Jubilife)", + "Rival Fight 3 (Route 209)", + "Rival Fight 4 (Pastoria City)", + "Rival Fight 5 (Canalave)", + "Rival Fight 6 (Pokemon League)", + "Return Roark (Oreburgh Mine)", + "Defeat the grunts on Floaroma Meadow", + "Liberate Valley Windworks", + "Escort Cheryl (Eterna Forest)", + "Defeat Jupiter (Team Galactic Eterna Building)", + "Escort Mira (Wayward Cave)", + "Defeat Cyrus (Celestic Ruins)", + "Escort Riley (Iron Island)", + "Defeat Saturn (Valor Cavern)", + "Defeat Mars (Lake Verity)", + "Defeat Cyrus (Team Galactic HQ)", + "Defeat Saturn (Team Galactic HQ)", + "Defeat Mars and Jupiter (Spear Pillar)", + "Catch or Defeat Dialga (Spear Pillar)", + "Catch or Defeat Palkia (Spear Pillar)", + "Catch or Defeat Giratina (Spear Pillar)", + "Catch or Defeat Arceus (Spear Pillar)", + "Catch or Defeat Regirock", + "Catch or Defeat Regice", + "Catch or Defeat Registeel", + "Catch or Defeat Regigigas", + "Catch or Defeat Darkrai", + "Escort Marley (Victory Road)", + ] + + @staticmethod + def hms() -> List[str]: + return [ + "HM01, Cut", + "HM02, Fly", + "HM03, Surf", + "HM04, Strength", + "HM05, Flash", + "HM06, Rock Smash", + "HM07, Waterfall", + ] + + @staticmethod + def types() -> List[str]: + return [ + "Normal", + "Fire", + "Water", + "Electric", + "Grass", + "Ice", + "Fighting", + "Poison", + "Ground", + "Flying", + "Psychic", + "Bug", + "Rock", + "Ghost", + "Dragon", + "Dark", + "Steel", + ] + +# Archipelago Options +# ... diff --git a/worlds/keymasters_keep/options.py b/worlds/keymasters_keep/options.py index 7af65ee2a10a..27fb5e1bd765 100644 --- a/worlds/keymasters_keep/options.py +++ b/worlds/keymasters_keep/options.py @@ -212,6 +212,19 @@ class GameMedleyPercentageChance(Range): default = 100 +class MetagameSelection(OptionSet): + """ + Defines the metagame pool to select from. + + All supported metagames are listed. Remove the ones you don't own or want to play. + """ + + display_name: str = "Metagame Selection" + valid_keys = sorted(AutoGameRegister.metagames.keys()) + + default = sorted(AutoGameRegister.metagames.keys()) + + class GameSelection(OptionSet): """ Defines the game pool to select from. @@ -225,17 +238,17 @@ class GameSelection(OptionSet): default = sorted(AutoGameRegister.games.keys()) -class MetagameSelection(OptionSet): +class ModdedGameSelection(OptionSet): """ - Defines the metagame pool to select from. + Defines the modded game pool to select from. - All supported metagames are listed. Remove the ones you don't own or want to play. + All supported modded games are listed. Remove the ones you don't own or want to play. """ - display_name: str = "Metagame Selection" - valid_keys = sorted(AutoGameRegister.metagames.keys()) + display_name: str = "Modded Game Selection" + valid_keys = sorted(AutoGameRegister.modded_games.keys()) - default = sorted(AutoGameRegister.metagames.keys()) + default = sorted(AutoGameRegister.modded_games.keys()) class IncludeAdultOnlyOrUnratedGames(Toggle): @@ -280,7 +293,11 @@ class ExcludedGamesDifficultObjectives(OptionSet): """ display_name: str = "Excluded Games Difficult Objectives" - valid_keys = sorted(AutoGameRegister.games.keys()) + sorted(AutoGameRegister.metagames.keys()) + valid_keys = ( + sorted(AutoGameRegister.games.keys()) + + sorted(AutoGameRegister.metagames.keys()) + + sorted(AutoGameRegister.modded_games.keys()) + ) default = list() @@ -307,7 +324,11 @@ class ExcludedGamesTimeConsumingObjectives(OptionSet): """ display_name: str = "Excluded Games Time-Consuming Objectives" - valid_keys = sorted(AutoGameRegister.games.keys()) + sorted(AutoGameRegister.metagames.keys()) + valid_keys = ( + sorted(AutoGameRegister.games.keys()) + + sorted(AutoGameRegister.metagames.keys()) + + sorted(AutoGameRegister.modded_games.keys()) + ) default = list() @@ -340,6 +361,7 @@ class KeymastersKeepOptions(PerGameCommonOptions, GameArchipelagoOptions): game_medley_percentage_chance: GameMedleyPercentageChance metagame_selection: MetagameSelection game_selection: GameSelection + modded_game_selection: ModdedGameSelection include_adult_only_or_unrated_games: IncludeAdultOnlyOrUnratedGames include_modern_console_games: IncludeModernConsoleGames include_difficult_objectives: IncludeDifficultObjectives @@ -388,6 +410,7 @@ class KeymastersKeepOptions(PerGameCommonOptions, GameArchipelagoOptions): GameMedleyPercentageChance, MetagameSelection, GameSelection, + ModdedGameSelection, ], ), OptionGroup( diff --git a/worlds/keymasters_keep/world.py b/worlds/keymasters_keep/world.py index e2441b6daead..207e00bb7137 100644 --- a/worlds/keymasters_keep/world.py +++ b/worlds/keymasters_keep/world.py @@ -111,6 +111,7 @@ class KeymastersKeepWorld(World): magic_keys_required: int magic_keys_total: int metagame_selection: List[str] + modded_game_selection: List[str] selected_areas: List[KeymastersKeepRegions] selected_magic_keys: List[KeymastersKeepItems] unlocked_areas: int @@ -210,6 +211,7 @@ def generate_early(self) -> None: self.game_selection = list(self.options.game_selection.value) self.metagame_selection = list(self.options.metagame_selection.value) + self.modded_game_selection = list(self.options.modded_game_selection.value) self.include_adult_only_or_unrated_games = bool(self.options.include_adult_only_or_unrated_games) self.include_modern_console_games = bool(self.options.include_modern_console_games) @@ -580,7 +582,11 @@ def _generate_keep(self) -> None: self.area_trials[area] = self.random.sample(possible_trials, trial_count) def _generate_game_objective_data(self) -> None: - game_selection: List[str] = sorted(self.game_selection[:] + self.metagame_selection[:]) + game_selection: List[str] = sorted( + self.game_selection[:] + + self.metagame_selection[:] + + self.modded_game_selection[:] + ) generator: GameObjectiveGenerator = GameObjectiveGenerator( game_selection,