Pokémon R/B: Version 3 ()

* Coin items received or found in the Game Corner are now shuffled, locations require Coin Case
* Prizesanity option (shuffle Game Corner Prizes)
* DexSanity option: location checks for marking Pokémon as caught in your Pokédex. Also an option to set all Pokémon in your Pokédex as seen from the start, to aid in locating them.
* Option to randomize the layout of the Rock Tunnel.
* Area 1-to-1 mapping: When one instance of a Wild Pokémon in a given area is randomized, all instances of that Pokémon will be the same. So that if a route had 3 different Pokémon before, it will have 3 after randomization.
* Option to randomize the moves taught by TMs.
* Exact controls for TM/HM compatibility chances.
* Option to randomize Pokémon's pallets or set them based on primary type.
* Added Cinnabar Gym trainers to Trainersanity and randomized the quiz questions and answers. Getting a correct answer will flag the trainer as defeated so that you can obtain the Trainersanity check without defeating the trainer if you answer correctly.
This commit is contained in:
Alchav 2023-03-13 18:40:55 -04:00 committed by GitHub
parent 4d7bd929bc
commit df55455fc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 2025 additions and 918 deletions

View File

@ -17,7 +17,7 @@ from CommonClient import CommonContext, server_loop, gui_enabled, ClientCommandP
from worlds.pokemon_rb.locations import location_data
from worlds.pokemon_rb.rom import RedDeltaPatch, BlueDeltaPatch
location_map = {"Rod": {}, "EventFlag": {}, "Missable": {}, "Hidden": {}, "list": {}}
location_map = {"Rod": {}, "EventFlag": {}, "Missable": {}, "Hidden": {}, "list": {}, "DexSanityFlag": {}}
location_bytes_bits = {}
for location in location_data:
if location.ram_address is not None:
@ -40,7 +40,7 @@ CONNECTION_INITIAL_STATUS = "Connection has not been initiated"
DISPLAY_MSGS = True
SCRIPT_VERSION = 1
SCRIPT_VERSION = 3
class GBCommandProcessor(ClientCommandProcessor):
@ -70,6 +70,8 @@ class GBContext(CommonContext):
self.set_deathlink = False
self.client_compatibility_mode = 0
self.items_handling = 0b001
self.sent_release = False
self.sent_collect = False
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
@ -124,7 +126,8 @@ def get_payload(ctx: GBContext):
"items": [item.item for item in ctx.items_received],
"messages": {f'{key[0]}:{key[1]}': value for key, value in ctx.messages.items()
if key[0] > current_time - 10},
"deathlink": ctx.deathlink_pending
"deathlink": ctx.deathlink_pending,
"options": ((ctx.permissions['release'] in ('goal', 'enabled')) * 2) + (ctx.permissions['collect'] in ('goal', 'enabled'))
}
)
ctx.deathlink_pending = False
@ -134,10 +137,13 @@ def get_payload(ctx: GBContext):
async def parse_locations(data: List, ctx: GBContext):
locations = []
flags = {"EventFlag": data[:0x140], "Missable": data[0x140:0x140 + 0x20],
"Hidden": data[0x140 + 0x20: 0x140 + 0x20 + 0x0E], "Rod": data[0x140 + 0x20 + 0x0E:]}
"Hidden": data[0x140 + 0x20: 0x140 + 0x20 + 0x0E],
"Rod": data[0x140 + 0x20 + 0x0E:0x140 + 0x20 + 0x0E + 0x01]}
if len(flags['Rod']) > 1:
return
if len(data) > 0x140 + 0x20 + 0x0E + 0x01:
flags["DexSanityFlag"] = data[0x140 + 0x20 + 0x0E + 0x01:]
else:
flags["DexSanityFlag"] = [0] * 19
for flag_type, loc_map in location_map.items():
for flag, loc_id in loc_map.items():
@ -207,6 +213,16 @@ async def gb_sync_task(ctx: GBContext):
async_start(parse_locations(data_decoded['locations'], ctx))
if 'deathLink' in data_decoded and data_decoded['deathLink'] and 'DeathLink' in ctx.tags:
await ctx.send_death(ctx.auth + " is out of usable Pokémon! " + ctx.auth + " blacked out!")
if 'options' in data_decoded:
msgs = []
if data_decoded['options'] & 4 and not ctx.sent_release:
ctx.sent_release = True
msgs.append({"cmd": "Say", "text": "!release"})
if data_decoded['options'] & 8 and not ctx.sent_collect:
ctx.sent_collect = True
msgs.append({"cmd": "Say", "text": "!collect"})
if msgs:
await ctx.send_msgs(msgs)
if ctx.set_deathlink:
await ctx.update_death_link(True)
except asyncio.TimeoutError:

View File

@ -7,7 +7,7 @@ local STATE_TENTATIVELY_CONNECTED = "Tentatively Connected"
local STATE_INITIAL_CONNECTION_MADE = "Initial Connection Made"
local STATE_UNINITIALIZED = "Uninitialized"
local SCRIPT_VERSION = 1
local SCRIPT_VERSION = 3
local APIndex = 0x1A6E
local APDeathLinkAddress = 0x00FD
@ -16,7 +16,8 @@ local EventFlagAddress = 0x1735
local MissableAddress = 0x161A
local HiddenItemsAddress = 0x16DE
local RodAddress = 0x1716
local InGame = 0x1A71
local DexSanityAddress = 0x1A71
local InGameAddress = 0x1A84
local ClientCompatibilityAddress = 0xFF00
local ItemsReceived = nil
@ -34,6 +35,7 @@ local frame = 0
local u8 = nil
local wU8 = nil
local u16
local compat = nil
local function defineMemoryFunctions()
local memDomain = {}
@ -70,18 +72,6 @@ function slice (tbl, s, e)
return new
end
function processBlock(block)
if block == nil then
return
end
local itemsBlock = block["items"]
memDomain.wram()
if itemsBlock ~= nil then
ItemsReceived = itemsBlock
end
deathlink_rec = block["deathlink"]
end
function difference(a, b)
local aa = {}
for k,v in pairs(a) do aa[v]=true end
@ -99,6 +89,7 @@ function generateLocationsChecked()
events = uRange(EventFlagAddress, 0x140)
missables = uRange(MissableAddress, 0x20)
hiddenitems = uRange(HiddenItemsAddress, 0x0E)
dexsanity = uRange(DexSanityAddress, 19)
rod = u8(RodAddress)
data = {}
@ -108,6 +99,9 @@ function generateLocationsChecked()
table.foreach(hiddenitems, function(k, v) table.insert(data, v) end)
table.insert(data, rod)
if compat > 1 then
table.foreach(dexsanity, function(k, v) table.insert(data, v) end)
end
return data
end
@ -141,7 +135,15 @@ function receive()
return
end
if l ~= nil then
processBlock(json.decode(l))
block = json.decode(l)
if block ~= nil then
local itemsBlock = block["items"]
if itemsBlock ~= nil then
ItemsReceived = itemsBlock
end
deathlink_rec = block["deathlink"]
end
end
-- Determine Message to send back
memDomain.rom()
@ -156,15 +158,31 @@ function receive()
seedName = newSeedName
local retTable = {}
retTable["scriptVersion"] = SCRIPT_VERSION
retTable["clientCompatibilityVersion"] = u8(ClientCompatibilityAddress)
if compat == nil then
compat = u8(ClientCompatibilityAddress)
if compat < 2 then
InGameAddress = 0x1A71
end
end
retTable["clientCompatibilityVersion"] = compat
retTable["playerName"] = playerName
retTable["seedName"] = seedName
memDomain.wram()
if u8(InGame) == 0xAC then
in_game = u8(InGameAddress)
if in_game == 0x2A or in_game == 0xAC then
retTable["locations"] = generateLocationsChecked()
elseif in_game ~= 0 then
print("Game may have crashed")
curstate = STATE_UNINITIALIZED
return
end
retTable["deathLink"] = deathlink_send
deathlink_send = false
msg = json.encode(retTable).."\n"
local ret, error = gbSocket:send(msg)
if ret == nil then
@ -193,16 +211,23 @@ function main()
if (curstate == STATE_OK) or (curstate == STATE_INITIAL_CONNECTION_MADE) or (curstate == STATE_TENTATIVELY_CONNECTED) then
if (frame % 5 == 0) then
receive()
if u8(InGame) == 0xAC and u8(APItemAddress) == 0x00 then
ItemIndex = u16(APIndex)
if deathlink_rec == true then
wU8(APDeathLinkAddress, 1)
elseif u8(APDeathLinkAddress) == 3 then
wU8(APDeathLinkAddress, 0)
deathlink_send = true
end
if ItemsReceived[ItemIndex + 1] ~= nil then
wU8(APItemAddress, ItemsReceived[ItemIndex + 1] - 172000000)
in_game = u8(InGameAddress)
if in_game == 0x2A or in_game == 0xAC then
if u8(APItemAddress) == 0x00 then
ItemIndex = u16(APIndex)
if deathlink_rec == true then
wU8(APDeathLinkAddress, 1)
elseif u8(APDeathLinkAddress) == 3 then
wU8(APDeathLinkAddress, 0)
deathlink_send = true
end
if ItemsReceived[ItemIndex + 1] ~= nil then
item_id = ItemsReceived[ItemIndex + 1] - 172000000
if item_id > 255 then
item_id = item_id - 256
end
wU8(APItemAddress, item_id)
end
end
end
end

View File

@ -15,7 +15,7 @@ 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_static_pokemon, process_move_data
from .rules import set_rules
import worlds.pokemon_rb.poke_data as poke_data
@ -40,13 +40,14 @@ class PokemonRedBlueWorld(World):
game = "Pokemon Red and Blue"
option_definitions = pokemon_rb_options
data_version = 5
required_client_version = (0, 3, 7)
data_version = 7
required_client_version = (0, 3, 9)
topology_present = False
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"}
location_name_to_id = {location.name: location.address for location in location_data if location.type == "Item"
and location.address is not None}
item_name_groups = item_groups
web = PokemonWebWorld()
@ -58,11 +59,14 @@ class PokemonRedBlueWorld(World):
self.extra_badges = {}
self.type_chart = None
self.local_poke_data = None
self.local_move_data = None
self.local_tms = None
self.learnsets = None
self.trainer_name = None
self.rival_name = None
self.type_chart = None
self.traps = None
self.trade_mons = {}
@classmethod
def stage_assert_generate(cls, multiworld: MultiWorld):
@ -94,6 +98,12 @@ 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 self.multiworld.badges_needed_for_hm_moves[self.player].value >= 2:
badges_to_add = ["Marsh Badge", "Volcano Badge", "Earth Badge"]
if self.multiworld.badges_needed_for_hm_moves[self.player].value == 3:
@ -107,6 +117,7 @@ class PokemonRedBlueWorld(World):
for badge in badges_to_add:
self.extra_badges[hm_moves.pop()] = badge
process_move_data(self)
process_pokemon_data(self)
if self.multiworld.randomize_type_chart[self.player] == "vanilla":
@ -178,8 +189,13 @@ class PokemonRedBlueWorld(World):
if self.multiworld.randomize_pokedex[self.player] == "start_with":
start_inventory["Pokedex"] = 1
self.multiworld.push_precollected(self.create_item("Pokedex"))
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:
if not location.inclusion(self.multiworld, self.player):
continue
@ -189,9 +205,18 @@ class PokemonRedBlueWorld(World):
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
location.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])
else:
item = self.create_item(location.original_item)
else:
item = self.create_item(location.original_item)
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 (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())
@ -205,9 +230,62 @@ class PokemonRedBlueWorld(World):
self.multiworld.itempool += item_pool
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"]
for location in self.multiworld.get_locations(self.player):
if location.name in pokemon_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]
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)
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):
intervene_move = "Surf"
if not test_state.pokemon_rb_can_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"
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)
last_intervene = intervene_move
else:
break
if self.multiworld.old_man[self.player].value == 1:
self.multiworld.local_early_items[self.player]["Oak's Parcel"] = 1
@ -237,17 +315,26 @@ class PokemonRedBlueWorld(World):
else:
raise FillError(f"Failed to place badges for player {self.player}")
locs = [self.multiworld.get_location("Fossil - Choice A", self.player),
self.multiworld.get_location("Fossil - Choice B", self.player)]
for loc in locs:
add_item_rule(loc, lambda i: i.advancement or i.name in self.item_name_groups["Unique"]
or i.name == "Master Ball")
# 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.
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)]):
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.append(loc)
locs.add(loc)
for loc in locs:
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)
@ -262,21 +349,6 @@ class PokemonRedBlueWorld(World):
unplaced_items.append(item)
self.multiworld.itempool += unplaced_items
intervene = False
test_state = self.multiworld.get_all_state(False)
if not test_state.pokemon_rb_can_surf(self.player) or not test_state.pokemon_rb_can_strength(self.player):
intervene = True
elif self.multiworld.accessibility[self.player].current_key != "minimal":
if not test_state.pokemon_rb_can_cut(self.player) or not test_state.pokemon_rb_can_flash(self.player):
intervene = True
if intervene:
# the way this is handled will be improved significantly in the future when I add options to
# let you choose the exact weights for HM compatibility
logging.warning(
f"HM-compatible Pokémon possibly missing, placing Mew on Route 1 for player {self.player}")
loc = self.multiworld.get_location("Route 1 - Wild Pokemon - 1", self.player)
loc.item = self.create_item("Mew")
def create_regions(self):
if self.multiworld.free_fly_location[self.player].value:
if self.multiworld.old_man[self.player].value == 0:
@ -317,6 +389,12 @@ class PokemonRedBlueWorld(World):
spoiler_handle.write(f"\n\nType matchups ({self.multiworld.player_name[self.player]}):\n\n")
for matchup in self.type_chart:
spoiler_handle.write(f"{matchup[0]} deals {matchup[2] * 10}% damage to {matchup[1]}\n")
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"]
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
@ -336,6 +414,21 @@ class PokemonRedBlueWorld(World):
self.traps += ["Ice Trap"] * self.multiworld.ice_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]:
hint_data[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):
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] = \
", ".join(mon_locations[mon])
def fill_slot_data(self) -> dict:
return {
"second_fossil_check_condition": self.multiworld.second_fossil_check_condition[self.player].value,
@ -358,7 +451,8 @@ class PokemonRedBlueWorld(World):
"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
"death_link": self.multiworld.death_link[self.player].value,
"prizesanity": self.multiworld.prizesanity[self.player].value
}

