Pokemon R/B: Version 5 Update (#3566)

* Quiz updates

* Enable Partial Trainersanity

* Losable Key Items Still Count

* New options api

* Type Chart Seed

* Continue switching to new options API

* Level Scaling and Quiz fixes

* Level Scaling and Quiz fixes

* Clarify that palettes are only for Super Gameboy

* Type chart seed groups use one random players' options

* remove goal option again

* Text updates

* Trainersanity Trainers ignore Blind Trainers setting

* Re-order simple connecting interiors so that directions are preserved when possible

* Dexsanity exact number

* Year update

* Dexsanity Doc update

* revert accidental file deletion

* Fixes

* Add world parameter to logic calls

* restore correct seeded random object

* missing world.options changes

* Trainersanity table bug fix

* delete entrances as well as exits when restarting door shuffle

* Do not collect route 25 item for level scaling if trainer is trainersanity

* world.options in level_scaling.py

* Update worlds/pokemon_rb/level_scaling.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/pokemon_rb/encounters.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/pokemon_rb/encounters.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* world -> multiworld

* Fix Cerulean Cave Hidden Item Center Rocks region

* Fix Cerulean Cave Hidden Item Center Rocks region for real

* Remove "self-locking" rules

* Update worlds/pokemon_rb/regions.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Fossil events

* Update worlds/pokemon_rb/level_scaling.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

---------

Co-authored-by: alchav <alchav@jalchavware.com>
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
This commit is contained in:
Alchav 2024-09-18 14:37:17 -04:00 committed by GitHub
parent 51a6dc150c
commit db5d9fbf70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 1435 additions and 1335 deletions

View File

@ -3,6 +3,7 @@ import settings
import typing import typing
import threading import threading
import base64 import base64
import random
from copy import deepcopy from copy import deepcopy
from typing import TextIO from typing import TextIO
@ -14,7 +15,7 @@ from worlds.generic.Rules import add_item_rule
from .items import item_table, item_groups from .items import item_table, item_groups
from .locations import location_data, PokemonRBLocation from .locations import location_data, PokemonRBLocation
from .regions import create_regions from .regions import create_regions
from .options import pokemon_rb_options from .options import PokemonRBOptions
from .rom_addresses import rom_addresses from .rom_addresses import rom_addresses
from .text import encode_text from .text import encode_text
from .rom import generate_output, get_base_rom_bytes, get_base_rom_path, RedDeltaPatch, BlueDeltaPatch from .rom import generate_output, get_base_rom_bytes, get_base_rom_path, RedDeltaPatch, BlueDeltaPatch
@ -71,7 +72,10 @@ class PokemonRedBlueWorld(World):
Elite Four to become the champion!""" Elite Four to become the champion!"""
# -MuffinJets#4559 # -MuffinJets#4559
game = "Pokemon Red and Blue" game = "Pokemon Red and Blue"
option_definitions = pokemon_rb_options
options_dataclass = PokemonRBOptions
options: PokemonRBOptions
settings: typing.ClassVar[PokemonSettings] settings: typing.ClassVar[PokemonSettings]
required_client_version = (0, 4, 2) required_client_version = (0, 4, 2)
@ -85,8 +89,8 @@ class PokemonRedBlueWorld(World):
web = PokemonWebWorld() web = PokemonWebWorld()
def __init__(self, world: MultiWorld, player: int): def __init__(self, multiworld: MultiWorld, player: int):
super().__init__(world, player) super().__init__(multiworld, player)
self.item_pool = [] self.item_pool = []
self.total_key_items = None self.total_key_items = None
self.fly_map = None self.fly_map = None
@ -101,11 +105,11 @@ class PokemonRedBlueWorld(World):
self.learnsets = None self.learnsets = None
self.trainer_name = None self.trainer_name = None
self.rival_name = None self.rival_name = None
self.type_chart = None
self.traps = None self.traps = None
self.trade_mons = {} self.trade_mons = {}
self.finished_level_scaling = threading.Event() self.finished_level_scaling = threading.Event()
self.dexsanity_table = [] self.dexsanity_table = []
self.trainersanity_table = []
self.local_locs = [] self.local_locs = []
@classmethod @classmethod
@ -113,11 +117,109 @@ class PokemonRedBlueWorld(World):
versions = set() versions = set()
for player in multiworld.player_ids: for player in multiworld.player_ids:
if multiworld.worlds[player].game == "Pokemon Red and Blue": if multiworld.worlds[player].game == "Pokemon Red and Blue":
versions.add(multiworld.game_version[player].current_key) versions.add(multiworld.worlds[player].options.game_version.current_key)
for version in versions: for version in versions:
if not os.path.exists(get_base_rom_path(version)): if not os.path.exists(get_base_rom_path(version)):
raise FileNotFoundError(get_base_rom_path(version)) raise FileNotFoundError(get_base_rom_path(version))
@classmethod
def stage_generate_early(cls, multiworld: MultiWorld):
seed_groups = {}
pokemon_rb_worlds = multiworld.get_game_worlds("Pokemon Red and Blue")
for world in pokemon_rb_worlds:
if not (world.options.type_chart_seed.value.isdigit() or world.options.type_chart_seed.value == "random"):
seed_groups[world.options.type_chart_seed.value] = seed_groups.get(world.options.type_chart_seed.value,
[]) + [world]
copy_chart_worlds = {}
for worlds in seed_groups.values():
chosen_world = multiworld.random.choice(worlds)
for world in worlds:
if world is not chosen_world:
copy_chart_worlds[world.player] = chosen_world
for world in pokemon_rb_worlds:
if world.player in copy_chart_worlds:
continue
tc_random = world.random
if world.options.type_chart_seed.value.isdigit():
tc_random = random.Random()
tc_random.seed(int(world.options.type_chart_seed.value))
if world.options.randomize_type_chart == "vanilla":
chart = deepcopy(poke_data.type_chart)
elif world.options.randomize_type_chart == "randomize":
types = poke_data.type_names.values()
matchups = []
for type1 in types:
for type2 in types:
matchups.append([type1, type2])
tc_random.shuffle(matchups)
immunities = world.options.immunity_matchups.value
super_effectives = world.options.super_effective_matchups.value
not_very_effectives = world.options.not_very_effective_matchups.value
normals = world.options.normal_matchups.value
while super_effectives + not_very_effectives + normals < 225 - immunities:
if super_effectives == not_very_effectives == normals == 0:
super_effectives = 225
not_very_effectives = 225
normals = 225
else:
super_effectives += world.options.super_effective_matchups.value
not_very_effectives += world.options.not_very_effective_matchups.value
normals += world.options.normal_matchups.value
if super_effectives + not_very_effectives + normals > 225 - immunities:
total = super_effectives + not_very_effectives + normals
excess = total - (225 - immunities)
subtract_amounts = (
int((excess / (super_effectives + not_very_effectives + normals)) * super_effectives),
int((excess / (super_effectives + not_very_effectives + normals)) * not_very_effectives),
int((excess / (super_effectives + not_very_effectives + normals)) * normals))
super_effectives -= subtract_amounts[0]
not_very_effectives -= subtract_amounts[1]
normals -= subtract_amounts[2]
while super_effectives + not_very_effectives + normals > 225 - immunities:
r = tc_random.randint(0, 2)
if r == 0 and super_effectives:
super_effectives -= 1
elif r == 1 and not_very_effectives:
not_very_effectives -= 1
elif normals:
normals -= 1
chart = []
for matchup_list, matchup_value in zip([immunities, normals, super_effectives, not_very_effectives],
[0, 10, 20, 5]):
for _ in range(matchup_list):
matchup = matchups.pop()
matchup.append(matchup_value)
chart.append(matchup)
elif world.options.randomize_type_chart == "chaos":
types = poke_data.type_names.values()
matchups = []
for type1 in types:
for type2 in types:
matchups.append([type1, type2])
chart = []
values = list(range(21))
tc_random.shuffle(matchups)
tc_random.shuffle(values)
for matchup in matchups:
value = values.pop(0)
values.append(value)
matchup.append(value)
chart.append(matchup)
# sort so that super-effective matchups occur first, to prevent dual "not very effective" / "super effective"
# matchups from leading to damage being ultimately divided by 2 and then multiplied by 2, which can lead to
# damage being reduced by 1 which leads to a "not very effective" message appearing due to my changes
# to the way effectiveness messages are generated.
world.type_chart = sorted(chart, key=lambda matchup: -matchup[2])
for player in copy_chart_worlds:
multiworld.worlds[player].type_chart = copy_chart_worlds[player].type_chart
def generate_early(self): def generate_early(self):
def encode_name(name, t): def encode_name(name, t):
try: try:
@ -126,33 +228,33 @@ class PokemonRedBlueWorld(World):
return encode_text(name, length=8, whitespace="@", safety=True) return encode_text(name, length=8, whitespace="@", safety=True)
except KeyError as e: except KeyError as e:
raise KeyError(f"Invalid character(s) in {t} name for player {self.multiworld.player_name[self.player]}") from e raise KeyError(f"Invalid character(s) in {t} name for player {self.multiworld.player_name[self.player]}") from e
if self.multiworld.trainer_name[self.player] == "choose_in_game": if self.options.trainer_name == "choose_in_game":
self.trainer_name = "choose_in_game" self.trainer_name = "choose_in_game"
else: else:
self.trainer_name = encode_name(self.multiworld.trainer_name[self.player].value, "Player") self.trainer_name = encode_name(self.options.trainer_name.value, "Player")
if self.multiworld.rival_name[self.player] == "choose_in_game": if self.options.rival_name == "choose_in_game":
self.rival_name = "choose_in_game" self.rival_name = "choose_in_game"
else: else:
self.rival_name = encode_name(self.multiworld.rival_name[self.player].value, "Rival") self.rival_name = encode_name(self.options.rival_name.value, "Rival")
if not self.multiworld.badgesanity[self.player]: if not self.options.badgesanity:
self.multiworld.non_local_items[self.player].value -= self.item_name_groups["Badges"] self.options.non_local_items.value -= self.item_name_groups["Badges"]
if self.multiworld.key_items_only[self.player]: if self.options.key_items_only:
self.multiworld.trainersanity[self.player] = self.multiworld.trainersanity[self.player].from_text("off") self.options.trainersanity.value = 0
self.multiworld.dexsanity[self.player].value = 0 self.options.dexsanity.value = 0
self.multiworld.randomize_hidden_items[self.player] = \ self.options.randomize_hidden_items = \
self.multiworld.randomize_hidden_items[self.player].from_text("off") self.options.randomize_hidden_items.from_text("off")
if self.multiworld.badges_needed_for_hm_moves[self.player].value >= 2: if self.options.badges_needed_for_hm_moves.value >= 2:
badges_to_add = ["Marsh Badge", "Volcano Badge", "Earth Badge"] badges_to_add = ["Marsh Badge", "Volcano Badge", "Earth Badge"]
if self.multiworld.badges_needed_for_hm_moves[self.player].value == 3: if self.options.badges_needed_for_hm_moves.value == 3:
badges = ["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Marsh Badge", badges = ["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Marsh Badge",
"Soul Badge", "Volcano Badge", "Earth Badge"] "Soul Badge", "Volcano Badge", "Earth Badge"]
self.multiworld.random.shuffle(badges) self.random.shuffle(badges)
badges_to_add += [badges.pop(), badges.pop()] badges_to_add += [badges.pop(), badges.pop()]
hm_moves = ["Cut", "Fly", "Surf", "Strength", "Flash"] hm_moves = ["Cut", "Fly", "Surf", "Strength", "Flash"]
self.multiworld.random.shuffle(hm_moves) self.random.shuffle(hm_moves)
self.extra_badges = {} self.extra_badges = {}
for badge in badges_to_add: for badge in badges_to_add:
self.extra_badges[hm_moves.pop()] = badge self.extra_badges[hm_moves.pop()] = badge
@ -160,79 +262,17 @@ class PokemonRedBlueWorld(World):
process_move_data(self) process_move_data(self)
process_pokemon_data(self) process_pokemon_data(self)
if self.multiworld.randomize_type_chart[self.player] == "vanilla":
chart = deepcopy(poke_data.type_chart)
elif self.multiworld.randomize_type_chart[self.player] == "randomize":
types = poke_data.type_names.values()
matchups = []
for type1 in types:
for type2 in types:
matchups.append([type1, type2])
self.multiworld.random.shuffle(matchups)
immunities = self.multiworld.immunity_matchups[self.player].value
super_effectives = self.multiworld.super_effective_matchups[self.player].value
not_very_effectives = self.multiworld.not_very_effective_matchups[self.player].value
normals = self.multiworld.normal_matchups[self.player].value
while super_effectives + not_very_effectives + normals < 225 - immunities:
if super_effectives == not_very_effectives == normals == 0:
super_effectives = 225
not_very_effectives = 225
normals = 225
else:
super_effectives += self.multiworld.super_effective_matchups[self.player].value
not_very_effectives += self.multiworld.not_very_effective_matchups[self.player].value
normals += self.multiworld.normal_matchups[self.player].value
if super_effectives + not_very_effectives + normals > 225 - immunities:
total = super_effectives + not_very_effectives + normals
excess = total - (225 - immunities)
subtract_amounts = (
int((excess / (super_effectives + not_very_effectives + normals)) * super_effectives),
int((excess / (super_effectives + not_very_effectives + normals)) * not_very_effectives),
int((excess / (super_effectives + not_very_effectives + normals)) * normals))
super_effectives -= subtract_amounts[0]
not_very_effectives -= subtract_amounts[1]
normals -= subtract_amounts[2]
while super_effectives + not_very_effectives + normals > 225 - immunities:
r = self.multiworld.random.randint(0, 2)
if r == 0 and super_effectives:
super_effectives -= 1
elif r == 1 and not_very_effectives:
not_very_effectives -= 1
elif normals:
normals -= 1
chart = []
for matchup_list, matchup_value in zip([immunities, normals, super_effectives, not_very_effectives],
[0, 10, 20, 5]):
for _ in range(matchup_list):
matchup = matchups.pop()
matchup.append(matchup_value)
chart.append(matchup)
elif self.multiworld.randomize_type_chart[self.player] == "chaos":
types = poke_data.type_names.values()
matchups = []
for type1 in types:
for type2 in types:
matchups.append([type1, type2])
chart = []
values = list(range(21))
self.multiworld.random.shuffle(matchups)
self.multiworld.random.shuffle(values)
for matchup in matchups:
value = values.pop(0)
values.append(value)
matchup.append(value)
chart.append(matchup)
# sort so that super-effective matchups occur first, to prevent dual "not very effective" / "super effective"
# matchups from leading to damage being ultimately divided by 2 and then multiplied by 2, which can lead to
# damage being reduced by 1 which leads to a "not very effective" message appearing due to my changes
# to the way effectiveness messages are generated.
self.type_chart = sorted(chart, key=lambda matchup: -matchup[2])
self.dexsanity_table = [ self.dexsanity_table = [
*(True for _ in range(round(self.multiworld.dexsanity[self.player].value * 1.51))), *(True for _ in range(round(self.options.dexsanity.value))),
*(False for _ in range(151 - round(self.multiworld.dexsanity[self.player].value * 1.51))) *(False for _ in range(151 - round(self.options.dexsanity.value)))
] ]
self.multiworld.random.shuffle(self.dexsanity_table) self.random.shuffle(self.dexsanity_table)
self.trainersanity_table = [
*(True for _ in range(self.options.trainersanity.value)),
*(False for _ in range(317 - self.options.trainersanity.value))
]
self.random.shuffle(self.trainersanity_table)
def create_items(self): def create_items(self):
self.multiworld.itempool += self.item_pool self.multiworld.itempool += self.item_pool
@ -275,9 +315,9 @@ class PokemonRedBlueWorld(World):
filleritempool += [item for item in unplaced_items if (not item.advancement) and (not item.useful)] filleritempool += [item for item in unplaced_items if (not item.advancement) and (not item.useful)]
def fill_hook(self, progitempool, usefulitempool, filleritempool, fill_locations): def fill_hook(self, progitempool, usefulitempool, filleritempool, fill_locations):
if not self.multiworld.badgesanity[self.player]: if not self.options.badgesanity:
# Door Shuffle options besides Simple place badges during door shuffling # Door Shuffle options besides Simple place badges during door shuffling
if self.multiworld.door_shuffle[self.player] in ("off", "simple"): if self.options.door_shuffle in ("off", "simple"):
badges = [item for item in progitempool if "Badge" in item.name and item.player == self.player] badges = [item for item in progitempool if "Badge" in item.name and item.player == self.player]
for badge in badges: for badge in badges:
self.multiworld.itempool.remove(badge) self.multiworld.itempool.remove(badge)
@ -297,8 +337,8 @@ class PokemonRedBlueWorld(World):
for mon in poke_data.pokemon_data.keys(): for mon in poke_data.pokemon_data.keys():
state.collect(self.create_item(mon), True) state.collect(self.create_item(mon), True)
state.sweep_for_advancements() state.sweep_for_advancements()
self.multiworld.random.shuffle(badges) self.random.shuffle(badges)
self.multiworld.random.shuffle(badgelocs) self.random.shuffle(badgelocs)
badgelocs_copy = badgelocs.copy() badgelocs_copy = badgelocs.copy()
# allow_partial so that unplaced badges aren't lost, for debugging purposes # allow_partial so that unplaced badges aren't lost, for debugging purposes
fill_restrictive(self.multiworld, state, badgelocs_copy, badges, True, True, allow_partial=True) fill_restrictive(self.multiworld, state, badgelocs_copy, badges, True, True, allow_partial=True)
@ -318,7 +358,7 @@ class PokemonRedBlueWorld(World):
raise FillError(f"Failed to place badges for player {self.player}") raise FillError(f"Failed to place badges for player {self.player}")
verify_hm_moves(self.multiworld, self, self.player) verify_hm_moves(self.multiworld, self, self.player)
if self.multiworld.key_items_only[self.player]: if self.options.key_items_only:
return return
tms = [item for item in usefulitempool + filleritempool if item.name.startswith("TM") and (item.player == tms = [item for item in usefulitempool + filleritempool if item.name.startswith("TM") and (item.player ==
@ -340,7 +380,7 @@ class PokemonRedBlueWorld(World):
int((int(tm.name[2:4]) - 1) / 8)] & 1 << ((int(tm.name[2:4]) - 1) % 8)] int((int(tm.name[2:4]) - 1) / 8)] & 1 << ((int(tm.name[2:4]) - 1) % 8)]
if not learnable_tms: if not learnable_tms:
learnable_tms = tms learnable_tms = tms
tm = self.multiworld.random.choice(learnable_tms) tm = self.random.choice(learnable_tms)
loc.place_locked_item(tm) loc.place_locked_item(tm)
fill_locations.remove(loc) fill_locations.remove(loc)
@ -370,9 +410,9 @@ class PokemonRedBlueWorld(World):
if not all_state.can_reach(location, player=self.player): if not all_state.can_reach(location, player=self.player):
evolutions_region.locations.remove(location) evolutions_region.locations.remove(location)
if self.multiworld.old_man[self.player] == "early_parcel": if self.options.old_man == "early_parcel":
self.multiworld.local_early_items[self.player]["Oak's Parcel"] = 1 self.multiworld.local_early_items[self.player]["Oak's Parcel"] = 1
if self.multiworld.dexsanity[self.player]: if self.options.dexsanity:
for i, mon in enumerate(poke_data.pokemon_data): for i, mon in enumerate(poke_data.pokemon_data):
if self.dexsanity_table[i]: if self.dexsanity_table[i]:
location = self.multiworld.get_location(f"Pokedex - {mon}", self.player) location = self.multiworld.get_location(f"Pokedex - {mon}", self.player)
@ -384,13 +424,13 @@ class PokemonRedBlueWorld(World):
locs = {self.multiworld.get_location("Fossil - Choice A", self.player), locs = {self.multiworld.get_location("Fossil - Choice A", self.player),
self.multiworld.get_location("Fossil - Choice B", self.player)} self.multiworld.get_location("Fossil - Choice B", self.player)}
if not self.multiworld.key_items_only[self.player]: if not self.options.key_items_only:
rule = None rule = None
if self.multiworld.fossil_check_item_types[self.player] == "key_items": if self.options.fossil_check_item_types == "key_items":
rule = lambda i: i.advancement rule = lambda i: i.advancement
elif self.multiworld.fossil_check_item_types[self.player] == "unique_items": elif self.options.fossil_check_item_types == "unique_items":
rule = lambda i: i.name in item_groups["Unique"] rule = lambda i: i.name in item_groups["Unique"]
elif self.multiworld.fossil_check_item_types[self.player] == "no_key_items": elif self.options.fossil_check_item_types == "no_key_items":
rule = lambda i: not i.advancement rule = lambda i: not i.advancement
if rule: if rule:
for loc in locs: for loc in locs:
@ -406,16 +446,16 @@ class PokemonRedBlueWorld(World):
if loc.item is None: if loc.item is None:
locs.add(loc) locs.add(loc)
if not self.multiworld.key_items_only[self.player]: if not self.options.key_items_only:
loc = self.multiworld.get_location("Player's House 2F - Player's PC", self.player) loc = self.multiworld.get_location("Player's House 2F - Player's PC", self.player)
if loc.item is None: if loc.item is None:
locs.add(loc) locs.add(loc)
for loc in sorted(locs): for loc in sorted(locs):
if loc.name in self.multiworld.priority_locations[self.player].value: if loc.name in self.options.priority_locations.value:
add_item_rule(loc, lambda i: i.advancement) add_item_rule(loc, lambda i: i.advancement)
add_item_rule(loc, lambda i: i.player == self.player) add_item_rule(loc, lambda i: i.player == self.player)
if self.multiworld.old_man[self.player] == "early_parcel" and loc.name != "Player's House 2F - Player's PC": if self.options.old_man == "early_parcel" and loc.name != "Player's House 2F - Player's PC":
add_item_rule(loc, lambda i: i.name != "Oak's Parcel") add_item_rule(loc, lambda i: i.name != "Oak's Parcel")
self.local_locs = locs self.local_locs = locs
@ -440,10 +480,10 @@ class PokemonRedBlueWorld(World):
else: else:
region_mons.add(location.item.name) region_mons.add(location.item.name)
self.multiworld.elite_four_pokedex_condition[self.player].total = \ self.options.elite_four_pokedex_condition.total = \
int((len(reachable_mons) / 100) * self.multiworld.elite_four_pokedex_condition[self.player].value) int((len(reachable_mons) / 100) * self.options.elite_four_pokedex_condition.value)
if self.multiworld.accessibility[self.player] == "full": if self.options.accessibility == "full":
balls = [self.create_item(ball) for ball in ["Poke Ball", "Great Ball", "Ultra Ball"]] balls = [self.create_item(ball) for ball in ["Poke Ball", "Great Ball", "Ultra Ball"]]
traps = [self.create_item(trap) for trap in item_groups["Traps"]] traps = [self.create_item(trap) for trap in item_groups["Traps"]]
locations = [location for location in self.multiworld.get_locations(self.player) if "Pokedex - " in locations = [location for location in self.multiworld.get_locations(self.player) if "Pokedex - " in
@ -469,7 +509,7 @@ class PokemonRedBlueWorld(World):
else: else:
break break
else: else:
self.multiworld.random.shuffle(traps) self.random.shuffle(traps)
for trap in traps: for trap in traps:
try: try:
self.multiworld.itempool.remove(trap) self.multiworld.itempool.remove(trap)
@ -497,22 +537,22 @@ class PokemonRedBlueWorld(World):
found_mons.add(key) found_mons.add(key)
def create_regions(self): def create_regions(self):
if (self.multiworld.old_man[self.player] == "vanilla" or if (self.options.old_man == "vanilla" or
self.multiworld.door_shuffle[self.player] in ("full", "insanity")): self.options.door_shuffle in ("full", "insanity")):
fly_map_codes = self.multiworld.random.sample(range(2, 11), 2) fly_map_codes = self.random.sample(range(2, 11), 2)
elif (self.multiworld.door_shuffle[self.player] == "simple" or elif (self.options.door_shuffle == "simple" or
self.multiworld.route_3_condition[self.player] == "boulder_badge" or self.options.route_3_condition == "boulder_badge" or
(self.multiworld.route_3_condition[self.player] == "any_badge" and (self.options.route_3_condition == "any_badge" and
self.multiworld.badgesanity[self.player])): self.options.badgesanity)):
fly_map_codes = self.multiworld.random.sample(range(3, 11), 2) fly_map_codes = self.random.sample(range(3, 11), 2)
else: else:
fly_map_codes = self.multiworld.random.sample([4, 6, 7, 8, 9, 10], 2) fly_map_codes = self.random.sample([4, 6, 7, 8, 9, 10], 2)
if self.multiworld.free_fly_location[self.player]: if self.options.free_fly_location:
fly_map_code = fly_map_codes[0] fly_map_code = fly_map_codes[0]
else: else:
fly_map_code = 0 fly_map_code = 0
if self.multiworld.town_map_fly_location[self.player]: if self.options.town_map_fly_location:
town_map_fly_map_code = fly_map_codes[1] town_map_fly_map_code = fly_map_codes[1]
else: else:
town_map_fly_map_code = 0 town_map_fly_map_code = 0
@ -528,7 +568,7 @@ class PokemonRedBlueWorld(World):
self.multiworld.completion_condition[self.player] = lambda state, player=self.player: state.has("Become Champion", player=player) self.multiworld.completion_condition[self.player] = lambda state, player=self.player: state.has("Become Champion", player=player)
def set_rules(self): def set_rules(self):
set_rules(self.multiworld, self.player) set_rules(self.multiworld, self, self.player)
def create_item(self, name: str) -> Item: def create_item(self, name: str) -> Item:
return PokemonRBItem(name, self.player) return PokemonRBItem(name, self.player)
@ -548,19 +588,19 @@ class PokemonRedBlueWorld(World):
multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]] multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]]
def write_spoiler_header(self, spoiler_handle: TextIO): def write_spoiler_header(self, spoiler_handle: TextIO):
spoiler_handle.write(f"Cerulean Cave Total Key Items: {self.multiworld.cerulean_cave_key_items_condition[self.player].total}\n") spoiler_handle.write(f"Cerulean Cave Total Key Items: {self.options.cerulean_cave_key_items_condition.total}\n")
spoiler_handle.write(f"Elite Four Total Key Items: {self.multiworld.elite_four_key_items_condition[self.player].total}\n") spoiler_handle.write(f"Elite Four Total Key Items: {self.options.elite_four_key_items_condition.total}\n")
spoiler_handle.write(f"Elite Four Total Pokemon: {self.multiworld.elite_four_pokedex_condition[self.player].total}\n") spoiler_handle.write(f"Elite Four Total Pokemon: {self.options.elite_four_pokedex_condition.total}\n")
if self.multiworld.free_fly_location[self.player]: if self.options.free_fly_location:
spoiler_handle.write(f"Free Fly Location: {self.fly_map}\n") spoiler_handle.write(f"Free Fly Location: {self.fly_map}\n")
if self.multiworld.town_map_fly_location[self.player]: if self.options.town_map_fly_location:
spoiler_handle.write(f"Town Map Fly Location: {self.town_map_fly_map}\n") spoiler_handle.write(f"Town Map Fly Location: {self.town_map_fly_map}\n")
if self.extra_badges: if self.extra_badges:
for hm_move, badge in self.extra_badges.items(): for hm_move, badge in self.extra_badges.items():
spoiler_handle.write(hm_move + " enabled by: " + (" " * 20)[:20 - len(hm_move)] + badge + "\n") spoiler_handle.write(hm_move + " enabled by: " + (" " * 20)[:20 - len(hm_move)] + badge + "\n")
def write_spoiler(self, spoiler_handle): def write_spoiler(self, spoiler_handle):
if self.multiworld.randomize_type_chart[self.player].value: if self.options.randomize_type_chart:
spoiler_handle.write(f"\n\nType matchups ({self.multiworld.player_name[self.player]}):\n\n") spoiler_handle.write(f"\n\nType matchups ({self.multiworld.player_name[self.player]}):\n\n")
for matchup in self.type_chart: for matchup in self.type_chart:
spoiler_handle.write(f"{matchup[0]} deals {matchup[2] * 10}% damage to {matchup[1]}\n") spoiler_handle.write(f"{matchup[0]} deals {matchup[2] * 10}% damage to {matchup[1]}\n")
@ -571,39 +611,39 @@ class PokemonRedBlueWorld(World):
spoiler_handle.write(location.name + ": " + location.item.name + "\n") spoiler_handle.write(location.name + ": " + location.item.name + "\n")
def get_filler_item_name(self) -> str: def get_filler_item_name(self) -> str:
combined_traps = (self.multiworld.poison_trap_weight[self.player].value combined_traps = (self.options.poison_trap_weight.value
+ self.multiworld.fire_trap_weight[self.player].value + self.options.fire_trap_weight.value
+ self.multiworld.paralyze_trap_weight[self.player].value + self.options.paralyze_trap_weight.value
+ self.multiworld.ice_trap_weight[self.player].value + self.options.ice_trap_weight.value
+ self.multiworld.sleep_trap_weight[self.player].value) + self.options.sleep_trap_weight.value)
if (combined_traps > 0 and if (combined_traps > 0 and
self.multiworld.random.randint(1, 100) <= self.multiworld.trap_percentage[self.player].value): self.random.randint(1, 100) <= self.options.trap_percentage.value):
return self.select_trap() return self.select_trap()
banned_items = item_groups["Unique"] banned_items = item_groups["Unique"]
if (((not self.multiworld.tea[self.player]) or "Saffron City" not in [self.fly_map, self.town_map_fly_map]) if (((not self.options.tea) or "Saffron City" not in [self.fly_map, self.town_map_fly_map])
and (not self.multiworld.door_shuffle[self.player])): and (not self.options.door_shuffle)):
# under these conditions, you should never be able to reach the Copycat or Pokémon Tower without being # under these conditions, you should never be able to reach the Copycat or Pokémon Tower without being
# able to reach the Celadon Department Store, so Poké Dolls would not allow early access to anything # able to reach the Celadon Department Store, so Poké Dolls would not allow early access to anything
banned_items.append("Poke Doll") banned_items.append("Poke Doll")
if not self.multiworld.tea[self.player]: if not self.options.tea:
banned_items += item_groups["Vending Machine Drinks"] banned_items += item_groups["Vending Machine Drinks"]
return self.multiworld.random.choice([item for item in item_table if item_table[item].id and item_table[ return self.random.choice([item for item in item_table if item_table[item].id and item_table[
item].classification == ItemClassification.filler and item not in banned_items]) item].classification == ItemClassification.filler and item not in banned_items])
def select_trap(self): def select_trap(self):
if self.traps is None: if self.traps is None:
self.traps = [] self.traps = []
self.traps += ["Poison Trap"] * self.multiworld.poison_trap_weight[self.player].value self.traps += ["Poison Trap"] * self.options.poison_trap_weight.value
self.traps += ["Fire Trap"] * self.multiworld.fire_trap_weight[self.player].value self.traps += ["Fire Trap"] * self.options.fire_trap_weight.value
self.traps += ["Paralyze Trap"] * self.multiworld.paralyze_trap_weight[self.player].value self.traps += ["Paralyze Trap"] * self.options.paralyze_trap_weight.value
self.traps += ["Ice Trap"] * self.multiworld.ice_trap_weight[self.player].value self.traps += ["Ice Trap"] * self.options.ice_trap_weight.value
self.traps += ["Sleep Trap"] * self.multiworld.sleep_trap_weight[self.player].value self.traps += ["Sleep Trap"] * self.options.sleep_trap_weight.value
return self.multiworld.random.choice(self.traps) return self.random.choice(self.traps)
def extend_hint_information(self, hint_data): def extend_hint_information(self, hint_data):
if self.multiworld.dexsanity[self.player] or self.multiworld.door_shuffle[self.player]: if self.options.dexsanity or self.options.door_shuffle:
hint_data[self.player] = {} hint_data[self.player] = {}
if self.multiworld.dexsanity[self.player]: if self.options.dexsanity:
mon_locations = {mon: set() for mon in poke_data.pokemon_data.keys()} mon_locations = {mon: set() for mon in poke_data.pokemon_data.keys()}
for loc in location_data: for loc in location_data:
if loc.type in ["Wild Encounter", "Static Pokemon", "Legendary Pokemon", "Missable Pokemon"]: if loc.type in ["Wild Encounter", "Static Pokemon", "Legendary Pokemon", "Missable Pokemon"]:
@ -616,57 +656,59 @@ class PokemonRedBlueWorld(World):
hint_data[self.player][self.multiworld.get_location(f"Pokedex - {mon}", self.player).address] =\ hint_data[self.player][self.multiworld.get_location(f"Pokedex - {mon}", self.player).address] =\
", ".join(mon_locations[mon]) ", ".join(mon_locations[mon])
if self.multiworld.door_shuffle[self.player]: if self.options.door_shuffle:
for location in self.multiworld.get_locations(self.player): for location in self.multiworld.get_locations(self.player):
if location.parent_region.entrance_hint and location.address: if location.parent_region.entrance_hint and location.address:
hint_data[self.player][location.address] = location.parent_region.entrance_hint hint_data[self.player][location.address] = location.parent_region.entrance_hint
def fill_slot_data(self) -> dict: def fill_slot_data(self) -> dict:
return { ret = {
"second_fossil_check_condition": self.multiworld.second_fossil_check_condition[self.player].value, "second_fossil_check_condition": self.options.second_fossil_check_condition.value,
"require_item_finder": self.multiworld.require_item_finder[self.player].value, "require_item_finder": self.options.require_item_finder.value,
"randomize_hidden_items": self.multiworld.randomize_hidden_items[self.player].value, "randomize_hidden_items": self.options.randomize_hidden_items.value,
"badges_needed_for_hm_moves": self.multiworld.badges_needed_for_hm_moves[self.player].value, "badges_needed_for_hm_moves": self.options.badges_needed_for_hm_moves.value,
"oaks_aide_rt_2": self.multiworld.oaks_aide_rt_2[self.player].value, "oaks_aide_rt_2": self.options.oaks_aide_rt_2.value,
"oaks_aide_rt_11": self.multiworld.oaks_aide_rt_11[self.player].value, "oaks_aide_rt_11": self.options.oaks_aide_rt_11.value,
"oaks_aide_rt_15": self.multiworld.oaks_aide_rt_15[self.player].value, "oaks_aide_rt_15": self.options.oaks_aide_rt_15.value,
"extra_key_items": self.multiworld.extra_key_items[self.player].value, "extra_key_items": self.options.extra_key_items.value,
"extra_strength_boulders": self.multiworld.extra_strength_boulders[self.player].value, "extra_strength_boulders": self.options.extra_strength_boulders.value,
"tea": self.multiworld.tea[self.player].value, "tea": self.options.tea.value,
"old_man": self.multiworld.old_man[self.player].value, "old_man": self.options.old_man.value,
"elite_four_badges_condition": self.multiworld.elite_four_badges_condition[self.player].value, "elite_four_badges_condition": self.options.elite_four_badges_condition.value,
"elite_four_key_items_condition": self.multiworld.elite_four_key_items_condition[self.player].total, "elite_four_key_items_condition": self.options.elite_four_key_items_condition.total,
"elite_four_pokedex_condition": self.multiworld.elite_four_pokedex_condition[self.player].total, "elite_four_pokedex_condition": self.options.elite_four_pokedex_condition.total,
"victory_road_condition": self.multiworld.victory_road_condition[self.player].value, "victory_road_condition": self.options.victory_road_condition.value,
"route_22_gate_condition": self.multiworld.route_22_gate_condition[self.player].value, "route_22_gate_condition": self.options.route_22_gate_condition.value,
"route_3_condition": self.multiworld.route_3_condition[self.player].value, "route_3_condition": self.options.route_3_condition.value,
"robbed_house_officer": self.multiworld.robbed_house_officer[self.player].value, "robbed_house_officer": self.options.robbed_house_officer.value,
"viridian_gym_condition": self.multiworld.viridian_gym_condition[self.player].value, "viridian_gym_condition": self.options.viridian_gym_condition.value,
"cerulean_cave_badges_condition": self.multiworld.cerulean_cave_badges_condition[self.player].value, "cerulean_cave_badges_condition": self.options.cerulean_cave_badges_condition.value,
"cerulean_cave_key_items_condition": self.multiworld.cerulean_cave_key_items_condition[self.player].total, "cerulean_cave_key_items_condition": self.options.cerulean_cave_key_items_condition.total,
"free_fly_map": self.fly_map_code, "free_fly_map": self.fly_map_code,
"town_map_fly_map": self.town_map_fly_map_code, "town_map_fly_map": self.town_map_fly_map_code,
"extra_badges": self.extra_badges, "extra_badges": self.extra_badges,
"type_chart": self.type_chart, "randomize_pokedex": self.options.randomize_pokedex.value,
"randomize_pokedex": self.multiworld.randomize_pokedex[self.player].value, "trainersanity": self.options.trainersanity.value,
"trainersanity": self.multiworld.trainersanity[self.player].value, "death_link": self.options.death_link.value,
"death_link": self.multiworld.death_link[self.player].value, "prizesanity": self.options.prizesanity.value,
"prizesanity": self.multiworld.prizesanity[self.player].value, "key_items_only": self.options.key_items_only.value,
"key_items_only": self.multiworld.key_items_only[self.player].value, "poke_doll_skip": self.options.poke_doll_skip.value,
"poke_doll_skip": self.multiworld.poke_doll_skip[self.player].value, "bicycle_gate_skips": self.options.bicycle_gate_skips.value,
"bicycle_gate_skips": self.multiworld.bicycle_gate_skips[self.player].value, "stonesanity": self.options.stonesanity.value,
"stonesanity": self.multiworld.stonesanity[self.player].value, "door_shuffle": self.options.door_shuffle.value,
"door_shuffle": self.multiworld.door_shuffle[self.player].value, "warp_tile_shuffle": self.options.warp_tile_shuffle.value,
"warp_tile_shuffle": self.multiworld.warp_tile_shuffle[self.player].value, "dark_rock_tunnel_logic": self.options.dark_rock_tunnel_logic.value,
"dark_rock_tunnel_logic": self.multiworld.dark_rock_tunnel_logic[self.player].value, "split_card_key": self.options.split_card_key.value,
"split_card_key": self.multiworld.split_card_key[self.player].value, "all_elevators_locked": self.options.all_elevators_locked.value,
"all_elevators_locked": self.multiworld.all_elevators_locked[self.player].value, "require_pokedex": self.options.require_pokedex.value,
"require_pokedex": self.multiworld.require_pokedex[self.player].value, "area_1_to_1_mapping": self.options.area_1_to_1_mapping.value,
"area_1_to_1_mapping": self.multiworld.area_1_to_1_mapping[self.player].value, "blind_trainers": self.options.blind_trainers.value,
"blind_trainers": self.multiworld.blind_trainers[self.player].value,
} }
if self.options.type_chart_seed == "random" or self.options.type_chart_seed.value.isdigit():
ret["type_chart"] = self.type_chart
return ret
class PokemonRBItem(Item): class PokemonRBItem(Item):
game = "Pokemon Red and Blue" game = "Pokemon Red and Blue"

