mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
Pokemon Emerald: Randomize rock smash encounters (#3912)
* Pokemon Emerald: WIP add rock smash encounter randomization * Pokemon Emerald: Refactor encounter data on maps * Pokemon Emerald: Remove unused import * Pokemon Emerald: Swap StrEnum for regular Enum and use .value
This commit is contained in:
@@ -27,6 +27,7 @@ from .pokemon import (get_random_move, get_species_id_by_label, randomize_abilit
|
|||||||
randomize_legendary_encounters, randomize_misc_pokemon, randomize_starters,
|
randomize_legendary_encounters, randomize_misc_pokemon, randomize_starters,
|
||||||
randomize_tm_hm_compatibility,randomize_types, randomize_wild_encounters)
|
randomize_tm_hm_compatibility,randomize_types, randomize_wild_encounters)
|
||||||
from .rom import PokemonEmeraldProcedurePatch, write_tokens
|
from .rom import PokemonEmeraldProcedurePatch, write_tokens
|
||||||
|
from .util import get_encounter_type_label
|
||||||
|
|
||||||
|
|
||||||
class PokemonEmeraldWebWorld(WebWorld):
|
class PokemonEmeraldWebWorld(WebWorld):
|
||||||
@@ -636,32 +637,11 @@ class PokemonEmeraldWorld(World):
|
|||||||
|
|
||||||
spoiler_handle.write(f"\n\nWild Pokemon ({self.player_name}):\n\n")
|
spoiler_handle.write(f"\n\nWild Pokemon ({self.player_name}):\n\n")
|
||||||
|
|
||||||
slot_to_rod_suffix = {
|
|
||||||
0: " (Old Rod)",
|
|
||||||
1: " (Old Rod)",
|
|
||||||
2: " (Good Rod)",
|
|
||||||
3: " (Good Rod)",
|
|
||||||
4: " (Good Rod)",
|
|
||||||
5: " (Super Rod)",
|
|
||||||
6: " (Super Rod)",
|
|
||||||
7: " (Super Rod)",
|
|
||||||
8: " (Super Rod)",
|
|
||||||
9: " (Super Rod)",
|
|
||||||
}
|
|
||||||
|
|
||||||
species_maps = defaultdict(set)
|
species_maps = defaultdict(set)
|
||||||
for map in self.modified_maps.values():
|
for map_data in self.modified_maps.values():
|
||||||
if map.land_encounters is not None:
|
for encounter_type, encounter_data in map_data.encounters.items():
|
||||||
for encounter in map.land_encounters.slots:
|
for i, encounter in enumerate(encounter_data.slots):
|
||||||
species_maps[encounter].add(map.label + " (Land)")
|
species_maps[encounter].add(f"{map_data.label} ({get_encounter_type_label(encounter_type, i)})")
|
||||||
|
|
||||||
if map.water_encounters is not None:
|
|
||||||
for encounter in map.water_encounters.slots:
|
|
||||||
species_maps[encounter].add(map.label + " (Water)")
|
|
||||||
|
|
||||||
if map.fishing_encounters is not None:
|
|
||||||
for slot, encounter in enumerate(map.fishing_encounters.slots):
|
|
||||||
species_maps[encounter].add(map.label + slot_to_rod_suffix[slot])
|
|
||||||
|
|
||||||
lines = [f"{emerald_data.species[species].label}: {', '.join(sorted(maps))}\n"
|
lines = [f"{emerald_data.species[species].label}: {', '.join(sorted(maps))}\n"
|
||||||
for species, maps in species_maps.items()]
|
for species, maps in species_maps.items()]
|
||||||
@@ -675,32 +655,11 @@ class PokemonEmeraldWorld(World):
|
|||||||
if self.options.dexsanity:
|
if self.options.dexsanity:
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
slot_to_rod_suffix = {
|
|
||||||
0: " (Old Rod)",
|
|
||||||
1: " (Old Rod)",
|
|
||||||
2: " (Good Rod)",
|
|
||||||
3: " (Good Rod)",
|
|
||||||
4: " (Good Rod)",
|
|
||||||
5: " (Super Rod)",
|
|
||||||
6: " (Super Rod)",
|
|
||||||
7: " (Super Rod)",
|
|
||||||
8: " (Super Rod)",
|
|
||||||
9: " (Super Rod)",
|
|
||||||
}
|
|
||||||
|
|
||||||
species_maps = defaultdict(set)
|
species_maps = defaultdict(set)
|
||||||
for map in self.modified_maps.values():
|
for map_data in self.modified_maps.values():
|
||||||
if map.land_encounters is not None:
|
for encounter_type, encounter_data in map_data.encounters.items():
|
||||||
for encounter in map.land_encounters.slots:
|
for i, encounter in enumerate(encounter_data.slots):
|
||||||
species_maps[encounter].add(map.label + " (Land)")
|
species_maps[encounter].add(f"{map_data.label} ({get_encounter_type_label(encounter_type, i)})")
|
||||||
|
|
||||||
if map.water_encounters is not None:
|
|
||||||
for encounter in map.water_encounters.slots:
|
|
||||||
species_maps[encounter].add(map.label + " (Water)")
|
|
||||||
|
|
||||||
if map.fishing_encounters is not None:
|
|
||||||
for slot, encounter in enumerate(map.fishing_encounters.slots):
|
|
||||||
species_maps[encounter].add(map.label + slot_to_rod_suffix[slot])
|
|
||||||
|
|
||||||
hint_data[self.player] = {
|
hint_data[self.player] = {
|
||||||
self.location_name_to_id[f"Pokedex - {emerald_data.species[species].label}"]: ", ".join(sorted(maps))
|
self.location_name_to_id[f"Pokedex - {emerald_data.species[species].label}"]: ", ".join(sorted(maps))
|
||||||
|
@@ -5,7 +5,7 @@ defined data (like location labels or usable pokemon species), some cleanup
|
|||||||
and sorting, and Warp methods.
|
and sorting, and Warp methods.
|
||||||
"""
|
"""
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import IntEnum
|
from enum import IntEnum, Enum
|
||||||
import orjson
|
import orjson
|
||||||
from typing import Dict, List, NamedTuple, Optional, Set, FrozenSet, Tuple, Any, Union
|
from typing import Dict, List, NamedTuple, Optional, Set, FrozenSet, Tuple, Any, Union
|
||||||
import pkgutil
|
import pkgutil
|
||||||
@@ -148,14 +148,20 @@ class EncounterTableData(NamedTuple):
|
|||||||
address: int
|
address: int
|
||||||
|
|
||||||
|
|
||||||
|
# class EncounterType(StrEnum): # StrEnum introduced in python 3.11
|
||||||
|
class EncounterType(Enum):
|
||||||
|
LAND = "LAND"
|
||||||
|
WATER = "WATER"
|
||||||
|
FISHING = "FISHING"
|
||||||
|
ROCK_SMASH = "ROCK_SMASH"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MapData:
|
class MapData:
|
||||||
name: str
|
name: str
|
||||||
label: str
|
label: str
|
||||||
header_address: int
|
header_address: int
|
||||||
land_encounters: Optional[EncounterTableData]
|
encounters: Dict[EncounterType, EncounterTableData]
|
||||||
water_encounters: Optional[EncounterTableData]
|
|
||||||
fishing_encounters: Optional[EncounterTableData]
|
|
||||||
|
|
||||||
|
|
||||||
class EventData(NamedTuple):
|
class EventData(NamedTuple):
|
||||||
@@ -348,25 +354,27 @@ def _init() -> None:
|
|||||||
if map_name in IGNORABLE_MAPS:
|
if map_name in IGNORABLE_MAPS:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
land_encounters = None
|
encounter_tables: Dict[EncounterType, EncounterTableData] = {}
|
||||||
water_encounters = None
|
|
||||||
fishing_encounters = None
|
|
||||||
|
|
||||||
if "land_encounters" in map_json:
|
if "land_encounters" in map_json:
|
||||||
land_encounters = EncounterTableData(
|
encounter_tables[EncounterType.LAND] = EncounterTableData(
|
||||||
map_json["land_encounters"]["slots"],
|
map_json["land_encounters"]["slots"],
|
||||||
map_json["land_encounters"]["address"]
|
map_json["land_encounters"]["address"]
|
||||||
)
|
)
|
||||||
if "water_encounters" in map_json:
|
if "water_encounters" in map_json:
|
||||||
water_encounters = EncounterTableData(
|
encounter_tables[EncounterType.WATER] = EncounterTableData(
|
||||||
map_json["water_encounters"]["slots"],
|
map_json["water_encounters"]["slots"],
|
||||||
map_json["water_encounters"]["address"]
|
map_json["water_encounters"]["address"]
|
||||||
)
|
)
|
||||||
if "fishing_encounters" in map_json:
|
if "fishing_encounters" in map_json:
|
||||||
fishing_encounters = EncounterTableData(
|
encounter_tables[EncounterType.FISHING] = EncounterTableData(
|
||||||
map_json["fishing_encounters"]["slots"],
|
map_json["fishing_encounters"]["slots"],
|
||||||
map_json["fishing_encounters"]["address"]
|
map_json["fishing_encounters"]["address"]
|
||||||
)
|
)
|
||||||
|
if "rock_smash_encounters" in map_json:
|
||||||
|
encounter_tables[EncounterType.ROCK_SMASH] = EncounterTableData(
|
||||||
|
map_json["rock_smash_encounters"]["slots"],
|
||||||
|
map_json["rock_smash_encounters"]["address"]
|
||||||
|
)
|
||||||
|
|
||||||
# Derive a user-facing label
|
# Derive a user-facing label
|
||||||
label = []
|
label = []
|
||||||
@@ -398,9 +406,7 @@ def _init() -> None:
|
|||||||
map_name,
|
map_name,
|
||||||
" ".join(label),
|
" ".join(label),
|
||||||
map_json["header_address"],
|
map_json["header_address"],
|
||||||
land_encounters,
|
encounter_tables
|
||||||
water_encounters,
|
|
||||||
fishing_encounters
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Load/merge region json files
|
# Load/merge region json files
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -4,7 +4,8 @@ Functions related to pokemon species and moves
|
|||||||
import functools
|
import functools
|
||||||
from typing import TYPE_CHECKING, Dict, List, Set, Optional, Tuple
|
from typing import TYPE_CHECKING, Dict, List, Set, Optional, Tuple
|
||||||
|
|
||||||
from .data import (NUM_REAL_SPECIES, OUT_OF_LOGIC_MAPS, EncounterTableData, LearnsetMove, SpeciesData, data)
|
from .data import (NUM_REAL_SPECIES, OUT_OF_LOGIC_MAPS, EncounterType, EncounterTableData, LearnsetMove, SpeciesData,
|
||||||
|
MapData, data)
|
||||||
from .options import (Goal, HmCompatibility, LevelUpMoves, RandomizeAbilities, RandomizeLegendaryEncounters,
|
from .options import (Goal, HmCompatibility, LevelUpMoves, RandomizeAbilities, RandomizeLegendaryEncounters,
|
||||||
RandomizeMiscPokemon, RandomizeStarters, RandomizeTypes, RandomizeWildPokemon,
|
RandomizeMiscPokemon, RandomizeStarters, RandomizeTypes, RandomizeWildPokemon,
|
||||||
TmTutorCompatibility)
|
TmTutorCompatibility)
|
||||||
@@ -226,6 +227,42 @@ def randomize_types(world: "PokemonEmeraldWorld") -> None:
|
|||||||
evolutions += [world.modified_species[evo.species_id] for evo in evolution.evolutions]
|
evolutions += [world.modified_species[evo.species_id] for evo in evolution.evolutions]
|
||||||
|
|
||||||
|
|
||||||
|
_encounter_subcategory_ranges: Dict[EncounterType, Dict[range, Optional[str]]] = {
|
||||||
|
EncounterType.LAND: {range(0, 12): None},
|
||||||
|
EncounterType.WATER: {range(0, 5): None},
|
||||||
|
EncounterType.FISHING: {range(0, 2): "OLD_ROD", range(2, 5): "GOOD_ROD", range(5, 10): "SUPER_ROD"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _rename_wild_events(world: "PokemonEmeraldWorld", map_data: MapData, new_slots: List[int], encounter_type: EncounterType):
|
||||||
|
"""
|
||||||
|
Renames the events that correspond to wild encounters to reflect the new species there after randomization
|
||||||
|
"""
|
||||||
|
for i, new_species_id in enumerate(new_slots):
|
||||||
|
# Get the subcategory for rods
|
||||||
|
subcategory_range, subcategory_name = next(
|
||||||
|
(r, sc)
|
||||||
|
for r, sc in _encounter_subcategory_ranges[encounter_type].items()
|
||||||
|
if i in r
|
||||||
|
)
|
||||||
|
subcategory_species = []
|
||||||
|
for k in subcategory_range:
|
||||||
|
if new_slots[k] not in subcategory_species:
|
||||||
|
subcategory_species.append(new_slots[k])
|
||||||
|
|
||||||
|
# Create the name of the location that corresponds to this encounter slot
|
||||||
|
# Fishing locations include the rod name
|
||||||
|
subcategory_str = "" if subcategory_name is None else "_" + subcategory_name
|
||||||
|
encounter_location_index = subcategory_species.index(new_species_id) + 1
|
||||||
|
encounter_location_name = f"{map_data.name}_{encounter_type.value}_ENCOUNTERS{subcategory_str}_{encounter_location_index}"
|
||||||
|
try:
|
||||||
|
# Get the corresponding location and change the event name to reflect the new species
|
||||||
|
slot_location = world.multiworld.get_location(encounter_location_name, world.player)
|
||||||
|
slot_location.item.name = f"CATCH_{data.species[new_species_id].name}"
|
||||||
|
except KeyError:
|
||||||
|
pass # Map probably isn't included; should be careful here about bad encounter location names
|
||||||
|
|
||||||
|
|
||||||
def randomize_wild_encounters(world: "PokemonEmeraldWorld") -> None:
|
def randomize_wild_encounters(world: "PokemonEmeraldWorld") -> None:
|
||||||
if world.options.wild_pokemon == RandomizeWildPokemon.option_vanilla:
|
if world.options.wild_pokemon == RandomizeWildPokemon.option_vanilla:
|
||||||
return
|
return
|
||||||
@@ -253,120 +290,96 @@ def randomize_wild_encounters(world: "PokemonEmeraldWorld") -> None:
|
|||||||
placed_priority_species = False
|
placed_priority_species = False
|
||||||
map_data = world.modified_maps[map_name]
|
map_data = world.modified_maps[map_name]
|
||||||
|
|
||||||
new_encounters: List[Optional[EncounterTableData]] = [None, None, None]
|
new_encounters: Dict[EncounterType, EncounterTableData] = {}
|
||||||
old_encounters = [map_data.land_encounters, map_data.water_encounters, map_data.fishing_encounters]
|
|
||||||
|
|
||||||
for i, table in enumerate(old_encounters):
|
for encounter_type, table in map_data.encounters.items():
|
||||||
if table is not None:
|
# Create a map from the original species to new species
|
||||||
# Create a map from the original species to new species
|
# instead of just randomizing every slot.
|
||||||
# instead of just randomizing every slot.
|
# Force area 1-to-1 mapping, in other words.
|
||||||
# Force area 1-to-1 mapping, in other words.
|
species_old_to_new_map: Dict[int, int] = {}
|
||||||
species_old_to_new_map: Dict[int, int] = {}
|
for species_id in table.slots:
|
||||||
for species_id in table.slots:
|
if species_id not in species_old_to_new_map:
|
||||||
if species_id not in species_old_to_new_map:
|
if not placed_priority_species and len(priority_species) > 0 \
|
||||||
if not placed_priority_species and len(priority_species) > 0 \
|
and encounter_type != EncounterType.ROCK_SMASH and map_name not in OUT_OF_LOGIC_MAPS:
|
||||||
and map_name not in OUT_OF_LOGIC_MAPS:
|
new_species_id = priority_species.pop()
|
||||||
new_species_id = priority_species.pop()
|
placed_priority_species = True
|
||||||
placed_priority_species = True
|
else:
|
||||||
else:
|
original_species = data.species[species_id]
|
||||||
original_species = data.species[species_id]
|
|
||||||
|
|
||||||
# Construct progressive tiers of blacklists that can be peeled back if they
|
# Construct progressive tiers of blacklists that can be peeled back if they
|
||||||
# collectively cover too much of the pokedex. A lower index in `blacklists`
|
# collectively cover too much of the pokedex. A lower index in `blacklists`
|
||||||
# indicates a more important set of species to avoid. Entries at `0` will
|
# indicates a more important set of species to avoid. Entries at `0` will
|
||||||
# always be blacklisted.
|
# always be blacklisted.
|
||||||
blacklists: Dict[int, List[Set[int]]] = defaultdict(list)
|
blacklists: Dict[int, List[Set[int]]] = defaultdict(list)
|
||||||
|
|
||||||
# Blacklist pokemon already on this table
|
# Blacklist pokemon already on this table
|
||||||
blacklists[0].append(set(species_old_to_new_map.values()))
|
blacklists[0].append(set(species_old_to_new_map.values()))
|
||||||
|
|
||||||
# If doing legendary hunt, blacklist Latios from wild encounters so
|
# If doing legendary hunt, blacklist Latios from wild encounters so
|
||||||
# it can be tracked as the roamer. Otherwise it may be impossible
|
# it can be tracked as the roamer. Otherwise it may be impossible
|
||||||
# to tell whether a highlighted route is the roamer or a wild
|
# to tell whether a highlighted route is the roamer or a wild
|
||||||
# encounter.
|
# encounter.
|
||||||
if world.options.goal == Goal.option_legendary_hunt:
|
if world.options.goal == Goal.option_legendary_hunt:
|
||||||
blacklists[0].append({data.constants["SPECIES_LATIOS"]})
|
blacklists[0].append({data.constants["SPECIES_LATIOS"]})
|
||||||
|
|
||||||
# If dexsanity/catch 'em all mode, blacklist already placed species
|
# If dexsanity/catch 'em all mode, blacklist already placed species
|
||||||
# until every species has been placed once
|
# until every species has been placed once
|
||||||
if world.options.dexsanity and len(already_placed) < num_placeable_species:
|
if world.options.dexsanity and len(already_placed) < num_placeable_species:
|
||||||
blacklists[1].append(already_placed)
|
blacklists[1].append(already_placed)
|
||||||
|
|
||||||
# Blacklist from player options
|
# Blacklist from player options
|
||||||
blacklists[2].append(world.blacklisted_wilds)
|
blacklists[2].append(world.blacklisted_wilds)
|
||||||
|
|
||||||
# Type matching blacklist
|
# Type matching blacklist
|
||||||
if should_match_type:
|
if should_match_type:
|
||||||
blacklists[3].append({
|
blacklists[3].append({
|
||||||
species.species_id
|
species.species_id
|
||||||
for species in world.modified_species.values()
|
|
||||||
if not bool(set(species.types) & set(original_species.types))
|
|
||||||
})
|
|
||||||
|
|
||||||
merged_blacklist: Set[int] = set()
|
|
||||||
for max_priority in reversed(sorted(blacklists.keys())):
|
|
||||||
merged_blacklist = set()
|
|
||||||
for priority in blacklists.keys():
|
|
||||||
if priority <= max_priority:
|
|
||||||
for blacklist in blacklists[priority]:
|
|
||||||
merged_blacklist |= blacklist
|
|
||||||
|
|
||||||
if len(merged_blacklist) < NUM_REAL_SPECIES:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise RuntimeError("This should never happen")
|
|
||||||
|
|
||||||
candidates = [
|
|
||||||
species
|
|
||||||
for species in world.modified_species.values()
|
for species in world.modified_species.values()
|
||||||
if species.species_id not in merged_blacklist
|
if not bool(set(species.types) & set(original_species.types))
|
||||||
]
|
})
|
||||||
|
|
||||||
if should_match_bst:
|
merged_blacklist: Set[int] = set()
|
||||||
candidates = filter_species_by_nearby_bst(candidates, sum(original_species.base_stats))
|
for max_priority in reversed(sorted(blacklists.keys())):
|
||||||
|
merged_blacklist = set()
|
||||||
|
for priority in blacklists.keys():
|
||||||
|
if priority <= max_priority:
|
||||||
|
for blacklist in blacklists[priority]:
|
||||||
|
merged_blacklist |= blacklist
|
||||||
|
|
||||||
new_species_id = world.random.choice(candidates).species_id
|
if len(merged_blacklist) < NUM_REAL_SPECIES:
|
||||||
species_old_to_new_map[species_id] = new_species_id
|
break
|
||||||
|
else:
|
||||||
|
raise RuntimeError("This should never happen")
|
||||||
|
|
||||||
if world.options.dexsanity and map_name not in OUT_OF_LOGIC_MAPS:
|
candidates = [
|
||||||
already_placed.add(new_species_id)
|
species
|
||||||
|
for species in world.modified_species.values()
|
||||||
|
if species.species_id not in merged_blacklist
|
||||||
|
]
|
||||||
|
|
||||||
# Actually create the new list of slots and encounter table
|
if should_match_bst:
|
||||||
new_slots: List[int] = []
|
candidates = filter_species_by_nearby_bst(candidates, sum(original_species.base_stats))
|
||||||
for species_id in table.slots:
|
|
||||||
new_slots.append(species_old_to_new_map[species_id])
|
|
||||||
|
|
||||||
new_encounters[i] = EncounterTableData(new_slots, table.address)
|
new_species_id = world.random.choice(candidates).species_id
|
||||||
|
|
||||||
# Rename event items for the new wild pokemon species
|
species_old_to_new_map[species_id] = new_species_id
|
||||||
slot_category: Tuple[str, List[Tuple[Optional[str], range]]] = [
|
|
||||||
("LAND", [(None, range(0, 12))]),
|
|
||||||
("WATER", [(None, range(0, 5))]),
|
|
||||||
("FISHING", [("OLD_ROD", range(0, 2)), ("GOOD_ROD", range(2, 5)), ("SUPER_ROD", range(5, 10))]),
|
|
||||||
][i]
|
|
||||||
for j, new_species_id in enumerate(new_slots):
|
|
||||||
# Get the subcategory for rods
|
|
||||||
subcategory = next(sc for sc in slot_category[1] if j in sc[1])
|
|
||||||
subcategory_species = []
|
|
||||||
for k in subcategory[1]:
|
|
||||||
if new_slots[k] not in subcategory_species:
|
|
||||||
subcategory_species.append(new_slots[k])
|
|
||||||
|
|
||||||
# Create the name of the location that corresponds to this encounter slot
|
if world.options.dexsanity and encounter_type != EncounterType.ROCK_SMASH \
|
||||||
# Fishing locations include the rod name
|
and map_name not in OUT_OF_LOGIC_MAPS:
|
||||||
subcategory_str = "" if subcategory[0] is None else "_" + subcategory[0]
|
already_placed.add(new_species_id)
|
||||||
encounter_location_index = subcategory_species.index(new_species_id) + 1
|
|
||||||
encounter_location_name = f"{map_data.name}_{slot_category[0]}_ENCOUNTERS{subcategory_str}_{encounter_location_index}"
|
|
||||||
try:
|
|
||||||
# Get the corresponding location and change the event name to reflect the new species
|
|
||||||
slot_location = world.multiworld.get_location(encounter_location_name, world.player)
|
|
||||||
slot_location.item.name = f"CATCH_{data.species[new_species_id].name}"
|
|
||||||
except KeyError:
|
|
||||||
pass # Map probably isn't included; should be careful here about bad encounter location names
|
|
||||||
|
|
||||||
map_data.land_encounters = new_encounters[0]
|
# Actually create the new list of slots and encounter table
|
||||||
map_data.water_encounters = new_encounters[1]
|
new_slots: List[int] = []
|
||||||
map_data.fishing_encounters = new_encounters[2]
|
for species_id in table.slots:
|
||||||
|
new_slots.append(species_old_to_new_map[species_id])
|
||||||
|
|
||||||
|
new_encounters[encounter_type] = EncounterTableData(new_slots, table.address)
|
||||||
|
|
||||||
|
# Rock smash encounters not used in logic, so they have no events
|
||||||
|
if encounter_type != EncounterType.ROCK_SMASH:
|
||||||
|
_rename_wild_events(world, map_data, new_slots, encounter_type)
|
||||||
|
|
||||||
|
map_data.encounters = new_encounters
|
||||||
|
|
||||||
|
|
||||||
def randomize_abilities(world: "PokemonEmeraldWorld") -> None:
|
def randomize_abilities(world: "PokemonEmeraldWorld") -> None:
|
||||||
|
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple
|
|||||||
|
|
||||||
from BaseClasses import CollectionState, ItemClassification, Region
|
from BaseClasses import CollectionState, ItemClassification, Region
|
||||||
|
|
||||||
from .data import data
|
from .data import EncounterType, data
|
||||||
from .items import PokemonEmeraldItem
|
from .items import PokemonEmeraldItem
|
||||||
from .locations import PokemonEmeraldLocation
|
from .locations import PokemonEmeraldLocation
|
||||||
|
|
||||||
@@ -19,11 +19,11 @@ def create_regions(world: "PokemonEmeraldWorld") -> Dict[str, Region]:
|
|||||||
Also creates and places events and connects regions via warps and the exits defined in the JSON.
|
Also creates and places events and connects regions via warps and the exits defined in the JSON.
|
||||||
"""
|
"""
|
||||||
# Used in connect_to_map_encounters. Splits encounter categories into "subcategories" and gives them names
|
# Used in connect_to_map_encounters. Splits encounter categories into "subcategories" and gives them names
|
||||||
# and rules so the rods can only access their specific slots.
|
# and rules so the rods can only access their specific slots. Rock smash encounters are not considered in logic.
|
||||||
encounter_categories: Dict[str, List[Tuple[Optional[str], range, Optional[Callable[[CollectionState], bool]]]]] = {
|
encounter_categories: Dict[EncounterType, List[Tuple[Optional[str], range, Optional[Callable[[CollectionState], bool]]]]] = {
|
||||||
"LAND": [(None, range(0, 12), None)],
|
EncounterType.LAND: [(None, range(0, 12), None)],
|
||||||
"WATER": [(None, range(0, 5), None)],
|
EncounterType.WATER: [(None, range(0, 5), None)],
|
||||||
"FISHING": [
|
EncounterType.FISHING: [
|
||||||
("OLD_ROD", range(0, 2), lambda state: state.has("Old Rod", world.player)),
|
("OLD_ROD", range(0, 2), lambda state: state.has("Old Rod", world.player)),
|
||||||
("GOOD_ROD", range(2, 5), lambda state: state.has("Good Rod", world.player)),
|
("GOOD_ROD", range(2, 5), lambda state: state.has("Good Rod", world.player)),
|
||||||
("SUPER_ROD", range(5, 10), lambda state: state.has("Super Rod", world.player)),
|
("SUPER_ROD", range(5, 10), lambda state: state.has("Super Rod", world.player)),
|
||||||
@@ -41,19 +41,19 @@ def create_regions(world: "PokemonEmeraldWorld") -> Dict[str, Region]:
|
|||||||
These regions are created lazily and dynamically so as not to bother with unused maps.
|
These regions are created lazily and dynamically so as not to bother with unused maps.
|
||||||
"""
|
"""
|
||||||
# For each of land, water, and fishing, connect the region if indicated by include_slots
|
# For each of land, water, and fishing, connect the region if indicated by include_slots
|
||||||
for i, encounter_category in enumerate(encounter_categories.items()):
|
for i, (encounter_type, subcategories) in enumerate(encounter_categories.items()):
|
||||||
if include_slots[i]:
|
if include_slots[i]:
|
||||||
region_name = f"{map_name}_{encounter_category[0]}_ENCOUNTERS"
|
region_name = f"{map_name}_{encounter_type.value}_ENCOUNTERS"
|
||||||
|
|
||||||
# If the region hasn't been created yet, create it now
|
# If the region hasn't been created yet, create it now
|
||||||
try:
|
try:
|
||||||
encounter_region = world.multiworld.get_region(region_name, world.player)
|
encounter_region = world.multiworld.get_region(region_name, world.player)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
encounter_region = Region(region_name, world.player, world.multiworld)
|
encounter_region = Region(region_name, world.player, world.multiworld)
|
||||||
encounter_slots = getattr(data.maps[map_name], f"{encounter_category[0].lower()}_encounters").slots
|
encounter_slots = data.maps[map_name].encounters[encounter_type].slots
|
||||||
|
|
||||||
# Subcategory is for splitting fishing rods; land and water only have one subcategory
|
# Subcategory is for splitting fishing rods; land and water only have one subcategory
|
||||||
for subcategory in encounter_category[1]:
|
for subcategory in subcategories:
|
||||||
# Want to create locations per species, not per slot
|
# Want to create locations per species, not per slot
|
||||||
# encounter_categories includes info on which slots belong to which subcategory
|
# encounter_categories includes info on which slots belong to which subcategory
|
||||||
unique_species = []
|
unique_species = []
|
||||||
|
@@ -696,12 +696,10 @@ def _set_encounter_tables(world: "PokemonEmeraldWorld", patch: PokemonEmeraldPro
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
for map_data in world.modified_maps.values():
|
for map_data in world.modified_maps.values():
|
||||||
tables = [map_data.land_encounters, map_data.water_encounters, map_data.fishing_encounters]
|
for table in map_data.encounters.values():
|
||||||
for table in tables:
|
for i, species_id in enumerate(table.slots):
|
||||||
if table is not None:
|
address = table.address + 2 + (4 * i)
|
||||||
for i, species_id in enumerate(table.slots):
|
patch.write_token(APTokenTypes.WRITE, address, struct.pack("<H", species_id))
|
||||||
address = table.address + 2 + (4 * i)
|
|
||||||
patch.write_token(APTokenTypes.WRITE, address, struct.pack("<H", species_id))
|
|
||||||
|
|
||||||
|
|
||||||
def _set_species_info(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch, easter_egg: Tuple[int, int]) -> None:
|
def _set_species_info(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch, easter_egg: Tuple[int, int]) -> None:
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import orjson
|
import orjson
|
||||||
from typing import Any, Dict, List, Optional, Tuple, Iterable
|
from typing import Any, Dict, List, Optional, Tuple, Iterable
|
||||||
|
|
||||||
from .data import NATIONAL_ID_TO_SPECIES_ID, data
|
from .data import NATIONAL_ID_TO_SPECIES_ID, EncounterType, data
|
||||||
|
|
||||||
|
|
||||||
CHARACTER_DECODING_MAP = {
|
CHARACTER_DECODING_MAP = {
|
||||||
@@ -86,6 +86,28 @@ def decode_string(string_data: Iterable[int]) -> str:
|
|||||||
return string
|
return string
|
||||||
|
|
||||||
|
|
||||||
|
def get_encounter_type_label(encounter_type: EncounterType, slot: int) -> str:
|
||||||
|
if encounter_type == EncounterType.FISHING:
|
||||||
|
return {
|
||||||
|
0: "Old Rod",
|
||||||
|
1: "Old Rod",
|
||||||
|
2: "Good Rod",
|
||||||
|
3: "Good Rod",
|
||||||
|
4: "Good Rod",
|
||||||
|
5: "Super Rod",
|
||||||
|
6: "Super Rod",
|
||||||
|
7: "Super Rod",
|
||||||
|
8: "Super Rod",
|
||||||
|
9: "Super Rod",
|
||||||
|
}[slot]
|
||||||
|
|
||||||
|
return {
|
||||||
|
EncounterType.LAND: 'Land',
|
||||||
|
EncounterType.WATER: 'Water',
|
||||||
|
EncounterType.ROCK_SMASH: 'Rock Smash',
|
||||||
|
}[encounter_type]
|
||||||
|
|
||||||
|
|
||||||
def get_easter_egg(easter_egg: str) -> Tuple[int, int]:
|
def get_easter_egg(easter_egg: str) -> Tuple[int, int]:
|
||||||
easter_egg = easter_egg.upper()
|
easter_egg = easter_egg.upper()
|
||||||
result1 = 0
|
result1 = 0
|
||||||
|
Reference in New Issue
Block a user