View File

@ -33,6 +33,8 @@ fossil scientist. This may require reviving a number of fossils, depending on yo
* 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.
* 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
## What items and locations get shuffled?

View File

@ -65,7 +65,7 @@ item_table = {
"Super Repel": ItemData(56, ItemClassification.filler, ["Consumables"]),
"Max Repel": ItemData(57, ItemClassification.filler, ["Consumables"]),
"Dire Hit": ItemData(58, ItemClassification.filler, ["Consumables", "Battle Items"]),
#"Coin": ItemData(59, ItemClassification.filler),
"10 Coins": ItemData(59, ItemClassification.filler, ["Coins"]),
"Fresh Water": ItemData(60, ItemClassification.filler, ["Consumables", "Vending Machine Drinks"]),
"Soda Pop": ItemData(61, ItemClassification.filler, ["Consumables", "Vending Machine Drinks"]),
"Lemonade": ItemData(62, ItemClassification.filler, ["Consumables", "Vending Machine Drinks"]),
@ -103,6 +103,8 @@ item_table = {
"Paralyze Trap": ItemData(95, ItemClassification.trap, ["Traps"]),
"Ice Trap": ItemData(96, ItemClassification.trap, ["Traps"]),
"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"]),
@ -119,7 +121,7 @@ item_table = {
"TM09 Take Down": ItemData(209, ItemClassification.useful, ["Unique", "TMs"]),
"TM10 Double Edge": ItemData(210, ItemClassification.useful, ["Unique", "TMs"]),
"TM11 Bubble Beam": ItemData(211, ItemClassification.useful, ["Unique", "TMs"]),
"TM12 Water Gun": ItemData(212, ItemClassification.useful, ["Unique", "TMs"]),
"TM12 Water Gun": ItemData(212, ItemClassification.filler, ["Unique", "TMs"]),
"TM13 Ice Beam": ItemData(213, ItemClassification.useful, ["Unique", "TMs"]),
"TM14 Blizzard": ItemData(214, ItemClassification.useful, ["Unique", "TMs"]),
"TM15 Hyper Beam": ItemData(215, ItemClassification.useful, ["Unique", "TMs"]),
@ -163,6 +165,10 @@ item_table = {
"Silph Co Liberated": ItemData(None, ItemClassification.progression, []),
"Become Champion": ItemData(None, ItemClassification.progression, [])
}
item_table.update({f"TM{str(i).zfill(2)}": ItemData(i + 456, ItemClassification.filler, ["Unique", "TMs"])
for i in range(1, 51)})
item_table.update(
{pokemon: ItemData(None, ItemClassification.progression, []) for pokemon in pokemon_data.keys()}
)

View File

@ -1,6 +1,8 @@
from BaseClasses import Location
from .rom_addresses import rom_addresses
from .poke_data import pokemon_data
loc_id_start = 172000000
@ -8,6 +10,10 @@ def trainersanity(multiworld, player):
return multiworld.trainersanity[player]
def dexsanity(multiworld, player):
return multiworld.dexsanity[player]
def hidden_items(multiworld, player):
return multiworld.randomize_hidden_items[player].value > 0
@ -20,14 +26,13 @@ def extra_key_items(multiworld, player):
return multiworld.extra_key_items[player]
def pokedex(multiworld, player):
return multiworld.randomize_pokedex[player].value > 0
def always_on(multiworld, player):
return True
def prizesanity(multiworld, player):
return multiworld.prizesanity[player]
class LocationData:
@ -72,6 +77,13 @@ class Rod:
self.flag = flag
class DexSanityFlag:
def __init__(self, flag):
self.byte = int(flag / 8)
self.bit = flag % 8
self.flag = flag
location_data = [
LocationData("Vermilion City", "Fishing Guru", "Old Rod", rom_addresses["Rod_Vermilion_City_Fishing_Guru"], Rod(3)),
@ -119,7 +131,7 @@ location_data = [
LocationData("Celadon City", "Gambling Addict", "Coin Case", rom_addresses["Event_Gambling_Addict"],
EventFlag(480)),
LocationData("Celadon Gym", "Erika 2", "TM21 Mega Drain", rom_addresses["Event_Celadon_Gym"], EventFlag(424)),
LocationData("Silph Co 11F", "Silph Co President", "Master Ball", rom_addresses["Event_Silph_Co_President"],
LocationData("Silph Co 11F", "Silph Co President (Card Key)", "Master Ball", rom_addresses["Event_Silph_Co_President"],
EventFlag(1933)),
LocationData("Silph Co 2F", "Woman", "TM36 Self-Destruct", rom_addresses["Event_Scared_Woman"],
EventFlag(1791)),
@ -374,7 +386,7 @@ location_data = [
LocationData("Seafoam Islands B4F", "Hidden Item Corner Island", "Ultra Ball", rom_addresses['Hidden_Item_Seafoam_Islands_B4F'], Hidden(26), inclusion=hidden_items),
LocationData("Pokemon Mansion 1F", "Hidden Item Block Near Entrance Carpet", "Moon Stone", rom_addresses['Hidden_Item_Pokemon_Mansion_1F'], Hidden(27), inclusion=hidden_items),
LocationData("Pokemon Mansion 3F", "Hidden Item Behind Burglar", "Max Revive", rom_addresses['Hidden_Item_Pokemon_Mansion_3F'], Hidden(28), inclusion=hidden_items),
LocationData("Route 23", "Hidden Item Rocks Before Final Guard", "Full Restore", rom_addresses['Hidden_Item_Route_23_1'], Hidden(29), inclusion=hidden_items),
LocationData("Route 23", "Hidden Item Rocks Before Victory Road", "Full Restore", rom_addresses['Hidden_Item_Route_23_1'], Hidden(29), inclusion=hidden_items),
LocationData("Route 23", "Hidden Item East Bush After Water", "Ultra Ball", rom_addresses['Hidden_Item_Route_23_2'], Hidden(30), inclusion=hidden_items),
LocationData("Route 23", "Hidden Item On Island", "Max Ether", rom_addresses['Hidden_Item_Route_23_3'], Hidden(31), inclusion=hidden_items),
LocationData("Victory Road 2F", "Hidden Item Rock Before Moltres", "Ultra Ball", rom_addresses['Hidden_Item_Victory_Road_2F_1'], Hidden(32), inclusion=hidden_items),
@ -400,7 +412,8 @@ location_data = [
LocationData("Cerulean City", "Hidden Item Gym Badge Guy's Backyard", "Rare Candy", rom_addresses['Hidden_Item_Cerulean_City'], Hidden(52), inclusion=hidden_items),
LocationData("Route 4", "Hidden Item Plateau East Of Mt Moon", "Great Ball", rom_addresses['Hidden_Item_Route_4'], Hidden(53), inclusion=hidden_items),
LocationData("Pallet Town", "Oak's Parcel Reward", "Pokedex", rom_addresses["Event_Pokedex"], EventFlag(0x38), inclusion=pokedex),
LocationData("Pallet Town", "Oak's Parcel Reward", "Pokedex", rom_addresses["Event_Pokedex"], EventFlag(0x38)),
LocationData("Pokemon Mansion 1F", "Scientist", None, rom_addresses["Trainersanity_EVENT_BEAT_MANSION_1_TRAINER_0_ITEM"], EventFlag(376), inclusion=trainersanity),
LocationData("Pokemon Mansion 2F", "Burglar", None, rom_addresses["Trainersanity_EVENT_BEAT_MANSION_2_TRAINER_0_ITEM"], EventFlag(43), inclusion=trainersanity),
@ -712,6 +725,36 @@ location_data = [
LocationData("Indigo Plateau", "Bruno", None, rom_addresses["Trainersanity_EVENT_BEAT_BRUNOS_ROOM_TRAINER_0_ITEM"], EventFlag(20), inclusion=trainersanity),
LocationData("Indigo Plateau", "Agatha", None, rom_addresses["Trainersanity_EVENT_BEAT_AGATHAS_ROOM_TRAINER_0_ITEM"], EventFlag(19), inclusion=trainersanity),
LocationData("Indigo Plateau", "Lance", None, rom_addresses["Trainersanity_EVENT_BEAT_LANCES_ROOM_TRAINER_0_ITEM"], EventFlag(18), inclusion=trainersanity),
LocationData("Cinnabar Gym", "Burglar 1", None, rom_addresses["Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_A_ITEM"], EventFlag(374), inclusion=trainersanity),
LocationData("Cinnabar Gym", "Super Nerd 1", None, rom_addresses["Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_B_ITEM"], EventFlag(373), inclusion=trainersanity),
LocationData("Cinnabar Gym", "Super Nerd 2", None, rom_addresses["Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_2_ITEM"], EventFlag(372), inclusion=trainersanity),
LocationData("Cinnabar Gym", "Burglar 2", None, rom_addresses["Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_3_ITEM"], EventFlag(371), inclusion=trainersanity),
LocationData("Cinnabar Gym", "Super Nerd 3", None, rom_addresses["Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_4_ITEM"], EventFlag(370), inclusion=trainersanity),
LocationData("Cinnabar Gym", "Super Nerd 4", None, rom_addresses["Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_5_ITEM"], EventFlag(369), inclusion=trainersanity),
LocationData("Cinnabar Gym", "Super Nerd 5", None, rom_addresses["Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_6_ITEM"], EventFlag(368), inclusion=trainersanity),
LocationData("Celadon Prize Corner", "Item Prize 1", "TM23 Dragon Rage", rom_addresses["Prize_Item_A"], EventFlag(0x69a), inclusion=prizesanity),
LocationData("Celadon Prize Corner", "Item Prize 2", "TM15 Hyper Beam", rom_addresses["Prize_Item_B"], EventFlag(0x69B), inclusion=prizesanity),
LocationData("Celadon Prize Corner", "Item Prize 3", "TM50 Substitute", rom_addresses["Prize_Item_C"], EventFlag(0x69C), inclusion=prizesanity),
LocationData("Celadon Game Corner", "West Gambler's Gift (Coin Case)", "10 Coins", rom_addresses["Event_Game_Corner_Gift_A"], EventFlag(0x1ba)),
LocationData("Celadon Game Corner", "Center Gambler's Gift (Coin Case)", "20 Coins", rom_addresses["Event_Game_Corner_Gift_C"], EventFlag(0x1bc)),
LocationData("Celadon Game Corner", "East Gambler's Gift (Coin Case)", "20 Coins", rom_addresses["Event_Game_Corner_Gift_B"], EventFlag(0x1bb)),
LocationData("Celadon Game Corner", "Hidden Item Northwest By Counter (Coin Case)", "10 Coins", rom_addresses["Hidden_Item_Game_Corner_1"], Hidden(54), inclusion=hidden_items),
LocationData("Celadon Game Corner", "Hidden Item Southwest Corner (Coin Case)", "10 Coins", rom_addresses["Hidden_Item_Game_Corner_2"], Hidden(55), inclusion=hidden_items),
LocationData("Celadon Game Corner", "Hidden Item Near Rumor Man (Coin Case)", "20 Coins", rom_addresses["Hidden_Item_Game_Corner_3"], Hidden(56), inclusion=hidden_items),
LocationData("Celadon Game Corner", "Hidden Item Near Speculating Woman (Coin Case)", "10 Coins", rom_addresses["Hidden_Item_Game_Corner_4"], Hidden(57), inclusion=hidden_items),
LocationData("Celadon Game Corner", "Hidden Item Near West Gifting Gambler (Coin Case)", "10 Coins", rom_addresses["Hidden_Item_Game_Corner_5"], Hidden(58), inclusion=hidden_items),
LocationData("Celadon Game Corner", "Hidden Item Near Wonderful Time Woman (Coin Case)", "20 Coins", rom_addresses["Hidden_Item_Game_Corner_6"], Hidden(59), inclusion=hidden_items),
LocationData("Celadon Game Corner", "Hidden Item Near Failing Gym Information Guy (Coin Case)", "10 Coins", rom_addresses["Hidden_Item_Game_Corner_7"], Hidden(60), inclusion=hidden_items),
LocationData("Celadon Game Corner", "Hidden Item Near East Gifting Gambler (Coin Case)", "10 Coins", rom_addresses["Hidden_Item_Game_Corner_8"], Hidden(61), inclusion=hidden_items),
LocationData("Celadon Game Corner", "Hidden Item Near Hooked Guy (Coin Case)", "10 Coins", rom_addresses["Hidden_Item_Game_Corner_9"], Hidden(62), inclusion=hidden_items),
LocationData("Celadon Game Corner", "Hidden Item at End of Horizontal Machine Row (Coin Case)", "20 Coins", rom_addresses["Hidden_Item_Game_Corner_10"], Hidden(63), inclusion=hidden_items),
LocationData("Celadon Game Corner", "Hidden Item in Front of Horizontal Machine Row (Coin Case)", "100 Coins", rom_addresses["Hidden_Item_Game_Corner_11"], Hidden(64), inclusion=hidden_items),
*[LocationData("Pokedex", mon, None, rom_addresses["Dexsanity_Items"] + i, DexSanityFlag(i),
type="Item", inclusion=dexsanity) for (mon, i) in zip(pokemon_data.keys(), range(0, 152))],
LocationData("Indigo Plateau", "Become Champion", "Become Champion", event=True),
LocationData("Pokemon Tower 7F", "Fuji Saved", "Fuji Saved", event=True),
@ -1965,6 +2008,25 @@ location_data = [
LocationData("Cinnabar Island", "Dome Fossil Pokemon", "Kabuto", rom_addresses["Gift_Kabuto"], None,
event=True, type="Static Pokemon"),
LocationData("Route 2 East", "Marcel Trade", "Mr Mime", rom_addresses["Trade_Marcel"] + 1, None, event=True,
type="Static Pokemon"),
LocationData("Underground Tunnel North-South", "Spot Trade", "Nidoran F", rom_addresses["Trade_Spot"] + 1, None,
event=True, type="Static Pokemon"),
LocationData("Route 11", "Terry Trade", "Nidorina", rom_addresses["Trade_Terry"] + 1, None, event=True,
type="Static Pokemon"),
LocationData("Route 18", "Marc Trade", "Lickitung", rom_addresses["Trade_Marc"] + 1, None, event=True,
type="Static Pokemon"),
LocationData("Cinnabar Island", "Sailor Trade", "Seel", rom_addresses["Trade_Sailor"] + 1, None, event=True,
type="Static Pokemon"),
LocationData("Cinnabar Island", "Crinkles Trade", "Tangela", rom_addresses["Trade_Crinkles"] + 1, None,
event=True, type="Static Pokemon"),
LocationData("Cinnabar Island", "Doris Trade", "Electrode", rom_addresses["Trade_Doris"] + 1, None,
event=True, type="Static Pokemon"),
LocationData("Vermilion City", "Dux Trade", "Farfetchd", rom_addresses["Trade_Dux"] + 1, None, event=True,
type="Static Pokemon"),
LocationData("Cerulean City", "Lola Trade", "Jynx", rom_addresses["Trade_Lola"] + 1, None, event=True,
type="Static Pokemon"),
# not counted for logic currently. Could perhaps make static encounters resettable in the future?
LocationData("Power Plant", "Fake Pokeball Battle 1", "Voltorb", rom_addresses["Static_Encounter_Voltorb_A"],
None, event=True, type="Missable Pokemon"),
@ -2043,20 +2105,24 @@ location_data = [
]
for i, location in enumerate(location_data):
i = 0
for location in location_data:
if location.event or location.rom_address is None:
location.address = None
else:
location.address = loc_id_start + i
i += 1
class PokemonRBLocation(Location):
game = "Pokemon Red and Blue"
def __init__(self, player, name, address, rom_address):
def __init__(self, player, name, address, rom_address, type):
super(PokemonRBLocation, self).__init__(
player, name,
address
)
self.rom_address = rom_address
self.rom_address = rom_address
self.type = type

View File

@ -45,14 +45,13 @@ class PokemonLogic(LogicMixin):
["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",
"Mansion Key", "Safari Pass", "Plant Key", "Hideout Key", "HM01 Cut", "HM02 Fly", "HM03 Surf",
"HM04 Strength", "HM05 Flash"] if self.has(item, player)]) >= count
"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:
# this could just be "True", but you never know what weird options I might introduce later ;)
return self.can_reach("Celadon City - Counter Man", "Location", player)
def pokemon_rb_has_badges(self, count, player):
@ -60,13 +59,8 @@ class PokemonLogic(LogicMixin):
"Soul Badge", "Volcano Badge", "Earth Badge"] if self.has(item, player)]) >= count
def pokemon_rb_oaks_aide(self, count, player):
if self.multiworld.randomize_pokedex[player].value > 0:
if not self.has("Pokedex", player):
return False
else:
if not self.has("Oak's Parcel", player):
return False
return self.pokemon_rb_has_pokemon(count, player)
return ((not self.multiworld.require_pokedex[player] or self.has("Pokedex", player))
and self.pokemon_rb_has_pokemon(count, player))
def pokemon_rb_has_pokemon(self, count, player):
obtained_pokemon = set()

View File

@ -65,7 +65,7 @@ class CeruleanCaveCondition(Range):
If extra_key_items is turned on, the number chosen will be increased by 4."""
display_name = "Cerulean Cave Condition"
range_start = 0
range_end = 25
range_end = 26
default = 20
@ -155,6 +155,12 @@ class RandomizeHiddenItems(Choice):
default = 0
class PrizeSanity(Toggle):
"""Shuffles the TM prizes at the Celadon Prize Corner into the item pool."""
display_name = "Prizesanity"
default = 0
class TrainerSanity(Toggle):
"""Add a location check to every trainer in the game, which can be obtained by talking to a trainer after defeating
them. Does not affect gym leaders and some scripted event battles (including all Rival, Giovanni, and
@ -163,12 +169,44 @@ class TrainerSanity(Toggle):
default = 0
class RequirePokedex(Toggle):
"""Require the Pokedex to obtain items from Oak's Aides or from Dexsanity checks."""
display_name = "Require Pokedex"
default = 1
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."""
default = 0
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.
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
class FreeFlyLocation(Toggle):
"""One random fly destination will be unlocked by default."""
display_name = "Free Fly Location"
default = 1
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."""
display_name = "Randomize Rock Tunnel"
default = 0
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."""
@ -229,6 +267,12 @@ class RandomizeWildPokemon(Choice):
option_completely_random = 4
class Area1To1Mapping(Toggle):
"""When randomizing wild Pokemon, for each zone, all instances of a particular Pokemon will be replaced with the
same Pokemon, resulting in fewer Pokemon in each area."""
default = 1
class RandomizeStarterPokemon(Choice):
"""Randomize the starter Pokemon choices."""
display_name = "Randomize Starter Pokemon"
@ -334,6 +378,13 @@ class MinimumCatchRate(Range):
default = 3
class MoveBalancing(Toggle):
"""All one-hit-KO moves and fixed-damage moves become normal damaging moves.
Blizzard, and moves that cause sleep have their accuracy reduced."""
display_name = "Move Balancing"
default = 0
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"
@ -343,6 +394,12 @@ class RandomizePokemonMovesets(Choice):
default = 0
class ConfineTranstormToDitto(Toggle):
"""Regardless of moveset randomization, will keep Ditto's first move as Transform no others will learn it.
If an enemy Pokemon uses transform before you catch it, it will permanently change to Ditto after capture."""
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"
@ -356,30 +413,62 @@ class SameTypeAttackBonus(Toggle):
default = 1
class TMCompatibility(Choice):
"""Randomize which Pokemon can learn each TM. prefer_types: 90% chance if Pokemon's type matches the move,
50% chance if move is Normal type and the Pokemon is not, and 25% chance otherwise. Pokemon will retain the same
TM compatibility when they evolve if the evolved form has the same type(s). Mew will always be able to learn
every TM."""
display_name = "TM Compatibility"
default = 0
option_vanilla = 0
option_prefer_types = 1
option_completely_random = 2
option_full_compatibility = 3
class RandomizeTMMoves(Toggle):
"""Randomize the moves taught by TMs.
All TM items will be flagged as 'filler' items regardless of how good the move they teach are."""
display_name = "Randomize TM Moves"
class HMCompatibility(Choice):
"""Randomize which Pokemon can learn each HM. prefer_types: 100% chance if Pokemon's type matches the move,
75% chance if move is Normal type and the Pokemon is not, and 25% chance otherwise. Pokemon will retain the same
HM compatibility when they evolve if the evolved form has the same type(s). Mew will always be able to learn
every HM."""
display_name = "HM Compatibility"
default = 0
option_vanilla = 0
option_prefer_types = 1
option_completely_random = 2
option_full_compatibility = 3
class TMHMCompatibility(SpecialRange):
range_start = -1
range_end = 100
special_range_names = {
"vanilla": -1,
"none": 0,
"full": 100
}
default = -1
class TMSameTypeCompatibility(TMHMCompatibility):
"""Chance of each TM being usable on each Pokemon whose type matches the move."""
display_name = "TM Same-Type Compatibility"
class TMNormalTypeCompatibility(TMHMCompatibility):
"""Chance of each TM being usable on each Pokemon if the move is Normal type and the Pokemon is not."""
display_name = "TM Normal-Type Compatibility"
class TMOtherTypeCompatibility(TMHMCompatibility):
"""Chance of each TM being usable on each Pokemon if the move a type other than Normal or one of the Pokemon's types."""
display_name = "TM Other-Type Compatibility"
class HMSameTypeCompatibility(TMHMCompatibility):
"""Chance of each HM being usable on each Pokemon whose type matches the move.
At least one Pokemon will always be able to learn the moves needed to meet your accessibility requirements."""
display_name = "HM Same-Type Compatibility"
class HMNormalTypeCompatibility(TMHMCompatibility):
"""Chance of each HM being usable on each Pokemon if the move is Normal type and the Pokemon is not.
At least one Pokemon will always be able to learn the moves needed to meet your accessibility requirements."""
display_name = "HM Normal-Type Compatibility"
class HMOtherTypeCompatibility(TMHMCompatibility):
"""Chance of each HM being usable on each Pokemon if the move a type other than Normal or one of the Pokemon's types.
At least one Pokemon will always be able to learn the moves needed to meet your accessibility requirements."""
display_name = "HM Other-Type Compatibility"
class InheritTMHMCompatibility(Toggle):
"""If on, evolved Pokemon will inherit their pre-evolved form's TM and HM compatibilities.
They will then roll the above set chances again at a 50% lower rate for all TMs and HMs their predecessor could not
learn, unless the evolved form has additional or different types, then moves of those new types will be rolled
at the full set chance."""
display_name = "Inherit TM/HM Compatibility"
class RandomizePokemonTypes(Choice):
@ -543,6 +632,17 @@ class IceTrapWeight(TrapWeight):
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),
Completely Random will randomize all Pokemons' palettes individually"""
display_name = "Randomize Pokemon Palettes"
option_vanilla = 0
option_primary_type = 1
option_follow_evolutions = 2
option_completely_random = 3
pokemon_rb_options = {
"game_version": GameVersion,
"trainer_name": TrainerName,
@ -561,16 +661,22 @@ pokemon_rb_options = {
"extra_strength_boulders": ExtraStrengthBoulders,
"require_item_finder": RequireItemFinder,
"randomize_hidden_items": RandomizeHiddenItems,
"prizesanity": PrizeSanity,
"trainersanity": TrainerSanity,
"badges_needed_for_hm_moves": BadgesNeededForHMMoves,
"free_fly_location": FreeFlyLocation,
"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,
"randomize_rock_tunnel": RandomizeRockTunnel,
"blind_trainers": BlindTrainers,
"minimum_steps_between_encounters": MinimumStepsBetweenEncounters,
"exp_modifier": ExpModifier,
"randomize_wild_pokemon": RandomizeWildPokemon,
"area_1_to_1_mapping": Area1To1Mapping,
"randomize_starter_pokemon": RandomizeStarterPokemon,
"randomize_static_pokemon": RandomizeStaticPokemon,
"randomize_legendary_pokemon": RandomizeLegendaryPokemon,
@ -580,11 +686,19 @@ pokemon_rb_options = {
"minimum_catch_rate": MinimumCatchRate,
"randomize_trainer_parties": RandomizeTrainerParties,
"trainer_legendaries": TrainerLegendaries,
"move_balancing": MoveBalancing,
"randomize_pokemon_movesets": RandomizePokemonMovesets,
"confine_transform_to_ditto": ConfineTranstormToDitto,
"start_with_four_moves": StartWithFourMoves,
"same_type_attack_bonus": SameTypeAttackBonus,
"tm_compatibility": TMCompatibility,
"hm_compatibility": HMCompatibility,
"randomize_tm_moves": RandomizeTMMoves,
"tm_same_type_compatibility": TMSameTypeCompatibility,
"tm_normal_type_compatibility": TMNormalTypeCompatibility,
"tm_other_type_compatibility": TMOtherTypeCompatibility,
"hm_same_type_compatibility": HMSameTypeCompatibility,
"hm_normal_type_compatibility": HMNormalTypeCompatibility,
"hm_other_type_compatibility": HMOtherTypeCompatibility,
"inherit_tm_hm_compatibility": InheritTMHMCompatibility,
"randomize_pokemon_types": RandomizePokemonTypes,
"secondary_type_chance": SecondaryTypeChance,
"randomize_type_chart": RandomizeTypeChart,
@ -604,5 +718,6 @@ pokemon_rb_options = {
"fire_trap_weight": FireTrapWeight,
"paralyze_trap_weight": ParalyzeTrapWeight,
"ice_trap_weight": IceTrapWeight,
"randomize_pokemon_palettes": RandomizePokemonPalettes,
"death_link": DeathLink
}

View File

@ -1006,172 +1006,172 @@ learnsets = {
}
moves = {
'No Move': {'id': 0, 'power': 0, 'type': 'Typeless', 'accuracy': 0, 'pp': 0},
'Pound': {'id': 1, 'power': 40, 'type': 'Normal', 'accuracy': 100, 'pp': 35},
'Karate Chop': {'id': 2, 'power': 50, 'type': 'Normal', 'accuracy': 100, 'pp': 25},
'Doubleslap': {'id': 3, 'power': 15, 'type': 'Normal', 'accuracy': 85, 'pp': 10},
'Comet Punch': {'id': 4, 'power': 18, 'type': 'Normal', 'accuracy': 85, 'pp': 15},
'Mega Punch': {'id': 5, 'power': 80, 'type': 'Normal', 'accuracy': 85, 'pp': 20},
'Pay Day': {'id': 6, 'power': 40, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
'Fire Punch': {'id': 7, 'power': 75, 'type': 'Fire', 'accuracy': 100, 'pp': 15},
'Ice Punch': {'id': 8, 'power': 75, 'type': 'Ice', 'accuracy': 100, 'pp': 15},
'Thunderpunch': {'id': 9, 'power': 75, 'type': 'Electric', 'accuracy': 100, 'pp': 15},
'Scratch': {'id': 10, 'power': 40, 'type': 'Normal', 'accuracy': 100, 'pp': 35},
'Vicegrip': {'id': 11, 'power': 55, 'type': 'Normal', 'accuracy': 100, 'pp': 30},
'Guillotine': {'id': 12, 'power': 1, 'type': 'Normal', 'accuracy': 30, 'pp': 5},
'Razor Wind': {'id': 13, 'power': 80, 'type': 'Normal', 'accuracy': 75, 'pp': 10},
'Swords Dance': {'id': 14, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30},
'Cut': {'id': 15, 'power': 50, 'type': 'Normal', 'accuracy': 95, 'pp': 30},
'Gust': {'id': 16, 'power': 40, 'type': 'Normal', 'accuracy': 100, 'pp': 35},
'Wing Attack': {'id': 17, 'power': 35, 'type': 'Flying', 'accuracy': 100, 'pp': 35},
'Whirlwind': {'id': 18, 'power': 0, 'type': 'Normal', 'accuracy': 85, 'pp': 20},
'Fly': {'id': 19, 'power': 70, 'type': 'Flying', 'accuracy': 95, 'pp': 15},
'Bind': {'id': 20, 'power': 15, 'type': 'Normal', 'accuracy': 75, 'pp': 20},
'Slam': {'id': 21, 'power': 80, 'type': 'Normal', 'accuracy': 75, 'pp': 20},
'Vine Whip': {'id': 22, 'power': 35, 'type': 'Grass', 'accuracy': 100, 'pp': 10},
'Stomp': {'id': 23, 'power': 65, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
'Double Kick': {'id': 24, 'power': 30, 'type': 'Fighting', 'accuracy': 100, 'pp': 30},
'Mega Kick': {'id': 25, 'power': 120, 'type': 'Normal', 'accuracy': 75, 'pp': 5},
'Jump Kick': {'id': 26, 'power': 70, 'type': 'Fighting', 'accuracy': 95, 'pp': 25},
'Rolling Kick': {'id': 27, 'power': 60, 'type': 'Fighting', 'accuracy': 85, 'pp': 15},
'Sand Attack': {'id': 28, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 15},
'Headbutt': {'id': 29, 'power': 70, 'type': 'Normal', 'accuracy': 100, 'pp': 15},
'Horn Attack': {'id': 30, 'power': 65, 'type': 'Normal', 'accuracy': 100, 'pp': 25},
'Fury Attack': {'id': 31, 'power': 15, 'type': 'Normal', 'accuracy': 85, 'pp': 20},
'Horn Drill': {'id': 32, 'power': 1, 'type': 'Normal', 'accuracy': 30, 'pp': 5},
'Tackle': {'id': 33, 'power': 35, 'type': 'Normal', 'accuracy': 95, 'pp': 35},
'Body Slam': {'id': 34, 'power': 85, 'type': 'Normal', 'accuracy': 100, 'pp': 15},
'Wrap': {'id': 35, 'power': 15, 'type': 'Normal', 'accuracy': 85, 'pp': 20},
'Take Down': {'id': 36, 'power': 90, 'type': 'Normal', 'accuracy': 85, 'pp': 20},
'Thrash': {'id': 37, 'power': 90, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
'Double Edge': {'id': 38, 'power': 100, 'type': 'Normal', 'accuracy': 100, 'pp': 15},
'Tail Whip': {'id': 39, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30},
'Poison Sting': {'id': 40, 'power': 15, 'type': 'Poison', 'accuracy': 100, 'pp': 35},
'Twineedle': {'id': 41, 'power': 25, 'type': 'Bug', 'accuracy': 100, 'pp': 20},
'Pin Missile': {'id': 42, 'power': 14, 'type': 'Bug', 'accuracy': 85, 'pp': 20},
'Leer': {'id': 43, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30},
'Bite': {'id': 44, 'power': 60, 'type': 'Normal', 'accuracy': 100, 'pp': 25},
'Growl': {'id': 45, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 40},
'Roar': {'id': 46, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
'Sing': {'id': 47, 'power': 0, 'type': 'Normal', 'accuracy': 55, 'pp': 15},
'Supersonic': {'id': 48, 'power': 0, 'type': 'Normal', 'accuracy': 55, 'pp': 20},
'Sonicboom': {'id': 49, 'power': 1, 'type': 'Normal', 'accuracy': 90, 'pp': 20},
'Disable': {'id': 50, 'power': 0, 'type': 'Normal', 'accuracy': 55, 'pp': 20},
'Acid': {'id': 51, 'power': 40, 'type': 'Poison', 'accuracy': 100, 'pp': 30},
'Ember': {'id': 52, 'power': 40, 'type': 'Fire', 'accuracy': 100, 'pp': 25},
'Flamethrower': {'id': 53, 'power': 95, 'type': 'Fire', 'accuracy': 100, 'pp': 15},
'Mist': {'id': 54, 'power': 0, 'type': 'Ice', 'accuracy': 100, 'pp': 30},
'Water Gun': {'id': 55, 'power': 40, 'type': 'Water', 'accuracy': 100, 'pp': 25},
'Hydro Pump': {'id': 56, 'power': 120, 'type': 'Water', 'accuracy': 80, 'pp': 5},
'Surf': {'id': 57, 'power': 95, 'type': 'Water', 'accuracy': 100, 'pp': 15},
'Ice Beam': {'id': 58, 'power': 95, 'type': 'Ice', 'accuracy': 100, 'pp': 10},
'Blizzard': {'id': 59, 'power': 120, 'type': 'Ice', 'accuracy': 90, 'pp': 5},
'Psybeam': {'id': 60, 'power': 65, 'type': 'Psychic', 'accuracy': 100, 'pp': 20},
'Bubblebeam': {'id': 61, 'power': 65, 'type': 'Water', 'accuracy': 100, 'pp': 20},
'Aurora Beam': {'id': 62, 'power': 65, 'type': 'Ice', 'accuracy': 100, 'pp': 20},
'Hyper Beam': {'id': 63, 'power': 150, 'type': 'Normal', 'accuracy': 90, 'pp': 5},
'Peck': {'id': 64, 'power': 35, 'type': 'Flying', 'accuracy': 100, 'pp': 35},
'Drill Peck': {'id': 65, 'power': 80, 'type': 'Flying', 'accuracy': 100, 'pp': 20},
'Submission': {'id': 66, 'power': 80, 'type': 'Fighting', 'accuracy': 80, 'pp': 25},
'Low Kick': {'id': 67, 'power': 50, 'type': 'Fighting', 'accuracy': 90, 'pp': 20},
'Counter': {'id': 68, 'power': 1, 'type': 'Fighting', 'accuracy': 100, 'pp': 20},
'Seismic Toss': {'id': 69, 'power': 1, 'type': 'Fighting', 'accuracy': 100, 'pp': 20},
'Strength': {'id': 70, 'power': 80, 'type': 'Normal', 'accuracy': 100, 'pp': 15},
'Absorb': {'id': 71, 'power': 20, 'type': 'Grass', 'accuracy': 100, 'pp': 20},
'Mega Drain': {'id': 72, 'power': 40, 'type': 'Grass', 'accuracy': 100, 'pp': 10},
'Leech Seed': {'id': 73, 'power': 0, 'type': 'Grass', 'accuracy': 90, 'pp': 10},
'Growth': {'id': 74, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 40},
'Razor Leaf': {'id': 75, 'power': 55, 'type': 'Grass', 'accuracy': 95, 'pp': 25},
'Solarbeam': {'id': 76, 'power': 120, 'type': 'Grass', 'accuracy': 100, 'pp': 10},
'Poisonpowder': {'id': 77, 'power': 0, 'type': 'Poison', 'accuracy': 75, 'pp': 35},
'Stun Spore': {'id': 78, 'power': 0, 'type': 'Grass', 'accuracy': 75, 'pp': 30},
'Sleep Powder': {'id': 79, 'power': 0, 'type': 'Grass', 'accuracy': 75, 'pp': 15},
'Petal Dance': {'id': 80, 'power': 70, 'type': 'Grass', 'accuracy': 100, 'pp': 20},
'String Shot': {'id': 81, 'power': 0, 'type': 'Bug', 'accuracy': 95, 'pp': 40},
'Dragon Rage': {'id': 82, 'power': 1, 'type': 'Dragon', 'accuracy': 100, 'pp': 10},
'Fire Spin': {'id': 83, 'power': 15, 'type': 'Fire', 'accuracy': 70, 'pp': 15},
'Thundershock': {'id': 84, 'power': 40, 'type': 'Electric', 'accuracy': 100, 'pp': 30},
'Thunderbolt': {'id': 85, 'power': 95, 'type': 'Electric', 'accuracy': 100, 'pp': 15},
'Thunder Wave': {'id': 86, 'power': 0, 'type': 'Electric', 'accuracy': 100, 'pp': 20},
'Thunder': {'id': 87, 'power': 120, 'type': 'Electric', 'accuracy': 70, 'pp': 10},
'Rock Throw': {'id': 88, 'power': 50, 'type': 'Rock', 'accuracy': 65, 'pp': 15},
'Earthquake': {'id': 89, 'power': 100, 'type': 'Ground', 'accuracy': 100, 'pp': 10},
'Fissure': {'id': 90, 'power': 1, 'type': 'Ground', 'accuracy': 30, 'pp': 5},
'Dig': {'id': 91, 'power': 100, 'type': 'Ground', 'accuracy': 100, 'pp': 10},
'Toxic': {'id': 92, 'power': 0, 'type': 'Poison', 'accuracy': 85, 'pp': 10},
'Confusion': {'id': 93, 'power': 50, 'type': 'Psychic', 'accuracy': 100, 'pp': 25},
'Psychic': {'id': 94, 'power': 90, 'type': 'Psychic', 'accuracy': 100, 'pp': 10},
'Hypnosis': {'id': 95, 'power': 0, 'type': 'Psychic', 'accuracy': 60, 'pp': 20},
'Meditate': {'id': 96, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 40},
'Agility': {'id': 97, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 30},
'Quick Attack': {'id': 98, 'power': 40, 'type': 'Normal', 'accuracy': 100, 'pp': 30},
'Rage': {'id': 99, 'power': 20, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
'Teleport': {'id': 100, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 20},
'Night Shade': {'id': 101, 'power': 0, 'type': 'Ghost', 'accuracy': 100, 'pp': 15},
'Mimic': {'id': 102, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 10},
'Screech': {'id': 103, 'power': 0, 'type': 'Normal', 'accuracy': 85, 'pp': 40},
'Double Team': {'id': 104, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 15},
'Recover': {'id': 105, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
'Harden': {'id': 106, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30},
'Minimize': {'id': 107, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
'Smokescreen': {'id': 108, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
'Confuse Ray': {'id': 109, 'power': 0, 'type': 'Ghost', 'accuracy': 100, 'pp': 10},
'Withdraw': {'id': 110, 'power': 0, 'type': 'Water', 'accuracy': 100, 'pp': 40},
'Defense Curl': {'id': 111, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 40},
'Barrier': {'id': 112, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 30},
'Light Screen': {'id': 113, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 30},
'Haze': {'id': 114, 'power': 0, 'type': 'Ice', 'accuracy': 100, 'pp': 30},
'Reflect': {'id': 115, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 20},
'Focus Energy': {'id': 116, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30},
'Bide': {'id': 117, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 10},
'Metronome': {'id': 118, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 10},
'Mirror Move': {'id': 119, 'power': 0, 'type': 'Flying', 'accuracy': 100, 'pp': 20},
'Selfdestruct': {'id': 120, 'power': 130, 'type': 'Normal', 'accuracy': 100, 'pp': 5},
'Egg Bomb': {'id': 121, 'power': 100, 'type': 'Normal', 'accuracy': 75, 'pp': 10},
'Lick': {'id': 122, 'power': 20, 'type': 'Ghost', 'accuracy': 100, 'pp': 30},
'Smog': {'id': 123, 'power': 20, 'type': 'Poison', 'accuracy': 70, 'pp': 20},
'Sludge': {'id': 124, 'power': 65, 'type': 'Poison', 'accuracy': 100, 'pp': 20},
'Bone Club': {'id': 125, 'power': 65, 'type': 'Ground', 'accuracy': 85, 'pp': 20},
'Fire Blast': {'id': 126, 'power': 120, 'type': 'Fire', 'accuracy': 85, 'pp': 5},
'Waterfall': {'id': 127, 'power': 80, 'type': 'Water', 'accuracy': 100, 'pp': 15},
'Clamp': {'id': 128, 'power': 35, 'type': 'Water', 'accuracy': 75, 'pp': 10},
'Swift': {'id': 129, 'power': 60, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
'Skull Bash': {'id': 130, 'power': 100, 'type': 'Normal', 'accuracy': 100, 'pp': 15},
'Spike Cannon': {'id': 131, 'power': 20, 'type': 'Normal', 'accuracy': 100, 'pp': 15},
'Constrict': {'id': 132, 'power': 10, 'type': 'Normal', 'accuracy': 100, 'pp': 35},
'Amnesia': {'id': 133, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 20},
'Kinesis': {'id': 134, 'power': 0, 'type': 'Psychic', 'accuracy': 80, 'pp': 15},
'Softboiled': {'id': 135, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 10},
'Hi Jump Kick': {'id': 136, 'power': 85, 'type': 'Fighting', 'accuracy': 90, 'pp': 20},
'Glare': {'id': 137, 'power': 0, 'type': 'Normal', 'accuracy': 75, 'pp': 30},
'Dream Eater': {'id': 138, 'power': 100, 'type': 'Psychic', 'accuracy': 100, 'pp': 15},
'Poison Gas': {'id': 139, 'power': 0, 'type': 'Poison', 'accuracy': 55, 'pp': 40},
'Barrage': {'id': 140, 'power': 15, 'type': 'Normal', 'accuracy': 85, 'pp': 20},
'Leech Life': {'id': 141, 'power': 20, 'type': 'Bug', 'accuracy': 100, 'pp': 15},
'Lovely Kiss': {'id': 142, 'power': 0, 'type': 'Normal', 'accuracy': 75, 'pp': 10},
'Sky Attack': {'id': 143, 'power': 140, 'type': 'Flying', 'accuracy': 90, 'pp': 5},
'Transform': {'id': 144, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 10},
'Bubble': {'id': 145, 'power': 20, 'type': 'Water', 'accuracy': 100, 'pp': 30},
'Dizzy Punch': {'id': 146, 'power': 70, 'type': 'Normal', 'accuracy': 100, 'pp': 10},
'Spore': {'id': 147, 'power': 0, 'type': 'Grass', 'accuracy': 100, 'pp': 15},
'Flash': {'id': 148, 'power': 0, 'type': 'Normal', 'accuracy': 70, 'pp': 20},
'Psywave': {'id': 149, 'power': 1, 'type': 'Psychic', 'accuracy': 80, 'pp': 15},
'Splash': {'id': 150, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 40},
'Acid Armor': {'id': 151, 'power': 0, 'type': 'Poison', 'accuracy': 100, 'pp': 40},
'Crabhammer': {'id': 152, 'power': 90, 'type': 'Water', 'accuracy': 85, 'pp': 10},
'Explosion': {'id': 153, 'power': 170, 'type': 'Normal', 'accuracy': 100, 'pp': 5},
'Fury Swipes': {'id': 154, 'power': 18, 'type': 'Normal', 'accuracy': 80, 'pp': 15},
'Bonemerang': {'id': 155, 'power': 50, 'type': 'Ground', 'accuracy': 90, 'pp': 10},
'Rest': {'id': 156, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 10},
'Rock Slide': {'id': 157, 'power': 75, 'type': 'Rock', 'accuracy': 90, 'pp': 10},
'Hyper Fang': {'id': 158, 'power': 80, 'type': 'Normal', 'accuracy': 90, 'pp': 15},
'Sharpen': {'id': 159, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30},
'Conversion': {'id': 160, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30},
'Tri Attack': {'id': 161, 'power': 80, 'type': 'Normal', 'accuracy': 100, 'pp': 10},
'Super Fang': {'id': 162, 'power': 1, 'type': 'Normal', 'accuracy': 90, 'pp': 10},
'Slash': {'id': 163, 'power': 70, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
'Substitute': {'id': 164, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 10},
#'Struggle': {'id': 165, 'power': 50, 'type': 'Struggle_Type', 'accuracy': 100, 'pp': 10}
'No Move': {'id': 0, 'power': 0, 'type': 'Typeless', 'accuracy': 0, 'pp': 0, 'effect': 0},
'Pound': {'id': 1, 'power': 40, 'type': 'Normal', 'accuracy': 100, 'pp': 35, 'effect': 0},
'Karate Chop': {'id': 2, 'power': 50, 'type': 'Normal', 'accuracy': 100, 'pp': 25, 'effect': 0},
'Doubleslap': {'id': 3, 'power': 15, 'type': 'Normal', 'accuracy': 85, 'pp': 10, 'effect': 29},
'Comet Punch': {'id': 4, 'power': 18, 'type': 'Normal', 'accuracy': 85, 'pp': 15, 'effect': 29},
'Mega Punch': {'id': 5, 'power': 80, 'type': 'Normal', 'accuracy': 85, 'pp': 20, 'effect': 0},
'Pay Day': {'id': 6, 'power': 40, 'type': 'Normal', 'accuracy': 100, 'pp': 20, 'effect': 16},
'Fire Punch': {'id': 7, 'power': 75, 'type': 'Fire', 'accuracy': 100, 'pp': 15, 'effect': 4},
'Ice Punch': {'id': 8, 'power': 75, 'type': 'Ice', 'accuracy': 100, 'pp': 15, 'effect': 5},
'Thunderpunch': {'id': 9, 'power': 75, 'type': 'Electric', 'accuracy': 100, 'pp': 15, 'effect': 6},
'Scratch': {'id': 10, 'power': 40, 'type': 'Normal', 'accuracy': 100, 'pp': 35, 'effect': 0},
'Vicegrip': {'id': 11, 'power': 55, 'type': 'Normal', 'accuracy': 100, 'pp': 30, 'effect': 0},
'Guillotine': {'id': 12, 'power': 1, 'type': 'Normal', 'accuracy': 30, 'pp': 5, 'effect': 38},
'Razor Wind': {'id': 13, 'power': 80, 'type': 'Normal', 'accuracy': 75, 'pp': 10, 'effect': 39},
'Swords Dance': {'id': 14, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30, 'effect': 50},
'Cut': {'id': 15, 'power': 50, 'type': 'Normal', 'accuracy': 95, 'pp': 30, 'effect': 0},
'Gust': {'id': 16, 'power': 40, 'type': 'Normal', 'accuracy': 100, 'pp': 35, 'effect': 0},
'Wing Attack': {'id': 17, 'power': 35, 'type': 'Flying', 'accuracy': 100, 'pp': 35, 'effect': 0},
'Whirlwind': {'id': 18, 'power': 0, 'type': 'Normal', 'accuracy': 85, 'pp': 20, 'effect': 28},
'Fly': {'id': 19, 'power': 70, 'type': 'Flying', 'accuracy': 95, 'pp': 15, 'effect': 43},
'Bind': {'id': 20, 'power': 15, 'type': 'Normal', 'accuracy': 75, 'pp': 20, 'effect': 42},
'Slam': {'id': 21, 'power': 80, 'type': 'Normal', 'accuracy': 75, 'pp': 20, 'effect': 0},
'Vine Whip': {'id': 22, 'power': 35, 'type': 'Grass', 'accuracy': 100, 'pp': 10, 'effect': 0},
'Stomp': {'id': 23, 'power': 65, 'type': 'Normal', 'accuracy': 100, 'pp': 20, 'effect': 37},
'Double Kick': {'id': 24, 'power': 30, 'type': 'Fighting', 'accuracy': 100, 'pp': 30, 'effect': 44},
'Mega Kick': {'id': 25, 'power': 120, 'type': 'Normal', 'accuracy': 75, 'pp': 5, 'effect': 0},
'Jump Kick': {'id': 26, 'power': 70, 'type': 'Fighting', 'accuracy': 95, 'pp': 25, 'effect': 45},
'Rolling Kick': {'id': 27, 'power': 60, 'type': 'Fighting', 'accuracy': 85, 'pp': 15, 'effect': 37},
'Sand Attack': {'id': 28, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 15, 'effect': 22},
'Headbutt': {'id': 29, 'power': 70, 'type': 'Normal', 'accuracy': 100, 'pp': 15, 'effect': 37},
'Horn Attack': {'id': 30, 'power': 65, 'type': 'Normal', 'accuracy': 100, 'pp': 25, 'effect': 0},
'Fury Attack': {'id': 31, 'power': 15, 'type': 'Normal', 'accuracy': 85, 'pp': 20, 'effect': 29},
'Horn Drill': {'id': 32, 'power': 1, 'type': 'Normal', 'accuracy': 30, 'pp': 5, 'effect': 38},
'Tackle': {'id': 33, 'power': 35, 'type': 'Normal', 'accuracy': 95, 'pp': 35, 'effect': 0},
'Body Slam': {'id': 34, 'power': 85, 'type': 'Normal', 'accuracy': 100, 'pp': 15, 'effect': 36},
'Wrap': {'id': 35, 'power': 15, 'type': 'Normal', 'accuracy': 85, 'pp': 20, 'effect': 42},
'Take Down': {'id': 36, 'power': 90, 'type': 'Normal', 'accuracy': 85, 'pp': 20, 'effect': 48},
'Thrash': {'id': 37, 'power': 90, 'type': 'Normal', 'accuracy': 100, 'pp': 20, 'effect': 27},
'Double Edge': {'id': 38, 'power': 100, 'type': 'Normal', 'accuracy': 100, 'pp': 15, 'effect': 48},
'Tail Whip': {'id': 39, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30, 'effect': 19},
'Poison Sting': {'id': 40, 'power': 15, 'type': 'Poison', 'accuracy': 100, 'pp': 35, 'effect': 2},
'Twineedle': {'id': 41, 'power': 25, 'type': 'Bug', 'accuracy': 100, 'pp': 20, 'effect': 77},
'Pin Missile': {'id': 42, 'power': 14, 'type': 'Bug', 'accuracy': 85, 'pp': 20, 'effect': 29},
'Leer': {'id': 43, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30, 'effect': 19},
'Bite': {'id': 44, 'power': 60, 'type': 'Normal', 'accuracy': 100, 'pp': 25, 'effect': 31},
'Growl': {'id': 45, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 40, 'effect': 18},
'Roar': {'id': 46, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 20, 'effect': 28},
'Sing': {'id': 47, 'power': 0, 'type': 'Normal', 'accuracy': 55, 'pp': 15, 'effect': 32},
'Supersonic': {'id': 48, 'power': 0, 'type': 'Normal', 'accuracy': 55, 'pp': 20, 'effect': 49},
'Sonicboom': {'id': 49, 'power': 1, 'type': 'Normal', 'accuracy': 90, 'pp': 20, 'effect': 41},
'Disable': {'id': 50, 'power': 0, 'type': 'Normal', 'accuracy': 55, 'pp': 20, 'effect': 86},
'Acid': {'id': 51, 'power': 40, 'type': 'Poison', 'accuracy': 100, 'pp': 30, 'effect': 69},
'Ember': {'id': 52, 'power': 40, 'type': 'Fire', 'accuracy': 100, 'pp': 25, 'effect': 4},
'Flamethrower': {'id': 53, 'power': 95, 'type': 'Fire', 'accuracy': 100, 'pp': 15, 'effect': 4},
'Mist': {'id': 54, 'power': 0, 'type': 'Ice', 'accuracy': 100, 'pp': 30, 'effect': 46},
'Water Gun': {'id': 55, 'power': 40, 'type': 'Water', 'accuracy': 100, 'pp': 25, 'effect': 0},
'Hydro Pump': {'id': 56, 'power': 120, 'type': 'Water', 'accuracy': 80, 'pp': 5, 'effect': 0},
'Surf': {'id': 57, 'power': 95, 'type': 'Water', 'accuracy': 100, 'pp': 15, 'effect': 0},
'Ice Beam': {'id': 58, 'power': 95, 'type': 'Ice', 'accuracy': 100, 'pp': 10, 'effect': 5},
'Blizzard': {'id': 59, 'power': 120, 'type': 'Ice', 'accuracy': 90, 'pp': 5, 'effect': 5},
'Psybeam': {'id': 60, 'power': 65, 'type': 'Psychic', 'accuracy': 100, 'pp': 20, 'effect': 76},
'Bubblebeam': {'id': 61, 'power': 65, 'type': 'Water', 'accuracy': 100, 'pp': 20, 'effect': 70},
'Aurora Beam': {'id': 62, 'power': 65, 'type': 'Ice', 'accuracy': 100, 'pp': 20, 'effect': 68},
'Hyper Beam': {'id': 63, 'power': 150, 'type': 'Normal', 'accuracy': 90, 'pp': 5, 'effect': 80},
'Peck': {'id': 64, 'power': 35, 'type': 'Flying', 'accuracy': 100, 'pp': 35, 'effect': 0},
'Drill Peck': {'id': 65, 'power': 80, 'type': 'Flying', 'accuracy': 100, 'pp': 20, 'effect': 0},
'Submission': {'id': 66, 'power': 80, 'type': 'Fighting', 'accuracy': 80, 'pp': 25, 'effect': 48},
'Low Kick': {'id': 67, 'power': 50, 'type': 'Fighting', 'accuracy': 90, 'pp': 20, 'effect': 37},
'Counter': {'id': 68, 'power': 1, 'type': 'Fighting', 'accuracy': 100, 'pp': 20, 'effect': 0},
'Seismic Toss': {'id': 69, 'power': 1, 'type': 'Fighting', 'accuracy': 100, 'pp': 20, 'effect': 41},
'Strength': {'id': 70, 'power': 80, 'type': 'Normal', 'accuracy': 100, 'pp': 15, 'effect': 0},
'Absorb': {'id': 71, 'power': 20, 'type': 'Grass', 'accuracy': 100, 'pp': 20, 'effect': 3},
'Mega Drain': {'id': 72, 'power': 40, 'type': 'Grass', 'accuracy': 100, 'pp': 10, 'effect': 3},
'Leech Seed': {'id': 73, 'power': 0, 'type': 'Grass', 'accuracy': 90, 'pp': 10, 'effect': 84},
'Growth': {'id': 74, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 40, 'effect': 13},
'Razor Leaf': {'id': 75, 'power': 55, 'type': 'Grass', 'accuracy': 95, 'pp': 25, 'effect': 0},
'Solarbeam': {'id': 76, 'power': 120, 'type': 'Grass', 'accuracy': 100, 'pp': 10, 'effect': 39},
'Poisonpowder': {'id': 77, 'power': 0, 'type': 'Poison', 'accuracy': 75, 'pp': 35, 'effect': 66},
'Stun Spore': {'id': 78, 'power': 0, 'type': 'Grass', 'accuracy': 75, 'pp': 30, 'effect': 67},
'Sleep Powder': {'id': 79, 'power': 0, 'type': 'Grass', 'accuracy': 75, 'pp': 15, 'effect': 32},
'Petal Dance': {'id': 80, 'power': 70, 'type': 'Grass', 'accuracy': 100, 'pp': 20, 'effect': 27},
'String Shot': {'id': 81, 'power': 0, 'type': 'Bug', 'accuracy': 95, 'pp': 40, 'effect': 20},
'Dragon Rage': {'id': 82, 'power': 1, 'type': 'Dragon', 'accuracy': 100, 'pp': 10, 'effect': 41},
'Fire Spin': {'id': 83, 'power': 15, 'type': 'Fire', 'accuracy': 70, 'pp': 15, 'effect': 42},
'Thundershock': {'id': 84, 'power': 40, 'type': 'Electric', 'accuracy': 100, 'pp': 30, 'effect': 6},
'Thunderbolt': {'id': 85, 'power': 95, 'type': 'Electric', 'accuracy': 100, 'pp': 15, 'effect': 6},
'Thunder Wave': {'id': 86, 'power': 0, 'type': 'Electric', 'accuracy': 100, 'pp': 20, 'effect': 67},
'Thunder': {'id': 87, 'power': 120, 'type': 'Electric', 'accuracy': 70, 'pp': 10, 'effect': 6},
'Rock Throw': {'id': 88, 'power': 50, 'type': 'Rock', 'accuracy': 65, 'pp': 15, 'effect': 0},
'Earthquake': {'id': 89, 'power': 100, 'type': 'Ground', 'accuracy': 100, 'pp': 10, 'effect': 0},
'Fissure': {'id': 90, 'power': 1, 'type': 'Ground', 'accuracy': 30, 'pp': 5, 'effect': 38},
'Dig': {'id': 91, 'power': 100, 'type': 'Ground', 'accuracy': 100, 'pp': 10, 'effect': 39},
'Toxic': {'id': 92, 'power': 0, 'type': 'Poison', 'accuracy': 85, 'pp': 10, 'effect': 66},
'Confusion': {'id': 93, 'power': 50, 'type': 'Psychic', 'accuracy': 100, 'pp': 25, 'effect': 76},
'Psychic': {'id': 94, 'power': 90, 'type': 'Psychic', 'accuracy': 100, 'pp': 10, 'effect': 71},
'Hypnosis': {'id': 95, 'power': 0, 'type': 'Psychic', 'accuracy': 60, 'pp': 20, 'effect': 32},
'Meditate': {'id': 96, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 40, 'effect': 10},
'Agility': {'id': 97, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 30, 'effect': 52},
'Quick Attack': {'id': 98, 'power': 40, 'type': 'Normal', 'accuracy': 100, 'pp': 30, 'effect': 0},
'Rage': {'id': 99, 'power': 20, 'type': 'Normal', 'accuracy': 100, 'pp': 20, 'effect': 81},
'Teleport': {'id': 100, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 20, 'effect': 28},
'Night Shade': {'id': 101, 'power': 0, 'type': 'Ghost', 'accuracy': 100, 'pp': 15, 'effect': 41},
'Mimic': {'id': 102, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 10, 'effect': 82},
'Screech': {'id': 103, 'power': 0, 'type': 'Normal', 'accuracy': 85, 'pp': 40, 'effect': 59},
'Double Team': {'id': 104, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 15, 'effect': 15},
'Recover': {'id': 105, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 20, 'effect': 56},
'Harden': {'id': 106, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30, 'effect': 11},
'Minimize': {'id': 107, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 20, 'effect': 15},
'Smokescreen': {'id': 108, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 20, 'effect': 22},
'Confuse Ray': {'id': 109, 'power': 0, 'type': 'Ghost', 'accuracy': 100, 'pp': 10, 'effect': 49},
'Withdraw': {'id': 110, 'power': 0, 'type': 'Water', 'accuracy': 100, 'pp': 40, 'effect': 11},
'Defense Curl': {'id': 111, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 40, 'effect': 11},
'Barrier': {'id': 112, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 30, 'effect': 51},
'Light Screen': {'id': 113, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 30, 'effect': 64},
'Haze': {'id': 114, 'power': 0, 'type': 'Ice', 'accuracy': 100, 'pp': 30, 'effect': 25},
'Reflect': {'id': 115, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 20, 'effect': 65},
'Focus Energy': {'id': 116, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30, 'effect': 47},
'Bide': {'id': 117, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 10, 'effect': 26},
'Metronome': {'id': 118, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 10, 'effect': 83},
'Mirror Move': {'id': 119, 'power': 0, 'type': 'Flying', 'accuracy': 100, 'pp': 20, 'effect': 9},
'Selfdestruct': {'id': 120, 'power': 130, 'type': 'Normal', 'accuracy': 100, 'pp': 5, 'effect': 7},
'Egg Bomb': {'id': 121, 'power': 100, 'type': 'Normal', 'accuracy': 75, 'pp': 10, 'effect': 0},
'Lick': {'id': 122, 'power': 20, 'type': 'Ghost', 'accuracy': 100, 'pp': 30, 'effect': 36},
'Smog': {'id': 123, 'power': 20, 'type': 'Poison', 'accuracy': 70, 'pp': 20, 'effect': 33},
'Sludge': {'id': 124, 'power': 65, 'type': 'Poison', 'accuracy': 100, 'pp': 20, 'effect': 33},
'Bone Club': {'id': 125, 'power': 65, 'type': 'Ground', 'accuracy': 85, 'pp': 20, 'effect': 31},
'Fire Blast': {'id': 126, 'power': 120, 'type': 'Fire', 'accuracy': 85, 'pp': 5, 'effect': 34},
'Waterfall': {'id': 127, 'power': 80, 'type': 'Water', 'accuracy': 100, 'pp': 15, 'effect': 0},
'Clamp': {'id': 128, 'power': 35, 'type': 'Water', 'accuracy': 75, 'pp': 10, 'effect': 42},
'Swift': {'id': 129, 'power': 60, 'type': 'Normal', 'accuracy': 100, 'pp': 20, 'effect': 17},
'Skull Bash': {'id': 130, 'power': 100, 'type': 'Normal', 'accuracy': 100, 'pp': 15, 'effect': 39},
'Spike Cannon': {'id': 131, 'power': 20, 'type': 'Normal', 'accuracy': 100, 'pp': 15, 'effect': 29},
'Constrict': {'id': 132, 'power': 10, 'type': 'Normal', 'accuracy': 100, 'pp': 35, 'effect': 70},
'Amnesia': {'id': 133, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 20, 'effect': 53},
'Kinesis': {'id': 134, 'power': 0, 'type': 'Psychic', 'accuracy': 80, 'pp': 15, 'effect': 22},
'Softboiled': {'id': 135, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 10, 'effect': 56},
'Hi Jump Kick': {'id': 136, 'power': 85, 'type': 'Fighting', 'accuracy': 90, 'pp': 20, 'effect': 45},
'Glare': {'id': 137, 'power': 0, 'type': 'Normal', 'accuracy': 75, 'pp': 30, 'effect': 67},
'Dream Eater': {'id': 138, 'power': 100, 'type': 'Psychic', 'accuracy': 100, 'pp': 15, 'effect': 8},
'Poison Gas': {'id': 139, 'power': 0, 'type': 'Poison', 'accuracy': 55, 'pp': 40, 'effect': 66},
'Barrage': {'id': 140, 'power': 15, 'type': 'Normal', 'accuracy': 85, 'pp': 20, 'effect': 29},
'Leech Life': {'id': 141, 'power': 20, 'type': 'Bug', 'accuracy': 100, 'pp': 15, 'effect': 3},
'Lovely Kiss': {'id': 142, 'power': 0, 'type': 'Normal', 'accuracy': 75, 'pp': 10, 'effect': 32},
'Sky Attack': {'id': 143, 'power': 140, 'type': 'Flying', 'accuracy': 90, 'pp': 5, 'effect': 39},
'Transform': {'id': 144, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 10, 'effect': 57},
'Bubble': {'id': 145, 'power': 20, 'type': 'Water', 'accuracy': 100, 'pp': 30, 'effect': 70},
'Dizzy Punch': {'id': 146, 'power': 70, 'type': 'Normal', 'accuracy': 100, 'pp': 10, 'effect': 0},
'Spore': {'id': 147, 'power': 0, 'type': 'Grass', 'accuracy': 100, 'pp': 15, 'effect': 32},
'Flash': {'id': 148, 'power': 0, 'type': 'Normal', 'accuracy': 70, 'pp': 20, 'effect': 22},
'Psywave': {'id': 149, 'power': 1, 'type': 'Psychic', 'accuracy': 80, 'pp': 15, 'effect': 41},
'Splash': {'id': 150, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 40, 'effect': 85},
'Acid Armor': {'id': 151, 'power': 0, 'type': 'Poison', 'accuracy': 100, 'pp': 40, 'effect': 51},
'Crabhammer': {'id': 152, 'power': 90, 'type': 'Water', 'accuracy': 85, 'pp': 10, 'effect': 0},
'Explosion': {'id': 153, 'power': 170, 'type': 'Normal', 'accuracy': 100, 'pp': 5, 'effect': 7},
'Fury Swipes': {'id': 154, 'power': 18, 'type': 'Normal', 'accuracy': 80, 'pp': 15, 'effect': 29},
'Bonemerang': {'id': 155, 'power': 50, 'type': 'Ground', 'accuracy': 90, 'pp': 10, 'effect': 44},
'Rest': {'id': 156, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 10, 'effect': 56},
'Rock Slide': {'id': 157, 'power': 75, 'type': 'Rock', 'accuracy': 90, 'pp': 10, 'effect': 0},
'Hyper Fang': {'id': 158, 'power': 80, 'type': 'Normal', 'accuracy': 90, 'pp': 15, 'effect': 31},
'Sharpen': {'id': 159, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30, 'effect': 10},
'Conversion': {'id': 160, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30, 'effect': 24},
'Tri Attack': {'id': 161, 'power': 80, 'type': 'Normal', 'accuracy': 100, 'pp': 10, 'effect': 0},
'Super Fang': {'id': 162, 'power': 1, 'type': 'Normal', 'accuracy': 90, 'pp': 10, 'effect': 40},
'Slash': {'id': 163, 'power': 70, 'type': 'Normal', 'accuracy': 100, 'pp': 20, 'effect': 0},
'Substitute': {'id': 164, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 10, 'effect': 79}
#'Struggle': {'id': 165, 'power': 50, 'type': 'Struggle_Type', 'accuracy': 100, 'pp': 10, 'effect': 48}
}
encounter_tables = {'Wild_Super_Rod_A': 2, 'Wild_Super_Rod_B': 2, 'Wild_Super_Rod_C': 3, 'Wild_Super_Rod_D': 2,
@ -1204,6 +1204,29 @@ 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"
#
# ]
first_stage_pokemon = [pokemon for pokemon in pokemon_data.keys() if pokemon not in evolves_from]
legendary_pokemon = ["Articuno", "Zapdos", "Moltres", "Mewtwo", "Mew"]

View File

@ -23,11 +23,12 @@ def create_regions(multiworld: MultiWorld, player: int):
locations_per_region.setdefault(location.region, [])
if location.inclusion(multiworld, player):
locations_per_region[location.region].append(PokemonRBLocation(player, location.name, location.address,
location.rom_address))
location.rom_address, location.type))
regions = [
create_region(multiworld, player, "Menu", locations_per_region),
create_region(multiworld, player, "Anywhere", locations_per_region),
create_region(multiworld, player, "Fossil", locations_per_region),
create_region(multiworld, player, "Pokedex", locations_per_region),
create_region(multiworld, player, "Pallet Town", locations_per_region),
create_region(multiworld, player, "Route 1", locations_per_region),
create_region(multiworld, player, "Viridian City", locations_per_region),
@ -88,6 +89,7 @@ def create_regions(multiworld: MultiWorld, player: int):
create_region(multiworld, player, "Route 8", locations_per_region),
create_region(multiworld, player, "Route 8 Grass", locations_per_region),
create_region(multiworld, player, "Celadon City", locations_per_region),
create_region(multiworld, player, "Celadon Game Corner", locations_per_region),
create_region(multiworld, player, "Celadon Prize Corner", locations_per_region),
create_region(multiworld, player, "Celadon Gym", locations_per_region),
create_region(multiworld, player, "Route 16", locations_per_region),
@ -148,6 +150,7 @@ def create_regions(multiworld: MultiWorld, player: int):
multiworld.regions += regions
connect(multiworld, player, "Menu", "Anywhere", one_way=True)
connect(multiworld, player, "Menu", "Pallet Town", one_way=True)
connect(multiworld, player, "Menu", "Pokedex", one_way=True)
connect(multiworld, player, "Menu", "Fossil", lambda state: state.pokemon_rb_fossil_checks(
state.multiworld.second_fossil_check_condition[player].value, player), one_way=True)
connect(multiworld, player, "Pallet Town", "Route 1")
@ -220,6 +223,7 @@ def create_regions(multiworld: MultiWorld, player: int):
connect(multiworld, player, "Route 8", "Route 8 Grass", lambda state: state.pokemon_rb_can_cut(player), one_way=True)
connect(multiworld, player, "Route 7", "Celadon City")
connect(multiworld, player, "Celadon City", "Celadon Gym", lambda state: state.pokemon_rb_can_cut(player), one_way=True)
connect(multiworld, player, "Celadon City", "Celadon Game Corner")
connect(multiworld, player, "Celadon City", "Celadon Prize Corner")
connect(multiworld, player, "Celadon City", "Route 16")
connect(multiworld, player, "Route 16", "Route 16 North", lambda state: state.pokemon_rb_can_cut(player), one_way=True)

View File

@ -0,0 +1,294 @@
from .rom_addresses import rom_addresses
disallowed1F = [[2, 2], [3, 2], [1, 8], [2, 8], [7, 7], [8, 7], [10, 4], [11, 4], [11, 12],
[11, 13], [16, 10], [17, 10], [18, 10], [16, 12], [17, 12], [18, 12]]
disallowed2F = [[16, 2], [17, 2], [18, 2], [15, 5], [15, 6], [10, 10], [11, 10], [12, 10], [7, 14], [8, 14], [1, 15],
[13, 15], [13, 16], [1, 12], [1, 10], [3, 5], [3, 6], [5, 6], [5, 7], [5, 8], [1, 2], [1, 3], [1, 4],
[11, 1]]
def randomize_rock_tunnel(data, random):
seed = random.randint(0, 999999999999999999)
random.seed(seed)
map1f = []
map2f = []
address = rom_addresses["Map_Rock_Tunnel1F"]
for y in range(0, 18):
row = []
for x in range(0, 20):
row.append(data[address])
address += 1
map1f.append(row)
address = rom_addresses["Map_Rock_TunnelB1F"]
for y in range(0, 18):
row = []
for x in range(0, 20):
row.append(data[address])
address += 1
map2f.append(row)
current_map = map1f
def floor(x, y):
current_map[y][x] = 1
def wide(x, y):
current_map[y][x] = 32
current_map[y][x + 1] = 34
def tall(x, y):
current_map[y][x] = 23
current_map[y + 1][x] = 31
def single(x, y):
current_map[y][x] = 2
# 0 = top left, 1 = middle, 2 = top right, 3 = bottom right
entrance_c = random.choice([0, 1, 2])
exit_c = [0, 1, 3]
if entrance_c == 2:
exit_c.remove(1)
else:
exit_c.remove(entrance_c)
exit_c = random.choice(exit_c)
remaining = [i for i in [0, 1, 2, 3] if i not in [entrance_c, exit_c]]
if entrance_c == 0:
floor(6, 3)
floor(6, 4)
tall(random.randint(8, 10), 2)
wide(4, random.randint(5, 7))
wide(1, random.choice([5, 6, 7, 9]))
elif entrance_c == 1:
if remaining == [0, 2] or random.randint(0, 1):
tall(random.randint(8, 10), 2)
floor(7, 4)
floor(8, 4)
else:
tall(random.randint(11, 12), 5)
floor(9, 5)
floor(9, 6)
elif entrance_c == 2:
floor(16, 2)
floor(16, 3)
if remaining == [1, 3]:
wide(17, 4)
else:
tall(random.randint(11, 17), random.choice([2, 5]))
if exit_c == 0:
r = random.sample([0, 1, 2], 2)
if 0 in r:
floor(1, 11)
floor(2, 11)
if 1 in r:
floor(3, 11)
floor(4, 11)
if 2 in r:
floor(5, 11)
floor(6, 11)
elif exit_c == 1 or (exit_c == 3 and entrance_c == 0):
r = random.sample([1, 3, 5, 7], random.randint(1, 2))
for i in r:
floor(i, 11)
floor(i + 1, 11)
if exit_c != 3:
tall(random.choice([9, 10, 12]), 12)
# 0 = top left, 1 = middle, 2 = top right, 3 = bottom right
# [0, 1] [0, 2] [1, 2] [1, 3], [2, 3]
if remaining[0] == 1:
floor(9, 5)
floor(9, 6)
if remaining == [0, 2]:
if random.randint(0, 1):
tall(9, 4)
floor(9, 6)
floor(9, 7)
else:
floor(10, 7)
floor(11, 7)
if remaining == [1, 2]:
floor(16, 2)
floor(16, 3)
tall(random.randint(11, 17), random.choice([2, 5]))
if remaining in [[1, 3], [2, 3]]:
r = round(random.triangular(0, 3, 0))
floor(12 + (r * 2), 7)
if r < 3:
floor(13 + (r * 2), 7)
if remaining == [1, 3]:
wide(10, random.choice([3, 5]))
if remaining != [0, 1] and exit_c != 1:
wide(7, 6)
if entrance_c != 0:
if random.randint(0, 1):
wide(4, random.randint(4, 7))
else:
wide(1, random.choice([5, 6, 7, 9]))
current_map = map2f
if 3 in remaining:
c = random.choice([entrance_c, exit_c])
else:
c = random.choice(remaining)
# 0 = top right, 1 = middle, 2 = bottom right, 3 = top left
if c in [0, 1]:
if random.randint(0, 2):
tall(random.choice([2, 4]), 5)
r = random.choice([1, 3, 7, 9, 11])
floor(3 if r < 11 else random.randint(1, 2), r)
floor(3 if r < 11 else random.randint(1, 2), r + 1)
if random.randint(0, 2):
tall(random.randint(6, 7), 7)
r = random.choice([1, 3, 5, 9])
floor(6, r)
floor(6, r + 1)
if random.randint(0, 2):
wide(7, 15)
r = random.randint(0, 4)
if r == 0:
floor(9, 14)
floor(10, 14)
elif r == 1:
floor(11, 14)
floor(12, 14)
elif r == 2:
floor(13, 13)
floor(13, 14)
elif r == 3:
floor(13, 11)
floor(13, 12)
elif r == 4:
floor(13, 10)
floor(14, 10)
if c == 0:
tall(random.randint(9, 10), 5)
if random.randint(0, 1):
floor(10, 7)
floor(11, 7)
tall(random.randint(12, 17), 8)
else:
floor(12, 5)
floor(12, 6)
wide(13, random.randint(4, 5))
wide(17, random.randint(3, 5))
r = random.choice([1, 3])
floor(12, r)
floor(12, + 1)
elif c == 2:
r = random.randint(0, 6)
if r == 0:
floor(12, 1)
floor(12, 2)
elif r == 1:
floor(12, 3)
floor(12, 4)
elif r == 2:
floor(12, 5)
floor(12, 6)
elif r == 3:
floor(10, 7)
floor(11, 7)
elif r == 4:
floor(9, 7)
floor(9, 8)
elif r == 5:
floor(9, 9)
floor(9, 10)
elif r == 6:
floor(8, 11)
floor(9, 11)
if r < 2 or (r in [2, 3] and random.randint(0, 1)):
wide(7, random.randint(6, 7))
elif r in [2, 3]:
tall(random.randint(9, 10), 5)
else:
tall(random.randint(6, 7), 7)
r = random.randint(r, 6)
if r == 0:
#early block
wide(13, random.randint(2, 5))
tall(random.randint(14, 15), 1)
elif r == 1:
if random.randint(0, 1):
tall(16, 5)
tall(random.choice([14, 15, 17]), 1)
else:
wide(16, random.randint(6,8))
single(18, 7)
elif r == 2:
tall(random.randint(12, 16), 8)
elif r == 3:
wide(10, 9)
single(12, 9)
elif r == 4:
wide(10, random.randint(11, 12))
single(12, random.randint(11, 12))
elif r == 5:
tall(random.randint(8, 10), 12)
elif r == 6:
wide(7, 15)
r = random.randint(r, 6)
if r == 6:
#late open
r2 = random.randint(0, 2)
floor(1 + (r2 * 2), 14)
floor(2 + (r2 * 2), 14)
elif r == 5:
floor(6, 12)
floor(6, 13)
elif r == 4:
if random.randint(0, 1):
floor(6, 11)
floor(7, 11)
else:
floor(8, 11)
floor(9, 11)
elif r == 3:
floor(9, 9)
floor(9, 10)
elif r < 3:
single(9, 7)
floor(9, 8)
def check_addable_block(check_map, disallowed):
if check_map[y][x] == 1 and [x, y] not in disallowed:
i = 0
for xx in range(x-1, x+2):
for yy in range(y-1, y+2):
if check_map[yy][xx] == 1:
i += 1
if i >= 8:
single(x, y)
for _ in range(100):
y = random.randint(1, 16)
x = random.randint(1, 18)
current_map = map1f
check_addable_block(map1f, disallowed1F)
current_map = map2f
check_addable_block(map2f, disallowed2F)
address = rom_addresses["Map_Rock_Tunnel1F"]
for y in map1f:
for x in y:
data[address] = x
address += 1
address = rom_addresses["Map_Rock_TunnelB1F"]
for y in map2f:
for x in y:
data[address] = x
address += 1
return seed

View File

@ -8,6 +8,7 @@ from .text import encode_text
from .rom_addresses import rom_addresses
from .locations import location_data
from .items import item_table
from .rock_tunnel import randomize_rock_tunnel
import worlds.pokemon_rb.poke_data as poke_data
@ -28,15 +29,15 @@ def filter_moves(moves, type, random):
return ret
def get_move(moves, chances, random, starting_move=False):
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 poke_data.moves[move]["accuracy"] > 80 and poke_data.moves[move]["power"] > 0 or not starting_move:
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(moves, [], random, starting_move)
return get_move(local_move_data, moves, [], random, starting_move)
def get_encounter_slots(self):
@ -75,6 +76,42 @@ def randomize_pokemon(self, mon, mons_list, randomize_type, random):
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]
@ -163,6 +200,7 @@ def process_static_pokemon(self):
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
@ -170,9 +208,13 @@ def process_static_pokemon(self):
if not randomize_type:
location.place_locked_item(self.create_item(slot_type + " " + slot.original_item))
else:
location.place_locked_item(self.create_item(slot_type + " " +
randomize_pokemon(self, slot.original_item, mons_list, randomize_type,
self.multiworld.random)))
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):
@ -180,27 +222,36 @@ 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:
mon = randomize_pokemon(self, slot.original_item, mons_list,
self.multiworld.randomize_wild_pokemon[self.player].value, self.multiworld.random)
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, slot.original_item, mons_list, 2, self.multiworld.random)
mon = randomize_pokemon(self, original_mon, mons_list, 2, self.multiworld.random)
placed_mons[mon] += 1
location = self.multiworld.get_location(slot.name, self.player)
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
@ -223,22 +274,46 @@ def process_wild_pokemon(self):
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].value in [1, 3]:
candidate_locations = [slot for slot in candidate_locations if 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"]]])]
if not candidate_locations:
candidate_locations = get_encounter_slots(self)
candidate_locations = [self.multiworld.get_location(location.name, self.player) for location in candidate_locations]
candidate_locations.sort(key=lambda slot: abs(get_base_stat_total(slot.item.name) - stat_base))
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.item.name) - 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"]]]))
for location in candidate_locations:
if placed_mons[location.item.name] > 1 or location.item.name not in poke_data.first_stage_pokemon:
placed_mons[location.item.name] -= 1
location.item = self.create_item(mon)
location.item.location = location
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
break
else:
raise Exception
else:
for slot in encounter_slots:
@ -250,10 +325,41 @@ def process_wild_pokemon(self):
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:
@ -265,18 +371,21 @@ def process_pokemon_data(self):
mon_data["spd"] = stats[3]
mon_data["spc"] = stats[4]
elif self.multiworld.randomize_pokemon_stats[self.player].value == 2:
old_stats = mon_data["hp"] + mon_data["atk"] + mon_data["def"] + mon_data["spd"] + mon_data["spc"] - 5
stats = [1, 1, 1, 1, 1]
while old_stats > 0:
stat = self.multiworld.random.randint(0, 4)
if stats[stat] < 255:
old_stats -= 1
stats[stat] += 1
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]
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"]
@ -318,46 +427,237 @@ def process_pokemon_data(self):
moves = list(poke_data.moves.keys())
for move in ["No Move"] + poke_data.hm_moves:
moves.remove(move)
mon_data["start move 1"] = get_move(moves, chances, self.multiworld.random, True)
for i in range(2, 5):
if mon_data[f"start move {i}"] != "No Move" or self.multiworld.start_with_four_moves[
self.player].value == 1:
mon_data[f"start move {i}"] = get_move(moves, chances, self.multiworld.random)
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:
for move_num in range(0, len(learnsets[mon])):
learnsets[mon][move_num] = get_move(moves, chances, self.multiworld.random)
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"])
if mon != "Mew":
tms_hms = poke_data.tm_moves + poke_data.hm_moves
for flag, tm_move in enumerate(tms_hms):
if ((mon in poke_data.evolves_from.keys() and mon_data["type1"] ==
local_poke_data[poke_data.evolves_from[mon]]["type1"] and mon_data["type2"] ==
local_poke_data[poke_data.evolves_from[mon]]["type2"]) and (
(flag < 50 and self.multiworld.tm_compatibility[self.player].value in [1, 2]) or (
flag >= 51 and self.multiworld.hm_compatibility[self.player].value in [1, 2]))):
bit = 1 if local_poke_data[poke_data.evolves_from[mon]]["tms"][int(flag / 8)] & 1 << (flag % 8) else 0
elif (flag < 50 and self.multiworld.tm_compatibility[self.player].value == 1) or (flag >= 50 and self.multiworld.hm_compatibility[self.player].value == 1):
type_match = poke_data.moves[tm_move]["type"] in [mon_data["type1"], mon_data["type2"]]
bit = int(self.multiworld.random.randint(1, 100) < [[90, 50, 25], [100, 75, 25]][flag >= 50][0 if type_match else 1 if poke_data.moves[tm_move]["type"] == "Normal" else 2])
elif (flag < 50 and self.multiworld.tm_compatibility[self.player].value == 2) or (flag >= 50 and self.multiworld.hm_compatibility[self.player].value == 2):
bit = self.multiworld.random.randint(0, 1)
elif (flag < 50 and self.multiworld.tm_compatibility[self.player].value == 3) or (flag >= 50 and self.multiworld.hm_compatibility[self.player].value == 3):
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:
continue
if bit:
mon_data["tms"][int(flag / 8)] |= 1 << (flag % 8)
else:
mon_data["tms"][int(flag / 8)] &= ~(1 << (flag % 8))
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
def write_quizzes(self, data, random):
def get_quiz(q, a):
if q == 0:
r = random.randint(0, 3)
if r == 0:
mon = self.trade_mons["Trade_Dux"]
text = "A woman in<LINE>Vermilion City<CONT>"
elif r == 1:
mon = self.trade_mons["Trade_Lola"]
text = "A man in<LINE>Cerulean City<CONT>"
elif r == 2:
mon = self.trade_mons["Trade_Marcel"]
text = "Someone on Route 2<LINE>"
elif r == 3:
mon = self.trade_mons["Trade_Spot"]
text = "Someone on Route 5<LINE>"
if not a:
answers.append(0)
old_mon = mon
while old_mon == mon:
mon = random.choice(list(poke_data.pokemon_data.keys()))
return encode_text(f"{text}was looking for<CONT>{mon}?<DONE>")
elif q == 1:
for location in self.multiworld.get_filled_locations():
if location.item.name == "Secret Key" and location.item.player == self.player:
break
if location.player == self.player:
player_name = "yourself"
else:
player_name = self.multiworld.player_names[location.player]
if not a:
if len(self.multiworld.player_name) > 1:
old_name = player_name
while old_name == player_name:
player_name = random.choice(list(self.multiworld.player_name.values()))
else:
return encode_text("You're playing<LINE>in a multiworld<CONT>with other<CONT>players?<DONE>")
return encode_text(f"The Secret Key was<LINE>found by<CONT>{player_name[:17]}?<DONE>")
elif q == 2:
if a:
return encode_text(f"#mon is<LINE>pronounced<CONT>Po-kay-mon?<DONE>")
else:
if random.randint(0, 1):
return encode_text(f"#mon is<LINE>pronounced<CONT>Po-key-mon?<DONE>")
else:
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)]
mon = random.choice(starters)
nots = random.choice(range(8, 16, 2))
if random.randint(0, 1):
while mon in starters:
mon = random.choice(list(poke_data.pokemon_data.keys()))
if a:
nots += 1
elif not a:
nots += 1
text = f"{mon} was<LINE>"
while nots > 0:
i = random.randint(1, min(4, nots))
text += ("not " * i) + "<CONT>"
nots -= i
text += "a starter choice?<DONE>"
return encode_text(text)
elif q == 4:
if a:
tm_text = self.local_tms[27]
else:
if self.multiworld.randomize_tm_moves[self.player]:
wrong_tms = self.local_tms.copy()
wrong_tms.pop(27)
tm_text = random.choice(wrong_tms)
else:
tm_text = "TOMBSTONER"
return encode_text(f"TM28 contains<LINE>{tm_text.upper()}?<DONE>")
elif q == 5:
i = 8
while not a and i in [1, 8]:
i = random.randint(0, 99999999)
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)
return encode_text(f"POLIWAG evolves {i}<LINE>times?<DONE>")
elif q == 7:
entity = "Motor Carrier"
if not a:
entity = random.choice(["Driver", "Shipper"])
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>")
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)]
questions = random.sample((range(0, 8)), 6)
question_texts = []
for i, question in enumerate(questions):
question_texts.append(get_quiz(question, answers[i]))
for i, quiz in enumerate(["A", "B", "C", "D", "E", "F"]):
data[rom_addresses[f"Quiz_Answer_{quiz}"]] = int(not answers[i]) << 4 | (i + 1)
write_bytes(data, question_texts[i], rom_addresses[f"Text_Quiz_{quiz}"])
def generate_output(self, output_directory: str):
random = self.multiworld.per_slot_randoms[self.player]
game_version = self.multiworld.game_version[self.player].current_key
@ -384,10 +684,33 @@ def generate_output(self, output_directory: str):
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:
data[address] = self.item_name_to_id[location.item.name] - 172000000
item_id = self.item_name_to_id[location.item.name] - 172000000
if item_id > 255:
item_id -= 256
data[address] = item_id
else:
data[location.rom_address] = 0x2C # AP Item
def set_trade_mon(address, loc):
mon = self.multiworld.get_location(loc, self.player).item.name
data[rom_addresses[address]] = poke_data.pokemon_data[mon]["id"]
self.trade_mons[address] = mon
if game_version == "red":
set_trade_mon("Trade_Terry", "Safari Zone Center - Wild Pokemon - 5")
set_trade_mon("Trade_Spot", "Safari Zone East - Wild Pokemon - 1")
else:
set_trade_mon("Trade_Terry", "Safari Zone Center - Wild Pokemon - 7")
set_trade_mon("Trade_Spot", "Safari Zone East - Wild Pokemon - 7")
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_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
if self.multiworld.tea[self.player].value:
@ -421,6 +744,14 @@ def generate_output(self, output_directory: str):
if self.multiworld.old_man[self.player].value == 2:
data[rom_addresses['Option_Old_Man']] = 0x11
data[rom_addresses['Option_Old_Man_Lying']] = 0x15
if self.multiworld.require_pokedex[self.player]:
data[rom_addresses["Require_Pokedex_A"]] = 1
data[rom_addresses["Require_Pokedex_B"]] = 1
if self.multiworld.dexsanity[self.player]:
data[rom_addresses["Option_Dexsanity_A"]] = 1
data[rom_addresses["Option_Dexsanity_B"]] = 1
if self.multiworld.all_pokemon_seen[self.player]:
data[rom_addresses["Option_Pokedex_Seen"]] = 1
money = str(self.multiworld.starting_money[self.player].value).zfill(6)
data[rom_addresses["Starting_Money_High"]] = int(money[:2], 16)
data[rom_addresses["Starting_Money_Middle"]] = int(money[2:4], 16)
@ -433,6 +764,7 @@ def generate_output(self, output_directory: str):
write_bytes(data, encode_text(
" ".join(self.multiworld.get_location("Route 3 - 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:
@ -492,10 +824,10 @@ def generate_output(self, output_directory: str):
data[address + 17] = poke_data.moves[self.local_poke_data[mon]["start move 3"]]["id"]
data[address + 18] = poke_data.moves[self.local_poke_data[mon]["start move 4"]]["id"]
write_bytes(data, self.local_poke_data[mon]["tms"], address + 20)
if mon in self.learnsets:
address = rom_addresses["Learnset_" + mon.replace(" ", "")]
for i, move in enumerate(self.learnsets[mon]):
data[(address + 1) + i * 2] = poke_data.moves[move]["id"]
if mon in self.learnsets and self.learnsets[mon]:
address = rom_addresses["Learnset_" + mon.replace(" ", "")]
for i, move in enumerate(self.learnsets[mon]):
data[(address + 1) + i * 2] = poke_data.moves[move]["id"]
data[rom_addresses["Option_Aide_Rt2"]] = self.multiworld.oaks_aide_rt_2[self.player].value
data[rom_addresses["Option_Aide_Rt11"]] = self.multiworld.oaks_aide_rt_11[self.player].value
@ -507,8 +839,8 @@ def generate_output(self, output_directory: str):
if self.multiworld.reusable_tms[self.player].value:
data[rom_addresses["Option_Reusable_TMs"]] = 0xC9
data[rom_addresses["Option_Trainersanity"]] = self.multiworld.trainersanity[self.player].value
data[rom_addresses["Option_Trainersanity2"]] = self.multiworld.trainersanity[self.player].value
for i in range(1, 10):
data[rom_addresses[f"Option_Trainersanity{i}"]] = self.multiworld.trainersanity[self.player].value
data[rom_addresses["Option_Always_Half_STAB"]] = int(not self.multiworld.same_type_attack_bonus[self.player].value)
@ -532,8 +864,23 @@ def generate_output(self, output_directory: str):
if data[rom_addresses["Start_Inventory"] + item.code - 172000000] < 255:
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:
continue
address = rom_addresses["Move_Data"] + ((move_data["id"] - 1) * 6)
write_bytes(data, bytearray([move_data["id"], move_data["effect"], move_data["power"],
poke_data.type_ids[move_data["type"]], round(move_data["accuracy"] * 2.55), move_data["pp"]]), address)
TM_IDs = bytearray([poke_data.moves[move]["id"] for move in self.local_tms])
write_bytes(data, TM_IDs, rom_addresses["TM_Moves"])
if self.multiworld.randomize_rock_tunnel[self.player]:
seed = randomize_rock_tunnel(data, random)
write_bytes(data, encode_text(f"SEED: <LINE>{seed}"), rom_addresses["Text_Rock_Tunnel_Sign"])
mons = [mon["id"] for mon in poke_data.pokemon_data.values()]
random.shuffle(mons)
data[rom_addresses['Title_Mon_First']] = mons.pop()
@ -564,7 +911,7 @@ def generate_output(self, output_directory: str):
else:
write_bytes(data, self.rival_name, rom_addresses['Rival_Name'])
data[0xFF00] = 1 # 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)

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +1,45 @@
from ..generic.Rules import add_item_rule, add_rule
from ..generic.Rules import add_item_rule, add_rule, item_name
from .items import item_groups
def set_rules(world, player):
add_item_rule(world.get_location("Pallet Town - Player's PC", player),
lambda i: i.player == player and "Badge" not in i.name and "Trap" not in i.name and
i.name != "Pokedex")
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)
}
if world.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:
item.name == "Bike Voucher"
and item.player == player)
world.get_location("Fuchsia City - 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),
"Cerulean City - Bicycle Shop": lambda state: state.has("Bike Voucher", player)
or 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": lambda state: state.has("Card Key", player),
"Fuchsia City - Safari Zone Warden": lambda state: state.has("Gold Teeth", 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 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),
@ -38,6 +57,23 @@ def set_rules(world, 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),
"Celadon Game Corner - Hidden Item Southwest Corner (Coin Case)": lambda state: state.has("Coin Case", player),
"Celadon Game Corner - Hidden Item Near Rumor Man (Coin Case)": lambda state: state.has("Coin Case", player),
"Celadon Game Corner - Hidden Item Near Speculating Woman (Coin Case)": lambda state: state.has("Coin Case", player),
"Celadon Game Corner - Hidden Item Near West Gifting Gambler (Coin Case)": lambda state: state.has("Coin Case", player),
"Celadon Game Corner - Hidden Item Near Wonderful Time Woman (Coin Case)": lambda state: state.has("Coin Case", player),
"Celadon Game Corner - Hidden Item Near Failing Gym Information Guy (Coin Case)": lambda state: state.has( "Coin Case", player),
"Celadon Game Corner - Hidden Item Near East Gifting Gambler (Coin Case)": lambda state: state.has("Coin Case", player),
"Celadon Game Corner - Hidden Item Near Hooked Guy (Coin Case)": lambda state: state.has("Coin Case", player),
"Celadon Game Corner - Hidden Item at End of Horizontal Machine Row (Coin Case)": lambda state: state.has("Coin Case", player),
"Celadon Game Corner - Hidden Item in Front of Horizontal Machine Row (Coin Case)": lambda state: state.has("Coin Case", player),
"Silph Co 11F - Silph Co Liberated": lambda state: state.has("Card Key", player),
@ -89,6 +125,16 @@ def set_rules(world, 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),
"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),
# Pokédex check
"Pallet Town - Oak's Parcel Reward": lambda state: state.has("Oak's Parcel", player),
@ -142,7 +188,7 @@ def set_rules(world, 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 Final Guard": lambda state: state.pokemon_rb_can_get_hidden_items(
"Route 23 - Hidden Item Rocks Before Victory Road": lambda state: state.pokemon_rb_can_get_hidden_items(
player),
"Route 23 - Hidden Item East Bush After Water": lambda state: state.pokemon_rb_can_get_hidden_items(
player),
@ -178,3 +224,11 @@ def set_rules(world, player):
for loc in world.get_locations(player):
if loc.name in access_rules:
add_rule(loc, access_rules[loc.name])
if loc.name in item_rules:
add_item_rule(loc, item_rules[loc.name])
if loc.name.startswith("Pokedex"):
mon = loc.name.split(" - ")[1]
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,5 +1,9 @@
special_chars = {
"PKMN": 0x4A,
"LINE": 0x4F,
"CONT": 0x55,
"DONE": 0x57,
"PROMPT": 0x58,
"'d": 0xBB,
"'l": 0xBC,
"'t": 0xBE,
@ -105,7 +109,7 @@ char_map = {
"9": 0xFF,
}
unsafe_chars = ["@", "#", "PKMN"]
unsafe_chars = ["@", "#", "PKMN", "LINE", "DONE", "CONT", "PROMPT"]
def encode_text(text: str, length: int=0, whitespace=False, force=False, safety=False):

View File

@ -509,7 +509,7 @@ class SMZ3World(World):
return self.smz3DungeonItems
else:
return []
def post_fill(self):
# some small or big keys (those always_allow) can be unreachable in-game
# while logic still collects some of them (probably to simulate the player collecting pot keys in the logic), some others don't
@ -524,7 +524,7 @@ class SMZ3World(World):
loc.item.classification = ItemClassification.filler
loc.item.item.Progression = False
loc.item.location.event = False
self.unreachable.append(loc)
self.unreachable.append(loc)
def get_filler_item_name(self) -> str:
return self.multiworld.random.choice(self.junkItemsNames)