Pokémon Red and Blue: Version 4 update (#1963)

## What is this fixing or adding?
Adds a large number of new options, including:

- Door Shuffle
- Sphere-based level scaling
- Key Item and Pokedex requirement options to reach the Elite Four
- Split Card Key option
- Dexsanity option can be set to a percentage of Pokémon that will be checks
- Stonesanity: remove the stones from the Celadon Department Store and shuffle them into the item pool, replacing 4 of the 5 Moon Stone items
- Sleep Trap items option
- Randomize Move Types option
- Town Map Fly Location option, to unlock a flight location when finding/receiving the Town Map

Many enhancements have been made, including:
- Game allows you to continue your save file _from Pallet Town_ as a way to save warp back to the beginning of the game. The one-way drop from Diglett's Cave to north Route 2 that had been added to the randomizer has been removed.
- Client auto-hints some locations when you are able to see the item before you can obtain it (but would only show AP Item if it is for another player), including Bike Shop, Oak's Aides, Celadon Prize Corner, and the unchosen Fossil location.

Various bugs have been fixed, including:
- Route 13 wild Pokémon not correctly logically requiring Cut
- Vanilla tm/hm compatibility options giving compatibility for many TMs/HMs erroneously 
- If an item that exists in multiple quantities in the item pool is chosen for one of the locations that are pre-filled with local items, it will continue placing that same item in the remaining locations as long as more of that item exist
- `start_with` option for `randomize_pokedex` still shuffling a Pokédex into the item pool
- The obedience threshold levels being incorrect with 0-2 badges, with Pokémon up to level 30 obeying with 0-1 badges and up to 10 with 2 badges
- Receiving a DeathLink trigger in the Safari Zone causing issues. Now, you will have your steps remaining set to 0 instead of blacking out when you're in the Safari Zone.

Many location names have been changed, as location names are automatically prepended using the Region name and a large number of areas have been split into new regions as part of the overhaul to add Door Shuffle.
This commit is contained in:
Alchav 2023-07-23 18:46:54 -04:00 committed by GitHub
parent cf8ac49f76
commit 85b92e2696
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 8098 additions and 3603 deletions

View File

@ -29,6 +29,9 @@ for location in location_data:
location_map[type(location.ram_address).__name__][location.ram_address.flag] = location.address
location_bytes_bits[location.address] = {'byte': location.ram_address.byte, 'bit': location.ram_address.bit}
location_name_to_id = {location.name: location.address for location in location_data if location.type == "Item"
and location.address is not None}
SYSTEM_MESSAGE_ID = 0
CONNECTION_TIMING_OUT_STATUS = "Connection timing out. Please restart your emulator, then restart pkmn_rb.lua"
@ -72,6 +75,7 @@ class GBContext(CommonContext):
self.items_handling = 0b001
self.sent_release = False
self.sent_collect = False
self.auto_hints = set()
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
@ -153,6 +157,31 @@ async def parse_locations(data: List, ctx: GBContext):
locations.append(loc_id)
elif flags[flag_type][location_bytes_bits[loc_id]['byte']] & 1 << location_bytes_bits[loc_id]['bit']:
locations.append(loc_id)
hints = []
if flags["EventFlag"][280] & 16:
hints.append("Cerulean Bicycle Shop")
if flags["EventFlag"][280] & 32:
hints.append("Route 2 Gate - Oak's Aide")
if flags["EventFlag"][280] & 64:
hints.append("Route 11 Gate 2F - Oak's Aide")
if flags["EventFlag"][280] & 128:
hints.append("Route 15 Gate 2F - Oak's Aide")
if flags["EventFlag"][281] & 1:
hints += ["Celadon Prize Corner - Item Prize 1", "Celadon Prize Corner - Item Prize 2",
"Celadon Prize Corner - Item Prize 3"]
if (location_name_to_id["Fossil - Choice A"] in ctx.checked_locations and location_name_to_id["Fossil - Choice B"]
not in ctx.checked_locations):
hints.append("Fossil - Choice B")
elif (location_name_to_id["Fossil - Choice B"] in ctx.checked_locations and location_name_to_id["Fossil - Choice A"]
not in ctx.checked_locations):
hints.append("Fossil - Choice A")
hints = [location_name_to_id[loc] for loc in hints if loc not in ctx.auto_hints and location_name_to_id[loc] in
ctx.missing_locations and location_name_to_id[loc] not in ctx.locations_checked]
if hints:
await ctx.send_msgs([{"cmd": "LocationScouts", "locations": hints, "create_as_hint": 2}])
ctx.auto_hints.update(hints)
if flags["EventFlag"][280] & 1 and not ctx.finished_game:
await ctx.send_msgs([
{"cmd": "StatusUpdate",

View File

@ -77,7 +77,6 @@ non_apworlds: set = {
"Meritous",
"Ocarina of Time",
"Overcooked! 2",
"Pokemon Red and Blue",
"Raft",
"Secret of Evermore",
"Slay the Spire",

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2022 Alex "Alchav" Avery
Copyright (c) 2022-2023 Alex "Alchav" Avery
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,26 +1,27 @@
import os
import settings
import typing
import threading
from copy import deepcopy
from typing import TextIO
from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification
from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification, LocationProgressType
from Fill import fill_restrictive, FillError, sweep_from_pool
from ..AutoWorld import World, WebWorld
from ..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 .logic import PokemonLogic
from .options import pokemon_rb_options
from .rom_addresses import rom_addresses
from .text import encode_text
from .rom import generate_output, get_base_rom_bytes, get_base_rom_path, process_pokemon_data, process_wild_pokemon,\
process_static_pokemon, process_move_data, RedDeltaPatch, BlueDeltaPatch
from .rom import generate_output, get_base_rom_bytes, get_base_rom_path, RedDeltaPatch, BlueDeltaPatch
from .pokemon import process_pokemon_data, process_move_data
from .encounters import process_pokemon_locations, process_trainer_data
from .rules import set_rules
import worlds.pokemon_rb.poke_data as poke_data
from .level_scaling import level_scaling
from . import logic
from . import poke_data
class PokemonSettings(settings.Group):
@ -67,10 +68,10 @@ class PokemonRedBlueWorld(World):
option_definitions = pokemon_rb_options
settings: typing.ClassVar[PokemonSettings]
data_version = 8
data_version = 9
required_client_version = (0, 3, 9)
topology_present = False
topology_present = True
item_name_to_id = {name: data.id for name, data in item_table.items()}
location_name_to_id = {location.name: location.address for location in location_data if location.type == "Item"
@ -81,8 +82,12 @@ class PokemonRedBlueWorld(World):
def __init__(self, world: MultiWorld, player: int):
super().__init__(world, player)
self.item_pool = []
self.total_key_items = None
self.fly_map = None
self.fly_map_code = None
self.town_map_fly_map = None
self.town_map_fly_map_code = None
self.extra_badges = {}
self.type_chart = None
self.local_poke_data = None
@ -94,6 +99,9 @@ class PokemonRedBlueWorld(World):
self.type_chart = None
self.traps = None
self.trade_mons = {}
self.finished_level_scaling = threading.Event()
self.dexsanity_table = []
self.local_locs = []
@classmethod
def stage_assert_generate(cls, multiworld: MultiWorld):
@ -125,11 +133,14 @@ class PokemonRedBlueWorld(World):
if len(self.multiworld.player_name[self.player].encode()) > 16:
raise Exception(f"Player name too long for {self.multiworld.get_player_name(self.player)}. Player name cannot exceed 16 bytes for Pokémon Red and Blue.")
if (self.multiworld.dexsanity[self.player] and self.multiworld.accessibility[self.player] == "locations"
and (self.multiworld.catch_em_all[self.player] != "all_pokemon"
or self.multiworld.randomize_wild_pokemon[self.player] == "vanilla"
or self.multiworld.randomize_legendary_pokemon[self.player] != "any")):
self.multiworld.accessibility[self.player] = self.multiworld.accessibility[self.player].from_text("items")
if not self.multiworld.badgesanity[self.player]:
self.multiworld.non_local_items[self.player].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] = self.multiworld.dexsanity[self.player].from_text("false")
self.multiworld.randomize_hidden_items[self.player] = \
self.multiworld.randomize_hidden_items[self.player].from_text("off")
if self.multiworld.badges_needed_for_hm_moves[self.player].value >= 2:
badges_to_add = ["Marsh Badge", "Volcano Badge", "Earth Badge"]
@ -161,9 +172,14 @@ class PokemonRedBlueWorld(World):
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:
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 == 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)
@ -209,143 +225,228 @@ class PokemonRedBlueWorld(World):
# 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.multiworld.early_items[self.player]["Exp. All"] = 1
def create_items(self) -> None:
start_inventory = self.multiworld.start_inventory[self.player].value.copy()
if self.multiworld.randomize_pokedex[self.player] == "start_with":
start_inventory["Pokedex"] = 1
self.multiworld.push_precollected(self.create_item("Pokedex"))
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)))
]
self.multiworld.random.shuffle(self.dexsanity_table)
locations = [location for location in location_data if location.type == "Item"]
item_pool = []
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)
for location in locations:
event = location.event
if not location.inclusion(self.multiworld, self.player):
def create_items(self):
self.multiworld.itempool += self.item_pool
@classmethod
def stage_fill_hook(cls, multiworld, progitempool, usefulitempool, filleritempool, fill_locations):
locs = []
for world in multiworld.get_game_worlds("Pokemon Red and Blue"):
locs += world.local_locs
for loc in sorted(locs):
if loc.item:
continue
if location.original_item in self.multiworld.start_inventory[self.player].value and \
location.original_item in item_groups["Unique"]:
start_inventory[location.original_item] -= 1
item = self.create_filler()
elif location.original_item is None:
item = self.create_filler()
elif location.original_item == "Pokedex":
if self.multiworld.randomize_pokedex[self.player] == "vanilla":
self.multiworld.get_location(location.name, self.player).event = True
event = True
item = self.create_item("Pokedex")
elif location.original_item.startswith("TM"):
if self.multiworld.randomize_tm_moves[self.player]:
item = self.create_item(location.original_item.split(" ")[0])
itempool = progitempool + usefulitempool + filleritempool
multiworld.random.shuffle(itempool)
unplaced_items = []
for item in itempool:
if item.player == loc.player and loc.can_fill(multiworld.state, item, False):
if item in progitempool:
progitempool.remove(item)
elif item in usefulitempool:
usefulitempool.remove(item)
elif item in filleritempool:
filleritempool.remove(item)
if item.advancement:
state = sweep_from_pool(multiworld.state, progitempool + unplaced_items)
if (not item.advancement) or state.can_reach(loc, "Location", loc.player):
multiworld.push_item(loc, item, False)
loc.event = item.advancement
fill_locations.remove(loc)
break
else:
unplaced_items.append(item)
progitempool += [item for item in unplaced_items if item.advancement]
usefulitempool += [item for item in unplaced_items if 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):
if not self.multiworld.badgesanity[self.player]:
# Door Shuffle options besides Simple place badges during door shuffling
if not self.multiworld.door_shuffle[self.player] not 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)
progitempool.remove(badge)
for _ in range(5):
badgelocs = [self.multiworld.get_location(loc, self.player) for loc in [
"Pewter Gym - Brock Prize", "Cerulean Gym - Misty Prize",
"Vermilion Gym - Lt. Surge Prize", "Celadon Gym - Erika Prize",
"Fuchsia Gym - Koga Prize", "Saffron Gym - Sabrina Prize",
"Cinnabar Gym - Blaine Prize", "Viridian Gym - Giovanni Prize"]]
state = self.multiworld.get_all_state(False)
self.multiworld.random.shuffle(badges)
self.multiworld.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)
if badges:
for location in badgelocs:
if location.item:
badges.append(location.item)
location.item = None
continue
else:
for location in badgelocs:
if location.item:
fill_locations.remove(location)
break
else:
item = self.create_item(location.original_item)
else:
item = self.create_item(location.original_item)
if (item.classification == ItemClassification.filler and self.multiworld.random.randint(1, 100)
<= self.multiworld.trap_percentage[self.player].value and combined_traps != 0):
item = self.create_item(self.select_trap())
if event:
self.multiworld.get_location(location.name, self.player).place_locked_item(item)
elif "Badge" not in item.name or self.multiworld.badgesanity[self.player].value:
item_pool.append(item)
raise FillError(f"Failed to place badges for player {self.player}")
self.multiworld.random.shuffle(item_pool)
if self.multiworld.key_items_only[self.player]:
return
self.multiworld.itempool += item_pool
tms = [item for item in usefulitempool + filleritempool if item.name.startswith("TM") and (item.player ==
self.player or (item.player in self.multiworld.groups and self.player in
self.multiworld.groups[item.player]["players"]))]
if len(tms) > 7:
for gym_leader in (("Pewter Gym", "Brock"), ("Cerulean Gym", "Misty"), ("Vermilion Gym", "Lt. Surge"),
("Celadon Gym-C", "Erika"), ("Fuchsia Gym", "Koga"), ("Saffron Gym-C", "Sabrina"),
("Cinnabar Gym", "Blaine"), ("Viridian Gym", "Giovanni")):
loc = self.multiworld.get_location(f"{gym_leader[0].split('-')[0]} - {gym_leader[1]} TM",
self.player)
if loc.item:
continue
for party in self.multiworld.get_location(gym_leader[0] + " - Trainer Parties", self.player).party_data:
if party["party_address"] == \
f"Trainer_Party_{gym_leader[1].replace('. ', '').replace('Giovanni', 'Viridian_Gym_Giovanni')}_A":
mon = party["party"][-1]
learnable_tms = [tm for tm in tms if self.local_poke_data[mon]["tms"][
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)
loc.place_locked_item(tm)
fill_locations.remove(loc)
tms.remove(tm)
if tm.useful:
usefulitempool.remove(tm)
else:
filleritempool.remove(tm)
break
else:
raise Exception("Missing Gym Leader data")
def pre_fill(self) -> None:
process_wild_pokemon(self)
process_static_pokemon(self)
pokemon_locs = [location.name for location in location_data if location.type != "Item"]
process_pokemon_locations(self)
process_trainer_data(self)
locs = [location.name for location in location_data if location.type != "Item"]
for location in self.multiworld.get_locations(self.player):
if location.name in pokemon_locs:
location.show_in_spoiler = False
if location.name in locs:
location.show_in_spoiler = False
def intervene(move):
accessible_slots = [loc for loc in self.multiworld.get_reachable_locations(test_state, self.player) if loc.type == "Wild Encounter"]
move_bit = pow(2, poke_data.hm_moves.index(move) + 2)
viable_mons = [mon for mon in self.local_poke_data if self.local_poke_data[mon]["tms"][6] & move_bit]
placed_mons = [slot.item.name for slot in accessible_slots]
# this sort method doesn't seem to work if you reference the same list being sorted in the lambda
placed_mons_copy = placed_mons.copy()
placed_mons.sort(key=lambda i: placed_mons_copy.count(i))
placed_mon = placed_mons.pop()
if self.multiworld.area_1_to_1_mapping[self.player]:
zone = " - ".join(placed_mon.split(" - ")[:-1])
replace_slots = [slot for slot in accessible_slots if slot.name.startswith(zone) and slot.item.name ==
placed_mon]
def intervene(move, test_state):
if self.multiworld.randomize_wild_pokemon[self.player]:
accessible_slots = [loc for loc in self.multiworld.get_reachable_locations(test_state, self.player) if
loc.type == "Wild Encounter"]
def number_of_zones(mon):
zones = set()
for loc in [slot for slot in accessible_slots if slot.item.name == mon]:
zones.add(loc.name.split(" - ")[0])
return len(zones)
move_bit = pow(2, poke_data.hm_moves.index(move) + 2)
viable_mons = [mon for mon in self.local_poke_data if self.local_poke_data[mon]["tms"][6] & move_bit]
placed_mons = [slot.item.name for slot in accessible_slots]
if self.multiworld.area_1_to_1_mapping[self.player]:
placed_mons.sort(key=lambda i: number_of_zones(i))
else:
# this sort method doesn't work if you reference the same list being sorted in the lambda
placed_mons_copy = placed_mons.copy()
placed_mons.sort(key=lambda i: placed_mons_copy.count(i))
placed_mon = placed_mons.pop()
replace_mon = self.multiworld.random.choice(viable_mons)
replace_slot = self.multiworld.random.choice([slot for slot in accessible_slots if slot.item.name
== placed_mon])
if self.multiworld.area_1_to_1_mapping[self.player]:
zone = " - ".join(replace_slot.name.split(" - ")[:-1])
replace_slots = [slot for slot in accessible_slots if slot.name.startswith(zone) and slot.item.name
== placed_mon]
for replace_slot in replace_slots:
replace_slot.item = self.create_item(replace_mon)
else:
replace_slot.item = self.create_item(replace_mon)
else:
replace_slots = [self.multiworld.random.choice([slot for slot in accessible_slots if slot.item.name ==
placed_mon])]
replace_mon = self.multiworld.random.choice(viable_mons)
for replace_slot in replace_slots:
replace_slot.item = self.create_item(replace_mon)
tms_hms = self.local_tms + poke_data.hm_moves
flag = tms_hms.index(move)
mon_list = [mon for mon in poke_data.pokemon_data.keys() if test_state.has(mon, self.player)]
self.multiworld.random.shuffle(mon_list)
mon_list.sort(key=lambda mon: self.local_move_data[move]["type"] not in
[self.local_poke_data[mon]["type1"], self.local_poke_data[mon]["type2"]])
for mon in mon_list:
if test_state.has(mon, self.player):
self.local_poke_data[mon]["tms"][int(flag / 8)] |= 1 << (flag % 8)
break
last_intervene = None
while True:
intervene_move = None
test_state = self.multiworld.get_all_state(False)
if not self.multiworld.badgesanity[self.player]:
for badge in ["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Soul Badge",
"Marsh Badge", "Volcano Badge", "Earth Badge"]:
test_state.collect(self.create_item(badge))
if not test_state.pokemon_rb_can_surf(self.player):
if not logic.can_learn_hm(test_state, "Surf", self.player):
intervene_move = "Surf"
if not test_state.pokemon_rb_can_strength(self.player):
elif not logic.can_learn_hm(test_state, "Strength", self.player):
intervene_move = "Strength"
# 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
if (self.multiworld.accessibility[self.player] != "minimal" or ((not
self.multiworld.badgesanity[self.player]) and max(self.multiworld.elite_four_condition[self.player],
self.multiworld.victory_road_condition[self.player]) > 7)):
if not test_state.pokemon_rb_can_cut(self.player):
intervene_move = "Cut"
if (self.multiworld.accessibility[self.player].current_key != "minimal" and
(self.multiworld.trainersanity[self.player] or self.multiworld.extra_key_items[self.player])):
if not test_state.pokemon_rb_can_flash(self.player):
intervene_move = "Flash"
elif ((not logic.can_learn_hm(test_state, "Cut", self.player)) and
(self.multiworld.accessibility[self.player] != "minimal" or ((not
self.multiworld.badgesanity[self.player]) and max(
self.multiworld.elite_four_badges_condition[self.player],
self.multiworld.route_22_gate_condition[self.player],
self.multiworld.victory_road_condition[self.player])
> 7) or (self.multiworld.door_shuffle[self.player] not in ("off", "simple")))):
intervene_move = "Cut"
elif ((not logic.can_learn_hm(test_state, "Flash", self.player)) and self.multiworld.dark_rock_tunnel_logic[self.player]
and (((self.multiworld.accessibility[self.player] != "minimal" and
(self.multiworld.trainersanity[self.player] or self.multiworld.extra_key_items[self.player])) or
self.multiworld.door_shuffle[self.player]))):
intervene_move = "Flash"
# 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.
# 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.
elif ((not logic.can_learn_hm(test_state, "Fly", self.player)) and logic.can_learn_hm(test_state, "Fly", self.player)
and self.multiworld.door_shuffle[self.player] not in
("off", "simple") and [self.fly_map, self.town_map_fly_map] != ["Pallet Town", "Pallet Town"]):
intervene_move = "Fly"
if intervene_move:
if intervene_move == last_intervene:
raise Exception(f"Caught in infinite loop attempting to ensure {intervene_move} is available to player {self.player}")
intervene(intervene_move)
intervene(intervene_move, test_state)
last_intervene = intervene_move
else:
break
# Delete evolution events for Pokémon that are not in logic in an all_state so that accessibility check does not
# fail. Re-use test_state from previous final loop.
evolutions_region = self.multiworld.get_region("Evolution", self.player)
clear_cache = False
for location in evolutions_region.locations.copy():
if not test_state.can_reach(location, player=self.player):
evolutions_region.locations.remove(location)
clear_cache = True
if clear_cache:
self.multiworld.clear_location_cache()
if self.multiworld.old_man[self.player] == "early_parcel":
self.multiworld.local_early_items[self.player]["Oak's Parcel"] = 1
if self.multiworld.dexsanity[self.player]:
for location in [self.multiworld.get_location(f"Pokedex - {mon}", self.player)
for mon in poke_data.pokemon_data.keys()]:
add_item_rule(location, lambda item: item.name != "Oak's Parcel" or item.player != self.player)
if not self.multiworld.badgesanity[self.player].value:
self.multiworld.non_local_items[self.player].value -= self.item_name_groups["Badges"]
for i in range(5):
try:
badges = []
badgelocs = []
for badge in ["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Soul Badge",
"Marsh Badge", "Volcano Badge", "Earth Badge"]:
badges.append(self.create_item(badge))
for loc in ["Pewter Gym - Brock 1", "Cerulean Gym - Misty 1", "Vermilion Gym - Lt. Surge 1",
"Celadon Gym - Erika 1", "Fuchsia Gym - Koga 1", "Saffron Gym - Sabrina 1",
"Cinnabar Gym - Blaine 1", "Viridian Gym - Giovanni 1"]:
badgelocs.append(self.multiworld.get_location(loc, self.player))
state = self.multiworld.get_all_state(False)
self.multiworld.random.shuffle(badges)
self.multiworld.random.shuffle(badgelocs)
fill_restrictive(self.multiworld, state, badgelocs.copy(), badges, True, True)
except FillError:
for location in badgelocs:
location.item = None
continue
break
else:
raise FillError(f"Failed to place badges for player {self.player}")
for i, mon in enumerate(poke_data.pokemon_data):
if self.dexsanity_table[i]:
location = self.multiworld.get_location(f"Pokedex - {mon}", self.player)
add_item_rule(location, lambda item: item.name != "Oak's Parcel" or item.player != self.player)
# Place local items in some locations to prevent save-scumming. Also Oak's PC to prevent an "AP Item" from
# entering the player's inventory.
@ -353,52 +454,144 @@ class PokemonRedBlueWorld(World):
locs = {self.multiworld.get_location("Fossil - Choice A", self.player),
self.multiworld.get_location("Fossil - Choice B", self.player)}
if self.multiworld.dexsanity[self.player]:
for mon in ([" ".join(self.multiworld.get_location(
f"Pallet Town - Starter {i}", self.player).item.name.split(" ")[1:]) for i in range(1, 4)]
+ [" ".join(self.multiworld.get_location(
f"Fighting Dojo - Gift {i}", self.player).item.name.split(" ")[1:]) for i in range(1, 3)]):
for loc in locs:
if self.multiworld.fossil_check_item_types[self.player] == "key_items":
add_item_rule(loc, lambda i: i.advancement)
elif self.multiworld.fossil_check_item_types[self.player] == "unique_items":
add_item_rule(loc, lambda i: i.name in item_groups["Unique"])
elif self.multiworld.fossil_check_item_types[self.player] == "no_key_items":
add_item_rule(loc, lambda i: not i.advancement)
for mon in ([" ".join(self.multiworld.get_location(
f"Oak's Lab - Starter {i}", self.player).item.name.split(" ")[1:]) for i in range(1, 4)]
+ [" ".join(self.multiworld.get_location(
f"Saffron Fighting Dojo - Gift {i}", self.player).item.name.split(" ")[1:]) for i in range(1, 3)]
+ ["Vaporeon", "Jolteon", "Flareon"]):
if self.dexsanity_table[poke_data.pokemon_dex[mon] - 1]:
loc = self.multiworld.get_location(f"Pokedex - {mon}", self.player)
if loc.item is None:
locs.add(loc)
loc = self.multiworld.get_location("Pallet Town - Player's PC", self.player)
if loc.item is None:
locs.add(loc)
if not self.multiworld.key_items_only[self.player]:
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):
unplaced_items = []
if loc.name in self.multiworld.priority_locations[self.player].value:
add_item_rule(loc, lambda i: i.advancement)
for item in reversed(self.multiworld.itempool):
if item.player == self.player and loc.can_fill(self.multiworld.state, item, False):
self.multiworld.itempool.remove(item)
if item.advancement:
state = sweep_from_pool(self.multiworld.state, self.multiworld.itempool + unplaced_items)
if (not item.advancement) or state.can_reach(loc, "Location", self.player):
loc.place_locked_item(item)
break
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":
add_item_rule(loc, lambda i: i.name != "Oak's Parcel")
self.local_locs = locs
all_state = self.multiworld.get_all_state(False)
reachable_mons = set()
for mon in poke_data.pokemon_data:
if all_state.has(mon, self.player) or all_state.has(f"Static {mon}", self.player):
reachable_mons.add(mon)
# The large number of wild Pokemon can make sweeping for events time-consuming, and is especially bad in
# the spoiler playthrough calculation because it removes each advancement item one at a time to verify
# if the game is beatable without it. We go through each zone and flag any duplicates as useful.
# Especially with area 1-to-1 mapping / vanilla wild Pokémon, this should cut down significantly on wasted time.
for region in self.multiworld.get_regions(self.player):
region_mons = set()
for location in region.locations:
if "Wild Pokemon" in location.name:
if location.item.name in region_mons:
location.item.classification = ItemClassification.useful
else:
unplaced_items.append(item)
self.multiworld.itempool += unplaced_items
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)
if self.multiworld.accessibility[self.player] == "locations":
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
location.name]
pokedex = self.multiworld.get_region("Pokedex", self.player)
remove_items = 0
for location in locations:
if not location.can_reach(all_state):
pokedex.locations.remove(location)
self.dexsanity_table[poke_data.pokemon_dex[location.name.split(" - ")[1]] - 1] = False
remove_items += 1
for _ in range(remove_items - 5):
balls.append(balls.pop(0))
for ball in balls:
try:
self.multiworld.itempool.remove(ball)
except ValueError:
continue
else:
break
else:
self.multiworld.random.shuffle(traps)
for trap in traps:
try:
self.multiworld.itempool.remove(trap)
except ValueError:
continue
else:
break
else:
raise Exception("Failed to remove corresponding item while deleting unreachable Dexsanity location")
self.multiworld._recache()
if self.multiworld.door_shuffle[self.player] == "decoupled":
swept_state = self.multiworld.state.copy()
swept_state.sweep_for_events(player=self.player)
locations = [location for location in
self.multiworld.get_reachable_locations(swept_state, self.player) if location.item is
None]
self.multiworld.random.shuffle(locations)
while len(locations) > 10:
location = locations.pop()
location.progress_type = LocationProgressType.EXCLUDED
if self.multiworld.key_items_only[self.player]:
locations = [location for location in self.multiworld.get_unfilled_locations(self.player) if
location.progress_type == LocationProgressType.DEFAULT]
for location in locations:
location.progress_type = LocationProgressType.PRIORITY
def create_regions(self):
if self.multiworld.free_fly_location[self.player].value:
if self.multiworld.old_man[self.player].value == 0:
fly_map_code = self.multiworld.random.randint(1, 9)
else:
fly_map_code = self.multiworld.random.randint(5, 9)
if fly_map_code == 5:
fly_map_code = 4
if fly_map_code == 9:
fly_map_code = 10
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)
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_code = fly_map_codes[0]
else:
fly_map_code = 0
self.fly_map = ["Pallet Town", "Viridian City", "Pewter City", "Cerulean City", "Lavender Town",
"Vermilion City", "Celadon City", "Fuchsia City", "Cinnabar Island", "Indigo Plateau",
"Saffron City"][fly_map_code]
if self.multiworld.town_map_fly_location[self.player]:
town_map_fly_map_code = fly_map_codes[1]
else:
town_map_fly_map_code = 0
fly_maps = ["Pallet Town", "Viridian City", "Pewter City", "Cerulean City", "Lavender Town",
"Vermilion City", "Celadon City", "Fuchsia City", "Cinnabar Island", "Indigo Plateau",
"Saffron City"]
self.fly_map = fly_maps[fly_map_code]
self.town_map_fly_map = fly_maps[town_map_fly_map_code]
self.fly_map_code = fly_map_code
create_regions(self.multiworld, self.player)
self.town_map_fly_map_code = town_map_fly_map_code
create_regions(self)
self.multiworld.completion_condition[self.player] = lambda state, player=self.player: state.has("Become Champion", player=player)
def set_rules(self):
@ -407,12 +600,21 @@ class PokemonRedBlueWorld(World):
def create_item(self, name: str) -> Item:
return PokemonRBItem(name, self.player)
@classmethod
def stage_generate_output(cls, multiworld, output_directory):
level_scaling(multiworld)
def generate_output(self, output_directory: str):
generate_output(self, output_directory)
def write_spoiler_header(self, spoiler_handle: TextIO):
if self.multiworld.free_fly_location[self.player].value:
spoiler_handle.write('Fly unlocks: %s\n' % self.fly_map)
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"Free Fly Location: {self.fly_map}\n")
if self.multiworld.town_map_fly_location[self.player]:
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")
@ -423,20 +625,30 @@ class PokemonRedBlueWorld(World):
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"\n\nPokémon locations ({self.multiworld.player_name[self.player]}):\n\n")
pokemon_locs = [location.name for location in location_data if location.type != "Item"]
pokemon_locs = [location.name for location in location_data if location.type not in ("Item", "Trainer Parties")]
for location in self.multiworld.get_locations(self.player):
if location.name in pokemon_locs:
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
if self.multiworld.random.randint(1, 100) <= self.multiworld.trap_percentage[self.player].value and combined_traps != 0:
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)
if (combined_traps > 0 and
self.multiworld.random.randint(1, 100) <= self.multiworld.trap_percentage[self.player].value):
return self.select_trap()
return self.multiworld.random.choice([item for item in item_table if item_table[
item].classification == ItemClassification.filler and item not in item_groups["Vending Machine Drinks"] +
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])
and (not self.multiworld.door_shuffle[self.player])):
# 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]:
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[
item].classification == ItemClassification.filler and item not in banned_items])
def select_trap(self):
if self.traps is None:
@ -445,23 +657,30 @@ class PokemonRedBlueWorld(World):
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)
def extend_hint_information(self, hint_data):
if self.multiworld.dexsanity[self.player]:
if self.multiworld.dexsanity[self.player] or self.multiworld.door_shuffle[self.player]:
hint_data[self.player] = {}
if self.multiworld.dexsanity[self.player]:
mon_locations = {mon: set() for mon in poke_data.pokemon_data.keys()}
for loc in location_data: #self.multiworld.get_locations(self.player):
for loc in location_data:
if loc.type in ["Wild Encounter", "Static Pokemon", "Legendary Pokemon", "Missable Pokemon"]:
mon = self.multiworld.get_location(loc.name, self.player).item.name
if mon.startswith("Static ") or mon.startswith("Missable "):
mon = " ".join(mon.split(" ")[1:])
mon_locations[mon].add(loc.name.split(" -")[0])
for mon in mon_locations:
if mon_locations[mon]:
hint_data[self.player][self.multiworld.get_location(f"Pokedex - {mon}", self.player).address] = \
for i, mon in enumerate(mon_locations):
if self.dexsanity_table[i] and mon_locations[mon]:
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]:
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,
@ -475,17 +694,25 @@ class PokemonRedBlueWorld(World):
"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_condition": self.multiworld.elite_four_condition[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_condition": self.multiworld.cerulean_cave_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,
"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
"prizesanity": self.multiworld.prizesanity[self.player].value,
"key_items_only": self.multiworld.key_items_only[self.player].value,
}
@ -499,4 +726,4 @@ class PokemonRBItem(Item):
name,
item_data.classification,
item_data.id, player
)
)

