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
15 changed files with 1435 additions and 1335 deletions

View File

@@ -3,6 +3,7 @@ import settings
import typing
import threading
import base64
import random
from copy import deepcopy
from typing import TextIO
@@ -14,7 +15,7 @@ from worlds.generic.Rules import add_item_rule
from .items import item_table, item_groups
from .locations import location_data, PokemonRBLocation
from .regions import create_regions
from .options import pokemon_rb_options
from .options import PokemonRBOptions
from .rom_addresses import rom_addresses
from .text import encode_text
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!"""
# -MuffinJets#4559
game = "Pokemon Red and Blue"
option_definitions = pokemon_rb_options
options_dataclass = PokemonRBOptions
options: PokemonRBOptions
settings: typing.ClassVar[PokemonSettings]
required_client_version = (0, 4, 2)
@@ -85,8 +89,8 @@ class PokemonRedBlueWorld(World):
web = PokemonWebWorld()
def __init__(self, world: MultiWorld, player: int):
super().__init__(world, player)
def __init__(self, multiworld: MultiWorld, player: int):
super().__init__(multiworld, player)
self.item_pool = []
self.total_key_items = None
self.fly_map = None
@@ -101,11 +105,11 @@ class PokemonRedBlueWorld(World):
self.learnsets = None
self.trainer_name = None
self.rival_name = None
self.type_chart = None
self.traps = None
self.trade_mons = {}
self.finished_level_scaling = threading.Event()
self.dexsanity_table = []
self.trainersanity_table = []
self.local_locs = []
@classmethod
@@ -113,11 +117,109 @@ class PokemonRedBlueWorld(World):
versions = set()
for player in multiworld.player_ids:
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:
if not os.path.exists(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 encode_name(name, t):
try:
@@ -126,33 +228,33 @@ class PokemonRedBlueWorld(World):
return encode_text(name, length=8, whitespace="@", safety=True)
except KeyError as 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"
else:
self.trainer_name = encode_name(self.multiworld.trainer_name[self.player].value, "Player")
if self.multiworld.rival_name[self.player] == "choose_in_game":
self.trainer_name = encode_name(self.options.trainer_name.value, "Player")
if self.options.rival_name == "choose_in_game":
self.rival_name = "choose_in_game"
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]:
self.multiworld.non_local_items[self.player].value -= self.item_name_groups["Badges"]
if not self.options.badgesanity:
self.options.non_local_items.value -= self.item_name_groups["Badges"]
if self.multiworld.key_items_only[self.player]:
self.multiworld.trainersanity[self.player] = self.multiworld.trainersanity[self.player].from_text("off")
self.multiworld.dexsanity[self.player].value = 0
self.multiworld.randomize_hidden_items[self.player] = \
self.multiworld.randomize_hidden_items[self.player].from_text("off")
if self.options.key_items_only:
self.options.trainersanity.value = 0
self.options.dexsanity.value = 0
self.options.randomize_hidden_items = \
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"]
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",
"Soul Badge", "Volcano Badge", "Earth Badge"]
self.multiworld.random.shuffle(badges)
self.random.shuffle(badges)
badges_to_add += [badges.pop(), badges.pop()]
hm_moves = ["Cut", "Fly", "Surf", "Strength", "Flash"]
self.multiworld.random.shuffle(hm_moves)
self.random.shuffle(hm_moves)
self.extra_badges = {}
for badge in badges_to_add:
self.extra_badges[hm_moves.pop()] = badge
@@ -160,79 +262,17 @@ class PokemonRedBlueWorld(World):
process_move_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 = [
*(True for _ in range(round(self.multiworld.dexsanity[self.player].value * 1.51))),
*(False for _ in range(151 - 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.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):
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)]
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
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]
for badge in badges:
self.multiworld.itempool.remove(badge)
@@ -297,8 +337,8 @@ class PokemonRedBlueWorld(World):
for mon in poke_data.pokemon_data.keys():
state.collect(self.create_item(mon), True)
state.sweep_for_advancements()
self.multiworld.random.shuffle(badges)
self.multiworld.random.shuffle(badgelocs)
self.random.shuffle(badges)
self.random.shuffle(badgelocs)
badgelocs_copy = badgelocs.copy()
# 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)
@@ -318,7 +358,7 @@ class PokemonRedBlueWorld(World):
raise FillError(f"Failed to place badges for player {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
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)]
if not learnable_tms:
learnable_tms = tms
tm = self.multiworld.random.choice(learnable_tms)
tm = self.random.choice(learnable_tms)
loc.place_locked_item(tm)
fill_locations.remove(loc)
@@ -370,9 +410,9 @@ class PokemonRedBlueWorld(World):
if not all_state.can_reach(location, player=self.player):
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
if self.multiworld.dexsanity[self.player]:
if self.options.dexsanity:
for i, mon in enumerate(poke_data.pokemon_data):
if self.dexsanity_table[i]:
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),
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
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
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"]
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
if rule:
for loc in locs:
@@ -406,16 +446,16 @@ class PokemonRedBlueWorld(World):
if loc.item is None:
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)
if loc.item is None:
locs.add(loc)
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.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")
self.local_locs = locs
@@ -440,10 +480,10 @@ class PokemonRedBlueWorld(World):
else:
region_mons.add(location.item.name)
self.multiworld.elite_four_pokedex_condition[self.player].total = \
int((len(reachable_mons) / 100) * self.multiworld.elite_four_pokedex_condition[self.player].value)
self.options.elite_four_pokedex_condition.total = \
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"]]
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
@@ -469,7 +509,7 @@ class PokemonRedBlueWorld(World):
else:
break
else:
self.multiworld.random.shuffle(traps)
self.random.shuffle(traps)
for trap in traps:
try:
self.multiworld.itempool.remove(trap)
@@ -497,22 +537,22 @@ class PokemonRedBlueWorld(World):
found_mons.add(key)
def create_regions(self):
if (self.multiworld.old_man[self.player] == "vanilla" or
self.multiworld.door_shuffle[self.player] in ("full", "insanity")):
fly_map_codes = self.multiworld.random.sample(range(2, 11), 2)
elif (self.multiworld.door_shuffle[self.player] == "simple" or
self.multiworld.route_3_condition[self.player] == "boulder_badge" or
(self.multiworld.route_3_condition[self.player] == "any_badge" and
self.multiworld.badgesanity[self.player])):
fly_map_codes = self.multiworld.random.sample(range(3, 11), 2)
if (self.options.old_man == "vanilla" or
self.options.door_shuffle in ("full", "insanity")):
fly_map_codes = self.random.sample(range(2, 11), 2)
elif (self.options.door_shuffle == "simple" or
self.options.route_3_condition == "boulder_badge" or
(self.options.route_3_condition == "any_badge" and
self.options.badgesanity)):
fly_map_codes = self.random.sample(range(3, 11), 2)
else:
fly_map_codes = self.multiworld.random.sample([4, 6, 7, 8, 9, 10], 2)
if self.multiworld.free_fly_location[self.player]:
fly_map_codes = self.random.sample([4, 6, 7, 8, 9, 10], 2)
if self.options.free_fly_location:
fly_map_code = fly_map_codes[0]
else:
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]
else:
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)
def set_rules(self):
set_rules(self.multiworld, self.player)
set_rules(self.multiworld, self, self.player)
def create_item(self, name: str) -> Item:
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]]
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"Elite Four Total Key Items: {self.multiworld.elite_four_key_items_condition[self.player].total}\n")
spoiler_handle.write(f"Elite Four Total Pokemon: {self.multiworld.elite_four_pokedex_condition[self.player].total}\n")
if self.multiworld.free_fly_location[self.player]:
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.options.elite_four_key_items_condition.total}\n")
spoiler_handle.write(f"Elite Four Total Pokemon: {self.options.elite_four_pokedex_condition.total}\n")
if self.options.free_fly_location:
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")
if self.extra_badges:
for hm_move, badge in self.extra_badges.items():
spoiler_handle.write(hm_move + " enabled by: " + (" " * 20)[:20 - len(hm_move)] + badge + "\n")
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")
for matchup in self.type_chart:
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")
def get_filler_item_name(self) -> str:
combined_traps = (self.multiworld.poison_trap_weight[self.player].value
+ self.multiworld.fire_trap_weight[self.player].value
+ self.multiworld.paralyze_trap_weight[self.player].value
+ self.multiworld.ice_trap_weight[self.player].value
+ self.multiworld.sleep_trap_weight[self.player].value)
combined_traps = (self.options.poison_trap_weight.value
+ self.options.fire_trap_weight.value
+ self.options.paralyze_trap_weight.value
+ self.options.ice_trap_weight.value
+ self.options.sleep_trap_weight.value)
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()
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])
and (not self.multiworld.door_shuffle[self.player])):
if (((not self.options.tea) or "Saffron City" not in [self.fly_map, self.town_map_fly_map])
and (not self.options.door_shuffle)):
# 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
banned_items.append("Poke Doll")
if not self.multiworld.tea[self.player]:
if not self.options.tea:
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])
def select_trap(self):
if self.traps is None:
self.traps = []
self.traps += ["Poison Trap"] * self.multiworld.poison_trap_weight[self.player].value
self.traps += ["Fire Trap"] * self.multiworld.fire_trap_weight[self.player].value
self.traps += ["Paralyze Trap"] * self.multiworld.paralyze_trap_weight[self.player].value
self.traps += ["Ice Trap"] * self.multiworld.ice_trap_weight[self.player].value
self.traps += ["Sleep Trap"] * self.multiworld.sleep_trap_weight[self.player].value
return self.multiworld.random.choice(self.traps)
self.traps += ["Poison Trap"] * self.options.poison_trap_weight.value
self.traps += ["Fire Trap"] * self.options.fire_trap_weight.value
self.traps += ["Paralyze Trap"] * self.options.paralyze_trap_weight.value
self.traps += ["Ice Trap"] * self.options.ice_trap_weight.value
self.traps += ["Sleep Trap"] * self.options.sleep_trap_weight.value
return self.random.choice(self.traps)
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] = {}
if self.multiworld.dexsanity[self.player]:
if self.options.dexsanity:
mon_locations = {mon: set() for mon in poke_data.pokemon_data.keys()}
for loc in location_data:
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] =\
", ".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):
if location.parent_region.entrance_hint and location.address:
hint_data[self.player][location.address] = location.parent_region.entrance_hint
def fill_slot_data(self) -> dict:
return {
"second_fossil_check_condition": self.multiworld.second_fossil_check_condition[self.player].value,
"require_item_finder": self.multiworld.require_item_finder[self.player].value,
"randomize_hidden_items": self.multiworld.randomize_hidden_items[self.player].value,
"badges_needed_for_hm_moves": self.multiworld.badges_needed_for_hm_moves[self.player].value,
"oaks_aide_rt_2": self.multiworld.oaks_aide_rt_2[self.player].value,
"oaks_aide_rt_11": self.multiworld.oaks_aide_rt_11[self.player].value,
"oaks_aide_rt_15": self.multiworld.oaks_aide_rt_15[self.player].value,
"extra_key_items": self.multiworld.extra_key_items[self.player].value,
"extra_strength_boulders": self.multiworld.extra_strength_boulders[self.player].value,
"tea": self.multiworld.tea[self.player].value,
"old_man": self.multiworld.old_man[self.player].value,
"elite_four_badges_condition": self.multiworld.elite_four_badges_condition[self.player].value,
"elite_four_key_items_condition": self.multiworld.elite_four_key_items_condition[self.player].total,
"elite_four_pokedex_condition": self.multiworld.elite_four_pokedex_condition[self.player].total,
"victory_road_condition": self.multiworld.victory_road_condition[self.player].value,
"route_22_gate_condition": self.multiworld.route_22_gate_condition[self.player].value,
"route_3_condition": self.multiworld.route_3_condition[self.player].value,
"robbed_house_officer": self.multiworld.robbed_house_officer[self.player].value,
"viridian_gym_condition": self.multiworld.viridian_gym_condition[self.player].value,
"cerulean_cave_badges_condition": self.multiworld.cerulean_cave_badges_condition[self.player].value,
"cerulean_cave_key_items_condition": self.multiworld.cerulean_cave_key_items_condition[self.player].total,
ret = {
"second_fossil_check_condition": self.options.second_fossil_check_condition.value,
"require_item_finder": self.options.require_item_finder.value,
"randomize_hidden_items": self.options.randomize_hidden_items.value,
"badges_needed_for_hm_moves": self.options.badges_needed_for_hm_moves.value,
"oaks_aide_rt_2": self.options.oaks_aide_rt_2.value,
"oaks_aide_rt_11": self.options.oaks_aide_rt_11.value,
"oaks_aide_rt_15": self.options.oaks_aide_rt_15.value,
"extra_key_items": self.options.extra_key_items.value,
"extra_strength_boulders": self.options.extra_strength_boulders.value,
"tea": self.options.tea.value,
"old_man": self.options.old_man.value,
"elite_four_badges_condition": self.options.elite_four_badges_condition.value,
"elite_four_key_items_condition": self.options.elite_four_key_items_condition.total,
"elite_four_pokedex_condition": self.options.elite_four_pokedex_condition.total,
"victory_road_condition": self.options.victory_road_condition.value,
"route_22_gate_condition": self.options.route_22_gate_condition.value,
"route_3_condition": self.options.route_3_condition.value,
"robbed_house_officer": self.options.robbed_house_officer.value,
"viridian_gym_condition": self.options.viridian_gym_condition.value,
"cerulean_cave_badges_condition": self.options.cerulean_cave_badges_condition.value,
"cerulean_cave_key_items_condition": self.options.cerulean_cave_key_items_condition.total,
"free_fly_map": self.fly_map_code,
"town_map_fly_map": self.town_map_fly_map_code,
"extra_badges": self.extra_badges,
"type_chart": self.type_chart,
"randomize_pokedex": self.multiworld.randomize_pokedex[self.player].value,
"trainersanity": self.multiworld.trainersanity[self.player].value,
"death_link": self.multiworld.death_link[self.player].value,
"prizesanity": self.multiworld.prizesanity[self.player].value,
"key_items_only": self.multiworld.key_items_only[self.player].value,
"poke_doll_skip": self.multiworld.poke_doll_skip[self.player].value,
"bicycle_gate_skips": self.multiworld.bicycle_gate_skips[self.player].value,
"stonesanity": self.multiworld.stonesanity[self.player].value,
"door_shuffle": self.multiworld.door_shuffle[self.player].value,
"warp_tile_shuffle": self.multiworld.warp_tile_shuffle[self.player].value,
"dark_rock_tunnel_logic": self.multiworld.dark_rock_tunnel_logic[self.player].value,
"split_card_key": self.multiworld.split_card_key[self.player].value,
"all_elevators_locked": self.multiworld.all_elevators_locked[self.player].value,
"require_pokedex": self.multiworld.require_pokedex[self.player].value,
"area_1_to_1_mapping": self.multiworld.area_1_to_1_mapping[self.player].value,
"blind_trainers": self.multiworld.blind_trainers[self.player].value,
"randomize_pokedex": self.options.randomize_pokedex.value,
"trainersanity": self.options.trainersanity.value,
"death_link": self.options.death_link.value,
"prizesanity": self.options.prizesanity.value,
"key_items_only": self.options.key_items_only.value,
"poke_doll_skip": self.options.poke_doll_skip.value,
"bicycle_gate_skips": self.options.bicycle_gate_skips.value,
"stonesanity": self.options.stonesanity.value,
"door_shuffle": self.options.door_shuffle.value,
"warp_tile_shuffle": self.options.warp_tile_shuffle.value,
"dark_rock_tunnel_logic": self.options.dark_rock_tunnel_logic.value,
"split_card_key": self.options.split_card_key.value,
"all_elevators_locked": self.options.all_elevators_locked.value,
"require_pokedex": self.options.require_pokedex.value,
"area_1_to_1_mapping": self.options.area_1_to_1_mapping.value,
"blind_trainers": self.options.blind_trainers.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):
game = "Pokemon Red and Blue"