View File

@ -60,11 +60,12 @@ and Safari Zone. Adds 4 extra item locations to Rock Tunnel B1F
* Split Card Key: Splits the Card Key into 10 different Card Keys, one for each floor of Silph Co that has locked doors. * Split Card Key: Splits the Card Key into 10 different Card Keys, one for each floor of Silph Co that has locked doors.
Adds 9 location checks to friendly NPCs in Silph Co. You can also choose Progressive Card Keys to always obtain the Adds 9 location checks to friendly NPCs in Silph Co. You can also choose Progressive Card Keys to always obtain the
keys in order from Card Key 2F to Card Key 11F. keys in order from Card Key 2F to Card Key 11F.
* Trainersanity: Adds location checks to 317 trainers. Does not include scripted trainers, most of which disappear * Trainersanity: Adds location checks to trainers. You may choose between 0 and 317 trainersanity checks. Trainers
will be randomly selected to be given checks. Does not include scripted trainers, most of which disappear
after battling them, but also includes Gym Leaders. You must talk to the trainer after defeating them to receive after battling them, but also includes Gym Leaders. You must talk to the trainer after defeating them to receive
your prize. Adds 317 random filler items to the item pool your prize. Adds random filler items to the item pool.
* Dexsanity: Location checks occur when registering Pokémon as owned in the Pokédex. You can choose a percentage * Dexsanity: Location checks occur when registering Pokémon as owned in the Pokédex. You can choose between 0 and 151
of Pokémon to have checks added to, chosen randomly. You can identify which Pokémon have location checks by an empty Pokémon to have checks added to, chosen randomly. You can identify which Pokémon have location checks by an empty
Poké Ball icon shown in battle or in the Pokédex menu. Poké Ball icon shown in battle or in the Pokédex menu.
## Which items can be in another player's world? ## Which items can be in another player's world?

View File