View File

@ -11,35 +11,59 @@ Items which the player would normally acquire throughout the game have been move
always able to be completed, but because of the item shuffle the player may need to access certain areas before they
would in the vanilla game.
A great many things besides item placement can be randomized, such as the location of Pokémon, their stats, types, etc., depending on your yaml settings.
A great many things besides item placement can be randomized, such as the location of Pokémon, their stats, types, etc.,
depending on your yaml settings.
Many baseline changes are made to the game, including:
* Bag item space increased to 128 slots (up from 20)
* PC item storage increased to 64 slots (up from 50)
* Bag item space increased to 128 slots (up from 20).
* PC item storage increased to 64 slots (up from 50).
* You can hold B to run (or bike extra fast!).
* You can hold select while talking to a trainer to re-battle them.
* You can return to route 2 from Diglett's Cave without the use of Cut.
* You can select "Pallet Warp" below the "Continue" option to warp to Pallet Towna s you load your save.
* Mew can be encountered at the S.S. Anne dock truck. This can be randomized depending on your settings.
* The S.S. Anne will never depart.
* Seafoam Islands entrances are swapped. This means you need Strength to travel through from Cinnabar Island to Fuchsia
City
City. You also cannot Surf onto the water from the end of Seafoam Islands going backwards if you have not yet dropped
the boulders.
* After obtaining one of the fossil item checks in Mt Moon, the remaining item can be received from the Cinnabar Lab
fossil scientist. This may require reviving a number of fossils, depending on your settings.
* Obedience depends on the total number of badges you have obtained instead of depending on specific badges.
* Pokémon that evolve by trading can also evolve by reaching level 35.
* Evolution stones are reusable.
* Evolution stones are reusable key items.
* Much of the dialogue throughout the game has been removed or shortened.
* If the Old Man is blocking your way through Viridian City, you do not have Oak's Parcel in your inventory, and you've
exhausted your money and Poké Balls, you can get a free Poké Ball from your mom.
* The Pokédex shows which HMs can be learned by any Pokémon registered as seen.
* HM moves can be overwritten if you have the HM for it in your bag.
* The NPC on the left behind the Celadon Game Corner counter will sell 1500 coins at once instead of giving information
about the Prize Corner
about the Prize Corner.
* A woman in Oak's Lab can send you back in time to replay the first rival battle, in case you have no other reachable
and repeatable source of money.
* You can disable and re-enable experience gains by talking to an aide in Oak's Lab.
* You can reset static encounters (Poké Flute encounter, legendaries, and the trap Poké Ball battles in Power Plant)
for any Pokémon you have defeated but not caught, by talking to an aide in Oak's Lab.
## What items and locations get shuffled?
All items that go into your bags given by NPCs or found on the ground, as well as gym badges.
Optionally, hidden items (those located with the Item Finder) can be shuffled as well.
Various options add more items / location checks to the pool, including:
* Randomize Hidden Items.
* Stonesanity: Replace 4 of the 5 Moon Stones in the item pool with the other 4 stones, and remove them from the
Celadon Department Store shop. Will shuffle the hidden item locations that contain Moon Stones in the original game
regardless of the Randomize Hidden Items option.
* Prizesanity: Shuffle the three item prizes from the Celadon Prize Corner.
* Tea: Adds a Tea item to the item pool which is required to pass the Saffron Gate guards instead of vending machine
drinks. Adds a location check to the woman in Celadon Mansion 1F, where the Tea item is found in FireRed and LeafGreen.
* Extra Key Items: Adds key items that will be required to access the Power Plant, Pokémon Mansion, Rocket Hideout,
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.
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.
* Trainersanity: Adds location checks to 317 trainers. 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
your prize. Adds 317 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
of 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.
## Which items can be in another player's world?

View File

@ -0,0 +1,275 @@
from copy import deepcopy
from . import poke_data
from .locations import location_data
def get_encounter_slots(self):
encounter_slots = deepcopy([location for location in location_data if location.type == "Wild Encounter"])
for location in encounter_slots:
if isinstance(location.original_item, list):
location.original_item = location.original_item[not self.multiworld.game_version[self.player].value]
return encounter_slots
def get_base_stat_total(mon):
return (poke_data.pokemon_data[mon]["atk"] + poke_data.pokemon_data[mon]["def"]
+ poke_data.pokemon_data[mon]["hp"] + poke_data.pokemon_data[mon]["spd"]
+ poke_data.pokemon_data[mon]["spc"])
def randomize_pokemon(self, mon, mons_list, randomize_type, random):
if randomize_type in [1, 3]:
type_mons = [pokemon for pokemon in mons_list if any([poke_data.pokemon_data[mon][
"type1"] in [self.local_poke_data[pokemon]["type1"], self.local_poke_data[pokemon]["type2"]],
poke_data.pokemon_data[mon]["type2"] in [self.local_poke_data[pokemon]["type1"],
self.local_poke_data[pokemon]["type2"]]])]
if not type_mons:
type_mons = mons_list.copy()
if randomize_type == 3:
stat_base = get_base_stat_total(mon)
type_mons.sort(key=lambda mon: abs(get_base_stat_total(mon) - stat_base))
mon = type_mons[round(random.triangular(0, len(type_mons) - 1, 0))]
if randomize_type == 2:
stat_base = get_base_stat_total(mon)
mons_list.sort(key=lambda mon: abs(get_base_stat_total(mon) - stat_base))
mon = mons_list[round(random.triangular(0, 50, 0))]
elif randomize_type == 4:
mon = random.choice(mons_list)
return mon
def process_trainer_data(self):
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]
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]
evolved_mons = [mon for mon in mons_list if mon not in unevolved_mons]
rival_map = {
"Charmander": self.multiworld.get_location("Oak's Lab - Starter 1", self.player).item.name[9:], # strip the
"Squirtle": self.multiworld.get_location("Oak's Lab - Starter 2", self.player).item.name[9:], # 'Missable'
"Bulbasaur": self.multiworld.get_location("Oak's Lab - Starter 3", self.player).item.name[9:], # from the name
}
def add_evolutions():
for a, b in rival_map.copy().items():
if a in poke_data.evolves_to and poke_data.evolves_to[a] not in rival_map:
if b in poke_data.evolves_to:
rival_map[poke_data.evolves_to[a]] = poke_data.evolves_to[b]
else:
rival_map[poke_data.evolves_to[a]] = b
add_evolutions()
add_evolutions()
parties_objs = [location for location in self.multiworld.get_locations(self.player)
if location.type == "Trainer Parties"]
# 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"
in i.name else 3 if "Anne" in i.name else 4 if "Pokemon Tower" in i.name else 5 if "Silph" in
i.name else 6 if "Route 22-F" in i.name else 7 if "Champion" in i.name else 8)
for parties in parties_objs:
parties_data = parties.party_data
for party in parties_data:
if party["party"] and isinstance(party["party"][0], list):
# only for Rival parties
for rival_party in party["party"]:
for i, mon in enumerate(rival_party):
if mon in ("Bulbasaur", "Ivysaur", "Venusaur", "Charmander", "Charmeleon", "Charizard",
"Squirtle", "Wartortle", "Blastoise"):
if self.multiworld.randomize_starter_pokemon[self.player]:
rival_party[i] = rival_map[mon]
elif self.multiworld.randomize_trainer_parties[self.player]:
if mon in rival_map:
rival_party[i] = rival_map[mon]
else:
new_mon = randomize_pokemon(self, mon,
unevolved_mons if mon in unevolved_mons else evolved_mons,
self.multiworld.randomize_trainer_parties[self.player].value,
self.multiworld.random)
rival_map[mon] = new_mon
rival_party[i] = new_mon
add_evolutions()
else:
if self.multiworld.randomize_trainer_parties[self.player]:
for i, mon in enumerate(party["party"]):
party["party"][i] = randomize_pokemon(self, mon, mons_list,
self.multiworld.randomize_trainer_parties[self.player].value,
self.multiworld.random)
def process_pokemon_locations(self):
starter_slots = deepcopy([location for location in location_data if location.type == "Starter Pokemon"])
legendary_slots = deepcopy([location for location in location_data if location.type == "Legendary Pokemon"])
static_slots = deepcopy([location for location in location_data if location.type in
["Static Pokemon", "Missable Pokemon"]])
legendary_mons = deepcopy([slot.original_item for slot in legendary_slots])
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
or self.multiworld.randomize_legendary_pokemon[self.player].value == 3]
if self.multiworld.randomize_legendary_pokemon[self.player] == "vanilla":
for slot in legendary_slots:
location = self.multiworld.get_location(slot.name, self.player)
location.place_locked_item(self.create_item("Static " + slot.original_item))
elif self.multiworld.randomize_legendary_pokemon[self.player] == "shuffle":
self.multiworld.random.shuffle(legendary_mons)
for slot in legendary_slots:
location = self.multiworld.get_location(slot.name, self.player)
mon = legendary_mons.pop()
location.place_locked_item(self.create_item("Static " + mon))
placed_mons[mon] += 1
elif self.multiworld.randomize_legendary_pokemon[self.player] == "static":
static_slots = static_slots + legendary_slots
self.multiworld.random.shuffle(static_slots)
static_slots.sort(key=lambda s: s.name != "Pokemon Tower 6F - Restless Soul")
while legendary_slots:
swap_slot = legendary_slots.pop()
slot = static_slots.pop()
slot_type = slot.type.split()[0]
if slot_type == "Legendary":
slot_type = "Static"
location = self.multiworld.get_location(slot.name, self.player)
location.place_locked_item(self.create_item(slot_type + " " + swap_slot.original_item))
swap_slot.original_item = slot.original_item
elif self.multiworld.randomize_legendary_pokemon[self.player] == "any":
static_slots = static_slots + legendary_slots
for slot in static_slots:
location = self.multiworld.get_location(slot.name, self.player)
randomize_type = self.multiworld.randomize_static_pokemon[self.player].value
slot_type = slot.type.split()[0]
if slot_type == "Legendary":
slot_type = "Static"
if not randomize_type:
location.place_locked_item(self.create_item(slot_type + " " + slot.original_item))
else:
mon = self.create_item(slot_type + " " +
randomize_pokemon(self, slot.original_item, mons_list, randomize_type,
self.multiworld.random))
location.place_locked_item(mon)
if slot_type != "Missable":
placed_mons[mon.name.replace("Static ", "")] += 1
chosen_mons = set()
for slot in starter_slots:
location = self.multiworld.get_location(slot.name, self.player)
randomize_type = self.multiworld.randomize_starter_pokemon[self.player].value
slot_type = "Missable"
if not randomize_type:
location.place_locked_item(self.create_item(slot_type + " " + slot.original_item))
else:
mon = self.create_item(slot_type + " " + randomize_pokemon(self, slot.original_item, mons_list,
randomize_type, self.multiworld.random))
while mon.name in chosen_mons:
mon = self.create_item(slot_type + " " + randomize_pokemon(self, slot.original_item, mons_list,
randomize_type, self.multiworld.random))
chosen_mons.add(mon.name)
location.place_locked_item(mon)
encounter_slots_master = get_encounter_slots(self)
encounter_slots = encounter_slots_master.copy()
zone_mapping = {}
if self.multiworld.randomize_wild_pokemon[self.player]:
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]
self.multiworld.random.shuffle(encounter_slots)
locations = []
for slot in encounter_slots:
location = self.multiworld.get_location(slot.name, self.player)
zone = " - ".join(location.name.split(" - ")[:-1])
if zone not in zone_mapping:
zone_mapping[zone] = {}
original_mon = slot.original_item
if self.multiworld.area_1_to_1_mapping[self.player] and original_mon in zone_mapping[zone]:
mon = zone_mapping[zone][original_mon]
else:
mon = randomize_pokemon(self, original_mon, mons_list,
self.multiworld.randomize_wild_pokemon[self.player].value, self.multiworld.random)
#
while ("Pokemon Tower 6F" in slot.name and
self.multiworld.get_location("Pokemon Tower 6F - Restless Soul", self.player).item.name
== f"Missable {mon}"):
# If you're fighting the Pokémon defined as the Restless Soul, and you're on the 6th floor of the tower,
# 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.
# 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)
placed_mons[mon] += 1
location.item = self.create_item(mon)
location.event = True
location.locked = True
location.item.location = location
locations.append(location)
zone_mapping[zone][original_mon] = mon
mons_to_add = []
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)]
if self.multiworld.catch_em_all[self.player] == "first_stage":
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)]
elif self.multiworld.catch_em_all[self.player] == "all_pokemon":
mons_to_add = remaining_pokemon.copy()
logic_needed_mons = max(self.multiworld.oaks_aide_rt_2[self.player].value,
self.multiworld.oaks_aide_rt_11[self.player].value,
self.multiworld.oaks_aide_rt_15[self.player].value)
if self.multiworld.accessibility[self.player] == "minimal":
logic_needed_mons = 0
self.multiworld.random.shuffle(remaining_pokemon)
while (len([pokemon for pokemon in placed_mons if placed_mons[pokemon] > 0])
+ len(mons_to_add) < logic_needed_mons):
mons_to_add.append(remaining_pokemon.pop())
for mon in mons_to_add:
stat_base = get_base_stat_total(mon)
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"]:
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"]:
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"]],
poke_data.pokemon_data[slot.original_item]["type2"] in
[self.local_poke_data[mon]["type1"], self.local_poke_data[mon]["type2"]]]))
candidate_locations = [self.multiworld.get_location(location.name, self.player) for location in candidate_locations]
for location in candidate_locations:
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 not [self.multiworld.get_location(l.name, self.player) for l in encounter_slots_master
if (not l.name.startswith(zone)) and
self.multiworld.get_location(l.name, self.player).item.name == location.item.name]:
continue
if self.multiworld.catch_em_all[self.player] == "first_stage" and self.multiworld.area_1_to_1_mapping[self.player]:
if not [self.multiworld.get_location(l.name, self.player) for l in encounter_slots_master
if (not l.name.startswith(zone)) and
self.multiworld.get_location(l.name, self.player).item.name == location.item.name and l.name
not in poke_data.evolves_from]:
continue
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]):
continue
if self.multiworld.area_1_to_1_mapping[self.player]:
place_locations = [place_location for place_location in candidate_locations if
place_location.name.startswith(zone) and
place_location.item.name == location.item.name]
else:
place_locations = [location]
for place_location in place_locations:
placed_mons[place_location.item.name] -= 1
place_location.item = self.create_item(mon)
place_location.item.location = place_location
placed_mons[mon] += 1
break
else:
raise Exception
else:
for slot in encounter_slots:
location = self.multiworld.get_location(slot.name, self.player)
location.item = self.create_item(slot.original_item)
location.event = True
location.locked = True
location.item.location = location
placed_mons[location.item.name] += 1

View File

