From 1629f8e6087d02ab311ad155c83fe1a5db20de48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AA=E3=82=B9?= Date: Thu, 13 Jun 2024 13:47:56 +0100 Subject: [PATCH] Pokemon Crystal: Review changes --- worlds/pokemon_crystal/__init__.py | 10 +-- worlds/pokemon_crystal/locations.py | 4 +- worlds/pokemon_crystal/misc.py | 6 +- worlds/pokemon_crystal/moves.py | 10 ++- worlds/pokemon_crystal/pokemon.py | 8 +-- worlds/pokemon_crystal/regions.py | 20 +++--- worlds/pokemon_crystal/rom.py | 4 +- worlds/pokemon_crystal/rules.py | 106 +++++++++++++++------------- worlds/pokemon_crystal/trainers.py | 6 +- worlds/pokemon_crystal/utils.py | 4 +- worlds/pokemon_crystal/wild.py | 6 +- 11 files changed, 85 insertions(+), 99 deletions(-) diff --git a/worlds/pokemon_crystal/__init__.py b/worlds/pokemon_crystal/__init__.py index 259b3e5ad0a7..91fbbda058e6 100644 --- a/worlds/pokemon_crystal/__init__.py +++ b/worlds/pokemon_crystal/__init__.py @@ -34,15 +34,7 @@ class RomFile(settings.UserFilePath): copy_to = "Pokemon - Crystal Version (UE) (V1.0) [C][!].gbc" md5s = ["9f2922b235a5eeb78d65594e82ef5dde"] - class RomStart(str): - """ - Set this to false to never autostart a rom (such as after patching) - True for operating system default program - Alternatively, a path to a program to open the .gb file with - """ - rom_file: RomFile = RomFile(RomFile.copy_to) - rom_start: Union[RomStart, bool] = True class PokemonCrystalWebWorld(WebWorld): @@ -193,7 +185,7 @@ def pre_fill(self) -> None: if self.options.early_fly: # take one of the 3 early badge locations, set it to storm badge storm_loc = self.random.choice([loc for loc in badge_locs if "EarlyBadge" in loc.tags]) - storm_badge = [item for item in badge_items if item.name == "Storm Badge"][0] + storm_badge = next(item for item in badge_items if item.name == "Storm Badge") storm_loc.place_locked_item(storm_badge) badge_locs.remove(storm_loc) badge_items.remove(storm_badge) diff --git a/worlds/pokemon_crystal/locations.py b/worlds/pokemon_crystal/locations.py index f0086bc075ac..fd644b3059c6 100644 --- a/worlds/pokemon_crystal/locations.py +++ b/worlds/pokemon_crystal/locations.py @@ -6,8 +6,6 @@ if TYPE_CHECKING: from . import PokemonCrystalWorld -else: - PokemonCrystalWorld = object class PokemonCrystalLocation(Location): @@ -51,7 +49,7 @@ def reverse_offset_flag(location_id: int) -> int: return location_id - BASE_OFFSET -def create_locations(world: PokemonCrystalWorld, regions: Dict[str, Region]) -> None: +def create_locations(world: "PokemonCrystalWorld", regions: Dict[str, Region]) -> None: exclude = set() if not world.options.randomize_hidden_items: exclude.add("Hidden") diff --git a/worlds/pokemon_crystal/misc.py b/worlds/pokemon_crystal/misc.py index 9f1a04dca05e..e5c22d5d6697 100644 --- a/worlds/pokemon_crystal/misc.py +++ b/worlds/pokemon_crystal/misc.py @@ -2,11 +2,9 @@ if TYPE_CHECKING: from . import PokemonCrystalWorld -else: - PokemonCrystalWorld = object -def misc_activities(world: PokemonCrystalWorld): +def misc_activities(world: "PokemonCrystalWorld"): # Randomize Yes/No answers for Radio Card quiz for i in range(5): world.generated_misc.radio_tower_questions[i] = world.random.choice(["Y", "N"]) @@ -33,7 +31,7 @@ def misc_activities(world: PokemonCrystalWorld): pair[i] = f"{direction}_{new_number}" -def get_misc_spoiler_log(world: PokemonCrystalWorld, write): +def get_misc_spoiler_log(world: "PokemonCrystalWorld", write): radio_tower_answers = " -> ".join( ["YES" if answer == "Y" else "NO" for answer in world.generated_misc.radio_tower_questions]) write(f"Radio Tower Quiz Answers:\n\n{radio_tower_answers}\n\n") diff --git a/worlds/pokemon_crystal/moves.py b/worlds/pokemon_crystal/moves.py index a4959f4b5e47..715ced0252cd 100644 --- a/worlds/pokemon_crystal/moves.py +++ b/worlds/pokemon_crystal/moves.py @@ -6,11 +6,9 @@ if TYPE_CHECKING: from . import PokemonCrystalWorld -else: - PokemonCrystalWorld = object -def randomize_learnset(world: PokemonCrystalWorld, pkmn_name): +def randomize_learnset(world: "PokemonCrystalWorld", pkmn_name): pkmn_data = world.generated_pokemon[pkmn_name] learn_levels = [] for move in pkmn_data.learnset: @@ -48,7 +46,7 @@ def get_random_move(random, move_type=None, attacking=None): return random.choice(move_pool) -def get_tmhm_compatibility(world: PokemonCrystalWorld, pkmn_name): +def get_tmhm_compatibility(world: "PokemonCrystalWorld", pkmn_name): pkmn_data = world.generated_pokemon[pkmn_name] tm_value = world.options.tm_compatibility.value hm_value = world.options.hm_compatibility.value @@ -68,7 +66,7 @@ def get_tmhm_compatibility(world: PokemonCrystalWorld, pkmn_name): return tmhms -def randomize_tms(world: PokemonCrystalWorld): +def randomize_tms(world: "PokemonCrystalWorld"): move_pool = [move_data for move_name, move_data in copy.deepcopy(crystal_data.moves).items() if not move_data.is_hm and move_name not in ["ROCK_SMASH", "NO_MOVE", "STRUGGLE"]] world.random.shuffle(move_pool) @@ -79,7 +77,7 @@ def randomize_tms(world: PokemonCrystalWorld): world.generated_tms[tm_name] = TMHMData(tm_data.tm_num, new_move.type, False, new_move.id) -def get_random_move_from_learnset(world: PokemonCrystalWorld, pokemon, level): +def get_random_move_from_learnset(world: "PokemonCrystalWorld", pokemon, level): move_pool = [move.move for move in world.generated_pokemon[pokemon].learnset if move.level <= level and move.move != "NO_MOVE"] # double learnset pool to dilute HMs slightly diff --git a/worlds/pokemon_crystal/pokemon.py b/worlds/pokemon_crystal/pokemon.py index 955c7bfb09f7..ee9ff40dddfd 100644 --- a/worlds/pokemon_crystal/pokemon.py +++ b/worlds/pokemon_crystal/pokemon.py @@ -8,11 +8,9 @@ if TYPE_CHECKING: from . import PokemonCrystalWorld -else: - PokemonCrystalWorld = object -def randomize_pokemon(world: PokemonCrystalWorld): +def randomize_pokemon(world: "PokemonCrystalWorld"): # follow_evolutions can change types after the pokemon has already been randomized, # so we randomize types before all else if world.options.randomize_types.value: @@ -61,7 +59,7 @@ def randomize_pokemon(world: PokemonCrystalWorld): base_stats=new_base_stats) -def randomize_starters(world: PokemonCrystalWorld): +def randomize_starters(world: "PokemonCrystalWorld"): def get_starter_rival_fights(starter_name): return [(rival_name, rival) for rival_name, rival in world.generated_trainers.items() if rival_name.startswith("RIVAL_" + starter_name)] @@ -110,7 +108,7 @@ def set_rival_fight_starter(rival_name, rival, new_pokemon): world.generated_starter_helditems = new_helditems -def get_random_pokemon(world: PokemonCrystalWorld, types=None, base_only=False): +def get_random_pokemon(world: "PokemonCrystalWorld", types=None, base_only=False): # unown is excluded because it has a tendency to crash the game if types is None or types[0] is None: pokemon_pool = [pkmn_name for pkmn_name, pkmn_data in world.generated_pokemon.items() if diff --git a/worlds/pokemon_crystal/regions.py b/worlds/pokemon_crystal/regions.py index ee4fa9b04080..f659d108c3ec 100644 --- a/worlds/pokemon_crystal/regions.py +++ b/worlds/pokemon_crystal/regions.py @@ -4,13 +4,11 @@ from .data import data from .items import PokemonCrystalItem from .locations import PokemonCrystalLocation -from .options import FreeFlyLocation +from .options import FreeFlyLocation, JohtoOnly from .rules import can_map_card_fly if TYPE_CHECKING: from . import PokemonCrystalWorld -else: - PokemonCrystalWorld = object class RegionData: @@ -34,13 +32,19 @@ class RegionData: 11: "REGION_FUCHSIA_CITY"} -def create_regions(world: PokemonCrystalWorld) -> Dict[str, Region]: +def create_regions(world: "PokemonCrystalWorld") -> Dict[str, Region]: regions: Dict[str, Region] = {} connections: List[Tuple[str, str, str]] = [] johto_only = world.options.johto_only.value + def should_include_region(region): + # check if region should be included per selected Johto Only option + return (region.johto + or johto_only == JohtoOnly.option_off + or (region.silver_cave and johto_only == JohtoOnly.option_include_silver_cave)) + for region_name, region_data in data.regions.items(): - if region_data.johto or not johto_only or (region_data.silver_cave and johto_only == 2): + if should_include_region(region_data): new_region = Region(region_name, world.player, world.multiworld) regions[region_name] = new_region @@ -56,9 +60,7 @@ def create_regions(world: PokemonCrystalWorld) -> Dict[str, Region]: connections.append((f"{region_name} -> {region_exit}", region_name, region_exit)) for name, source, dest in connections: - src_ok = data.regions[source].johto or (data.regions[source].silver_cave and johto_only > 1) or not johto_only - dest_ok = data.regions[dest].johto or (data.regions[dest].silver_cave and johto_only > 1) or not johto_only - if src_ok and dest_ok: + if should_include_region(data.regions[source]) and should_include_region(data.regions[dest]): regions[source].connect(regions[dest], name) regions["Menu"] = Region("Menu", world.player, world.multiworld) @@ -68,7 +70,7 @@ def create_regions(world: PokemonCrystalWorld) -> Dict[str, Region]: return regions -def setup_free_fly(world: PokemonCrystalWorld): +def setup_free_fly(world: "PokemonCrystalWorld"): fly = world.get_region("REGION_FLY") free_fly_location = FLY_REGIONS[world.free_fly_location] fly_region = world.get_region(free_fly_location) diff --git a/worlds/pokemon_crystal/rom.py b/worlds/pokemon_crystal/rom.py index ff4c41330957..29e2daa22a7a 100644 --- a/worlds/pokemon_crystal/rom.py +++ b/worlds/pokemon_crystal/rom.py @@ -10,8 +10,6 @@ if TYPE_CHECKING: from . import PokemonCrystalWorld -else: - PokemonCrystalWorld = object class PokemonCrystalProcedurePatch(APProcedurePatch, APTokenMixin): @@ -30,7 +28,7 @@ def get_source_data(cls) -> bytes: return get_base_rom_as_bytes() -def generate_output(world: PokemonCrystalWorld, output_directory: str, patch: PokemonCrystalProcedurePatch) -> None: +def generate_output(world: "PokemonCrystalWorld", output_directory: str, patch: PokemonCrystalProcedurePatch) -> None: item_texts = [] for location in world.multiworld.get_locations(world.player): if location.address is None: diff --git a/worlds/pokemon_crystal/rules.py b/worlds/pokemon_crystal/rules.py index 997b66d794fa..786f6610fba3 100644 --- a/worlds/pokemon_crystal/rules.py +++ b/worlds/pokemon_crystal/rules.py @@ -3,77 +3,87 @@ from BaseClasses import CollectionState from worlds.generic.Rules import add_rule, set_rule from .data import data +from .options import JohtoOnly if TYPE_CHECKING: from . import PokemonCrystalWorld -else: - PokemonCrystalWorld = object -def can_map_card_fly(state: CollectionState, world: PokemonCrystalWorld): +def can_map_card_fly(state: CollectionState, world: "PokemonCrystalWorld"): if world.options.randomize_pokegear: return state.has("Map Card", world.player) and state.has("Pokegear", world.player) return state.has("EVENT_GOT_MAP_CARD", world.player) and state.has("EVENT_GOT_POKEGEAR", world.player) -def set_rules(world: PokemonCrystalWorld) -> None: - def can_cut(state: CollectionState): - if world.options.hm_badge_requirements == 0: +def set_rules(world: "PokemonCrystalWorld") -> None: + if world.options.hm_badge_requirements == 0: + def can_cut(state: CollectionState): return state.has("HM01 Cut", world.player) and has_badge(state, "hive") - elif world.options.hm_badge_requirements == 1: - return state.has("HM01 Cut", world.player) - else: - return state.has("HM01 Cut", world.player) and (has_badge(state, "hive") or has_badge(state, "cascade")) - def can_fly(state: CollectionState): - if world.options.hm_badge_requirements == 0: + def can_fly(state: CollectionState): return state.has("HM02 Fly", world.player) and has_badge(state, "storm") - elif world.options.hm_badge_requirements == 1: - return state.has("HM02 Fly", world.player) - else: - return state.has("HM02 Fly", world.player) and ( - has_badge(state, "storm") or has_badge(state, "thunder")) - def can_surf(state: CollectionState): - if world.options.hm_badge_requirements == 0: + def can_surf(state: CollectionState): return state.has("HM03 Surf", world.player) and has_badge(state, "fog") - elif world.options.hm_badge_requirements == 1: - return state.has("HM03 Surf", world.player) - else: - return state.has("HM03 Surf", world.player) and (has_badge(state, "fog") or has_badge(state, "soul")) - def can_strength(state: CollectionState): - if world.options.hm_badge_requirements == 0: + def can_strength(state: CollectionState): return state.has("HM04 Strength", world.player) and has_badge(state, "plain") - elif world.options.hm_badge_requirements == 1: - return state.has("HM04 Strength", world.player) - else: - return state.has("HM04 Strength", world.player) and ( - has_badge(state, "plain") or has_badge(state, "rainbow")) - def can_flash(state: CollectionState): - if world.options.hm_badge_requirements == 0: + def can_flash(state: CollectionState): return state.has("HM05 Flash", world.player) and has_badge(state, "zephyr") - elif world.options.hm_badge_requirements == 1: - return state.has("HM05 Flash", world.player) - else: - return state.has("HM05 Flash", world.player) and (has_badge(state, "zephyr") or has_badge(state, "boulder")) - def can_whirlpool(state: CollectionState): - if world.options.hm_badge_requirements == 0: + def can_whirlpool(state: CollectionState): return state.has("HM06 Whirlpool", world.player) and has_badge(state, "glacier") and can_surf(state) - elif world.options.hm_badge_requirements == 1: + + def can_waterfall(state: CollectionState): + return state.has("HM07 Waterfall", world.player) and has_badge(state, "rising") and can_surf(state) + elif world.options.hm_badge_requirements == 1: + def can_cut(state: CollectionState): + return state.has("HM01 Cut", world.player) + + def can_fly(state: CollectionState): + return state.has("HM02 Fly", world.player) + + def can_surf(state: CollectionState): + return state.has("HM03 Surf", world.player) + + def can_strength(state: CollectionState): + return state.has("HM04 Strength", world.player) + + def can_flash(state: CollectionState): + return state.has("HM05 Flash", world.player) + + def can_whirlpool(state: CollectionState): return state.has("HM06 Whirlpool", world.player) and can_surf(state) - else: + + def can_waterfall(state: CollectionState): + return state.has("HM07 Waterfall", world.player) and can_surf(state) + else: + def can_cut(state: CollectionState): + return state.has("HM01 Cut", world.player) and ( + has_badge(state, "hive") or has_badge(state, "cascade")) + + def can_fly(state: CollectionState): + return state.has("HM02 Fly", world.player) and ( + has_badge(state, "storm") or has_badge(state, "thunder")) + + def can_surf(state: CollectionState): + return state.has("HM03 Surf", world.player) and ( + has_badge(state, "fog") or has_badge(state, "soul")) + + def can_strength(state: CollectionState): + return state.has("HM04 Strength", world.player) and ( + has_badge(state, "plain") or has_badge(state, "rainbow")) + + def can_flash(state: CollectionState): + return state.has("HM05 Flash", world.player) and ( + has_badge(state, "zephyr") or has_badge(state, "boulder")) + + def can_whirlpool(state: CollectionState): return state.has("HM06 Whirlpool", world.player) and ( has_badge(state, "glacier") or has_badge(state, "volcano")) and can_surf(state) - def can_waterfall(state: CollectionState): - if world.options.hm_badge_requirements == 0: - return state.has("HM07 Waterfall", world.player) and has_badge(state, "rising") and can_surf(state) - elif world.options.hm_badge_requirements == 1: - return state.has("HM07 Waterfall", world.player) and can_surf(state) - else: + def can_waterfall(state: CollectionState): return state.has("HM07 Waterfall", world.player) and ( has_badge(state, "rising") or has_badge(state, "earth")) and can_surf(state) @@ -525,11 +535,9 @@ def expn(state: CollectionState): set_rule(get_entrance("REGION_VICTORY_ROAD_GATE -> REGION_VICTORY_ROAD"), has_elite_four_badges) - # set_rule(get_entrance("REGION_VICTORY_ROAD_GATE -> REGION_ROUTE_26"), has_elite_four_badges) - # Victory Road - if johto_only() != 1: + if johto_only() != JohtoOnly.option_on: set_rule(get_entrance("REGION_ROUTE_28 -> REGION_VICTORY_ROAD_GATE"), lambda state: state.has("EVENT_OPENED_MT_SILVER", world.player)) diff --git a/worlds/pokemon_crystal/trainers.py b/worlds/pokemon_crystal/trainers.py index fc2aa5a0b408..f1475b6ab2b0 100644 --- a/worlds/pokemon_crystal/trainers.py +++ b/worlds/pokemon_crystal/trainers.py @@ -8,8 +8,6 @@ if TYPE_CHECKING: from . import PokemonCrystalWorld -else: - PokemonCrystalWorld = object def is_rival_starter_pokemon(trainer_name, trainer_data, index): @@ -19,7 +17,7 @@ def is_rival_starter_pokemon(trainer_name, trainer_data, index): return index == len(trainer_data.pokemon) - 1 -def randomize_trainers(world: PokemonCrystalWorld): +def randomize_trainers(world: "PokemonCrystalWorld"): for trainer_name, trainer_data in world.generated_trainers.items(): new_party = trainer_data.pokemon for i, pkmn_data in enumerate(trainer_data.pokemon): @@ -43,7 +41,7 @@ def randomize_trainers(world: PokemonCrystalWorld): world.generated_trainers[trainer_name] = world.generated_trainers[trainer_name]._replace(pokemon=new_party) -def vanilla_trainer_movesets(world: PokemonCrystalWorld): +def vanilla_trainer_movesets(world: "PokemonCrystalWorld"): # if trainers parties are vanilla but learnsets are randomized, # we need to change the predefined trainer movesets to account for this for trainer_name, trainer_data in world.generated_trainers.items(): diff --git a/worlds/pokemon_crystal/utils.py b/worlds/pokemon_crystal/utils.py index 6232b2c13458..3e0370c9d495 100644 --- a/worlds/pokemon_crystal/utils.py +++ b/worlds/pokemon_crystal/utils.py @@ -4,8 +4,6 @@ if TYPE_CHECKING: from . import PokemonCrystalWorld -else: - PokemonCrystalWorld = object def get_random_filler_item(random): @@ -24,7 +22,7 @@ def get_random_filler_item(random): return random.choice(group) -def get_free_fly_location(world: PokemonCrystalWorld): +def get_free_fly_location(world: "PokemonCrystalWorld"): # Ecruteak, Olivine, Cianwood, Mahogany, Blackthorn location_pool = [22, 21, 19, 23, 25] if not world.options.johto_only: diff --git a/worlds/pokemon_crystal/wild.py b/worlds/pokemon_crystal/wild.py index 1b8e53302fc1..1e31ccc3d607 100644 --- a/worlds/pokemon_crystal/wild.py +++ b/worlds/pokemon_crystal/wild.py @@ -5,11 +5,9 @@ if TYPE_CHECKING: from . import PokemonCrystalWorld -else: - PokemonCrystalWorld = object -def randomize_wild_pokemon(world: PokemonCrystalWorld): +def randomize_wild_pokemon(world: "PokemonCrystalWorld"): world.generated_wooper = get_random_pokemon(world) for grass_name, grass_encounters in world.generated_wild.grass.items(): @@ -50,6 +48,6 @@ def randomize_wild_pokemon(world: PokemonCrystalWorld): new_rare.append(encounter._replace(pokemon=get_random_pokemon(world))) -def randomize_static_pokemon(world: PokemonCrystalWorld): +def randomize_static_pokemon(world: "PokemonCrystalWorld"): for static_name, pkmn_data in world.generated_static.items(): world.generated_static[static_name] = pkmn_data._replace(pokemon=get_random_pokemon(world))