@ -8,7 +8,7 @@ def get_encounter_slots(self):
for location in encounter_slots: for location in encounter_slots:
if isinstance(location.original_item, list): if isinstance(location.original_item, list):
location.original_item = location.original_item[not self.multiworld.game_version[self.player].value] location.original_item = location.original_item[not self.options.game_version.value]
return encounter_slots return encounter_slots
@ -39,16 +39,16 @@ def randomize_pokemon(self, mon, mons_list, randomize_type, random):
return mon return mon
def process_trainer_data(self): def process_trainer_data(world):
mons_list = [pokemon for pokemon in poke_data.pokemon_data.keys() if pokemon not in poke_data.legendary_pokemon mons_list = [pokemon for pokemon in poke_data.pokemon_data.keys() if pokemon not in poke_data.legendary_pokemon
or self.multiworld.trainer_legendaries[self.player].value] or world.options.trainer_legendaries.value]
unevolved_mons = [pokemon for pokemon in poke_data.first_stage_pokemon if pokemon not in poke_data.legendary_pokemon unevolved_mons = [pokemon for pokemon in poke_data.first_stage_pokemon if pokemon not in poke_data.legendary_pokemon
or self.multiworld.randomize_legendary_pokemon[self.player].value == 3] or world.options.randomize_legendary_pokemon.value == 3]
evolved_mons = [mon for mon in mons_list if mon not in unevolved_mons] evolved_mons = [mon for mon in mons_list if mon not in unevolved_mons]
rival_map = { rival_map = {
"Charmander": self.multiworld.get_location("Oak's Lab - Starter 1", self.player).item.name[9:], # strip the "Charmander": world.multiworld.get_location("Oak's Lab - Starter 1", world.player).item.name[9:], # strip the
"Squirtle": self.multiworld.get_location("Oak's Lab - Starter 2", self.player).item.name[9:], # 'Missable' "Squirtle": world.multiworld.get_location("Oak's Lab - Starter 2", world.player).item.name[9:], # 'Missable'
"Bulbasaur": self.multiworld.get_location("Oak's Lab - Starter 3", self.player).item.name[9:], # from the name "Bulbasaur": world.multiworld.get_location("Oak's Lab - Starter 3", world.player).item.name[9:], # from the name
} }
def add_evolutions(): def add_evolutions():
@ -60,7 +60,7 @@ def process_trainer_data(self):
rival_map[poke_data.evolves_to[a]] = b rival_map[poke_data.evolves_to[a]] = b
add_evolutions() add_evolutions()
add_evolutions() add_evolutions()
parties_objs = [location for location in self.multiworld.get_locations(self.player) parties_objs = [location for location in world.multiworld.get_locations(world.player)
if location.type == "Trainer Parties"] if location.type == "Trainer Parties"]
# Process Rival parties in order "Route 22 " is not a typo # Process Rival parties in order "Route 22 " is not a typo
parties_objs.sort(key=lambda i: 0 if "Oak's Lab" in i.name else 1 if "Route 22 " in i.name else 2 if "Cerulean City" parties_objs.sort(key=lambda i: 0 if "Oak's Lab" in i.name else 1 if "Route 22 " in i.name else 2 if "Cerulean City"
@ -75,25 +75,25 @@ def process_trainer_data(self):
for i, mon in enumerate(rival_party): for i, mon in enumerate(rival_party):
if mon in ("Bulbasaur", "Ivysaur", "Venusaur", "Charmander", "Charmeleon", "Charizard", if mon in ("Bulbasaur", "Ivysaur", "Venusaur", "Charmander", "Charmeleon", "Charizard",
"Squirtle", "Wartortle", "Blastoise"): "Squirtle", "Wartortle", "Blastoise"):
if self.multiworld.randomize_starter_pokemon[self.player]: if world.options.randomize_starter_pokemon:
rival_party[i] = rival_map[mon] rival_party[i] = rival_map[mon]
elif self.multiworld.randomize_trainer_parties[self.player]: elif world.options.randomize_trainer_parties:
if mon in rival_map: if mon in rival_map:
rival_party[i] = rival_map[mon] rival_party[i] = rival_map[mon]
else: else:
new_mon = randomize_pokemon(self, mon, new_mon = randomize_pokemon(world, mon,
unevolved_mons if mon in unevolved_mons else evolved_mons, unevolved_mons if mon in unevolved_mons else evolved_mons,
self.multiworld.randomize_trainer_parties[self.player].value, world.options.randomize_trainer_parties.value,
self.multiworld.random) world.random)
rival_map[mon] = new_mon rival_map[mon] = new_mon
rival_party[i] = new_mon rival_party[i] = new_mon
add_evolutions() add_evolutions()
else: else:
if self.multiworld.randomize_trainer_parties[self.player]: if world.options.randomize_trainer_parties:
for i, mon in enumerate(party["party"]): for i, mon in enumerate(party["party"]):
party["party"][i] = randomize_pokemon(self, mon, mons_list, party["party"][i] = randomize_pokemon(world, mon, mons_list,
self.multiworld.randomize_trainer_parties[self.player].value, world.options.randomize_trainer_parties.value,
self.multiworld.random) world.random)
def process_pokemon_locations(self): def process_pokemon_locations(self):
@ -106,21 +106,21 @@ def process_pokemon_locations(self):
placed_mons = {pokemon: 0 for pokemon in poke_data.pokemon_data.keys()} placed_mons = {pokemon: 0 for pokemon in poke_data.pokemon_data.keys()}
mons_list = [pokemon for pokemon in poke_data.first_stage_pokemon if pokemon not in poke_data.legendary_pokemon mons_list = [pokemon for pokemon in poke_data.first_stage_pokemon if pokemon not in poke_data.legendary_pokemon
or self.multiworld.randomize_legendary_pokemon[self.player].value == 3] or self.options.randomize_legendary_pokemon.value == 3]
if self.multiworld.randomize_legendary_pokemon[self.player] == "vanilla": if self.options.randomize_legendary_pokemon == "vanilla":
for slot in legendary_slots: for slot in legendary_slots:
location = self.multiworld.get_location(slot.name, self.player) location = self.multiworld.get_location(slot.name, self.player)
location.place_locked_item(self.create_item("Static " + slot.original_item)) location.place_locked_item(self.create_item("Static " + slot.original_item))
elif self.multiworld.randomize_legendary_pokemon[self.player] == "shuffle": elif self.options.randomize_legendary_pokemon == "shuffle":
self.multiworld.random.shuffle(legendary_mons) self.random.shuffle(legendary_mons)
for slot in legendary_slots: for slot in legendary_slots:
location = self.multiworld.get_location(slot.name, self.player) location = self.multiworld.get_location(slot.name, self.player)
mon = legendary_mons.pop() mon = legendary_mons.pop()
location.place_locked_item(self.create_item("Static " + mon)) location.place_locked_item(self.create_item("Static " + mon))
placed_mons[mon] += 1 placed_mons[mon] += 1
elif self.multiworld.randomize_legendary_pokemon[self.player] == "static": elif self.options.randomize_legendary_pokemon == "static":
static_slots = static_slots + legendary_slots static_slots = static_slots + legendary_slots
self.multiworld.random.shuffle(static_slots) self.random.shuffle(static_slots)
static_slots.sort(key=lambda s: s.name != "Pokemon Tower 6F - Restless Soul") static_slots.sort(key=lambda s: s.name != "Pokemon Tower 6F - Restless Soul")
while legendary_slots: while legendary_slots:
swap_slot = legendary_slots.pop() swap_slot = legendary_slots.pop()
@ -131,12 +131,12 @@ def process_pokemon_locations(self):
location = self.multiworld.get_location(slot.name, self.player) location = self.multiworld.get_location(slot.name, self.player)
location.place_locked_item(self.create_item(slot_type + " " + swap_slot.original_item)) location.place_locked_item(self.create_item(slot_type + " " + swap_slot.original_item))
swap_slot.original_item = slot.original_item swap_slot.original_item = slot.original_item
elif self.multiworld.randomize_legendary_pokemon[self.player] == "any": elif self.options.randomize_legendary_pokemon == "any":
static_slots = static_slots + legendary_slots static_slots = static_slots + legendary_slots
for slot in static_slots: for slot in static_slots:
location = self.multiworld.get_location(slot.name, self.player) location = self.multiworld.get_location(slot.name, self.player)
randomize_type = self.multiworld.randomize_static_pokemon[self.player].value randomize_type = self.options.randomize_static_pokemon.value
slot_type = slot.type.split()[0] slot_type = slot.type.split()[0]
if slot_type == "Legendary": if slot_type == "Legendary":
slot_type = "Static" slot_type = "Static"
@ -145,7 +145,7 @@ def process_pokemon_locations(self):
else: else:
mon = self.create_item(slot_type + " " + mon = self.create_item(slot_type + " " +
randomize_pokemon(self, slot.original_item, mons_list, randomize_type, randomize_pokemon(self, slot.original_item, mons_list, randomize_type,
self.multiworld.random)) self.random))
location.place_locked_item(mon) location.place_locked_item(mon)
if slot_type != "Missable": if slot_type != "Missable":
placed_mons[mon.name.replace("Static ", "")] += 1 placed_mons[mon.name.replace("Static ", "")] += 1
@ -153,16 +153,16 @@ def process_pokemon_locations(self):
chosen_mons = set() chosen_mons = set()
for slot in starter_slots: for slot in starter_slots:
location = self.multiworld.get_location(slot.name, self.player) location = self.multiworld.get_location(slot.name, self.player)
randomize_type = self.multiworld.randomize_starter_pokemon[self.player].value randomize_type = self.options.randomize_starter_pokemon.value
slot_type = "Missable" slot_type = "Missable"
if not randomize_type: if not randomize_type:
location.place_locked_item(self.create_item(slot_type + " " + slot.original_item)) location.place_locked_item(self.create_item(slot_type + " " + slot.original_item))
else: else:
mon = self.create_item(slot_type + " " + randomize_pokemon(self, slot.original_item, mons_list, mon = self.create_item(slot_type + " " + randomize_pokemon(self, slot.original_item, mons_list,
randomize_type, self.multiworld.random)) randomize_type, self.random))
while mon.name in chosen_mons: while mon.name in chosen_mons:
mon = self.create_item(slot_type + " " + randomize_pokemon(self, slot.original_item, mons_list, mon = self.create_item(slot_type + " " + randomize_pokemon(self, slot.original_item, mons_list,
randomize_type, self.multiworld.random)) randomize_type, self.random))
chosen_mons.add(mon.name) chosen_mons.add(mon.name)
location.place_locked_item(mon) location.place_locked_item(mon)
@ -170,10 +170,10 @@ def process_pokemon_locations(self):
encounter_slots = encounter_slots_master.copy() encounter_slots = encounter_slots_master.copy()
zone_mapping = {} zone_mapping = {}
if self.multiworld.randomize_wild_pokemon[self.player]: if self.options.randomize_wild_pokemon:
mons_list = [pokemon for pokemon in poke_data.pokemon_data.keys() if pokemon not in poke_data.legendary_pokemon mons_list = [pokemon for pokemon in poke_data.pokemon_data.keys() if pokemon not in poke_data.legendary_pokemon
or self.multiworld.randomize_legendary_pokemon[self.player].value == 3] or self.options.randomize_legendary_pokemon.value == 3]
self.multiworld.random.shuffle(encounter_slots) self.random.shuffle(encounter_slots)
locations = [] locations = []
for slot in encounter_slots: for slot in encounter_slots:
location = self.multiworld.get_location(slot.name, self.player) location = self.multiworld.get_location(slot.name, self.player)
@ -181,11 +181,11 @@ def process_pokemon_locations(self):
if zone not in zone_mapping: if zone not in zone_mapping:
zone_mapping[zone] = {} zone_mapping[zone] = {}
original_mon = slot.original_item original_mon = slot.original_item
if self.multiworld.area_1_to_1_mapping[self.player] and original_mon in zone_mapping[zone]: if self.options.area_1_to_1_mapping and original_mon in zone_mapping[zone]:
mon = zone_mapping[zone][original_mon] mon = zone_mapping[zone][original_mon]
else: else:
mon = randomize_pokemon(self, original_mon, mons_list, mon = randomize_pokemon(self, original_mon, mons_list,
self.multiworld.randomize_wild_pokemon[self.player].value, self.multiworld.random) self.options.randomize_wild_pokemon.value, self.random)
# #
while ("Pokemon Tower 6F" in slot.name and while ("Pokemon Tower 6F" in slot.name and
self.multiworld.get_location("Pokemon Tower 6F - Restless Soul", self.player).item.name self.multiworld.get_location("Pokemon Tower 6F - Restless Soul", self.player).item.name
@ -194,7 +194,7 @@ def process_pokemon_locations(self):
# the battle is treates as the Restless Soul battle and you cannot catch it. So, prevent any wild mons # the battle is treates as the Restless Soul battle and you cannot catch it. So, prevent any wild mons
# from being the same species as the Restless Soul. # from being the same species as the Restless Soul.
# to account for the possibility that only one ground type Pokemon exists, match only stats for this fix # to account for the possibility that only one ground type Pokemon exists, match only stats for this fix
mon = randomize_pokemon(self, original_mon, mons_list, 2, self.multiworld.random) mon = randomize_pokemon(self, original_mon, mons_list, 2, self.random)
placed_mons[mon] += 1 placed_mons[mon] += 1
location.item = self.create_item(mon) location.item = self.create_item(mon)
location.locked = True location.locked = True
@ -204,28 +204,28 @@ def process_pokemon_locations(self):
mons_to_add = [] mons_to_add = []
remaining_pokemon = [pokemon for pokemon in poke_data.pokemon_data.keys() if placed_mons[pokemon] == 0 and remaining_pokemon = [pokemon for pokemon in poke_data.pokemon_data.keys() if placed_mons[pokemon] == 0 and
(pokemon not in poke_data.legendary_pokemon or self.multiworld.randomize_legendary_pokemon[self.player].value == 3)] (pokemon not in poke_data.legendary_pokemon or self.options.randomize_legendary_pokemon.value == 3)]
if self.multiworld.catch_em_all[self.player] == "first_stage": if self.options.catch_em_all == "first_stage":
mons_to_add = [pokemon for pokemon in poke_data.first_stage_pokemon if placed_mons[pokemon] == 0 and mons_to_add = [pokemon for pokemon in poke_data.first_stage_pokemon if placed_mons[pokemon] == 0 and
(pokemon not in poke_data.legendary_pokemon or self.multiworld.randomize_legendary_pokemon[self.player].value == 3)] (pokemon not in poke_data.legendary_pokemon or self.options.randomize_legendary_pokemon.value == 3)]
elif self.multiworld.catch_em_all[self.player] == "all_pokemon": elif self.options.catch_em_all == "all_pokemon":
mons_to_add = remaining_pokemon.copy() mons_to_add = remaining_pokemon.copy()
logic_needed_mons = max(self.multiworld.oaks_aide_rt_2[self.player].value, logic_needed_mons = max(self.options.oaks_aide_rt_2.value,
self.multiworld.oaks_aide_rt_11[self.player].value, self.options.oaks_aide_rt_11.value,
self.multiworld.oaks_aide_rt_15[self.player].value) self.options.oaks_aide_rt_15.value)
if self.multiworld.accessibility[self.player] == "minimal": if self.options.accessibility == "minimal":
logic_needed_mons = 0 logic_needed_mons = 0
self.multiworld.random.shuffle(remaining_pokemon) self.random.shuffle(remaining_pokemon)
while (len([pokemon for pokemon in placed_mons if placed_mons[pokemon] > 0]) while (len([pokemon for pokemon in placed_mons if placed_mons[pokemon] > 0])
+ len(mons_to_add) < logic_needed_mons): + len(mons_to_add) < logic_needed_mons):
mons_to_add.append(remaining_pokemon.pop()) mons_to_add.append(remaining_pokemon.pop())
for mon in mons_to_add: for mon in mons_to_add:
stat_base = get_base_stat_total(mon) stat_base = get_base_stat_total(mon)
candidate_locations = encounter_slots_master.copy() candidate_locations = encounter_slots_master.copy()
if self.multiworld.randomize_wild_pokemon[self.player].current_key in ["match_base_stats", "match_types_and_base_stats"]: if self.options.randomize_wild_pokemon.current_key in ["match_base_stats", "match_types_and_base_stats"]:
candidate_locations.sort(key=lambda slot: abs(get_base_stat_total(slot.original_item) - stat_base)) candidate_locations.sort(key=lambda slot: abs(get_base_stat_total(slot.original_item) - stat_base))
if self.multiworld.randomize_wild_pokemon[self.player].current_key in ["match_types", "match_types_and_base_stats"]: if self.options.randomize_wild_pokemon.current_key in ["match_types", "match_types_and_base_stats"]:
candidate_locations.sort(key=lambda slot: not any([poke_data.pokemon_data[slot.original_item]["type1"] in candidate_locations.sort(key=lambda slot: not any([poke_data.pokemon_data[slot.original_item]["type1"] in
[self.local_poke_data[mon]["type1"], self.local_poke_data[mon]["type2"]], [self.local_poke_data[mon]["type1"], self.local_poke_data[mon]["type2"]],
poke_data.pokemon_data[slot.original_item]["type2"] in poke_data.pokemon_data[slot.original_item]["type2"] in
@ -233,12 +233,12 @@ def process_pokemon_locations(self):
candidate_locations = [self.multiworld.get_location(location.name, self.player) for location in candidate_locations] candidate_locations = [self.multiworld.get_location(location.name, self.player) for location in candidate_locations]
for location in candidate_locations: for location in candidate_locations:
zone = " - ".join(location.name.split(" - ")[:-1]) zone = " - ".join(location.name.split(" - ")[:-1])
if self.multiworld.catch_em_all[self.player] == "all_pokemon" and self.multiworld.area_1_to_1_mapping[self.player]: if self.options.catch_em_all == "all_pokemon" and self.options.area_1_to_1_mapping:
if not [self.multiworld.get_location(l.name, self.player) for l in encounter_slots_master if not [self.multiworld.get_location(l.name, self.player) for l in encounter_slots_master
if (not l.name.startswith(zone)) and if (not l.name.startswith(zone)) and
self.multiworld.get_location(l.name, self.player).item.name == location.item.name]: self.multiworld.get_location(l.name, self.player).item.name == location.item.name]:
continue continue
if self.multiworld.catch_em_all[self.player] == "first_stage" and self.multiworld.area_1_to_1_mapping[self.player]: if self.options.catch_em_all == "first_stage" and self.options.area_1_to_1_mapping:
if not [self.multiworld.get_location(l.name, self.player) for l in encounter_slots_master if not [self.multiworld.get_location(l.name, self.player) for l in encounter_slots_master
if (not l.name.startswith(zone)) and if (not l.name.startswith(zone)) and
self.multiworld.get_location(l.name, self.player).item.name == location.item.name and l.name self.multiworld.get_location(l.name, self.player).item.name == location.item.name and l.name
@ -246,10 +246,10 @@ def process_pokemon_locations(self):
continue continue
if placed_mons[location.item.name] < 2 and (location.item.name in poke_data.first_stage_pokemon if placed_mons[location.item.name] < 2 and (location.item.name in poke_data.first_stage_pokemon
or self.multiworld.catch_em_all[self.player]): or self.options.catch_em_all):
continue continue
if self.multiworld.area_1_to_1_mapping[self.player]: if self.options.area_1_to_1_mapping:
place_locations = [place_location for place_location in candidate_locations if place_locations = [place_location for place_location in candidate_locations if
place_location.name.startswith(zone) and place_location.name.startswith(zone) and
place_location.item.name == location.item.name] place_location.item.name == location.item.name]

View File

@ -194,6 +194,8 @@ item_table = {
"Fuji Saved": ItemData(None, ItemClassification.progression, []), "Fuji Saved": ItemData(None, ItemClassification.progression, []),
"Silph Co Liberated": ItemData(None, ItemClassification.progression, []), "Silph Co Liberated": ItemData(None, ItemClassification.progression, []),
"Become Champion": ItemData(None, ItemClassification.progression, []), "Become Champion": ItemData(None, ItemClassification.progression, []),
"Mt Moon Fossils": ItemData(None, ItemClassification.progression, []),
"Cinnabar Lab": ItemData(None, ItemClassification.progression, []),
"Trainer Parties": ItemData(None, ItemClassification.filler, []) "Trainer Parties": ItemData(None, ItemClassification.filler, [])
} }

View File

@ -10,9 +10,9 @@ def level_scaling(multiworld):
while locations: while locations:
sphere = set() sphere = set()
for world in multiworld.get_game_worlds("Pokemon Red and Blue"): for world in multiworld.get_game_worlds("Pokemon Red and Blue"):
if (multiworld.level_scaling[world.player] != "by_spheres_and_distance" if (world.options.level_scaling != "by_spheres_and_distance"
and (multiworld.level_scaling[world.player] != "auto" or multiworld.door_shuffle[world.player] and (world.options.level_scaling != "auto"
in ("off", "simple"))): or world.options.door_shuffle in ("off", "simple"))):
continue continue
regions = {multiworld.get_region("Menu", world.player)} regions = {multiworld.get_region("Menu", world.player)}
checked_regions = set() checked_regions = set()
@ -41,7 +41,8 @@ def level_scaling(multiworld):
# reach them earlier. We treat them both as reachable right away for this purpose # reach them earlier. We treat them both as reachable right away for this purpose
return True return True
if (location.name == "Route 25 - Item" and state.can_reach("Route 25", "Region", location.player) if (location.name == "Route 25 - Item" and state.can_reach("Route 25", "Region", location.player)
and multiworld.blind_trainers[location.player].value < 100): and multiworld.worlds[location.player].options.blind_trainers.value < 100
and "Route 25 - Jr. Trainer M" not in multiworld.regions.location_cache[location.player]):
# Assume they will take their one chance to get the trainer to walk out of the way to reach # Assume they will take their one chance to get the trainer to walk out of the way to reach
# the item behind them # the item behind them
return True return True
@ -95,9 +96,9 @@ def level_scaling(multiworld):
if (location.item.game == "Pokemon Red and Blue" and (location.item.name.startswith("Missable ") or if (location.item.game == "Pokemon Red and Blue" and (location.item.name.startswith("Missable ") or
location.item.name.startswith("Static ")) and location.name != location.item.name.startswith("Static ")) and location.name !=
"Pokemon Tower 6F - Restless Soul"): "Pokemon Tower 6F - Restless Soul"):
# Normally, missable Pokemon (starters, the dojo rewards) are not considered in logic static Pokemon # Normally, missable Pokemon (starters, the dojo rewards) are not considered in logic, and static
# are not considered for moves or evolutions, as you could release them and potentially soft lock # Pokemon are not considered for moves or evolutions, as you could release them and potentially soft
# the game. However, for level scaling purposes, we will treat them as not missable or static. # lock the game. However, for level scaling purposes, we will treat them as not missable or static.
# We would not want someone playing a minimal accessibility Dexsanity game to get what would be # We would not want someone playing a minimal accessibility Dexsanity game to get what would be
# technically an "out of logic" Mansion Key from selecting Bulbasaur at the beginning of the game # technically an "out of logic" Mansion Key from selecting Bulbasaur at the beginning of the game
# and end up in the Mansion early and encountering level 67 Pokémon # and end up in the Mansion early and encountering level 67 Pokémon
@ -106,7 +107,7 @@ def level_scaling(multiworld):
else: else:
state.collect(location.item, True, location) state.collect(location.item, True, location)
for world in multiworld.get_game_worlds("Pokemon Red and Blue"): for world in multiworld.get_game_worlds("Pokemon Red and Blue"):
if multiworld.level_scaling[world.player] == "off": if world.options.level_scaling == "off":
continue continue
level_list_copy = level_list.copy() level_list_copy = level_list.copy()
for sphere in spheres: for sphere in spheres:
@ -136,4 +137,4 @@ def level_scaling(multiworld):
else: else:
sphere_objects[object].level = level_list_copy.pop(0) sphere_objects[object].level = level_list_copy.pop(0)
for world in multiworld.get_game_worlds("Pokemon Red and Blue"): for world in multiworld.get_game_worlds("Pokemon Red and Blue"):
world.finished_level_scaling.set() world.finished_level_scaling.set()

View File

@ -5,46 +5,48 @@ from . import poke_data
loc_id_start = 172000000 loc_id_start = 172000000
def trainersanity(multiworld, player): def trainersanity(world, player):
return multiworld.trainersanity[player] include = world.trainersanity_table.pop(0)
world.trainersanity_table.append(include)
def dexsanity(multiworld, player):
include = multiworld.worlds[player].dexsanity_table.pop(0)
multiworld.worlds[player].dexsanity_table.append(include)
return include return include
def hidden_items(multiworld, player): def dexsanity(world, player):
return multiworld.randomize_hidden_items[player] include = world.dexsanity_table.pop(0)
world.dexsanity_table.append(include)
return include
def hidden_moon_stones(multiworld, player): def hidden_items(world, player):
return multiworld.randomize_hidden_items[player] or multiworld.stonesanity[player] return world.options.randomize_hidden_items
def tea(multiworld, player): def hidden_moon_stones(world, player):
return multiworld.tea[player] return world.options.randomize_hidden_items or world.options.stonesanity
def extra_key_items(multiworld, player): def tea(world, player):
return multiworld.extra_key_items[player] return world.options.tea
def always_on(multiworld, player): def extra_key_items(world, player):
return world.options.extra_key_items
def always_on(world, player):
return True return True
def prizesanity(multiworld, player): def prizesanity(world, player):
return multiworld.prizesanity[player] return world.options.prizesanity
def split_card_key(multiworld, player): def split_card_key(world, player):
return multiworld.split_card_key[player].value > 0 return world.options.split_card_key.value > 0
def not_stonesanity(multiworld, player): def not_stonesanity(world, player):
return not multiworld.stonesanity[player] return not world.options.stonesanity
class LocationData: class LocationData:
@ -395,7 +397,7 @@ location_data = [
LocationData("Silph Co 5F", "Hidden Item Pot Plant", "Elixir", rom_addresses['Hidden_Item_Silph_Co_5F'], Hidden(18), inclusion=hidden_items), LocationData("Silph Co 5F", "Hidden Item Pot Plant", "Elixir", rom_addresses['Hidden_Item_Silph_Co_5F'], Hidden(18), inclusion=hidden_items),
LocationData("Silph Co 9F-SW", "Hidden Item Nurse Bed", "Max Potion", rom_addresses['Hidden_Item_Silph_Co_9F'], Hidden(19), inclusion=hidden_items), LocationData("Silph Co 9F-SW", "Hidden Item Nurse Bed", "Max Potion", rom_addresses['Hidden_Item_Silph_Co_9F'], Hidden(19), inclusion=hidden_items),
LocationData("Saffron Copycat's House 2F", "Hidden Item Desk", "Nugget", rom_addresses['Hidden_Item_Copycats_House'], Hidden(20), inclusion=hidden_items), LocationData("Saffron Copycat's House 2F", "Hidden Item Desk", "Nugget", rom_addresses['Hidden_Item_Copycats_House'], Hidden(20), inclusion=hidden_items),
LocationData("Cerulean Cave 1F-NW", "Hidden Item Center Rocks", "Rare Candy", rom_addresses['Hidden_Item_Cerulean_Cave_1F'], Hidden(21), inclusion=hidden_items), LocationData("Cerulean Cave 1F-SW", "Hidden Item Center Rocks", "Rare Candy", rom_addresses['Hidden_Item_Cerulean_Cave_1F'], Hidden(21), inclusion=hidden_items),
LocationData("Cerulean Cave B1F-E", "Hidden Item Northeast Rocks", "Ultra Ball", rom_addresses['Hidden_Item_Cerulean_Cave_B1F'], Hidden(22), inclusion=hidden_items), LocationData("Cerulean Cave B1F-E", "Hidden Item Northeast Rocks", "Ultra Ball", rom_addresses['Hidden_Item_Cerulean_Cave_B1F'], Hidden(22), inclusion=hidden_items),
LocationData("Power Plant", "Hidden Item Central Dead End", "Max Elixir", rom_addresses['Hidden_Item_Power_Plant_1'], Hidden(23), inclusion=hidden_items), LocationData("Power Plant", "Hidden Item Central Dead End", "Max Elixir", rom_addresses['Hidden_Item_Power_Plant_1'], Hidden(23), inclusion=hidden_items),
LocationData("Power Plant", "Hidden Item Before Zapdos", "PP Up", rom_addresses['Hidden_Item_Power_Plant_2'], Hidden(24), inclusion=hidden_items), LocationData("Power Plant", "Hidden Item Before Zapdos", "PP Up", rom_addresses['Hidden_Item_Power_Plant_2'], Hidden(24), inclusion=hidden_items),
@ -786,6 +788,8 @@ location_data = [
LocationData("Celadon Game Corner", "", "Game Corner", event=True), LocationData("Celadon Game Corner", "", "Game Corner", event=True),
LocationData("Cinnabar Island", "", "Cinnabar Island", event=True), LocationData("Cinnabar Island", "", "Cinnabar Island", event=True),
LocationData("Cinnabar Lab", "", "Cinnabar Lab", event=True),
LocationData("Mt Moon B2F", "Mt Moon Fossils", "Mt Moon Fossils", event=True),
LocationData("Celadon Department Store 4F", "Buy Poke Doll", "Buy Poke Doll", event=True), LocationData("Celadon Department Store 4F", "Buy Poke Doll", "Buy Poke Doll", event=True),
LocationData("Celadon Department Store 4F", "Buy Fire Stone", "Fire Stone", event=True, inclusion=not_stonesanity), LocationData("Celadon Department Store 4F", "Buy Fire Stone", "Fire Stone", event=True, inclusion=not_stonesanity),
LocationData("Celadon Department Store 4F", "Buy Water Stone", "Water Stone", event=True, inclusion=not_stonesanity), LocationData("Celadon Department Store 4F", "Buy Water Stone", "Water Stone", event=True, inclusion=not_stonesanity),

View File

@ -1,49 +1,47 @@
from . import poke_data from . import poke_data
def can_surf(state, player): def can_surf(state, world, player):
return (((state.has("HM03 Surf", player) and can_learn_hm(state, "Surf", player)) return (((state.has("HM03 Surf", player) and can_learn_hm(state, world, "Surf", player))) and (state.has("Soul Badge", player) or
or state.has("Flippers", player)) and (state.has("Soul Badge", player) or state.has(world.extra_badges.get("Surf"), player)
state.has(state.multiworld.worlds[player].extra_badges.get("Surf"), player) or world.options.badges_needed_for_hm_moves.value == 0))
or state.multiworld.badges_needed_for_hm_moves[player].value == 0))
def can_cut(state, player): def can_cut(state, world, player):
return ((state.has("HM01 Cut", player) and can_learn_hm(state, "Cut", player) or state.has("Master Sword", player)) return ((state.has("HM01 Cut", player) and can_learn_hm(state, world, "Cut", player))
and (state.has("Cascade Badge", player) or and (state.has("Cascade Badge", player) or state.has(world.extra_badges.get("Cut"), player) or
state.has(state.multiworld.worlds[player].extra_badges.get("Cut"), player) or world.options.badges_needed_for_hm_moves.value == 0))
state.multiworld.badges_needed_for_hm_moves[player].value == 0))
def can_fly(state, player): def can_fly(state, world, player):
return (((state.has("HM02 Fly", player) and can_learn_hm(state, "Fly", player)) or state.has("Flute", player)) and return (((state.has("HM02 Fly", player) and can_learn_hm(state, world, "Fly", player)) or state.has("Flute", player)) and
(state.has("Thunder Badge", player) or state.has(state.multiworld.worlds[player].extra_badges.get("Fly"), player) (state.has("Thunder Badge", player) or state.has(world.extra_badges.get("Fly"), player)
or state.multiworld.badges_needed_for_hm_moves[player].value == 0)) or world.options.badges_needed_for_hm_moves.value == 0))
def can_strength(state, player): def can_strength(state, world, player):
return ((state.has("HM04 Strength", player) and can_learn_hm(state, "Strength", player)) or return ((state.has("HM04 Strength", player) and can_learn_hm(state, world, "Strength", player)) or
state.has("Titan's Mitt", player)) and (state.has("Rainbow Badge", player) or state.has("Titan's Mitt", player)) and (state.has("Rainbow Badge", player) or
state.has(state.multiworld.worlds[player].extra_badges.get("Strength"), player) state.has(world.extra_badges.get("Strength"), player)
or state.multiworld.badges_needed_for_hm_moves[player].value == 0) or world.options.badges_needed_for_hm_moves.value == 0)
def can_flash(state, player): def can_flash(state, world, player):
return (((state.has("HM05 Flash", player) and can_learn_hm(state, "Flash", player)) or state.has("Lamp", player)) return (((state.has("HM05 Flash", player) and can_learn_hm(state, world, "Flash", player)) or state.has("Lamp", player))
and (state.has("Boulder Badge", player) or state.has(state.multiworld.worlds[player].extra_badges.get("Flash"), and (state.has("Boulder Badge", player) or state.has(world.extra_badges.get("Flash"),
player) or state.multiworld.badges_needed_for_hm_moves[player].value == 0)) player) or world.options.badges_needed_for_hm_moves.value == 0))
def can_learn_hm(state, move, player): def can_learn_hm(state, world, move, player):
for pokemon, data in state.multiworld.worlds[player].local_poke_data.items(): for pokemon, data in world.local_poke_data.items():
if state.has(pokemon, player) and data["tms"][6] & 1 << (["Cut", "Fly", "Surf", "Strength", if state.has(pokemon, player) and data["tms"][6] & 1 << (["Cut", "Fly", "Surf", "Strength",
"Flash"].index(move) + 2): "Flash"].index(move) + 2):
return True return True
return False return False
def can_get_hidden_items(state, player): def can_get_hidden_items(state, world, player):
return state.has("Item Finder", player) or not state.multiworld.require_item_finder[player].value return state.has("Item Finder", player) or not world.options.require_item_finder.value
def has_key_items(state, count, player): def has_key_items(state, count, player):
@ -53,13 +51,14 @@ def has_key_items(state, count, player):
"Hideout Key", "Card Key 2F", "Card Key 3F", "Card Key 4F", "Card Key 5F", "Hideout Key", "Card Key 2F", "Card Key 3F", "Card Key 4F", "Card Key 5F",
"Card Key 6F", "Card Key 7F", "Card Key 8F", "Card Key 9F", "Card Key 10F", "Card Key 6F", "Card Key 7F", "Card Key 8F", "Card Key 9F", "Card Key 10F",
"Card Key 11F", "Exp. All", "Fire Stone", "Thunder Stone", "Water Stone", "Card Key 11F", "Exp. All", "Fire Stone", "Thunder Stone", "Water Stone",
"Leaf Stone", "Moon Stone"] if state.has(item, player)]) "Leaf Stone", "Moon Stone", "Oak's Parcel", "Helix Fossil", "Dome Fossil",
"Old Amber", "Tea", "Gold Teeth", "Bike Voucher"] if state.has(item, player)])
+ min(state.count("Progressive Card Key", player), 10)) + min(state.count("Progressive Card Key", player), 10))
return key_items >= count return key_items >= count
def can_pass_guards(state, player): def can_pass_guards(state, world, player):
if state.multiworld.tea[player]: if world.options.tea:
return state.has("Tea", player) return state.has("Tea", player)
else: else:
return state.has("Vending Machine Drinks", player) return state.has("Vending Machine Drinks", player)
@ -70,8 +69,8 @@ def has_badges(state, count, player):
"Soul Badge", "Volcano Badge", "Earth Badge"] if state.has(item, player)]) >= count "Soul Badge", "Volcano Badge", "Earth Badge"] if state.has(item, player)]) >= count
def oaks_aide(state, count, player): def oaks_aide(state, world, count, player):
return ((not state.multiworld.require_pokedex[player] or state.has("Pokedex", player)) return ((not world.options.require_pokedex or state.has("Pokedex", player))
and has_pokemon(state, count, player)) and has_pokemon(state, count, player))
@ -85,9 +84,7 @@ def has_pokemon(state, count, player):
def fossil_checks(state, count, player): def fossil_checks(state, count, player):
return (state.can_reach('Mt Moon B2F', 'Region', player) and return (state.has_all(["Mt Moon Fossils", "Cinnabar Lab", "Cinnabar Island"], player) and len(
state.can_reach('Cinnabar Lab Fossil Room', 'Region', player) and
state.can_reach('Cinnabar Island', 'Region', player) and len(
[item for item in ["Dome Fossil", "Helix Fossil", "Old Amber"] if state.has(item, player)]) >= count) [item for item in ["Dome Fossil", "Helix Fossil", "Old Amber"] if state.has(item, player)]) >= count)
@ -96,19 +93,19 @@ def card_key(state, floor, player):
state.has("Progressive Card Key", player, floor - 1) state.has("Progressive Card Key", player, floor - 1)
def rock_tunnel(state, player): def rock_tunnel(state, world, player):
return can_flash(state, player) or not state.multiworld.dark_rock_tunnel_logic[player] return can_flash(state, world, player) or not world.options.dark_rock_tunnel_logic
def route_3(state, player): def route(state, world, player):
if state.multiworld.route_3_condition[player] == "defeat_brock": if world.options.route_3_condition == "defeat_brock":
return state.has("Defeat Brock", player) return state.has("Defeat Brock", player)
elif state.multiworld.route_3_condition[player] == "defeat_any_gym": elif world.options.route_3_condition == "defeat_any_gym":
return state.has_any(["Defeat Brock", "Defeat Misty", "Defeat Lt. Surge", "Defeat Erika", "Defeat Koga", return state.has_any(["Defeat Brock", "Defeat Misty", "Defeat Lt. Surge", "Defeat Erika", "Defeat Koga",
"Defeat Blaine", "Defeat Sabrina", "Defeat Viridian Gym Giovanni"], player) "Defeat Blaine", "Defeat Sabrina", "Defeat Viridian Gym Giovanni"], player)
elif state.multiworld.route_3_condition[player] == "boulder_badge": elif world.options.route_3_condition == "boulder_badge":
return state.has("Boulder Badge", player) return state.has("Boulder Badge", player)
elif state.multiworld.route_3_condition[player] == "any_badge": elif world.options.route_3_condition == "any_badge":
return state.has_any(["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Marsh Badge", return state.has_any(["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Marsh Badge",
"Soul Badge", "Volcano Badge", "Earth Badge"], player) "Soul Badge", "Volcano Badge", "Earth Badge"], player)
# open # open

View File

