Skip to content

Commit

Permalink
Pokemon Crystal: Review changes
Browse files Browse the repository at this point in the history
  • Loading branch information
AliceMousie committed Jun 13, 2024
1 parent c4ea26d commit 1629f8e
Show file tree
Hide file tree
Showing 11 changed files with 85 additions and 99 deletions.
10 changes: 1 addition & 9 deletions worlds/pokemon_crystal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 1 addition & 3 deletions worlds/pokemon_crystal/locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

if TYPE_CHECKING:
from . import PokemonCrystalWorld
else:
PokemonCrystalWorld = object


class PokemonCrystalLocation(Location):
Expand Down Expand Up @@ -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")
Expand Down
6 changes: 2 additions & 4 deletions worlds/pokemon_crystal/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand All @@ -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")
Expand Down
10 changes: 4 additions & 6 deletions worlds/pokemon_crystal/moves.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand Down
8 changes: 3 additions & 5 deletions worlds/pokemon_crystal/pokemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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
Expand Down
20 changes: 11 additions & 9 deletions worlds/pokemon_crystal/regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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)
Expand Down
4 changes: 1 addition & 3 deletions worlds/pokemon_crystal/rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@

if TYPE_CHECKING:
from . import PokemonCrystalWorld
else:
PokemonCrystalWorld = object


class PokemonCrystalProcedurePatch(APProcedurePatch, APTokenMixin):
Expand All @@ -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:
Expand Down
106 changes: 57 additions & 49 deletions worlds/pokemon_crystal/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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))

Expand Down
6 changes: 2 additions & 4 deletions worlds/pokemon_crystal/trainers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@

if TYPE_CHECKING:
from . import PokemonCrystalWorld
else:
PokemonCrystalWorld = object


def is_rival_starter_pokemon(trainer_name, trainer_data, index):
Expand All @@ -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):
Expand All @@ -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():
Expand Down
Loading

0 comments on commit 1629f8e

Please sign in to comment.