mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user