@ -1,4 +1,6 @@
from Options import Toggle, Choice, Range, NamedRange, TextChoice, DeathLink, ItemsAccessibility from dataclasses import dataclass
from Options import (PerGameCommonOptions, Toggle, Choice, Range, NamedRange, FreeText, TextChoice, DeathLink,
ItemsAccessibility)
class GameVersion(Choice): class GameVersion(Choice):
@ -263,12 +265,18 @@ class PrizeSanity(Toggle):
default = 0 default = 0
class TrainerSanity(Toggle): class TrainerSanity(NamedRange):
"""Add a location check to every trainer in the game, which can be obtained by talking to a trainer after defeating """Add location checks to trainers, which can be obtained by talking to a trainer after defeating them. Does not
them. Does not affect gym leaders and some scripted event battles (including all Rival, Giovanni, and affect gym leaders and some scripted event battles. You may specify a number of trainers to have checks, and in
Cinnabar Gym battles).""" this case they will be randomly selected. There is no in-game indication as to which trainers have checks."""
display_name = "Trainersanity" display_name = "Trainersanity"
default = 0 default = 0
range_start = 0
range_end = 317
special_range_names = {
"disabled": 0,
"full": 317
}
class RequirePokedex(Toggle): class RequirePokedex(Toggle):
@ -286,19 +294,19 @@ class AllPokemonSeen(Toggle):
class DexSanity(NamedRange): class DexSanity(NamedRange):
"""Adds location checks for Pokemon flagged "owned" on your Pokedex. You may specify a percentage of Pokemon to """Adds location checks for Pokemon flagged "owned" on your Pokedex. You may specify the exact number of Dexsanity
have checks added. If Accessibility is set to full, this will be the percentage of all logically reachable checks to add, and they will be distributed to Pokemon randomly.
Pokemon that will get a location check added to it. With items or minimal Accessibility, it will be the percentage If Accessibility is set to Full, Dexsanity checks for Pokemon that are not logically reachable will be removed,
of all 151 Pokemon. so the number could be lower than you specified.
If Pokedex is required, the items for Pokemon acquired before acquiring the Pokedex can be found by talking to If Pokedex is required, the Dexsanity checks for Pokemon you acquired before acquiring the Pokedex can be found by
Professor Oak or evaluating the Pokedex via Oak's PC.""" talking to Professor Oak or evaluating the Pokedex via Oak's PC."""
display_name = "Dexsanity" display_name = "Dexsanity"
default = 0 default = 0
range_start = 0 range_start = 0
range_end = 100 range_end = 151
special_range_names = { special_range_names = {
"disabled": 0, "disabled": 0,
"full": 100 "full": 151
} }
@ -519,7 +527,8 @@ class TrainerLegendaries(Toggle):
class BlindTrainers(Range): class BlindTrainers(Range):
"""Chance each frame that you are standing on a tile in a trainer's line of sight that they will fail to initiate a """Chance each frame that you are standing on a tile in a trainer's line of sight that they will fail to initiate a
battle. If you move into and out of their line of sight without stopping, this chance will only trigger once.""" battle. If you move into and out of their line of sight without stopping, this chance will only trigger once.
Trainers which have Trainersanity location checks ignore the Blind Trainers setting."""
display_name = "Blind Trainers" display_name = "Blind Trainers"
range_start = 0 range_start = 0
range_end = 100 range_end = 100
@ -704,6 +713,15 @@ class RandomizeTypeChart(Choice):
default = 0 default = 0
class TypeChartSeed(FreeText):
"""You can enter a number to use as a seed for the type chart. If you enter anything besides a number or "random",
it will be used as a type chart group name, and everyone using the same group name will get the same type chart,
made using the type chart options of one random player within the group. If a group name is used, the type matchup
information will not be made available for trackers."""
display_name = "Type Chart Seed"
default = "random"
class NormalMatchups(Range): class NormalMatchups(Range):
"""If 'randomize' is chosen for Randomize Type Chart, this will be the weight for neutral matchups. """If 'randomize' is chosen for Randomize Type Chart, this will be the weight for neutral matchups.
No effect if 'chaos' is chosen""" No effect if 'chaos' is chosen"""
@ -850,8 +868,8 @@ class BicycleGateSkips(Choice):
class RandomizePokemonPalettes(Choice): class RandomizePokemonPalettes(Choice):
"""Modify palettes of Pokemon. Primary Type will set Pokemons' palettes based on their primary type, Follow """Modify Super Gameboy palettes of Pokemon. Primary Type will set Pokemons' palettes based on their primary type,
Evolutions will randomize palettes but palettes will remain the same through evolutions (except Eeveelutions), Follow Evolutions will randomize palettes but they will remain the same through evolutions (except Eeveelutions),
Completely Random will randomize all Pokemons' palettes individually""" Completely Random will randomize all Pokemons' palettes individually"""
display_name = "Randomize Pokemon Palettes" display_name = "Randomize Pokemon Palettes"
option_vanilla = 0 option_vanilla = 0
@ -860,104 +878,105 @@ class RandomizePokemonPalettes(Choice):
option_completely_random = 3 option_completely_random = 3
pokemon_rb_options = { @dataclass
"accessibility": ItemsAccessibility, class PokemonRBOptions(PerGameCommonOptions):
"game_version": GameVersion, accessibility: ItemsAccessibility
"trainer_name": TrainerName, game_version: GameVersion
"rival_name": RivalName, trainer_name: TrainerName
#"goal": Goal, rival_name: RivalName
"elite_four_badges_condition": EliteFourBadgesCondition, # goal: Goal
"elite_four_key_items_condition": EliteFourKeyItemsCondition, elite_four_badges_condition: EliteFourBadgesCondition
"elite_four_pokedex_condition": EliteFourPokedexCondition, elite_four_key_items_condition: EliteFourKeyItemsCondition
"victory_road_condition": VictoryRoadCondition, elite_four_pokedex_condition: EliteFourPokedexCondition
"route_22_gate_condition": Route22GateCondition, victory_road_condition: VictoryRoadCondition
"viridian_gym_condition": ViridianGymCondition, route_22_gate_condition: Route22GateCondition
"cerulean_cave_badges_condition": CeruleanCaveBadgesCondition, viridian_gym_condition: ViridianGymCondition
"cerulean_cave_key_items_condition": CeruleanCaveKeyItemsCondition, cerulean_cave_badges_condition: CeruleanCaveBadgesCondition
"route_3_condition": Route3Condition, cerulean_cave_key_items_condition: CeruleanCaveKeyItemsCondition
"robbed_house_officer": RobbedHouseOfficer, route_3_condition: Route3Condition
"second_fossil_check_condition": SecondFossilCheckCondition, robbed_house_officer: RobbedHouseOfficer
"fossil_check_item_types": FossilCheckItemTypes, second_fossil_check_condition: SecondFossilCheckCondition
"exp_all": ExpAll, fossil_check_item_types: FossilCheckItemTypes
"old_man": OldMan, exp_all: ExpAll
"badgesanity": BadgeSanity, old_man: OldMan
"badges_needed_for_hm_moves": BadgesNeededForHMMoves, badgesanity: BadgeSanity
"key_items_only": KeyItemsOnly, badges_needed_for_hm_moves: BadgesNeededForHMMoves
"tea": Tea, key_items_only: KeyItemsOnly
"extra_key_items": ExtraKeyItems, tea: Tea
"split_card_key": SplitCardKey, extra_key_items: ExtraKeyItems
"all_elevators_locked": AllElevatorsLocked, split_card_key: SplitCardKey
"extra_strength_boulders": ExtraStrengthBoulders, all_elevators_locked: AllElevatorsLocked
"require_item_finder": RequireItemFinder, extra_strength_boulders: ExtraStrengthBoulders
"randomize_hidden_items": RandomizeHiddenItems, require_item_finder: RequireItemFinder
"prizesanity": PrizeSanity, randomize_hidden_items: RandomizeHiddenItems
"trainersanity": TrainerSanity, prizesanity: PrizeSanity
"dexsanity": DexSanity, trainersanity: TrainerSanity
"randomize_pokedex": RandomizePokedex, dexsanity: DexSanity
"require_pokedex": RequirePokedex, randomize_pokedex: RandomizePokedex
"all_pokemon_seen": AllPokemonSeen, require_pokedex: RequirePokedex
"oaks_aide_rt_2": OaksAidRt2, all_pokemon_seen: AllPokemonSeen
"oaks_aide_rt_11": OaksAidRt11, oaks_aide_rt_2: OaksAidRt2
"oaks_aide_rt_15": OaksAidRt15, oaks_aide_rt_11: OaksAidRt11
"stonesanity": Stonesanity, oaks_aide_rt_15: OaksAidRt15
"door_shuffle": DoorShuffle, stonesanity: Stonesanity
"warp_tile_shuffle": WarpTileShuffle, door_shuffle: DoorShuffle
"randomize_rock_tunnel": RandomizeRockTunnel, warp_tile_shuffle: WarpTileShuffle
"dark_rock_tunnel_logic": DarkRockTunnelLogic, randomize_rock_tunnel: RandomizeRockTunnel
"free_fly_location": FreeFlyLocation, dark_rock_tunnel_logic: DarkRockTunnelLogic
"town_map_fly_location": TownMapFlyLocation, free_fly_location: FreeFlyLocation
"blind_trainers": BlindTrainers, town_map_fly_location: TownMapFlyLocation
"minimum_steps_between_encounters": MinimumStepsBetweenEncounters, blind_trainers: BlindTrainers
"level_scaling": LevelScaling, minimum_steps_between_encounters: MinimumStepsBetweenEncounters
"exp_modifier": ExpModifier, level_scaling: LevelScaling
"randomize_wild_pokemon": RandomizeWildPokemon, exp_modifier: ExpModifier
"area_1_to_1_mapping": Area1To1Mapping, randomize_wild_pokemon: RandomizeWildPokemon
"randomize_starter_pokemon": RandomizeStarterPokemon, area_1_to_1_mapping: Area1To1Mapping
"randomize_static_pokemon": RandomizeStaticPokemon, randomize_starter_pokemon: RandomizeStarterPokemon
"randomize_legendary_pokemon": RandomizeLegendaryPokemon, randomize_static_pokemon: RandomizeStaticPokemon
"catch_em_all": CatchEmAll, randomize_legendary_pokemon: RandomizeLegendaryPokemon
"randomize_pokemon_stats": RandomizePokemonStats, catch_em_all: CatchEmAll
"randomize_pokemon_catch_rates": RandomizePokemonCatchRates, randomize_pokemon_stats: RandomizePokemonStats
"minimum_catch_rate": MinimumCatchRate, randomize_pokemon_catch_rates: RandomizePokemonCatchRates
"randomize_trainer_parties": RandomizeTrainerParties, minimum_catch_rate: MinimumCatchRate
"trainer_legendaries": TrainerLegendaries, randomize_trainer_parties: RandomizeTrainerParties
"move_balancing": MoveBalancing, trainer_legendaries: TrainerLegendaries
"fix_combat_bugs": FixCombatBugs, move_balancing: MoveBalancing
"randomize_pokemon_movesets": RandomizePokemonMovesets, fix_combat_bugs: FixCombatBugs
"confine_transform_to_ditto": ConfineTranstormToDitto, randomize_pokemon_movesets: RandomizePokemonMovesets
"start_with_four_moves": StartWithFourMoves, confine_transform_to_ditto: ConfineTranstormToDitto
"same_type_attack_bonus": SameTypeAttackBonus, start_with_four_moves: StartWithFourMoves
"randomize_tm_moves": RandomizeTMMoves, same_type_attack_bonus: SameTypeAttackBonus
"tm_same_type_compatibility": TMSameTypeCompatibility, randomize_tm_moves: RandomizeTMMoves
"tm_normal_type_compatibility": TMNormalTypeCompatibility, tm_same_type_compatibility: TMSameTypeCompatibility
"tm_other_type_compatibility": TMOtherTypeCompatibility, tm_normal_type_compatibility: TMNormalTypeCompatibility
"hm_same_type_compatibility": HMSameTypeCompatibility, tm_other_type_compatibility: TMOtherTypeCompatibility
"hm_normal_type_compatibility": HMNormalTypeCompatibility, hm_same_type_compatibility: HMSameTypeCompatibility
"hm_other_type_compatibility": HMOtherTypeCompatibility, hm_normal_type_compatibility: HMNormalTypeCompatibility
"inherit_tm_hm_compatibility": InheritTMHMCompatibility, hm_other_type_compatibility: HMOtherTypeCompatibility
"randomize_move_types": RandomizeMoveTypes, inherit_tm_hm_compatibility: InheritTMHMCompatibility
"randomize_pokemon_types": RandomizePokemonTypes, randomize_move_types: RandomizeMoveTypes
"secondary_type_chance": SecondaryTypeChance, randomize_pokemon_types: RandomizePokemonTypes
"randomize_type_chart": RandomizeTypeChart, secondary_type_chance: SecondaryTypeChance
"normal_matchups": NormalMatchups, randomize_type_chart: RandomizeTypeChart
"super_effective_matchups": SuperEffectiveMatchups, normal_matchups: NormalMatchups
"not_very_effective_matchups": NotVeryEffectiveMatchups, super_effective_matchups: SuperEffectiveMatchups
"immunity_matchups": ImmunityMatchups, not_very_effective_matchups: NotVeryEffectiveMatchups
"safari_zone_normal_battles": SafariZoneNormalBattles, immunity_matchups: ImmunityMatchups
"normalize_encounter_chances": NormalizeEncounterChances, type_chart_seed: TypeChartSeed
"reusable_tms": ReusableTMs, safari_zone_normal_battles: SafariZoneNormalBattles
"better_shops": BetterShops, normalize_encounter_chances: NormalizeEncounterChances
"master_ball_price": MasterBallPrice, reusable_tms: ReusableTMs
"starting_money": StartingMoney, better_shops: BetterShops
"lose_money_on_blackout": LoseMoneyOnBlackout, master_ball_price: MasterBallPrice
"poke_doll_skip": PokeDollSkip, starting_money: StartingMoney
"bicycle_gate_skips": BicycleGateSkips, lose_money_on_blackout: LoseMoneyOnBlackout
"trap_percentage": TrapPercentage, poke_doll_skip: PokeDollSkip
"poison_trap_weight": PoisonTrapWeight, bicycle_gate_skips: BicycleGateSkips
"fire_trap_weight": FireTrapWeight, trap_percentage: TrapPercentage
"paralyze_trap_weight": ParalyzeTrapWeight, poison_trap_weight: PoisonTrapWeight
"sleep_trap_weight": SleepTrapWeight, fire_trap_weight: FireTrapWeight
"ice_trap_weight": IceTrapWeight, paralyze_trap_weight: ParalyzeTrapWeight
"randomize_pokemon_palettes": RandomizePokemonPalettes, sleep_trap_weight: SleepTrapWeight
"death_link": DeathLink ice_trap_weight: IceTrapWeight
} randomize_pokemon_palettes: RandomizePokemonPalettes
death_link: DeathLink

View File

@ -3,8 +3,8 @@ from . import poke_data, logic
from .rom_addresses import rom_addresses from .rom_addresses import rom_addresses
def set_mon_palettes(self, random, data): def set_mon_palettes(world, random, data):
if self.multiworld.randomize_pokemon_palettes[self.player] == "vanilla": if world.options.randomize_pokemon_palettes == "vanilla":
return return
pallet_map = { pallet_map = {
"Poison": 0x0F, "Poison": 0x0F,
@ -25,9 +25,9 @@ def set_mon_palettes(self, random, data):
} }
palettes = [] palettes = []
for mon in poke_data.pokemon_data: for mon in poke_data.pokemon_data:
if self.multiworld.randomize_pokemon_palettes[self.player] == "primary_type": if world.options.randomize_pokemon_palettes == "primary_type":
pallet = pallet_map[self.local_poke_data[mon]["type1"]] pallet = pallet_map[world.local_poke_data[mon]["type1"]]
elif (self.multiworld.randomize_pokemon_palettes[self.player] == "follow_evolutions" and mon in elif (world.options.randomize_pokemon_palettes == "follow_evolutions" and mon in
poke_data.evolves_from and poke_data.evolves_from[mon] != "Eevee"): poke_data.evolves_from and poke_data.evolves_from[mon] != "Eevee"):
pallet = palettes[-1] pallet = palettes[-1]
else: # completely_random or follow_evolutions and it is not an evolved form (except eeveelutions) else: # completely_random or follow_evolutions and it is not an evolved form (except eeveelutions)
@ -93,40 +93,41 @@ def move_power(move_data):
return power return power
def process_move_data(self): def process_move_data(world):
self.local_move_data = deepcopy(poke_data.moves) world.local_move_data = deepcopy(poke_data.moves)
if self.multiworld.randomize_move_types[self.player]: if world.options.randomize_move_types:
for move, data in self.local_move_data.items(): for move, data in world.local_move_data.items():
if move == "No Move": if move == "No Move":
continue continue
# The chance of randomized moves choosing a normal type move is high, so we want to retain having a higher # The chance of randomized moves choosing a normal type move is high, so we want to retain having a higher
# rate of normal type moves # rate of normal type moves
data["type"] = self.multiworld.random.choice(list(poke_data.type_ids) + (["Normal"] * 4)) data["type"] = world.random.choice(list(poke_data.type_ids) + (["Normal"] * 4))
if self.multiworld.move_balancing[self.player]: if world.options.move_balancing:
self.local_move_data["Sing"]["accuracy"] = 30 world.local_move_data["Sing"]["accuracy"] = 30
self.local_move_data["Sleep Powder"]["accuracy"] = 40 world.local_move_data["Sleep Powder"]["accuracy"] = 40
self.local_move_data["Spore"]["accuracy"] = 50 world.local_move_data["Spore"]["accuracy"] = 50
self.local_move_data["Sonicboom"]["effect"] = 0 world.local_move_data["Sonicboom"]["effect"] = 0
self.local_move_data["Sonicboom"]["power"] = 50 world.local_move_data["Sonicboom"]["power"] = 50
self.local_move_data["Dragon Rage"]["effect"] = 0 world.local_move_data["Dragon Rage"]["effect"] = 0
self.local_move_data["Dragon Rage"]["power"] = 80 world.local_move_data["Dragon Rage"]["power"] = 80
self.local_move_data["Horn Drill"]["effect"] = 0 world.local_move_data["Horn Drill"]["effect"] = 0
self.local_move_data["Horn Drill"]["power"] = 70 world.local_move_data["Horn Drill"]["power"] = 70
self.local_move_data["Horn Drill"]["accuracy"] = 90 world.local_move_data["Horn Drill"]["accuracy"] = 90
self.local_move_data["Guillotine"]["effect"] = 0 world.local_move_data["Guillotine"]["effect"] = 0
self.local_move_data["Guillotine"]["power"] = 70 world.local_move_data["Guillotine"]["power"] = 70
self.local_move_data["Guillotine"]["accuracy"] = 90 world.local_move_data["Guillotine"]["accuracy"] = 90
self.local_move_data["Fissure"]["effect"] = 0 world.local_move_data["Fissure"]["effect"] = 0
self.local_move_data["Fissure"]["power"] = 70 world.local_move_data["Fissure"]["power"] = 70
self.local_move_data["Fissure"]["accuracy"] = 90 world.local_move_data["Fissure"]["accuracy"] = 90
self.local_move_data["Blizzard"]["accuracy"] = 70 world.local_move_data["Blizzard"]["accuracy"] = 70
if self.multiworld.randomize_tm_moves[self.player]:
self.local_tms = self.multiworld.random.sample([move for move in poke_data.moves.keys() if move not in if world.options.randomize_tm_moves:
["No Move"] + poke_data.hm_moves], 50) world.local_tms = world.random.sample([move for move in poke_data.moves.keys() if move not in
["No Move"] + poke_data.hm_moves], 50)
else: else:
self.local_tms = poke_data.tm_moves.copy() world.local_tms = poke_data.tm_moves.copy()
def process_pokemon_data(self): def process_pokemon_data(self):
@ -138,12 +139,12 @@ def process_pokemon_data(self):
compat_hms = set() compat_hms = set()
for mon, mon_data in local_poke_data.items(): for mon, mon_data in local_poke_data.items():
if self.multiworld.randomize_pokemon_stats[self.player] == "shuffle": if self.options.randomize_pokemon_stats == "shuffle":
stats = [mon_data["hp"], mon_data["atk"], mon_data["def"], mon_data["spd"], mon_data["spc"]] stats = [mon_data["hp"], mon_data["atk"], mon_data["def"], mon_data["spd"], mon_data["spc"]]
if mon in poke_data.evolves_from: if mon in poke_data.evolves_from:
stat_shuffle_map = local_poke_data[poke_data.evolves_from[mon]]["stat_shuffle_map"] stat_shuffle_map = local_poke_data[poke_data.evolves_from[mon]]["stat_shuffle_map"]
else: else:
stat_shuffle_map = self.multiworld.random.sample(range(0, 5), 5) stat_shuffle_map = self.random.sample(range(0, 5), 5)
mon_data["stat_shuffle_map"] = stat_shuffle_map mon_data["stat_shuffle_map"] = stat_shuffle_map
mon_data["hp"] = stats[stat_shuffle_map[0]] mon_data["hp"] = stats[stat_shuffle_map[0]]
@ -151,7 +152,7 @@ def process_pokemon_data(self):
mon_data["def"] = stats[stat_shuffle_map[2]] mon_data["def"] = stats[stat_shuffle_map[2]]
mon_data["spd"] = stats[stat_shuffle_map[3]] mon_data["spd"] = stats[stat_shuffle_map[3]]
mon_data["spc"] = stats[stat_shuffle_map[4]] mon_data["spc"] = stats[stat_shuffle_map[4]]
elif self.multiworld.randomize_pokemon_stats[self.player] == "randomize": elif self.options.randomize_pokemon_stats == "randomize":
first_run = True first_run = True
while (mon_data["hp"] > 255 or mon_data["atk"] > 255 or mon_data["def"] > 255 or mon_data["spd"] > 255 while (mon_data["hp"] > 255 or mon_data["atk"] > 255 or mon_data["def"] > 255 or mon_data["spd"] > 255
or mon_data["spc"] > 255 or first_run): or mon_data["spc"] > 255 or first_run):
@ -168,9 +169,9 @@ def process_pokemon_data(self):
mon_data[stat] = 10 mon_data[stat] = 10
total_stats -= 10 total_stats -= 10
assert total_stats >= 0, f"Error distributing stats for {mon} for player {self.player}" assert total_stats >= 0, f"Error distributing stats for {mon} for player {self.player}"
dist = [self.multiworld.random.randint(1, 101) / 100, self.multiworld.random.randint(1, 101) / 100, dist = [self.random.randint(1, 101) / 100, self.random.randint(1, 101) / 100,
self.multiworld.random.randint(1, 101) / 100, self.multiworld.random.randint(1, 101) / 100, self.random.randint(1, 101) / 100, self.random.randint(1, 101) / 100,
self.multiworld.random.randint(1, 101) / 100] self.random.randint(1, 101) / 100]
total_dist = sum(dist) total_dist = sum(dist)
mon_data["hp"] += int(round(dist[0] / total_dist * total_stats)) mon_data["hp"] += int(round(dist[0] / total_dist * total_stats))
@ -178,30 +179,30 @@ def process_pokemon_data(self):
mon_data["def"] += int(round(dist[2] / total_dist * total_stats)) mon_data["def"] += int(round(dist[2] / total_dist * total_stats))
mon_data["spd"] += int(round(dist[3] / total_dist * total_stats)) mon_data["spd"] += int(round(dist[3] / total_dist * total_stats))
mon_data["spc"] += int(round(dist[4] / total_dist * total_stats)) mon_data["spc"] += int(round(dist[4] / total_dist * total_stats))
if self.multiworld.randomize_pokemon_types[self.player]: if self.options.randomize_pokemon_types:
if self.multiworld.randomize_pokemon_types[self.player].value == 1 and mon in poke_data.evolves_from: if self.options.randomize_pokemon_types.value == 1 and mon in poke_data.evolves_from:
type1 = local_poke_data[poke_data.evolves_from[mon]]["type1"] type1 = local_poke_data[poke_data.evolves_from[mon]]["type1"]
type2 = local_poke_data[poke_data.evolves_from[mon]]["type2"] type2 = local_poke_data[poke_data.evolves_from[mon]]["type2"]
if type1 == type2: if type1 == type2:
if self.multiworld.secondary_type_chance[self.player].value == -1: if self.options.secondary_type_chance.value == -1:
if mon_data["type1"] != mon_data["type2"]: if mon_data["type1"] != mon_data["type2"]:
while type2 == type1: while type2 == type1:
type2 = self.multiworld.random.choice(list(poke_data.type_names.values())) type2 = self.random.choice(list(poke_data.type_names.values()))
elif self.multiworld.random.randint(1, 100) <= self.multiworld.secondary_type_chance[self.player].value: elif self.random.randint(1, 100) <= self.options.secondary_type_chance.value:
type2 = self.multiworld.random.choice(list(poke_data.type_names.values())) type2 = self.random.choice(list(poke_data.type_names.values()))
else: else:
type1 = self.multiworld.random.choice(list(poke_data.type_names.values())) type1 = self.random.choice(list(poke_data.type_names.values()))
type2 = type1 type2 = type1
if ((self.multiworld.secondary_type_chance[self.player].value == -1 and mon_data["type1"] if ((self.options.secondary_type_chance.value == -1 and mon_data["type1"]
!= mon_data["type2"]) or self.multiworld.random.randint(1, 100) != mon_data["type2"]) or self.random.randint(1, 100)
<= self.multiworld.secondary_type_chance[self.player].value): <= self.options.secondary_type_chance.value):
while type2 == type1: while type2 == type1:
type2 = self.multiworld.random.choice(list(poke_data.type_names.values())) type2 = self.random.choice(list(poke_data.type_names.values()))
mon_data["type1"] = type1 mon_data["type1"] = type1
mon_data["type2"] = type2 mon_data["type2"] = type2
if self.multiworld.randomize_pokemon_movesets[self.player]: if self.options.randomize_pokemon_movesets:
if self.multiworld.randomize_pokemon_movesets[self.player] == "prefer_types": if self.options.randomize_pokemon_movesets == "prefer_types":
if mon_data["type1"] == "Normal" and mon_data["type2"] == "Normal": if mon_data["type1"] == "Normal" and mon_data["type2"] == "Normal":
chances = [[75, "Normal"]] chances = [[75, "Normal"]]
elif mon_data["type1"] == "Normal" or mon_data["type2"] == "Normal": elif mon_data["type1"] == "Normal" or mon_data["type2"] == "Normal":
@ -219,9 +220,9 @@ def process_pokemon_data(self):
moves = list(poke_data.moves.keys()) moves = list(poke_data.moves.keys())
for move in ["No Move"] + poke_data.hm_moves: for move in ["No Move"] + poke_data.hm_moves:
moves.remove(move) moves.remove(move)
if self.multiworld.confine_transform_to_ditto[self.player]: if self.options.confine_transform_to_ditto:
moves.remove("Transform") moves.remove("Transform")
if self.multiworld.start_with_four_moves[self.player]: if self.options.start_with_four_moves:
num_moves = 4 num_moves = 4
else: else:
num_moves = len([i for i in [mon_data["start move 1"], mon_data["start move 2"], num_moves = len([i for i in [mon_data["start move 1"], mon_data["start move 2"],
@ -231,12 +232,12 @@ def process_pokemon_data(self):
non_power_moves = [] non_power_moves = []
learnsets[mon] = [] learnsets[mon] = []
for i in range(num_moves): for i in range(num_moves):
if i == 0 and mon == "Ditto" and self.multiworld.confine_transform_to_ditto[self.player]: if i == 0 and mon == "Ditto" and self.options.confine_transform_to_ditto:
move = "Transform" move = "Transform"
else: else:
move = get_move(self.local_move_data, moves, chances, self.multiworld.random) move = get_move(self.local_move_data, moves, chances, self.random)
while move == "Transform" and self.multiworld.confine_transform_to_ditto[self.player]: while move == "Transform" and self.options.confine_transform_to_ditto:
move = get_move(self.local_move_data, moves, chances, self.multiworld.random) move = get_move(self.local_move_data, moves, chances, self.random)
if self.local_move_data[move]["power"] < 5: if self.local_move_data[move]["power"] < 5:
non_power_moves.append(move) non_power_moves.append(move)
else: else:
@ -244,59 +245,58 @@ def process_pokemon_data(self):
learnsets[mon].sort(key=lambda move: move_power(self.local_move_data[move])) learnsets[mon].sort(key=lambda move: move_power(self.local_move_data[move]))
if learnsets[mon]: if learnsets[mon]:
for move in non_power_moves: for move in non_power_moves:
learnsets[mon].insert(self.multiworld.random.randint(1, len(learnsets[mon])), move) learnsets[mon].insert(self.random.randint(1, len(learnsets[mon])), move)
else: else:
learnsets[mon] = non_power_moves learnsets[mon] = non_power_moves
for i in range(1, 5): for i in range(1, 5):
if mon_data[f"start move {i}"] != "No Move" or self.multiworld.start_with_four_moves[self.player]: if mon_data[f"start move {i}"] != "No Move" or self.options.start_with_four_moves:
mon_data[f"start move {i}"] = learnsets[mon].pop(0) mon_data[f"start move {i}"] = learnsets[mon].pop(0)
if self.multiworld.randomize_pokemon_catch_rates[self.player]: if self.options.randomize_pokemon_catch_rates:
mon_data["catch rate"] = self.multiworld.random.randint(self.multiworld.minimum_catch_rate[self.player], mon_data["catch rate"] = self.random.randint(self.options.minimum_catch_rate, 255)
255)
else: else:
mon_data["catch rate"] = max(self.multiworld.minimum_catch_rate[self.player], mon_data["catch rate"]) mon_data["catch rate"] = max(self.options.minimum_catch_rate, mon_data["catch rate"])
def roll_tm_compat(roll_move): def roll_tm_compat(roll_move):
if self.local_move_data[roll_move]["type"] in [mon_data["type1"], mon_data["type2"]]: if self.local_move_data[roll_move]["type"] in [mon_data["type1"], mon_data["type2"]]:
if roll_move in poke_data.hm_moves: if roll_move in poke_data.hm_moves:
if self.multiworld.hm_same_type_compatibility[self.player].value == -1: if self.options.hm_same_type_compatibility.value == -1:
return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8) return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8)
r = self.multiworld.random.randint(1, 100) <= self.multiworld.hm_same_type_compatibility[self.player].value r = self.random.randint(1, 100) <= self.options.hm_same_type_compatibility.value
if r and mon not in poke_data.legendary_pokemon: if r and mon not in poke_data.legendary_pokemon:
compat_hms.add(roll_move) compat_hms.add(roll_move)
return r return r
else: else:
if self.multiworld.tm_same_type_compatibility[self.player].value == -1: if self.options.tm_same_type_compatibility.value == -1:
return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8) return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8)
return self.multiworld.random.randint(1, 100) <= self.multiworld.tm_same_type_compatibility[self.player].value return self.random.randint(1, 100) <= self.options.tm_same_type_compatibility.value
elif self.local_move_data[roll_move]["type"] == "Normal" and "Normal" not in [mon_data["type1"], mon_data["type2"]]: elif self.local_move_data[roll_move]["type"] == "Normal" and "Normal" not in [mon_data["type1"], mon_data["type2"]]:
if roll_move in poke_data.hm_moves: if roll_move in poke_data.hm_moves:
if self.multiworld.hm_normal_type_compatibility[self.player].value == -1: if self.options.hm_normal_type_compatibility.value == -1:
return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8) return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8)
r = self.multiworld.random.randint(1, 100) <= self.multiworld.hm_normal_type_compatibility[self.player].value r = self.random.randint(1, 100) <= self.options.hm_normal_type_compatibility.value
if r and mon not in poke_data.legendary_pokemon: if r and mon not in poke_data.legendary_pokemon:
compat_hms.add(roll_move) compat_hms.add(roll_move)
return r return r
else: else:
if self.multiworld.tm_normal_type_compatibility[self.player].value == -1: if self.options.tm_normal_type_compatibility.value == -1:
return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8) return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8)
return self.multiworld.random.randint(1, 100) <= self.multiworld.tm_normal_type_compatibility[self.player].value return self.random.randint(1, 100) <= self.options.tm_normal_type_compatibility.value
else: else:
if roll_move in poke_data.hm_moves: if roll_move in poke_data.hm_moves:
if self.multiworld.hm_other_type_compatibility[self.player].value == -1: if self.options.hm_other_type_compatibility.value == -1:
return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8) return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8)
r = self.multiworld.random.randint(1, 100) <= self.multiworld.hm_other_type_compatibility[self.player].value r = self.random.randint(1, 100) <= self.options.hm_other_type_compatibility.value
if r and mon not in poke_data.legendary_pokemon: if r and mon not in poke_data.legendary_pokemon:
compat_hms.add(roll_move) compat_hms.add(roll_move)
return r return r
else: else:
if self.multiworld.tm_other_type_compatibility[self.player].value == -1: if self.options.tm_other_type_compatibility.value == -1:
return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8) return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8)
return self.multiworld.random.randint(1, 100) <= self.multiworld.tm_other_type_compatibility[self.player].value return self.random.randint(1, 100) <= self.options.tm_other_type_compatibility.value
for flag, tm_move in enumerate(tms_hms): for flag, tm_move in enumerate(tms_hms):
if mon in poke_data.evolves_from and self.multiworld.inherit_tm_hm_compatibility[self.player]: if mon in poke_data.evolves_from and self.options.inherit_tm_hm_compatibility:
if local_poke_data[poke_data.evolves_from[mon]]["tms"][int(flag / 8)] & 1 << (flag % 8): if local_poke_data[poke_data.evolves_from[mon]]["tms"][int(flag / 8)] & 1 << (flag % 8):
# always inherit learnable tms/hms # always inherit learnable tms/hms
@ -310,7 +310,7 @@ def process_pokemon_data(self):
# so this gets full chance roll # so this gets full chance roll
bit = roll_tm_compat(tm_move) bit = roll_tm_compat(tm_move)
# otherwise 50% reduced chance to add compatibility over pre-evolved form # otherwise 50% reduced chance to add compatibility over pre-evolved form
elif self.multiworld.random.randint(1, 100) > 50 and roll_tm_compat(tm_move): elif self.random.randint(1, 100) > 50 and roll_tm_compat(tm_move):
bit = 1 bit = 1
else: else:
bit = 0 bit = 0
@ -322,15 +322,13 @@ def process_pokemon_data(self):
mon_data["tms"][int(flag / 8)] &= ~(1 << (flag % 8)) mon_data["tms"][int(flag / 8)] &= ~(1 << (flag % 8))
hm_verify = ["Surf", "Strength"] hm_verify = ["Surf", "Strength"]
if self.multiworld.accessibility[self.player] != "minimal" or ((not if self.options.accessibility != "minimal" or ((not
self.multiworld.badgesanity[self.player]) and max(self.multiworld.elite_four_badges_condition[self.player], self.options.badgesanity) and max(self.options.elite_four_badges_condition,
self.multiworld.route_22_gate_condition[self.player], self.multiworld.victory_road_condition[self.player]) self.options.route_22_gate_condition, self.options.victory_road_condition)
> 7) or (self.multiworld.door_shuffle[self.player] not in ("off", "simple")): > 7) or (self.options.door_shuffle not in ("off", "simple")):
hm_verify += ["Cut"] hm_verify += ["Cut"]
if self.multiworld.accessibility[self.player] != "minimal" or (not if (self.options.accessibility != "minimal" or (not self.options.dark_rock_tunnel_logic) and
self.multiworld.dark_rock_tunnel_logic[self.player]) and ((self.multiworld.trainersanity[self.player] or ((self.options.trainersanity or self.options.extra_key_items) or self.options.door_shuffle)):
self.multiworld.extra_key_items[self.player])
or self.multiworld.door_shuffle[self.player]):
hm_verify += ["Flash"] hm_verify += ["Flash"]
# Fly does not need to be verified. Full/Insanity/Decoupled door shuffle connects reachable regions to unreachable # Fly does not need to be verified. Full/Insanity/Decoupled door shuffle connects reachable regions to unreachable
# regions, so if Fly is available and can be learned, the towns you can fly to would be considered reachable for # regions, so if Fly is available and can be learned, the towns you can fly to would be considered reachable for
@ -339,8 +337,7 @@ def process_pokemon_data(self):
for hm_move in hm_verify: for hm_move in hm_verify:
if hm_move not in compat_hms: if hm_move not in compat_hms:
mon = self.multiworld.random.choice([mon for mon in poke_data.pokemon_data if mon not in mon = self.random.choice([mon for mon in poke_data.pokemon_data if mon not in poke_data.legendary_pokemon])
poke_data.legendary_pokemon])
flag = tms_hms.index(hm_move) flag = tms_hms.index(hm_move)
local_poke_data[mon]["tms"][int(flag / 8)] |= 1 << (flag % 8) local_poke_data[mon]["tms"][int(flag / 8)] |= 1 << (flag % 8)
@ -352,7 +349,7 @@ def verify_hm_moves(multiworld, world, player):
def intervene(move, test_state): def intervene(move, test_state):
move_bit = pow(2, poke_data.hm_moves.index(move) + 2) move_bit = pow(2, poke_data.hm_moves.index(move) + 2)
viable_mons = [mon for mon in world.local_poke_data if world.local_poke_data[mon]["tms"][6] & move_bit] viable_mons = [mon for mon in world.local_poke_data if world.local_poke_data[mon]["tms"][6] & move_bit]
if multiworld.randomize_wild_pokemon[player] and viable_mons: if world.options.randomize_wild_pokemon and viable_mons:
accessible_slots = [loc for loc in multiworld.get_reachable_locations(test_state, player) if accessible_slots = [loc for loc in multiworld.get_reachable_locations(test_state, player) if
loc.type == "Wild Encounter"] loc.type == "Wild Encounter"]
@ -364,7 +361,7 @@ def verify_hm_moves(multiworld, world, player):
placed_mons = [slot.item.name for slot in accessible_slots] placed_mons = [slot.item.name for slot in accessible_slots]
if multiworld.area_1_to_1_mapping[player]: if world.options.area_1_to_1_mapping:
placed_mons.sort(key=lambda i: number_of_zones(i)) placed_mons.sort(key=lambda i: number_of_zones(i))
else: else:
# this sort method doesn't work if you reference the same list being sorted in the lambda # this sort method doesn't work if you reference the same list being sorted in the lambda
@ -372,10 +369,10 @@ def verify_hm_moves(multiworld, world, player):
placed_mons.sort(key=lambda i: placed_mons_copy.count(i)) placed_mons.sort(key=lambda i: placed_mons_copy.count(i))
placed_mon = placed_mons.pop() placed_mon = placed_mons.pop()
replace_mon = multiworld.random.choice(viable_mons) replace_mon = world.random.choice(viable_mons)
replace_slot = multiworld.random.choice([slot for slot in accessible_slots if slot.item.name replace_slot = world.random.choice([slot for slot in accessible_slots if slot.item.name
== placed_mon]) == placed_mon])
if multiworld.area_1_to_1_mapping[player]: if world.options.area_1_to_1_mapping:
zone = " - ".join(replace_slot.name.split(" - ")[:-1]) zone = " - ".join(replace_slot.name.split(" - ")[:-1])
replace_slots = [slot for slot in accessible_slots if slot.name.startswith(zone) and slot.item.name replace_slots = [slot for slot in accessible_slots if slot.name.startswith(zone) and slot.item.name
== placed_mon] == placed_mon]
@ -387,7 +384,7 @@ def verify_hm_moves(multiworld, world, player):
tms_hms = world.local_tms + poke_data.hm_moves tms_hms = world.local_tms + poke_data.hm_moves
flag = tms_hms.index(move) flag = tms_hms.index(move)
mon_list = [mon for mon in poke_data.pokemon_data.keys() if test_state.has(mon, player)] mon_list = [mon for mon in poke_data.pokemon_data.keys() if test_state.has(mon, player)]
multiworld.random.shuffle(mon_list) world.random.shuffle(mon_list)
mon_list.sort(key=lambda mon: world.local_move_data[move]["type"] not in mon_list.sort(key=lambda mon: world.local_move_data[move]["type"] not in
[world.local_poke_data[mon]["type1"], world.local_poke_data[mon]["type2"]]) [world.local_poke_data[mon]["type1"], world.local_poke_data[mon]["type2"]])
for mon in mon_list: for mon in mon_list:
@ -399,31 +396,31 @@ def verify_hm_moves(multiworld, world, player):
while True: while True:
intervene_move = None intervene_move = None
test_state = multiworld.get_all_state(False) test_state = multiworld.get_all_state(False)
if not logic.can_learn_hm(test_state, "Surf", player): if not logic.can_learn_hm(test_state, world, "Surf", player):
intervene_move = "Surf" intervene_move = "Surf"
elif not logic.can_learn_hm(test_state, "Strength", player): elif not logic.can_learn_hm(test_state, world, "Strength", player):
intervene_move = "Strength" intervene_move = "Strength"
# cut may not be needed if accessibility is minimal, unless you need all 8 badges and badgesanity is off, # cut may not be needed if accessibility is minimal, unless you need all 8 badges and badgesanity is off,
# as you will require cut to access celadon gyn # as you will require cut to access celadon gyn
elif ((not logic.can_learn_hm(test_state, "Cut", player)) and elif ((not logic.can_learn_hm(test_state, world, "Cut", player)) and
(multiworld.accessibility[player] != "minimal" or ((not (world.options.accessibility != "minimal" or ((not
multiworld.badgesanity[player]) and max( world.options.badgesanity) and max(
multiworld.elite_four_badges_condition[player], world.options.elite_four_badges_condition,
multiworld.route_22_gate_condition[player], world.options.route_22_gate_condition,
multiworld.victory_road_condition[player]) world.options.victory_road_condition)
> 7) or (multiworld.door_shuffle[player] not in ("off", "simple")))): > 7) or (world.options.door_shuffle not in ("off", "simple")))):
intervene_move = "Cut" intervene_move = "Cut"
elif ((not logic.can_learn_hm(test_state, "Flash", player)) elif ((not logic.can_learn_hm(test_state, world, "Flash", player))
and multiworld.dark_rock_tunnel_logic[player] and world.options.dark_rock_tunnel_logic
and (multiworld.accessibility[player] != "minimal" and (world.options.accessibility != "minimal"
or multiworld.door_shuffle[player])): or world.options.door_shuffle)):
intervene_move = "Flash" intervene_move = "Flash"
# If no Pokémon can learn Fly, then during door shuffle it would simply not treat the free fly maps # If no Pokémon can learn Fly, then during door shuffle it would simply not treat the free fly maps
# as reachable, and if on no door shuffle or simple, fly is simply never necessary. # as reachable, and if on no door shuffle or simple, fly is simply never necessary.
# We only intervene if a Pokémon is able to learn fly but none are reachable, as that would have been # We only intervene if a Pokémon is able to learn fly but none are reachable, as that would have been
# considered in door shuffle. # considered in door shuffle.
elif ((not logic.can_learn_hm(test_state, "Fly", player)) elif ((not logic.can_learn_hm(test_state, world, "Fly", player))
and multiworld.door_shuffle[player] not in and world.options.door_shuffle not in
("off", "simple") and [world.fly_map, world.town_map_fly_map] != ["Pallet Town", "Pallet Town"]): ("off", "simple") and [world.fly_map, world.town_map_fly_map] != ["Pallet Town", "Pallet Town"]):
intervene_move = "Fly" intervene_move = "Fly"
if intervene_move: if intervene_move:
@ -432,4 +429,4 @@ def verify_hm_moves(multiworld, world, player):
intervene(intervene_move, test_state) intervene(intervene_move, test_state)
last_intervene = intervene_move last_intervene = intervene_move
else: else:
break break

File diff suppressed because it is too large Load Diff

View File

@ -13,22 +13,22 @@ from .regions import PokemonRBWarp, map_ids, town_map_coords
from . import poke_data from . import poke_data
def write_quizzes(self, data, random): def write_quizzes(world, data, random):
def get_quiz(q, a): def get_quiz(q, a):
if q == 0: if q == 0:
r = random.randint(0, 3) r = random.randint(0, 3)
if r == 0: if r == 0:
mon = self.trade_mons["Trade_Dux"] mon = world.trade_mons["Trade_Dux"]
text = "A woman in<LINE>Vermilion City<CONT>" text = "A woman in<LINE>Vermilion City<CONT>"
elif r == 1: elif r == 1:
mon = self.trade_mons["Trade_Lola"] mon = world.trade_mons["Trade_Lola"]
text = "A man in<LINE>Cerulean City<CONT>" text = "A man in<LINE>Cerulean City<CONT>"
elif r == 2: elif r == 2:
mon = self.trade_mons["Trade_Marcel"] mon = world.trade_mons["Trade_Marcel"]
text = "Someone on Route 2<LINE>" text = "Someone on Route 2<LINE>"
elif r == 3: elif r == 3:
mon = self.trade_mons["Trade_Spot"] mon = world.trade_mons["Trade_Spot"]
text = "Someone on Route 5<LINE>" text = "Someone on Route 5<LINE>"
if not a: if not a:
answers.append(0) answers.append(0)
@ -38,21 +38,30 @@ def write_quizzes(self, data, random):
return encode_text(f"{text}was looking for<CONT>{mon}?<DONE>") return encode_text(f"{text}was looking for<CONT>{mon}?<DONE>")
elif q == 1: elif q == 1:
for location in self.multiworld.get_filled_locations(): for location in world.multiworld.get_filled_locations():
if location.item.name == "Secret Key" and location.item.player == self.player: if location.item.name == "Secret Key" and location.item.player == world.player:
break break
player_name = self.multiworld.player_name[location.player] player_name = world.multiworld.player_name[location.player]
if not a: if not a:
if len(self.multiworld.player_name) > 1: if len(world.multiworld.player_name) > 1:
old_name = player_name old_name = player_name
while old_name == player_name: while old_name == player_name:
player_name = random.choice(list(self.multiworld.player_name.values())) player_name = random.choice(list(world.multiworld.player_name.values()))
else: else:
return encode_text("You're playing<LINE>in a multiworld<CONT>with other<CONT>players?<DONE>") return encode_text("You're playing<LINE>in a multiworld<CONT>with other<CONT>players?<DONE>")
if player_name == self.multiworld.player_name[self.player]: if world.multiworld.get_entrance(
player_name = "yourself" "Cinnabar Island-G to Cinnabar Gym", world.player).connected_region.name == "Cinnabar Gym":
player_name = encode_text(player_name, force=True, safety=True) if player_name == world.multiworld.player_name[world.player]:
return encode_text(f"The Secret Key was<LINE>found by<CONT>") + player_name + encode_text("<DONE>") player_name = "yourself"
player_name = encode_text(player_name, force=True, safety=True)
return encode_text(f"The Secret Key was<LINE>found by<CONT>") + player_name + encode_text("?<DONE>")
else:
# Might not have found it yet
if player_name == world.multiworld.player_name[world.player]:
return encode_text(f"The Secret Key was<LINE>placed in<CONT>your own world?<DONE>")
player_name = encode_text(player_name, force=True, safety=True)
return (encode_text(f"The Secret Key was<LINE>placed in<CONT>") + player_name
+ encode_text("'s<CONT>world?<DONE>"))
elif q == 2: elif q == 2:
if a: if a:
return encode_text(f"#mon is<LINE>pronounced<CONT>Po-kay-mon?<DONE>") return encode_text(f"#mon is<LINE>pronounced<CONT>Po-kay-mon?<DONE>")
@ -62,8 +71,8 @@ def write_quizzes(self, data, random):
else: else:
return encode_text(f"#mon is<LINE>pronounced<CONT>Po-kuh-mon?<DONE>") return encode_text(f"#mon is<LINE>pronounced<CONT>Po-kuh-mon?<DONE>")
elif q == 3: elif q == 3:
starters = [" ".join(self.multiworld.get_location( starters = [" ".join(world.multiworld.get_location(
f"Oak's Lab - Starter {i}", self.player).item.name.split(" ")[1:]) for i in range(1, 4)] f"Oak's Lab - Starter {i}", world.player).item.name.split(" ")[1:]) for i in range(1, 4)]
mon = random.choice(starters) mon = random.choice(starters)
nots = random.choice(range(8, 16, 2)) nots = random.choice(range(8, 16, 2))
if random.randint(0, 1): if random.randint(0, 1):
@ -82,10 +91,10 @@ def write_quizzes(self, data, random):
return encode_text(text) return encode_text(text)
elif q == 4: elif q == 4:
if a: if a:
tm_text = self.local_tms[27] tm_text = world.local_tms[27]
else: else:
if self.multiworld.randomize_tm_moves[self.player]: if world.options.randomize_tm_moves:
wrong_tms = self.local_tms.copy() wrong_tms = world.local_tms.copy()
wrong_tms.pop(27) wrong_tms.pop(27)
tm_text = random.choice(wrong_tms) tm_text = random.choice(wrong_tms)
else: else:
@ -102,12 +111,36 @@ def write_quizzes(self, data, random):
i = random.randint(0, random.choice([9, 99])) i = random.randint(0, random.choice([9, 99]))
return encode_text(f"POLIWAG evolves {i}<LINE>times?<DONE>") return encode_text(f"POLIWAG evolves {i}<LINE>times?<DONE>")
elif q == 7: elif q == 7:
entity = "Motor Carrier" q2 = random.randint(0, 2)
if not a: if q2 == 0:
entity = random.choice(["Driver", "Shipper"]) entity = "Motor Carrier"
return encode_text("Title 49 of the<LINE>U.S. Code of<CONT>Federal<CONT>Regulations part<CONT>397.67 states" if not a:
f"<CONT>that the<CONT>{entity}<CONT>is responsible<CONT>for planning<CONT>routes when" entity = random.choice(["Driver", "Shipper"])
"<CONT>hazardous<CONT>materials are<CONT>transported?<DONE>") return encode_text("Title 49 of the<LINE>U.S. Code of<CONT>Federal<CONT>Regulations part<CONT>397.67 "
f"states<CONT>that the<CONT>{entity}<CONT>is responsible<CONT>for planning<CONT>"
"routes when<CONT>hazardous<CONT>materials are<CONT>transported?<DONE>")
elif q2 == 1:
if a:
state = random.choice(
['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut',
'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas',
'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota',
'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Jersey', 'New Mexico',
'New York', 'North Carolina', 'North Dakota', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania',
'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont',
'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming'])
else:
state = "New Hampshire"
return encode_text(
f"As of 2024,<LINE>{state}<CONT>has a law<CONT>requiring all<CONT>front seat vehicle<CONT>occupants to use<CONT>seatbelts?<DONE>")
elif q2 == 2:
if a:
country = random.choice(["The United States", "Mexico", "Canada", "Germany", "France", "China",
"Russia", "Spain", "Brazil", "Ukraine", "Saudi Arabia", "Egypt"])
else:
country = random.choice(["The U.K.", "Pakistan", "India", "Japan", "Australia",
"New Zealand", "Thailand"])
return encode_text(f"As of 2020,<LINE>drivers in<CONT>{country}<CONT>drive on the<CONT>right side of<CONT>the road?<DONE>")
elif q == 8: elif q == 8:
mon = random.choice(list(poke_data.evolution_levels.keys())) mon = random.choice(list(poke_data.evolution_levels.keys()))
level = poke_data.evolution_levels[mon] level = poke_data.evolution_levels[mon]
@ -115,17 +148,17 @@ def write_quizzes(self, data, random):
level += random.choice(range(1, 6)) * random.choice((-1, 1)) level += random.choice(range(1, 6)) * random.choice((-1, 1))
return encode_text(f"{mon} evolves<LINE>at level {level}?<DONE>") return encode_text(f"{mon} evolves<LINE>at level {level}?<DONE>")
elif q == 9: elif q == 9:
move = random.choice(list(self.local_move_data.keys())) move = random.choice(list(world.local_move_data.keys()))
actual_type = self.local_move_data[move]["type"] actual_type = world.local_move_data[move]["type"]
question_type = actual_type question_type = actual_type
while question_type == actual_type and not a: while question_type == actual_type and not a:
question_type = random.choice(list(poke_data.type_ids.keys())) question_type = random.choice(list(poke_data.type_ids.keys()))
return encode_text(f"{move} is<LINE>{question_type} type?<DONE>") return encode_text(f"{move} is<LINE>{question_type} type?<DONE>")
elif q == 10: elif q == 10:
mon = random.choice(list(poke_data.pokemon_data.keys())) mon = random.choice(list(poke_data.pokemon_data.keys()))
actual_type = self.local_poke_data[mon][random.choice(("type1", "type2"))] actual_type = world.local_poke_data[mon][random.choice(("type1", "type2"))]
question_type = actual_type question_type = actual_type
while question_type in [self.local_poke_data[mon]["type1"], self.local_poke_data[mon]["type2"]] and not a: while question_type in [world.local_poke_data[mon]["type1"], world.local_poke_data[mon]["type2"]] and not a:
question_type = random.choice(list(poke_data.type_ids.keys())) question_type = random.choice(list(poke_data.type_ids.keys()))
return encode_text(f"{mon} is<LINE>{question_type} type?<DONE>") return encode_text(f"{mon} is<LINE>{question_type} type?<DONE>")
elif q == 11: elif q == 11:
@ -147,8 +180,8 @@ def write_quizzes(self, data, random):
return encode_text(f"{equation}<LINE>= {question_result}?<DONE>") return encode_text(f"{equation}<LINE>= {question_result}?<DONE>")
elif q == 12: elif q == 12:
route = random.choice((12, 16)) route = random.choice((12, 16))
actual_mon = self.multiworld.get_location(f"Route {route} - Sleeping Pokemon", actual_mon = world.multiworld.get_location(f"Route {route} - Sleeping Pokemon",
self.player).item.name.split("Static ")[1] world.player).item.name.split("Static ")[1]
question_mon = actual_mon question_mon = actual_mon
while question_mon == actual_mon and not a: while question_mon == actual_mon and not a:
question_mon = random.choice(list(poke_data.pokemon_data.keys())) question_mon = random.choice(list(poke_data.pokemon_data.keys()))
@ -157,7 +190,7 @@ def write_quizzes(self, data, random):
type1 = random.choice(list(poke_data.type_ids.keys())) type1 = random.choice(list(poke_data.type_ids.keys()))
type2 = random.choice(list(poke_data.type_ids.keys())) type2 = random.choice(list(poke_data.type_ids.keys()))
eff_msgs = ["super effective<CONT>", "no ", "not very<CONT>effective<CONT>", "normal "] eff_msgs = ["super effective<CONT>", "no ", "not very<CONT>effective<CONT>", "normal "]
for matchup in self.type_chart: for matchup in world.type_chart:
if matchup[0] == type1 and matchup[1] == type2: if matchup[0] == type1 and matchup[1] == type2:
if matchup[2] > 10: if matchup[2] > 10:
eff = eff_msgs[0] eff = eff_msgs[0]
@ -175,15 +208,25 @@ def write_quizzes(self, data, random):
eff = random.choice(eff_msgs) eff = random.choice(eff_msgs)
return encode_text(f"{type1} deals<LINE>{eff}damage to<CONT>{type2} type?<DONE>") return encode_text(f"{type1} deals<LINE>{eff}damage to<CONT>{type2} type?<DONE>")
elif q == 14: elif q == 14:
fossil_level = self.multiworld.get_location("Fossil Level - Trainer Parties", fossil_level = world.multiworld.get_location("Fossil Level - Trainer Parties",
self.player).party_data[0]['level'] world.player).party_data[0]['level']
if not a: if not a:
fossil_level += random.choice((-5, 5)) fossil_level += random.choice((-5, 5))
return encode_text(f"Fossil #MON<LINE>revive at level<CONT>{fossil_level}?<DONE>") return encode_text(f"Fossil #MON<LINE>revive at level<CONT>{fossil_level}?<DONE>")
elif q == 15:
if a:
fodmap = random.choice(["garlic", "onion", "milk", "watermelon", "cherries", "wheat", "barley",
"pistachios", "cashews", "kidney beans", "apples", "honey"])
else:
fodmap = random.choice(["carrots", "potatoes", "oranges", "pineapple", "blueberries", "parmesan",
"eggs", "beef", "chicken", "oat", "rice", "maple syrup", "peanuts"])
are_is = "are" if fodmap[-1] == "s" else "is"
return encode_text(f"According to<LINE>Monash Uni.,<CONT>{fodmap} {are_is}<CONT>considered high<CONT>in FODMAPs?<DONE>")
answers = [random.randint(0, 1) for _ in range(6)] answers = [random.randint(0, 1) for _ in range(6)]
questions = random.sample((range(0, 15)), 6) questions = random.sample((range(0, 16)), 6)
question_texts = [] question_texts = []
for i, question in enumerate(questions): for i, question in enumerate(questions):
question_texts.append(get_quiz(question, answers[i])) question_texts.append(get_quiz(question, answers[i]))
@ -193,9 +236,9 @@ def write_quizzes(self, data, random):
write_bytes(data, question_texts[i], rom_addresses[f"Text_Quiz_{quiz}"]) write_bytes(data, question_texts[i], rom_addresses[f"Text_Quiz_{quiz}"])
def generate_output(self, output_directory: str): def generate_output(world, output_directory: str):
random = self.multiworld.per_slot_randoms[self.player] random = world.random
game_version = self.multiworld.game_version[self.player].current_key game_version = world.options.game_version.current_key
data = bytes(get_base_rom_bytes(game_version)) data = bytes(get_base_rom_bytes(game_version))
base_patch = pkgutil.get_data(__name__, f'basepatch_{game_version}.bsdiff4') base_patch = pkgutil.get_data(__name__, f'basepatch_{game_version}.bsdiff4')
@ -205,8 +248,8 @@ def generate_output(self, output_directory: str):
basemd5 = hashlib.md5() basemd5 = hashlib.md5()
basemd5.update(data) basemd5.update(data)
pallet_connections = {entrance: self.multiworld.get_entrance(f"Pallet Town to {entrance}", pallet_connections = {entrance: world.multiworld.get_entrance(f"Pallet Town to {entrance}",
self.player).connected_region.name for world.player).connected_region.name for
entrance in ["Player's House 1F", "Oak's Lab", entrance in ["Player's House 1F", "Oak's Lab",
"Rival's House"]} "Rival's House"]}
paths = None paths = None
@ -222,11 +265,11 @@ def generate_output(self, output_directory: str):
elif pallet_connections["Oak's Lab"] == "Player's House 1F": elif pallet_connections["Oak's Lab"] == "Player's House 1F":
write_bytes(data, [0x5F, 0xC7, 0x0C, 0x0C, 0x00, 0x00], rom_addresses["Pallet_Fly_Coords"]) write_bytes(data, [0x5F, 0xC7, 0x0C, 0x0C, 0x00, 0x00], rom_addresses["Pallet_Fly_Coords"])
for region in self.multiworld.get_regions(self.player): for region in world.multiworld.get_regions(world.player):
for entrance in region.exits: for entrance in region.exits:
if isinstance(entrance, PokemonRBWarp): if isinstance(entrance, PokemonRBWarp):
self.multiworld.spoiler.set_entrance(entrance.name, entrance.connected_region.name, "entrance", world.multiworld.spoiler.set_entrance(entrance.name, entrance.connected_region.name, "entrance",
self.player) world.player)
warp_ids = (entrance.warp_id,) if isinstance(entrance.warp_id, int) else entrance.warp_id warp_ids = (entrance.warp_id,) if isinstance(entrance.warp_id, int) else entrance.warp_id
warp_to_ids = (entrance.target,) if isinstance(entrance.target, int) else entrance.target warp_to_ids = (entrance.target,) if isinstance(entrance.target, int) else entrance.target
for i, warp_id in enumerate(warp_ids): for i, warp_id in enumerate(warp_ids):
@ -241,32 +284,32 @@ def generate_output(self, output_directory: str):
data[address] = 0 if "Elevator" in connected_map_name else warp_to_ids[i] data[address] = 0 if "Elevator" in connected_map_name else warp_to_ids[i]
data[address + 1] = map_ids[connected_map_name] data[address + 1] = map_ids[connected_map_name]
if self.multiworld.door_shuffle[self.player] == "simple": if world.options.door_shuffle == "simple":
for (entrance, _, _, map_coords_entries, map_name, _) in town_map_coords.values(): for (entrance, _, _, map_coords_entries, map_name, _) in town_map_coords.values():
destination = self.multiworld.get_entrance(entrance, self.player).connected_region.name destination = world.multiworld.get_entrance(entrance, world.player).connected_region.name
(_, x, y, _, _, map_order_entry) = town_map_coords[destination] (_, x, y, _, _, map_order_entry) = town_map_coords[destination]
for map_coord_entry in map_coords_entries: for map_coord_entry in map_coords_entries:
data[rom_addresses["Town_Map_Coords"] + (map_coord_entry * 4) + 1] = (y << 4) | x data[rom_addresses["Town_Map_Coords"] + (map_coord_entry * 4) + 1] = (y << 4) | x
data[rom_addresses["Town_Map_Order"] + map_order_entry] = map_ids[map_name] data[rom_addresses["Town_Map_Order"] + map_order_entry] = map_ids[map_name]
if not self.multiworld.key_items_only[self.player]: if not world.options.key_items_only:
for i, gym_leader in enumerate(("Pewter Gym - Brock TM", "Cerulean Gym - Misty TM", for i, gym_leader in enumerate(("Pewter Gym - Brock TM", "Cerulean Gym - Misty TM",
"Vermilion Gym - Lt. Surge TM", "Celadon Gym - Erika TM", "Vermilion Gym - Lt. Surge TM", "Celadon Gym - Erika TM",
"Fuchsia Gym - Koga TM", "Saffron Gym - Sabrina TM", "Fuchsia Gym - Koga TM", "Saffron Gym - Sabrina TM",
"Cinnabar Gym - Blaine TM", "Viridian Gym - Giovanni TM")): "Cinnabar Gym - Blaine TM", "Viridian Gym - Giovanni TM")):
item_name = self.multiworld.get_location(gym_leader, self.player).item.name item_name = world.multiworld.get_location(gym_leader, world.player).item.name
if item_name.startswith("TM"): if item_name.startswith("TM"):
try: try:
tm = int(item_name[2:4]) tm = int(item_name[2:4])
move = poke_data.moves[self.local_tms[tm - 1]]["id"] move = poke_data.moves[world.local_tms[tm - 1]]["id"]
data[rom_addresses["Gym_Leader_Moves"] + (2 * i)] = move data[rom_addresses["Gym_Leader_Moves"] + (2 * i)] = move
except KeyError: except KeyError:
pass pass
def set_trade_mon(address, loc): def set_trade_mon(address, loc):
mon = self.multiworld.get_location(loc, self.player).item.name mon = world.multiworld.get_location(loc, world.player).item.name
data[rom_addresses[address]] = poke_data.pokemon_data[mon]["id"] data[rom_addresses[address]] = poke_data.pokemon_data[mon]["id"]
self.trade_mons[address] = mon world.trade_mons[address] = mon
if game_version == "red": if game_version == "red":
set_trade_mon("Trade_Terry", "Safari Zone Center - Wild Pokemon - 5") set_trade_mon("Trade_Terry", "Safari Zone Center - Wild Pokemon - 5")
@ -282,10 +325,10 @@ def generate_output(self, output_directory: str):
set_trade_mon("Trade_Doris", "Cerulean Cave 1F - Wild Pokemon - 9") set_trade_mon("Trade_Doris", "Cerulean Cave 1F - Wild Pokemon - 9")
set_trade_mon("Trade_Crinkles", "Route 12 - Wild Pokemon - 4") set_trade_mon("Trade_Crinkles", "Route 12 - Wild Pokemon - 4")
data[rom_addresses['Fly_Location']] = self.fly_map_code data[rom_addresses['Fly_Location']] = world.fly_map_code
data[rom_addresses['Map_Fly_Location']] = self.town_map_fly_map_code data[rom_addresses['Map_Fly_Location']] = world.town_map_fly_map_code
if self.multiworld.fix_combat_bugs[self.player]: if world.options.fix_combat_bugs:
data[rom_addresses["Option_Fix_Combat_Bugs"]] = 1 data[rom_addresses["Option_Fix_Combat_Bugs"]] = 1
data[rom_addresses["Option_Fix_Combat_Bugs_Focus_Energy"]] = 0x28 # jr z data[rom_addresses["Option_Fix_Combat_Bugs_Focus_Energy"]] = 0x28 # jr z
data[rom_addresses["Option_Fix_Combat_Bugs_HP_Drain_Dream_Eater"]] = 0x1A # ld a, (de) data[rom_addresses["Option_Fix_Combat_Bugs_HP_Drain_Dream_Eater"]] = 0x1A # ld a, (de)
@ -298,25 +341,25 @@ def generate_output(self, output_directory: str):
data[rom_addresses["Option_Fix_Combat_Bugs_Heal_Effect"] + 1] = 5 # 5 bytes ahead data[rom_addresses["Option_Fix_Combat_Bugs_Heal_Effect"] + 1] = 5 # 5 bytes ahead
data[rom_addresses["Option_Fix_Combat_Bugs_Heal_Stat_Modifiers"]] = 1 data[rom_addresses["Option_Fix_Combat_Bugs_Heal_Stat_Modifiers"]] = 1
if self.multiworld.poke_doll_skip[self.player] == "in_logic": if world.options.poke_doll_skip == "in_logic":
data[rom_addresses["Option_Silph_Scope_Skip"]] = 0x00 # nop data[rom_addresses["Option_Silph_Scope_Skip"]] = 0x00 # nop
data[rom_addresses["Option_Silph_Scope_Skip"] + 1] = 0x00 # nop data[rom_addresses["Option_Silph_Scope_Skip"] + 1] = 0x00 # nop
data[rom_addresses["Option_Silph_Scope_Skip"] + 2] = 0x00 # nop data[rom_addresses["Option_Silph_Scope_Skip"] + 2] = 0x00 # nop
if self.multiworld.bicycle_gate_skips[self.player] == "patched": if world.options.bicycle_gate_skips == "patched":
data[rom_addresses["Option_Route_16_Gate_Fix"]] = 0x00 # nop data[rom_addresses["Option_Route_16_Gate_Fix"]] = 0x00 # nop
data[rom_addresses["Option_Route_16_Gate_Fix"] + 1] = 0x00 # nop data[rom_addresses["Option_Route_16_Gate_Fix"] + 1] = 0x00 # nop
data[rom_addresses["Option_Route_18_Gate_Fix"]] = 0x00 # nop data[rom_addresses["Option_Route_18_Gate_Fix"]] = 0x00 # nop
data[rom_addresses["Option_Route_18_Gate_Fix"] + 1] = 0x00 # nop data[rom_addresses["Option_Route_18_Gate_Fix"] + 1] = 0x00 # nop
if self.multiworld.door_shuffle[self.player]: if world.options.door_shuffle:
data[rom_addresses["Entrance_Shuffle_Fuji_Warp"]] = 1 # prevent warping to Fuji's House from Pokemon Tower 7F data[rom_addresses["Entrance_Shuffle_Fuji_Warp"]] = 1 # prevent warping to Fuji's House from Pokemon Tower 7F
if self.multiworld.all_elevators_locked[self.player]: if world.options.all_elevators_locked:
data[rom_addresses["Option_Locked_Elevator_Celadon"]] = 0x20 # jr nz data[rom_addresses["Option_Locked_Elevator_Celadon"]] = 0x20 # jr nz
data[rom_addresses["Option_Locked_Elevator_Silph"]] = 0x20 # jr nz data[rom_addresses["Option_Locked_Elevator_Silph"]] = 0x20 # jr nz
if self.multiworld.tea[self.player].value: if world.options.tea:
data[rom_addresses["Option_Tea"]] = 1 data[rom_addresses["Option_Tea"]] = 1
data[rom_addresses["Guard_Drink_List"]] = 0x54 data[rom_addresses["Guard_Drink_List"]] = 0x54
data[rom_addresses["Guard_Drink_List"] + 1] = 0 data[rom_addresses["Guard_Drink_List"] + 1] = 0
@ -325,90 +368,94 @@ def generate_output(self, output_directory: str):
"<PARA>Oh wait there,<LINE>the road's closed.<DONE>"), "<PARA>Oh wait there,<LINE>the road's closed.<DONE>"),
rom_addresses["Text_Saffron_Gate"]) rom_addresses["Text_Saffron_Gate"])
data[rom_addresses["Tea_Key_Item_A"]] = 0x28 # jr .z
data[rom_addresses["Tea_Key_Item_B"]] = 0x28 # jr .z
data[rom_addresses["Tea_Key_Item_C"]] = 0x28 # jr .z
data[rom_addresses["Fossils_Needed_For_Second_Item"]] = ( data[rom_addresses["Fossils_Needed_For_Second_Item"]] = (
self.multiworld.second_fossil_check_condition[self.player].value) world.options.second_fossil_check_condition.value)
data[rom_addresses["Option_Lose_Money"]] = int(not self.multiworld.lose_money_on_blackout[self.player].value) data[rom_addresses["Option_Lose_Money"]] = int(not world.options.lose_money_on_blackout.value)
if self.multiworld.extra_key_items[self.player]: if world.options.extra_key_items:
data[rom_addresses['Option_Extra_Key_Items_A']] = 1 data[rom_addresses['Option_Extra_Key_Items_A']] = 1
data[rom_addresses['Option_Extra_Key_Items_B']] = 1 data[rom_addresses['Option_Extra_Key_Items_B']] = 1
data[rom_addresses['Option_Extra_Key_Items_C']] = 1 data[rom_addresses['Option_Extra_Key_Items_C']] = 1
data[rom_addresses['Option_Extra_Key_Items_D']] = 1 data[rom_addresses['Option_Extra_Key_Items_D']] = 1
data[rom_addresses["Option_Split_Card_Key"]] = self.multiworld.split_card_key[self.player].value data[rom_addresses["Option_Split_Card_Key"]] = world.options.split_card_key.value
data[rom_addresses["Option_Blind_Trainers"]] = round(self.multiworld.blind_trainers[self.player].value * 2.55) data[rom_addresses["Option_Blind_Trainers"]] = round(world.options.blind_trainers.value * 2.55)
data[rom_addresses["Option_Cerulean_Cave_Badges"]] = self.multiworld.cerulean_cave_badges_condition[self.player].value data[rom_addresses["Option_Cerulean_Cave_Badges"]] = world.options.cerulean_cave_badges_condition.value
data[rom_addresses["Option_Cerulean_Cave_Key_Items"]] = self.multiworld.cerulean_cave_key_items_condition[self.player].total data[rom_addresses["Option_Cerulean_Cave_Key_Items"]] = world.options.cerulean_cave_key_items_condition.total
write_bytes(data, encode_text(str(self.multiworld.cerulean_cave_badges_condition[self.player].value)), rom_addresses["Text_Cerulean_Cave_Badges"]) write_bytes(data, encode_text(str(world.options.cerulean_cave_badges_condition.value)), rom_addresses["Text_Cerulean_Cave_Badges"])
write_bytes(data, encode_text(str(self.multiworld.cerulean_cave_key_items_condition[self.player].total) + " key items."), rom_addresses["Text_Cerulean_Cave_Key_Items"]) write_bytes(data, encode_text(str(world.options.cerulean_cave_key_items_condition.total) + " key items."), rom_addresses["Text_Cerulean_Cave_Key_Items"])
data[rom_addresses['Option_Encounter_Minimum_Steps']] = self.multiworld.minimum_steps_between_encounters[self.player].value data[rom_addresses['Option_Encounter_Minimum_Steps']] = world.options.minimum_steps_between_encounters.value
data[rom_addresses['Option_Route23_Badges']] = self.multiworld.victory_road_condition[self.player].value data[rom_addresses['Option_Route23_Badges']] = world.options.victory_road_condition.value
data[rom_addresses['Option_Victory_Road_Badges']] = self.multiworld.route_22_gate_condition[self.player].value data[rom_addresses['Option_Victory_Road_Badges']] = world.options.route_22_gate_condition.value
data[rom_addresses['Option_Elite_Four_Pokedex']] = self.multiworld.elite_four_pokedex_condition[self.player].total data[rom_addresses['Option_Elite_Four_Pokedex']] = world.options.elite_four_pokedex_condition.total
data[rom_addresses['Option_Elite_Four_Key_Items']] = self.multiworld.elite_four_key_items_condition[self.player].total data[rom_addresses['Option_Elite_Four_Key_Items']] = world.options.elite_four_key_items_condition.total
data[rom_addresses['Option_Elite_Four_Badges']] = self.multiworld.elite_four_badges_condition[self.player].value data[rom_addresses['Option_Elite_Four_Badges']] = world.options.elite_four_badges_condition.value
write_bytes(data, encode_text(str(self.multiworld.elite_four_badges_condition[self.player].value)), rom_addresses["Text_Elite_Four_Badges"]) write_bytes(data, encode_text(str(world.options.elite_four_badges_condition.value)), rom_addresses["Text_Elite_Four_Badges"])
write_bytes(data, encode_text(str(self.multiworld.elite_four_key_items_condition[self.player].total) + " key items, and"), rom_addresses["Text_Elite_Four_Key_Items"]) write_bytes(data, encode_text(str(world.options.elite_four_key_items_condition.total) + " key items, and"), rom_addresses["Text_Elite_Four_Key_Items"])
write_bytes(data, encode_text(str(self.multiworld.elite_four_pokedex_condition[self.player].total) + " #MON"), rom_addresses["Text_Elite_Four_Pokedex"]) write_bytes(data, encode_text(str(world.options.elite_four_pokedex_condition.total) + " #MON"), rom_addresses["Text_Elite_Four_Pokedex"])
write_bytes(data, encode_text(str(self.total_key_items), length=2), rom_addresses["Trainer_Screen_Total_Key_Items"]) write_bytes(data, encode_text(str(world.total_key_items), length=2), rom_addresses["Trainer_Screen_Total_Key_Items"])
data[rom_addresses['Option_Viridian_Gym_Badges']] = self.multiworld.viridian_gym_condition[self.player].value data[rom_addresses['Option_Viridian_Gym_Badges']] = world.options.viridian_gym_condition.value
data[rom_addresses['Option_EXP_Modifier']] = self.multiworld.exp_modifier[self.player].value data[rom_addresses['Option_EXP_Modifier']] = world.options.exp_modifier.value
if not self.multiworld.require_item_finder[self.player]: if not world.options.require_item_finder:
data[rom_addresses['Option_Itemfinder']] = 0 # nop data[rom_addresses['Option_Itemfinder']] = 0 # nop
if self.multiworld.extra_strength_boulders[self.player]: if world.options.extra_strength_boulders:
for i in range(0, 3): for i in range(0, 3):
data[rom_addresses['Option_Boulders'] + (i * 3)] = 0x15 data[rom_addresses['Option_Boulders'] + (i * 3)] = 0x15
if self.multiworld.extra_key_items[self.player]: if world.options.extra_key_items:
for i in range(0, 4): for i in range(0, 4):
data[rom_addresses['Option_Rock_Tunnel_Extra_Items'] + (i * 3)] = 0x15 data[rom_addresses['Option_Rock_Tunnel_Extra_Items'] + (i * 3)] = 0x15
if self.multiworld.old_man[self.player] == "open_viridian_city": if world.options.old_man == "open_viridian_city":
data[rom_addresses['Option_Old_Man']] = 0x11 data[rom_addresses['Option_Old_Man']] = 0x11
data[rom_addresses['Option_Old_Man_Lying']] = 0x15 data[rom_addresses['Option_Old_Man_Lying']] = 0x15
data[rom_addresses['Option_Route3_Guard_B']] = self.multiworld.route_3_condition[self.player].value data[rom_addresses['Option_Route3_Guard_B']] = world.options.route_3_condition.value
if self.multiworld.route_3_condition[self.player] == "open": if world.options.route_3_condition == "open":
data[rom_addresses['Option_Route3_Guard_A']] = 0x11 data[rom_addresses['Option_Route3_Guard_A']] = 0x11
if not self.multiworld.robbed_house_officer[self.player]: if not world.options.robbed_house_officer:
data[rom_addresses['Option_Trashed_House_Guard_A']] = 0x15 data[rom_addresses['Option_Trashed_House_Guard_A']] = 0x15
data[rom_addresses['Option_Trashed_House_Guard_B']] = 0x11 data[rom_addresses['Option_Trashed_House_Guard_B']] = 0x11
if self.multiworld.require_pokedex[self.player]: if world.options.require_pokedex:
data[rom_addresses["Require_Pokedex_A"]] = 1 data[rom_addresses["Require_Pokedex_A"]] = 1
data[rom_addresses["Require_Pokedex_B"]] = 1 data[rom_addresses["Require_Pokedex_B"]] = 1
data[rom_addresses["Require_Pokedex_C"]] = 1 data[rom_addresses["Require_Pokedex_C"]] = 1
else: else:
data[rom_addresses["Require_Pokedex_D"]] = 0x18 # jr data[rom_addresses["Require_Pokedex_D"]] = 0x18 # jr
if self.multiworld.dexsanity[self.player]: if world.options.dexsanity:
data[rom_addresses["Option_Dexsanity_A"]] = 1 data[rom_addresses["Option_Dexsanity_A"]] = 1
data[rom_addresses["Option_Dexsanity_B"]] = 1 data[rom_addresses["Option_Dexsanity_B"]] = 1
if self.multiworld.all_pokemon_seen[self.player]: if world.options.all_pokemon_seen:
data[rom_addresses["Option_Pokedex_Seen"]] = 1 data[rom_addresses["Option_Pokedex_Seen"]] = 1
money = str(self.multiworld.starting_money[self.player].value).zfill(6) money = str(world.options.starting_money.value).zfill(6)
data[rom_addresses["Starting_Money_High"]] = int(money[:2], 16) data[rom_addresses["Starting_Money_High"]] = int(money[:2], 16)
data[rom_addresses["Starting_Money_Middle"]] = int(money[2:4], 16) data[rom_addresses["Starting_Money_Middle"]] = int(money[2:4], 16)
data[rom_addresses["Starting_Money_Low"]] = int(money[4:], 16) data[rom_addresses["Starting_Money_Low"]] = int(money[4:], 16)
data[rom_addresses["Text_Badges_Needed_Viridian_Gym"]] = encode_text( data[rom_addresses["Text_Badges_Needed_Viridian_Gym"]] = encode_text(
str(self.multiworld.viridian_gym_condition[self.player].value))[0] str(world.options.viridian_gym_condition.value))[0]
data[rom_addresses["Text_Rt23_Badges_A"]] = encode_text( data[rom_addresses["Text_Rt23_Badges_A"]] = encode_text(
str(self.multiworld.victory_road_condition[self.player].value))[0] str(world.options.victory_road_condition.value))[0]
data[rom_addresses["Text_Rt23_Badges_B"]] = encode_text( data[rom_addresses["Text_Rt23_Badges_B"]] = encode_text(
str(self.multiworld.victory_road_condition[self.player].value))[0] str(world.options.victory_road_condition.value))[0]
data[rom_addresses["Text_Rt23_Badges_C"]] = encode_text( data[rom_addresses["Text_Rt23_Badges_C"]] = encode_text(
str(self.multiworld.victory_road_condition[self.player].value))[0] str(world.options.victory_road_condition.value))[0]
data[rom_addresses["Text_Rt23_Badges_D"]] = encode_text( data[rom_addresses["Text_Rt23_Badges_D"]] = encode_text(
str(self.multiworld.victory_road_condition[self.player].value))[0] str(world.options.victory_road_condition.value))[0]
data[rom_addresses["Text_Badges_Needed"]] = encode_text( data[rom_addresses["Text_Badges_Needed"]] = encode_text(
str(self.multiworld.elite_four_badges_condition[self.player].value))[0] str(world.options.elite_four_badges_condition.value))[0]
write_bytes(data, encode_text( write_bytes(data, encode_text(
" ".join(self.multiworld.get_location("Route 4 Pokemon Center - Pokemon For Sale", self.player).item.name.upper().split()[1:])), " ".join(world.multiworld.get_location("Route 4 Pokemon Center - Pokemon For Sale", world.player).item.name.upper().split()[1:])),
rom_addresses["Text_Magikarp_Salesman"]) rom_addresses["Text_Magikarp_Salesman"])
if self.multiworld.badges_needed_for_hm_moves[self.player].value == 0: if world.options.badges_needed_for_hm_moves.value == 0:
for hm_move in poke_data.hm_moves: for hm_move in poke_data.hm_moves:
write_bytes(data, bytearray([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), write_bytes(data, bytearray([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
rom_addresses["HM_" + hm_move + "_Badge_a"]) rom_addresses["HM_" + hm_move + "_Badge_a"])
elif self.extra_badges: elif world.extra_badges:
written_badges = {} written_badges = {}
for hm_move, badge in self.extra_badges.items(): for hm_move, badge in world.extra_badges.items():
data[rom_addresses["HM_" + hm_move + "_Badge_b"]] = {"Boulder Badge": 0x47, "Cascade Badge": 0x4F, data[rom_addresses["HM_" + hm_move + "_Badge_b"]] = {"Boulder Badge": 0x47, "Cascade Badge": 0x4F,
"Thunder Badge": 0x57, "Rainbow Badge": 0x5F, "Thunder Badge": 0x57, "Rainbow Badge": 0x5F,
"Soul Badge": 0x67, "Marsh Badge": 0x6F, "Soul Badge": 0x67, "Marsh Badge": 0x6F,
@ -427,7 +474,7 @@ def generate_output(self, output_directory: str):
write_bytes(data, encode_text("Nothing"), rom_addresses["Badge_Text_" + badge.replace(" ", "_")]) write_bytes(data, encode_text("Nothing"), rom_addresses["Badge_Text_" + badge.replace(" ", "_")])
type_loc = rom_addresses["Type_Chart"] type_loc = rom_addresses["Type_Chart"]
for matchup in self.type_chart: for matchup in world.type_chart:
if matchup[2] != 10: # don't needlessly divide damage by 10 and multiply by 10 if matchup[2] != 10: # don't needlessly divide damage by 10 and multiply by 10
data[type_loc] = poke_data.type_ids[matchup[0]] data[type_loc] = poke_data.type_ids[matchup[0]]
data[type_loc + 1] = poke_data.type_ids[matchup[1]] data[type_loc + 1] = poke_data.type_ids[matchup[1]]
@ -437,52 +484,49 @@ def generate_output(self, output_directory: str):
data[type_loc + 1] = 0xFF data[type_loc + 1] = 0xFF
data[type_loc + 2] = 0xFF data[type_loc + 2] = 0xFF
if self.multiworld.normalize_encounter_chances[self.player].value: if world.options.normalize_encounter_chances.value:
chances = [25, 51, 77, 103, 129, 155, 180, 205, 230, 255] chances = [25, 51, 77, 103, 129, 155, 180, 205, 230, 255]
for i, chance in enumerate(chances): for i, chance in enumerate(chances):
data[rom_addresses['Encounter_Chances'] + (i * 2)] = chance data[rom_addresses['Encounter_Chances'] + (i * 2)] = chance
for mon, mon_data in self.local_poke_data.items(): for mon, mon_data in world.local_poke_data.items():
if mon == "Mew": if mon == "Mew":
address = rom_addresses["Base_Stats_Mew"] address = rom_addresses["Base_Stats_Mew"]
else: else:
address = rom_addresses["Base_Stats"] + (28 * (mon_data["dex"] - 1)) address = rom_addresses["Base_Stats"] + (28 * (mon_data["dex"] - 1))
data[address + 1] = self.local_poke_data[mon]["hp"] data[address + 1] = world.local_poke_data[mon]["hp"]
data[address + 2] = self.local_poke_data[mon]["atk"] data[address + 2] = world.local_poke_data[mon]["atk"]
data[address + 3] = self.local_poke_data[mon]["def"] data[address + 3] = world.local_poke_data[mon]["def"]
data[address + 4] = self.local_poke_data[mon]["spd"] data[address + 4] = world.local_poke_data[mon]["spd"]
data[address + 5] = self.local_poke_data[mon]["spc"] data[address + 5] = world.local_poke_data[mon]["spc"]
data[address + 6] = poke_data.type_ids[self.local_poke_data[mon]["type1"]] data[address + 6] = poke_data.type_ids[world.local_poke_data[mon]["type1"]]
data[address + 7] = poke_data.type_ids[self.local_poke_data[mon]["type2"]] data[address + 7] = poke_data.type_ids[world.local_poke_data[mon]["type2"]]
data[address + 8] = self.local_poke_data[mon]["catch rate"] data[address + 8] = world.local_poke_data[mon]["catch rate"]
data[address + 15] = poke_data.moves[self.local_poke_data[mon]["start move 1"]]["id"] data[address + 15] = poke_data.moves[world.local_poke_data[mon]["start move 1"]]["id"]
data[address + 16] = poke_data.moves[self.local_poke_data[mon]["start move 2"]]["id"] data[address + 16] = poke_data.moves[world.local_poke_data[mon]["start move 2"]]["id"]
data[address + 17] = poke_data.moves[self.local_poke_data[mon]["start move 3"]]["id"] data[address + 17] = poke_data.moves[world.local_poke_data[mon]["start move 3"]]["id"]
data[address + 18] = poke_data.moves[self.local_poke_data[mon]["start move 4"]]["id"] data[address + 18] = poke_data.moves[world.local_poke_data[mon]["start move 4"]]["id"]
write_bytes(data, self.local_poke_data[mon]["tms"], address + 20) write_bytes(data, world.local_poke_data[mon]["tms"], address + 20)
if mon in self.learnsets and self.learnsets[mon]: if mon in world.learnsets and world.learnsets[mon]:
address = rom_addresses["Learnset_" + mon.replace(" ", "")] address = rom_addresses["Learnset_" + mon.replace(" ", "")]
for i, move in enumerate(self.learnsets[mon]): for i, move in enumerate(world.learnsets[mon]):
data[(address + 1) + i * 2] = poke_data.moves[move]["id"] data[(address + 1) + i * 2] = poke_data.moves[move]["id"]
data[rom_addresses["Option_Aide_Rt2"]] = self.multiworld.oaks_aide_rt_2[self.player].value data[rom_addresses["Option_Aide_Rt2"]] = world.options.oaks_aide_rt_2.value
data[rom_addresses["Option_Aide_Rt11"]] = self.multiworld.oaks_aide_rt_11[self.player].value data[rom_addresses["Option_Aide_Rt11"]] = world.options.oaks_aide_rt_11.value
data[rom_addresses["Option_Aide_Rt15"]] = self.multiworld.oaks_aide_rt_15[self.player].value data[rom_addresses["Option_Aide_Rt15"]] = world.options.oaks_aide_rt_15.value
if self.multiworld.safari_zone_normal_battles[self.player].value == 1: if world.options.safari_zone_normal_battles.value == 1:
data[rom_addresses["Option_Safari_Zone_Battle_Type"]] = 255 data[rom_addresses["Option_Safari_Zone_Battle_Type"]] = 255
if self.multiworld.reusable_tms[self.player].value: if world.options.reusable_tms.value:
data[rom_addresses["Option_Reusable_TMs"]] = 0xC9 data[rom_addresses["Option_Reusable_TMs"]] = 0xC9
for i in range(1, 10): data[rom_addresses["Option_Always_Half_STAB"]] = int(not world.options.same_type_attack_bonus.value)
data[rom_addresses[f"Option_Trainersanity{i}"]] = self.multiworld.trainersanity[self.player].value
data[rom_addresses["Option_Always_Half_STAB"]] = int(not self.multiworld.same_type_attack_bonus[self.player].value) if world.options.better_shops:
if self.multiworld.better_shops[self.player]:
inventory = ["Poke Ball", "Great Ball", "Ultra Ball"] inventory = ["Poke Ball", "Great Ball", "Ultra Ball"]
if self.multiworld.better_shops[self.player].value == 2: if world.options.better_shops.value == 2:
inventory.append("Master Ball") inventory.append("Master Ball")
inventory += ["Potion", "Super Potion", "Hyper Potion", "Max Potion", "Full Restore", "Revive", "Antidote", inventory += ["Potion", "Super Potion", "Hyper Potion", "Max Potion", "Full Restore", "Revive", "Antidote",
"Awakening", "Burn Heal", "Ice Heal", "Paralyze Heal", "Full Heal", "Repel", "Super Repel", "Awakening", "Burn Heal", "Ice Heal", "Paralyze Heal", "Full Heal", "Repel", "Super Repel",
@ -492,30 +536,30 @@ def generate_output(self, output_directory: str):
shop_data.append(0xFF) shop_data.append(0xFF)
for shop in range(1, 11): for shop in range(1, 11):
write_bytes(data, shop_data, rom_addresses[f"Shop{shop}"]) write_bytes(data, shop_data, rom_addresses[f"Shop{shop}"])
if self.multiworld.stonesanity[self.player]: if world.options.stonesanity:
write_bytes(data, bytearray([0xFE, 1, item_table["Poke Doll"].id - 172000000, 0xFF]), rom_addresses[f"Shop_Stones"]) write_bytes(data, bytearray([0xFE, 1, item_table["Poke Doll"].id - 172000000, 0xFF]), rom_addresses[f"Shop_Stones"])
price = str(self.multiworld.master_ball_price[self.player].value).zfill(6) price = str(world.options.master_ball_price.value).zfill(6)
price = bytearray([int(price[:2], 16), int(price[2:4], 16), int(price[4:], 16)]) price = bytearray([int(price[:2], 16), int(price[2:4], 16), int(price[4:], 16)])
write_bytes(data, price, rom_addresses["Price_Master_Ball"]) # Money values in Red and Blue are weird write_bytes(data, price, rom_addresses["Price_Master_Ball"]) # Money values in Red and Blue are weird
for item in reversed(self.multiworld.precollected_items[self.player]): for item in reversed(world.multiworld.precollected_items[world.player]):
if data[rom_addresses["Start_Inventory"] + item.code - 172000000] < 255: if data[rom_addresses["Start_Inventory"] + item.code - 172000000] < 255:
data[rom_addresses["Start_Inventory"] + item.code - 172000000] += 1 data[rom_addresses["Start_Inventory"] + item.code - 172000000] += 1
set_mon_palettes(self, random, data) set_mon_palettes(world, random, data)
for move_data in self.local_move_data.values(): for move_data in world.local_move_data.values():
if move_data["id"] == 0: if move_data["id"] == 0:
continue continue
address = rom_addresses["Move_Data"] + ((move_data["id"] - 1) * 6) address = rom_addresses["Move_Data"] + ((move_data["id"] - 1) * 6)
write_bytes(data, bytearray([move_data["id"], move_data["effect"], move_data["power"], write_bytes(data, bytearray([move_data["id"], move_data["effect"], move_data["power"],
poke_data.type_ids[move_data["type"]], round(move_data["accuracy"] * 2.55), move_data["pp"]]), address) poke_data.type_ids[move_data["type"]], round(move_data["accuracy"] * 2.55), move_data["pp"]]), address)
TM_IDs = bytearray([poke_data.moves[move]["id"] for move in self.local_tms]) TM_IDs = bytearray([poke_data.moves[move]["id"] for move in world.local_tms])
write_bytes(data, TM_IDs, rom_addresses["TM_Moves"]) write_bytes(data, TM_IDs, rom_addresses["TM_Moves"])
if self.multiworld.randomize_rock_tunnel[self.player]: if world.options.randomize_rock_tunnel:
seed = randomize_rock_tunnel(data, random) seed = randomize_rock_tunnel(data, random)
write_bytes(data, encode_text(f"SEED: <LINE>{seed}"), rom_addresses["Text_Rock_Tunnel_Sign"]) write_bytes(data, encode_text(f"SEED: <LINE>{seed}"), rom_addresses["Text_Rock_Tunnel_Sign"])
@ -524,44 +568,44 @@ def generate_output(self, output_directory: str):
data[rom_addresses['Title_Mon_First']] = mons.pop() data[rom_addresses['Title_Mon_First']] = mons.pop()
for mon in range(0, 16): for mon in range(0, 16):
data[rom_addresses['Title_Mons'] + mon] = mons.pop() data[rom_addresses['Title_Mons'] + mon] = mons.pop()
if self.multiworld.game_version[self.player].value: if world.options.game_version.value:
mons.sort(key=lambda mon: 0 if mon == self.multiworld.get_location("Oak's Lab - Starter 1", self.player).item.name mons.sort(key=lambda mon: 0 if mon == world.multiworld.get_location("Oak's Lab - Starter 1", world.player).item.name
else 1 if mon == self.multiworld.get_location("Oak's Lab - Starter 2", self.player).item.name else else 1 if mon == world.multiworld.get_location("Oak's Lab - Starter 2", world.player).item.name else
2 if mon == self.multiworld.get_location("Oak's Lab - Starter 3", self.player).item.name else 3) 2 if mon == world.multiworld.get_location("Oak's Lab - Starter 3", world.player).item.name else 3)
else: else:
mons.sort(key=lambda mon: 0 if mon == self.multiworld.get_location("Oak's Lab - Starter 2", self.player).item.name mons.sort(key=lambda mon: 0 if mon == world.multiworld.get_location("Oak's Lab - Starter 2", world.player).item.name
else 1 if mon == self.multiworld.get_location("Oak's Lab - Starter 1", self.player).item.name else else 1 if mon == world.multiworld.get_location("Oak's Lab - Starter 1", world.player).item.name else
2 if mon == self.multiworld.get_location("Oak's Lab - Starter 3", self.player).item.name else 3) 2 if mon == world.multiworld.get_location("Oak's Lab - Starter 3", world.player).item.name else 3)
write_bytes(data, encode_text(self.multiworld.seed_name[-20:], 20, True), rom_addresses['Title_Seed']) write_bytes(data, encode_text(world.multiworld.seed_name[-20:], 20, True), rom_addresses['Title_Seed'])
slot_name = self.multiworld.player_name[self.player] slot_name = world.multiworld.player_name[world.player]
slot_name.replace("@", " ") slot_name.replace("@", " ")
slot_name.replace("<", " ") slot_name.replace("<", " ")
slot_name.replace(">", " ") slot_name.replace(">", " ")
write_bytes(data, encode_text(slot_name, 16, True, True), rom_addresses['Title_Slot_Name']) write_bytes(data, encode_text(slot_name, 16, True, True), rom_addresses['Title_Slot_Name'])
if self.trainer_name == "choose_in_game": if world.trainer_name == "choose_in_game":
data[rom_addresses["Skip_Player_Name"]] = 0 data[rom_addresses["Skip_Player_Name"]] = 0
else: else:
write_bytes(data, self.trainer_name, rom_addresses['Player_Name']) write_bytes(data, world.trainer_name, rom_addresses['Player_Name'])
if self.rival_name == "choose_in_game": if world.rival_name == "choose_in_game":
data[rom_addresses["Skip_Rival_Name"]] = 0 data[rom_addresses["Skip_Rival_Name"]] = 0
else: else:
write_bytes(data, self.rival_name, rom_addresses['Rival_Name']) write_bytes(data, world.rival_name, rom_addresses['Rival_Name'])
data[0xFF00] = 2 # client compatibility version data[0xFF00] = 2 # client compatibility version
rom_name = bytearray(f'AP{Utils.__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed:11}\0', rom_name = bytearray(f'AP{Utils.__version__.replace(".", "")[0:3]}_{world.player}_{world.multiworld.seed:11}\0',
'utf8')[:21] 'utf8')[:21]
rom_name.extend([0] * (21 - len(rom_name))) rom_name.extend([0] * (21 - len(rom_name)))
write_bytes(data, rom_name, 0xFFC6) write_bytes(data, rom_name, 0xFFC6)
write_bytes(data, self.multiworld.seed_name.encode(), 0xFFDB) write_bytes(data, world.multiworld.seed_name.encode(), 0xFFDB)
write_bytes(data, self.multiworld.player_name[self.player].encode(), 0xFFF0) write_bytes(data, world.multiworld.player_name[world.player].encode(), 0xFFF0)
self.finished_level_scaling.wait() world.finished_level_scaling.wait()
write_quizzes(self, data, random) write_quizzes(world, data, random)
for location in self.multiworld.get_locations(self.player): for location in world.multiworld.get_locations(world.player):
if location.party_data: if location.party_data:
for party in location.party_data: for party in location.party_data:
if not isinstance(party["party_address"], list): if not isinstance(party["party_address"], list):
@ -588,7 +632,7 @@ def generate_output(self, output_directory: str):
continue continue
elif location.rom_address is None: elif location.rom_address is None:
continue continue
if location.item and location.item.player == self.player: if location.item and location.item.player == world.player:
if location.rom_address: if location.rom_address:
rom_address = location.rom_address rom_address = location.rom_address
if not isinstance(rom_address, list): if not isinstance(rom_address, list):
@ -599,7 +643,7 @@ def generate_output(self, output_directory: str):
elif " ".join(location.item.name.split()[1:]) in poke_data.pokemon_data.keys(): elif " ".join(location.item.name.split()[1:]) in poke_data.pokemon_data.keys():
data[address] = poke_data.pokemon_data[" ".join(location.item.name.split()[1:])]["id"] data[address] = poke_data.pokemon_data[" ".join(location.item.name.split()[1:])]["id"]
else: else:
item_id = self.item_name_to_id[location.item.name] - 172000000 item_id = world.item_name_to_id[location.item.name] - 172000000
if item_id > 255: if item_id > 255:
item_id -= 256 item_id -= 256
data[address] = item_id data[address] = item_id
@ -613,18 +657,18 @@ def generate_output(self, output_directory: str):
for address in rom_address: for address in rom_address:
data[address] = 0x2C # AP Item data[address] = 0x2C # AP Item
outfilepname = f'_P{self.player}' outfilepname = f'_P{world.player}'
outfilepname += f"_{self.multiworld.get_file_safe_player_name(self.player).replace(' ', '_')}" \ outfilepname += f"_{world.multiworld.get_file_safe_player_name(world.player).replace(' ', '_')}" \
if self.multiworld.player_name[self.player] != 'Player%d' % self.player else '' if world.multiworld.player_name[world.player] != 'Player%d' % world.player else ''
rompath = os.path.join(output_directory, f'AP_{self.multiworld.seed_name}{outfilepname}.gb') rompath = os.path.join(output_directory, f'AP_{world.multiworld.seed_name}{outfilepname}.gb')
with open(rompath, 'wb') as outfile: with open(rompath, 'wb') as outfile:
outfile.write(data) outfile.write(data)
if self.multiworld.game_version[self.player].current_key == "red": if world.options.game_version.current_key == "red":
patch = RedDeltaPatch(os.path.splitext(rompath)[0] + RedDeltaPatch.patch_file_ending, player=self.player, patch = RedDeltaPatch(os.path.splitext(rompath)[0] + RedDeltaPatch.patch_file_ending, player=world.player,
player_name=self.multiworld.player_name[self.player], patched_path=rompath) player_name=world.multiworld.player_name[world.player], patched_path=rompath)
else: else:
patch = BlueDeltaPatch(os.path.splitext(rompath)[0] + BlueDeltaPatch.patch_file_ending, player=self.player, patch = BlueDeltaPatch(os.path.splitext(rompath)[0] + BlueDeltaPatch.patch_file_ending, player=world.player,
player_name=self.multiworld.player_name[self.player], patched_path=rompath) player_name=world.multiworld.player_name[world.player], patched_path=rompath)
patch.write() patch.write()
os.unlink(rompath) os.unlink(rompath)

View File

@ -1,10 +1,9 @@
rom_addresses = { rom_addresses = {
"Option_Encounter_Minimum_Steps": 0x3c1, "Option_Encounter_Minimum_Steps": 0x3c1,
"Option_Pitch_Black_Rock_Tunnel": 0x76a, "Option_Pitch_Black_Rock_Tunnel": 0x76a,
"Option_Blind_Trainers": 0x30d5, "Option_Blind_Trainers": 0x32f0,
"Option_Trainersanity1": 0x3165, "Option_Split_Card_Key": 0x3e19,
"Option_Split_Card_Key": 0x3e1e, "Option_Fix_Combat_Bugs": 0x3e1a,
"Option_Fix_Combat_Bugs": 0x3e1f,
"Option_Lose_Money": 0x40d4, "Option_Lose_Money": 0x40d4,
"Base_Stats_Mew": 0x4260, "Base_Stats_Mew": 0x4260,
"Title_Mon_First": 0x4373, "Title_Mon_First": 0x4373,
@ -115,9 +114,10 @@ rom_addresses = {
"HM_Strength_Badge_b": 0x131ed, "HM_Strength_Badge_b": 0x131ed,
"HM_Flash_Badge_a": 0x131fc, "HM_Flash_Badge_a": 0x131fc,
"HM_Flash_Badge_b": 0x13201, "HM_Flash_Badge_b": 0x13201,
"Trainer_Screen_Total_Key_Items": 0x135dc, "Tea_Key_Item_A": 0x135ac,
"TM_Moves": 0x137b1, "Trainer_Screen_Total_Key_Items": 0x1361b,
"Encounter_Chances": 0x13950, "TM_Moves": 0x137f0,
"Encounter_Chances": 0x1398f,
"Warps_CeladonCity": 0x18026, "Warps_CeladonCity": 0x18026,
"Warps_PalletTown": 0x182c7, "Warps_PalletTown": 0x182c7,
"Warps_ViridianCity": 0x18388, "Warps_ViridianCity": 0x18388,
@ -128,52 +128,54 @@ rom_addresses = {
"Option_Viridian_Gym_Badges": 0x1901d, "Option_Viridian_Gym_Badges": 0x1901d,
"Event_Sleepy_Guy": 0x191d1, "Event_Sleepy_Guy": 0x191d1,
"Option_Route3_Guard_B": 0x1928a, "Option_Route3_Guard_B": 0x1928a,
"Starter2_K": 0x19611, "Starter2_K": 0x19618,
"Starter3_K": 0x19619, "Starter3_K": 0x19620,
"Event_Rocket_Thief": 0x19733, "Event_Rocket_Thief": 0x1973a,
"Option_Cerulean_Cave_Badges": 0x19861, "Tea_Key_Item_C": 0x1988f,
"Option_Cerulean_Cave_Key_Items": 0x19868, "Option_Cerulean_Cave_Badges": 0x198a0,
"Text_Cerulean_Cave_Badges": 0x198d7, "Option_Cerulean_Cave_Key_Items": 0x198a7,
"Text_Cerulean_Cave_Key_Items": 0x198e5, "Text_Cerulean_Cave_Badges": 0x19916,
"Event_Stranded_Man": 0x19b3c, "Text_Cerulean_Cave_Key_Items": 0x19924,
"Event_Rivals_Sister": 0x19d0f, "Event_Stranded_Man": 0x19b7b,
"Warps_BluesHouse": 0x19d65, "Event_Rivals_Sister": 0x19d4e,
"Warps_VermilionTradeHouse": 0x19dbc, "Warps_BluesHouse": 0x19da4,
"Require_Pokedex_D": 0x19e53, "Warps_VermilionTradeHouse": 0x19dfb,
"Option_Elite_Four_Key_Items": 0x19e9d, "Require_Pokedex_D": 0x19e99,
"Option_Elite_Four_Pokedex": 0x19ea4, "Tea_Key_Item_B": 0x19f13,
"Option_Elite_Four_Badges": 0x19eab, "Option_Elite_Four_Key_Items": 0x19f1b,
"Text_Elite_Four_Badges": 0x19f47, "Option_Elite_Four_Pokedex": 0x19f22,
"Text_Elite_Four_Key_Items": 0x19f51, "Option_Elite_Four_Badges": 0x19f29,
"Text_Elite_Four_Pokedex": 0x19f64, "Text_Elite_Four_Badges": 0x19fc5,
"Shop10": 0x1a018, "Text_Elite_Four_Key_Items": 0x19fcf,
"Warps_IndigoPlateauLobby": 0x1a044, "Text_Elite_Four_Pokedex": 0x19fe2,
"Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_0_ITEM": 0x1a16c, "Shop10": 0x1a096,
"Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_1_ITEM": 0x1a17a, "Warps_IndigoPlateauLobby": 0x1a0c2,
"Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_2_ITEM": 0x1a188, "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_0_ITEM": 0x1a1ea,
"Event_SKC4F": 0x1a19b, "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_1_ITEM": 0x1a1f8,
"Warps_SilphCo4F": 0x1a21d, "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_2_ITEM": 0x1a206,
"Missable_Silph_Co_4F_Item_1": 0x1a25d, "Event_SKC4F": 0x1a219,
"Missable_Silph_Co_4F_Item_2": 0x1a264, "Warps_SilphCo4F": 0x1a29b,
"Missable_Silph_Co_4F_Item_3": 0x1a26b, "Missable_Silph_Co_4F_Item_1": 0x1a2db,
"Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_0_ITEM": 0x1a3c3, "Missable_Silph_Co_4F_Item_2": 0x1a2e2,
"Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_1_ITEM": 0x1a3d1, "Missable_Silph_Co_4F_Item_3": 0x1a2e9,
"Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_2_ITEM": 0x1a3df, "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_0_ITEM": 0x1a441,
"Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_3_ITEM": 0x1a3ed, "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_1_ITEM": 0x1a44f,
"Event_SKC5F": 0x1a400, "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_2_ITEM": 0x1a45d,
"Warps_SilphCo5F": 0x1a4aa, "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_3_ITEM": 0x1a46b,
"Missable_Silph_Co_5F_Item_1": 0x1a4f2, "Event_SKC5F": 0x1a47e,
"Missable_Silph_Co_5F_Item_2": 0x1a4f9, "Warps_SilphCo5F": 0x1a528,
"Missable_Silph_Co_5F_Item_3": 0x1a500, "Missable_Silph_Co_5F_Item_1": 0x1a570,
"Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_0_ITEM": 0x1a630, "Missable_Silph_Co_5F_Item_2": 0x1a577,
"Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_1_ITEM": 0x1a63e, "Missable_Silph_Co_5F_Item_3": 0x1a57e,
"Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_2_ITEM": 0x1a64c, "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_0_ITEM": 0x1a6ae,
"Event_SKC6F": 0x1a66d, "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_1_ITEM": 0x1a6bc,
"Warps_SilphCo6F": 0x1a74b, "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_2_ITEM": 0x1a6ca,
"Missable_Silph_Co_6F_Item_1": 0x1a79b, "Event_SKC6F": 0x1a6eb,
"Missable_Silph_Co_6F_Item_2": 0x1a7a2, "Warps_SilphCo6F": 0x1a7c9,
"Path_Pallet_Oak": 0x1a928, "Missable_Silph_Co_6F_Item_1": 0x1a819,
"Path_Pallet_Player": 0x1a935, "Missable_Silph_Co_6F_Item_2": 0x1a820,
"Path_Pallet_Oak": 0x1a9a6,
"Path_Pallet_Player": 0x1a9b3,
"Warps_CinnabarIsland": 0x1c026, "Warps_CinnabarIsland": 0x1c026,
"Warps_Route1": 0x1c0e9, "Warps_Route1": 0x1c0e9,
"Option_Extra_Key_Items_B": 0x1ca46, "Option_Extra_Key_Items_B": 0x1ca46,
@ -191,75 +193,75 @@ rom_addresses = {
"Starter2_E": 0x1d2f7, "Starter2_E": 0x1d2f7,
"Starter3_E": 0x1d2ff, "Starter3_E": 0x1d2ff,
"Event_Pokedex": 0x1d363, "Event_Pokedex": 0x1d363,
"Event_Oaks_Gift": 0x1d393, "Event_Oaks_Gift": 0x1d398,
"Starter2_P": 0x1d481, "Starter2_P": 0x1d486,
"Starter3_P": 0x1d489, "Starter3_P": 0x1d48e,
"Warps_OaksLab": 0x1d6af, "Warps_OaksLab": 0x1d6b4,
"Event_Pokemart_Quest": 0x1d76b, "Event_Pokemart_Quest": 0x1d770,
"Shop1": 0x1d795, "Shop1": 0x1d79a,
"Warps_ViridianMart": 0x1d7d8, "Warps_ViridianMart": 0x1d7dd,
"Warps_ViridianSchoolHouse": 0x1d82b, "Warps_ViridianSchoolHouse": 0x1d830,
"Warps_ViridianNicknameHouse": 0x1d889, "Warps_ViridianNicknameHouse": 0x1d88e,
"Warps_PewterNidoranHouse": 0x1d8e4, "Warps_PewterNidoranHouse": 0x1d8e9,
"Warps_PewterSpeechHouse": 0x1d927, "Warps_PewterSpeechHouse": 0x1d92c,
"Warps_CeruleanTrashedHouse": 0x1d98d, "Warps_CeruleanTrashedHouse": 0x1d992,
"Warps_CeruleanTradeHouse": 0x1d9de, "Warps_CeruleanTradeHouse": 0x1d9e3,
"Event_Bicycle_Shop": 0x1da2f, "Event_Bicycle_Shop": 0x1da34,
"Bike_Shop_Item_Display": 0x1da8a, "Bike_Shop_Item_Display": 0x1da8f,
"Warps_BikeShop": 0x1db45, "Warps_BikeShop": 0x1db4a,
"Event_Fuji": 0x1dbfd, "Event_Fuji": 0x1dc02,
"Warps_MrFujisHouse": 0x1dc44, "Warps_MrFujisHouse": 0x1dc49,
"Warps_LavenderCuboneHouse": 0x1dcc0, "Warps_LavenderCuboneHouse": 0x1dcc5,
"Warps_NameRatersHouse": 0x1ddae, "Warps_NameRatersHouse": 0x1ddb3,
"Warps_VermilionPidgeyHouse": 0x1ddf8, "Warps_VermilionPidgeyHouse": 0x1ddfd,
"Trainersanity_EVENT_BEAT_MEW_ITEM": 0x1de4e, "Trainersanity_EVENT_BEAT_MEW_ITEM": 0x1de53,
"Warps_VermilionDock": 0x1de70, "Warps_VermilionDock": 0x1de75,
"Static_Encounter_Mew": 0x1de7e, "Static_Encounter_Mew": 0x1de83,
"Gift_Eevee": 0x1def7, "Gift_Eevee": 0x1defc,
"Warps_CeladonMansionRoofHouse": 0x1df0e, "Warps_CeladonMansionRoofHouse": 0x1df13,
"Shop7": 0x1df49, "Shop7": 0x1df4e,
"Warps_FuchsiaMart": 0x1df74, "Warps_FuchsiaMart": 0x1df79,
"Warps_SaffronPidgeyHouse": 0x1dfdd, "Warps_SaffronPidgeyHouse": 0x1dfe2,
"Event_Mr_Psychic": 0x1e020, "Event_Mr_Psychic": 0x1e025,
"Warps_MrPsychicsHouse": 0x1e05d, "Warps_MrPsychicsHouse": 0x1e062,
"Warps_DiglettsCaveRoute2": 0x1e092, "Warps_DiglettsCaveRoute2": 0x1e097,
"Warps_Route2TradeHouse": 0x1e0da, "Warps_Route2TradeHouse": 0x1e0df,
"Warps_Route5Gate": 0x1e1db, "Warps_Route5Gate": 0x1e1e0,
"Warps_Route6Gate": 0x1e2ad, "Warps_Route6Gate": 0x1e2b2,
"Warps_Route7Gate": 0x1e383, "Warps_Route7Gate": 0x1e388,
"Warps_Route8Gate": 0x1e454, "Warps_Route8Gate": 0x1e459,
"Warps_UndergroundPathRoute8": 0x1e4a5, "Warps_UndergroundPathRoute8": 0x1e4aa,
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_0_ITEM": 0x1e511, "Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_0_ITEM": 0x1e516,
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_1_ITEM": 0x1e51f, "Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_1_ITEM": 0x1e524,
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_2_ITEM": 0x1e52d, "Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_2_ITEM": 0x1e532,
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_3_ITEM": 0x1e53b, "Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_3_ITEM": 0x1e540,
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_4_ITEM": 0x1e549, "Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_4_ITEM": 0x1e54e,
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_5_ITEM": 0x1e557, "Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_5_ITEM": 0x1e55c,
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_6_ITEM": 0x1e565, "Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_6_ITEM": 0x1e56a,
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_7_ITEM": 0x1e573, "Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_7_ITEM": 0x1e578,
"Trainersanity_EVENT_BEAT_ZAPDOS_ITEM": 0x1e581, "Trainersanity_EVENT_BEAT_ZAPDOS_ITEM": 0x1e586,
"Warps_PowerPlant": 0x1e5de, "Warps_PowerPlant": 0x1e5e3,
"Static_Encounter_Voltorb_A": 0x1e5f0, "Static_Encounter_Voltorb_A": 0x1e5f5,
"Static_Encounter_Voltorb_B": 0x1e5f8, "Static_Encounter_Voltorb_B": 0x1e5fd,
"Static_Encounter_Voltorb_C": 0x1e600, "Static_Encounter_Voltorb_C": 0x1e605,
"Static_Encounter_Electrode_A": 0x1e608, "Static_Encounter_Electrode_A": 0x1e60d,
"Static_Encounter_Voltorb_D": 0x1e610, "Static_Encounter_Voltorb_D": 0x1e615,
"Static_Encounter_Voltorb_E": 0x1e618, "Static_Encounter_Voltorb_E": 0x1e61d,
"Static_Encounter_Electrode_B": 0x1e620, "Static_Encounter_Electrode_B": 0x1e625,
"Static_Encounter_Voltorb_F": 0x1e628, "Static_Encounter_Voltorb_F": 0x1e62d,
"Static_Encounter_Zapdos": 0x1e630, "Static_Encounter_Zapdos": 0x1e635,
"Missable_Power_Plant_Item_1": 0x1e638, "Missable_Power_Plant_Item_1": 0x1e63d,
"Missable_Power_Plant_Item_2": 0x1e63f, "Missable_Power_Plant_Item_2": 0x1e644,
"Missable_Power_Plant_Item_3": 0x1e646, "Missable_Power_Plant_Item_3": 0x1e64b,
"Missable_Power_Plant_Item_4": 0x1e64d, "Missable_Power_Plant_Item_4": 0x1e652,
"Missable_Power_Plant_Item_5": 0x1e654, "Missable_Power_Plant_Item_5": 0x1e659,
"Warps_DiglettsCaveRoute11": 0x1e7e9, "Warps_DiglettsCaveRoute11": 0x1e7ee,
"Event_Rt16_House_Woman": 0x1e827, "Event_Rt16_House_Woman": 0x1e82c,
"Warps_Route16FlyHouse": 0x1e870, "Warps_Route16FlyHouse": 0x1e875,
"Option_Victory_Road_Badges": 0x1e8f3, "Option_Victory_Road_Badges": 0x1e8f8,
"Warps_Route22Gate": 0x1e9e3, "Warps_Route22Gate": 0x1e9e8,
"Event_Bill": 0x1eb24, "Event_Bill": 0x1eb29,
"Warps_BillsHouse": 0x1eb83, "Warps_BillsHouse": 0x1eb88,
"Starter1_O": 0x372b0, "Starter1_O": 0x372b0,
"Starter2_O": 0x372b4, "Starter2_O": 0x372b4,
"Starter3_O": 0x372b8, "Starter3_O": 0x372b8,
@ -1470,74 +1472,73 @@ rom_addresses = {
"Trainersanity_EVENT_BEAT_POKEMONTOWER_5_TRAINER_3_ITEM": 0x609ea, "Trainersanity_EVENT_BEAT_POKEMONTOWER_5_TRAINER_3_ITEM": 0x609ea,
"Warps_PokemonTower5F": 0x60a5e, "Warps_PokemonTower5F": 0x60a5e,
"Missable_Pokemon_Tower_5F_Item": 0x60a92, "Missable_Pokemon_Tower_5F_Item": 0x60a92,
"Option_Trainersanity2": 0x60b2a, "Ghost_Battle1": 0x60b93,
"Ghost_Battle1": 0x60b83, "Ghost_Battle_Level": 0x60b98,
"Ghost_Battle_Level": 0x60b88, "Trainersanity_EVENT_BEAT_POKEMONTOWER_6_TRAINER_0_ITEM": 0x60c35,
"Trainersanity_EVENT_BEAT_POKEMONTOWER_6_TRAINER_0_ITEM": 0x60c25, "Trainersanity_EVENT_BEAT_POKEMONTOWER_6_TRAINER_1_ITEM": 0x60c43,
"Trainersanity_EVENT_BEAT_POKEMONTOWER_6_TRAINER_1_ITEM": 0x60c33, "Trainersanity_EVENT_BEAT_POKEMONTOWER_6_TRAINER_2_ITEM": 0x60c51,
"Trainersanity_EVENT_BEAT_POKEMONTOWER_6_TRAINER_2_ITEM": 0x60c41, "Ghost_Battle2": 0x60c79,
"Ghost_Battle2": 0x60c69, "Warps_PokemonTower6F": 0x60cce,
"Warps_PokemonTower6F": 0x60cbe, "Missable_Pokemon_Tower_6F_Item_1": 0x60cf4,
"Missable_Pokemon_Tower_6F_Item_1": 0x60ce4, "Missable_Pokemon_Tower_6F_Item_2": 0x60cfb,
"Missable_Pokemon_Tower_6F_Item_2": 0x60ceb, "Entrance_Shuffle_Fuji_Warp": 0x60dfb,
"Entrance_Shuffle_Fuji_Warp": 0x60deb, "Trainersanity_EVENT_BEAT_POKEMONTOWER_7_TRAINER_0_ITEM": 0x60eef,
"Trainersanity_EVENT_BEAT_POKEMONTOWER_7_TRAINER_0_ITEM": 0x60edf, "Trainersanity_EVENT_BEAT_POKEMONTOWER_7_TRAINER_1_ITEM": 0x60efd,
"Trainersanity_EVENT_BEAT_POKEMONTOWER_7_TRAINER_1_ITEM": 0x60eed, "Trainersanity_EVENT_BEAT_POKEMONTOWER_7_TRAINER_2_ITEM": 0x60f0b,
"Trainersanity_EVENT_BEAT_POKEMONTOWER_7_TRAINER_2_ITEM": 0x60efb, "Warps_PokemonTower7F": 0x60f9b,
"Warps_PokemonTower7F": 0x60f8b, "Warps_CeladonMart1F": 0x61043,
"Warps_CeladonMart1F": 0x61033, "Gift_Aerodactyl": 0x61105,
"Gift_Aerodactyl": 0x610f5, "Gift_Omanyte": 0x61109,
"Gift_Omanyte": 0x610f9, "Gift_Kabuto": 0x6110d,
"Gift_Kabuto": 0x610fd, "Trainersanity_EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_0_ITEM": 0x61209,
"Trainersanity_EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_0_ITEM": 0x611de, "Trainersanity_EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_1_ITEM": 0x61217,
"Trainersanity_EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_1_ITEM": 0x611ec, "Trainersanity_EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_2_ITEM": 0x61225,
"Trainersanity_EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_2_ITEM": 0x611fa, "Warps_ViridianForest": 0x6129e,
"Warps_ViridianForest": 0x61273, "Missable_Viridian_Forest_Item_1": 0x612ec,
"Missable_Viridian_Forest_Item_1": 0x612c1, "Missable_Viridian_Forest_Item_2": 0x612f3,
"Missable_Viridian_Forest_Item_2": 0x612c8, "Missable_Viridian_Forest_Item_3": 0x612fa,
"Missable_Viridian_Forest_Item_3": 0x612cf, "Warps_SSAnne1F": 0x6133b,
"Warps_SSAnne1F": 0x61310, "Starter2_M": 0x61510,
"Starter2_M": 0x614e5, "Starter3_M": 0x61518,
"Starter3_M": 0x614ed, "Warps_SSAnne2F": 0x615d6,
"Warps_SSAnne2F": 0x615ab, "Warps_SSAnneB1F": 0x616f4,
"Warps_SSAnneB1F": 0x616c9, "Trainersanity_EVENT_BEAT_SS_ANNE_5_TRAINER_0_ITEM": 0x6179c,
"Trainersanity_EVENT_BEAT_SS_ANNE_5_TRAINER_0_ITEM": 0x61771, "Trainersanity_EVENT_BEAT_SS_ANNE_5_TRAINER_1_ITEM": 0x617aa,
"Trainersanity_EVENT_BEAT_SS_ANNE_5_TRAINER_1_ITEM": 0x6177f, "Warps_SSAnneBow": 0x617f1,
"Warps_SSAnneBow": 0x617c6, "Warps_SSAnneKitchen": 0x618e1,
"Warps_SSAnneKitchen": 0x618b6, "Event_SS_Anne_Captain": 0x61979,
"Event_SS_Anne_Captain": 0x6194e, "Warps_SSAnneCaptainsRoom": 0x61a00,
"Warps_SSAnneCaptainsRoom": 0x619d5, "Trainersanity_EVENT_BEAT_SS_ANNE_8_TRAINER_0_ITEM": 0x61a68,
"Trainersanity_EVENT_BEAT_SS_ANNE_8_TRAINER_0_ITEM": 0x61a3d, "Trainersanity_EVENT_BEAT_SS_ANNE_8_TRAINER_1_ITEM": 0x61a76,
"Trainersanity_EVENT_BEAT_SS_ANNE_8_TRAINER_1_ITEM": 0x61a4b, "Trainersanity_EVENT_BEAT_SS_ANNE_8_TRAINER_2_ITEM": 0x61a84,
"Trainersanity_EVENT_BEAT_SS_ANNE_8_TRAINER_2_ITEM": 0x61a59, "Trainersanity_EVENT_BEAT_SS_ANNE_8_TRAINER_3_ITEM": 0x61a92,
"Trainersanity_EVENT_BEAT_SS_ANNE_8_TRAINER_3_ITEM": 0x61a67, "Warps_SSAnne1FRooms": 0x61b22,
"Warps_SSAnne1FRooms": 0x61af7, "Missable_SS_Anne_1F_Item": 0x61b7e,
"Missable_SS_Anne_1F_Item": 0x61b53, "Trainersanity_EVENT_BEAT_SS_ANNE_9_TRAINER_0_ITEM": 0x61c4f,
"Trainersanity_EVENT_BEAT_SS_ANNE_9_TRAINER_0_ITEM": 0x61c24, "Trainersanity_EVENT_BEAT_SS_ANNE_9_TRAINER_1_ITEM": 0x61c5d,
"Trainersanity_EVENT_BEAT_SS_ANNE_9_TRAINER_1_ITEM": 0x61c32, "Trainersanity_EVENT_BEAT_SS_ANNE_9_TRAINER_2_ITEM": 0x61c6b,
"Trainersanity_EVENT_BEAT_SS_ANNE_9_TRAINER_2_ITEM": 0x61c40, "Trainersanity_EVENT_BEAT_SS_ANNE_9_TRAINER_3_ITEM": 0x61c79,
"Trainersanity_EVENT_BEAT_SS_ANNE_9_TRAINER_3_ITEM": 0x61c4e, "Warps_SSAnne2FRooms": 0x61d57,
"Warps_SSAnne2FRooms": 0x61d2c, "Missable_SS_Anne_2F_Item_1": 0x61db3,
"Missable_SS_Anne_2F_Item_1": 0x61d88, "Missable_SS_Anne_2F_Item_2": 0x61dc6,
"Missable_SS_Anne_2F_Item_2": 0x61d9b, "Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_0_ITEM": 0x61e57,
"Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_0_ITEM": 0x61e2c, "Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_1_ITEM": 0x61e65,
"Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_1_ITEM": 0x61e3a, "Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_2_ITEM": 0x61e73,
"Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_2_ITEM": 0x61e48, "Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_3_ITEM": 0x61e81,
"Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_3_ITEM": 0x61e56, "Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_4_ITEM": 0x61e8f,
"Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_4_ITEM": 0x61e64, "Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_5_ITEM": 0x61e9d,
"Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_5_ITEM": 0x61e72, "Warps_SSAnneB1FRooms": 0x61f4b,
"Warps_SSAnneB1FRooms": 0x61f20, "Missable_SS_Anne_B1F_Item_1": 0x61fb5,
"Missable_SS_Anne_B1F_Item_1": 0x61f8a, "Missable_SS_Anne_B1F_Item_2": 0x61fbc,
"Missable_SS_Anne_B1F_Item_2": 0x61f91, "Missable_SS_Anne_B1F_Item_3": 0x61fc3,
"Missable_SS_Anne_B1F_Item_3": 0x61f98, "Warps_UndergroundPathNorthSouth": 0x62000,
"Warps_UndergroundPathNorthSouth": 0x61fd5, "Warps_UndergroundPathWestEast": 0x62024,
"Warps_UndergroundPathWestEast": 0x61ff9, "Warps_DiglettsCave": 0x62048,
"Warps_DiglettsCave": 0x6201d, "Trainersanity_EVENT_BEAT_SILPH_CO_11F_TRAINER_0_ITEM": 0x62383,
"Trainersanity_EVENT_BEAT_SILPH_CO_11F_TRAINER_0_ITEM": 0x62358, "Trainersanity_EVENT_BEAT_SILPH_CO_11F_TRAINER_1_ITEM": 0x62391,
"Trainersanity_EVENT_BEAT_SILPH_CO_11F_TRAINER_1_ITEM": 0x62366, "Event_Silph_Co_President": 0x6239e,
"Event_Silph_Co_President": 0x62373, "Event_SKC11F": 0x623e8,
"Event_SKC11F": 0x623bd, "Warps_SilphCo11F": 0x62471,
"Warps_SilphCo11F": 0x62446,
"Ghost_Battle4": 0x708e1, "Ghost_Battle4": 0x708e1,
"Town_Map_Order": 0x70f0f, "Town_Map_Order": 0x70f0f,
"Town_Map_Coords": 0x71381, "Town_Map_Coords": 0x71381,
@ -1589,44 +1590,37 @@ rom_addresses = {
"Warps_FuchsiaMeetingRoom": 0x75879, "Warps_FuchsiaMeetingRoom": 0x75879,
"Badge_Cinnabar_Gym": 0x759de, "Badge_Cinnabar_Gym": 0x759de,
"Event_Cinnabar_Gym": 0x759f2, "Event_Cinnabar_Gym": 0x759f2,
"Option_Trainersanity4": 0x75ace, "Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_B_ITEM": 0x75adc,
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_B_ITEM": 0x75ada, "Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_A_ITEM": 0x75b2e,
"Option_Trainersanity3": 0x75b1e, "Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_2_ITEM": 0x75b97,
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_A_ITEM": 0x75b2a, "Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_3_ITEM": 0x75be9,
"Option_Trainersanity5": 0x75b85, "Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_4_ITEM": 0x75c3b,
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_2_ITEM": 0x75b91, "Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_5_ITEM": 0x75c8d,
"Option_Trainersanity6": 0x75bd5, "Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_6_ITEM": 0x75cdf,
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_3_ITEM": 0x75be1, "Warps_CinnabarGym": 0x75d29,
"Option_Trainersanity7": 0x75c25, "Warps_CinnabarLab": 0x75e10,
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_4_ITEM": 0x75c31, "Warps_CinnabarLabTradeRoom": 0x75ea2,
"Option_Trainersanity8": 0x75c75, "Event_Lab_Scientist": 0x75ef7,
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_5_ITEM": 0x75c81, "Warps_CinnabarLabMetronomeRoom": 0x75f43,
"Option_Trainersanity9": 0x75cc5, "Fossils_Needed_For_Second_Item": 0x75fc4,
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_6_ITEM": 0x75cd1, "Fossil_Level": 0x76025,
"Warps_CinnabarGym": 0x75d1b, "Event_Dome_Fossil_B": 0x7603f,
"Warps_CinnabarLab": 0x75e02, "Event_Helix_Fossil_B": 0x7605f,
"Warps_CinnabarLabTradeRoom": 0x75e94, "Warps_CinnabarLabFossilRoom": 0x760e0,
"Event_Lab_Scientist": 0x75ee9, "Warps_CinnabarPokecenter": 0x76136,
"Warps_CinnabarLabMetronomeRoom": 0x75f35, "Shop8": 0x7617d,
"Fossils_Needed_For_Second_Item": 0x75fb6, "Warps_CinnabarMart": 0x761a9,
"Fossil_Level": 0x76017, "Warps_CopycatsHouse1F": 0x761fb,
"Event_Dome_Fossil_B": 0x76031, "Starter2_N": 0x762b0,
"Event_Helix_Fossil_B": 0x76051, "Starter3_N": 0x762b8,
"Warps_CinnabarLabFossilRoom": 0x760d2, "Warps_ChampionsRoom": 0x764e3,
"Warps_CinnabarPokecenter": 0x76128, "Trainersanity_EVENT_BEAT_LORELEIS_ROOM_TRAINER_0_ITEM": 0x76612,
"Shop8": 0x7616f, "Warps_LoreleisRoom": 0x76636,
"Warps_CinnabarMart": 0x7619b, "Trainersanity_EVENT_BEAT_BRUNOS_ROOM_TRAINER_0_ITEM": 0x7676b,
"Warps_CopycatsHouse1F": 0x761ed, "Warps_BrunosRoom": 0x7678f,
"Starter2_N": 0x762a2, "Trainersanity_EVENT_BEAT_AGATHAS_ROOM_TRAINER_0_ITEM": 0x768ca,
"Starter3_N": 0x762aa, "Warps_AgathasRoom": 0x768ee,
"Warps_ChampionsRoom": 0x764d5, "Option_Itemfinder": 0x76a41,
"Trainersanity_EVENT_BEAT_LORELEIS_ROOM_TRAINER_0_ITEM": 0x76604,
"Warps_LoreleisRoom": 0x76628,
"Trainersanity_EVENT_BEAT_BRUNOS_ROOM_TRAINER_0_ITEM": 0x7675d,
"Warps_BrunosRoom": 0x76781,
"Trainersanity_EVENT_BEAT_AGATHAS_ROOM_TRAINER_0_ITEM": 0x768bc,
"Warps_AgathasRoom": 0x768e0,
"Option_Itemfinder": 0x76a33,
"Text_Quiz_A": 0x88806, "Text_Quiz_A": 0x88806,
"Text_Quiz_B": 0x8893a, "Text_Quiz_B": 0x8893a,
"Text_Quiz_C": 0x88a6e, "Text_Quiz_C": 0x88a6e,

View File

@ -3,7 +3,7 @@ from .items import item_groups
from . import logic from . import logic
def set_rules(multiworld, player): def set_rules(multiworld, world, player):
item_rules = { item_rules = {
# Some items do special things when they are passed into the GiveItem function in the game, but # Some items do special things when they are passed into the GiveItem function in the game, but
@ -15,54 +15,46 @@ def set_rules(multiworld, player):
not in i.name) not in i.name)
} }
if multiworld.prizesanity[player]: if world.options.prizesanity:
def prize_rule(i): def prize_rule(i):
return i.player != player or i.name in item_groups["Unique"] return i.player != player or i.name in item_groups["Unique"]
item_rules["Celadon Prize Corner - Item Prize 1"] = prize_rule item_rules["Celadon Prize Corner - Item Prize 1"] = prize_rule
item_rules["Celadon Prize Corner - Item Prize 2"] = prize_rule item_rules["Celadon Prize Corner - Item Prize 2"] = prize_rule
item_rules["Celadon Prize Corner - Item Prize 3"] = prize_rule item_rules["Celadon Prize Corner - Item Prize 3"] = prize_rule
if multiworld.accessibility[player] != "full":
multiworld.get_location("Cerulean Bicycle Shop", player).always_allow = (lambda state, item:
item.name == "Bike Voucher"
and item.player == player)
multiworld.get_location("Fuchsia Warden's House - Safari Zone Warden", player).always_allow = (lambda state, item:
item.name == "Gold Teeth" and
item.player == player)
access_rules = { access_rules = {
"Rival's House - Rival's Sister": lambda state: state.has("Oak's Parcel", player), "Rival's House - Rival's Sister": lambda state: state.has("Oak's Parcel", player),
"Oak's Lab - Oak's Post-Route-22-Rival Gift": lambda state: state.has("Oak's Parcel", player), "Oak's Lab - Oak's Post-Route-22-Rival Gift": lambda state: state.has("Oak's Parcel", player),
"Viridian City - Sleepy Guy": lambda state: logic.can_cut(state, player) or logic.can_surf(state, player), "Viridian City - Sleepy Guy": lambda state: logic.can_cut(state, world, player) or logic.can_surf(state, world, player),
"Route 2 Gate - Oak's Aide": lambda state: logic.oaks_aide(state, state.multiworld.oaks_aide_rt_2[player].value + 5, player), "Route 2 Gate - Oak's Aide": lambda state: logic.oaks_aide(state, world, world.options.oaks_aide_rt_2.value + 5, player),
"Cerulean Bicycle Shop": lambda state: state.has("Bike Voucher", player) "Cerulean Bicycle Shop": lambda state: state.has("Bike Voucher", player)
or location_item_name(state, "Cerulean Bicycle Shop", player) == ("Bike Voucher", player), or location_item_name(state, "Cerulean Bicycle Shop", player) == ("Bike Voucher", player),
"Lavender Mr. Fuji's House - Mr. Fuji": lambda state: state.has("Fuji Saved", player), "Lavender Mr. Fuji's House - Mr. Fuji": lambda state: state.has("Fuji Saved", player),
"Route 11 Gate 2F - Oak's Aide": lambda state: logic.oaks_aide(state, state.multiworld.oaks_aide_rt_11[player].value + 5, player), "Route 11 Gate 2F - Oak's Aide": lambda state: logic.oaks_aide(state, world, world.options.oaks_aide_rt_11.value + 5, player),
"Celadon City - Stranded Man": lambda state: logic.can_surf(state, player), "Celadon City - Stranded Man": lambda state: logic.can_surf(state, world, player),
"Fuchsia Warden's House - Safari Zone Warden": lambda state: state.has("Gold Teeth", player) "Fuchsia Warden's House - Safari Zone Warden": lambda state: state.has("Gold Teeth", player)
or location_item_name(state, "Fuchsia Warden's House - Safari Zone Warden", player) == ("Gold Teeth", player), or location_item_name(state, "Fuchsia Warden's House - Safari Zone Warden", player) == ("Gold Teeth", player),
"Route 12 - Island Item": lambda state: logic.can_surf(state, player), "Route 12 - Island Item": lambda state: logic.can_surf(state, world, player),
"Route 15 Gate 2F - Oak's Aide": lambda state: logic.oaks_aide(state, state.multiworld.oaks_aide_rt_15[player].value + 5, player), "Route 15 Gate 2F - Oak's Aide": lambda state: logic.oaks_aide(state, world, world.options.oaks_aide_rt_15.value + 5, player),
"Route 25 - Item": lambda state: logic.can_cut(state, player), "Route 25 - Item": lambda state: logic.can_cut(state, world, player),
"Fuchsia Warden's House - Behind Boulder Item": lambda state: logic.can_strength(state, player), "Fuchsia Warden's House - Behind Boulder Item": lambda state: logic.can_strength(state, world, player),
"Safari Zone Center - Island Item": lambda state: logic.can_surf(state, player), "Safari Zone Center - Island Item": lambda state: logic.can_surf(state, world, player),
"Saffron Copycat's House 2F - Copycat": lambda state: state.has("Buy Poke Doll", player), "Saffron Copycat's House 2F - Copycat": lambda state: state.has("Buy Poke Doll", player),
"Celadon Game Corner - West Gambler's Gift": lambda state: state.has("Coin Case", player), "Celadon Game Corner - West Gambler's Gift": lambda state: state.has("Coin Case", player),
"Celadon Game Corner - Center Gambler's Gift": lambda state: state.has("Coin Case", player), "Celadon Game Corner - Center Gambler's Gift": lambda state: state.has("Coin Case", player),
"Celadon Game Corner - East Gambler's Gift": lambda state: state.has("Coin Case", player), "Celadon Game Corner - East Gambler's Gift": lambda state: state.has("Coin Case", player),
"Celadon Game Corner - Hidden Item Northwest By Counter": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player), "Celadon Game Corner - Hidden Item Northwest By Counter": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
"Celadon Game Corner - Hidden Item Southwest Corner": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player), "Celadon Game Corner - Hidden Item Southwest Corner": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
"Celadon Game Corner - Hidden Item Near Rumor Man": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player), "Celadon Game Corner - Hidden Item Near Rumor Man": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
"Celadon Game Corner - Hidden Item Near Speculating Woman": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player), "Celadon Game Corner - Hidden Item Near Speculating Woman": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
"Celadon Game Corner - Hidden Item Near West Gifting Gambler": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player), "Celadon Game Corner - Hidden Item Near West Gifting Gambler": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
"Celadon Game Corner - Hidden Item Near Wonderful Time Woman": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player), "Celadon Game Corner - Hidden Item Near Wonderful Time Woman": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
"Celadon Game Corner - Hidden Item Near Failing Gym Information Guy": lambda state: state.has( "Coin Case", player) and logic.can_get_hidden_items(state, player), "Celadon Game Corner - Hidden Item Near Failing Gym Information Guy": lambda state: state.has( "Coin Case", player) and logic.can_get_hidden_items(state, world, player),
"Celadon Game Corner - Hidden Item Near East Gifting Gambler": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player), "Celadon Game Corner - Hidden Item Near East Gifting Gambler": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
"Celadon Game Corner - Hidden Item Near Hooked Guy": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player), "Celadon Game Corner - Hidden Item Near Hooked Guy": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
"Celadon Game Corner - Hidden Item at End of Horizontal Machine Row": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player), "Celadon Game Corner - Hidden Item at End of Horizontal Machine Row": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
"Celadon Game Corner - Hidden Item in Front of Horizontal Machine Row": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player), "Celadon Game Corner - Hidden Item in Front of Horizontal Machine Row": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
"Celadon Prize Corner - Item Prize 1": lambda state: state.has("Coin Case", player) and state.has("Game Corner", player), "Celadon Prize Corner - Item Prize 1": lambda state: state.has("Coin Case", player) and state.has("Game Corner", player),
"Celadon Prize Corner - Item Prize 2": lambda state: state.has("Coin Case", player) and state.has("Game Corner", player), "Celadon Prize Corner - Item Prize 2": lambda state: state.has("Coin Case", player) and state.has("Game Corner", player),
@ -79,9 +71,9 @@ def set_rules(multiworld, player):
"Cinnabar Lab Fossil Room - Dome Fossil Pokemon": lambda state: state.has("Dome Fossil", player) and state.has("Cinnabar Island", player), "Cinnabar Lab Fossil Room - Dome Fossil Pokemon": lambda state: state.has("Dome Fossil", player) and state.has("Cinnabar Island", player),
"Route 12 - Sleeping Pokemon": lambda state: state.has("Poke Flute", player), "Route 12 - Sleeping Pokemon": lambda state: state.has("Poke Flute", player),
"Route 16 - Sleeping Pokemon": lambda state: state.has("Poke Flute", player), "Route 16 - Sleeping Pokemon": lambda state: state.has("Poke Flute", player),
"Seafoam Islands B4F - Legendary Pokemon": lambda state: logic.can_strength(state, player) and state.has("Seafoam Boss Boulders", player), "Seafoam Islands B4F - Legendary Pokemon": lambda state: logic.can_strength(state, world, player) and state.has("Seafoam Boss Boulders", player),
"Vermilion Dock - Legendary Pokemon": lambda state: logic.can_surf(state, player), "Vermilion Dock - Legendary Pokemon": lambda state: logic.can_surf(state, world, player),
"Cerulean Cave B1F - Legendary Pokemon": lambda state: logic.can_surf(state, player), "Cerulean Cave B1F - Legendary Pokemon": lambda state: logic.can_surf(state, world, player),
**{f"Pokemon Tower {floor}F - Wild Pokemon - {slot}": lambda state: state.has("Silph Scope", player) for floor in range(3, 8) for slot in range(1, 11)}, **{f"Pokemon Tower {floor}F - Wild Pokemon - {slot}": lambda state: state.has("Silph Scope", player) for floor in range(3, 8) for slot in range(1, 11)},
"Pokemon Tower 6F - Restless Soul": lambda state: state.has("Silph Scope", player), # just for level scaling "Pokemon Tower 6F - Restless Soul": lambda state: state.has("Silph Scope", player), # just for level scaling
@ -103,101 +95,101 @@ def set_rules(multiworld, player):
"Route 22 - Trainer Parties": lambda state: state.has("Oak's Parcel", player), "Route 22 - Trainer Parties": lambda state: state.has("Oak's Parcel", player),
# # Rock Tunnel # # Rock Tunnel
"Rock Tunnel 1F - PokeManiac": lambda state: logic.rock_tunnel(state, player), "Rock Tunnel 1F - PokeManiac": lambda state: logic.rock_tunnel(state, world, player),
"Rock Tunnel 1F - Hiker 1": lambda state: logic.rock_tunnel(state, player), "Rock Tunnel 1F - Hiker 1": lambda state: logic.rock_tunnel(state, world, player),
"Rock Tunnel 1F - Hiker 2": lambda state: logic.rock_tunnel(state, player), "Rock Tunnel 1F - Hiker 2": lambda state: logic.rock_tunnel(state, world, player),
"Rock Tunnel 1F - Hiker 3": lambda state: logic.rock_tunnel(state, player), "Rock Tunnel 1F - Hiker 3": lambda state: logic.rock_tunnel(state, world, player),
"Rock Tunnel 1F - Jr. Trainer F 1": lambda state: logic.rock_tunnel(state, player), "Rock Tunnel 1F - Jr. Trainer F 1": lambda state: logic.rock_tunnel(state, world, player),
"Rock Tunnel 1F - Jr. Trainer F 2": lambda state: logic.rock_tunnel(state, player), "Rock Tunnel 1F - Jr. Trainer F 2": lambda state: logic.rock_tunnel(state, world, player),
"Rock Tunnel 1F - Jr. Trainer F 3": lambda state: logic.rock_tunnel(state, player), "Rock Tunnel 1F - Jr. Trainer F 3": lambda state: logic.rock_tunnel(state, world, player),
"Rock Tunnel B1F - PokeManiac 1": lambda state: logic.rock_tunnel(state, player), "Rock Tunnel B1F - PokeManiac 1": lambda state: logic.rock_tunnel(state, world, player),
"Rock Tunnel B1F - PokeManiac 2": lambda state: logic.rock_tunnel(state, player), "Rock Tunnel B1F - PokeManiac 2": lambda state: logic.rock_tunnel(state, world, player),
"Rock Tunnel B1F - PokeManiac 3": lambda state: logic.rock_tunnel(state, player), "Rock Tunnel B1F - PokeManiac 3": lambda state: logic.rock_tunnel(state, world, player),
"Rock Tunnel B1F - Jr. Trainer F 1": lambda state: logic.rock_tunnel(state, player), "Rock Tunnel B1F - Jr. Trainer F 1": lambda state: logic.rock_tunnel(state, world, player),
"Rock Tunnel B1F - Jr. Trainer F 2": lambda state: logic.rock_tunnel(state, player), "Rock Tunnel B1F - Jr. Trainer F 2": lambda state: logic.rock_tunnel(state, world, player),
"Rock Tunnel B1F - Hiker 1": lambda state: logic.rock_tunnel(state, player), "Rock Tunnel B1F - Hiker 1": lambda state: logic.rock_tunnel(state, world, player),
"Rock Tunnel B1F - Hiker 2": lambda state: logic.rock_tunnel(state, player), "Rock Tunnel B1F - Hiker 2": lambda state: logic.rock_tunnel(state, world, player),
"Rock Tunnel B1F - Hiker 3": lambda state: logic.rock_tunnel(state, player), "Rock Tunnel B1F - Hiker 3": lambda state: logic.rock_tunnel(state, world, player),
"Rock Tunnel B1F - North Item": lambda state: logic.rock_tunnel(state, player), "Rock Tunnel B1F - North Item": lambda state: logic.rock_tunnel(state, world, player),
"Rock Tunnel B1F - Northwest Item": lambda state: logic.rock_tunnel(state, player), "Rock Tunnel B1F - Northwest Item": lambda state: logic.rock_tunnel(state, world, player),
"Rock Tunnel B1F - Southwest Item": lambda state: logic.rock_tunnel(state, player), "Rock Tunnel B1F - Southwest Item": lambda state: logic.rock_tunnel(state, world, player),
"Rock Tunnel B1F - West Item": lambda state: logic.rock_tunnel(state, player), "Rock Tunnel B1F - West Item": lambda state: logic.rock_tunnel(state, world, player),
# Pokédex check # Pokédex check
"Oak's Lab - Oak's Parcel Reward": lambda state: state.has("Oak's Parcel", player), "Oak's Lab - Oak's Parcel Reward": lambda state: state.has("Oak's Parcel", player),
# Hidden items # Hidden items
"Viridian Forest - Hidden Item Northwest by Trainer": lambda state: logic.can_get_hidden_items(state, "Viridian Forest - Hidden Item Northwest by Trainer": lambda state: logic.can_get_hidden_items(state, world,
player), player),
"Viridian Forest - Hidden Item Entrance Tree": lambda state: logic.can_get_hidden_items(state, player), "Viridian Forest - Hidden Item Entrance Tree": lambda state: logic.can_get_hidden_items(state, world, player),
"Mt Moon B2F - Hidden Item Dead End Before Fossils": lambda state: logic.can_get_hidden_items(state, "Mt Moon B2F - Hidden Item Dead End Before Fossils": lambda state: logic.can_get_hidden_items(state, world,
player), player),
"Route 25 - Hidden Item Fence Outside Bill's House": lambda state: logic.can_get_hidden_items(state, "Route 25 - Hidden Item Fence Outside Bill's House": lambda state: logic.can_get_hidden_items(state, world,
player), player),
"Route 9 - Hidden Item Bush By Grass": lambda state: logic.can_get_hidden_items(state, player), "Route 9 - Hidden Item Bush By Grass": lambda state: logic.can_get_hidden_items(state, world, player),
"S.S. Anne Kitchen - Hidden Item Kitchen Trash": lambda state: logic.can_get_hidden_items(state, player), "S.S. Anne Kitchen - Hidden Item Kitchen Trash": lambda state: logic.can_get_hidden_items(state, world, player),
"S.S. Anne B1F Rooms - Hidden Item Under Pillow": lambda state: logic.can_get_hidden_items(state, player), "S.S. Anne B1F Rooms - Hidden Item Under Pillow": lambda state: logic.can_get_hidden_items(state, world, player),
"Route 10 - Hidden Item Behind Rock Tunnel Entrance Cuttable Tree": lambda "Route 10 - Hidden Item Behind Rock Tunnel Entrance Cuttable Tree": lambda
state: logic.can_get_hidden_items(state, player) and logic.can_cut(state, player), state: logic.can_get_hidden_items(state, world, player) and logic.can_cut(state, world, player),
"Route 10 - Hidden Item Bush": lambda state: logic.can_get_hidden_items(state, player), "Route 10 - Hidden Item Bush": lambda state: logic.can_get_hidden_items(state, world, player),
"Rocket Hideout B1F - Hidden Item Pot Plant": lambda state: logic.can_get_hidden_items(state, player), "Rocket Hideout B1F - Hidden Item Pot Plant": lambda state: logic.can_get_hidden_items(state, world, player),
"Rocket Hideout B3F - Hidden Item Near East Item": lambda state: logic.can_get_hidden_items(state, player), "Rocket Hideout B3F - Hidden Item Near East Item": lambda state: logic.can_get_hidden_items(state, world, player),
"Rocket Hideout B4F - Hidden Item Behind Giovanni": lambda state: "Rocket Hideout B4F - Hidden Item Behind Giovanni": lambda state:
logic.can_get_hidden_items(state, player), logic.can_get_hidden_items(state, world, player),
"Pokemon Tower 5F - Hidden Item Near West Staircase": lambda state: logic.can_get_hidden_items(state, "Pokemon Tower 5F - Hidden Item Near West Staircase": lambda state: logic.can_get_hidden_items(state, world,
player), player),
"Route 13 - Hidden Item Dead End Bush": lambda state: logic.can_get_hidden_items(state, player), "Route 13 - Hidden Item Dead End Bush": lambda state: logic.can_get_hidden_items(state, world, player),
"Route 13 - Hidden Item Dead End By Water Corner": lambda state: logic.can_get_hidden_items(state, player), "Route 13 - Hidden Item Dead End By Water Corner": lambda state: logic.can_get_hidden_items(state, world, player),
"Pokemon Mansion B1F - Hidden Item Secret Key Room Corner": lambda state: logic.can_get_hidden_items(state, "Pokemon Mansion B1F - Hidden Item Secret Key Room Corner": lambda state: logic.can_get_hidden_items(state, world,
player), player),
"Safari Zone West - Hidden Item Secret House Statue": lambda state: logic.can_get_hidden_items(state, "Safari Zone West - Hidden Item Secret House Statue": lambda state: logic.can_get_hidden_items(state, world,
player), player),
"Silph Co 5F - Hidden Item Pot Plant": lambda state: logic.can_get_hidden_items(state, player), "Silph Co 5F - Hidden Item Pot Plant": lambda state: logic.can_get_hidden_items(state, world, player),
"Silph Co 9F - Hidden Item Nurse Bed": lambda state: logic.can_get_hidden_items(state, player), "Silph Co 9F - Hidden Item Nurse Bed": lambda state: logic.can_get_hidden_items(state, world, player),
"Saffron Copycat's House 2F - Hidden Item Desk": lambda state: logic.can_get_hidden_items(state, player), "Saffron Copycat's House 2F - Hidden Item Desk": lambda state: logic.can_get_hidden_items(state, world, player),
"Cerulean Cave 1F - Hidden Item Center Rocks": lambda state: logic.can_get_hidden_items(state, player), "Cerulean Cave 1F - Hidden Item Center Rocks": lambda state: logic.can_get_hidden_items(state, world, player),
"Cerulean Cave B1F - Hidden Item Northeast Rocks": lambda state: logic.can_get_hidden_items(state, player), "Cerulean Cave B1F - Hidden Item Northeast Rocks": lambda state: logic.can_get_hidden_items(state, world, player),
"Power Plant - Hidden Item Central Dead End": lambda state: logic.can_get_hidden_items(state, player), "Power Plant - Hidden Item Central Dead End": lambda state: logic.can_get_hidden_items(state, world, player),
"Power Plant - Hidden Item Before Zapdos": lambda state: logic.can_get_hidden_items(state, player), "Power Plant - Hidden Item Before Zapdos": lambda state: logic.can_get_hidden_items(state, world, player),
"Seafoam Islands B2F - Hidden Item Rock": lambda state: logic.can_get_hidden_items(state, player), "Seafoam Islands B2F - Hidden Item Rock": lambda state: logic.can_get_hidden_items(state, world, player),
"Seafoam Islands B3F - Hidden Item Rock": lambda state: logic.can_get_hidden_items(state, player), "Seafoam Islands B3F - Hidden Item Rock": lambda state: logic.can_get_hidden_items(state, world, player),
# if you can reach any exit boulders, that means you can drop into the water tunnel and auto-surf # if you can reach any exit boulders, that means you can drop into the water tunnel and auto-surf
"Seafoam Islands B4F - Hidden Item Corner Island": lambda state: logic.can_get_hidden_items(state, player), "Seafoam Islands B4F - Hidden Item Corner Island": lambda state: logic.can_get_hidden_items(state, world, player),
"Pokemon Mansion 1F - Hidden Item Block Near Entrance Carpet": lambda "Pokemon Mansion 1F - Hidden Item Block Near Entrance Carpet": lambda
state: logic.can_get_hidden_items(state, player), state: logic.can_get_hidden_items(state, world, player),
"Pokemon Mansion 3F - Hidden Item Behind Burglar": lambda state: logic.can_get_hidden_items(state, player), "Pokemon Mansion 3F - Hidden Item Behind Burglar": lambda state: logic.can_get_hidden_items(state, world, player),
"Route 23 - Hidden Item Rocks Before Victory Road": lambda state: logic.can_get_hidden_items(state, "Route 23 - Hidden Item Rocks Before Victory Road": lambda state: logic.can_get_hidden_items(state, world,
player), player),
"Route 23 - Hidden Item East Bush After Water": lambda state: logic.can_get_hidden_items(state, "Route 23 - Hidden Item East Bush After Water": lambda state: logic.can_get_hidden_items(state, world,
player), player),
"Route 23 - Hidden Item On Island": lambda state: logic.can_get_hidden_items(state, "Route 23 - Hidden Item On Island": lambda state: logic.can_get_hidden_items(state, world,
player) and logic.can_surf(state, player), player) and logic.can_surf(state, world, player),
"Victory Road 2F - Hidden Item Rock Before Moltres": lambda state: logic.can_get_hidden_items(state, "Victory Road 2F - Hidden Item Rock Before Moltres": lambda state: logic.can_get_hidden_items(state, world,
player), player),
"Victory Road 2F - Hidden Item Rock In Final Room": lambda state: logic.can_get_hidden_items(state, player), "Victory Road 2F - Hidden Item Rock In Final Room": lambda state: logic.can_get_hidden_items(state, world, player),
"Viridian City - Hidden Item Cuttable Tree": lambda state: logic.can_get_hidden_items(state, player), "Viridian City - Hidden Item Cuttable Tree": lambda state: logic.can_get_hidden_items(state, world, player),
"Route 11 - Hidden Item Isolated Bush Near Gate": lambda state: logic.can_get_hidden_items(state, player), "Route 11 - Hidden Item Isolated Bush Near Gate": lambda state: logic.can_get_hidden_items(state, world, player),
"Route 12 - Hidden Item Bush Near Gate": lambda state: logic.can_get_hidden_items(state, player), "Route 12 - Hidden Item Bush Near Gate": lambda state: logic.can_get_hidden_items(state, world, player),
"Route 17 - Hidden Item In Grass": lambda state: logic.can_get_hidden_items(state, player), "Route 17 - Hidden Item In Grass": lambda state: logic.can_get_hidden_items(state, world, player),
"Route 17 - Hidden Item Near Northernmost Sign": lambda state: logic.can_get_hidden_items(state, player), "Route 17 - Hidden Item Near Northernmost Sign": lambda state: logic.can_get_hidden_items(state, world, player),
"Route 17 - Hidden Item East Center": lambda state: logic.can_get_hidden_items(state, player), "Route 17 - Hidden Item East Center": lambda state: logic.can_get_hidden_items(state, world, player),
"Route 17 - Hidden Item West Center": lambda state: logic.can_get_hidden_items(state, player), "Route 17 - Hidden Item West Center": lambda state: logic.can_get_hidden_items(state, world, player),
"Route 17 - Hidden Item Before Final Bridge": lambda state: logic.can_get_hidden_items(state, player), "Route 17 - Hidden Item Before Final Bridge": lambda state: logic.can_get_hidden_items(state, world, player),
"Underground Path North South - Hidden Item Near Northern Stairs": lambda "Underground Path North South - Hidden Item Near Northern Stairs": lambda
state: logic.can_get_hidden_items(state, player), state: logic.can_get_hidden_items(state, world, player),
"Underground Path North South - Hidden Item Near Southern Stairs": lambda "Underground Path North South - Hidden Item Near Southern Stairs": lambda
state: logic.can_get_hidden_items(state, player), state: logic.can_get_hidden_items(state, world, player),
"Underground Path West East - Hidden Item West": lambda state: logic.can_get_hidden_items(state, player), "Underground Path West East - Hidden Item West": lambda state: logic.can_get_hidden_items(state, world, player),
"Underground Path West East - Hidden Item East": lambda state: logic.can_get_hidden_items(state, player), "Underground Path West East - Hidden Item East": lambda state: logic.can_get_hidden_items(state, world, player),
"Celadon City - Hidden Item Dead End Near Cuttable Tree": lambda state: logic.can_get_hidden_items(state, "Celadon City - Hidden Item Dead End Near Cuttable Tree": lambda state: logic.can_get_hidden_items(state, world,
player), player),
"Route 25 - Hidden Item Northeast Of Grass": lambda state: logic.can_get_hidden_items(state, player), "Route 25 - Hidden Item Northeast Of Grass": lambda state: logic.can_get_hidden_items(state, world, player),
"Mt Moon B2F - Hidden Item Lone Rock": lambda state: logic.can_get_hidden_items(state, player), "Mt Moon B2F - Hidden Item Lone Rock": lambda state: logic.can_get_hidden_items(state, world, player),
"Vermilion City - Hidden Item In Water Near Fan Club": lambda state: logic.can_get_hidden_items(state, "Vermilion City - Hidden Item In Water Near Fan Club": lambda state: logic.can_get_hidden_items(state, world,
player) and logic.can_surf(state, player), player) and logic.can_surf(state, world, player),
"Cerulean City - Hidden Item Gym Badge Guy's Backyard": lambda state: logic.can_get_hidden_items(state, "Cerulean City - Hidden Item Gym Badge Guy's Backyard": lambda state: logic.can_get_hidden_items(state, world,
player), player),
"Route 4 - Hidden Item Plateau East Of Mt Moon": lambda state: logic.can_get_hidden_items(state, player), "Route 4 - Hidden Item Plateau East Of Mt Moon": lambda state: logic.can_get_hidden_items(state, world, player),
# Evolutions # Evolutions
"Evolution - Ivysaur": lambda state: state.has("Bulbasaur", player) and logic.evolve_level(state, 16, player), "Evolution - Ivysaur": lambda state: state.has("Bulbasaur", player) and logic.evolve_level(state, 16, player),
@ -281,5 +273,4 @@ def set_rules(multiworld, player):
if loc.name.startswith("Pokedex"): if loc.name.startswith("Pokedex"):
mon = loc.name.split(" - ")[1] mon = loc.name.split(" - ")[1]
add_rule(loc, lambda state, i=mon: (state.has("Pokedex", player) or not add_rule(loc, lambda state, i=mon: (state.has("Pokedex", player) or not
state.multiworld.require_pokedex[player]) and (state.has(i, player) world.options.require_pokedex) and (state.has(i, player) or state.has(f"Static {i}", player)))
or state.has(f"Static {i}", player)))