@ -1,11 +1,13 @@
from BaseClasses import ItemClassification
from .poke_data import pokemon_data
class ItemData:
def __init__(self, id, classification, groups):
def __init__(self, item_id, classification, groups):
self.groups = groups
self.classification = classification
self.id = None if id is None else id + 172000000
self.id = None if item_id is None else item_id + 172000000
item_table = {
"Master Ball": ItemData(1, ItemClassification.useful, ["Consumables", "Poke Balls"]),
@ -15,9 +17,9 @@ item_table = {
"Town Map": ItemData(5, ItemClassification.progression_skip_balancing, ["Unique", "Key Items"]),
"Bicycle": ItemData(6, ItemClassification.progression, ["Unique", "Key Items"]),
# "Flippers": ItemData(7, ItemClassification.progression),
#"Safari Ball": ItemData(8, ItemClassification.filler),
# "Safari Ball": ItemData(8, ItemClassification.filler),
"Pokedex": ItemData(9, ItemClassification.progression, ["Unique", "Key Items"]),
"Moon Stone": ItemData(10, ItemClassification.useful, ["Unique", "Evolution Stones"]),
"Moon Stone": ItemData(10, ItemClassification.progression_skip_balancing, ["Unique", "Evolution Stones", "Key Items"]),
"Antidote": ItemData(11, ItemClassification.filler, ["Consumables"]),
"Burn Heal": ItemData(12, ItemClassification.filler, ["Consumables"]),
"Ice Heal": ItemData(13, ItemClassification.filler, ["Consumables"]),
@ -38,23 +40,23 @@ item_table = {
"Earth Badge": ItemData(28, ItemClassification.progression, ["Unique", "Key Items", "Badges"]),
"Escape Rope": ItemData(29, ItemClassification.filler, ["Consumables"]),
"Repel": ItemData(30, ItemClassification.filler, ["Consumables"]),
"Old Amber": ItemData(31, ItemClassification.progression_skip_balancing, ["Unique", "Fossils"]),
"Fire Stone": ItemData(32, ItemClassification.useful, ["Unique", "Evolution Stones"]),
"Thunder Stone": ItemData(33, ItemClassification.useful, ["Unique", "Evolution Stones"]),
"Water Stone": ItemData(34, ItemClassification.useful, ["Unique", "Evolution Stones"]),
"Old Amber": ItemData(31, ItemClassification.progression_skip_balancing, ["Unique", "Fossils", "Key Items"]),
"Fire Stone": ItemData(32, ItemClassification.progression_skip_balancing, ["Unique", "Evolution Stones", "Key Items"]),
"Thunder Stone": ItemData(33, ItemClassification.progression_skip_balancing, ["Unique", "Evolution Stones" "Key Items"]),
"Water Stone": ItemData(34, ItemClassification.progression_skip_balancing, ["Unique", "Evolution Stones", "Key Items"]),
"HP Up": ItemData(35, ItemClassification.filler, ["Consumables", "Vitamins"]),
"Protein": ItemData(36, ItemClassification.filler, ["Consumables", "Vitamins"]),
"Iron": ItemData(37, ItemClassification.filler, ["Consumables", "Vitamins"]),
"Carbos": ItemData(38, ItemClassification.filler, ["Consumables", "Vitamins"]),
"Calcium": ItemData(39, ItemClassification.filler, ["Consumables", "Vitamins"]),
"Rare Candy": ItemData(40, ItemClassification.useful, ["Consumables"]),
"Dome Fossil": ItemData(41, ItemClassification.progression_skip_balancing, ["Unique", "Fossils"]),
"Helix Fossil": ItemData(42, ItemClassification.progression_skip_balancing, ["Unique", "Fossils"]),
"Rare Candy": ItemData(40, ItemClassification.filler, ["Consumables"]),
"Dome Fossil": ItemData(41, ItemClassification.progression_skip_balancing, ["Unique", "Fossils", "Key Items"]),
"Helix Fossil": ItemData(42, ItemClassification.progression_skip_balancing, ["Unique", "Fossils", "Key Items"]),
"Secret Key": ItemData(43, ItemClassification.progression, ["Unique", "Key Items"]),
"Bike Voucher": ItemData(45, ItemClassification.progression, ["Unique", "Key Items"]),
"X Accuracy": ItemData(46, ItemClassification.filler, ["Consumables", "Battle Items"]),
"Leaf Stone": ItemData(47, ItemClassification.useful, ["Unique", "Evolution Stones"]),
"Card Key": ItemData(48, ItemClassification.progression, ["Unique", "Key Items"]),
"Leaf Stone": ItemData(47, ItemClassification.progression_skip_balancing, ["Unique", "Evolution Stones", "Key Items"]),
"Card Key": ItemData(48, ItemClassification.progression, ["Unique", "Key Items", "Card Keys"]),
"Nugget": ItemData(49, ItemClassification.filler, []),
#"Laptop": ItemData(50, ItemClassification.useful, ["Unique"]),
"Poke Doll": ItemData(51, ItemClassification.filler, ["Consumables"]),
@ -75,13 +77,13 @@ item_table = {
"X Defend": ItemData(66, ItemClassification.filler, ["Consumables", "Battle Items"]),
"X Speed": ItemData(67, ItemClassification.filler, ["Consumables", "Battle Items"]),
"X Special": ItemData(68, ItemClassification.filler, ["Consumables", "Battle Items"]),
"Coin Case": ItemData(69, ItemClassification.progression_skip_balancing, ["Unique", "Key Items"]),
"Coin Case": ItemData(69, ItemClassification.progression, ["Unique", "Key Items"]),
"Oak's Parcel": ItemData(70, ItemClassification.progression, ["Unique", "Key Items"]),
"Item Finder": ItemData(71, ItemClassification.progression, ["Unique", "Key Items"]),
"Silph Scope": ItemData(72, ItemClassification.progression, ["Unique", "Key Items"]),
"Poke Flute": ItemData(73, ItemClassification.progression, ["Unique", "Key Items"]),
"Lift Key": ItemData(74, ItemClassification.progression, ["Unique", "Key Items"]),
"Exp. All": ItemData(75, ItemClassification.useful, ["Unique"]),
"Exp. All": ItemData(75, ItemClassification.progression_skip_balancing, ["Unique", "Key Items"]),
"Old Rod": ItemData(76, ItemClassification.progression_skip_balancing, ["Unique", "Key Items", "Rods"]),
"Good Rod": ItemData(77, ItemClassification.progression_skip_balancing, ["Unique", "Key Items", "Rods"]),
"Super Rod": ItemData(78, ItemClassification.progression_skip_balancing, ["Unique", "Key Items", "Rods"]),
@ -105,11 +107,23 @@ item_table = {
"Fire Trap": ItemData(97, ItemClassification.trap, ["Traps"]),
"20 Coins": ItemData(98, ItemClassification.filler, ["Coins"]),
"100 Coins": ItemData(99, ItemClassification.filler, ["Coins"]),
"HM01 Cut": ItemData(196, ItemClassification.progression, ["Unique", "HMs"]),
"HM02 Fly": ItemData(197, ItemClassification.progression, ["Unique", "HMs"]),
"HM03 Surf": ItemData(198, ItemClassification.progression, ["Unique", "HMs"]),
"HM04 Strength": ItemData(199, ItemClassification.progression, ["Unique", "HMs"]),
"HM05 Flash": ItemData(200, ItemClassification.progression, ["Unique", "HMs"]),
"Card Key 2F": ItemData(100, ItemClassification.progression, ["Unique", "Key Items", "Card Keys"]),
"Card Key 3F": ItemData(101, ItemClassification.progression, ["Unique", "Key Items", "Card Keys"]),
"Card Key 4F": ItemData(102, ItemClassification.progression, ["Unique", "Key Items", "Card Keys"]),
"Card Key 5F": ItemData(103, ItemClassification.progression, ["Unique", "Key Items", "Card Keys"]),
"Card Key 6F": ItemData(104, ItemClassification.progression, ["Unique", "Key Items", "Card Keys"]),
"Card Key 7F": ItemData(105, ItemClassification.progression, ["Unique", "Key Items", "Card Keys"]),
"Card Key 8F": ItemData(106, ItemClassification.progression, ["Unique", "Key Items", "Card Keys"]),
"Card Key 9F": ItemData(107, ItemClassification.progression, ["Unique", "Key Items", "Card Keys"]),
"Card Key 10F": ItemData(108, ItemClassification.progression, ["Unique", "Key Items", "Card Keys"]),
"Card Key 11F": ItemData(109, ItemClassification.progression, ["Unique", "Key Items", "Card Keys"]),
"Progressive Card Key": ItemData(110, ItemClassification.progression, ["Unique", "Key Items", "Card Keys"]),
"Sleep Trap": ItemData(111, ItemClassification.trap, ["Traps"]),
"HM01 Cut": ItemData(196, ItemClassification.progression, ["Unique", "HMs", "Key Items"]),
"HM02 Fly": ItemData(197, ItemClassification.progression, ["Unique", "HMs", "Key Items"]),
"HM03 Surf": ItemData(198, ItemClassification.progression, ["Unique", "HMs", "Key Items"]),
"HM04 Strength": ItemData(199, ItemClassification.progression, ["Unique", "HMs", "Key Items"]),
"HM05 Flash": ItemData(200, ItemClassification.progression, ["Unique", "HMs", "Key Items"]),
"TM01 Mega Punch": ItemData(201, ItemClassification.useful, ["Unique", "TMs"]),
"TM02 Razor Wind": ItemData(202, ItemClassification.filler, ["Unique", "TMs"]),
"TM03 Swords Dance": ItemData(203, ItemClassification.useful, ["Unique", "TMs"]),
@ -161,9 +175,27 @@ item_table = {
"TM49 Tri Attack": ItemData(249, ItemClassification.useful, ["Unique", "TMs"]),
"TM50 Substitute": ItemData(250, ItemClassification.useful, ["Unique", "TMs"]),
"Game Corner": ItemData(None, ItemClassification.progression, []),
"Cinnabar Island": ItemData(None, ItemClassification.progression, []),
"Buy Poke Doll": ItemData(None, ItemClassification.progression, []),
"Vending Machine Drinks": ItemData(None, ItemClassification.progression, []),
"Help Bill": ItemData(None, ItemClassification.progression, []),
"Defeat Brock": ItemData(None, ItemClassification.progression, []),
"Defeat Misty": ItemData(None, ItemClassification.progression, []),
"Defeat Lt. Surge": ItemData(None, ItemClassification.progression, []),
"Defeat Erika": ItemData(None, ItemClassification.progression, []),
"Defeat Koga": ItemData(None, ItemClassification.progression, []),
"Defeat Blaine": ItemData(None, ItemClassification.progression, []),
"Defeat Sabrina": ItemData(None, ItemClassification.progression, []),
"Defeat Viridian Gym Giovanni": ItemData(None, ItemClassification.progression, []),
"Seafoam Exit Boulder": ItemData(None, ItemClassification.progression, []),
"Seafoam Boss Boulders": ItemData(None, ItemClassification.progression, []),
"Victory Road Boulder": ItemData(None, ItemClassification.progression, []),
"Fuji Saved": ItemData(None, ItemClassification.progression, []),
"Silph Co Liberated": ItemData(None, ItemClassification.progression, []),
"Become Champion": ItemData(None, ItemClassification.progression, [])
"Become Champion": ItemData(None, ItemClassification.progression, []),
"Trainer Parties": ItemData(None, ItemClassification.filler, [])
}
item_table.update({f"TM{str(i).zfill(2)}": ItemData(i + 456, ItemClassification.filler, ["Unique", "TMs"])
@ -183,4 +215,4 @@ item_table.update(
item_groups = {}
for item, data in item_table.items():
for group in data.groups:
item_groups[group] = item_groups.get(group, []) + [item]
item_groups[group] = item_groups.get(group, []) + [item]

View File

@ -0,0 +1,138 @@
from BaseClasses import CollectionState
from .locations import level_name_list, level_list
def level_scaling(multiworld):
state = CollectionState(multiworld)
locations = set(multiworld.get_filled_locations())
spheres = []
while locations:
sphere = set()
for world in multiworld.get_game_worlds("Pokemon Red and Blue"):
if multiworld.level_scaling[world.player] != "by_spheres_and_distance":
continue
regions = {multiworld.get_region("Menu", world.player)}
checked_regions = set()
distance = 0
while regions:
next_regions = set()
for region in regions:
if not getattr(region, "distance"):
region.distance = distance
next_regions.update({e.connected_region for e in region.exits if e.connected_region not in
checked_regions and e.access_rule(state)})
checked_regions.update(regions)
regions = next_regions
distance += 1
distances = {}
for location in locations:
def reachable():
if location.can_reach(state):
return True
if location.parent_region.name == "Fossil" and state.can_reach("Mt Moon B2F", "Region",
location.player):
# We want areas that are accessible earlier to have lower levels. If an important item is at a
# fossil location, it may not be in logic until much later, despite your ability to potentially
# reach them earlier. We treat them both as reachable right away for this purpose
return True
if (location.name == "Route 25 - Item" and state.can_reach("Route 25", "Region", location.player)
and multiworld.blind_trainers[location.player].value < 100):
# Assume they will take their one chance to get the trainer to walk out of the way to reach
# the item behind them
return True
if (("Rock Tunnel 1F - Wild Pokemon" in location.name
and any([multiworld.get_entrance(e, location.player).connected_region.can_reach(state)
for e in ['Rock Tunnel 1F-NE to Route 10-N',
'Rock Tunnel 1F-NE to Rock Tunnel B1F-E',
'Rock Tunnel 1F-NW to Rock Tunnel B1F-E',
'Rock Tunnel 1F-NW to Rock Tunnel B1F-W',
'Rock Tunnel 1F-S to Route 10-S',
'Rock Tunnel 1F-S to Rock Tunnel B1F-W']])) or
("Rock Tunnel B1F - Wild Pokemon" in location.name and
any([multiworld.get_entrance(e, location.player).connected_region.can_reach(state)
for e in ['Rock Tunnel B1F-E to Rock Tunnel 1F-NE',
'Rock Tunnel B1F-E to Rock Tunnel 1F-NW',
'Rock Tunnel B1F-W to Rock Tunnel 1F-NW',
'Rock Tunnel B1F-W to Rock Tunnel 1F-S']]))):
# Even if checks in Rock Tunnel are out of logic due to lack of Flash, it is very easy to
# wander in the dark and encounter wild Pokémon, even unintentionally while attempting to
# leave the way you entered. We'll count the wild Pokémon as reachable as soon as the Rock
# Tunnel is reachable, so you don't have an opportunity to catch high level Pokémon early.
# If the connections between Rock Tunnel floors are vanilla, you will still potentially
# have very high level Pokémon in B1F if you reach it out of logic, but that would always
# mean intentionally breaking the logic you picked in your yaml, and may require
# defeating trainers in 1F that would be at the higher levels.
return True
return False
if reachable():
sphere.add(location)
parent_region = location.parent_region
if getattr(parent_region, "distance", None) is None:
distance = 0
else:
distance = parent_region.distance
if distance not in distances:
distances[distance] = {location}
else:
distances[distance].add(location)
if sphere:
for distance in sorted(distances.keys()):
spheres.append(distances[distance])
locations -= distances[distance]
else:
spheres.append(locations)
break
for location in sphere:
if not location.item:
continue
if (location.item.game == "Pokemon Red and Blue" and (location.item.name.startswith("Missable ") or
location.item.name.startswith("Static ")) and location.name !=
"Pokemon Tower 6F - Restless Soul"):
# Normally, missable Pokemon (starters, the dojo rewards) are not considered in logic static Pokemon
# are not considered for moves or evolutions, as you could release them and potentially soft 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
# 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
state.collect(multiworld.worlds[location.item.player].create_item(
location.item.name.split("Missable ")[-1].split("Static ")[-1]), True, location)
else:
state.collect(location.item, True, location)
for world in multiworld.get_game_worlds("Pokemon Red and Blue"):
if multiworld.level_scaling[world.player] == "off":
continue
level_list_copy = level_list.copy()
for sphere in spheres:
sphere_objects = {loc.name: loc for loc in sphere if loc.player == world.player
and (loc.type == "Wild Encounter" or "Pokemon" in loc.type) and loc.level is not None}
party_objects = [loc for loc in sphere if loc.player == world.player and loc.type == "Trainer Parties"]
for parties in party_objects:
for party in parties.party_data:
if isinstance(party["level"], int):
sphere_objects[(party["party_address"][0] if isinstance(party["party_address"], list)
else party["party_address"], 0)] = parties
else:
for i, level in enumerate(party["level"]):
sphere_objects[(party["party_address"][0] if isinstance(party["party_address"], list)
else party["party_address"], i)] = parties
ordered_sphere_objects = list(sphere_objects.keys())
ordered_sphere_objects.sort(key=lambda obj: level_name_list.index(obj))
for object in ordered_sphere_objects:
if sphere_objects[object].type == "Trainer Parties":
for party in sphere_objects[object].party_data:
if (isinstance(party["party_address"], list) and party["party_address"][0] == object[0]) or party["party_address"] == object[0]:
if isinstance(party["level"], int):
party["level"] = level_list_copy.pop(0)
else:
party["level"][object[1]] = level_list_copy.pop(0)
break
else:
sphere_objects[object].level = level_list_copy.pop(0)
for world in multiworld.get_game_worlds("Pokemon Red and Blue"):
world.finished_level_scaling.set()

File diff suppressed because it is too large Load Diff

View File

@ -1,86 +1,121 @@
from ..AutoWorld import LogicMixin
import worlds.pokemon_rb.poke_data as poke_data
from . import poke_data
class PokemonLogic(LogicMixin):
def pokemon_rb_can_surf(self, player):
return (((self.has("HM03 Surf", player) and self.can_learn_hm("10000", player))
or self.has("Flippers", player)) and (self.has("Soul Badge", player) or
self.has(self.multiworld.worlds[player].extra_badges.get("Surf"), player)
or self.multiworld.badges_needed_for_hm_moves[player].value == 0))
def can_surf(state, player):
return (((state.has("HM03 Surf", player) and can_learn_hm(state, "Surf", player))
or state.has("Flippers", player)) and (state.has("Soul Badge", player) or
state.has(state.multiworld.worlds[player].extra_badges.get("Surf"), player)
or state.multiworld.badges_needed_for_hm_moves[player].value == 0))
def pokemon_rb_can_cut(self, player):
return ((self.has("HM01 Cut", player) and self.can_learn_hm("100", player) or self.has("Master Sword", player))
and (self.has("Cascade Badge", player) or
self.has(self.multiworld.worlds[player].extra_badges.get("Cut"), player) or
self.multiworld.badges_needed_for_hm_moves[player].value == 0))
def pokemon_rb_can_fly(self, player):
return (((self.has("HM02 Fly", player) and self.can_learn_hm("1000", player)) or self.has("Flute", player)) and
(self.has("Thunder Badge", player) or self.has(self.multiworld.worlds[player].extra_badges.get("Fly"), player)
or self.multiworld.badges_needed_for_hm_moves[player].value == 0))
def can_cut(state, player):
return ((state.has("HM01 Cut", player) and can_learn_hm(state, "Cut", player) or state.has("Master Sword", player))
and (state.has("Cascade Badge", player) or
state.has(state.multiworld.worlds[player].extra_badges.get("Cut"), player) or
state.multiworld.badges_needed_for_hm_moves[player].value == 0))
def pokemon_rb_can_strength(self, player):
return ((self.has("HM04 Strength", player) and self.can_learn_hm("100000", player)) or
self.has("Titan's Mitt", player)) and (self.has("Rainbow Badge", player) or
self.has(self.multiworld.worlds[player].extra_badges.get("Strength"), player)
or self.multiworld.badges_needed_for_hm_moves[player].value == 0)
def pokemon_rb_can_flash(self, player):
return (((self.has("HM05 Flash", player) and self.can_learn_hm("1000000", player)) or self.has("Lamp", player))
and (self.has("Boulder Badge", player) or self.has(self.multiworld.worlds[player].extra_badges.get("Flash"),
player) or self.multiworld.badges_needed_for_hm_moves[player].value == 0))
def can_fly(state, player):
return (((state.has("HM02 Fly", player) and can_learn_hm(state, "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)
or state.multiworld.badges_needed_for_hm_moves[player].value == 0))
def can_learn_hm(self, move, player):
for pokemon, data in self.multiworld.worlds[player].local_poke_data.items():
if self.has(pokemon, player) and data["tms"][6] & int(move, 2):
return True
return False
def pokemon_rb_can_get_hidden_items(self, player):
return self.has("Item Finder", player) or not self.multiworld.require_item_finder[player].value
def can_strength(state, player):
return ((state.has("HM04 Strength", player) and can_learn_hm(state, "Strength", 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)
or state.multiworld.badges_needed_for_hm_moves[player].value == 0)
def pokemon_rb_cerulean_cave(self, count, player):
return len([item for item in
["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Soul Badge", "Marsh Badge",
"Volcano Badge", "Earth Badge", "Bicycle", "Silph Scope", "Item Finder", "Super Rod", "Good Rod",
"Old Rod", "Lift Key", "Card Key", "Town Map", "Coin Case", "S.S. Ticket", "Secret Key",
"Poke Flute", "Mansion Key", "Safari Pass", "Plant Key", "Hideout Key", "HM01 Cut", "HM02 Fly",
"HM03 Surf", "HM04 Strength", "HM05 Flash"] if self.has(item, player)]) >= count
def pokemon_rb_can_pass_guards(self, player):
if self.multiworld.tea[player].value:
return self.has("Tea", player)
else:
return self.can_reach("Celadon City - Counter Man", "Location", player)
def can_flash(state, player):
return (((state.has("HM05 Flash", player) and can_learn_hm(state, "Flash", player)) or state.has("Lamp", player))
and (state.has("Boulder Badge", player) or state.has(state.multiworld.worlds[player].extra_badges.get("Flash"),
player) or state.multiworld.badges_needed_for_hm_moves[player].value == 0))
def pokemon_rb_has_badges(self, count, player):
return len([item for item in ["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Marsh Badge",
"Soul Badge", "Volcano Badge", "Earth Badge"] if self.has(item, player)]) >= count
def pokemon_rb_oaks_aide(self, count, player):
return ((not self.multiworld.require_pokedex[player] or self.has("Pokedex", player))
and self.pokemon_rb_has_pokemon(count, player))
def can_learn_hm(state, move, player):
for pokemon, data in state.multiworld.worlds[player].local_poke_data.items():
if state.has(pokemon, player) and data["tms"][6] & 1 << (["Cut", "Fly", "Surf", "Strength",
"Flash"].index(move) + 2):
return True
return False
def pokemon_rb_has_pokemon(self, count, player):
obtained_pokemon = set()
for pokemon in poke_data.pokemon_data.keys():
if self.has(pokemon, player) or self.has(f"Static {pokemon}", player):
obtained_pokemon.add(pokemon)
return len(obtained_pokemon) >= count
def can_get_hidden_items(state, player):
return state.has("Item Finder", player) or not state.multiworld.require_item_finder[player].value
def pokemon_rb_fossil_checks(self, count, player):
return (self.can_reach('Mt Moon 1F - Southwest Item', 'Location', player) and
self.can_reach('Cinnabar Island - Lab Scientist', 'Location', player) and len(
[item for item in ["Dome Fossil", "Helix Fossil", "Old Amber"] if self.has(item, player)]) >= count)
def pokemon_rb_cinnabar_gym(self, player):
# ensures higher level Pokémon are obtainable before Cinnabar Gym is in logic
return ((self.multiworld.old_man[player] != "vanilla") or (not self.multiworld.extra_key_items[player]) or
self.has("Mansion Key", player) or self.has("Oak's Parcel", player) or self.pokemon_rb_can_surf(player))
def has_key_items(state, count, player):
key_items = (len([item for item in ["Bicycle", "Silph Scope", "Item Finder", "Super Rod", "Good Rod",
"Old Rod", "Lift Key", "Card Key", "Town Map", "Coin Case", "S.S. Ticket",
"Secret Key", "Poke Flute", "Mansion Key", "Safari Pass", "Plant Key",
"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 11F", "Exp. All", "Fire Stone", "Thunder Stone", "Water Stone",
"Leaf Stone"] if state.has(item, player)])
+ min(state.count("Progressive Card Key", player), 10))
return key_items >= count
def pokemon_rb_dojo(self, player):
# ensures higher level Pokémon are obtainable before Fighting Dojo is in logic
return (self.pokemon_rb_can_pass_guards(player) or self.has("Oak's Parcel", player) or
self.pokemon_rb_can_surf(player))
def can_pass_guards(state, player):
if state.multiworld.tea[player]:
return state.has("Tea", player)
else:
return state.has("Vending Machine Drinks", player)
def has_badges(state, count, player):
return len([item for item in ["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Marsh Badge",
"Soul Badge", "Volcano Badge", "Earth Badge"] if state.has(item, player)]) >= count
def oaks_aide(state, count, player):
return ((not state.multiworld.require_pokedex[player] or state.has("Pokedex", player))
and has_pokemon(state, count, player))
def has_pokemon(state, count, player):
obtained_pokemon = set()
for pokemon in poke_data.pokemon_data.keys():
if state.has(pokemon, player) or state.has(f"Static {pokemon}", player):
obtained_pokemon.add(pokemon)
return len(obtained_pokemon) >= count
def fossil_checks(state, count, player):
return (state.can_reach('Mt Moon B2F', 'Region', player) and
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)
def card_key(state, floor, player):
return state.has(f"Card Key {floor}F", player) or state.has("Card Key", player) or \
state.has("Progressive Card Key", player, floor - 1)
def rock_tunnel(state, player):
return can_flash(state, player) or not state.multiworld.dark_rock_tunnel_logic[player]
def route_3(state, player):
if state.multiworld.route_3_condition[player] == "defeat_brock":
return state.has("Defeat Brock", player)
elif state.multiworld.route_3_condition[player] == "defeat_any_gym":
return state.has_any(["Defeat Brock", "Defeat Misty", "Defeat Lt. Surge", "Defeat Erika", "Defeat Koga",
"Defeat Blaine", "Defeat Sabrina", "Defeat Viridian Gym Giovanni"], player)
elif state.multiworld.route_3_condition[player] == "boulder_badge":
return state.has("Boulder Badge", player)
elif state.multiworld.route_3_condition[player] == "any_badge":
return state.has_any(["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Marsh Badge",
"Soul Badge", "Volcano Badge", "Earth Badge"], player)
# open
return True
def evolve_level(state, level, player):
return len([item for item in (
"Defeat Brock", "Defeat Misty", "Defeat Lt. Surge", "Defeat Erika", "Defeat Koga", "Defeat Blaine",
"Defeat Sabrina", "Defeat Viridian Gym Giovanni") if state.has(item, player)]) > level / 7

View File

@ -1,4 +1,3 @@
from Options import Toggle, Choice, Range, SpecialRange, TextChoice, DeathLink
@ -35,21 +34,49 @@ class Goal(Choice):
default = 0
class EliteFourCondition(Range):
class EliteFourBadgesCondition(Range):
"""Number of badges required to challenge the Elite Four once the Indigo Plateau has been reached.
Your rival will reveal the amount needed on the first Route 22 battle (after turning in Oak's Parcel)."""
display_name = "Elite Four Condition"
display_name = "Elite Four Badges Condition"
range_start = 0
range_end = 8
default = 8
class EliteFourKeyItemsCondition(Range):
"""Percentage of available key items (not counting items you can lose) required to challenge the Elite Four. Does
not count HMs. Evolution stones and Exp. All are key items in Archipelago."""
display_name = "Elite Four Key Items Condition"
range_start = 0
range_end = 100
default = 0
total = 0
class EliteFourPokedexCondition(Range):
"""Percentage of logically-reachable Pokemon that must be registered as "owned" in the Pokedex in order to
challenge the Elite Four."""
display_name = "Elite Four Pokedex Condition"
range_start = 0
range_end = 100
default = 0
total = 0
class VictoryRoadCondition(Range):
"""Number of badges required to reach Victory Road."""
display_name = "Victory Road Condition"
"""Number of badges required to reach the front entrance of Victory Road."""
display_name = "Route 23 Condition"
range_start = 0
range_end = 8
default = 8
default = 7
class Route22GateCondition(Range):
"""Number of badges required to pass through the Route 22 Gate"""
display_name = "Route 22 Gate Condition"
range_start = 0
range_end = 7
default = 7
class ViridianGymCondition(Range):
@ -60,13 +87,40 @@ class ViridianGymCondition(Range):
default = 7
class CeruleanCaveCondition(Range):
"""Number of badges, HMs, and key items (not counting items you can lose) required to access Cerulean Cave.
If extra_key_items is turned on, the number chosen will be increased by 4."""
display_name = "Cerulean Cave Condition"
class CeruleanCaveBadgesCondition(Range):
"""Number of badges needed to access the Cerulean Cave entrance in addition to the required Key Items."""
display_name = "Cerulean Cave Badges Condition"
range_start = 0
range_end = 26
default = 20
range_end = 8
default = 4
class CeruleanCaveKeyItemsCondition(Range):
"""Percentage of available key items (not counting items you can lose) required to access the Cerulean Cave
entrance in addition to the required badges. Does not count HMs.
Evolution stones and Exp. All are key items in Archipelago."""
display_name = "Cerulean Cave Key Items Condition"
range_start = 0
range_end = 100
default = 50
total = 0
class Route3Condition(Choice):
"""Set a condition to pass through from Pewter City to Route 3."""
display_name = "Route 3 Condition"
option_open = 0
option_defeat_brock = 1
option_defeat_any_gym = 2
option_boulder_badge = 3
option_any_badge = 4
default = 1
class RobbedHouseOfficer(Toggle):
"""You can disable to remove the requirement to help Bill before you can enter the robbed house in Cerulean City."""
display_name = "Robbed House Officer"
default = 1
class SecondFossilCheckCondition(Range):
@ -78,6 +132,18 @@ class SecondFossilCheckCondition(Range):
default = 3
class FossilCheckItemTypes(Choice):
"""The two fossil checks always contain items for your own game. Here, you can choose what types of items can
appear. Key Items means only advancement items can appear. Unique means key items or TMs may appear. No Key Items
means no advancement items may appear."""
display_name = "Fossil Check Item Types"
option_any = 0
option_key_items = 1
option_unique_items = 2
option_no_key_items = 3
default = 0
class BadgeSanity(Toggle):
"""Shuffle gym badges into the general item pool. If turned off, badges will be shuffled across the 8 gyms."""
display_name = "Badgesanity"
@ -108,8 +174,18 @@ class OldMan(Choice):
default = 1
class ExpAll(Choice):
"""Choose how the Exp. All item is handled. It can be removed entirely, shuffled into the item pool, or you can
start with it."""
display_name = "Exp. All"
option_remove = 0
option_randomize = 1
option_start_with = 2
default = 1
class RandomizePokedex(Choice):
"""Randomize the location of the Pokedex, or start with it. It is required to receive items from Oak's Aides."""
"""Randomize the location of the Pokedex, or start with it."""
display_name = "Randomize Pokedex"
option_vanilla = 0
option_randomize = 1
@ -117,6 +193,14 @@ class RandomizePokedex(Choice):
default = 0
class KeyItemsOnly(Toggle):
"""Shuffle only Key Items. This overrides Randomize Hidden Items, Trainersanity, and Dexsanity.
Sets all non-excluded locations in your game to Priority Locations.
May have high generation failure rates for solo games or small multiworlds, especially with Door Shuffle."""
display_name = "Key Items Only"
default = 0
class Tea(Toggle):
"""Adds a Tea item to the item pool which the Saffron guards require instead of the vending machine drinks.
Adds a location check to the Celadon Mansion 1F, where Tea is acquired in FireRed and LeafGreen."""
@ -131,6 +215,24 @@ class ExtraKeyItems(Toggle):
default = 0
class SplitCardKey(Choice):
"""Splits the Card Key item into 10 different Keys, one for each Silph Co floor 2F through 11F.
Adds location checks to 9 NPCs in Silph Co.
With Progressive, you will always obtain the keys in order from 2F to 11F."""
display_name = "Split Card Key"
option_off = 0
option_on = 1
option_progressive = 2
default = 0
class AllElevatorsLocked(Toggle):
"""Adds requirements to the Celadon Department Store elevator and Silph Co elevators to have the Lift Key.
No logical implications normally, but may have a significant impact on Insanity Door Shuffle."""
display_name = "All Elevators Locked"
default = 1
class ExtraStrengthBoulders(Toggle):
"""Adds Strength Boulders blocking the Route 11 gate, and in Route 13 (can be bypassed with Surf).
This potentially increases the usefulness of Strength as well as the Bicycle."""
@ -177,42 +279,95 @@ class RequirePokedex(Toggle):
class AllPokemonSeen(Toggle):
"""Start with all Pokemon "seen" in your Pokedex. This allows you to see where Pokemon can be encountered in the
wild. Pokemon found by fishing or in the Cerulean Cave are not displayed."""
wild. Pokemon found by fishing or in the Cerulean Cave are not displayed.
The Pokedex also shows which HMs can be learned by Pokemon registered as seen."""
default = 0
display_name = "All Pokemon Seen"
class DexSanity(Toggle):
"""Adds a location check for each Pokemon flagged "Owned" on your Pokedex. If accessibility is set to `locations`
and randomize_wild_pokemon is off, catch_em_all is not `all_pokemon` or randomize_legendary_pokemon is not `any`,
accessibility will be forced to `items` instead, as not all Dexsanity locations can be guaranteed to be considered
reachable in logic.
class DexSanity(SpecialRange):
"""Adds location checks for Pokemon flagged "owned" on your Pokedex. You may specify a percentage of Pokemon to
have checks added. If Accessibility is set to locations, this will be the percentage of all logically reachable
Pokemon that will get a location check added to it. With items or minimal Accessibility, it will be the percentage
of all 151 Pokemon.
If Pokedex is required, the items for Pokemon acquired before acquiring the Pokedex can be found by talking to
Professor Oak or evaluating the Pokedex via Oak's PC."""
display_name = "Dexsanity"
default = 0
range_start = 0
range_end = 100
special_range_names = {
"false": 0,
"true": 100
}
class FreeFlyLocation(Toggle):
"""One random fly destination will be unlocked by default."""
"""One random Fly destination will be unlocked by default."""
display_name = "Free Fly Location"
default = 1
class TownMapFlyLocation(Toggle):
"""One random Fly destination will be unlocked when you obtain the Town Map."""
display_name = "Town Map Fly Location"
default = 0
class DoorShuffle(Choice):
"""Simple: entrances are randomized together in groups: Pokemarts, Gyms, single exit dungeons, dual exit dungeons,
single exit misc interiors, dual exit misc interiors are all shuffled separately. Safari Zone is not shuffled.
Full: Any outdoor entrance may lead to any interior.
Insanity: All rooms in the game are shuffled."""
display_name = "Door Shuffle"
option_off = 0
option_simple = 1
option_full = 2
option_insanity = 3
# Disabled for now, has issues with elevators that need to be resolved
# option_decoupled = 4
default = 0
# remove assertions that blow up checks for decoupled
def __eq__(self, other):
if isinstance(other, self.__class__):
return other.value == self.value
elif isinstance(other, str):
return other == self.current_key
elif isinstance(other, int):
return other == self.value
elif isinstance(other, bool):
return other == bool(self.value)
else:
raise TypeError(f"Can't compare {self.__class__.__name__} with {other.__class__.__name__}")
class WarpTileShuffle(Toggle):
"""Shuffle the warp tiles in Silph Co and Sabrina's Gym among themselves, separately.
On Insanity, turning this off means they are mixed into the general door shuffle instead of only being shuffled
among themselves."""
display_name = "Warp Tile Shuffle"
default = 0
class RandomizeRockTunnel(Toggle):
"""Randomize the layout of Rock Tunnel. This is highly experimental, if you encounter any issues (items or trainers
unreachable, trainers walking over walls, inability to reach end of tunnel, anything looking strange) to
Alchav#8826 in the Archipelago Discord (directly or in #pkmn-red-blue) along with the seed number found on the
signs outside the tunnel."""
"""Randomize the layout of Rock Tunnel.
If Insanity Door Shuffle is on, this will cause only the main entrances to Rock Tunnel to be shuffled."""
display_name = "Randomize Rock Tunnel"
default = 0
class DarkRockTunnelLogic(Toggle):
"""Logically require Flash to traverse the Rock Tunnel, so you are never forced to traverse it in the dark."""
display_name = "Dark Rock Tunnel Logic"
default = 1
class OaksAidRt2(Range):
"""Number of Pokemon registered in the Pokedex required to receive the item from Oak's Aide on Route 2.
Vanilla is 10."""
display_name = "Oak's Aide Route 2"
range_start = 0
range_start = 1
range_end = 80
default = 10
@ -221,7 +376,7 @@ class OaksAidRt11(Range):
"""Number of Pokemon registered in the Pokedex required to receive the item from Oak's Aide on Route 11.
Vanilla is 30."""
display_name = "Oak's Aide Route 11"
range_start = 0
range_start = 1
range_end = 80
default = 20
@ -230,17 +385,39 @@ class OaksAidRt15(Range):
"""Number of Pokemon registered in the Pokedex required to receive the item from Oak's Aide on Route 15.
Vanilla is 50."""
display_name = "Oak's Aide Route 15"
range_start = 0
range_start = 1
range_end = 80
default = 30
class Stonesanity(Toggle):
"""Removes the four evolution stones from the Celadon Department Store and replaces four of the five Moon Stones
in the item pool with the four shop stones. If randomize_hidden_items is off, this will cause the two hidden
Moon Stone locations to be randomized anyway. These are in Pokemon Mansion 1F and Mt Moon B2F."""
display_name = "Stonesanity"
default = 0
class LevelScaling(Choice):
"""Off: Encounters use vanilla game levels.
By Spheres: Levels are scaled by access sphere. Areas reachable in later spheres will have higher levels.
Spheres and Distance: Levels are scaled by access spheres as well as distance from Pallet Town, measured by number
of internal region connections. This is a much more severe curving of levels and may lead to much less variation in
levels found in a particular map. However, it may make the higher door shuffle settings significantly more bearable,
as these options more often result in a smaller number of larger access spheres."""
display_name = "Level Scaling"
option_off = 0
option_by_spheres = 1
option_by_spheres_and_distance = 2
default = 1
class ExpModifier(SpecialRange):
"""Modifier for EXP gained. When specifying a number, exp is multiplied by this amount and divided by 16."""
display_name = "Exp Modifier"
range_start = 0
range_end = 255
default = 16
range_start = default / 4
range_end = 255
special_range_names = {
"half": default / 2,
"normal": default,
@ -351,7 +528,7 @@ class MinimumStepsBetweenEncounters(Range):
"""Minimum number of steps between wild Pokemon encounters."""
display_name = "Minimum Steps Between Encounters"
default = 3
range_start = 0
range_start = 1
range_end = 255
@ -387,6 +564,14 @@ class MoveBalancing(Toggle):
default = 0
class FixCombatBugs(Toggle):
"""Fixes a variety of combat-related bugs. Note that this fixes the Focus Energy bug. The Focus Energy bug causes
critical strike chances to be doubled when Focus Energy has not been used and halved when it is used.
Fixing this bug means critical strike chances outside the use of Focus Energy are quartered from the vanilla rate."""
display_name = "Fix Combat Bugs"
default = 1
class RandomizePokemonMovesets(Choice):
"""Randomize the moves learned by Pokemon. prefer_types will prefer moves that match the type of the Pokemon."""
display_name = "Randomize Pokemon Movesets"
@ -402,6 +587,7 @@ class ConfineTranstormToDitto(Toggle):
display_name = "Confine Transform to Ditto"
default = 1
class StartWithFourMoves(Toggle):
"""If movesets are randomized, this will give all Pokemon 4 starting moves."""
display_name = "Start With Four Moves"
@ -483,6 +669,12 @@ class RandomizePokemonTypes(Choice):
default = 0
class RandomizeMoveTypes(Toggle):
"""Randomize the types of each move."""
display_name = "Randomize Move Types"
default = 0
class SecondaryTypeChance(SpecialRange):
"""If randomize_pokemon_types is on, this is the chance each Pokemon will have a secondary type. If follow_evolutions
is selected, it is the chance a second type will be added at each evolution stage. vanilla will give secondary types
@ -511,7 +703,7 @@ class RandomizeTypeChart(Choice):
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"""
display_name = "Normal Matchups"
default = 143
@ -520,7 +712,7 @@ class NormalMatchups(Range):
class SuperEffectiveMatchups(Range):
"""If 'randomize' is chosen for randomize_type_chart, this will be the weight for super effective matchups.
"""If 'randomize' is chosen for Randomize Type Chart, this will be the weight for super effective matchups.
No effect if 'chaos' is chosen"""
display_name = "Super Effective Matchups"
default = 38
@ -529,7 +721,7 @@ class SuperEffectiveMatchups(Range):
class NotVeryEffectiveMatchups(Range):
"""If 'randomize' is chosen for randomize_type_chart, this will be the weight for not very effective matchups.
"""If 'randomize' is chosen for Randomize Type Chart, this will be the weight for not very effective matchups.
No effect if 'chaos' is chosen"""
display_name = "Not Very Effective Matchups"
default = 38
@ -538,7 +730,7 @@ class NotVeryEffectiveMatchups(Range):
class ImmunityMatchups(Range):
"""If 'randomize' is chosen for randomize_type_chart, this will be the exact number of immunities.
"""If 'randomize' is chosen for Randomize Type Chart, this will be the exact number of immunities.
No effect if 'chaos' is chosen"""
display_name = "Immunity Matchups"
default = 6
@ -547,7 +739,7 @@ class ImmunityMatchups(Range):
class SafariZoneNormalBattles(Toggle):
"""Change the Safari Zone to have standard wild pokemon battles."""
"""Change the Safari Zone to have standard wild Pokemon battles."""
display_name = "Safari Zone Normal Battles"
default = 0
@ -576,7 +768,7 @@ class BetterShops(Choice):
class MasterBallPrice(Range):
"""Price for Master Balls. Can only be bought if better_shops is set to add_master_ball, but this will affect the
"""Price for Master Balls. Can only be bought if Better Shops is set to Add Master Ball, but this will affect the
sell price regardless. Vanilla is 0"""
display_name = "Master Ball Price"
range_end = 999999
@ -628,12 +820,33 @@ class ParalyzeTrapWeight(TrapWeight):
display_name = "Paralyze Trap Weight"
class SleepTrapWeight(TrapWeight):
"""Weights for Sleep Traps. These apply the Sleep status to all your party members, for randomly between 1 and 7 turns."""
display_name = "Sleep Trap Weight"
class IceTrapWeight(TrapWeight):
"""Weights for Ice Traps. These apply the Ice status to all your party members. Don't forget to buy Ice Heals!"""
display_name = "Ice Trap Weight"
default = 0
class PokeDollSkip(Choice):
"""Patch out the Pokemon Tower Poke Doll skip or have this skip considered in logic."""
display_name = "Poke Doll Skip"
option_patched = 0
option_in_logic = 1
default = 0
class BicycleGateSkips(Choice):
"""Patch out the Route 16/18 Bicycle Gate skips or have these skips considered in logic."""
display_name = "Bicycle Gate Skips"
option_patched = 0
option_in_logic = 1
default = 0
class RandomizePokemonPalettes(Choice):
"""Modify palettes of Pokemon. Primary Type will set Pokemons' palettes based on their primary type, Follow
Evolutions will randomize palettes but palettes will remain the same through evolutions (except Eeveelutions),
@ -650,32 +863,49 @@ pokemon_rb_options = {
"trainer_name": TrainerName,
"rival_name": RivalName,
#"goal": Goal,
"elite_four_condition": EliteFourCondition,
"elite_four_badges_condition": EliteFourBadgesCondition,
"elite_four_key_items_condition": EliteFourKeyItemsCondition,
"elite_four_pokedex_condition": EliteFourPokedexCondition,
"victory_road_condition": VictoryRoadCondition,
"route_22_gate_condition": Route22GateCondition,
"viridian_gym_condition": ViridianGymCondition,
"cerulean_cave_condition": CeruleanCaveCondition,
"cerulean_cave_badges_condition": CeruleanCaveBadgesCondition,
"cerulean_cave_key_items_condition": CeruleanCaveKeyItemsCondition,
"route_3_condition": Route3Condition,
"robbed_house_officer": RobbedHouseOfficer,
"second_fossil_check_condition": SecondFossilCheckCondition,
"badgesanity": BadgeSanity,
"fossil_check_item_types": FossilCheckItemTypes,
"exp_all": ExpAll,
"old_man": OldMan,
"randomize_pokedex": RandomizePokedex,
"badgesanity": BadgeSanity,
"badges_needed_for_hm_moves": BadgesNeededForHMMoves,
"key_items_only": KeyItemsOnly,
"tea": Tea,
"extra_key_items": ExtraKeyItems,
"split_card_key": SplitCardKey,
"all_elevators_locked": AllElevatorsLocked,
"extra_strength_boulders": ExtraStrengthBoulders,
"require_item_finder": RequireItemFinder,
"randomize_hidden_items": RandomizeHiddenItems,
"prizesanity": PrizeSanity,
"trainersanity": TrainerSanity,
"dexsanity": DexSanity,
"randomize_pokedex": RandomizePokedex,
"require_pokedex": RequirePokedex,
"all_pokemon_seen": AllPokemonSeen,
"dexsanity": DexSanity,
"oaks_aide_rt_2": OaksAidRt2,
"oaks_aide_rt_11": OaksAidRt11,
"oaks_aide_rt_15": OaksAidRt15,
"badges_needed_for_hm_moves": BadgesNeededForHMMoves,
"free_fly_location": FreeFlyLocation,
"stonesanity": Stonesanity,
"door_shuffle": DoorShuffle,
"warp_tile_shuffle": WarpTileShuffle,
"randomize_rock_tunnel": RandomizeRockTunnel,
"dark_rock_tunnel_logic": DarkRockTunnelLogic,
"free_fly_location": FreeFlyLocation,
"town_map_fly_location": TownMapFlyLocation,
"blind_trainers": BlindTrainers,
"minimum_steps_between_encounters": MinimumStepsBetweenEncounters,
"level_scaling": LevelScaling,
"exp_modifier": ExpModifier,
"randomize_wild_pokemon": RandomizeWildPokemon,
"area_1_to_1_mapping": Area1To1Mapping,
@ -689,6 +919,7 @@ pokemon_rb_options = {
"randomize_trainer_parties": RandomizeTrainerParties,
"trainer_legendaries": TrainerLegendaries,
"move_balancing": MoveBalancing,
"fix_combat_bugs": FixCombatBugs,
"randomize_pokemon_movesets": RandomizePokemonMovesets,
"confine_transform_to_ditto": ConfineTranstormToDitto,
"start_with_four_moves": StartWithFourMoves,
@ -701,6 +932,7 @@ pokemon_rb_options = {
"hm_normal_type_compatibility": HMNormalTypeCompatibility,
"hm_other_type_compatibility": HMOtherTypeCompatibility,
"inherit_tm_hm_compatibility": InheritTMHMCompatibility,
"randomize_move_types": RandomizeMoveTypes,
"randomize_pokemon_types": RandomizePokemonTypes,
"secondary_type_chance": SecondaryTypeChance,
"randomize_type_chart": RandomizeTypeChart,
@ -715,11 +947,14 @@ pokemon_rb_options = {
"master_ball_price": MasterBallPrice,
"starting_money": StartingMoney,
"lose_money_on_blackout": LoseMoneyOnBlackout,
"poke_doll_skip": PokeDollSkip,
"bicycle_gate_skips": BicycleGateSkips,
"trap_percentage": TrapPercentage,
"poison_trap_weight": PoisonTrapWeight,
"fire_trap_weight": FireTrapWeight,
"paralyze_trap_weight": ParalyzeTrapWeight,
"sleep_trap_weight": SleepTrapWeight,
"ice_trap_weight": IceTrapWeight,
"randomize_pokemon_palettes": RandomizePokemonPalettes,
"death_link": DeathLink
}
}

View File

@ -1204,28 +1204,17 @@ tm_moves = [
'Selfdestruct', 'Egg Bomb', 'Fire Blast', 'Swift', 'Skull Bash', 'Softboiled', 'Dream Eater', 'Sky Attack', 'Rest',
'Thunder Wave', 'Psywave', 'Explosion', 'Rock Slide', 'Tri Attack', 'Substitute'
]
#['No Move', 'Pound', 'Karate Chop', 'Doubleslap', 'Comet Punch', 'Fire Punch', 'Ice Punch', 'Thunderpunch', 'Scratch',
# 'Vicegrip', 'Guillotine', 'Cut', 'Gust', 'Wing Attack', 'Fly', 'Bind', 'Slam', 'Vine Whip', 'Stomp', 'Double Kick', 'Jump Kick',
# 'Rolling Kick', 'Sand Attack', 'Headbutt', 'Horn Attack', 'Fury Attack', 'Tackle', 'Wrap', 'Thrash', 'Tail Whip', 'Poison Sting',
# 'Twineedle', 'Pin Missile', 'Leer', 'Bite', 'Growl', 'Roar', 'Sing', 'Supersonic', 'Sonicboom', 'Disable', 'Acid', 'Ember', 'Flamethrower',
# 'Mist', 'Hydro Pump', 'Surf', 'Psybeam', 'Aurora Beam', 'Peck', 'Drill Peck', 'Low Kick', 'Strength', 'Absorb', 'Leech Seed', 'Growth',
# 'Razor Leaf', 'Poisonpowder', 'Stun Spore', 'Sleep Powder', 'Petal Dance', 'String Shot', 'Fire Spin', 'Thundershock', 'Rock Throw', 'Confusion',
# 'Hypnosis', 'Meditate', 'Agility', 'Quick Attack', 'Night Shade', 'Screech', 'Recover', 'Harden', 'Minimize', 'Smokescreen', 'Confuse Ray', 'Withdraw',
# 'Defense Curl', 'Barrier', 'Light Screen', 'Haze', 'Focus Energy', 'Mirror Move', 'Lick', 'Smog', 'Sludge', 'Bone Club', 'Waterfall', 'Clamp', 'Spike Cannon',
# 'Constrict', 'Amnesia', 'Kinesis', 'Hi Jump Kick', 'Glare', 'Poison Gas', 'Barrage', 'Leech Life', 'Lovely Kiss', 'Transform', 'Bubble', 'Dizzy Punch', 'Spore', 'Flash',
# 'Splash', 'Acid Armor', 'Crabhammer', 'Fury Swipes', 'Bonemerang', 'Hyper Fang', 'Sharpen', 'Conversion', 'Super Fang', 'Slash']
# print([i for i in list(moves.keys()) if i not in tm_moves])
# filler_moves = [
# "Razor Wind", "Whirlwind", "Counter", "Teleport", "Bide", "Skull Bash", "Sky Attack", "Psywave",
# "Pound", "Karate Chop", "Doubleslap", "Comet Punch", "Scratch", "Vicegrip", "Gust", "Wing Attack", "Bind",
# "Vine Whip", "Sand Attack", "Fury Attack", "Tackle", "Wrap", "Tail Whip", "Poison Sting", "Twineedle",
# "Leer", "Growl", "Roar", "Sing", "Supersonic", "Sonicboom", "Disable", "Acid", "Ember", "Mist", "Peck", "Absorb",
# "Growth", "Poisonpowder", "String Shot", "Meditate", "Agility", "Screech", "Double Team", "Harden", "Minimize",
# "Smokescreen", "Confuse Ray", "Withdraw", "Defense Curl", "Barrier", "Light Screen", "Haze", "Reflect",
# "Focus Energy", "Lick", "Smog", "Clamp", "Spike Cannon", "Constrict"
#
# ]
evolution_levels = {
'Bulbasaur': 16, 'Ivysaur': 32, 'Charmander': 16, 'Charmeleon': 36, 'Squirtle': 16, 'Wartortle': 36, 'Caterpie': 7,
'Metapod': 10, 'Weedle': 7, 'Kakuna': 10, 'Pidgey': 18, 'Pidgeotto': 36, 'Rattata': 20, 'Spearow': 20, 'Ekans': 22,
'Sandshrew': 22, 'Nidoran F': 16, 'Nidoran M': 16, 'Zubat': 22, 'Oddish': 21, 'Paras': 24, 'Venonat': 31,
'Diglett': 26, 'Meowth': 28, 'Psyduck': 33, 'Mankey': 28, 'Poliwag': 25, 'Abra': 16, 'Kadabra': 35, 'Machop': 28,
'Machoke': 35, 'Bellsprout': 21, 'Tentacool': 30, 'Geodude': 25, 'Graveler': 35, 'Ponyta': 40, 'Slowpoke': 37,
'Magnemite': 30, 'Doduo': 31, 'Seel': 34, 'Grimer': 38, 'Gastly': 25, 'Haunter': 35, 'Drowzee': 26, 'Krabby': 28,
'Voltorb': 30, 'Cubone': 28, 'Koffing': 35, 'Rhyhorn': 42, 'Horsea': 32, 'Goldeen': 33, 'Magikarp': 33,
'Omanyte': 40, 'Kabuto': 40, 'Dratini': 30, 'Dragonair': 55
}
first_stage_pokemon = [pokemon for pokemon in pokemon_data.keys() if pokemon not in evolves_from]

View File

@ -0,0 +1,348 @@
from copy import deepcopy
from . import poke_data
from .rom_addresses import rom_addresses
def set_mon_palettes(self, random, data):
if self.multiworld.randomize_pokemon_palettes[self.player] == "vanilla":
return
pallet_map = {
"Poison": 0x0F,
"Normal": 0x10,
"Ice": 0x11,
"Fire": 0x12,
"Water": 0x13,
"Ghost": 0x14,
"Ground": 0x15,
"Grass": 0x16,
"Psychic": 0x17,
"Electric": 0x18,
"Rock": 0x19,
"Dragon": 0x1F,
"Flying": 0x20,
"Fighting": 0x21,
"Bug": 0x22
}
palettes = []
for mon in poke_data.pokemon_data:
if self.multiworld.randomize_pokemon_palettes[self.player] == "primary_type":
pallet = pallet_map[self.local_poke_data[mon]["type1"]]
elif (self.multiworld.randomize_pokemon_palettes[self.player] == "follow_evolutions" and mon in
poke_data.evolves_from and poke_data.evolves_from[mon] != "Eevee"):
pallet = palettes[-1]
else: # completely_random or follow_evolutions and it is not an evolved form (except eeveelutions)
pallet = random.choice(list(pallet_map.values()))
palettes.append(pallet)
address = rom_addresses["Mon_Palettes"]
for pallet in palettes:
data[address] = pallet
address += 1
def choose_forced_type(chances, random):
n = random.randint(1, 100)
for chance in chances:
if chance[0] >= n:
return chance[1]
return None
def filter_moves(local_move_data, moves, type, random):
ret = []
for move in moves:
if local_move_data[move]["type"] == type or type is None:
ret.append(move)
random.shuffle(ret)
return ret
def get_move(local_move_data, moves, chances, random, starting_move=False):
type = choose_forced_type(chances, random)
filtered_moves = filter_moves(local_move_data, moves, type, random)
for move in filtered_moves:
if (not starting_move) or (local_move_data[move]["accuracy"] > 80 and local_move_data[move]["power"] > 0):
moves.remove(move)
return move
else:
return get_move(local_move_data, moves, [], random, starting_move)
def move_power(move_data):
power = move_data["power"]
if move_data["effect"] in (29, 42):
# 29: two-to-five attacks. 42: trapping effect, two-to-five turns.
power *= 3
elif move_data["effect"] in (77, 44):
# 77: Twineedle. Two attacks and poison chance. 44: Just two attacks
power *= 2
elif move_data["effect"] == 48:
# 25% recoil damage taken. Reduce power considered by that amount
power *= 0.75
elif move_data["effect"] == 3:
# 50% absorb. Increase power considered by that amount
power *= 1.5
elif move_data["effect"] == 39 and move_data["id"] != 91:
# Takes two turns while vulnerable. Dig uses this effect ID but is semi-invulnerable
power *= 0.66
elif move_data["effect"] == 7:
# Faint user
power *= 0.5
elif move_data["id"] in (2, 75, 152, 163,):
# High critical strike moves: Karate Chop, Razor Leaf, Crabhammer, Slash
power *= 2
return power
def process_move_data(self):
self.local_move_data = deepcopy(poke_data.moves)
if self.multiworld.randomize_move_types[self.player]:
for move, data in self.local_move_data.items():
if move == "No Move":
continue
# 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
data["type"] = self.multiworld.random.choice(list(poke_data.type_ids) + (["Normal"] * 4))
if self.multiworld.move_balancing[self.player]:
self.local_move_data["Sing"]["accuracy"] = 30
self.local_move_data["Sleep Powder"]["accuracy"] = 40
self.local_move_data["Spore"]["accuracy"] = 50
self.local_move_data["Sonicboom"]["effect"] = 0
self.local_move_data["Sonicboom"]["power"] = 50
self.local_move_data["Dragon Rage"]["effect"] = 0
self.local_move_data["Dragon Rage"]["power"] = 80
self.local_move_data["Horn Drill"]["effect"] = 0
self.local_move_data["Horn Drill"]["power"] = 70
self.local_move_data["Horn Drill"]["accuracy"] = 90
self.local_move_data["Guillotine"]["effect"] = 0
self.local_move_data["Guillotine"]["power"] = 70
self.local_move_data["Guillotine"]["accuracy"] = 90
self.local_move_data["Fissure"]["effect"] = 0
self.local_move_data["Fissure"]["power"] = 70
self.local_move_data["Fissure"]["accuracy"] = 90
self.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
["No Move"] + poke_data.hm_moves], 50)
else:
self.local_tms = poke_data.tm_moves.copy()
def process_pokemon_data(self):
local_poke_data = deepcopy(poke_data.pokemon_data)
learnsets = deepcopy(poke_data.learnsets)
tms_hms = self.local_tms + poke_data.hm_moves
compat_hms = set()
for mon, mon_data in local_poke_data.items():
if self.multiworld.randomize_pokemon_stats[self.player] == "shuffle":
stats = [mon_data["hp"], mon_data["atk"], mon_data["def"], mon_data["spd"], mon_data["spc"]]
if mon in poke_data.evolves_from:
stat_shuffle_map = local_poke_data[poke_data.evolves_from[mon]]["stat_shuffle_map"]
else:
stat_shuffle_map = self.multiworld.random.sample(range(0, 5), 5)
mon_data["stat_shuffle_map"] = stat_shuffle_map
mon_data["hp"] = stats[stat_shuffle_map[0]]
mon_data["atk"] = stats[stat_shuffle_map[1]]
mon_data["def"] = stats[stat_shuffle_map[2]]
mon_data["spd"] = stats[stat_shuffle_map[3]]
mon_data["spc"] = stats[stat_shuffle_map[4]]
elif self.multiworld.randomize_pokemon_stats[self.player] == "randomize":
first_run = True
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):
first_run = False
total_stats = mon_data["hp"] + mon_data["atk"] + mon_data["def"] + mon_data["spd"] + mon_data["spc"]
for stat in ("hp", "atk", "def", "spd", "spc"):
if mon in poke_data.evolves_from:
mon_data[stat] = local_poke_data[poke_data.evolves_from[mon]][stat]
total_stats -= mon_data[stat]
elif stat == "hp":
mon_data[stat] = 20
total_stats -= 20
else:
mon_data[stat] = 10
total_stats -= 10
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,
self.multiworld.random.randint(1, 101) / 100, self.multiworld.random.randint(1, 101) / 100,
self.multiworld.random.randint(1, 101) / 100]
total_dist = sum(dist)
mon_data["hp"] += int(round(dist[0] / total_dist * total_stats))
mon_data["atk"] += int(round(dist[1] / 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["spc"] += int(round(dist[4] / total_dist * total_stats))
if self.multiworld.randomize_pokemon_types[self.player]:
if self.multiworld.randomize_pokemon_types[self.player].value == 1 and mon in poke_data.evolves_from:
type1 = local_poke_data[poke_data.evolves_from[mon]]["type1"]
type2 = local_poke_data[poke_data.evolves_from[mon]]["type2"]
if type1 == type2:
if self.multiworld.secondary_type_chance[self.player].value == -1:
if mon_data["type1"] != mon_data["type2"]:
while type2 == type1:
type2 = self.multiworld.random.choice(list(poke_data.type_names.values()))
elif self.multiworld.random.randint(1, 100) <= self.multiworld.secondary_type_chance[self.player].value:
type2 = self.multiworld.random.choice(list(poke_data.type_names.values()))
else:
type1 = self.multiworld.random.choice(list(poke_data.type_names.values()))
type2 = type1
if ((self.multiworld.secondary_type_chance[self.player].value == -1 and mon_data["type1"]
!= mon_data["type2"]) or self.multiworld.random.randint(1, 100)
<= self.multiworld.secondary_type_chance[self.player].value):
while type2 == type1:
type2 = self.multiworld.random.choice(list(poke_data.type_names.values()))
mon_data["type1"] = type1
mon_data["type2"] = type2
if self.multiworld.randomize_pokemon_movesets[self.player]:
if self.multiworld.randomize_pokemon_movesets[self.player] == "prefer_types":
if mon_data["type1"] == "Normal" and mon_data["type2"] == "Normal":
chances = [[75, "Normal"]]
elif mon_data["type1"] == "Normal" or mon_data["type2"] == "Normal":
if mon_data["type1"] == "Normal":
second_type = mon_data["type2"]
else:
second_type = mon_data["type1"]
chances = [[30, "Normal"], [85, second_type]]
elif mon_data["type1"] == mon_data["type2"]:
chances = [[60, mon_data["type1"]], [80, "Normal"]]
else:
chances = [[50, mon_data["type1"]], [80, mon_data["type2"]], [85, "Normal"]]
else:
chances = []
moves = list(poke_data.moves.keys())
for move in ["No Move"] + poke_data.hm_moves:
moves.remove(move)
if self.multiworld.confine_transform_to_ditto[self.player]:
moves.remove("Transform")
if self.multiworld.start_with_four_moves[self.player]:
num_moves = 4
else:
num_moves = len([i for i in [mon_data["start move 1"], mon_data["start move 2"],
mon_data["start move 3"], mon_data["start move 4"]] if i != "No Move"])
if mon in learnsets:
num_moves += len(learnsets[mon])
non_power_moves = []
learnsets[mon] = []
for i in range(num_moves):
if i == 0 and mon == "Ditto" and self.multiworld.confine_transform_to_ditto[self.player]:
move = "Transform"
else:
move = get_move(self.local_move_data, moves, chances, self.multiworld.random)
while move == "Transform" and self.multiworld.confine_transform_to_ditto[self.player]:
move = get_move(self.local_move_data, moves, chances, self.multiworld.random)
if self.local_move_data[move]["power"] < 5:
non_power_moves.append(move)
else:
learnsets[mon].append(move)
learnsets[mon].sort(key=lambda move: move_power(self.local_move_data[move]))
if learnsets[mon]:
for move in non_power_moves:
learnsets[mon].insert(self.multiworld.random.randint(1, len(learnsets[mon])), move)
else:
learnsets[mon] = non_power_moves
for i in range(1, 5):
if mon_data[f"start move {i}"] != "No Move" or self.multiworld.start_with_four_moves[self.player]:
mon_data[f"start move {i}"] = learnsets[mon].pop(0)
if self.multiworld.randomize_pokemon_catch_rates[self.player]:
mon_data["catch rate"] = self.multiworld.random.randint(self.multiworld.minimum_catch_rate[self.player],
255)
else:
mon_data["catch rate"] = max(self.multiworld.minimum_catch_rate[self.player], mon_data["catch rate"])
def roll_tm_compat(roll_move):
if self.local_move_data[roll_move]["type"] in [mon_data["type1"], mon_data["type2"]]:
if roll_move in poke_data.hm_moves:
if self.multiworld.hm_same_type_compatibility[self.player].value == -1:
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
if r and mon not in poke_data.legendary_pokemon:
compat_hms.add(roll_move)
return r
else:
if self.multiworld.tm_same_type_compatibility[self.player].value == -1:
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
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 self.multiworld.hm_normal_type_compatibility[self.player].value == -1:
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
if r and mon not in poke_data.legendary_pokemon:
compat_hms.add(roll_move)
return r
else:
if self.multiworld.tm_normal_type_compatibility[self.player].value == -1:
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
else:
if roll_move in poke_data.hm_moves:
if self.multiworld.hm_other_type_compatibility[self.player].value == -1:
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
if r and mon not in poke_data.legendary_pokemon:
compat_hms.add(roll_move)
return r
else:
if self.multiworld.tm_other_type_compatibility[self.player].value == -1:
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
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 local_poke_data[poke_data.evolves_from[mon]]["tms"][int(flag / 8)] & 1 << (flag % 8):
# always inherit learnable tms/hms
bit = 1
else:
if self.local_move_data[tm_move]["type"] in [mon_data["type1"], mon_data["type2"]] and \
self.local_move_data[tm_move]["type"] not in [
local_poke_data[poke_data.evolves_from[mon]]["type1"],
local_poke_data[poke_data.evolves_from[mon]]["type2"]]:
# the tm/hm is for a move whose type matches current mon, but not pre-evolved form
# so this gets full chance roll
bit = roll_tm_compat(tm_move)
# 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):
bit = 1
else:
bit = 0
else:
bit = roll_tm_compat(tm_move)
if bit:
mon_data["tms"][int(flag / 8)] |= 1 << (flag % 8)
else:
mon_data["tms"][int(flag / 8)] &= ~(1 << (flag % 8))
hm_verify = ["Surf", "Strength"]
if self.multiworld.accessibility[self.player] == "locations" or ((not
self.multiworld.badgesanity[self.player]) and max(self.multiworld.elite_four_badges_condition[self.player],
self.multiworld.route_22_gate_condition[self.player], self.multiworld.victory_road_condition[self.player])
> 7) or (self.multiworld.door_shuffle[self.player] not in ("off", "simple")):
hm_verify += ["Cut"]
if self.multiworld.accessibility[self.player] == "locations" or (not
self.multiworld.dark_rock_tunnel_logic[self.player]) and ((self.multiworld.trainersanity[self.player] or
self.multiworld.extra_key_items[self.player])
or self.multiworld.door_shuffle[self.player]):
hm_verify += ["Flash"]
# Fly does not need to be verified. Full/Insanity 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 reachable, but if no Pokémon can
# learn it this simply would not occur
for hm_move in hm_verify:
if hm_move not in compat_hms:
mon = self.multiworld.random.choice([mon for mon in poke_data.pokemon_data if mon not in
poke_data.legendary_pokemon])
flag = tms_hms.index(hm_move)
local_poke_data[mon]["tms"][int(flag / 8)] |= 1 << (flag % 8)
self.local_poke_data = local_poke_data
self.learnsets = learnsets

File diff suppressed because it is too large Load Diff

View File

@ -5,548 +5,12 @@ import bsdiff4
from copy import deepcopy
from worlds.Files import APDeltaPatch
from .text import encode_text
from .rom_addresses import rom_addresses
from .locations import location_data
from .items import item_table
from .pokemon import set_mon_palettes
from .rock_tunnel import randomize_rock_tunnel
import worlds.pokemon_rb.poke_data as poke_data
def choose_forced_type(chances, random):
n = random.randint(1, 100)
for chance in chances:
if chance[0] >= n:
return chance[1]
return None
def filter_moves(moves, type, random):
ret = []
for move in moves:
if poke_data.moves[move]["type"] == type or type is None:
ret.append(move)
random.shuffle(ret)
return ret
def get_move(local_move_data, moves, chances, random, starting_move=False):
type = choose_forced_type(chances, random)
filtered_moves = filter_moves(moves, type, random)
for move in filtered_moves:
if local_move_data[move]["accuracy"] > 80 and local_move_data[move]["power"] > 0 or not starting_move:
moves.remove(move)
return move
else:
return get_move(local_move_data, moves, [], random, starting_move)
def get_encounter_slots(self):
encounter_slots = deepcopy([location for location in location_data if location.type == "Wild Encounter"])
for location in encounter_slots:
if isinstance(location.original_item, list):
location.original_item = location.original_item[not self.multiworld.game_version[self.player].value]
return encounter_slots
def get_base_stat_total(mon):
return (poke_data.pokemon_data[mon]["atk"] + poke_data.pokemon_data[mon]["def"]
+ poke_data.pokemon_data[mon]["hp"] + poke_data.pokemon_data[mon]["spd"]
+ poke_data.pokemon_data[mon]["spc"])
def randomize_pokemon(self, mon, mons_list, randomize_type, random):
if randomize_type in [1, 3]:
type_mons = [pokemon for pokemon in mons_list if any([poke_data.pokemon_data[mon][
"type1"] in [self.local_poke_data[pokemon]["type1"], self.local_poke_data[pokemon]["type2"]],
poke_data.pokemon_data[mon]["type2"] in [self.local_poke_data[pokemon]["type1"],
self.local_poke_data[pokemon]["type2"]]])]
if not type_mons:
type_mons = mons_list.copy()
if randomize_type == 3:
stat_base = get_base_stat_total(mon)
type_mons.sort(key=lambda mon: abs(get_base_stat_total(mon) - stat_base))
mon = type_mons[round(random.triangular(0, len(type_mons) - 1, 0))]
if randomize_type == 2:
stat_base = get_base_stat_total(mon)
mons_list.sort(key=lambda mon: abs(get_base_stat_total(mon) - stat_base))
mon = mons_list[round(random.triangular(0, 50, 0))]
elif randomize_type == 4:
mon = random.choice(mons_list)
return mon
def set_mon_palettes(self, random, data):
if self.multiworld.randomize_pokemon_palettes[self.player] == "vanilla":
return
pallet_map = {
"Poison": 0x0F,
"Normal": 0x10,
"Ice": 0x11,
"Fire": 0x12,
"Water": 0x13,
"Ghost": 0x14,
"Ground": 0x15,
"Grass": 0x16,
"Psychic": 0x17,
"Electric": 0x18,
"Rock": 0x19,
"Dragon": 0x1F,
"Flying": 0x20,
"Fighting": 0x21,
"Bug": 0x22
}
palettes = []
for mon in poke_data.pokemon_data:
if self.multiworld.randomize_pokemon_palettes[self.player] == "primary_type":
pallet = pallet_map[self.local_poke_data[mon]["type1"]]
elif (self.multiworld.randomize_pokemon_palettes[self.player] == "follow_evolutions" and mon in
poke_data.evolves_from and poke_data.evolves_from[mon] != "Eevee"):
pallet = palettes[-1]
else: # completely_random or follow_evolutions and it is not an evolved form (except eeveelutions)
pallet = random.choice(list(pallet_map.values()))
palettes.append(pallet)
address = rom_addresses["Mon_Palettes"]
for pallet in palettes:
data[address] = pallet
address += 1
def process_trainer_data(self, data, random):
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]
address = rom_addresses["Trainer_Data"]
while address < rom_addresses["Trainer_Data_End"]:
if data[address] == 255:
mode = 1
else:
mode = 0
while True:
address += 1
if data[address] == 0:
address += 1
break
address += mode
mon = None
for i in range(1, 4):
for l in ["A", "B", "C", "D", "E", "F", "G", "H"]:
if rom_addresses[f"Rival_Starter{i}_{l}"] == address:
mon = " ".join(self.multiworld.get_location(f"Pallet Town - Starter {i}",
self.player).item.name.split()[1:])
if l in ["D", "E", "F", "G", "H"] and mon in poke_data.evolves_to:
mon = poke_data.evolves_to[mon]
if l in ["F", "G", "H"] and mon in poke_data.evolves_to:
mon = poke_data.evolves_to[mon]
if mon is None and self.multiworld.randomize_trainer_parties[self.player].value:
mon = poke_data.id_to_mon[data[address]]
mon = randomize_pokemon(self, mon, mons_list,
self.multiworld.randomize_trainer_parties[self.player].value, random)
if mon is not None:
data[address] = poke_data.pokemon_data[mon]["id"]
def process_static_pokemon(self):
starter_slots = deepcopy([location for location in location_data if location.type == "Starter Pokemon"])
legendary_slots = deepcopy([location for location in location_data if location.type == "Legendary Pokemon"])
static_slots = deepcopy([location for location in location_data if location.type in
["Static Pokemon", "Missable Pokemon"]])
legendary_mons = deepcopy([slot.original_item for slot in legendary_slots])
tower_6F_mons = set()
for i in range(1, 11):
tower_6F_mons.add(self.multiworld.get_location(f"Pokemon Tower 6F - Wild Pokemon - {i}", self.player).item.name)
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]
if self.multiworld.randomize_legendary_pokemon[self.player].value == 0:
for slot in legendary_slots:
location = self.multiworld.get_location(slot.name, self.player)
location.place_locked_item(self.create_item("Missable " + slot.original_item))
elif self.multiworld.randomize_legendary_pokemon[self.player].value == 1:
self.multiworld.random.shuffle(legendary_mons)
for slot in legendary_slots:
location = self.multiworld.get_location(slot.name, self.player)
location.place_locked_item(self.create_item("Missable " + legendary_mons.pop()))
elif self.multiworld.randomize_legendary_pokemon[self.player].value == 2:
static_slots = static_slots + legendary_slots
self.multiworld.random.shuffle(static_slots)
static_slots.sort(key=lambda s: 0 if s.name == "Pokemon Tower 6F - Restless Soul" else 1)
while legendary_slots:
swap_slot = legendary_slots.pop()
slot = static_slots.pop()
slot_type = slot.type.split()[0]
if slot_type == "Legendary":
slot_type = "Missable"
location = self.multiworld.get_location(slot.name, self.player)
location.place_locked_item(self.create_item(slot_type + " " + swap_slot.original_item))
swap_slot.original_item = slot.original_item
elif self.multiworld.randomize_legendary_pokemon[self.player].value == 3:
static_slots = static_slots + legendary_slots
for slot in static_slots:
location = self.multiworld.get_location(slot.name, self.player)
randomize_type = self.multiworld.randomize_static_pokemon[self.player].value
slot_type = slot.type.split()[0]
if slot_type == "Legendary":
slot_type = "Missable"
if not randomize_type:
location.place_locked_item(self.create_item(slot_type + " " + slot.original_item))
else:
mon = self.create_item(slot_type + " " +
randomize_pokemon(self, slot.original_item, mons_list, randomize_type,
self.multiworld.random))
while location.name == "Pokemon Tower 6F - Restless Soul" and mon in tower_6F_mons:
mon = self.create_item(slot_type + " " + randomize_pokemon(self, slot.original_item, mons_list,
randomize_type, self.multiworld.random))
location.place_locked_item(mon)
chosen_mons = set()
for slot in starter_slots:
location = self.multiworld.get_location(slot.name, self.player)
randomize_type = self.multiworld.randomize_starter_pokemon[self.player].value
slot_type = "Missable"
if not randomize_type:
location.place_locked_item(self.create_item(slot_type + " " + slot.original_item))
else:
mon = self.create_item(slot_type + " " + randomize_pokemon(self, slot.original_item, mons_list,
randomize_type, self.multiworld.random))
while mon.name in chosen_mons:
mon = self.create_item(slot_type + " " + randomize_pokemon(self, slot.original_item, mons_list,
randomize_type, self.multiworld.random))
chosen_mons.add(mon.name)
location.place_locked_item(mon)
def process_wild_pokemon(self):
encounter_slots = get_encounter_slots(self)
placed_mons = {pokemon: 0 for pokemon in poke_data.pokemon_data.keys()}
zone_mapping = {}
if self.multiworld.randomize_wild_pokemon[self.player].value:
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]
self.multiworld.random.shuffle(encounter_slots)
locations = []
for slot in encounter_slots:
location = self.multiworld.get_location(slot.name, self.player)
zone = " - ".join(location.name.split(" - ")[:-1])
if zone not in zone_mapping:
zone_mapping[zone] = {}
original_mon = slot.original_item
if self.multiworld.area_1_to_1_mapping[self.player] and original_mon in zone_mapping[zone]:
mon = zone_mapping[zone][original_mon]
else:
mon = randomize_pokemon(self, original_mon, mons_list,
self.multiworld.randomize_wild_pokemon[self.player].value, self.multiworld.random)
# if static Pokemon are not randomized, we make sure nothing on Pokemon Tower 6F is a Marowak
# if static Pokemon are randomized we deal with that during static encounter randomization
while (self.multiworld.randomize_static_pokemon[self.player].value == 0 and mon == "Marowak"
and "Pokemon Tower 6F" in slot.name):
# 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)
placed_mons[mon] += 1
location.item = self.create_item(mon)
location.event = True
location.locked = True
location.item.location = location
locations.append(location)
zone_mapping[zone][original_mon] = mon
mons_to_add = []
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)]
if self.multiworld.catch_em_all[self.player].value == 1:
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)]
elif self.multiworld.catch_em_all[self.player].value == 2:
mons_to_add = remaining_pokemon.copy()
logic_needed_mons = max(self.multiworld.oaks_aide_rt_2[self.player].value,
self.multiworld.oaks_aide_rt_11[self.player].value,
self.multiworld.oaks_aide_rt_15[self.player].value)
if self.multiworld.accessibility[self.player] == "minimal":
logic_needed_mons = 0
self.multiworld.random.shuffle(remaining_pokemon)
while (len([pokemon for pokemon in placed_mons if placed_mons[pokemon] > 0])
+ len(mons_to_add) < logic_needed_mons):
mons_to_add.append(remaining_pokemon.pop())
for mon in mons_to_add:
stat_base = get_base_stat_total(mon)
candidate_locations = get_encounter_slots(self)
if self.multiworld.randomize_wild_pokemon[self.player].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))
if self.multiworld.randomize_wild_pokemon[self.player].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
[self.local_poke_data[mon]["type1"], self.local_poke_data[mon]["type2"]],
poke_data.pokemon_data[slot.original_item]["type2"] in
[self.local_poke_data[mon]["type1"], self.local_poke_data[mon]["type2"]]]))
candidate_locations = [self.multiworld.get_location(location.name, self.player) for location in candidate_locations]
for location in candidate_locations:
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 not [self.multiworld.get_location(l.name, self.player) for l in get_encounter_slots(self)
if (not l.name.startswith(zone)) and
self.multiworld.get_location(l.name, self.player).item.name == location.item.name]:
continue
if self.multiworld.catch_em_all[self.player] == "first_stage" and self.multiworld.area_1_to_1_mapping[self.player]:
if not [self.multiworld.get_location(l.name, self.player) for l in get_encounter_slots(self)
if (not l.name.startswith(zone)) and
self.multiworld.get_location(l.name, self.player).item.name == location.item.name and l.name
not in poke_data.evolves_from]:
continue
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]):
continue
if self.multiworld.area_1_to_1_mapping[self.player]:
place_locations = [place_location for place_location in candidate_locations if
place_location.name.startswith(zone) and
place_location.item.name == location.item.name]
else:
place_locations = [location]
for place_location in place_locations:
placed_mons[place_location.item.name] -= 1
place_location.item = self.create_item(mon)
place_location.item.location = place_location
placed_mons[mon] += 1
break
else:
raise Exception
else:
for slot in encounter_slots:
location = self.multiworld.get_location(slot.name, self.player)
location.item = self.create_item(slot.original_item)
location.event = True
location.locked = True
location.item.location = location
placed_mons[location.item.name] += 1
def process_move_data(self):
self.local_move_data = deepcopy(poke_data.moves)
if self.multiworld.move_balancing[self.player]:
self.local_move_data["Sing"]["accuracy"] = 30
self.local_move_data["Sleep Powder"]["accuracy"] = 40
self.local_move_data["Spore"]["accuracy"] = 50
self.local_move_data["Sonicboom"]["effect"] = 0
self.local_move_data["Sonicboom"]["power"] = 50
self.local_move_data["Dragon Rage"]["effect"] = 0
self.local_move_data["Dragon Rage"]["power"] = 80
self.local_move_data["Horn Drill"]["effect"] = 0
self.local_move_data["Horn Drill"]["power"] = 70
self.local_move_data["Horn Drill"]["accuracy"] = 90
self.local_move_data["Guillotine"]["effect"] = 0
self.local_move_data["Guillotine"]["power"] = 70
self.local_move_data["Guillotine"]["accuracy"] = 90
self.local_move_data["Fissure"]["effect"] = 0
self.local_move_data["Fissure"]["power"] = 70
self.local_move_data["Fissure"]["accuracy"] = 90
self.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
["No Move"] + poke_data.hm_moves], 50)
else:
self.local_tms = poke_data.tm_moves.copy()
def process_pokemon_data(self):
local_poke_data = deepcopy(poke_data.pokemon_data)
learnsets = deepcopy(poke_data.learnsets)
tms_hms = self.local_tms + poke_data.hm_moves
compat_hms = set()
for mon, mon_data in local_poke_data.items():
if self.multiworld.randomize_pokemon_stats[self.player].value == 1:
stats = [mon_data["hp"], mon_data["atk"], mon_data["def"], mon_data["spd"], mon_data["spc"]]
self.multiworld.random.shuffle(stats)
mon_data["hp"] = stats[0]
mon_data["atk"] = stats[1]
mon_data["def"] = stats[2]
mon_data["spd"] = stats[3]
mon_data["spc"] = stats[4]
elif self.multiworld.randomize_pokemon_stats[self.player].value == 2:
first_run = True
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):
first_run = False
total_stats = mon_data["hp"] + mon_data["atk"] + mon_data["def"] + mon_data["spd"] + mon_data["spc"] - 60
dist = [self.multiworld.random.randint(1, 101) / 100, self.multiworld.random.randint(1, 101) / 100,
self.multiworld.random.randint(1, 101) / 100, self.multiworld.random.randint(1, 101) / 100,
self.multiworld.random.randint(1, 101) / 100]
total_dist = sum(dist)
mon_data["hp"] = int(round(dist[0] / total_dist * total_stats) + 20)
mon_data["atk"] = int(round(dist[1] / total_dist * total_stats) + 10)
mon_data["def"] = int(round(dist[2] / total_dist * total_stats) + 10)
mon_data["spd"] = int(round(dist[3] / total_dist * total_stats) + 10)
mon_data["spc"] = int(round(dist[4] / total_dist * total_stats) + 10)
if self.multiworld.randomize_pokemon_types[self.player].value:
if self.multiworld.randomize_pokemon_types[self.player].value == 1 and mon in poke_data.evolves_from:
type1 = local_poke_data[poke_data.evolves_from[mon]]["type1"]
type2 = local_poke_data[poke_data.evolves_from[mon]]["type2"]
if type1 == type2:
if self.multiworld.secondary_type_chance[self.player].value == -1:
if mon_data["type1"] != mon_data["type2"]:
while type2 == type1:
type2 = self.multiworld.random.choice(list(poke_data.type_names.values()))
elif self.multiworld.random.randint(1, 100) <= self.multiworld.secondary_type_chance[self.player].value:
type2 = self.multiworld.random.choice(list(poke_data.type_names.values()))
else:
type1 = self.multiworld.random.choice(list(poke_data.type_names.values()))
type2 = type1
if ((self.multiworld.secondary_type_chance[self.player].value == -1 and mon_data["type1"]
!= mon_data["type2"]) or self.multiworld.random.randint(1, 100)
<= self.multiworld.secondary_type_chance[self.player].value):
while type2 == type1:
type2 = self.multiworld.random.choice(list(poke_data.type_names.values()))
mon_data["type1"] = type1
mon_data["type2"] = type2
if self.multiworld.randomize_pokemon_movesets[self.player].value:
if self.multiworld.randomize_pokemon_movesets[self.player].value == 1:
if mon_data["type1"] == "Normal" and mon_data["type2"] == "Normal":
chances = [[75, "Normal"]]
elif mon_data["type1"] == "Normal" or mon_data["type2"] == "Normal":
if mon_data["type1"] == "Normal":
second_type = mon_data["type2"]
else:
second_type = mon_data["type1"]
chances = [[30, "Normal"], [85, second_type]]
elif mon_data["type1"] == mon_data["type2"]:
chances = [[60, mon_data["type1"]], [80, "Normal"]]
else:
chances = [[50, mon_data["type1"]], [80, mon_data["type2"]], [85, "Normal"]]
else:
chances = []
moves = list(poke_data.moves.keys())
for move in ["No Move"] + poke_data.hm_moves:
moves.remove(move)
if self.multiworld.confine_transform_to_ditto[self.player]:
moves.remove("Transform")
if self.multiworld.start_with_four_moves[self.player]:
num_moves = 4
else:
num_moves = len([i for i in [mon_data["start move 1"], mon_data["start move 2"],
mon_data["start move 3"], mon_data["start move 4"]] if i != "No Move"])
if mon in learnsets:
num_moves += len(learnsets[mon])
non_power_moves = []
learnsets[mon] = []
for i in range(num_moves):
if i == 0 and mon == "Ditto" and self.multiworld.confine_transform_to_ditto[self.player]:
move = "Transform"
else:
move = get_move(self.local_move_data, moves, chances, self.multiworld.random)
while move == "Transform" and self.multiworld.confine_transform_to_ditto[self.player]:
move = get_move(self.local_move_data, moves, chances, self.multiworld.random)
if self.local_move_data[move]["power"] < 5:
non_power_moves.append(move)
else:
learnsets[mon].append(move)
learnsets[mon].sort(key=lambda move: self.local_move_data[move]["power"])
if learnsets[mon]:
for move in non_power_moves:
learnsets[mon].insert(self.multiworld.random.randint(1, len(learnsets[mon])), move)
else:
learnsets[mon] = non_power_moves
for i in range(1, 5):
if mon_data[f"start move {i}"] != "No Move" or self.multiworld.start_with_four_moves[self.player]:
mon_data[f"start move {i}"] = learnsets[mon].pop(0)
if self.multiworld.randomize_pokemon_catch_rates[self.player].value:
mon_data["catch rate"] = self.multiworld.random.randint(self.multiworld.minimum_catch_rate[self.player], 255)
else:
mon_data["catch rate"] = max(self.multiworld.minimum_catch_rate[self.player], mon_data["catch rate"])
def roll_tm_compat(roll_move):
if self.local_move_data[roll_move]["type"] in [mon_data["type1"], mon_data["type2"]]:
if roll_move in poke_data.hm_moves:
if self.multiworld.hm_same_type_compatibility[self.player].value == -1:
return mon_data["tms"][int(flag / 8)]
r = self.multiworld.random.randint(1, 100) <= self.multiworld.hm_same_type_compatibility[self.player].value
if r and mon not in poke_data.legendary_pokemon:
compat_hms.add(roll_move)
return r
else:
if self.multiworld.tm_same_type_compatibility[self.player].value == -1:
return mon_data["tms"][int(flag / 8)]
return self.multiworld.random.randint(1, 100) <= self.multiworld.tm_same_type_compatibility[self.player].value
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 self.multiworld.hm_normal_type_compatibility[self.player].value == -1:
return mon_data["tms"][int(flag / 8)]
r = self.multiworld.random.randint(1, 100) <= self.multiworld.hm_normal_type_compatibility[self.player].value
if r and mon not in poke_data.legendary_pokemon:
compat_hms.add(roll_move)
return r
else:
if self.multiworld.tm_normal_type_compatibility[self.player].value == -1:
return mon_data["tms"][int(flag / 8)]
return self.multiworld.random.randint(1, 100) <= self.multiworld.tm_normal_type_compatibility[self.player].value
else:
if roll_move in poke_data.hm_moves:
if self.multiworld.hm_other_type_compatibility[self.player].value == -1:
return mon_data["tms"][int(flag / 8)]
r = self.multiworld.random.randint(1, 100) <= self.multiworld.hm_other_type_compatibility[self.player].value
if r and mon not in poke_data.legendary_pokemon:
compat_hms.add(roll_move)
return r
else:
if self.multiworld.tm_other_type_compatibility[self.player].value == -1:
return mon_data["tms"][int(flag / 8)]
return self.multiworld.random.randint(1, 100) <= self.multiworld.tm_other_type_compatibility[self.player].value
for flag, tm_move in enumerate(tms_hms):
if mon in poke_data.evolves_from.keys() and self.multiworld.inherit_tm_hm_compatibility[self.player]:
if local_poke_data[poke_data.evolves_from[mon]]["tms"][int(flag / 8)] & 1 << (flag % 8):
# always inherit learnable tms/hms
bit = 1
else:
if self.local_move_data[tm_move]["type"] in [mon_data["type1"], mon_data["type2"]] and \
self.local_move_data[tm_move]["type"] not in [
local_poke_data[poke_data.evolves_from[mon]]["type1"],
local_poke_data[poke_data.evolves_from[mon]]["type2"]]:
# the tm/hm is for a move whose type matches current mon, but not pre-evolved form
# so this gets full chance roll
bit = roll_tm_compat(tm_move)
# 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):
bit = 1
else:
bit = 0
else:
bit = roll_tm_compat(tm_move)
if bit:
mon_data["tms"][int(flag / 8)] |= 1 << (flag % 8)
else:
mon_data["tms"][int(flag / 8)] &= ~(1 << (flag % 8))
hm_verify = ["Surf", "Strength"]
if self.multiworld.accessibility[self.player] != "minimal" or ((not
self.multiworld.badgesanity[self.player]) and max(self.multiworld.elite_four_condition[self.player],
self.multiworld.victory_road_condition[self.player]) > 7):
hm_verify += ["Cut"]
if self.multiworld.accessibility[self.player] != "minimal" and (self.multiworld.trainersanity[self.player] or
self.multiworld.extra_key_items[self.player]):
hm_verify += ["Flash"]
for hm_move in hm_verify:
if hm_move not in compat_hms:
mon = self.multiworld.random.choice([mon for mon in poke_data.pokemon_data if mon not in
poke_data.legendary_pokemon])
flag = tms_hms.index(hm_move)
local_poke_data[mon]["tms"][int(flag / 8)] |= 1 << (flag % 8)
self.local_poke_data = local_poke_data
self.learnsets = learnsets
from .rom_addresses import rom_addresses
from .regions import PokemonRBWarp, map_ids
from . import poke_data
def write_quizzes(self, data, random):
@ -599,7 +63,7 @@ def write_quizzes(self, data, random):
return encode_text(f"#mon is<LINE>pronounced<CONT>Po-kuh-mon?<DONE>")
elif q == 3:
starters = [" ".join(self.multiworld.get_location(
f"Pallet Town - Starter {i}", self.player).item.name.split(" ")[1:]) for i in range(1, 4)]
f"Oak's Lab - Starter {i}", self.player).item.name.split(" ")[1:]) for i in range(1, 4)]
mon = random.choice(starters)
nots = random.choice(range(8, 16, 2))
if random.randint(0, 1):
@ -630,12 +94,12 @@ def write_quizzes(self, data, random):
elif q == 5:
i = 8
while not a and i in [1, 8]:
i = random.randint(0, 99999999)
i = random.randint(0, int("99999999"[random.randint(0, 7):]))
return encode_text(f"There are {i}<LINE>certified #MON<CONT>LEAGUE BADGEs?<DONE>")
elif q == 6:
i = 2
while not a and i in [1, 2]:
i = random.randint(0, 99)
i = random.randint(0, random.choice([9, 99]))
return encode_text(f"POLIWAG evolves {i}<LINE>times?<DONE>")
elif q == 7:
entity = "Motor Carrier"
@ -644,11 +108,82 @@ def write_quizzes(self, data, random):
return encode_text("Title 49 of the<LINE>U.S. Code of<CONT>Federal<CONT>Regulations part<CONT>397.67 states"
f"<CONT>that the<CONT>{entity}<CONT>is responsible<CONT>for planning<CONT>routes when"
"<CONT>hazardous<CONT>materials are<CONT>transported?<DONE>")
elif q == 8:
mon = random.choice(list(poke_data.evolution_levels.keys()))
level = poke_data.evolution_levels[mon]
if not a:
level += random.choice(range(1, 6)) * random.choice((-1, 1))
return encode_text(f"{mon} evolves<LINE>at level {level}?<DONE>")
elif q == 9:
move = random.choice(list(self.local_move_data.keys()))
actual_type = self.local_move_data[move]["type"]
question_type = actual_type
while question_type == actual_type and not a:
question_type = random.choice(list(poke_data.type_ids.keys()))
return encode_text(f"{move} is<LINE>{question_type} type?<DONE>")
elif q == 10:
mon = random.choice(list(poke_data.pokemon_data.keys()))
actual_type = self.local_poke_data[mon][random.choice(("type1", "type2"))]
question_type = actual_type
while question_type in [self.local_poke_data[mon]["type1"], self.local_poke_data[mon]["type2"]] and not a:
question_type = random.choice(list(poke_data.type_ids.keys()))
return encode_text(f"{mon} is<LINE>{question_type} type?<DONE>")
elif q == 11:
equation = ""
while "*" not in equation:
equation = f"{random.randint(0, 9)} {random.choice(['+', '-', '*'])} {random.randint(0, 9)} {random.choice(['+', '-', '*'])} {random.randint(0, 9)} {random.choice(['+', '-', '*'])} {random.randint(0, 9)}"
result = eval(equation)
question_result = result
if not a:
modifiers = random.sample(range(3), 3)
for modifier in modifiers:
question_result = eval(equation[:modifier * 4] + "(" + equation[modifier * 4:(modifier * 4) + 5]
+ ")" + equation[5 + (modifier * 4):])
if question_result != result:
break
else:
question_result += random.choice(range(1, 6)) * random.choice((-1, 1))
answers = [random.randint(0, 1), random.randint(0, 1), random.randint(0, 1),
random.randint(0, 1), random.randint(0, 1), random.randint(0, 1)]
return encode_text(f"{equation}<LINE>= {question_result}?<DONE>")
elif q == 12:
route = random.choice((12, 16))
actual_mon = self.multiworld.get_location(f"Route {route} - Sleeping Pokemon",
self.player).item.name.split("Static ")[1]
question_mon = actual_mon
while question_mon == actual_mon and not a:
question_mon = random.choice(list(poke_data.pokemon_data.keys()))
return encode_text(f"{question_mon} was<LINE>sleeping on route<CONT>{route}?<DONE>")
elif q == 13:
type1 = 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 "]
for matchup in self.type_chart:
if matchup[0] == type1 and matchup[1] == type2:
if matchup[2] > 10:
eff = eff_msgs[0]
elif matchup[2] == 0:
eff = eff_msgs[1]
elif matchup[2] < 10:
eff = eff_msgs[2]
else:
eff = eff_msgs[3]
break
else:
eff = eff_msgs[3]
if not a:
eff_msgs.remove(eff)
eff = random.choice(eff_msgs)
return encode_text(f"{type1} deals<LINE>{eff}damage to<CONT>{type2} type?<DONE>")
elif q == 14:
fossil_level = self.multiworld.get_location("Fossil Level - Trainer Parties",
self.player).party_data[0]['level']
if not a:
fossil_level += random.choice((-5, 5))
return encode_text(f"Fossil #MON<LINE>revive at level<CONT>{fossil_level}?<DONE>")
questions = random.sample((range(0, 8)), 6)
answers = [random.randint(0, 1) for _ in range(6)]
questions = random.sample((range(0, 15)), 6)
question_texts = []
for i, question in enumerate(questions):
question_texts.append(get_quiz(question, answers[i]))
@ -670,27 +205,52 @@ def generate_output(self, output_directory: str):
basemd5 = hashlib.md5()
basemd5.update(data)
for location in self.multiworld.get_locations():
if location.player != self.player or location.rom_address is None:
continue
if location.item and location.item.player == self.player:
if location.rom_address:
rom_address = location.rom_address
if not isinstance(rom_address, list):
rom_address = [rom_address]
for address in rom_address:
if location.item.name in poke_data.pokemon_data.keys():
data[address] = poke_data.pokemon_data[location.item.name]["id"]
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"]
else:
item_id = self.item_name_to_id[location.item.name] - 172000000
if item_id > 255:
item_id -= 256
data[address] = item_id
lab_loc = self.multiworld.get_entrance("Oak's Lab to Pallet Town", self.player).target
paths = None
if lab_loc == 0: # Player's House
paths = ((0x00, 4, 0x80, 5, 0x40, 1, 0xE0, 1, 0xFF), (0x40, 2, 0x20, 5, 0x80, 5, 0xFF))
elif lab_loc == 1: # Rival's House
paths = ((0x00, 4, 0xC0, 3, 0x40, 1, 0xE0, 1, 0xFF), (0x40, 2, 0x10, 3, 0x80, 5, 0xFF))
if paths:
write_bytes(data, paths[0], rom_addresses["Path_Pallet_Oak"])
write_bytes(data, paths[1], rom_addresses["Path_Pallet_Player"])
home_loc = self.multiworld.get_entrance("Player's House 1F to Pallet Town", self.player).target
if home_loc == 1: # Rival's House
write_bytes(data, [0x2F, 0xC7, 0x06, 0x0D, 0x00, 0x01], rom_addresses["Pallet_Fly_Coords"])
elif home_loc == 2: # Oak's Lab
write_bytes(data, [0x5F, 0xC7, 0x0C, 0x0C, 0x00, 0x00], rom_addresses["Pallet_Fly_Coords"])
else:
data[location.rom_address] = 0x2C # AP Item
for region in self.multiworld.get_regions(self.player):
for entrance in region.exits:
if isinstance(entrance, PokemonRBWarp):
self.multiworld.spoiler.set_entrance(entrance.name, entrance.connected_region.name, "entrance",
self.player)
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
for i, warp_id in enumerate(warp_ids):
address = rom_addresses[entrance.address]
if "Elevator" in entrance.parent_region.name:
address += (2 * warp_id)
else:
address += (4 * warp_id)
while i > len(warp_to_ids) - 1:
i -= len(warp_to_ids)
connected_map_name = entrance.connected_region.name.split("-")[0]
data[address] = 0 if "Elevator" in connected_map_name else warp_to_ids[i]
data[address + 1] = map_ids[connected_map_name]
for i, gym_leader in enumerate(("Pewter Gym - Brock TM", "Cerulean Gym - Misty TM",
"Vermilion Gym - Lt. Surge TM", "Celadon Gym - Erika TM",
"Fuchsia Gym - Koga TM", "Saffron Gym - Sabrina TM",
"Cinnabar Gym - Blaine TM", "Viridian Gym - Giovanni TM")):
item_name = self.multiworld.get_location(gym_leader, self.player).item.name
if item_name.startswith("TM"):
try:
tm = int(item_name[2:4])
move = poke_data.moves[self.local_tms[tm - 1]]["id"]
data[rom_addresses["Gym_Leader_Moves"] + (2 * i)] = move
except KeyError:
pass
def set_trade_mon(address, loc):
mon = self.multiworld.get_location(loc, self.player).item.name
@ -706,48 +266,106 @@ def generate_output(self, output_directory: str):
set_trade_mon("Trade_Marcel", "Route 24 - Wild Pokemon - 6")
set_trade_mon("Trade_Sailor", "Pokemon Mansion 1F - Wild Pokemon - 3")
set_trade_mon("Trade_Dux", "Route 3 - Wild Pokemon - 2")
set_trade_mon("Trade_Marc", "Route 23 - Super Rod Pokemon - 1")
set_trade_mon("Trade_Lola", "Route 10 - Super Rod Pokemon - 1")
set_trade_mon("Trade_Marc", "Route 23/Cerulean Cave Fishing - Super Rod Pokemon - 1")
set_trade_mon("Trade_Lola", "Route 10/Celadon Fishing - Super Rod Pokemon - 1")
set_trade_mon("Trade_Doris", "Cerulean Cave 1F - Wild Pokemon - 9")
set_trade_mon("Trade_Crinkles", "Route 12 - Wild Pokemon - 4")
data[rom_addresses['Fly_Location']] = self.fly_map_code
data[rom_addresses['Map_Fly_Location']] = self.town_map_fly_map_code
if self.multiworld.fix_combat_bugs[self.player]:
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_HP_Drain_Dream_Eater"]] = 0x1A # ld a, (de)
data[rom_addresses["Option_Fix_Combat_Bugs_PP_Restore"]] = 0xe6 # and a, direct
data[rom_addresses["Option_Fix_Combat_Bugs_PP_Restore"] + 1] = 0b0011111
data[rom_addresses["Option_Fix_Combat_Bugs_Struggle"]] = 0xe6 # and a, direct
data[rom_addresses["Option_Fix_Combat_Bugs_Struggle"] + 1] = 0x3f
data[rom_addresses["Option_Fix_Combat_Bugs_Dig_Fly"]] = 0b10001100
data[rom_addresses["Option_Fix_Combat_Bugs_Heal_Effect"]] = 0x20 # jr nz,
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
if self.multiworld.poke_doll_skip[self.player] == "in_logic":
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"] + 2] = 0x00 # nop
if self.multiworld.bicycle_gate_skips[self.player] == "patched":
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_18_Gate_Fix"]] = 0x00 # nop
data[rom_addresses["Option_Route_18_Gate_Fix"] + 1] = 0x00 # nop
if self.multiworld.door_shuffle[self.player]:
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]:
data[rom_addresses["Option_Locked_Elevator_Celadon"]] = 0x20 # jr nz
data[rom_addresses["Option_Locked_Elevator_Silph"]] = 0x20 # jr nz
if self.multiworld.tea[self.player].value:
data[rom_addresses["Option_Tea"]] = 1
data[rom_addresses["Guard_Drink_List"]] = 0x54
data[rom_addresses["Guard_Drink_List"] + 1] = 0
data[rom_addresses["Guard_Drink_List"] + 2] = 0
write_bytes(data, encode_text("<LINE>Gee, I have the<CONT>worst caffeine<CONT>headache though."
"<PARA>Oh wait there,<LINE>the road's closed.<DONE>"),
rom_addresses["Text_Saffron_Gate"])
data[rom_addresses["Fossils_Needed_For_Second_Item"]] = (
self.multiworld.second_fossil_check_condition[self.player].value)
data[rom_addresses["Option_Lose_Money"]] = int(not self.multiworld.lose_money_on_blackout[self.player].value)
if self.multiworld.extra_key_items[self.player].value:
data[rom_addresses['Options']] |= 4
if self.multiworld.extra_key_items[self.player]:
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_C']] = 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_Blind_Trainers"]] = round(self.multiworld.blind_trainers[self.player].value * 2.55)
data[rom_addresses['Option_Cerulean_Cave_Condition']] = self.multiworld.cerulean_cave_condition[self.player].value
data[rom_addresses["Option_Cerulean_Cave_Badges"]] = self.multiworld.cerulean_cave_badges_condition[self.player].value
data[rom_addresses["Option_Cerulean_Cave_Key_Items"]] = self.multiworld.cerulean_cave_key_items_condition[self.player].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(self.multiworld.cerulean_cave_key_items_condition[self.player].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_Victory_Road_Badges']] = self.multiworld.victory_road_condition[self.player].value
data[rom_addresses['Option_Pokemon_League_Badges']] = self.multiworld.elite_four_condition[self.player].value
data[rom_addresses['Option_Route23_Badges']] = self.multiworld.victory_road_condition[self.player].value
data[rom_addresses['Option_Victory_Road_Badges']] = self.multiworld.route_22_gate_condition[self.player].value
data[rom_addresses['Option_Elite_Four_Pokedex']] = self.multiworld.elite_four_pokedex_condition[self.player].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_Badges']] = self.multiworld.elite_four_badges_condition[self.player].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(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(self.multiworld.elite_four_pokedex_condition[self.player].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"])
data[rom_addresses['Option_Viridian_Gym_Badges']] = self.multiworld.viridian_gym_condition[self.player].value
data[rom_addresses['Option_EXP_Modifier']] = self.multiworld.exp_modifier[self.player].value
if not self.multiworld.require_item_finder[self.player].value:
data[rom_addresses['Option_Itemfinder']] = 0
if self.multiworld.extra_strength_boulders[self.player].value:
if not self.multiworld.require_item_finder[self.player]:
data[rom_addresses['Option_Itemfinder']] = 0 # nop
if self.multiworld.extra_strength_boulders[self.player]:
for i in range(0, 3):
data[rom_addresses['Option_Boulders'] + (i * 3)] = 0x15
if self.multiworld.extra_key_items[self.player].value:
if self.multiworld.extra_key_items[self.player]:
for i in range(0, 4):
data[rom_addresses['Option_Rock_Tunnel_Extra_Items'] + (i * 3)] = 0x15
if self.multiworld.old_man[self.player].value == 2:
if self.multiworld.old_man[self.player] == "open_viridian_city":
data[rom_addresses['Option_Old_Man']] = 0x11
data[rom_addresses['Option_Old_Man_Lying']] = 0x15
data[rom_addresses['Option_Route3_Guard_A']] = self.multiworld.route_3_condition[self.player].value
if self.multiworld.route_3_condition[self.player] == "open":
data[rom_addresses['Option_Route3_Guard_B']] = 0x11
if not self.multiworld.robbed_house_officer[self.player]:
data[rom_addresses['Option_Trashed_House_Guard_A']] = 0x15
data[rom_addresses['Option_Trashed_House_Guard_B']] = 0x11
if self.multiworld.require_pokedex[self.player]:
data[rom_addresses["Require_Pokedex_A"]] = 1
data[rom_addresses["Require_Pokedex_B"]] = 1
data[rom_addresses["Require_Pokedex_C"]] = 1
else:
data[rom_addresses["Require_Pokedex_D"]] = 0x18 # jr
if self.multiworld.dexsanity[self.player]:
data[rom_addresses["Option_Dexsanity_A"]] = 1
data[rom_addresses["Option_Dexsanity_B"]] = 1
@ -759,13 +377,19 @@ def generate_output(self, output_directory: str):
data[rom_addresses["Starting_Money_Low"]] = int(money[4:], 16)
data[rom_addresses["Text_Badges_Needed_Viridian_Gym"]] = encode_text(
str(self.multiworld.viridian_gym_condition[self.player].value))[0]
data[rom_addresses["Text_Rt23_Badges_A"]] = encode_text(
str(self.multiworld.victory_road_condition[self.player].value))[0]
data[rom_addresses["Text_Rt23_Badges_B"]] = encode_text(
str(self.multiworld.victory_road_condition[self.player].value))[0]
data[rom_addresses["Text_Rt23_Badges_C"]] = encode_text(
str(self.multiworld.victory_road_condition[self.player].value))[0]
data[rom_addresses["Text_Rt23_Badges_D"]] = encode_text(
str(self.multiworld.victory_road_condition[self.player].value))[0]
data[rom_addresses["Text_Badges_Needed"]] = encode_text(
str(max(self.multiworld.victory_road_condition[self.player].value,
self.multiworld.elite_four_condition[self.player].value)))[0]
str(self.multiworld.elite_four_badges_condition[self.player].value))[0]
write_bytes(data, encode_text(
" ".join(self.multiworld.get_location("Route 3 - Pokemon For Sale", self.player).item.name.upper().split()[1:])),
" ".join(self.multiworld.get_location("Route 4 Pokemon Center - Pokemon For Sale", self.player).item.name.upper().split()[1:])),
rom_addresses["Text_Magikarp_Salesman"])
write_quizzes(self, data, random)
if self.multiworld.badges_needed_for_hm_moves[self.player].value == 0:
for hm_move in poke_data.hm_moves:
@ -845,7 +469,7 @@ def generate_output(self, output_directory: str):
data[rom_addresses["Option_Always_Half_STAB"]] = int(not self.multiworld.same_type_attack_bonus[self.player].value)
if self.multiworld.better_shops[self.player].value:
if self.multiworld.better_shops[self.player]:
inventory = ["Poke Ball", "Great Ball", "Ultra Ball"]
if self.multiworld.better_shops[self.player].value == 2:
inventory.append("Master Ball")
@ -855,8 +479,11 @@ def generate_output(self, output_directory: str):
shop_data = bytearray([0xFE, len(inventory)])
shop_data += bytearray([item_table[item].id - 172000000 for item in inventory])
shop_data.append(0xFF)
for shop in range(1, 10):
for shop in range(1, 11):
write_bytes(data, shop_data, rom_addresses[f"Shop{shop}"])
if self.multiworld.stonesanity[self.player]:
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 = 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
@ -866,7 +493,6 @@ def generate_output(self, output_directory: str):
data[rom_addresses["Start_Inventory"] + item.code - 172000000] += 1
set_mon_palettes(self, random, data)
process_trainer_data(self, data, random)
for move_data in self.local_move_data.values():
if move_data["id"] == 0:
@ -888,13 +514,13 @@ def generate_output(self, output_directory: str):
for mon in range(0, 16):
data[rom_addresses['Title_Mons'] + mon] = mons.pop()
if self.multiworld.game_version[self.player].value:
mons.sort(key=lambda mon: 0 if mon == self.multiworld.get_location("Pallet Town - Starter 1", self.player).item.name
else 1 if mon == self.multiworld.get_location("Pallet Town - Starter 2", self.player).item.name else
2 if mon == self.multiworld.get_location("Pallet Town - Starter 3", self.player).item.name else 3)
mons.sort(key=lambda mon: 0 if mon == self.multiworld.get_location("Oak's Lab - Starter 1", self.player).item.name
else 1 if mon == self.multiworld.get_location("Oak's Lab - Starter 2", self.player).item.name else
2 if mon == self.multiworld.get_location("Oak's Lab - Starter 3", self.player).item.name else 3)
else:
mons.sort(key=lambda mon: 0 if mon == self.multiworld.get_location("Pallet Town - Starter 2", self.player).item.name
else 1 if mon == self.multiworld.get_location("Pallet Town - Starter 1", self.player).item.name else
2 if mon == self.multiworld.get_location("Pallet Town - Starter 3", self.player).item.name else 3)
mons.sort(key=lambda mon: 0 if mon == self.multiworld.get_location("Oak's Lab - Starter 2", self.player).item.name
else 1 if mon == self.multiworld.get_location("Oak's Lab - Starter 1", self.player).item.name else
2 if mon == self.multiworld.get_location("Oak's Lab - Starter 3", self.player).item.name else 3)
write_bytes(data, encode_text(self.multiworld.seed_name[-20:], 20, True), rom_addresses['Title_Seed'])
slot_name = self.multiworld.player_name[self.player]
@ -912,10 +538,68 @@ def generate_output(self, output_directory: str):
else:
write_bytes(data, self.rival_name, rom_addresses['Rival_Name'])
data[0xFF00] = 2 # client compatibility version
data[0xFF00] = 2 # client compatibility version
write_bytes(data, self.multiworld.seed_name.encode(), 0xFFDB)
write_bytes(data, self.multiworld.player_name[self.player].encode(), 0xFFF0)
self.finished_level_scaling.wait()
write_quizzes(self, data, random)
for location in self.multiworld.get_locations():
if location.player != self.player:
continue
elif location.party_data:
for party in location.party_data:
if not isinstance(party["party_address"], list):
addresses = [rom_addresses[party["party_address"]]]
parties = [party["party"]]
else:
addresses = [rom_addresses[address] for address in party["party_address"]]
parties = party["party"]
levels = party["level"]
for address, party in zip(addresses, parties):
if isinstance(levels, int):
data[address] = levels
address += 1
for mon in party:
data[address] = poke_data.pokemon_data[mon]["id"]
address += 1
else:
address += 1
for level, mon in zip(levels, party):
data[address] = level
data[address + 1] = poke_data.pokemon_data[mon]["id"]
address += 2
assert data[address] == 0 or location.name == "Fossil Level - Trainer Parties"
continue
elif location.rom_address is None:
continue
if location.item and location.item.player == self.player:
if location.rom_address:
rom_address = location.rom_address
if not isinstance(rom_address, list):
rom_address = [rom_address]
for address in rom_address:
if location.item.name in poke_data.pokemon_data.keys():
data[address] = poke_data.pokemon_data[location.item.name]["id"]
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"]
else:
item_id = self.item_name_to_id[location.item.name] - 172000000
if item_id > 255:
item_id -= 256
data[address] = item_id
if location.level:
data[location.level_address] = location.level
else:
rom_address = location.rom_address
if not isinstance(rom_address, list):
rom_address = [rom_address]
for address in rom_address:
data[address] = 0x2C # AP Item
outfilepname = f'_P{self.player}'
outfilepname += f"_{self.multiworld.get_file_safe_player_name(self.player).replace(' ', '_')}" \
if self.multiworld.player_name[self.player] != 'Player%d' % self.player else ''

File diff suppressed because it is too large Load Diff

View File

@ -1,229 +1,279 @@
from ..generic.Rules import add_item_rule, add_rule, location_item_name
from .items import item_groups
from . import logic
def set_rules(world, player):
def set_rules(multiworld, player):
item_rules = {
"Pallet Town - Player's PC": (lambda i: i.player == player and "Badge" not in i.name and "Trap" not in i.name
and i.name != "Pokedex" and "Coins" not in i.name)
# Some items do special things when they are passed into the GiveItem function in the game, but
# withdrawing from the PC or buying from a shop will not call the function and will add the items
# directly to the inventory, so we need to avoid placing these special items (including "AP Item") to
# such places
"Player's House 2F - Player's PC": (lambda i: i.player == player and "Badge" not in i.name and "Trap" not in
i.name and i.name != "Pokedex" and "Coins" not in i.name and "Progressive"
not in i.name)
}
if world.prizesanity[player]:
if multiworld.prizesanity[player]:
def prize_rule(i):
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 2"] = prize_rule
item_rules["Celadon Prize Corner - Item Prize 3"] = prize_rule
if world.accessibility[player] != "locations":
world.get_location("Cerulean City - Bicycle Shop", player).always_allow = (lambda state, item:
if multiworld.accessibility[player] != "locations":
multiworld.get_location("Cerulean Bicycle Shop", player).always_allow = (lambda state, item:
item.name == "Bike Voucher"
and item.player == player)
world.get_location("Fuchsia City - Safari Zone Warden", player).always_allow = (lambda state, item:
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 = {
"Pallet Town - Rival's Sister": lambda state: state.has("Oak's Parcel", player),
"Pallet Town - Oak's Post-Route-22-Rival Gift": lambda state: state.has("Oak's Parcel", player),
"Viridian City - Sleepy Guy": lambda state: state.pokemon_rb_can_cut(player) or state.pokemon_rb_can_surf(player),
"Route 2 - Oak's Aide": lambda state: state.pokemon_rb_oaks_aide(state.multiworld.oaks_aide_rt_2[player].value + 5, player),
"Pewter City - Museum": lambda state: state.pokemon_rb_can_cut(player),
"Cerulean City - Bicycle Shop": lambda state: state.has("Bike Voucher", player)
or location_item_name(state, "Cerulean City - Bicycle Shop", player) == ("Bike Voucher", player),
"Lavender Town - Mr. Fuji": lambda state: state.has("Fuji Saved", player),
"Vermilion Gym - Lt. Surge 1": lambda state: state.pokemon_rb_can_cut(player or state.pokemon_rb_can_surf(player)),
"Vermilion Gym - Lt. Surge 2": lambda state: state.pokemon_rb_can_cut(player or state.pokemon_rb_can_surf(player)),
"Route 11 - Oak's Aide": lambda state: state.pokemon_rb_oaks_aide(state.multiworld.oaks_aide_rt_11[player].value + 5, player),
"Celadon City - Stranded Man": lambda state: state.pokemon_rb_can_surf(player),
"Silph Co 11F - Silph Co President (Card Key)": lambda state: state.has("Card Key", player),
"Fuchsia City - Safari Zone Warden": lambda state: state.has("Gold Teeth", player)
or location_item_name(state, "Fuchsia City - Safari Zone Warden", player) == ("Gold Teeth", player),
"Route 12 - Island Item": lambda state: state.pokemon_rb_can_surf(player),
"Route 12 - Item Behind Cuttable Tree": lambda state: state.pokemon_rb_can_cut(player),
"Route 15 - Oak's Aide": lambda state: state.pokemon_rb_oaks_aide(state.multiworld.oaks_aide_rt_15[player].value + 5, player),
"Route 15 - Item": lambda state: state.pokemon_rb_can_cut(player),
"Route 25 - Item": lambda state: state.pokemon_rb_can_cut(player),
"Fuchsia City - Warden's House Item": lambda state: state.pokemon_rb_can_strength(player),
"Rocket Hideout B4F - Southwest Item (Lift Key)": lambda state: state.has("Lift Key", player),
"Rocket Hideout B4F - Giovanni Item (Lift Key)": lambda state: state.has("Lift Key", player),
"Silph Co 3F - Item (Card Key)": lambda state: state.has("Card Key", player),
"Silph Co 4F - Left Item (Card Key)": lambda state: state.has("Card Key", player),
"Silph Co 4F - Middle Item (Card Key)": lambda state: state.has("Card Key", player),
"Silph Co 4F - Right Item (Card Key)": lambda state: state.has("Card Key", player),
"Silph Co 5F - Northwest Item (Card Key)": lambda state: state.has("Card Key", player),
"Silph Co 6F - West Item (Card Key)": lambda state: state.has("Card Key", player),
"Silph Co 6F - Southwest Item (Card Key)": lambda state: state.has("Card Key", player),
"Silph Co 7F - East Item (Card Key)": lambda state: state.has("Card Key", player),
"Safari Zone Center - Island Item": lambda state: state.pokemon_rb_can_surf(player),
"Celadon Prize Corner - Item Prize 1": lambda state: state.has("Coin Case", player),
"Celadon Prize Corner - Item Prize 2": lambda state: state.has("Coin Case", player),
"Celadon Prize Corner - Item Prize 3": lambda state: state.has("Coin Case", player),
"Celadon Game Corner - West Gambler's Gift (Coin Case)": lambda state: state.has("Coin Case", player),
"Celadon Game Corner - Center Gambler's Gift (Coin Case)": lambda state: state.has("Coin Case", player),
"Celadon Game Corner - East Gambler's Gift (Coin Case)": lambda state: state.has("Coin Case", player),
"Celadon Game Corner - Hidden Item Northwest By Counter (Coin Case)": lambda state: state.has("Coin Case", player) and state.pokemon_rb_can_get_hidden_items(player),
"Celadon Game Corner - Hidden Item Southwest Corner (Coin Case)": lambda state: state.has("Coin Case", player) and state.pokemon_rb_can_get_hidden_items(player),
"Celadon Game Corner - Hidden Item Near Rumor Man (Coin Case)": lambda state: state.has("Coin Case", player) and state.pokemon_rb_can_get_hidden_items(player),
"Celadon Game Corner - Hidden Item Near Speculating Woman (Coin Case)": lambda state: state.has("Coin Case", player) and state.pokemon_rb_can_get_hidden_items(player),
"Celadon Game Corner - Hidden Item Near West Gifting Gambler (Coin Case)": lambda state: state.has("Coin Case", player) and state.pokemon_rb_can_get_hidden_items(player),
"Celadon Game Corner - Hidden Item Near Wonderful Time Woman (Coin Case)": lambda state: state.has("Coin Case", player) and state.pokemon_rb_can_get_hidden_items(player),
"Celadon Game Corner - Hidden Item Near Failing Gym Information Guy (Coin Case)": lambda state: state.has( "Coin Case", player) and state.pokemon_rb_can_get_hidden_items(player),
"Celadon Game Corner - Hidden Item Near East Gifting Gambler (Coin Case)": lambda state: state.has("Coin Case", player) and state.pokemon_rb_can_get_hidden_items(player),
"Celadon Game Corner - Hidden Item Near Hooked Guy (Coin Case)": lambda state: state.has("Coin Case", player) and state.pokemon_rb_can_get_hidden_items(player),
"Celadon Game Corner - Hidden Item at End of Horizontal Machine Row (Coin Case)": lambda state: state.has("Coin Case", player) and state.pokemon_rb_can_get_hidden_items(player),
"Celadon Game Corner - Hidden Item in Front of Horizontal Machine Row (Coin Case)": lambda state: state.has("Coin Case", player) and state.pokemon_rb_can_get_hidden_items(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),
"Viridian City - Sleepy Guy": lambda state: logic.can_cut(state, player) or logic.can_surf(state, player),
"Route 2 Gate - Oak's Aide": lambda state: logic.oaks_aide(state, state.multiworld.oaks_aide_rt_2[player].value + 5, player),
"Cerulean Bicycle Shop": lambda state: state.has("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),
"Route 11 Gate 2F - Oak's Aide": lambda state: logic.oaks_aide(state, state.multiworld.oaks_aide_rt_11[player].value + 5, player),
"Celadon City - Stranded Man": lambda state: logic.can_surf(state, 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),
"Route 12 - Island Item": lambda state: logic.can_surf(state, 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 25 - Item": lambda state: logic.can_cut(state, player),
"Fuchsia Warden's House - Behind Boulder Item": lambda state: logic.can_strength(state, player),
"Safari Zone Center - Island Item": lambda state: logic.can_surf(state, player),
"Saffron Copycat's House 2F - Copycat": lambda state: state.has("Buy Poke Doll", player),
"Silph Co 11F - Silph Co Liberated": lambda state: state.has("Card Key", 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 - 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 Southwest Corner": 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, 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 West Gifting Gambler": 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, 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 East Gifting Gambler": 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, 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 in Front of Horizontal Machine Row": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player),
"Pallet Town - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
"Pallet Town - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
"Route 22 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
"Route 22 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
"Route 24 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
"Route 24 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
"Route 24 - Super Rod Pokemon - 3": lambda state: state.has("Super Rod", player),
"Route 6 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
"Route 6 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
"Route 10 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
"Route 10 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
"Safari Zone Center - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
"Safari Zone Center - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
"Safari Zone Center - Super Rod Pokemon - 3": lambda state: state.has("Super Rod", player),
"Safari Zone Center - Super Rod Pokemon - 4": lambda state: state.has("Super Rod", player),
"Route 12 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
"Route 12 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
"Route 12 - Super Rod Pokemon - 3": lambda state: state.has("Super Rod", player),
"Route 12 - Super Rod Pokemon - 4": lambda state: state.has("Super Rod", player),
"Route 19 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
"Route 19 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
"Route 19 - Super Rod Pokemon - 3": lambda state: state.has("Super Rod", player),
"Route 19 - Super Rod Pokemon - 4": lambda state: state.has("Super Rod", player),
"Route 23 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
"Route 23 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
"Route 23 - Super Rod Pokemon - 3": lambda state: state.has("Super Rod", player),
"Route 23 - Super Rod Pokemon - 4": lambda state: state.has("Super Rod", player),
"Fuchsia City - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
"Fuchsia City - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
"Fuchsia City - Super Rod Pokemon - 3": lambda state: state.has("Super Rod", player),
"Fuchsia City - Super Rod Pokemon - 4": lambda state: state.has("Super Rod", player),
"Anywhere - Good Rod Pokemon - 1": lambda state: state.has("Good Rod", player),
"Anywhere - Good Rod Pokemon - 2": lambda state: state.has("Good Rod", player),
"Anywhere - Old Rod Pokemon": lambda state: state.has("Old Rod", player),
"Celadon Prize Corner - Pokemon Prize - 1": lambda state: state.has("Coin Case", player),
"Celadon Prize Corner - Pokemon Prize - 2": lambda state: state.has("Coin Case", player),
"Celadon Prize Corner - Pokemon Prize - 3": lambda state: state.has("Coin Case", player),
"Celadon Prize Corner - Pokemon Prize - 4": lambda state: state.has("Coin Case", player),
"Celadon Prize Corner - Pokemon Prize - 5": lambda state: state.has("Coin Case", player),
"Celadon Prize Corner - Pokemon Prize - 6": lambda state: state.has("Coin Case", player),
"Cinnabar Island - Old Amber Pokemon": lambda state: state.has("Old Amber", player),
"Cinnabar Island - Helix Fossil Pokemon": lambda state: state.has("Helix Fossil", player),
"Cinnabar Island - Dome Fossil Pokemon": lambda state: state.has("Dome Fossil", 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 3": lambda state: state.has("Coin Case", player) and state.has("Game Corner", player),
"Celadon Prize Corner - Pokemon Prize - 1": lambda state: state.has("Coin Case", player) and state.has("Game Corner", player),
"Celadon Prize Corner - Pokemon Prize - 2": lambda state: state.has("Coin Case", player) and state.has("Game Corner", player),
"Celadon Prize Corner - Pokemon Prize - 3": lambda state: state.has("Coin Case", player) and state.has("Game Corner", player),
"Celadon Prize Corner - Pokemon Prize - 4": lambda state: state.has("Coin Case", player) and state.has("Game Corner", player),
"Celadon Prize Corner - Pokemon Prize - 5": lambda state: state.has("Coin Case", player) and state.has("Game Corner", player),
"Celadon Prize Corner - Pokemon Prize - 6": lambda state: state.has("Coin Case", player) and state.has("Game Corner", player),
"Cinnabar Lab Fossil Room - Old Amber Pokemon": lambda state: state.has("Old Amber", player) and state.has("Cinnabar Island", player),
"Cinnabar Lab Fossil Room - Helix Fossil Pokemon": lambda state: state.has("Helix 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 16 - Sleeping Pokemon": lambda state: state.has("Poke Flute", player),
"Seafoam Islands B4F - Legendary Pokemon": lambda state: state.pokemon_rb_can_strength(player),
"Vermilion City - Legendary Pokemon": lambda state: state.pokemon_rb_can_surf(player) and state.has("S.S. Ticket", player),
"Seafoam Islands B4F - Legendary Pokemon": lambda state: logic.can_strength(state, player) and state.has("Seafoam Boss Boulders", player),
"Vermilion Dock - Legendary Pokemon": lambda state: logic.can_surf(state, player),
"Cerulean Cave B1F - Legendary Pokemon": lambda state: logic.can_surf(state, 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)},
"Pokemon Tower 6F - Restless Soul": lambda state: state.has("Silph Scope", player), # just for level scaling
"Route 2 - Marcel Trade": lambda state: state.can_reach("Route 24 - Wild Pokemon - 6", "Location", player),
"Underground Tunnel West-East - Spot Trade": lambda state: state.can_reach("Route 24 - Wild Pokemon - 6", "Location", player),
"Route 11 - Terry Trade": lambda state: state.can_reach("Safari Zone Center - Wild Pokemon - 5", "Location", player),
"Route 18 - Marc Trade": lambda state: state.can_reach("Route 23 - Super Rod Pokemon - 1", "Location", player),
"Cinnabar Island - Sailor Trade": lambda state: state.can_reach("Pokemon Mansion 1F - Wild Pokemon - 3", "Location", player),
"Cinnabar Island - Crinkles Trade": lambda state: state.can_reach("Route 12 - Wild Pokemon - 4", "Location", player),
"Cinnabar Island - Doris Trade": lambda state: state.can_reach("Cerulean Cave 1F - Wild Pokemon - 9", "Location", player),
"Vermilion City - Dux Trade": lambda state: state.can_reach("Route 3 - Wild Pokemon - 2", "Location", player),
"Cerulean City - Lola Trade": lambda state: state.can_reach("Route 10 - Super Rod Pokemon - 1", "Location", player),
"Silph Co 1F - Receptionist": lambda state: state.has("Silph Co Liberated", player),
"Silph Co 5F - Hostage": lambda state: logic.card_key(state, 5, player),
"Silph Co 7F - Hostage": lambda state: logic.card_key(state, 7, player),
"Route 2 Trade House - Marcel Trade": lambda state: state.can_reach("Route 24 - Wild Pokemon - 6", "Location", player),
"Underground Path Route 5 - Spot Trade": lambda state: state.can_reach("Route 24 - Wild Pokemon - 6", "Location", player),
"Route 11 Gate 2F - Terry Trade": lambda state: state.can_reach("Safari Zone Center - Wild Pokemon - 5", "Location", player),
"Route 18 Gate 2F - Marc Trade": lambda state: state.can_reach("Route 23/Cerulean Cave Fishing - Super Rod Pokemon - 1", "Location", player),
"Cinnabar Lab Fossil Room - Sailor Trade": lambda state: state.can_reach("Pokemon Mansion 1F - Wild Pokemon - 3", "Location", player),
"Cinnabar Lab Trade Room - Crinkles Trade": lambda state: state.can_reach("Route 12 - Wild Pokemon - 4", "Location", player),
"Cinnabar Lab Trade Room - Doris Trade": lambda state: state.can_reach("Cerulean Cave 1F - Wild Pokemon - 9", "Location", player),
"Vermilion Trade House - Dux Trade": lambda state: state.can_reach("Route 3 - Wild Pokemon - 2", "Location", player),
"Cerulean Trade House - Lola Trade": lambda state: state.can_reach("Route 10/Celadon Fishing - Super Rod Pokemon - 1", "Location", player),
"Route 22 - Trainer Parties": lambda state: state.has("Oak's Parcel", player),
# # Rock Tunnel
# "Rock Tunnel 1F - PokeManiac": lambda state: logic.rock_tunnel(state, player),
# "Rock Tunnel 1F - Hiker 1": lambda state: logic.rock_tunnel(state, player),
# "Rock Tunnel 1F - Hiker 2": lambda state: logic.rock_tunnel(state, player),
# "Rock Tunnel 1F - Hiker 3": lambda state: logic.rock_tunnel(state, player),
# "Rock Tunnel 1F - Jr. Trainer F 1": lambda state: logic.rock_tunnel(state, player),
# "Rock Tunnel 1F - Jr. Trainer F 2": lambda state: logic.rock_tunnel(state, player),
# "Rock Tunnel 1F - Jr. Trainer F 3": lambda state: logic.rock_tunnel(state, player),
# "Rock Tunnel B1F - PokeManiac 1": lambda state: logic.rock_tunnel(state, player),
# "Rock Tunnel B1F - PokeManiac 2": lambda state: logic.rock_tunnel(state, player),
# "Rock Tunnel B1F - PokeManiac 3": lambda state: logic.rock_tunnel(state, player),
# "Rock Tunnel B1F - Jr. Trainer F 1": lambda state: logic.rock_tunnel(state, player),
# "Rock Tunnel B1F - Jr. Trainer F 2": lambda state: logic.rock_tunnel(state, player),
# "Rock Tunnel B1F - Hiker 1": lambda state: logic.rock_tunnel(state, player),
# "Rock Tunnel B1F - Hiker 2": lambda state: logic.rock_tunnel(state, player),
# "Rock Tunnel B1F - Hiker 3": lambda state: logic.rock_tunnel(state, player),
# "Rock Tunnel B1F - North Item": lambda state: logic.rock_tunnel(state, player),
# "Rock Tunnel B1F - Northwest Item": lambda state: logic.rock_tunnel(state, player),
# "Rock Tunnel B1F - Southwest Item": lambda state: logic.rock_tunnel(state, player),
# "Rock Tunnel B1F - West Item": lambda state: logic.rock_tunnel(state, player),
# Pokédex check
"Pallet Town - 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),
# trainers
"Route 4 - Cooltrainer F": lambda state: state.pokemon_rb_can_surf(player),
"Route 15 - Jr. Trainer F 1": lambda state: state.pokemon_rb_can_cut(player),
"Silph Co 11F - Rocket 2 (Card Key)": lambda state: state.has("Card Key", player),
"Silph Co 9F - Rocket 2 (Card Key)": lambda state: state.has("Card Key", player),
"Silph Co 3F - Scientist (Card Key)": lambda state: state.has("Card Key", player),
"Route 10 - Pokemaniac": lambda state: state.pokemon_rb_can_surf(player),
"Rocket Hideout B1F - Rocket 5 (Lift Key)": lambda state: state.has("Lift Key", player),
"Rocket Hideout B4F - Rocket 2 (Lift Key)": lambda state: state.has("Lift Key", player),
"Rocket Hideout B4F - Rocket 3 (Lift Key)": lambda state: state.has("Lift Key", player),
# hidden items
"Viridian Forest - Hidden Item Northwest by Trainer": lambda state: state.pokemon_rb_can_get_hidden_items(
# Hidden items
"Viridian Forest - Hidden Item Northwest by Trainer": lambda state: logic.can_get_hidden_items(state,
player),
"Viridian Forest - Hidden Item Entrance Tree": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Mt Moon B2F - Hidden Item Dead End Before Fossils": lambda state: state.pokemon_rb_can_get_hidden_items(
"Viridian Forest - Hidden Item Entrance Tree": lambda state: logic.can_get_hidden_items(state, player),
"Mt Moon B2F - Hidden Item Dead End Before Fossils": lambda state: logic.can_get_hidden_items(state,
player),
"Route 25 - Hidden Item Fence Outside Bill's House": lambda state: state.pokemon_rb_can_get_hidden_items(
"Route 25 - Hidden Item Fence Outside Bill's House": lambda state: logic.can_get_hidden_items(state,
player),
"Route 9 - Hidden Item Bush By Grass": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"S.S. Anne 1F - Hidden Item Kitchen Trash": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"S.S. Anne B1F - Hidden Item Under Pillow": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Route 9 - Hidden Item Bush By Grass": 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, player),
"S.S. Anne B1F Rooms - Hidden Item Under Pillow": lambda state: logic.can_get_hidden_items(state, player),
"Route 10 - Hidden Item Behind Rock Tunnel Entrance Cuttable Tree": lambda
state: state.pokemon_rb_can_get_hidden_items(player),
"Route 10 - Hidden Item Bush": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Rocket Hideout B1F - Hidden Item Pot Plant": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Rocket Hideout B3F - Hidden Item Near East Item": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Rocket Hideout B4F - Hidden Item Behind Giovanni (Lift Key)": lambda state:
state.pokemon_rb_can_get_hidden_items(player) and state.has("Lift Key", player),
"Pokemon Tower 5F - Hidden Item Near West Staircase": lambda state: state.pokemon_rb_can_get_hidden_items(
state: logic.can_get_hidden_items(state, player) and logic.can_cut(state, player),
"Route 10 - Hidden Item Bush": lambda state: logic.can_get_hidden_items(state, player),
"Rocket Hideout B1F - Hidden Item Pot Plant": 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, player),
"Rocket Hideout B4F - Hidden Item Behind Giovanni": lambda state:
logic.can_get_hidden_items(state, player),
"Pokemon Tower 5F - Hidden Item Near West Staircase": lambda state: logic.can_get_hidden_items(state,
player),
"Route 13 - Hidden Item Dead End Bush": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Route 13 - Hidden Item Dead End By Water Corner": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Pokemon Mansion B1F - Hidden Item Secret Key Room Corner": lambda state: state.pokemon_rb_can_get_hidden_items(
"Route 13 - Hidden Item Dead End Bush": 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, player),
"Pokemon Mansion B1F - Hidden Item Secret Key Room Corner": lambda state: logic.can_get_hidden_items(state,
player),
"Safari Zone West - Hidden Item Secret House Statue": lambda state: state.pokemon_rb_can_get_hidden_items(
"Safari Zone West - Hidden Item Secret House Statue": lambda state: logic.can_get_hidden_items(state,
player),
"Silph Co 5F - Hidden Item Pot Plant": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Silph Co 9F - Hidden Item Nurse Bed (Card Key)": lambda state: state.pokemon_rb_can_get_hidden_items(
player) and state.has("Card Key", player),
"Copycat's House - Hidden Item Desk": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Cerulean Cave 1F - Hidden Item Center Rocks": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Cerulean Cave B1F - Hidden Item Northeast Rocks": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Power Plant - Hidden Item Central Dead End": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Power Plant - Hidden Item Before Zapdos": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Seafoam Islands B2F - Hidden Item Rock": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Seafoam Islands B4F - Hidden Item Corner Island": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Silph Co 5F - Hidden Item Pot Plant": lambda state: logic.can_get_hidden_items(state, player),
"Silph Co 9F - Hidden Item Nurse Bed": 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, player),
"Cerulean Cave 1F - Hidden Item Center 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, player),
"Power Plant - Hidden Item Central Dead End": lambda state: logic.can_get_hidden_items(state, player),
"Power Plant - Hidden Item Before Zapdos": lambda state: logic.can_get_hidden_items(state, player),
"Seafoam Islands B2F - 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, player),
# 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),
"Pokemon Mansion 1F - Hidden Item Block Near Entrance Carpet": lambda
state: state.pokemon_rb_can_get_hidden_items(player),
"Pokemon Mansion 3F - Hidden Item Behind Burglar": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Route 23 - Hidden Item Rocks Before Victory Road": lambda state: state.pokemon_rb_can_get_hidden_items(
state: logic.can_get_hidden_items(state, player),
"Pokemon Mansion 3F - Hidden Item Behind Burglar": lambda state: logic.can_get_hidden_items(state, player),
"Route 23 - Hidden Item Rocks Before Victory Road": lambda state: logic.can_get_hidden_items(state,
player),
"Route 23 - Hidden Item East Bush After Water": lambda state: state.pokemon_rb_can_get_hidden_items(
"Route 23 - Hidden Item East Bush After Water": lambda state: logic.can_get_hidden_items(state,
player),
"Route 23 - Hidden Item On Island": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Victory Road 2F - Hidden Item Rock Before Moltres": lambda state: state.pokemon_rb_can_get_hidden_items(
"Route 23 - Hidden Item On Island": lambda state: logic.can_get_hidden_items(state,
player) and logic.can_surf(state, player),
"Victory Road 2F - Hidden Item Rock Before Moltres": lambda state: logic.can_get_hidden_items(state,
player),
"Victory Road 2F - Hidden Item Rock In Final Room": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Viridian City - Hidden Item Cuttable Tree": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Route 11 - Hidden Item Isolated Bush Near Gate": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Route 12 - Hidden Item Bush Near Gate": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Route 17 - Hidden Item In Grass": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Route 17 - Hidden Item Near Northernmost Sign": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Route 17 - Hidden Item East Center": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Route 17 - Hidden Item West Center": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Route 17 - Hidden Item Before Final Bridge": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Underground Tunnel North-South - Hidden Item Near Northern Stairs": lambda
state: state.pokemon_rb_can_get_hidden_items(player),
"Underground Tunnel North-South - Hidden Item Near Southern Stairs": lambda
state: state.pokemon_rb_can_get_hidden_items(player),
"Underground Tunnel West-East - Hidden Item West": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Underground Tunnel West-East - Hidden Item East": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Celadon City - Hidden Item Dead End Near Cuttable Tree": lambda state: state.pokemon_rb_can_get_hidden_items(
"Victory Road 2F - Hidden Item Rock In Final Room": lambda state: logic.can_get_hidden_items(state, player),
"Viridian City - Hidden Item Cuttable Tree": 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, player),
"Route 12 - Hidden Item Bush Near Gate": lambda state: logic.can_get_hidden_items(state, player),
"Route 17 - Hidden Item In Grass": lambda state: logic.can_get_hidden_items(state, player),
"Route 17 - Hidden Item Near Northernmost Sign": lambda state: logic.can_get_hidden_items(state, player),
"Route 17 - Hidden Item East Center": lambda state: logic.can_get_hidden_items(state, player),
"Route 17 - Hidden Item West Center": lambda state: logic.can_get_hidden_items(state, player),
"Route 17 - Hidden Item Before Final Bridge": lambda state: logic.can_get_hidden_items(state, player),
"Underground Path North South - Hidden Item Near Northern Stairs": lambda
state: logic.can_get_hidden_items(state, player),
"Underground Path North South - Hidden Item Near Southern Stairs": lambda
state: logic.can_get_hidden_items(state, player),
"Underground Path West East - Hidden Item West": lambda state: logic.can_get_hidden_items(state, player),
"Underground Path West East - Hidden Item East": lambda state: logic.can_get_hidden_items(state, player),
"Celadon City - Hidden Item Dead End Near Cuttable Tree": lambda state: logic.can_get_hidden_items(state,
player),
"Route 25 - Hidden Item Northeast Of Grass": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Mt Moon B2F - Hidden Item Lone Rock": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Seafoam Islands B3F - Hidden Item Rock": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Vermilion City - Hidden Item In Water Near Fan Club": lambda state: state.pokemon_rb_can_get_hidden_items(
player) and state.pokemon_rb_can_surf(player),
"Cerulean City - Hidden Item Gym Badge Guy's Backyard": lambda state: state.pokemon_rb_can_get_hidden_items(
"Route 25 - Hidden Item Northeast Of Grass": lambda state: logic.can_get_hidden_items(state, player),
"Mt Moon B2F - Hidden Item Lone Rock": lambda state: logic.can_get_hidden_items(state, player),
"Vermilion City - Hidden Item In Water Near Fan Club": lambda state: logic.can_get_hidden_items(state,
player) and logic.can_surf(state, player),
"Cerulean City - Hidden Item Gym Badge Guy's Backyard": lambda state: logic.can_get_hidden_items(state,
player),
"Route 4 - Hidden Item Plateau East Of Mt Moon": lambda state: state.pokemon_rb_can_get_hidden_items(player),
"Route 4 - Hidden Item Plateau East Of Mt Moon": lambda state: logic.can_get_hidden_items(state, player),
# Evolutions
"Evolution - Ivysaur": lambda state: state.has("Bulbasaur", player) and logic.evolve_level(state, 16, player),
"Evolution - Venusaur": lambda state: state.has("Ivysaur", player) and logic.evolve_level(state, 32, player),
"Evolution - Charmeleon": lambda state: state.has("Charmander", player) and logic.evolve_level(state, 16, player),
"Evolution - Charizard": lambda state: state.has("Charmeleon", player) and logic.evolve_level(state, 36, player),
"Evolution - Wartortle": lambda state: state.has("Squirtle", player) and logic.evolve_level(state, 16, player),
"Evolution - Blastoise": lambda state: state.has("Wartortle", player) and logic.evolve_level(state, 36, player),
"Evolution - Metapod": lambda state: state.has("Caterpie", player) and logic.evolve_level(state, 7, player),
"Evolution - Butterfree": lambda state: state.has("Metapod", player) and logic.evolve_level(state, 10, player),
"Evolution - Kakuna": lambda state: state.has("Weedle", player) and logic.evolve_level(state, 7, player),
"Evolution - Beedrill": lambda state: state.has("Kakuna", player) and logic.evolve_level(state, 10, player),
"Evolution - Pidgeotto": lambda state: state.has("Pidgey", player) and logic.evolve_level(state, 18, player),
"Evolution - Pidgeot": lambda state: state.has("Pidgeotto", player) and logic.evolve_level(state, 36, player),
"Evolution - Raticate": lambda state: state.has("Rattata", player) and logic.evolve_level(state, 20, player),
"Evolution - Fearow": lambda state: state.has("Spearow", player) and logic.evolve_level(state, 20, player),
"Evolution - Arbok": lambda state: state.has("Ekans", player) and logic.evolve_level(state, 22, player),
"Evolution - Raichu": lambda state: state.has("Pikachu", player) and state.has("Thunder Stone", player),
"Evolution - Sandslash": lambda state: state.has("Sandshrew", player) and logic.evolve_level(state, 22, player),
"Evolution - Nidorina": lambda state: state.has("Nidoran F", player) and logic.evolve_level(state, 16, player),
"Evolution - Nidoqueen": lambda state: state.has("Nidorina", player) and state.has("Moon Stone", player),
"Evolution - Nidorino": lambda state: state.has("Nidoran M", player) and logic.evolve_level(state, 16, player),
"Evolution - Nidoking": lambda state: state.has("Nidorino", player) and state.has("Moon Stone", player),
"Evolution - Clefable": lambda state: state.has("Clefairy", player) and state.has("Moon Stone", player),
"Evolution - Ninetales": lambda state: state.has("Vulpix", player) and state.has("Fire Stone", player),
"Evolution - Wigglytuff": lambda state: state.has("Jigglypuff", player) and state.has("Moon Stone", player),
"Evolution - Golbat": lambda state: state.has("Zubat", player) and logic.evolve_level(state, 22, player),
"Evolution - Gloom": lambda state: state.has("Oddish", player) and logic.evolve_level(state, 21, player),
"Evolution - Vileplume": lambda state: state.has("Gloom", player) and state.has("Leaf Stone", player),
"Evolution - Parasect": lambda state: state.has("Paras", player) and logic.evolve_level(state, 24, player),
"Evolution - Venomoth": lambda state: state.has("Venonat", player) and logic.evolve_level(state, 31, player),
"Evolution - Dugtrio": lambda state: state.has("Diglett", player) and logic.evolve_level(state, 26, player),
"Evolution - Persian": lambda state: state.has("Meowth", player) and logic.evolve_level(state, 28, player),
"Evolution - Golduck": lambda state: state.has("Psyduck", player) and logic.evolve_level(state, 33, player),
"Evolution - Primeape": lambda state: state.has("Mankey", player) and logic.evolve_level(state, 28, player),
"Evolution - Arcanine": lambda state: state.has("Growlithe", player) and state.has("Fire Stone", player),
"Evolution - Poliwhirl": lambda state: state.has("Poliwag", player) and logic.evolve_level(state, 25, player),
"Evolution - Poliwrath": lambda state: state.has("Poliwhirl", player) and state.has("Water Stone", player),
"Evolution - Kadabra": lambda state: state.has("Abra", player) and logic.evolve_level(state, 16, player),
"Evolution - Alakazam": lambda state: state.has("Kadabra", player) and logic.evolve_level(state, 35, player),
"Evolution - Machoke": lambda state: state.has("Machop", player) and logic.evolve_level(state, 28, player),
"Evolution - Machamp": lambda state: state.has("Machoke", player) and logic.evolve_level(state, 35, player),
"Evolution - Weepinbell": lambda state: state.has("Bellsprout", player) and logic.evolve_level(state, 21, player),
"Evolution - Victreebel": lambda state: state.has("Weepinbell", player) and state.has("Leaf Stone", player),
"Evolution - Tentacruel": lambda state: state.has("Tentacool", player) and logic.evolve_level(state, 30, player),
"Evolution - Graveler": lambda state: state.has("Geodude", player) and logic.evolve_level(state, 25, player),
"Evolution - Golem": lambda state: state.has("Graveler", player) and logic.evolve_level(state, 35, player),
"Evolution - Rapidash": lambda state: state.has("Ponyta", player) and logic.evolve_level(state, 40, player),
"Evolution - Slowbro": lambda state: state.has("Slowpoke", player) and logic.evolve_level(state, 37, player),
"Evolution - Magneton": lambda state: state.has("Magnemite", player) and logic.evolve_level(state, 30, player),
"Evolution - Dodrio": lambda state: state.has("Doduo", player) and logic.evolve_level(state, 31, player),
"Evolution - Dewgong": lambda state: state.has("Seel", player) and logic.evolve_level(state, 34, player),
"Evolution - Muk": lambda state: state.has("Grimer", player) and logic.evolve_level(state, 38, player),
"Evolution - Cloyster": lambda state: state.has("Shellder", player) and state.has("Water Stone", player),
"Evolution - Haunter": lambda state: state.has("Gastly", player) and logic.evolve_level(state, 25, player),
"Evolution - Gengar": lambda state: state.has("Haunter", player) and logic.evolve_level(state, 35, player),
"Evolution - Hypno": lambda state: state.has("Drowzee", player) and logic.evolve_level(state, 26, player),
"Evolution - Kingler": lambda state: state.has("Krabby", player) and logic.evolve_level(state, 28, player),
"Evolution - Electrode": lambda state: state.has("Voltorb", player) and logic.evolve_level(state, 30, player),
"Evolution - Exeggutor": lambda state: state.has("Exeggcute", player) and state.has("Leaf Stone", player),
"Evolution - Marowak": lambda state: state.has("Cubone", player) and logic.evolve_level(state, 28, player),
"Evolution - Weezing": lambda state: state.has("Koffing", player) and logic.evolve_level(state, 35, player),
"Evolution - Rhydon": lambda state: state.has("Rhyhorn", player) and logic.evolve_level(state, 42, player),
"Evolution - Seadra": lambda state: state.has("Horsea", player) and logic.evolve_level(state, 32, player),
"Evolution - Seaking": lambda state: state.has("Goldeen", player) and logic.evolve_level(state, 33, player),
"Evolution - Starmie": lambda state: state.has("Staryu", player) and state.has("Water Stone", player),
"Evolution - Gyarados": lambda state: state.has("Magikarp", player) and logic.evolve_level(state, 33, player),
"Evolution - Vaporeon": lambda state: state.has("Eevee", player) and state.has("Water Stone", player),
"Evolution - Jolteon": lambda state: state.has("Eevee", player) and state.has("Thunder Stone", player),
"Evolution - Flareon": lambda state: state.has("Eevee", player) and state.has("Fire Stone", player),
"Evolution - Omastar": lambda state: state.has("Omanyte", player) and logic.evolve_level(state, 40, player),
"Evolution - Kabutops": lambda state: state.has("Kabuto", player) and logic.evolve_level(state, 40, player),
"Evolution - Dragonair": lambda state: state.has("Dratini", player) and logic.evolve_level(state, 30, player),
"Evolution - Dragonite": lambda state: state.has("Dragonair", player) and logic.evolve_level(state, 55, player),
}
for loc in world.get_locations(player):
for loc in multiworld.get_locations(player):
if loc.name in access_rules:
add_rule(loc, access_rules[loc.name])
if loc.name in item_rules:
@ -233,4 +283,3 @@ def set_rules(world, player):
add_rule(loc, lambda state, i=mon: (state.has("Pokedex", player) or not
state.multiworld.require_pokedex[player]) and (state.has(i, player)
or state.has(f"Static {i}", player)))

View File

@ -1,6 +1,7 @@
special_chars = {
"PKMN": 0x4A,
"LINE": 0x4F,
"PARA": 0x51,
"CONT": 0x55,
"DONE": 0x57,
"PROMPT": 0x58,
@ -90,10 +91,13 @@ char_map = {
"?": 0xE6,
"!": 0xE7,
".": 0xE8,
"+": 0xEA,
"=": 0xEB,
"": 0xEF,
"¥": 0xF0,
"$": 0xF0,
"×": 0xF1,
"*": 0xF1, # alias
"/": 0xF3,
",": 0xF4,
"": 0xF5,