[Pokemon Red and Blue] Initial implementation (#1016)
This commit is contained in:
parent
0afb7096de
commit
30a4bcbbbe
|
@ -13,6 +13,9 @@
|
|||
*.z64
|
||||
*.n64
|
||||
*.nes
|
||||
*.gb
|
||||
*.gbc
|
||||
*.gba
|
||||
*.wixobj
|
||||
*.lck
|
||||
*.db3
|
||||
|
|
|
@ -145,6 +145,8 @@ components: Iterable[Component] = (
|
|||
Component('OoT Adjuster', 'OoTAdjuster'),
|
||||
# FF1
|
||||
Component('FF1 Client', 'FF1Client'),
|
||||
# Pokémon
|
||||
Component('Pokemon Client', 'PokemonClient', file_identifier=SuffixIdentifier('.apred', '.apblue')),
|
||||
# ChecksFinder
|
||||
Component('ChecksFinder Client', 'ChecksFinderClient'),
|
||||
# Starcraft 2
|
||||
|
|
|
@ -0,0 +1,319 @@
|
|||
import asyncio
|
||||
import json
|
||||
import time
|
||||
import os
|
||||
import bsdiff4
|
||||
import subprocess
|
||||
import zipfile
|
||||
import hashlib
|
||||
from asyncio import StreamReader, StreamWriter
|
||||
from typing import List
|
||||
|
||||
|
||||
import Utils
|
||||
from CommonClient import CommonContext, server_loop, gui_enabled, ClientCommandProcessor, logger, \
|
||||
get_base_parser
|
||||
|
||||
from worlds.pokemon_rb.locations import location_data
|
||||
|
||||
location_map = {"Rod": {}, "EventFlag": {}, "Missable": {}, "Hidden": {}, "list": {}}
|
||||
location_bytes_bits = {}
|
||||
for location in location_data:
|
||||
if location.ram_address is not None:
|
||||
if type(location.ram_address) == list:
|
||||
location_map[type(location.ram_address).__name__][(location.ram_address[0].flag, location.ram_address[1].flag)] = location.address
|
||||
location_bytes_bits[location.address] = [{'byte': location.ram_address[0].byte, 'bit': location.ram_address[0].bit},
|
||||
{'byte': location.ram_address[1].byte, 'bit': location.ram_address[1].bit}]
|
||||
else:
|
||||
location_map[type(location.ram_address).__name__][location.ram_address.flag] = location.address
|
||||
location_bytes_bits[location.address] = {'byte': location.ram_address.byte, 'bit': location.ram_address.bit}
|
||||
|
||||
SYSTEM_MESSAGE_ID = 0
|
||||
|
||||
CONNECTION_TIMING_OUT_STATUS = "Connection timing out. Please restart your emulator, then restart pkmn_rb.lua"
|
||||
CONNECTION_REFUSED_STATUS = "Connection Refused. Please start your emulator and make sure pkmn_rb.lua is running"
|
||||
CONNECTION_RESET_STATUS = "Connection was reset. Please restart your emulator, then restart pkmn_rb.lua"
|
||||
CONNECTION_TENTATIVE_STATUS = "Initial Connection Made"
|
||||
CONNECTION_CONNECTED_STATUS = "Connected"
|
||||
CONNECTION_INITIAL_STATUS = "Connection has not been initiated"
|
||||
|
||||
DISPLAY_MSGS = True
|
||||
|
||||
|
||||
class GBCommandProcessor(ClientCommandProcessor):
|
||||
def __init__(self, ctx: CommonContext):
|
||||
super().__init__(ctx)
|
||||
|
||||
def _cmd_gb(self):
|
||||
"""Check Gameboy Connection State"""
|
||||
if isinstance(self.ctx, GBContext):
|
||||
logger.info(f"Gameboy Status: {self.ctx.gb_status}")
|
||||
|
||||
|
||||
class GBContext(CommonContext):
|
||||
command_processor = GBCommandProcessor
|
||||
game = 'Pokemon Red and Blue'
|
||||
items_handling = 0b101
|
||||
|
||||
def __init__(self, server_address, password):
|
||||
super().__init__(server_address, password)
|
||||
self.gb_streams: (StreamReader, StreamWriter) = None
|
||||
self.gb_sync_task = None
|
||||
self.messages = {}
|
||||
self.locations_array = None
|
||||
self.gb_status = CONNECTION_INITIAL_STATUS
|
||||
self.awaiting_rom = False
|
||||
self.display_msgs = True
|
||||
|
||||
async def server_auth(self, password_requested: bool = False):
|
||||
if password_requested and not self.password:
|
||||
await super(GBContext, self).server_auth(password_requested)
|
||||
if not self.auth:
|
||||
self.awaiting_rom = True
|
||||
logger.info('Awaiting connection to Bizhawk to get Player information')
|
||||
return
|
||||
|
||||
await self.send_connect()
|
||||
|
||||
def _set_message(self, msg: str, msg_id: int):
|
||||
if DISPLAY_MSGS:
|
||||
self.messages[(time.time(), msg_id)] = msg
|
||||
|
||||
def on_package(self, cmd: str, args: dict):
|
||||
if cmd == 'Connected':
|
||||
self.locations_array = None
|
||||
elif cmd == "RoomInfo":
|
||||
self.seed_name = args['seed_name']
|
||||
elif cmd == 'Print':
|
||||
msg = args['text']
|
||||
if ': !' not in msg:
|
||||
self._set_message(msg, SYSTEM_MESSAGE_ID)
|
||||
elif cmd == "ReceivedItems":
|
||||
msg = f"Received {', '.join([self.item_names[item.item] for item in args['items']])}"
|
||||
self._set_message(msg, SYSTEM_MESSAGE_ID)
|
||||
|
||||
def run_gui(self):
|
||||
from kvui import GameManager
|
||||
|
||||
class GBManager(GameManager):
|
||||
logging_pairs = [
|
||||
("Client", "Archipelago")
|
||||
]
|
||||
base_title = "Archipelago Pokémon Client"
|
||||
|
||||
self.ui = GBManager(self)
|
||||
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
|
||||
|
||||
|
||||
def get_payload(ctx: GBContext):
|
||||
current_time = time.time()
|
||||
return json.dumps(
|
||||
{
|
||||
"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}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
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:]}
|
||||
|
||||
# Check for clear problems
|
||||
if len(flags['Rod']) > 1:
|
||||
return
|
||||
if flags["EventFlag"][1] + flags["EventFlag"][8] + flags["EventFlag"][9] + flags["EventFlag"][12] \
|
||||
+ flags["EventFlag"][61] + flags["EventFlag"][62] + flags["EventFlag"][63] + flags["EventFlag"][64] \
|
||||
+ flags["EventFlag"][65] + flags["EventFlag"][66] + flags["EventFlag"][67] + flags["EventFlag"][68] \
|
||||
+ flags["EventFlag"][69] + flags["EventFlag"][70] != 0:
|
||||
return
|
||||
|
||||
for flag_type, loc_map in location_map.items():
|
||||
for flag, loc_id in loc_map.items():
|
||||
if flag_type == "list":
|
||||
if (flags["EventFlag"][location_bytes_bits[loc_id][0]['byte']] & 1 << location_bytes_bits[loc_id][0]['bit']
|
||||
and flags["Missable"][location_bytes_bits[loc_id][1]['byte']] & 1 << location_bytes_bits[loc_id][1]['bit']):
|
||||
locations.append(loc_id)
|
||||
elif flags[flag_type][location_bytes_bits[loc_id]['byte']] & 1 << location_bytes_bits[loc_id]['bit']:
|
||||
locations.append(loc_id)
|
||||
if flags["EventFlag"][280] & 1 and not ctx.finished_game:
|
||||
await ctx.send_msgs([
|
||||
{"cmd": "StatusUpdate",
|
||||
"status": 30}
|
||||
])
|
||||
ctx.finished_game = True
|
||||
if locations == ctx.locations_array:
|
||||
return
|
||||
ctx.locations_array = locations
|
||||
if locations is not None:
|
||||
await ctx.send_msgs([{"cmd": "LocationChecks", "locations": locations}])
|
||||
|
||||
|
||||
async def gb_sync_task(ctx: GBContext):
|
||||
logger.info("Starting GB connector. Use /gb for status information")
|
||||
while not ctx.exit_event.is_set():
|
||||
error_status = None
|
||||
if ctx.gb_streams:
|
||||
(reader, writer) = ctx.gb_streams
|
||||
msg = get_payload(ctx).encode()
|
||||
writer.write(msg)
|
||||
writer.write(b'\n')
|
||||
try:
|
||||
await asyncio.wait_for(writer.drain(), timeout=1.5)
|
||||
try:
|
||||
# Data will return a dict with up to two fields:
|
||||
# 1. A keepalive response of the Players Name (always)
|
||||
# 2. An array representing the memory values of the locations area (if in game)
|
||||
data = await asyncio.wait_for(reader.readline(), timeout=5)
|
||||
data_decoded = json.loads(data.decode())
|
||||
#print(data_decoded)
|
||||
|
||||
if ctx.seed_name and ctx.seed_name != bytes(data_decoded['seedName']).decode():
|
||||
msg = "The server is running a different multiworld than your client is. (invalid seed_name)"
|
||||
logger.info(msg, extra={'compact_gui': True})
|
||||
ctx.gui_error('Error', msg)
|
||||
error_status = CONNECTION_RESET_STATUS
|
||||
ctx.seed_name = bytes(data_decoded['seedName']).decode()
|
||||
if not ctx.auth:
|
||||
ctx.auth = ''.join([chr(i) for i in data_decoded['playerName'] if i != 0])
|
||||
if ctx.auth == '':
|
||||
logger.info("Invalid ROM detected. No player name built into the ROM.")
|
||||
if ctx.awaiting_rom:
|
||||
await ctx.server_auth(False)
|
||||
if 'locations' in data_decoded and ctx.game and ctx.gb_status == CONNECTION_CONNECTED_STATUS \
|
||||
and not error_status and ctx.auth:
|
||||
# Not just a keep alive ping, parse
|
||||
asyncio.create_task(parse_locations(data_decoded['locations'], ctx))
|
||||
except asyncio.TimeoutError:
|
||||
logger.debug("Read Timed Out, Reconnecting")
|
||||
error_status = CONNECTION_TIMING_OUT_STATUS
|
||||
writer.close()
|
||||
ctx.gb_streams = None
|
||||
except ConnectionResetError as e:
|
||||
logger.debug("Read failed due to Connection Lost, Reconnecting")
|
||||
error_status = CONNECTION_RESET_STATUS
|
||||
writer.close()
|
||||
ctx.gb_streams = None
|
||||
except TimeoutError:
|
||||
logger.debug("Connection Timed Out, Reconnecting")
|
||||
error_status = CONNECTION_TIMING_OUT_STATUS
|
||||
writer.close()
|
||||
ctx.gb_streams = None
|
||||
except ConnectionResetError:
|
||||
logger.debug("Connection Lost, Reconnecting")
|
||||
error_status = CONNECTION_RESET_STATUS
|
||||
writer.close()
|
||||
ctx.gb_streams = None
|
||||
if ctx.gb_status == CONNECTION_TENTATIVE_STATUS:
|
||||
if not error_status:
|
||||
logger.info("Successfully Connected to Gameboy")
|
||||
ctx.gb_status = CONNECTION_CONNECTED_STATUS
|
||||
else:
|
||||
ctx.gb_status = f"Was tentatively connected but error occured: {error_status}"
|
||||
elif error_status:
|
||||
ctx.gb_status = error_status
|
||||
logger.info("Lost connection to Gameboy and attempting to reconnect. Use /gb for status updates")
|
||||
else:
|
||||
try:
|
||||
logger.debug("Attempting to connect to Gameboy")
|
||||
ctx.gb_streams = await asyncio.wait_for(asyncio.open_connection("localhost", 17242), timeout=10)
|
||||
ctx.gb_status = CONNECTION_TENTATIVE_STATUS
|
||||
except TimeoutError:
|
||||
logger.debug("Connection Timed Out, Trying Again")
|
||||
ctx.gb_status = CONNECTION_TIMING_OUT_STATUS
|
||||
continue
|
||||
except ConnectionRefusedError:
|
||||
logger.debug("Connection Refused, Trying Again")
|
||||
ctx.gb_status = CONNECTION_REFUSED_STATUS
|
||||
continue
|
||||
|
||||
|
||||
async def run_game(romfile):
|
||||
auto_start = Utils.get_options()["pokemon_rb_options"].get("rom_start", True)
|
||||
if auto_start is True:
|
||||
import webbrowser
|
||||
webbrowser.open(romfile)
|
||||
elif os.path.isfile(auto_start):
|
||||
subprocess.Popen([auto_start, romfile],
|
||||
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
|
||||
async def patch_and_run_game(game_version, patch_file, ctx):
|
||||
base_name = os.path.splitext(patch_file)[0]
|
||||
comp_path = base_name + '.gb'
|
||||
with open(Utils.local_path(Utils.get_options()["pokemon_rb_options"][f"{game_version}_rom_file"]), "rb") as stream:
|
||||
base_rom = bytes(stream.read())
|
||||
try:
|
||||
with open(Utils.local_path('lib', 'worlds', 'pokemon_rb', f'basepatch_{game_version}.bsdiff4'), 'rb') as stream:
|
||||
base_patch = bytes(stream.read())
|
||||
except FileNotFoundError:
|
||||
with open(Utils.local_path('worlds', 'pokemon_rb', f'basepatch_{game_version}.bsdiff4'), 'rb') as stream:
|
||||
base_patch = bytes(stream.read())
|
||||
base_patched_rom_data = bsdiff4.patch(base_rom, base_patch)
|
||||
basemd5 = hashlib.md5()
|
||||
basemd5.update(base_patched_rom_data)
|
||||
|
||||
with zipfile.ZipFile(patch_file, 'r') as patch_archive:
|
||||
with patch_archive.open('delta.bsdiff4', 'r') as stream:
|
||||
patch = stream.read()
|
||||
patched_rom_data = bsdiff4.patch(base_patched_rom_data, patch)
|
||||
|
||||
written_hash = patched_rom_data[0xFFCC:0xFFDC]
|
||||
if written_hash == basemd5.digest():
|
||||
with open(comp_path, "wb") as patched_rom_file:
|
||||
patched_rom_file.write(patched_rom_data)
|
||||
|
||||
asyncio.create_task(run_game(comp_path))
|
||||
else:
|
||||
msg = "Patch supplied was not generated with the same base patch version as this client. Patching failed."
|
||||
logger.warning(msg)
|
||||
ctx.gui_error('Error', msg)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Utils.init_logging("PokemonClient")
|
||||
|
||||
options = Utils.get_options()
|
||||
|
||||
async def main():
|
||||
parser = get_base_parser()
|
||||
parser.add_argument('patch_file', default="", type=str, nargs="?",
|
||||
help='Path to an APRED or APBLUE patch file')
|
||||
args = parser.parse_args()
|
||||
|
||||
ctx = GBContext(args.connect, args.password)
|
||||
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
|
||||
if gui_enabled:
|
||||
ctx.run_gui()
|
||||
ctx.run_cli()
|
||||
ctx.gb_sync_task = asyncio.create_task(gb_sync_task(ctx), name="GB Sync")
|
||||
|
||||
if args.patch_file:
|
||||
ext = args.patch_file.split(".")[len(args.patch_file.split(".")) - 1].lower()
|
||||
if ext == "apred":
|
||||
logger.info("APRED file supplied, beginning patching process...")
|
||||
asyncio.create_task(patch_and_run_game("red", args.patch_file, ctx))
|
||||
elif ext == "apblue":
|
||||
logger.info("APBLUE file supplied, beginning patching process...")
|
||||
asyncio.create_task(patch_and_run_game("blue", args.patch_file, ctx))
|
||||
else:
|
||||
logger.warning(f"Unknown patch file extension {ext}")
|
||||
|
||||
await ctx.exit_event.wait()
|
||||
ctx.server_address = None
|
||||
|
||||
await ctx.shutdown()
|
||||
|
||||
if ctx.gb_sync_task:
|
||||
await ctx.gb_sync_task
|
||||
|
||||
|
||||
import colorama
|
||||
|
||||
colorama.init()
|
||||
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
|
@ -29,6 +29,7 @@ Currently, the following games are supported:
|
|||
* Donkey Kong Country 3
|
||||
* Dark Souls 3
|
||||
* Super Mario World
|
||||
* Pokémon Red and Blue
|
||||
|
||||
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
|
||||
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled
|
||||
|
|
5
Utils.py
5
Utils.py
|
@ -295,6 +295,11 @@ def get_default_options() -> OptionsType:
|
|||
"sni": "SNI",
|
||||
"rom_start": True,
|
||||
},
|
||||
"pokemon_rb_options": {
|
||||
"red_rom_file": "Pokemon Red (UE) [S][!].gb",
|
||||
"blue_rom_file": "Pokemon Blue (UE) [S][!].gb",
|
||||
"rom_start": True
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,389 @@
|
|||
--
|
||||
-- json.lua
|
||||
--
|
||||
-- Copyright (c) 2015 rxi
|
||||
--
|
||||
-- This library is free software; you can redistribute it and/or modify it
|
||||
-- under the terms of the MIT license. See LICENSE for details.
|
||||
--
|
||||
|
||||
local json = { _version = "0.1.0" }
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Encode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local encode
|
||||
|
||||
function error(err)
|
||||
print(err)
|
||||
end
|
||||
|
||||
local escape_char_map = {
|
||||
[ "\\" ] = "\\\\",
|
||||
[ "\"" ] = "\\\"",
|
||||
[ "\b" ] = "\\b",
|
||||
[ "\f" ] = "\\f",
|
||||
[ "\n" ] = "\\n",
|
||||
[ "\r" ] = "\\r",
|
||||
[ "\t" ] = "\\t",
|
||||
}
|
||||
|
||||
local escape_char_map_inv = { [ "\\/" ] = "/" }
|
||||
for k, v in pairs(escape_char_map) do
|
||||
escape_char_map_inv[v] = k
|
||||
end
|
||||
|
||||
|
||||
local function escape_char(c)
|
||||
return escape_char_map[c] or string.format("\\u%04x", c:byte())
|
||||
end
|
||||
|
||||
|
||||
local function encode_nil(val)
|
||||
return "null"
|
||||
end
|
||||
|
||||
|
||||
local function encode_table(val, stack)
|
||||
local res = {}
|
||||
stack = stack or {}
|
||||
|
||||
-- Circular reference?
|
||||
if stack[val] then error("circular reference") end
|
||||
|
||||
stack[val] = true
|
||||
|
||||
if val[1] ~= nil or next(val) == nil then
|
||||
-- Treat as array -- check keys are valid and it is not sparse
|
||||
local n = 0
|
||||
for k in pairs(val) do
|
||||
if type(k) ~= "number" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
if n ~= #val then
|
||||
print("invalid table: sparse array")
|
||||
print(n)
|
||||
print("VAL:")
|
||||
print(val)
|
||||
print("STACK:")
|
||||
print(stack)
|
||||
end
|
||||
-- Encode
|
||||
for i, v in ipairs(val) do
|
||||
table.insert(res, encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "[" .. table.concat(res, ",") .. "]"
|
||||
|
||||
else
|
||||
-- Treat as an object
|
||||
for k, v in pairs(val) do
|
||||
if type(k) ~= "string" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "{" .. table.concat(res, ",") .. "}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function encode_string(val)
|
||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
||||
end
|
||||
|
||||
|
||||
local function encode_number(val)
|
||||
-- Check for NaN, -inf and inf
|
||||
if val ~= val or val <= -math.huge or val >= math.huge then
|
||||
error("unexpected number value '" .. tostring(val) .. "'")
|
||||
end
|
||||
return string.format("%.14g", val)
|
||||
end
|
||||
|
||||
|
||||
local type_func_map = {
|
||||
[ "nil" ] = encode_nil,
|
||||
[ "table" ] = encode_table,
|
||||
[ "string" ] = encode_string,
|
||||
[ "number" ] = encode_number,
|
||||
[ "boolean" ] = tostring,
|
||||
}
|
||||
|
||||
|
||||
encode = function(val, stack)
|
||||
local t = type(val)
|
||||
local f = type_func_map[t]
|
||||
if f then
|
||||
return f(val, stack)
|
||||
end
|
||||
error("unexpected type '" .. t .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.encode(val)
|
||||
return ( encode(val) )
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Decode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local parse
|
||||
|
||||
local function create_set(...)
|
||||
local res = {}
|
||||
for i = 1, select("#", ...) do
|
||||
res[ select(i, ...) ] = true
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local space_chars = create_set(" ", "\t", "\r", "\n")
|
||||
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
||||
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
||||
local literals = create_set("true", "false", "null")
|
||||
|
||||
local literal_map = {
|
||||
[ "true" ] = true,
|
||||
[ "false" ] = false,
|
||||
[ "null" ] = nil,
|
||||
}
|
||||
|
||||
|
||||
local function next_char(str, idx, set, negate)
|
||||
for i = idx, #str do
|
||||
if set[str:sub(i, i)] ~= negate then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return #str + 1
|
||||
end
|
||||
|
||||
|
||||
local function decode_error(str, idx, msg)
|
||||
--local line_count = 1
|
||||
--local col_count = 1
|
||||
--for i = 1, idx - 1 do
|
||||
-- col_count = col_count + 1
|
||||
-- if str:sub(i, i) == "\n" then
|
||||
-- line_count = line_count + 1
|
||||
-- col_count = 1
|
||||
-- end
|
||||
-- end
|
||||
-- emu.message( string.format("%s at line %d col %d", msg, line_count, col_count) )
|
||||
end
|
||||
|
||||
|
||||
local function codepoint_to_utf8(n)
|
||||
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
||||
local f = math.floor
|
||||
if n <= 0x7f then
|
||||
return string.char(n)
|
||||
elseif n <= 0x7ff then
|
||||
return string.char(f(n / 64) + 192, n % 64 + 128)
|
||||
elseif n <= 0xffff then
|
||||
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
elseif n <= 0x10ffff then
|
||||
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
||||
f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
end
|
||||
error( string.format("invalid unicode codepoint '%x'", n) )
|
||||
end
|
||||
|
||||
|
||||
local function parse_unicode_escape(s)
|
||||
local n1 = tonumber( s:sub(3, 6), 16 )
|
||||
local n2 = tonumber( s:sub(9, 12), 16 )
|
||||
-- Surrogate pair?
|
||||
if n2 then
|
||||
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
||||
else
|
||||
return codepoint_to_utf8(n1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function parse_string(str, i)
|
||||
local has_unicode_escape = false
|
||||
local has_surrogate_escape = false
|
||||
local has_escape = false
|
||||
local last
|
||||
for j = i + 1, #str do
|
||||
local x = str:byte(j)
|
||||
|
||||
if x < 32 then
|
||||
decode_error(str, j, "control character in string")
|
||||
end
|
||||
|
||||
if last == 92 then -- "\\" (escape char)
|
||||
if x == 117 then -- "u" (unicode escape sequence)
|
||||
local hex = str:sub(j + 1, j + 5)
|
||||
if not hex:find("%x%x%x%x") then
|
||||
decode_error(str, j, "invalid unicode escape in string")
|
||||
end
|
||||
if hex:find("^[dD][89aAbB]") then
|
||||
has_surrogate_escape = true
|
||||
else
|
||||
has_unicode_escape = true
|
||||
end
|
||||
else
|
||||
local c = string.char(x)
|
||||
if not escape_chars[c] then
|
||||
decode_error(str, j, "invalid escape char '" .. c .. "' in string")
|
||||
end
|
||||
has_escape = true
|
||||
end
|
||||
last = nil
|
||||
|
||||
elseif x == 34 then -- '"' (end of string)
|
||||
local s = str:sub(i + 1, j - 1)
|
||||
if has_surrogate_escape then
|
||||
s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
|
||||
end
|
||||
if has_unicode_escape then
|
||||
s = s:gsub("\\u....", parse_unicode_escape)
|
||||
end
|
||||
if has_escape then
|
||||
s = s:gsub("\\.", escape_char_map_inv)
|
||||
end
|
||||
return s, j + 1
|
||||
|
||||
else
|
||||
last = x
|
||||
end
|
||||
end
|
||||
decode_error(str, i, "expected closing quote for string")
|
||||
end
|
||||
|
||||
|
||||
local function parse_number(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local s = str:sub(i, x - 1)
|
||||
local n = tonumber(s)
|
||||
if not n then
|
||||
decode_error(str, i, "invalid number '" .. s .. "'")
|
||||
end
|
||||
return n, x
|
||||
end
|
||||
|
||||
|
||||
local function parse_literal(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local word = str:sub(i, x - 1)
|
||||
if not literals[word] then
|
||||
decode_error(str, i, "invalid literal '" .. word .. "'")
|
||||
end
|
||||
return literal_map[word], x
|
||||
end
|
||||
|
||||
|
||||
local function parse_array(str, i)
|
||||
local res = {}
|
||||
local n = 1
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local x
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of array?
|
||||
if str:sub(i, i) == "]" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read token
|
||||
x, i = parse(str, i)
|
||||
res[n] = x
|
||||
n = n + 1
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "]" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local function parse_object(str, i)
|
||||
local res = {}
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local key, val
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of object?
|
||||
if str:sub(i, i) == "}" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read key
|
||||
if str:sub(i, i) ~= '"' then
|
||||
decode_error(str, i, "expected string for key")
|
||||
end
|
||||
key, i = parse(str, i)
|
||||
-- Read ':' delimiter
|
||||
i = next_char(str, i, space_chars, true)
|
||||
if str:sub(i, i) ~= ":" then
|
||||
decode_error(str, i, "expected ':' after key")
|
||||
end
|
||||
i = next_char(str, i + 1, space_chars, true)
|
||||
-- Read value
|
||||
val, i = parse(str, i)
|
||||
-- Set
|
||||
res[key] = val
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "}" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local char_func_map = {
|
||||
[ '"' ] = parse_string,
|
||||
[ "0" ] = parse_number,
|
||||
[ "1" ] = parse_number,
|
||||
[ "2" ] = parse_number,
|
||||
[ "3" ] = parse_number,
|
||||
[ "4" ] = parse_number,
|
||||
[ "5" ] = parse_number,
|
||||
[ "6" ] = parse_number,
|
||||
[ "7" ] = parse_number,
|
||||
[ "8" ] = parse_number,
|
||||
[ "9" ] = parse_number,
|
||||
[ "-" ] = parse_number,
|
||||
[ "t" ] = parse_literal,
|
||||
[ "f" ] = parse_literal,
|
||||
[ "n" ] = parse_literal,
|
||||
[ "[" ] = parse_array,
|
||||
[ "{" ] = parse_object,
|
||||
}
|
||||
|
||||
|
||||
parse = function(str, idx)
|
||||
local chr = str:sub(idx, idx)
|
||||
local f = char_func_map[chr]
|
||||
if f then
|
||||
return f(str, idx)
|
||||
end
|
||||
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.decode(str)
|
||||
if type(str) ~= "string" then
|
||||
error("expected argument of type string, got " .. type(str))
|
||||
end
|
||||
return ( parse(str, next_char(str, 1, space_chars, true)) )
|
||||
end
|
||||
|
||||
|
||||
return json
|
|
@ -0,0 +1,238 @@
|
|||
local socket = require("socket")
|
||||
local json = require('json')
|
||||
local math = require('math')
|
||||
|
||||
local STATE_OK = "Ok"
|
||||
local STATE_TENTATIVELY_CONNECTED = "Tentatively Connected"
|
||||
local STATE_INITIAL_CONNECTION_MADE = "Initial Connection Made"
|
||||
local STATE_UNINITIALIZED = "Uninitialized"
|
||||
|
||||
local APIndex = 0x1A6E
|
||||
local APItemAddress = 0x00FF
|
||||
local EventFlagAddress = 0x1735
|
||||
local MissableAddress = 0x161A
|
||||
local HiddenItemsAddress = 0x16DE
|
||||
local RodAddress = 0x1716
|
||||
local InGame = 0x1A71
|
||||
|
||||
local ItemsReceived = nil
|
||||
local playerName = nil
|
||||
local seedName = nil
|
||||
|
||||
local prevstate = ""
|
||||
local curstate = STATE_UNINITIALIZED
|
||||
local gbSocket = nil
|
||||
local frame = 0
|
||||
|
||||
local u8 = nil
|
||||
local wU8 = nil
|
||||
local u16
|
||||
|
||||
--Sets correct memory access functions based on whether NesHawk or QuickNES is loaded
|
||||
local function defineMemoryFunctions()
|
||||
local memDomain = {}
|
||||
local domains = memory.getmemorydomainlist()
|
||||
--if domains[1] == "System Bus" then
|
||||
-- --NesHawk
|
||||
-- isNesHawk = true
|
||||
-- memDomain["systembus"] = function() memory.usememorydomain("System Bus") end
|
||||
-- memDomain["saveram"] = function() memory.usememorydomain("Battery RAM") end
|
||||
-- memDomain["rom"] = function() memory.usememorydomain("PRG ROM") end
|
||||
--elseif domains[1] == "WRAM" then
|
||||
-- --QuickNES
|
||||
-- memDomain["systembus"] = function() memory.usememorydomain("System Bus") end
|
||||
-- memDomain["saveram"] = function() memory.usememorydomain("WRAM") end
|
||||
-- memDomain["rom"] = function() memory.usememorydomain("PRG ROM") end
|
||||
--end
|
||||
memDomain["rom"] = function() memory.usememorydomain("ROM") end
|
||||
memDomain["wram"] = function() memory.usememorydomain("WRAM") end
|
||||
return memDomain
|
||||
end
|
||||
|
||||
local memDomain = defineMemoryFunctions()
|
||||
u8 = memory.read_u8
|
||||
wU8 = memory.write_u8
|
||||
u16 = memory.read_u16_le
|
||||
function uRange(address, bytes)
|
||||
data = memory.readbyterange(address - 1, bytes + 1)
|
||||
data[0] = nil
|
||||
return data
|
||||
end
|
||||
|
||||
|
||||
function table.empty (self)
|
||||
for _, _ in pairs(self) do
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function slice (tbl, s, e)
|
||||
local pos, new = 1, {}
|
||||
for i = s + 1, e do
|
||||
new[pos] = tbl[i]
|
||||
pos = pos + 1
|
||||
end
|
||||
return new
|
||||
end
|
||||
|
||||
function processBlock(block)
|
||||
if block == nil then
|
||||
return
|
||||
end
|
||||
local itemsBlock = block["items"]
|
||||
memDomain.wram()
|
||||
if itemsBlock ~= nil then-- and u8(0x116B) ~= 0x00 then
|
||||
-- print(itemsBlock)
|
||||
ItemsReceived = itemsBlock
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
function difference(a, b)
|
||||
local aa = {}
|
||||
for k,v in pairs(a) do aa[v]=true end
|
||||
for k,v in pairs(b) do aa[v]=nil end
|
||||
local ret = {}
|
||||
local n = 0
|
||||
for k,v in pairs(a) do
|
||||
if aa[v] then n=n+1 ret[n]=v end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function generateLocationsChecked()
|
||||
memDomain.wram()
|
||||
events = uRange(EventFlagAddress, 0x140)
|
||||
missables = uRange(MissableAddress, 0x20)
|
||||
hiddenitems = uRange(HiddenItemsAddress, 0x0E)
|
||||
rod = u8(RodAddress)
|
||||
|
||||
data = {}
|
||||
|
||||
table.foreach(events, function(k, v) table.insert(data, v) end)
|
||||
table.foreach(missables, function(k, v) table.insert(data, v) end)
|
||||
table.foreach(hiddenitems, function(k, v) table.insert(data, v) end)
|
||||
table.insert(data, rod)
|
||||
|
||||
return data
|
||||
end
|
||||
function generateSerialData()
|
||||
memDomain.wram()
|
||||
status = u8(0x1A73)
|
||||
if status == 0 then
|
||||
return nil
|
||||
end
|
||||
return uRange(0x1A76, u8(0x1A74))
|
||||
end
|
||||
local function arrayEqual(a1, a2)
|
||||
if #a1 ~= #a2 then
|
||||
return false
|
||||
end
|
||||
|
||||
for i, v in ipairs(a1) do
|
||||
if v ~= a2[i] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function receive()
|
||||
l, e = gbSocket:receive()
|
||||
if e == 'closed' then
|
||||
if curstate == STATE_OK then
|
||||
print("Connection closed")
|
||||
end
|
||||
curstate = STATE_UNINITIALIZED
|
||||
return
|
||||
elseif e == 'timeout' then
|
||||
--print("timeout") -- this keeps happening for some reason? just hide it
|
||||
return
|
||||
elseif e ~= nil then
|
||||
print(e)
|
||||
curstate = STATE_UNINITIALIZED
|
||||
return
|
||||
end
|
||||
if l ~= nil then
|
||||
processBlock(json.decode(l))
|
||||
end
|
||||
-- Determine Message to send back
|
||||
memDomain.rom()
|
||||
newPlayerName = uRange(0xFFF0, 0x10)
|
||||
newSeedName = uRange(0xFFDC, 20)
|
||||
if (playerName ~= nil and not arrayEqual(playerName, newPlayerName)) or (seedName ~= nil and not arrayEqual(seedName, newSeedName)) then
|
||||
print("ROM changed, quitting")
|
||||
curstate = STATE_UNINITIALIZED
|
||||
return
|
||||
end
|
||||
playerName = newPlayerName
|
||||
seedName = newSeedName
|
||||
local retTable = {}
|
||||
retTable["playerName"] = playerName
|
||||
retTable["seedName"] = seedName
|
||||
memDomain.wram()
|
||||
if u8(InGame) == 0xAC then
|
||||
retTable["locations"] = generateLocationsChecked()
|
||||
serialData = generateSerialData()
|
||||
if serialData ~= nil then
|
||||
retTable["serial"] = serialData
|
||||
end
|
||||
end
|
||||
msg = json.encode(retTable).."\n"
|
||||
local ret, error = gbSocket:send(msg)
|
||||
if ret == nil then
|
||||
print(error)
|
||||
elseif curstate == STATE_INITIAL_CONNECTION_MADE then
|
||||
curstate = STATE_TENTATIVELY_CONNECTED
|
||||
elseif curstate == STATE_TENTATIVELY_CONNECTED then
|
||||
print("Connected!")
|
||||
curstate = STATE_OK
|
||||
end
|
||||
end
|
||||
|
||||
function main()
|
||||
if (is23Or24Or25 or is26To28) == false then
|
||||
print("Must use a version of bizhawk 2.3.1 or higher")
|
||||
return
|
||||
end
|
||||
server, error = socket.bind('localhost', 17242)
|
||||
|
||||
while true do
|
||||
if not (curstate == prevstate) then
|
||||
print("Current state: "..curstate)
|
||||
prevstate = curstate
|
||||
end
|
||||
if (curstate == STATE_OK) or (curstate == STATE_INITIAL_CONNECTION_MADE) or (curstate == STATE_TENTATIVELY_CONNECTED) then
|
||||
if (frame % 60 == 0) then
|
||||
receive()
|
||||
if u8(InGame) == 0xAC then
|
||||
ItemIndex = u16(APIndex)
|
||||
if ItemsReceived[ItemIndex + 1] ~= nil then
|
||||
wU8(APItemAddress, ItemsReceived[ItemIndex + 1] - 172000000)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif (curstate == STATE_UNINITIALIZED) then
|
||||
if (frame % 60 == 0) then
|
||||
|
||||
print("Waiting for client.")
|
||||
|
||||
emu.frameadvance()
|
||||
server:settimeout(2)
|
||||
print("Attempting to connect")
|
||||
local client, timeout = server:accept()
|
||||
if timeout == nil then
|
||||
-- print('Initial Connection Made')
|
||||
curstate = STATE_INITIAL_CONNECTION_MADE
|
||||
gbSocket = client
|
||||
gbSocket:settimeout(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
emu.frameadvance()
|
||||
end
|
||||
end
|
||||
|
||||
main()
|
|
@ -0,0 +1,132 @@
|
|||
-----------------------------------------------------------------------------
|
||||
-- LuaSocket helper module
|
||||
-- Author: Diego Nehab
|
||||
-- RCS ID: $Id: socket.lua,v 1.22 2005/11/22 08:33:29 diego Exp $
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Declare module and import dependencies
|
||||
-----------------------------------------------------------------------------
|
||||
local base = _G
|
||||
local string = require("string")
|
||||
local math = require("math")
|
||||
local socket = require("socket.core")
|
||||
module("socket")
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Exported auxiliar functions
|
||||
-----------------------------------------------------------------------------
|
||||
function connect(address, port, laddress, lport)
|
||||
local sock, err = socket.tcp()
|
||||
if not sock then return nil, err end
|
||||
if laddress then
|
||||
local res, err = sock:bind(laddress, lport, -1)
|
||||
if not res then return nil, err end
|
||||
end
|
||||
local res, err = sock:connect(address, port)
|
||||
if not res then return nil, err end
|
||||
return sock
|
||||
end
|
||||
|
||||
function bind(host, port, backlog)
|
||||
local sock, err = socket.tcp()
|
||||
if not sock then return nil, err end
|
||||
sock:setoption("reuseaddr", true)
|
||||
local res, err = sock:bind(host, port)
|
||||
if not res then return nil, err end
|
||||
res, err = sock:listen(backlog)
|
||||
if not res then return nil, err end
|
||||
return sock
|
||||
end
|
||||
|
||||
try = newtry()
|
||||
|
||||
function choose(table)
|
||||
return function(name, opt1, opt2)
|
||||
if base.type(name) ~= "string" then
|
||||
name, opt1, opt2 = "default", name, opt1
|
||||
end
|
||||
local f = table[name or "nil"]
|
||||
if not f then base.error("unknown key (".. base.tostring(name) ..")", 3)
|
||||
else return f(opt1, opt2) end
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Socket sources and sinks, conforming to LTN12
|
||||
-----------------------------------------------------------------------------
|
||||
-- create namespaces inside LuaSocket namespace
|
||||
sourcet = {}
|
||||
sinkt = {}
|
||||
|
||||
BLOCKSIZE = 2048
|
||||
|
||||
sinkt["close-when-done"] = function(sock)
|
||||
return base.setmetatable({
|
||||
getfd = function() return sock:getfd() end,
|
||||
dirty = function() return sock:dirty() end
|
||||
}, {
|
||||
__call = function(self, chunk, err)
|
||||
if not chunk then
|
||||
sock:close()
|
||||
return 1
|
||||
else return sock:send(chunk) end
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
sinkt["keep-open"] = function(sock)
|
||||
return base.setmetatable({
|
||||
getfd = function() return sock:getfd() end,
|
||||
dirty = function() return sock:dirty() end
|
||||
}, {
|
||||
__call = function(self, chunk, err)
|
||||
if chunk then return sock:send(chunk)
|
||||
else return 1 end
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
sinkt["default"] = sinkt["keep-open"]
|
||||
|
||||
sink = choose(sinkt)
|
||||
|
||||
sourcet["by-length"] = function(sock, length)
|
||||
return base.setmetatable({
|
||||
getfd = function() return sock:getfd() end,
|
||||
dirty = function() return sock:dirty() end
|
||||
}, {
|
||||
__call = function()
|
||||
if length <= 0 then return nil end
|
||||
local size = math.min(socket.BLOCKSIZE, length)
|
||||
local chunk, err = sock:receive(size)
|
||||
if err then return nil, err end
|
||||
length = length - string.len(chunk)
|
||||
return chunk
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
sourcet["until-closed"] = function(sock)
|
||||
local done
|
||||
return base.setmetatable({
|
||||
getfd = function() return sock:getfd() end,
|
||||
dirty = function() return sock:dirty() end
|
||||
}, {
|
||||
__call = function()
|
||||
if done then return nil end
|
||||
local chunk, err, partial = sock:receive(socket.BLOCKSIZE)
|
||||
if not err then return chunk
|
||||
elseif err == "closed" then
|
||||
sock:close()
|
||||
done = 1
|
||||
return partial
|
||||
else return nil, err end
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
sourcet["default"] = sourcet["until-closed"]
|
||||
|
||||
source = choose(sourcet)
|
|
@ -138,6 +138,14 @@ dkc3_options:
|
|||
# True for operating system default program
|
||||
# Alternatively, a path to a program to open the .sfc file with
|
||||
rom_start: true
|
||||
pokemon_rb_options:
|
||||
# File names of the Pokemon Red and Blue roms
|
||||
red_rom_file: "Pokemon Red (UE) [S][!].gb"
|
||||
blue_rom_file: "Pokemon Blue (UE) [S][!].gb"
|
||||
# Set this to false to never autostart a rom (such as after patching)
|
||||
# True for operating system default program
|
||||
# Alternatively, a path to a program to open the .gb file with
|
||||
rom_start: true
|
||||
smw_options:
|
||||
# File name of the SMW US rom
|
||||
rom_file: "Super Mario World (USA).sfc"
|
||||
|
|
|
@ -59,6 +59,8 @@ Name: "generator/smw"; Description: "Super Mario World ROM Setup"; Types: ful
|
|||
Name: "generator/soe"; Description: "Secret of Evermore ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728; Flags: disablenouninstallwarning
|
||||
Name: "generator/lttp"; Description: "A Link to the Past ROM Setup and Enemizer"; Types: full hosting; ExtraDiskSpaceRequired: 5191680
|
||||
Name: "generator/oot"; Description: "Ocarina of Time ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 100663296; Flags: disablenouninstallwarning
|
||||
Name: "generator/pkmn_r"; Description: "Pokemon Red ROM Setup"; Types: full hosting
|
||||
Name: "generator/pkmn_b"; Description: "Pokemon Blue ROM Setup"; Types: full hosting
|
||||
Name: "server"; Description: "Server"; Types: full hosting
|
||||
Name: "client"; Description: "Clients"; Types: full playing
|
||||
Name: "client/sni"; Description: "SNI Client"; Types: full playing
|
||||
|
@ -70,6 +72,9 @@ Name: "client/factorio"; Description: "Factorio"; Types: full playing
|
|||
Name: "client/minecraft"; Description: "Minecraft"; Types: full playing; ExtraDiskSpaceRequired: 226894278
|
||||
Name: "client/oot"; Description: "Ocarina of Time"; Types: full playing
|
||||
Name: "client/ff1"; Description: "Final Fantasy 1"; Types: full playing
|
||||
Name: "client/pkmn"; Description: "Pokemon Client"
|
||||
Name: "client/pkmn/red"; Description: "Pokemon Client - Pokemon Red Setup"; Types: full playing; ExtraDiskSpaceRequired: 1048576
|
||||
Name: "client/pkmn/blue"; Description: "Pokemon Client - Pokemon Blue Setup"; Types: full playing; ExtraDiskSpaceRequired: 1048576
|
||||
Name: "client/cf"; Description: "ChecksFinder"; Types: full playing
|
||||
Name: "client/sc2"; Description: "Starcraft 2"; Types: full playing
|
||||
Name: "client/text"; Description: "Text, to !command and chat"; Types: full playing
|
||||
|
@ -84,6 +89,8 @@ Source: "{code:GetDKC3ROMPath}"; DestDir: "{app}"; DestName: "Donkey Kong Countr
|
|||
Source: "{code:GetSMWROMPath}"; DestDir: "{app}"; DestName: "Super Mario World (USA).sfc"; Flags: external; Components: client/sni/smw or generator/smw
|
||||
Source: "{code:GetSoEROMPath}"; DestDir: "{app}"; DestName: "Secret of Evermore (USA).sfc"; Flags: external; Components: generator/soe
|
||||
Source: "{code:GetOoTROMPath}"; DestDir: "{app}"; DestName: "The Legend of Zelda - Ocarina of Time.z64"; Flags: external; Components: client/oot or generator/oot
|
||||
Source: "{code:GetRedROMPath}"; DestDir: "{app}"; DestName: "Pokemon Red (UE) [S][!].gb"; Flags: external; Components: client/pkmn/red or generator/pkmn_r
|
||||
Source: "{code:GetBlueROMPath}"; DestDir: "{app}"; DestName: "Pokemon Blue (UE) [S][!].gb"; Flags: external; Components: client/pkmn/blue or generator/pkmn_b
|
||||
Source: "{#source_path}\*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI, EnemizerCLI, Archipelago*.exe"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "{#source_path}\SNI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\SNI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: client/sni
|
||||
Source: "{#source_path}\EnemizerCLI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\EnemizerCLI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: generator/lttp
|
||||
|
@ -98,6 +105,7 @@ Source: "{#source_path}\ArchipelagoMinecraftClient.exe"; DestDir: "{app}"; Flags
|
|||
Source: "{#source_path}\ArchipelagoOoTClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/oot
|
||||
Source: "{#source_path}\ArchipelagoOoTAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/oot
|
||||
Source: "{#source_path}\ArchipelagoFF1Client.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/ff1
|
||||
Source: "{#source_path}\ArchipelagoPokemonClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/pkmn
|
||||
Source: "{#source_path}\ArchipelagoChecksFinderClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/cf
|
||||
Source: "{#source_path}\ArchipelagoStarcraft2Client.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sc2
|
||||
Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall
|
||||
|
@ -111,6 +119,7 @@ Name: "{group}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactor
|
|||
Name: "{group}\{#MyAppName} Minecraft Client"; Filename: "{app}\ArchipelagoMinecraftClient.exe"; Components: client/minecraft
|
||||
Name: "{group}\{#MyAppName} Ocarina of Time Client"; Filename: "{app}\ArchipelagoOoTClient.exe"; Components: client/oot
|
||||
Name: "{group}\{#MyAppName} Final Fantasy 1 Client"; Filename: "{app}\ArchipelagoFF1Client.exe"; Components: client/ff1
|
||||
Name: "{group}\{#MyAppName} Pokemon Client"; Filename: "{app}\ArchipelagoPokemonClient.exe"; Components: client/pkmn
|
||||
Name: "{group}\{#MyAppName} ChecksFinder Client"; Filename: "{app}\ArchipelagoChecksFinderClient.exe"; Components: client/cf
|
||||
Name: "{group}\{#MyAppName} Starcraft 2 Client"; Filename: "{app}\ArchipelagoStarcraft2Client.exe"; Components: client/sc2
|
||||
|
||||
|
@ -121,6 +130,7 @@ Name: "{commondesktop}\{#MyAppName} Factorio Client"; Filename: "{app}\Archipela
|
|||
Name: "{commondesktop}\{#MyAppName} Minecraft Client"; Filename: "{app}\ArchipelagoMinecraftClient.exe"; Tasks: desktopicon; Components: client/minecraft
|
||||
Name: "{commondesktop}\{#MyAppName} Ocarina of Time Client"; Filename: "{app}\ArchipelagoOoTClient.exe"; Tasks: desktopicon; Components: client/oot
|
||||
Name: "{commondesktop}\{#MyAppName} Final Fantasy 1 Client"; Filename: "{app}\ArchipelagoFF1Client.exe"; Tasks: desktopicon; Components: client/ff1
|
||||
Name: "{commondesktop}\{#MyAppName} Pokemon Client"; Filename: "{app}\ArchipelagoPokemonClient.exe"; Tasks: desktopicon; Components: client/pkmn
|
||||
Name: "{commondesktop}\{#MyAppName} ChecksFinder Client"; Filename: "{app}\ArchipelagoChecksFinderClient.exe"; Tasks: desktopicon; Components: client/cf
|
||||
Name: "{commondesktop}\{#MyAppName} Starcraft 2 Client"; Filename: "{app}\ArchipelagoStarcraft2Client.exe"; Tasks: desktopicon; Components: client/sc2
|
||||
|
||||
|
@ -179,6 +189,16 @@ Root: HKCR; Subkey: "{#MyAppName}n64zpf"; ValueData: "Archip
|
|||
Root: HKCR; Subkey: "{#MyAppName}n64zpf\DefaultIcon"; ValueData: "{app}\ArchipelagoOoTClient.exe,0"; ValueType: string; ValueName: ""; Components: client/oot
|
||||
Root: HKCR; Subkey: "{#MyAppName}n64zpf\shell\open\command"; ValueData: """{app}\ArchipelagoOoTClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/oot
|
||||
|
||||
Root: HKCR; Subkey: ".apred"; ValueData: "{#MyAppName}pkmnrpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/pkmn/red
|
||||
Root: HKCR; Subkey: "{#MyAppName}pkmnrpatch"; ValueData: "Archipelago Pokemon Red Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/pkmn/red
|
||||
Root: HKCR; Subkey: "{#MyAppName}pkmnrpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoPokemonClient.exe,0"; ValueType: string; ValueName: ""; Components: client/pkmn/red
|
||||
Root: HKCR; Subkey: "{#MyAppName}pkmnrpatch\shell\open\command"; ValueData: """{app}\ArchipelagoPokemonClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/pkmn/red
|
||||
|
||||
Root: HKCR; Subkey: ".apblue"; ValueData: "{#MyAppName}pkmnbpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/pkmn/blue
|
||||
Root: HKCR; Subkey: "{#MyAppName}pkmnbpatch"; ValueData: "Archipelago Pokemon Blue Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/pkmn/blue
|
||||
Root: HKCR; Subkey: "{#MyAppName}pkmnbpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoPokemonClient.exe,0"; ValueType: string; ValueName: ""; Components: client/pkmn/blue
|
||||
Root: HKCR; Subkey: "{#MyAppName}pkmnbpatch\shell\open\command"; ValueData: """{app}\ArchipelagoPokemonClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/pkmn/blue
|
||||
|
||||
Root: HKCR; Subkey: ".archipelago"; ValueData: "{#MyAppName}multidata"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: server
|
||||
Root: HKCR; Subkey: "{#MyAppName}multidata"; ValueData: "Archipelago Server Data"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: server
|
||||
Root: HKCR; Subkey: "{#MyAppName}multidata\DefaultIcon"; ValueData: "{app}\ArchipelagoServer.exe,0"; ValueType: string; ValueName: ""; Components: server
|
||||
|
@ -234,6 +254,12 @@ var SoERomFilePage: TInputFileWizardPage;
|
|||
var ootrom: string;
|
||||
var OoTROMFilePage: TInputFileWizardPage;
|
||||
|
||||
var redrom: string;
|
||||
var RedROMFilePage: TInputFileWizardPage;
|
||||
|
||||
var bluerom: string;
|
||||
var BlueROMFilePage: TInputFileWizardPage;
|
||||
|
||||
function GetSNESMD5OfFile(const rom: string): string;
|
||||
var data: AnsiString;
|
||||
begin
|
||||
|
@ -281,6 +307,21 @@ begin
|
|||
'.sfc');
|
||||
end;
|
||||
|
||||
function AddGBRomPage(name: string): TInputFileWizardPage;
|
||||
begin
|
||||
Result :=
|
||||
CreateInputFilePage(
|
||||
wpSelectComponents,
|
||||
'Select ROM File',
|
||||
'Where is your ' + name + ' located?',
|
||||
'Select the file, then click Next.');
|
||||
|
||||
Result.Add(
|
||||
'Location of ROM file:',
|
||||
'GB ROM files|*.gb;*.gbc|All files|*.*',
|
||||
'.gb');
|
||||
end;
|
||||
|
||||
procedure AddOoTRomPage();
|
||||
begin
|
||||
ootrom := FileSearch('The Legend of Zelda - Ocarina of Time.z64', WizardDirValue());
|
||||
|
@ -425,6 +466,38 @@ begin
|
|||
Result := '';
|
||||
end;
|
||||
|
||||
function GetRedROMPath(Param: string): string;
|
||||
begin
|
||||
if Length(redrom) > 0 then
|
||||
Result := redrom
|
||||
else if Assigned(RedRomFilePage) then
|
||||
begin
|
||||
R := CompareStr(GetMD5OfFile(RedROMFilePage.Values[0]), '3d45c1ee9abd5738df46d2bdda8b57dc')
|
||||
if R <> 0 then
|
||||
MsgBox('Pokemon Red ROM validation failed. Very likely wrong file.', mbInformation, MB_OK);
|
||||
|
||||
Result := RedROMFilePage.Values[0]
|
||||
end
|
||||
else
|
||||
Result := '';
|
||||
end;
|
||||
|
||||
function GetBlueROMPath(Param: string): string;
|
||||
begin
|
||||
if Length(bluerom) > 0 then
|
||||
Result := bluerom
|
||||
else if Assigned(BlueRomFilePage) then
|
||||
begin
|
||||
R := CompareStr(GetMD5OfFile(BlueROMFilePage.Values[0]), '50927e843568814f7ed45ec4f944bd8b')
|
||||
if R <> 0 then
|
||||
MsgBox('Pokemon Blue ROM validation failed. Very likely wrong file.', mbInformation, MB_OK);
|
||||
|
||||
Result := BlueROMFilePage.Values[0]
|
||||
end
|
||||
else
|
||||
Result := '';
|
||||
end;
|
||||
|
||||
procedure InitializeWizard();
|
||||
begin
|
||||
AddOoTRomPage();
|
||||
|
@ -448,6 +521,14 @@ begin
|
|||
soerom := CheckRom('Secret of Evermore (USA).sfc', '6e9c94511d04fac6e0a1e582c170be3a');
|
||||
if Length(soerom) = 0 then
|
||||
SoEROMFilePage:= AddRomPage('Secret of Evermore (USA).sfc');
|
||||
|
||||
redrom := CheckRom('Pokemon Red (UE) [S][!].gb','3d45c1ee9abd5738df46d2bdda8b57dc');
|
||||
if Length(redrom) = 0 then
|
||||
RedROMFilePage:= AddGBRomPage('Pokemon Red (UE) [S][!].gb');
|
||||
|
||||
bluerom := CheckRom('Pokemon Blue (UE) [S][!].gb','50927e843568814f7ed45ec4f944bd8b');
|
||||
if Length(redrom) = 0 then
|
||||
BlueROMFilePage:= AddGBRomPage('Pokemon Blue (UE) [S][!].gb');
|
||||
end;
|
||||
|
||||
|
||||
|
@ -466,4 +547,8 @@ begin
|
|||
Result := not (WizardIsComponentSelected('generator/soe'));
|
||||
if (assigned(OoTROMFilePage)) and (PageID = OoTROMFilePage.ID) then
|
||||
Result := not (WizardIsComponentSelected('generator/oot') or WizardIsComponentSelected('client/oot'));
|
||||
end;
|
||||
if (assigned(RedROMFilePage)) and (PageID = RedROMFilePage.ID) then
|
||||
Result := not (WizardIsComponentSelected('generator/pkmn_r') or WizardIsComponentSelected('client/pkmn/red'));
|
||||
if (assigned(BlueROMFilePage)) and (PageID = BlueROMFilePage.ID) then
|
||||
Result := not (WizardIsComponentSelected('generator/pkmn_b') or WizardIsComponentSelected('client/pkmn/blue'));
|
||||
end;
|
||||
|
|
|
@ -99,7 +99,7 @@ class APContainer:
|
|||
"player_name": self.player_name,
|
||||
"game": self.game,
|
||||
# minimum version of patch system expected for patching to be successful
|
||||
"compatible_version": 4,
|
||||
"compatible_version": 5,
|
||||
"version": current_patch_version,
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Alex "Alchav" Avery
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,254 @@
|
|||
from typing import TextIO
|
||||
import os
|
||||
import logging
|
||||
|
||||
from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification
|
||||
from Fill import fill_restrictive, FillError, sweep_from_pool
|
||||
from ..AutoWorld import World, WebWorld
|
||||
from ..generic.Rules import add_item_rule
|
||||
from .items import item_table, item_groups
|
||||
from .locations import location_data, PokemonRBLocation
|
||||
from .regions import create_regions
|
||||
from .logic import PokemonLogic
|
||||
from .options import pokemon_rb_options
|
||||
from .rom_addresses import rom_addresses
|
||||
from .text import encode_text
|
||||
from .rom import generate_output, get_base_rom_bytes, get_base_rom_path, process_pokemon_data, process_wild_pokemon,\
|
||||
process_static_pokemon
|
||||
from .rules import set_rules
|
||||
|
||||
import worlds.pokemon_rb.poke_data as poke_data
|
||||
|
||||
|
||||
class PokemonWebWorld(WebWorld):
|
||||
tutorials = [Tutorial(
|
||||
"Multiworld Setup Guide",
|
||||
"A guide to playing Pokemon Red and Blue with Archipelago.",
|
||||
"English",
|
||||
"setup_en.md",
|
||||
"setup/en",
|
||||
["Alchav"]
|
||||
)]
|
||||
|
||||
|
||||
class PokemonRedBlueWorld(World):
|
||||
"""Pokémon Red and Pokémon Blue are the original monster-collecting turn-based RPGs. Explore the Kanto region with
|
||||
your Pokémon, catch more than 150 unique creatures, earn badges from the region's Gym Leaders, and challenge the
|
||||
Elite Four to become the champion!"""
|
||||
# -MuffinJets#4559
|
||||
game = "Pokemon Red and Blue"
|
||||
option_definitions = pokemon_rb_options
|
||||
remote_items = False
|
||||
data_version = 1
|
||||
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"}
|
||||
item_name_groups = item_groups
|
||||
|
||||
web = PokemonWebWorld()
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
super().__init__(world, player)
|
||||
self.fly_map = None
|
||||
self.fly_map_code = None
|
||||
self.extra_badges = {}
|
||||
self.type_chart = None
|
||||
self.local_poke_data = None
|
||||
self.learnsets = None
|
||||
self.trainer_name = None
|
||||
self.rival_name = None
|
||||
|
||||
@classmethod
|
||||
def stage_assert_generate(cls, world):
|
||||
versions = set()
|
||||
for player in world.player_ids:
|
||||
if world.worlds[player].game == "Pokemon Red and Blue":
|
||||
versions.add(world.game_version[player].current_key)
|
||||
for version in versions:
|
||||
if not os.path.exists(get_base_rom_path(version)):
|
||||
raise FileNotFoundError(get_base_rom_path(version))
|
||||
|
||||
def generate_early(self):
|
||||
def encode_name(name, t):
|
||||
try:
|
||||
if len(encode_text(name)) > 7:
|
||||
raise IndexError(f"{t} name too long for player {self.world.player_name[self.player]}. Must be 7 characters or fewer.")
|
||||
return encode_text(name, length=8, whitespace="@", safety=True)
|
||||
except KeyError as e:
|
||||
raise KeyError(f"Invalid character(s) in {t} name for player {self.world.player_name[self.player]}") from e
|
||||
self.trainer_name = encode_name(self.world.trainer_name[self.player].value, "Player")
|
||||
self.rival_name = encode_name(self.world.rival_name[self.player].value, "Rival")
|
||||
|
||||
if self.world.badges_needed_for_hm_moves[self.player].value >= 2:
|
||||
badges_to_add = ["Marsh Badge", "Volcano Badge", "Earth Badge"]
|
||||
if self.world.badges_needed_for_hm_moves[self.player].value == 3:
|
||||
badges = ["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Marsh Badge",
|
||||
"Soul Badge", "Volcano Badge", "Earth Badge"]
|
||||
self.world.random.shuffle(badges)
|
||||
badges_to_add += [badges.pop(), badges.pop()]
|
||||
hm_moves = ["Cut", "Fly", "Surf", "Strength", "Flash"]
|
||||
self.world.random.shuffle(hm_moves)
|
||||
self.extra_badges = {}
|
||||
for badge in badges_to_add:
|
||||
self.extra_badges[hm_moves.pop()] = badge
|
||||
|
||||
process_pokemon_data(self)
|
||||
|
||||
def create_items(self) -> None:
|
||||
locations = [location for location in location_data if location.type == "Item"]
|
||||
item_pool = []
|
||||
for location in locations:
|
||||
if "Hidden" in location.name and not self.world.randomize_hidden_items[self.player].value:
|
||||
continue
|
||||
if "Rock Tunnel B1F" in location.region and not self.world.extra_key_items[self.player].value:
|
||||
continue
|
||||
if location.name == "Celadon City - Mansion Lady" and not self.world.tea[self.player].value:
|
||||
continue
|
||||
item = self.create_item(location.original_item)
|
||||
if location.event:
|
||||
self.world.get_location(location.name, self.player).place_locked_item(item)
|
||||
elif ("Badge" not in item.name or self.world.badgesanity[self.player].value) and \
|
||||
(item.name != "Oak's Parcel" or self.world.old_man[self.player].value != 1):
|
||||
item_pool.append(item)
|
||||
self.world.random.shuffle(item_pool)
|
||||
|
||||
self.world.itempool += item_pool
|
||||
|
||||
|
||||
def pre_fill(self):
|
||||
|
||||
process_wild_pokemon(self)
|
||||
process_static_pokemon(self)
|
||||
|
||||
if self.world.old_man[self.player].value == 1:
|
||||
item = self.create_item("Oak's Parcel")
|
||||
locations = []
|
||||
for location in self.world.get_locations():
|
||||
if location.player == self.player and location.item is None and location.can_reach(self.world.state) \
|
||||
and location.item_rule(item):
|
||||
locations.append(location)
|
||||
self.world.random.choice(locations).place_locked_item(item)
|
||||
|
||||
|
||||
|
||||
if not self.world.badgesanity[self.player].value:
|
||||
self.world.non_local_items[self.player].value -= self.item_name_groups["Badges"]
|
||||
for i in range(5):
|
||||
try:
|
||||
badges = []
|
||||
badgelocs = []
|
||||
for badge in ["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Soul Badge",
|
||||
"Marsh Badge", "Volcano Badge", "Earth Badge"]:
|
||||
badges.append(self.create_item(badge))
|
||||
for loc in ["Pewter Gym - Brock 1", "Cerulean Gym - Misty 1", "Vermilion Gym - Lt. Surge 1",
|
||||
"Celadon Gym - Erika 1", "Fuchsia Gym - Koga 1", "Saffron Gym - Sabrina 1",
|
||||
"Cinnabar Gym - Blaine 1", "Viridian Gym - Giovanni 1"]:
|
||||
badgelocs.append(self.world.get_location(loc, self.player))
|
||||
state = self.world.get_all_state(False)
|
||||
self.world.random.shuffle(badges)
|
||||
self.world.random.shuffle(badgelocs)
|
||||
fill_restrictive(self.world, state, badgelocs.copy(), badges, True, True)
|
||||
except FillError:
|
||||
for location in badgelocs:
|
||||
location.item = None
|
||||
continue
|
||||
break
|
||||
else:
|
||||
raise FillError(f"Failed to place badges for player {self.player}")
|
||||
|
||||
locs = [self.world.get_location("Fossil - Choice A", self.player),
|
||||
self.world.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")
|
||||
|
||||
loc = self.world.get_location("Pallet Town - Player's PC", self.player)
|
||||
if loc.item is None:
|
||||
locs.append(loc)
|
||||
|
||||
for loc in locs:
|
||||
unplaced_items = []
|
||||
if loc.name in self.world.priority_locations[self.player].value:
|
||||
add_item_rule(loc, lambda i: i.advancement)
|
||||
for item in self.world.itempool:
|
||||
if item.player == self.player and loc.item_rule(item):
|
||||
self.world.itempool.remove(item)
|
||||
state = sweep_from_pool(self.world.state, self.world.itempool + unplaced_items)
|
||||
if state.can_reach(loc, "Location", self.player):
|
||||
loc.place_locked_item(item)
|
||||
break
|
||||
else:
|
||||
unplaced_items.append(item)
|
||||
self.world.itempool += unplaced_items
|
||||
|
||||
intervene = False
|
||||
test_state = self.world.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.world.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.world.get_location("Route 1 - Wild Pokemon - 1", self.player)
|
||||
loc.item = self.create_item("Mew")
|
||||
|
||||
def create_regions(self):
|
||||
if self.world.free_fly_location[self.player].value:
|
||||
fly_map_code = self.world.random.randint(5, 9)
|
||||
if fly_map_code == 9:
|
||||
fly_map_code = 10
|
||||
if fly_map_code == 5:
|
||||
fly_map_code = 4
|
||||
else:
|
||||
fly_map_code = 0
|
||||
self.fly_map = ["Pallet Town", "Viridian City", "Pewter City", "Cerulean City", "Lavender Town",
|
||||
"Vermilion City", "Celadon City", "Fuchsia City", "Cinnabar Island", "Indigo Plateau",
|
||||
"Saffron City"][fly_map_code]
|
||||
self.fly_map_code = fly_map_code
|
||||
create_regions(self.world, self.player)
|
||||
self.world.completion_condition[self.player] = lambda state, player=self.player: state.has("Become Champion", player=player)
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return PokemonRBItem(name, self.player)
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
generate_output(self, output_directory)
|
||||
|
||||
def write_spoiler_header(self, spoiler_handle: TextIO):
|
||||
if self.world.free_fly_location[self.player].value:
|
||||
spoiler_handle.write('Fly unlocks: %s\n' % self.fly_map)
|
||||
if self.extra_badges:
|
||||
for hm_move, badge in self.extra_badges.items():
|
||||
spoiler_handle.write(hm_move + " enabled by: " + (" " * 20)[:20 - len(hm_move)] + badge + "\n")
|
||||
|
||||
def write_spoiler(self, spoiler_handle):
|
||||
if self.world.randomize_type_matchup_types[self.player].value or \
|
||||
self.world.randomize_type_matchup_type_effectiveness[self.player].value:
|
||||
spoiler_handle.write(f"\n\nType matchups ({self.world.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")
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choice([item for item in item_table if item_table[item].classification in
|
||||
[ItemClassification.filler, ItemClassification.trap]])
|
||||
|
||||
|
||||
class PokemonRBItem(Item):
|
||||
game = "Pokemon Red and Blue"
|
||||
type = None
|
||||
|
||||
def __init__(self, name, player: int = None):
|
||||
item_data = item_table[name]
|
||||
super(PokemonRBItem, self).__init__(
|
||||
name,
|
||||
item_data.classification,
|
||||
item_data.id, player
|
||||
)
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,55 @@
|
|||
# Pokémon Red and Blue
|
||||
|
||||
## Where is the settings page?
|
||||
|
||||
The [player settings page for this game](../player-settings) contains all the options you need to configure and export a
|
||||
config file.
|
||||
|
||||
## What does randomization do to this game?
|
||||
|
||||
Items which the player would normally acquire throughout the game have been moved around. Logic remains, so the game is
|
||||
always able to be completed, but because of the item shuffle the player may need to access certain areas before they
|
||||
would in the vanilla game.
|
||||
|
||||
A great many things besides item placement can be randomized, such as the location of Pokémon, their stats, types, etc., depending on your yaml settings.
|
||||
|
||||
Many baseline changes are made to the game, including:
|
||||
|
||||
* Bag item space increased to 128 slots (up from 20)
|
||||
* PC item storage increased to 64 slots (up from 50)
|
||||
* You can hold B to run (or bike extra fast!).
|
||||
* You can hold select while talking to a trainer to re-battle them.
|
||||
* You can return to route 2 from Diglett's Cave without the use of Cut.
|
||||
* Mew can be encountered at the S.S. Anne dock truck. This can be randomized depending on your settings.
|
||||
* The S.S. Anne will never depart.
|
||||
* Seafoam Islands entrances are swapped. This means you need Strength to travel through from Cinnabar Island to Fuchsia
|
||||
City
|
||||
* After obtaining one of the fossil item checks in Mt Moon, the remaining item can be received from the Cinnabar Lab
|
||||
fossil scientist. This may require reviving a number of fossils, depending on your settings.
|
||||
* Obedience depends on the total number of badges you have obtained instead of depending on specific badges.
|
||||
* Pokémon that evolve by trading can also evolve by reaching level 35.
|
||||
* Evolution stones are reusable.
|
||||
* Much of the dialogue throughout the game has been removed or shortened.
|
||||
* If the Old Man is blocking your way through Viridian City, you do not have Oak's Parcel in your inventory, and you've
|
||||
exhausted your money and Poké Balls, you can get a free Poké Ball from your mom.
|
||||
|
||||
## What items and locations get shuffled?
|
||||
|
||||
All items that go into your bags given by NPCs or found on the ground, as well as gym badges.
|
||||
Optionally, hidden items (those located with the Item Finder) can be shuffled as well.
|
||||
|
||||
## Which items can be in another player's world?
|
||||
|
||||
Any of the items which can be shuffled may also be placed into another player's world.
|
||||
By default, gym badges are shuffled across only the 8 gyms, but you can turn on Badgesanity in your yaml to shuffle them
|
||||
into the general item pool.
|
||||
|
||||
## What does another world's item look like in Pokémon Red and Blue?
|
||||
|
||||
All items for other games will display simply as "AP ITEM," including those for other Pokémon Red and Blue games.
|
||||
|
||||
## When the player receives an item, what happens?
|
||||
|
||||
A "received item" sound effect will play. Currently, there is no in-game message informing you of what the item is.
|
||||
If you are in battle, have menus or text boxes opened, or scripted events are occurring, the items will not be given to
|
||||
you until these have ended.
|
|
@ -0,0 +1,84 @@
|
|||
# Setup Guide for Pokémon Red and Blue: Archipelago
|
||||
|
||||
## Important
|
||||
|
||||
As we are using Bizhawk, this guide is only applicable to Windows and Linux systems.
|
||||
|
||||
## Required Software
|
||||
|
||||
- Bizhawk: [Bizhawk Releases from TASVideos](https://tasvideos.org/BizHawk/ReleaseHistory)
|
||||
- Version 2.3.1 and later are supported. Version 2.7 is recommended for stability.
|
||||
- Detailed installation instructions for Bizhawk can be found at the above link.
|
||||
- Windows users must run the prereq installer first, which can also be found at the above link.
|
||||
- The built-in Archipelago client, which can be installed [here](https://github.com/ArchipelagoMW/Archipelago/releases)
|
||||
(select `Pokemon Client` during installation).
|
||||
- Pokémon Red and/or Blue ROM files. The Archipelago community cannot provide these.
|
||||
|
||||
## Configuring Bizhawk
|
||||
|
||||
Once Bizhawk has been installed, open Bizhawk and change the following settings:
|
||||
|
||||
- Under Config > Customize > Advanced, make sure the box for AutoSaveRAM is checked, and click the 5s button.
|
||||
This reduces the possibility of losing save data in emulator crashes.
|
||||
- Under Config > Customize, check the "Run in background" and "Accept background input" boxes. This will allow you to
|
||||
continue playing in the background, even if another window is selected.
|
||||
|
||||
It is strongly recommended to associate GB rom extensions (\*.gb) to the Bizhawk we've just installed.
|
||||
To do so, we simply have to search any Gameboy rom we happened to own, right click and select "Open with...", unfold
|
||||
the list that appears and select the bottom option "Look for another application", then browse to the Bizhawk folder
|
||||
and select EmuHawk.exe.
|
||||
|
||||
## Configuring your YAML file
|
||||
|
||||
### What is a YAML file and why do I need one?
|
||||
|
||||
Your YAML file contains a set of configuration options which provide the generator with information about how it should
|
||||
generate your game. Each player of a multiworld will provide their own YAML file. This setup allows each player to enjoy
|
||||
an experience customized for their taste, and different players in the same multiworld can all have different options.
|
||||
|
||||
### Where do I get a YAML file?
|
||||
|
||||
You can generate a yaml or download a template by visiting the [Pokemon Red and Blue Player Settings Page](/games/Pokemon Red and Blue/player-settings)
|
||||
|
||||
It is important to note that the `game_version` option determines the ROM file that will be patched.
|
||||
Both the player and the person generating (if they are generating locally) will need the corresponding ROM file.
|
||||
|
||||
For `trainer_name` and `rival_name` the following regular characters are allowed:
|
||||
|
||||
* `‘’“”·… ABCDEFGHIJKLMNOPQRSTUVWXYZ():;[]abcdefghijklmnopqrstuvwxyzé'-?!.♂$×/,♀0123456789`
|
||||
|
||||
And the following special characters (these each take up one character):
|
||||
* `<'d>`
|
||||
* `<'l>`
|
||||
* `<'t>`
|
||||
* `<'v>`
|
||||
* `<'r>`
|
||||
* `<'m>`
|
||||
* `<PK>`
|
||||
* `<MN>`
|
||||
* `<MALE>` alias for `♂`
|
||||
* `<FEMALE>` alias for `♀`
|
||||
|
||||
## Joining a MultiWorld Game
|
||||
|
||||
### Obtain your Pokémon patch file
|
||||
|
||||
When you join a multiworld game, you will be asked to provide your YAML file to whoever is hosting. Once that is done,
|
||||
the host will provide you with either a link to download your data file, or with a zip file containing everyone's data
|
||||
files. Your data file should have a `.apred` or `.apblue` extension.
|
||||
|
||||
Double-click on your patch file to start your client and start the ROM patch process. Once the process is finished
|
||||
(this can take a while), the client and the emulator will be started automatically (if you associated the extension
|
||||
to the emulator as recommended).
|
||||
|
||||
### Connect to the Multiserver
|
||||
|
||||
Once both the client and the emulator are started, you must connect them. Within the emulator click on the "Tools"
|
||||
menu and select "Lua Console". Click the folder button or press Ctrl+O to open a Lua script.
|
||||
|
||||
Navigate to your Archipelago install folder and open `data/lua/PKMN_RB/pkmr_rb.lua`.
|
||||
|
||||
To connect the client to the multiserver simply put `<address>:<port>` on the textfield on top and press enter (if the
|
||||
server uses password, type in the bottom textfield `/connect <address>:<port> [password]`)
|
||||
|
||||
Now you are ready to start your adventure in Kanto.
|
|
@ -0,0 +1,176 @@
|
|||
from BaseClasses import ItemClassification
|
||||
from .poke_data import pokemon_data
|
||||
|
||||
class ItemData:
|
||||
def __init__(self, id, classification, groups):
|
||||
self.groups = groups
|
||||
self.classification = classification
|
||||
self.id = None if id is None else id + 172000000
|
||||
|
||||
item_table = {
|
||||
"Master Ball": ItemData(1, ItemClassification.useful, ["Consumables", "Poke Balls"]),
|
||||
"Ultra Ball": ItemData(2, ItemClassification.filler, ["Consumables", "Poke Balls"]),
|
||||
"Great Ball": ItemData(3, ItemClassification.filler, ["Consumables", "Poke Balls"]),
|
||||
"Poke Ball": ItemData(4, ItemClassification.filler, ["Consumables", "Poke Balls"]),
|
||||
"Town Map": ItemData(5, ItemClassification.progression_skip_balancing, ["Unique", "Key Items"]),
|
||||
"Bicycle": ItemData(6, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
# "Flippers": ItemData(7, ItemClassification.progression),
|
||||
#"Safari Ball": ItemData(8, ItemClassification.filler),
|
||||
#"Pokedex": ItemData(9, ItemClassification.filler),
|
||||
"Moon Stone": ItemData(10, ItemClassification.useful, ["Unique", "Evolution Stones"]),
|
||||
"Antidote": ItemData(11, ItemClassification.filler, ["Consumables"]),
|
||||
"Burn Heal": ItemData(12, ItemClassification.filler, ["Consumables"]),
|
||||
"Ice Heal": ItemData(13, ItemClassification.filler, ["Consumables"]),
|
||||
"Awakening": ItemData(14, ItemClassification.filler, ["Consumables"]),
|
||||
"Paralyze Heal": ItemData(15, ItemClassification.filler, ["Consumables"]),
|
||||
"Full Restore": ItemData(16, ItemClassification.filler, ["Consumables"]),
|
||||
"Max Potion": ItemData(17, ItemClassification.filler, ["Consumables"]),
|
||||
"Hyper Potion": ItemData(18, ItemClassification.filler, ["Consumables"]),
|
||||
"Super Potion": ItemData(19, ItemClassification.filler, ["Consumables"]),
|
||||
"Potion": ItemData(20, ItemClassification.filler, ["Consumables"]),
|
||||
"Boulder Badge": ItemData(21, ItemClassification.progression, ["Unique", "Key Items", "Badges"]),
|
||||
"Cascade Badge": ItemData(22, ItemClassification.progression, ["Unique", "Key Items", "Badges"]),
|
||||
"Thunder Badge": ItemData(23, ItemClassification.progression, ["Unique", "Key Items", "Badges"]),
|
||||
"Rainbow Badge": ItemData(24, ItemClassification.progression, ["Unique", "Key Items", "Badges"]),
|
||||
"Soul Badge": ItemData(25, ItemClassification.progression, ["Unique", "Key Items", "Badges"]),
|
||||
"Marsh Badge": ItemData(26, ItemClassification.progression, ["Unique", "Key Items", "Badges"]),
|
||||
"Volcano Badge": ItemData(27, ItemClassification.progression, ["Unique", "Key Items", "Badges"]),
|
||||
"Earth Badge": ItemData(28, ItemClassification.progression, ["Unique", "Key Items", "Badges"]),
|
||||
"Escape Rope": ItemData(29, ItemClassification.filler, ["Consumables"]),
|
||||
"Repel": ItemData(30, ItemClassification.filler, ["Consumables"]),
|
||||
"Old Amber": ItemData(31, ItemClassification.progression_skip_balancing, ["Unique", "Fossils"]),
|
||||
"Fire Stone": ItemData(32, ItemClassification.useful, ["Unique", "Evolution Stones"]),
|
||||
"Thunder Stone": ItemData(33, ItemClassification.useful, ["Unique", "Evolution Stones"]),
|
||||
"Water Stone": ItemData(34, ItemClassification.useful, ["Unique", "Evolution Stones"]),
|
||||
"HP Up": ItemData(35, ItemClassification.filler, ["Consumables", "Vitamins"]),
|
||||
"Protein": ItemData(36, ItemClassification.filler, ["Consumables", "Vitamins"]),
|
||||
"Iron": ItemData(37, ItemClassification.filler, ["Consumables", "Vitamins"]),
|
||||
"Carbos": ItemData(38, ItemClassification.filler, ["Consumables", "Vitamins"]),
|
||||
"Calcium": ItemData(39, ItemClassification.filler, ["Consumables", "Vitamins"]),
|
||||
"Rare Candy": ItemData(40, ItemClassification.useful, ["Consumables"]),
|
||||
"Dome Fossil": ItemData(41, ItemClassification.progression_skip_balancing, ["Unique", "Fossils"]),
|
||||
"Helix Fossil": ItemData(42, ItemClassification.progression_skip_balancing, ["Unique", "Fossils"]),
|
||||
"Secret Key": ItemData(43, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
"Bike Voucher": ItemData(45, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
"X Accuracy": ItemData(46, ItemClassification.filler, ["Consumables", "Battle Items"]),
|
||||
"Leaf Stone": ItemData(47, ItemClassification.useful, ["Unique", "Evolution Stones"]),
|
||||
"Card Key": ItemData(48, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
"Nugget": ItemData(49, ItemClassification.filler, []),
|
||||
#"Laptop": ItemData(50, ItemClassification.useful, ["Unique"]),
|
||||
"Poke Doll": ItemData(51, ItemClassification.filler, ["Consumables"]),
|
||||
"Full Heal": ItemData(52, ItemClassification.filler, ["Consumables"]),
|
||||
"Revive": ItemData(53, ItemClassification.filler, ["Consumables"]),
|
||||
"Max Revive": ItemData(54, ItemClassification.filler, ["Consumables"]),
|
||||
"Guard Spec": ItemData(55, ItemClassification.filler, ["Consumables", "Battle Items"]),
|
||||
"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),
|
||||
"Fresh Water": ItemData(60, ItemClassification.filler, ["Consumables"]),
|
||||
"Soda Pop": ItemData(61, ItemClassification.filler, ["Consumables"]),
|
||||
"Lemonade": ItemData(62, ItemClassification.filler, ["Consumables"]),
|
||||
"S.S. Ticket": ItemData(63, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
"Gold Teeth": ItemData(64, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
"X Attack": ItemData(65, ItemClassification.filler, ["Consumables", "Battle Items"]),
|
||||
"X Defend": ItemData(66, ItemClassification.filler, ["Consumables", "Battle Items"]),
|
||||
"X Speed": ItemData(67, ItemClassification.filler, ["Consumables", "Battle Items"]),
|
||||
"X Special": ItemData(68, ItemClassification.filler, ["Consumables", "Battle Items"]),
|
||||
"Coin Case": ItemData(69, ItemClassification.progression_skip_balancing, ["Unique", "Key Items"]),
|
||||
"Oak's Parcel": ItemData(70, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
"Item Finder": ItemData(71, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
"Silph Scope": ItemData(72, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
"Poke Flute": ItemData(73, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
"Lift Key": ItemData(74, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
"Exp. All": ItemData(75, ItemClassification.useful, ["Unique"]),
|
||||
"Old Rod": ItemData(76, ItemClassification.progression_skip_balancing, ["Unique", "Key Items", "Rods"]),
|
||||
"Good Rod": ItemData(77, ItemClassification.progression_skip_balancing, ["Unique", "Key Items", "Rods"]),
|
||||
"Super Rod": ItemData(78, ItemClassification.progression_skip_balancing, ["Unique", "Key Items", "Rods"]),
|
||||
"PP Up": ItemData(79, ItemClassification.filler, ["Consumables"]),
|
||||
"Ether": ItemData(80, ItemClassification.filler, ["Consumables"]),
|
||||
"Max Ether": ItemData(81, ItemClassification.filler, ["Consumables"]),
|
||||
"Elixir": ItemData(82, ItemClassification.filler, ["Consumables"]),
|
||||
"Max Elixir": ItemData(83, ItemClassification.filler, ["Consumables"]),
|
||||
"Tea": ItemData(84, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
# "Master Sword": ItemData(85, ItemClassification.progression),
|
||||
# "Flute": ItemData(86, ItemClassification.progression),
|
||||
# "Titan's Mitt": ItemData(87, ItemClassification.progression),
|
||||
# "Lamp": ItemData(88, ItemClassification.progression),
|
||||
"Plant Key": ItemData(89, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
"Mansion Key": ItemData(90, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
"Hideout Key": ItemData(91, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
"Safari Pass": ItemData(93, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
"HM01 Cut": ItemData(196, ItemClassification.progression, ["Unique", "HMs"]),
|
||||
"HM02 Fly": ItemData(197, ItemClassification.progression, ["Unique", "HMs"]),
|
||||
"HM03 Surf": ItemData(198, ItemClassification.progression, ["Unique", "HMs"]),
|
||||
"HM04 Strength": ItemData(199, ItemClassification.progression, ["Unique", "HMs"]),
|
||||
"HM05 Flash": ItemData(200, ItemClassification.progression, ["Unique", "HMs"]),
|
||||
"TM01 Mega Punch": ItemData(201, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM02 Razor Wind": ItemData(202, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM03 Swords Dance": ItemData(203, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM04 Whirlwind": ItemData(204, ItemClassification.filler, ["Unique", "TMs"]),
|
||||
"TM05 Mega Kick": ItemData(205, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM06 Toxic": ItemData(206, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM07 Horn Drill": ItemData(207, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM08 Body Slam": ItemData(208, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"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"]),
|
||||
"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"]),
|
||||
"TM16 Pay Day": ItemData(216, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM17 Submission": ItemData(217, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM18 Counter": ItemData(218, ItemClassification.filler, ["Unique", "TMs"]),
|
||||
"TM19 Seismic Toss": ItemData(219, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM20 Rage": ItemData(220, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM21 Mega Drain": ItemData(221, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM22 Solar Beam": ItemData(222, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM23 Dragon Rage": ItemData(223, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM24 Thunderbolt": ItemData(224, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM25 Thunder": ItemData(225, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM26 Earthquake": ItemData(226, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM27 Fissure": ItemData(227, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM28 Dig": ItemData(228, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM29 Psychic": ItemData(229, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM30 Teleport": ItemData(230, ItemClassification.filler, ["Unique", "TMs"]),
|
||||
"TM31 Mimic": ItemData(231, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM32 Double Team": ItemData(232, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM33 Reflect": ItemData(233, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM34 Bide": ItemData(234, ItemClassification.filler, ["Unique", "TMs"]),
|
||||
"TM35 Metronome": ItemData(235, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM36 Self Destruct": ItemData(236, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM37 Egg Bomb": ItemData(237, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM38 Fire Blast": ItemData(238, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM39 Swift": ItemData(239, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM40 Skull Bash": ItemData(240, ItemClassification.filler, ["Unique", "TMs"]),
|
||||
"TM41 Soft Boiled": ItemData(241, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM42 Dream Eater": ItemData(242, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM43 Sky Attack": ItemData(243, ItemClassification.filler, ["Unique", "TMs"]),
|
||||
"TM44 Rest": ItemData(244, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM45 Thunder Wave": ItemData(245, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM46 Psywave": ItemData(246, ItemClassification.filler, ["Unique", "TMs"]),
|
||||
"TM47 Explosion": ItemData(247, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM48 Rock Slide": ItemData(248, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM49 Tri Attack": ItemData(249, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
"TM50 Substitute": ItemData(250, ItemClassification.useful, ["Unique", "TMs"]),
|
||||
|
||||
"Fuji Saved": ItemData(None, ItemClassification.progression, []),
|
||||
"Silph Co Liberated": ItemData(None, ItemClassification.progression, []),
|
||||
"Become Champion": ItemData(None, ItemClassification.progression, [])
|
||||
}
|
||||
item_table.update(
|
||||
{pokemon: ItemData(None, ItemClassification.progression, []) for pokemon in pokemon_data.keys()}
|
||||
)
|
||||
item_table.update(
|
||||
{f"Missable {pokemon}": ItemData(None, ItemClassification.useful, []) for pokemon in pokemon_data.keys()}
|
||||
)
|
||||
item_table.update(
|
||||
{f"Static {pokemon}": ItemData(None, ItemClassification.progression, []) for pokemon in pokemon_data.keys()}
|
||||
)
|
||||
|
||||
|
||||
item_groups = {}
|
||||
for item, data in item_table.items():
|
||||
for group in data.groups:
|
||||
item_groups[group] = item_groups.get(group, []) + [item]
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,73 @@
|
|||
from ..AutoWorld import LogicMixin
|
||||
import worlds.pokemon_rb.poke_data as poke_data
|
||||
|
||||
|
||||
class PokemonLogic(LogicMixin):
|
||||
def pokemon_rb_can_surf(self, player):
|
||||
return (((self.has("HM03 Surf", player) and self.can_learn_hm("10000", player))
|
||||
or self.has("Flippers", player)) and (self.has("Soul Badge", player) or
|
||||
self.has(self.world.worlds[player].extra_badges.get("Surf"), player)
|
||||
or self.world.badges_needed_for_hm_moves[player].value == 0))
|
||||
|
||||
def pokemon_rb_can_cut(self, player):
|
||||
return ((self.has("HM01 Cut", player) and self.can_learn_hm("100", player) or self.has("Master Sword", player))
|
||||
and (self.has("Cascade Badge", player) or
|
||||
self.has(self.world.worlds[player].extra_badges.get("Cut"), player) or
|
||||
self.world.badges_needed_for_hm_moves[player].value == 0))
|
||||
|
||||
def pokemon_rb_can_fly(self, player):
|
||||
return (((self.has("HM02 Fly", player) and self.can_learn_hm("1000", player)) or self.has("Flute", player)) and
|
||||
(self.has("Thunder Badge", player) or self.has(self.world.worlds[player].extra_badges.get("Fly"), player)
|
||||
or self.world.badges_needed_for_hm_moves[player].value == 0))
|
||||
|
||||
def pokemon_rb_can_strength(self, player):
|
||||
return ((self.has("HM04 Strength", player) and self.can_learn_hm("100000", player)) or
|
||||
self.has("Titan's Mitt", player)) and (self.has("Rainbow Badge", player) or
|
||||
self.has(self.world.worlds[player].extra_badges.get("Strength"), player)
|
||||
or self.world.badges_needed_for_hm_moves[player].value == 0)
|
||||
|
||||
def pokemon_rb_can_flash(self, player):
|
||||
return (((self.has("HM05 Flash", player) and self.can_learn_hm("1000000", player)) or self.has("Lamp", player))
|
||||
and (self.has("Boulder Badge", player) or self.has(self.world.worlds[player].extra_badges.get("Flash"),
|
||||
player) or self.world.badges_needed_for_hm_moves[player].value == 0))
|
||||
|
||||
def can_learn_hm(self, move, player):
|
||||
for pokemon, data in self.world.worlds[player].local_poke_data.items():
|
||||
if self.has(pokemon, player) and data["tms"][6] & int(move, 2):
|
||||
return True
|
||||
return False
|
||||
|
||||
def pokemon_rb_can_get_hidden_items(self, player):
|
||||
return self.has("Item Finder", player) or not self.world.require_item_finder[player].value
|
||||
|
||||
def pokemon_rb_cerulean_cave(self, count, player):
|
||||
return len([item for item in
|
||||
["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Soul Badge", "Marsh Badge",
|
||||
"Volcano Badge", "Earth Badge", "Bicycle", "Silph Scope", "Item Finder", "Super Rod", "Good Rod",
|
||||
"Old Rod", "Lift Key", "Card Key", "Town Map", "Coin Case", "S.S. Ticket", "Secret Key",
|
||||
"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.world.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):
|
||||
return len([item for item in ["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Marsh Badge",
|
||||
"Soul Badge", "Volcano Badge", "Earth Badge"] if self.has(item, player)]) >= count
|
||||
|
||||
def pokemon_rb_has_pokemon(self, count, player):
|
||||
obtained_pokemon = set()
|
||||
for pokemon in poke_data.pokemon_data.keys():
|
||||
if self.has(pokemon, player) or self.has(f"Static {pokemon}", player):
|
||||
obtained_pokemon.add(pokemon)
|
||||
|
||||
return len(obtained_pokemon) >= count
|
||||
|
||||
def pokemon_rb_fossil_checks(self, count, player):
|
||||
return (self.can_reach('Mt Moon 1F - Southwest Item', 'Location', player) and
|
||||
self.can_reach('Cinnabar Island - Lab Scientist', 'Location', player) and len(
|
||||
[item for item in ["Dome Fossil", "Helix Fossil", "Old Amber"] if self.has(item, player)]) >= count)
|
|
@ -0,0 +1,481 @@
|
|||
|
||||
from Options import Toggle, Choice, Range, SpecialRange, FreeText, TextChoice
|
||||
|
||||
|
||||
class GameVersion(Choice):
|
||||
"""Select Red or Blue version."""
|
||||
display_name = "Game Version"
|
||||
option_red = 1
|
||||
option_blue = 0
|
||||
default = "random"
|
||||
|
||||
|
||||
class TrainerName(FreeText):
|
||||
"""Your trainer name. Cannot exceed 7 characters.
|
||||
See the setup guide on archipelago.gg for a list of allowed characters."""
|
||||
display_name = "Trainer Name"
|
||||
default = "ASH"
|
||||
|
||||
|
||||
class RivalName(FreeText):
|
||||
"""Your rival's name. Cannot exceed 7 characters.
|
||||
See the setup guide on archipelago.gg for a list of allowed characters."""
|
||||
display_name = "Rival's Name"
|
||||
default = "GARY"
|
||||
|
||||
|
||||
class Goal(Choice):
|
||||
"""If Professor Oak is selected, your victory condition will require challenging and defeating Oak after becoming"""
|
||||
"""Champion and defeating or capturing the Pokemon at the end of Cerulean Cave."""
|
||||
display_name = "Goal"
|
||||
option_pokemon_league = 0
|
||||
option_professor_oak = 1
|
||||
default = 0
|
||||
|
||||
|
||||
class EliteFourCondition(Range):
|
||||
"""Number of badges required to challenge the Elite Four once the Indigo Plateau has been reached.
|
||||
Your rival will reveal the amount needed on the first Route 22 battle (after turning in Oak's Parcel)."""
|
||||
display_name = "Elite Four Condition"
|
||||
range_start = 0
|
||||
range_end = 8
|
||||
default = 8
|
||||
|
||||
|
||||
class VictoryRoadCondition(Range):
|
||||
"""Number of badges required to reach Victory Road."""
|
||||
display_name = "Victory Road Condition"
|
||||
range_start = 0
|
||||
range_end = 8
|
||||
default = 8
|
||||
|
||||
|
||||
class ViridianGymCondition(Range):
|
||||
"""Number of badges required to enter Viridian Gym."""
|
||||
display_name = "Viridian Gym Condition"
|
||||
range_start = 0
|
||||
range_end = 7
|
||||
default = 7
|
||||
|
||||
|
||||
class CeruleanCaveCondition(Range):
|
||||
"""Number of badges, HMs, and key items (not counting items you can lose) required to access Cerulean Cave."""
|
||||
"""If extra_key_items is turned on, the number chosen will be increased by 4."""
|
||||
display_name = "Cerulean Cave Condition"
|
||||
range_start = 0
|
||||
range_end = 25
|
||||
default = 20
|
||||
|
||||
|
||||
class SecondFossilCheckCondition(Range):
|
||||
"""After choosing one of the fossil location items, you can obtain the remaining item from the Cinnabar Lab
|
||||
Scientist after reviving this number of fossils."""
|
||||
display_name = "Second Fossil Check Condition"
|
||||
range_start = 0
|
||||
range_end = 3
|
||||
default = 3
|
||||
|
||||
|
||||
class BadgeSanity(Toggle):
|
||||
"""Shuffle gym badges into the general item pool. If turned off, badges will be shuffled across the 8 gyms."""
|
||||
display_name = "Badgesanity"
|
||||
default = 0
|
||||
|
||||
|
||||
class BadgesNeededForHMMoves(Choice):
|
||||
"""Off will remove the requirement for badges to use HM moves. Extra will give the Marsh, Volcano, and Earth
|
||||
Badges a random HM move to enable. Extra Plus will additionally pick two random badges to enable a second HM move.
|
||||
A man in Cerulean City will reveal the moves enabled by each Badge."""
|
||||
display_name = "Badges Needed For HM Moves"
|
||||
default = 1
|
||||
option_on = 1
|
||||
alias_true = 1
|
||||
option_off = 0
|
||||
alias_false = 0
|
||||
option_extra = 2
|
||||
option_extra_plus = 3
|
||||
|
||||
|
||||
class OldMan(Choice):
|
||||
"""With Open Viridian City, the Old Man will let you through without needing to turn in Oak's Parcel."""
|
||||
"""Early Parcel will ensure Oak's Parcel is available at the beginning of your game."""
|
||||
display_name = "Old Man"
|
||||
option_vanilla = 0
|
||||
option_early_parcel = 1
|
||||
option_open_viridian_city = 2
|
||||
default = 1
|
||||
|
||||
|
||||
class Tea(Toggle):
|
||||
"""Adds a Tea item to the item pool which the Saffron guards require instead of the vending machine drinks.
|
||||
Adds a location check to the Celadon Mansion 1F, where Tea is acquired in FireRed and LeafGreen."""
|
||||
display_name = "Tea"
|
||||
default = 0
|
||||
|
||||
|
||||
class ExtraKeyItems(Toggle):
|
||||
"""Adds key items that are required to access the Rocket Hideout, Cinnabar Mansion, Safari Zone, and Power Plant.
|
||||
Adds four item pickups to Rock Tunnel B1F."""
|
||||
display_name = "Extra Key Items"
|
||||
default = 0
|
||||
|
||||
|
||||
class ExtraStrengthBoulders(Toggle):
|
||||
"""Adds Strength Boulders blocking the Route 11 gate, and in Route 13 (can be bypassed with Surf).
|
||||
This potentially increases the usefulness of Strength as well as the Bicycle."""
|
||||
display_name = "Extra Strength Boulders"
|
||||
default = 0
|
||||
|
||||
|
||||
class RequireItemFinder(Toggle):
|
||||
"""Require Item Finder to pick up hidden items."""
|
||||
display_name = "Require Item Finder"
|
||||
default = 0
|
||||
|
||||
|
||||
class RandomizeHiddenItems(Choice):
|
||||
"""Randomize hidden items. If you choose exclude, they will be randomized but will be guaranteed junk items."""
|
||||
display_name = "Randomize Hidden Items"
|
||||
option_on = 1
|
||||
option_off = 0
|
||||
alias_true = 1
|
||||
alias_false = 0
|
||||
option_exclude = 2
|
||||
default = 0
|
||||
|
||||
|
||||
class FreeFlyLocation(Toggle):
|
||||
"""One random fly destination will be unlocked by default."""
|
||||
display_name = "Free Fly Location"
|
||||
default = 1
|
||||
|
||||
|
||||
class OaksAidRt2(Range):
|
||||
"""Number of Pokemon registered in the Pokedex required to receive the item from Oak's Aide on Route 2"""
|
||||
display_name = "Oak's Aide Route 2"
|
||||
range_start = 0
|
||||
range_end = 80
|
||||
default = 10
|
||||
|
||||
|
||||
class OaksAidRt11(Range):
|
||||
"""Number of Pokemon registered in the Pokedex required to receive the item from Oak's Aide on Route 11"""
|
||||
display_name = "Oak's Aide Route 11"
|
||||
range_start = 0
|
||||
range_end = 80
|
||||
default = 30
|
||||
|
||||
|
||||
class OaksAidRt15(Range):
|
||||
"""Number of Pokemon registered in the Pokedex required to receive the item from Oak's Aide on Route 15"""
|
||||
display_name = "Oak's Aide Route 15"
|
||||
range_start = 0
|
||||
range_end = 80
|
||||
default = 50
|
||||
|
||||
|
||||
class ExpModifier(SpecialRange):
|
||||
"""Modifier for EXP gained. When specifying a number, exp is multiplied by this amount and divided by 16."""
|
||||
display_name = "Exp Modifier"
|
||||
range_start = 0
|
||||
range_end = 255
|
||||
default = 16
|
||||
special_range_names = {
|
||||
"half": default / 2,
|
||||
"normal": default,
|
||||
"double": default * 2,
|
||||
"triple": default * 3,
|
||||
"quadruple": default * 4,
|
||||
"quintuple": default * 5,
|
||||
"sextuple": default * 6,
|
||||
"septuple": default * 7,
|
||||
"octuple": default * 8,
|
||||
}
|
||||
|
||||
|
||||
class RandomizeWildPokemon(Choice):
|
||||
"""Randomize all wild Pokemon and game corner prize Pokemon. match_types will select a Pokemon with at least one
|
||||
type matching the original type of the original Pokemon. match_base_stats will prefer Pokemon with closer base stat
|
||||
totals. match_types_and_base_stats will match types and will weight towards similar base stats, but there may not be
|
||||
many to choose from."""
|
||||
display_name = "Randomize Wild Pokemon"
|
||||
default = 0
|
||||
option_vanilla = 0
|
||||
option_match_types = 1
|
||||
option_match_base_stats = 2
|
||||
option_match_types_and_base_stats = 3
|
||||
option_completely_random = 4
|
||||
|
||||
|
||||
class RandomizeStarterPokemon(Choice):
|
||||
"""Randomize the starter Pokemon choices."""
|
||||
display_name = "Randomize Starter Pokemon"
|
||||
default = 0
|
||||
option_vanilla = 0
|
||||
option_match_types = 1
|
||||
option_match_base_stats = 2
|
||||
option_match_types_and_base_stats = 3
|
||||
option_completely_random = 4
|
||||
|
||||
|
||||
class RandomizeStaticPokemon(Choice):
|
||||
"""Randomize all one-time gift and encountered Pokemon, except legendaries.
|
||||
These will always be first evolution stage Pokemon."""
|
||||
display_name = "Randomize Static Pokemon"
|
||||
default = 0
|
||||
option_vanilla = 0
|
||||
option_match_types = 1
|
||||
option_match_base_stats = 2
|
||||
option_match_types_and_base_stats = 3
|
||||
option_completely_random = 4
|
||||
|
||||
|
||||
class RandomizeLegendaryPokemon(Choice):
|
||||
"""Randomize Legendaries. Mew has been added as an encounter at the Vermilion dock truck.
|
||||
Shuffle will shuffle the legendaries with each other. Static will shuffle them into other static Pokemon locations.
|
||||
'Any' will allow legendaries to appear anywhere based on wild and static randomization options, and their locations
|
||||
will be randomized according to static Pokemon randomization options."""
|
||||
display_name = "Randomize Legendary Pokemon"
|
||||
default = 0
|
||||
option_vanilla = 0
|
||||
option_shuffle = 1
|
||||
option_static = 2
|
||||
option_any = 3
|
||||
|
||||
|
||||
class CatchEmAll(Choice):
|
||||
"""Guarantee all first evolution stage Pokemon are available, or all Pokemon of all stages.
|
||||
Currently only has an effect if wild Pokemon are randomized."""
|
||||
display_name = "Catch 'Em All"
|
||||
default = 0
|
||||
option_off = 0
|
||||
alias_false = 0
|
||||
option_first_stage = 1
|
||||
option_all_pokemon = 2
|
||||
|
||||
|
||||
class RandomizeTrainerParties(Choice):
|
||||
"""Randomize enemy Pokemon encountered in trainer battles."""
|
||||
display_name = "Randomize Trainer Parties"
|
||||
default = 0
|
||||
option_vanilla = 0
|
||||
option_match_types = 1
|
||||
option_match_base_stats = 2
|
||||
option_match_types_and_base_stats = 3
|
||||
option_completely_random = 4
|
||||
|
||||
|
||||
class TrainerLegendaries(Toggle):
|
||||
"""Allow legendary Pokemon in randomized trainer parties."""
|
||||
display_name = "Trainer Legendaries"
|
||||
default = 0
|
||||
|
||||
|
||||
class BlindTrainers(Range):
|
||||
"""Chance each frame that you are standing on a tile in a trainer's line of sight that they will fail to initiate a
|
||||
battle. If you move into and out of their line of sight without stopping, this chance will only trigger once."""
|
||||
display_name = "Blind Trainers"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
default = 0
|
||||
|
||||
|
||||
class MinimumStepsBetweenEncounters(Range):
|
||||
"""Minimum number of steps between wild Pokemon encounters."""
|
||||
display_name = "Minimum Steps Between Encounters"
|
||||
default = 3
|
||||
range_start = 0
|
||||
range_end = 255
|
||||
|
||||
|
||||
class RandomizePokemonStats(Choice):
|
||||
"""Randomize base stats for each Pokemon. Shuffle will shuffle the 5 base stat values amongst each other. Randomize
|
||||
will completely randomize each stat, but will still add up to the same base stat total."""
|
||||
display_name = "Randomize Pokemon Stats"
|
||||
default = 0
|
||||
option_vanilla = 0
|
||||
option_shuffle = 1
|
||||
option_randomize = 2
|
||||
|
||||
|
||||
class RandomizePokemonCatchRates(Toggle):
|
||||
"""Randomize the catch rate for each Pokemon."""
|
||||
display_name = "Randomize Catch Rates"
|
||||
default = 0
|
||||
|
||||
|
||||
class MinimumCatchRate(Range):
|
||||
"""Minimum catch rate for each Pokemon. If randomize_catch_rates is on, this will be the minimum value that can be
|
||||
chosen. Otherwise, it will raise any Pokemon's catch rate up to this value if its normal catch rate is lower."""
|
||||
display_name = "Minimum Catch Rate"
|
||||
range_start = 1
|
||||
range_end = 255
|
||||
default = 3
|
||||
|
||||
|
||||
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"
|
||||
option_vanilla = 0
|
||||
option_prefer_types = 1
|
||||
option_completely_random = 2
|
||||
default = 0
|
||||
|
||||
|
||||
class StartWithFourMoves(Toggle):
|
||||
"""If movesets are randomized, this will give all Pokemon 4 starting moves."""
|
||||
display_name = "Start With Four Moves"
|
||||
default = 0
|
||||
|
||||
|
||||
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 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 RandomizePokemonTypes(Choice):
|
||||
"""Randomize the types of each Pokemon. Follow Evolutions will ensure Pokemon's types remain the same when evolving
|
||||
(except possibly gaining a type)."""
|
||||
display_name = "Pokemon Types"
|
||||
option_vanilla = 0
|
||||
option_follow_evolutions = 1
|
||||
option_randomize = 2
|
||||
default = 0
|
||||
|
||||
|
||||
class SecondaryTypeChance(SpecialRange):
|
||||
"""If randomize_pokemon_types is on, this is the chance each Pokemon will have a secondary type. If follow_evolutions
|
||||
is selected, it is the chance a second type will be added at each evolution stage. vanilla will give secondary types
|
||||
to Pokemon that normally have a secondary type."""
|
||||
display_name = "Secondary Type Chance"
|
||||
range_start = -1
|
||||
range_end = 100
|
||||
default = -1
|
||||
special_range_names = {
|
||||
"vanilla": -1
|
||||
}
|
||||
|
||||
|
||||
class RandomizeTypeChartTypes(Choice):
|
||||
"""The game's type chart consists of 3 columns: attacking type, defending type, and type effectiveness.
|
||||
Matchups that have regular type effectiveness are not in the chart. Shuffle will shuffle the attacking types
|
||||
across the attacking type column and the defending types across the defending type column (so for example Normal
|
||||
type will still have exactly 2 types that it receives non-regular damage from, and 2 types it deals non-regular
|
||||
damage to). Randomize will randomize each type in both columns to any random type."""
|
||||
display_name = "Randomize Type Chart Types"
|
||||
option_vanilla = 0
|
||||
option_shuffle = 1
|
||||
option_randomize = 2
|
||||
default = 0
|
||||
|
||||
|
||||
class RandomizeTypeChartTypeEffectiveness(Choice):
|
||||
"""The game's type chart consists of 3 columns: attacking type, defending type, and type effectiveness.
|
||||
Matchups that have regular type effectiveness are not in the chart. Shuffle will shuffle the type effectiveness
|
||||
across the type effectiveness column (so for example there will always be 6 type immunities). Randomize will
|
||||
randomize each entry in the table to no effect, not very effective, or super effective; with no effect occurring
|
||||
at a low chance. Chaos will randomize the values to anywhere between 0% and 200% damage, in 10% increments."""
|
||||
display_name = "Randomize Type Chart Type Effectiveness"
|
||||
option_vanilla = 0
|
||||
option_shuffle = 1
|
||||
option_randomize = 2
|
||||
option_chaos = 3
|
||||
default = 0
|
||||
|
||||
|
||||
class SafariZoneNormalBattles(Toggle):
|
||||
"""Change the Safari Zone to have standard wild pokemon battles."""
|
||||
display_name = "Safari Zone Normal Battles"
|
||||
default = 0
|
||||
|
||||
|
||||
class NormalizeEncounterChances(Toggle):
|
||||
"""Each wild encounter table has 10 slots for Pokemon. Normally the chance for each being chosen ranges from
|
||||
19.9% to 1.2%. Turn this on to normalize them all to 10% each."""
|
||||
display_name = "Normalize Encounter Chances"
|
||||
default = 0
|
||||
|
||||
|
||||
class ReusableTMs(Toggle):
|
||||
"""Makes TMs reusable, so they will not be consumed upon use."""
|
||||
display_name = "Reusable TMs"
|
||||
default = 0
|
||||
|
||||
|
||||
class StartingMoney(Range):
|
||||
"""The amount of money you start with."""
|
||||
display_name = "Starting Money"
|
||||
default = 3000
|
||||
range_start = 0
|
||||
range_end = 999999
|
||||
|
||||
|
||||
pokemon_rb_options = {
|
||||
"game_version": GameVersion,
|
||||
"trainer_name": TrainerName,
|
||||
"rival_name": RivalName,
|
||||
#"goal": Goal,
|
||||
"elite_four_condition": EliteFourCondition,
|
||||
"victory_road_condition": VictoryRoadCondition,
|
||||
"viridian_gym_condition": ViridianGymCondition,
|
||||
"cerulean_cave_condition": CeruleanCaveCondition,
|
||||
"second_fossil_check_condition": SecondFossilCheckCondition,
|
||||
"badgesanity": BadgeSanity,
|
||||
"old_man": OldMan,
|
||||
"tea": Tea,
|
||||
"extra_key_items": ExtraKeyItems,
|
||||
"extra_strength_boulders": ExtraStrengthBoulders,
|
||||
"require_item_finder": RequireItemFinder,
|
||||
"randomize_hidden_items": RandomizeHiddenItems,
|
||||
"badges_needed_for_hm_moves": BadgesNeededForHMMoves,
|
||||
"free_fly_location": FreeFlyLocation,
|
||||
"oaks_aide_rt_2": OaksAidRt2,
|
||||
"oaks_aide_rt_11": OaksAidRt11,
|
||||
"oaks_aide_rt_15": OaksAidRt15,
|
||||
"blind_trainers": BlindTrainers,
|
||||
"minimum_steps_between_encounters": MinimumStepsBetweenEncounters,
|
||||
"exp_modifier": ExpModifier,
|
||||
"randomize_wild_pokemon": RandomizeWildPokemon,
|
||||
"randomize_starter_pokemon": RandomizeStarterPokemon,
|
||||
"randomize_static_pokemon": RandomizeStaticPokemon,
|
||||
"randomize_legendary_pokemon": RandomizeLegendaryPokemon,
|
||||
"catch_em_all": CatchEmAll,
|
||||
"randomize_pokemon_stats": RandomizePokemonStats,
|
||||
"randomize_pokemon_catch_rates": RandomizePokemonCatchRates,
|
||||
"minimum_catch_rate": MinimumCatchRate,
|
||||
"randomize_trainer_parties": RandomizeTrainerParties,
|
||||
"trainer_legendaries": TrainerLegendaries,
|
||||
"randomize_pokemon_movesets": RandomizePokemonMovesets,
|
||||
"start_with_four_moves": StartWithFourMoves,
|
||||
"tm_compatibility": TMCompatibility,
|
||||
"hm_compatibility": HMCompatibility,
|
||||
"randomize_pokemon_types": RandomizePokemonTypes,
|
||||
"secondary_type_chance": SecondaryTypeChance,
|
||||
"randomize_type_matchup_types": RandomizeTypeChartTypes,
|
||||
"randomize_type_matchup_type_effectiveness": RandomizeTypeChartTypeEffectiveness,
|
||||
"safari_zone_normal_battles": SafariZoneNormalBattles,
|
||||
"normalize_encounter_chances": NormalizeEncounterChances,
|
||||
"reusable_tms": ReusableTMs,
|
||||
"starting_money": StartingMoney,
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,305 @@
|
|||
|
||||
from BaseClasses import MultiWorld, Region, Entrance, RegionType, LocationProgressType
|
||||
from worlds.generic.Rules import add_item_rule
|
||||
from .locations import location_data, PokemonRBLocation
|
||||
|
||||
|
||||
def create_region(world: MultiWorld, player: int, name: str, locations_per_region=None, exits=None):
|
||||
ret = Region(name, RegionType.Generic, name, player, world)
|
||||
for location in locations_per_region.get(name, []):
|
||||
if (world.randomize_hidden_items[player].value or "Hidden" not in location.name) and \
|
||||
(world.extra_key_items[player].value or name != "Rock Tunnel B1F" or "Item" not in location.name) and \
|
||||
(world.tea[player].value or location.name != "Celadon City - Mansion Lady"):
|
||||
location.parent_region = ret
|
||||
ret.locations.append(location)
|
||||
if world.randomize_hidden_items[player].value == 2 and "Hidden" in location.name:
|
||||
location.progress_type = LocationProgressType.EXCLUDED
|
||||
add_item_rule(location, lambda i: not (i.advancement or i.useful))
|
||||
if exits:
|
||||
for exit in exits:
|
||||
ret.exits.append(Entrance(player, exit, ret))
|
||||
locations_per_region[name] = []
|
||||
return ret
|
||||
|
||||
|
||||
def create_regions(world: MultiWorld, player: int):
|
||||
locations_per_region = {}
|
||||
for location in location_data:
|
||||
locations_per_region.setdefault(location.region, [])
|
||||
locations_per_region[location.region].append(PokemonRBLocation(player, location.name, location.address,
|
||||
location.rom_address))
|
||||
regions = [
|
||||
create_region(world, player, "Menu", locations_per_region),
|
||||
create_region(world, player, "Anywhere", locations_per_region),
|
||||
create_region(world, player, "Fossil", locations_per_region),
|
||||
create_region(world, player, "Pallet Town", locations_per_region),
|
||||
create_region(world, player, "Route 1", locations_per_region),
|
||||
create_region(world, player, "Viridian City", locations_per_region),
|
||||
create_region(world, player, "Viridian City North", locations_per_region),
|
||||
create_region(world, player, "Viridian Gym", locations_per_region),
|
||||
create_region(world, player, "Route 2", locations_per_region),
|
||||
create_region(world, player, "Route 2 East", locations_per_region),
|
||||
create_region(world, player, "Diglett's Cave", locations_per_region),
|
||||
create_region(world, player, "Route 22", locations_per_region),
|
||||
create_region(world, player, "Route 23 South", locations_per_region),
|
||||
create_region(world, player, "Route 23 North", locations_per_region),
|
||||
create_region(world, player, "Viridian Forest", locations_per_region),
|
||||
create_region(world, player, "Pewter City", locations_per_region),
|
||||
create_region(world, player, "Pewter Gym", locations_per_region),
|
||||
create_region(world, player, "Route 3", locations_per_region),
|
||||
create_region(world, player, "Mt Moon 1F", locations_per_region),
|
||||
create_region(world, player, "Mt Moon B1F", locations_per_region),
|
||||
create_region(world, player, "Mt Moon B2F", locations_per_region),
|
||||
create_region(world, player, "Route 4", locations_per_region),
|
||||
create_region(world, player, "Cerulean City", locations_per_region),
|
||||
create_region(world, player, "Cerulean Gym", locations_per_region),
|
||||
create_region(world, player, "Route 24", locations_per_region),
|
||||
create_region(world, player, "Route 25", locations_per_region),
|
||||
create_region(world, player, "Route 9", locations_per_region),
|
||||
create_region(world, player, "Route 10 North", locations_per_region),
|
||||
create_region(world, player, "Rock Tunnel 1F", locations_per_region),
|
||||
create_region(world, player, "Rock Tunnel B1F", locations_per_region),
|
||||
create_region(world, player, "Power Plant", locations_per_region),
|
||||
create_region(world, player, "Route 10 South", locations_per_region),
|
||||
create_region(world, player, "Lavender Town", locations_per_region),
|
||||
create_region(world, player, "Pokemon Tower 1F", locations_per_region),
|
||||
create_region(world, player, "Pokemon Tower 2F", locations_per_region),
|
||||
create_region(world, player, "Pokemon Tower 3F", locations_per_region),
|
||||
create_region(world, player, "Pokemon Tower 4F", locations_per_region),
|
||||
create_region(world, player, "Pokemon Tower 5F", locations_per_region),
|
||||
create_region(world, player, "Pokemon Tower 6F", locations_per_region),
|
||||
create_region(world, player, "Pokemon Tower 7F", locations_per_region),
|
||||
create_region(world, player, "Route 5", locations_per_region),
|
||||
create_region(world, player, "Saffron City", locations_per_region),
|
||||
create_region(world, player, "Saffron Gym", locations_per_region),
|
||||
create_region(world, player, "Copycat's House", locations_per_region),
|
||||
create_region(world, player, "Underground Tunnel North-South", locations_per_region),
|
||||
create_region(world, player, "Route 6", locations_per_region),
|
||||
create_region(world, player, "Vermilion City", locations_per_region),
|
||||
create_region(world, player, "Vermilion Gym", locations_per_region),
|
||||
create_region(world, player, "S.S. Anne 1F", locations_per_region),
|
||||
create_region(world, player, "S.S. Anne B1F", locations_per_region),
|
||||
create_region(world, player, "S.S. Anne 2F", locations_per_region),
|
||||
create_region(world, player, "Route 11", locations_per_region),
|
||||
create_region(world, player, "Route 11 East", locations_per_region),
|
||||
create_region(world, player, "Route 12 North", locations_per_region),
|
||||
create_region(world, player, "Route 12 South", locations_per_region),
|
||||
create_region(world, player, "Route 12 Grass", locations_per_region),
|
||||
create_region(world, player, "Route 12 West", locations_per_region),
|
||||
create_region(world, player, "Route 7", locations_per_region),
|
||||
create_region(world, player, "Underground Tunnel West-East", locations_per_region),
|
||||
create_region(world, player, "Route 8", locations_per_region),
|
||||
create_region(world, player, "Route 8 Grass", locations_per_region),
|
||||
create_region(world, player, "Celadon City", locations_per_region),
|
||||
create_region(world, player, "Celadon Prize Corner", locations_per_region),
|
||||
create_region(world, player, "Celadon Gym", locations_per_region),
|
||||
create_region(world, player, "Route 16", locations_per_region),
|
||||
create_region(world, player, "Route 16 North", locations_per_region),
|
||||
create_region(world, player, "Route 17", locations_per_region),
|
||||
create_region(world, player, "Route 18", locations_per_region),
|
||||
create_region(world, player, "Fuchsia City", locations_per_region),
|
||||
create_region(world, player, "Fuchsia Gym", locations_per_region),
|
||||
create_region(world, player, "Safari Zone Gate", locations_per_region),
|
||||
create_region(world, player, "Safari Zone Center", locations_per_region),
|
||||
create_region(world, player, "Safari Zone East", locations_per_region),
|
||||
create_region(world, player, "Safari Zone North", locations_per_region),
|
||||
create_region(world, player, "Safari Zone West", locations_per_region),
|
||||
create_region(world, player, "Route 15", locations_per_region),
|
||||
create_region(world, player, "Route 14", locations_per_region),
|
||||
create_region(world, player, "Route 13", locations_per_region),
|
||||
create_region(world, player, "Route 19", locations_per_region),
|
||||
create_region(world, player, "Route 20 East", locations_per_region),
|
||||
create_region(world, player, "Route 20 West", locations_per_region),
|
||||
create_region(world, player, "Seafoam Islands 1F", locations_per_region),
|
||||
create_region(world, player, "Seafoam Islands B1F", locations_per_region),
|
||||
create_region(world, player, "Seafoam Islands B2F", locations_per_region),
|
||||
create_region(world, player, "Seafoam Islands B3F", locations_per_region),
|
||||
create_region(world, player, "Seafoam Islands B4F", locations_per_region),
|
||||
create_region(world, player, "Cinnabar Island", locations_per_region),
|
||||
create_region(world, player, "Cinnabar Gym", locations_per_region),
|
||||
create_region(world, player, "Route 21", locations_per_region),
|
||||
create_region(world, player, "Silph Co 1F", locations_per_region),
|
||||
create_region(world, player, "Silph Co 2F", locations_per_region),
|
||||
create_region(world, player, "Silph Co 3F", locations_per_region),
|
||||
create_region(world, player, "Silph Co 4F", locations_per_region),
|
||||
create_region(world, player, "Silph Co 5F", locations_per_region),
|
||||
create_region(world, player, "Silph Co 6F", locations_per_region),
|
||||
create_region(world, player, "Silph Co 7F", locations_per_region),
|
||||
create_region(world, player, "Silph Co 8F", locations_per_region),
|
||||
create_region(world, player, "Silph Co 9F", locations_per_region),
|
||||
create_region(world, player, "Silph Co 10F", locations_per_region),
|
||||
create_region(world, player, "Silph Co 11F", locations_per_region),
|
||||
create_region(world, player, "Rocket Hideout B1F", locations_per_region),
|
||||
create_region(world, player, "Rocket Hideout B2F", locations_per_region),
|
||||
create_region(world, player, "Rocket Hideout B3F", locations_per_region),
|
||||
create_region(world, player, "Rocket Hideout B4F", locations_per_region),
|
||||
create_region(world, player, "Pokemon Mansion 1F", locations_per_region),
|
||||
create_region(world, player, "Pokemon Mansion 2F", locations_per_region),
|
||||
create_region(world, player, "Pokemon Mansion 3F", locations_per_region),
|
||||
create_region(world, player, "Pokemon Mansion B1F", locations_per_region),
|
||||
create_region(world, player, "Victory Road 1F", locations_per_region),
|
||||
create_region(world, player, "Victory Road 2F", locations_per_region),
|
||||
create_region(world, player, "Victory Road 3F", locations_per_region),
|
||||
create_region(world, player, "Indigo Plateau", locations_per_region),
|
||||
create_region(world, player, "Cerulean Cave 1F", locations_per_region),
|
||||
create_region(world, player, "Cerulean Cave 2F", locations_per_region),
|
||||
create_region(world, player, "Cerulean Cave B1F", locations_per_region),
|
||||
create_region(world, player, "Evolution", locations_per_region),
|
||||
]
|
||||
world.regions += regions
|
||||
connect(world, player, "Menu", "Anywhere", one_way=True)
|
||||
connect(world, player, "Menu", "Pallet Town", one_way=True)
|
||||
connect(world, player, "Menu", "Fossil", lambda state: state.pokemon_rb_fossil_checks(
|
||||
state.world.second_fossil_check_condition[player].value, player), one_way=True)
|
||||
connect(world, player, "Pallet Town", "Route 1")
|
||||
connect(world, player, "Route 1", "Viridian City")
|
||||
connect(world, player, "Viridian City", "Route 22")
|
||||
connect(world, player, "Route 22", "Route 23 South",
|
||||
lambda state: state.pokemon_rb_has_badges(state.world.victory_road_condition[player].value, player))
|
||||
connect(world, player, "Route 23 South", "Route 23 North", lambda state: state.pokemon_rb_can_surf(player))
|
||||
connect(world, player, "Viridian City North", "Viridian Gym", lambda state:
|
||||
state.pokemon_rb_has_badges(state.world.viridian_gym_condition[player].value, player), one_way=True)
|
||||
connect(world, player, "Route 2", "Route 2 East", lambda state: state.pokemon_rb_can_cut(player))
|
||||
connect(world, player, "Route 2 East", "Diglett's Cave", lambda state: state.pokemon_rb_can_cut(player))
|
||||
connect(world, player, "Route 2", "Viridian City North")
|
||||
connect(world, player, "Route 2", "Viridian Forest")
|
||||
connect(world, player, "Route 2", "Pewter City")
|
||||
connect(world, player, "Pewter City", "Pewter Gym", one_way=True)
|
||||
connect(world, player, "Pewter City", "Route 3")
|
||||
connect(world, player, "Route 4", "Route 3", one_way=True)
|
||||
connect(world, player, "Mt Moon 1F", "Mt Moon B1F", one_way=True)
|
||||
connect(world, player, "Mt Moon B1F", "Mt Moon B2F", one_way=True)
|
||||
connect(world, player, "Mt Moon B1F", "Route 4", one_way=True)
|
||||
connect(world, player, "Route 4", "Cerulean City")
|
||||
connect(world, player, "Cerulean City", "Cerulean Gym", one_way=True)
|
||||
connect(world, player, "Cerulean City", "Route 24", one_way=True)
|
||||
connect(world, player, "Route 24", "Route 25", one_way=True)
|
||||
connect(world, player, "Cerulean City", "Route 9", lambda state: state.pokemon_rb_can_cut(player))
|
||||
connect(world, player, "Route 9", "Route 10 North")
|
||||
connect(world, player, "Route 10 North", "Rock Tunnel 1F", lambda state: state.pokemon_rb_can_flash(player))
|
||||
connect(world, player, "Route 10 North", "Power Plant", lambda state: state.pokemon_rb_can_surf(player) and
|
||||
(state.has("Plant Key", player) or not state.world.extra_key_items[player].value), one_way=True)
|
||||
connect(world, player, "Rock Tunnel 1F", "Route 10 South", lambda state: state.pokemon_rb_can_flash(player))
|
||||
connect(world, player, "Rock Tunnel 1F", "Rock Tunnel B1F")
|
||||
connect(world, player, "Lavender Town", "Pokemon Tower 1F", one_way=True)
|
||||
connect(world, player, "Lavender Town", "Pokemon Tower 1F", one_way=True)
|
||||
connect(world, player, "Pokemon Tower 1F", "Pokemon Tower 2F", one_way=True)
|
||||
connect(world, player, "Pokemon Tower 2F", "Pokemon Tower 3F", one_way=True)
|
||||
connect(world, player, "Pokemon Tower 3F", "Pokemon Tower 4F", one_way=True)
|
||||
connect(world, player, "Pokemon Tower 4F", "Pokemon Tower 5F", one_way=True)
|
||||
connect(world, player, "Pokemon Tower 5F", "Pokemon Tower 6F", one_way=True)
|
||||
connect(world, player, "Pokemon Tower 6F", "Pokemon Tower 7F", lambda state: state.has("Silph Scope", player))
|
||||
connect(world, player, "Cerulean City", "Route 5")
|
||||
connect(world, player, "Route 5", "Saffron City", lambda state: state.pokemon_rb_can_pass_guards(player))
|
||||
connect(world, player, "Route 5", "Underground Tunnel North-South")
|
||||
connect(world, player, "Route 6", "Underground Tunnel North-South")
|
||||
connect(world, player, "Route 6", "Saffron City", lambda state: state.pokemon_rb_can_pass_guards(player))
|
||||
connect(world, player, "Route 7", "Saffron City", lambda state: state.pokemon_rb_can_pass_guards(player))
|
||||
connect(world, player, "Route 8", "Saffron City", lambda state: state.pokemon_rb_can_pass_guards(player))
|
||||
connect(world, player, "Saffron City", "Copycat's House", lambda state: state.has("Silph Co Liberated", player), one_way=True)
|
||||
connect(world, player, "Saffron City", "Saffron Gym", lambda state: state.has("Silph Co Liberated", player), one_way=True)
|
||||
connect(world, player, "Route 6", "Vermilion City")
|
||||
connect(world, player, "Vermilion City", "Vermilion Gym", lambda state: state.pokemon_rb_can_surf(player) or state.pokemon_rb_can_cut(player), one_way=True)
|
||||
connect(world, player, "Vermilion City", "S.S. Anne 1F", lambda state: state.has("S.S. Ticket", player), one_way=True)
|
||||
connect(world, player, "S.S. Anne 1F", "S.S. Anne 2F", one_way=True)
|
||||
connect(world, player, "S.S. Anne 1F", "S.S. Anne B1F", one_way=True)
|
||||
connect(world, player, "Vermilion City", "Route 11")
|
||||
connect(world, player, "Vermilion City", "Diglett's Cave")
|
||||
connect(world, player, "Route 12 West", "Route 11 East", lambda state: state.pokemon_rb_can_strength(player) or not state.world.extra_strength_boulders[player].value)
|
||||
connect(world, player, "Route 12 North", "Route 12 South", lambda state: state.has("Poke Flute", player) or state.pokemon_rb_can_surf( player))
|
||||
connect(world, player, "Route 12 West", "Route 12 North", lambda state: state.has("Poke Flute", player))
|
||||
connect(world, player, "Route 12 West", "Route 12 South", lambda state: state.has("Poke Flute", player))
|
||||
connect(world, player, "Route 12 South", "Route 12 Grass", lambda state: state.pokemon_rb_can_cut(player))
|
||||
connect(world, player, "Route 12 North", "Lavender Town")
|
||||
connect(world, player, "Route 7", "Lavender Town")
|
||||
connect(world, player, "Route 10 South", "Lavender Town")
|
||||
connect(world, player, "Route 7", "Underground Tunnel West-East")
|
||||
connect(world, player, "Route 8", "Underground Tunnel West-East")
|
||||
connect(world, player, "Route 8", "Celadon City")
|
||||
connect(world, player, "Route 8", "Route 8 Grass", lambda state: state.pokemon_rb_can_cut(player), one_way=True)
|
||||
connect(world, player, "Route 7", "Celadon City")
|
||||
connect(world, player, "Celadon City", "Celadon Gym", lambda state: state.pokemon_rb_can_cut(player), one_way=True)
|
||||
connect(world, player, "Celadon City", "Celadon Prize Corner")
|
||||
connect(world, player, "Celadon City", "Route 16")
|
||||
connect(world, player, "Route 16", "Route 16 North", lambda state: state.pokemon_rb_can_cut(player), one_way=True)
|
||||
connect(world, player, "Route 16", "Route 17", lambda state: state.has("Poke Flute", player) and state.has("Bicycle", player))
|
||||
connect(world, player, "Route 17", "Route 18", lambda state: state.has("Bicycle", player))
|
||||
connect(world, player, "Fuchsia City", "Fuchsia Gym", one_way=True)
|
||||
connect(world, player, "Fuchsia City", "Route 18")
|
||||
connect(world, player, "Fuchsia City", "Safari Zone Gate", one_way=True)
|
||||
connect(world, player, "Safari Zone Gate", "Safari Zone Center", lambda state: state.has("Safari Pass", player) or not state.world.extra_key_items[player].value, one_way=True)
|
||||
connect(world, player, "Safari Zone Center", "Safari Zone East", one_way=True)
|
||||
connect(world, player, "Safari Zone Center", "Safari Zone West", one_way=True)
|
||||
connect(world, player, "Safari Zone Center", "Safari Zone North", one_way=True)
|
||||
connect(world, player, "Fuchsia City", "Route 15")
|
||||
connect(world, player, "Route 15", "Route 14")
|
||||
connect(world, player, "Route 14", "Route 13")
|
||||
connect(world, player, "Route 13", "Route 12 South", lambda state: state.pokemon_rb_can_strength(player) or state.pokemon_rb_can_surf(player) or not state.world.extra_strength_boulders[player].value)
|
||||
connect(world, player, "Fuchsia City", "Route 19", lambda state: state.pokemon_rb_can_surf(player))
|
||||
connect(world, player, "Route 20 East", "Route 19")
|
||||
connect(world, player, "Route 20 West", "Cinnabar Island", lambda state: state.pokemon_rb_can_surf(player))
|
||||
connect(world, player, "Route 20 West", "Seafoam Islands 1F")
|
||||
connect(world, player, "Route 20 East", "Seafoam Islands 1F", one_way=True)
|
||||
connect(world, player, "Seafoam Islands 1F", "Route 20 East", lambda state: state.pokemon_rb_can_strength(player), one_way=True)
|
||||
connect(world, player, "Viridian City", "Viridian City North", lambda state: state.has("Oak's Parcel", player) or state.world.old_man[player].value == 2 or state.pokemon_rb_can_cut(player))
|
||||
connect(world, player, "Route 3", "Mt Moon 1F", one_way=True)
|
||||
connect(world, player, "Route 11", "Route 11 East", lambda state: state.pokemon_rb_can_strength(player))
|
||||
connect(world, player, "Cinnabar Island", "Cinnabar Gym", lambda state: state.has("Secret Key", player), one_way=True)
|
||||
connect(world, player, "Cinnabar Island", "Pokemon Mansion 1F", lambda state: state.has("Mansion Key", player) or not state.world.extra_key_items[player].value, one_way=True)
|
||||
connect(world, player, "Seafoam Islands 1F", "Seafoam Islands B1F", one_way=True)
|
||||
connect(world, player, "Seafoam Islands B1F", "Seafoam Islands B2F", one_way=True)
|
||||
connect(world, player, "Seafoam Islands B2F", "Seafoam Islands B3F", one_way=True)
|
||||
connect(world, player, "Seafoam Islands B3F", "Seafoam Islands B4F", one_way=True)
|
||||
connect(world, player, "Route 21", "Cinnabar Island", lambda state: state.pokemon_rb_can_surf(player))
|
||||
connect(world, player, "Pallet Town", "Route 21", lambda state: state.pokemon_rb_can_surf(player))
|
||||
connect(world, player, "Saffron City", "Silph Co 1F", lambda state: state.has("Fuji Saved", player), one_way=True)
|
||||
connect(world, player, "Silph Co 1F", "Silph Co 2F", one_way=True)
|
||||
connect(world, player, "Silph Co 2F", "Silph Co 3F", one_way=True)
|
||||
connect(world, player, "Silph Co 3F", "Silph Co 4F", one_way=True)
|
||||
connect(world, player, "Silph Co 4F", "Silph Co 5F", one_way=True)
|
||||
connect(world, player, "Silph Co 5F", "Silph Co 6F", one_way=True)
|
||||
connect(world, player, "Silph Co 6F", "Silph Co 7F", one_way=True)
|
||||
connect(world, player, "Silph Co 7F", "Silph Co 8F", one_way=True)
|
||||
connect(world, player, "Silph Co 8F", "Silph Co 9F", one_way=True)
|
||||
connect(world, player, "Silph Co 9F", "Silph Co 10F", one_way=True)
|
||||
connect(world, player, "Silph Co 10F", "Silph Co 11F", one_way=True)
|
||||
connect(world, player, "Celadon City", "Rocket Hideout B1F", lambda state: state.has("Hideout Key", player) or not state.world.extra_key_items[player].value, one_way=True)
|
||||
connect(world, player, "Rocket Hideout B1F", "Rocket Hideout B2F", one_way=True)
|
||||
connect(world, player, "Rocket Hideout B2F", "Rocket Hideout B3F", one_way=True)
|
||||
connect(world, player, "Rocket Hideout B3F", "Rocket Hideout B4F", one_way=True)
|
||||
connect(world, player, "Pokemon Mansion 1F", "Pokemon Mansion 2F", one_way=True)
|
||||
connect(world, player, "Pokemon Mansion 2F", "Pokemon Mansion 3F", one_way=True)
|
||||
connect(world, player, "Pokemon Mansion 1F", "Pokemon Mansion B1F", one_way=True)
|
||||
connect(world, player, "Route 23 North", "Victory Road 1F", lambda state: state.pokemon_rb_can_strength(player), one_way=True)
|
||||
connect(world, player, "Victory Road 1F", "Victory Road 2F", one_way=True)
|
||||
connect(world, player, "Victory Road 2F", "Victory Road 3F", one_way=True)
|
||||
connect(world, player, "Victory Road 2F", "Indigo Plateau", lambda state: state.pokemon_rb_has_badges(state.world.elite_four_condition[player], player), one_way=True)
|
||||
connect(world, player, "Cerulean City", "Cerulean Cave 1F", lambda state:
|
||||
state.pokemon_rb_cerulean_cave(state.world.cerulean_cave_condition[player].value + (state.world.extra_key_items[player].value * 4), player) and
|
||||
state.pokemon_rb_can_surf(player), one_way=True)
|
||||
connect(world, player, "Cerulean Cave 1F", "Cerulean Cave 2F", one_way=True)
|
||||
connect(world, player, "Cerulean Cave 1F", "Cerulean Cave B1F", lambda state: state.pokemon_rb_can_surf(player), one_way=True)
|
||||
if world.worlds[player].fly_map != "Pallet Town":
|
||||
connect(world, player, "Menu", world.worlds[player].fly_map, lambda state: state.pokemon_rb_can_fly(player), one_way=True,
|
||||
name="Fly to " + world.worlds[player].fly_map)
|
||||
|
||||
|
||||
def connect(world: MultiWorld, player: int, source: str, target: str, rule: callable = lambda state: True, one_way=False, name=None):
|
||||
source_region = world.get_region(source, player)
|
||||
target_region = world.get_region(target, player)
|
||||
|
||||
if name is None:
|
||||
name = source + " to " + target
|
||||
|
||||
connection = Entrance(
|
||||
player,
|
||||
name,
|
||||
source_region
|
||||
)
|
||||
|
||||
connection.access_rule = rule
|
||||
|
||||
source_region.exits.append(connection)
|
||||
connection.connect(target_region)
|
||||
if not one_way:
|
||||
connect(world, player, target, source, rule, True)
|
|
@ -0,0 +1,614 @@
|
|||
import os
|
||||
import hashlib
|
||||
import Utils
|
||||
import bsdiff4
|
||||
from copy import deepcopy
|
||||
from Patch import APDeltaPatch
|
||||
from .text import encode_text
|
||||
from .rom_addresses import rom_addresses
|
||||
from .locations import location_data
|
||||
import worlds.pokemon_rb.poke_data as poke_data
|
||||
|
||||
|
||||
def choose_forced_type(chances, random):
|
||||
n = random.randint(1, 100)
|
||||
for chance in chances:
|
||||
if chance[0] >= n:
|
||||
return chance[1]
|
||||
return None
|
||||
|
||||
|
||||
def filter_moves(moves, type, random):
|
||||
ret = []
|
||||
for move in moves:
|
||||
if poke_data.moves[move]["type"] == type or type is None:
|
||||
ret.append(move)
|
||||
random.shuffle(ret)
|
||||
return ret
|
||||
|
||||
|
||||
def get_move(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:
|
||||
moves.remove(move)
|
||||
return move
|
||||
else:
|
||||
return get_move(moves, [], random, starting_move)
|
||||
|
||||
|
||||
def get_encounter_slots(self):
|
||||
encounter_slots = [location for location in location_data if location.type == "Wild Encounter"]
|
||||
|
||||
for location in encounter_slots:
|
||||
if isinstance(location.original_item, list):
|
||||
location.original_item = location.original_item[not self.world.game_version[self.player].value]
|
||||
return encounter_slots
|
||||
|
||||
|
||||
def get_base_stat_total(mon):
|
||||
return (poke_data.pokemon_data[mon]["atk"] + poke_data.pokemon_data[mon]["def"]
|
||||
+ poke_data.pokemon_data[mon]["hp"] + poke_data.pokemon_data[mon]["spd"]
|
||||
+ poke_data.pokemon_data[mon]["spc"])
|
||||
|
||||
|
||||
def randomize_pokemon(self, mon, mons_list, randomize_type):
|
||||
if randomize_type in [1, 3]:
|
||||
type_mons = [pokemon for pokemon in mons_list if any([poke_data.pokemon_data[mon][
|
||||
"type1"] in [self.local_poke_data[pokemon]["type1"], self.local_poke_data[pokemon]["type2"]],
|
||||
poke_data.pokemon_data[mon]["type2"] in [self.local_poke_data[pokemon]["type1"],
|
||||
self.local_poke_data[pokemon]["type2"]]])]
|
||||
if not type_mons:
|
||||
type_mons = mons_list.copy()
|
||||
if randomize_type == 3:
|
||||
stat_base = get_base_stat_total(mon)
|
||||
type_mons.sort(key=lambda mon: abs(get_base_stat_total(mon) - stat_base))
|
||||
mon = type_mons[round(self.world.random.triangular(0, len(type_mons) - 1, 0))]
|
||||
if randomize_type == 2:
|
||||
stat_base = get_base_stat_total(mon)
|
||||
mons_list.sort(key=lambda mon: abs(get_base_stat_total(mon) - stat_base))
|
||||
mon = mons_list[round(self.world.random.triangular(0, 50, 0))]
|
||||
elif randomize_type == 4:
|
||||
mon = self.world.random.choice(mons_list)
|
||||
return mon
|
||||
|
||||
|
||||
def process_trainer_data(self, data):
|
||||
mons_list = [pokemon for pokemon in poke_data.pokemon_data.keys() if pokemon not in poke_data.legendary_pokemon
|
||||
or self.world.trainer_legendaries[self.player].value]
|
||||
address = rom_addresses["Trainer_Data"]
|
||||
while address < rom_addresses["Trainer_Data_End"]:
|
||||
if data[address] == 255:
|
||||
mode = 1
|
||||
else:
|
||||
mode = 0
|
||||
while True:
|
||||
address += 1
|
||||
if data[address] == 0:
|
||||
address += 1
|
||||
break
|
||||
address += mode
|
||||
mon = None
|
||||
for i in range(1, 4):
|
||||
for l in ["A", "B", "C", "D", "E", "F", "G", "H"]:
|
||||
if rom_addresses[f"Rival_Starter{i}_{l}"] == address:
|
||||
mon = " ".join(self.world.get_location(f"Pallet Town - Starter {i}", self.player).item.name.split()[1:])
|
||||
if l in ["D", "E", "F", "G", "H"] and mon in poke_data.evolves_to:
|
||||
mon = poke_data.evolves_to[mon]
|
||||
if l in ["F", "G", "H"] and mon in poke_data.evolves_to:
|
||||
mon = poke_data.evolves_to[mon]
|
||||
if mon is None and self.world.randomize_trainer_parties[self.player].value:
|
||||
mon = poke_data.id_to_mon[data[address]]
|
||||
mon = randomize_pokemon(self, mon, mons_list, self.world.randomize_trainer_parties[self.player].value)
|
||||
if mon is not None:
|
||||
data[address] = poke_data.pokemon_data[mon]["id"]
|
||||
|
||||
|
||||
def process_static_pokemon(self):
|
||||
starter_slots = [location for location in location_data if location.type == "Starter Pokemon"]
|
||||
legendary_slots = [location for location in location_data if location.type == "Legendary Pokemon"]
|
||||
static_slots = [location for location in location_data if location.type in
|
||||
["Static Pokemon", "Missable Pokemon"]]
|
||||
legendary_mons = [slot.original_item for slot in legendary_slots]
|
||||
|
||||
mons_list = [pokemon for pokemon in poke_data.first_stage_pokemon if pokemon not in poke_data.legendary_pokemon
|
||||
or self.world.randomize_legendary_pokemon[self.player].value == 3]
|
||||
if self.world.randomize_legendary_pokemon[self.player].value == 0:
|
||||
for slot in legendary_slots:
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
location.place_locked_item(self.create_item("Missable " + slot.original_item))
|
||||
elif self.world.randomize_legendary_pokemon[self.player].value == 1:
|
||||
self.world.random.shuffle(legendary_mons)
|
||||
for slot in legendary_slots:
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
location.place_locked_item(self.create_item("Missable " + legendary_mons.pop()))
|
||||
elif self.world.randomize_legendary_pokemon[self.player].value == 2:
|
||||
static_slots = static_slots + legendary_slots
|
||||
self.world.random.shuffle(static_slots)
|
||||
while legendary_slots:
|
||||
swap_slot = legendary_slots.pop()
|
||||
slot = static_slots.pop()
|
||||
slot_type = slot.type.split()[0]
|
||||
if slot_type == "Legendary":
|
||||
slot_type = "Missable"
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
location.place_locked_item(self.create_item(slot_type + " " + swap_slot.original_item))
|
||||
swap_slot.original_item = slot.original_item
|
||||
elif self.world.randomize_legendary_pokemon[self.player].value == 3:
|
||||
static_slots = static_slots + legendary_slots
|
||||
|
||||
for slot in static_slots:
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
randomize_type = self.world.randomize_static_pokemon[self.player].value
|
||||
slot_type = slot.type.split()[0]
|
||||
if slot_type == "Legendary":
|
||||
slot_type = "Missable"
|
||||
if not randomize_type:
|
||||
location.place_locked_item(self.create_item(slot_type + " " + slot.original_item))
|
||||
else:
|
||||
location.place_locked_item(self.create_item(slot_type + " " +
|
||||
randomize_pokemon(self, slot.original_item, mons_list, randomize_type)))
|
||||
|
||||
for slot in starter_slots:
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
randomize_type = self.world.randomize_starter_pokemon[self.player].value
|
||||
slot_type = "Missable"
|
||||
if not randomize_type:
|
||||
location.place_locked_item(self.create_item(slot_type + " " + slot.original_item))
|
||||
else:
|
||||
location.place_locked_item(self.create_item(slot_type + " " +
|
||||
randomize_pokemon(self, slot.original_item, mons_list, randomize_type)))
|
||||
|
||||
def process_wild_pokemon(self):
|
||||
|
||||
encounter_slots = get_encounter_slots(self)
|
||||
|
||||
placed_mons = {pokemon: 0 for pokemon in poke_data.pokemon_data.keys()}
|
||||
if self.world.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.world.randomize_legendary_pokemon[self.player].value == 3]
|
||||
self.world.random.shuffle(encounter_slots)
|
||||
locations = []
|
||||
for slot in encounter_slots:
|
||||
mon = randomize_pokemon(self, slot.original_item, mons_list, self.world.randomize_wild_pokemon[self.player].value)
|
||||
placed_mons[mon] += 1
|
||||
location = self.world.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)
|
||||
|
||||
mons_to_add = []
|
||||
remaining_pokemon = [pokemon for pokemon in poke_data.pokemon_data.keys() if placed_mons[pokemon] == 0 and
|
||||
(pokemon not in poke_data.legendary_pokemon or self.world.randomize_legendary_pokemon[self.player].value == 3)]
|
||||
if self.world.catch_em_all[self.player].value == 1:
|
||||
mons_to_add = [pokemon for pokemon in poke_data.first_stage_pokemon if placed_mons[pokemon] == 0 and
|
||||
(pokemon not in poke_data.legendary_pokemon or self.world.randomize_legendary_pokemon[self.player].value == 3)]
|
||||
elif self.world.catch_em_all[self.player].value == 2:
|
||||
mons_to_add = remaining_pokemon.copy()
|
||||
logic_needed_mons = max(self.world.oaks_aide_rt_2[self.player].value,
|
||||
self.world.oaks_aide_rt_11[self.player].value,
|
||||
self.world.oaks_aide_rt_15[self.player].value)
|
||||
if self.world.accessibility[self.player] == "minimal":
|
||||
logic_needed_mons = 0
|
||||
|
||||
self.world.random.shuffle(remaining_pokemon)
|
||||
while (len([pokemon for pokemon in placed_mons if placed_mons[pokemon] > 0])
|
||||
+ len(mons_to_add) < logic_needed_mons):
|
||||
mons_to_add.append(remaining_pokemon.pop())
|
||||
for mon in mons_to_add:
|
||||
stat_base = get_base_stat_total(mon)
|
||||
candidate_locations = get_encounter_slots(self)
|
||||
if self.world.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 = location_data
|
||||
candidate_locations = [self.world.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))
|
||||
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
|
||||
placed_mons[mon] += 1
|
||||
break
|
||||
|
||||
else:
|
||||
for slot in encounter_slots:
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
location.item = self.create_item(slot.original_item)
|
||||
location.event = True
|
||||
location.locked = True
|
||||
location.item.location = location
|
||||
placed_mons[location.item.name] += 1
|
||||
|
||||
|
||||
def process_pokemon_data(self):
|
||||
|
||||
local_poke_data = deepcopy(poke_data.pokemon_data)
|
||||
learnsets = deepcopy(poke_data.learnsets)
|
||||
|
||||
for mon, mon_data in local_poke_data.items():
|
||||
if self.world.randomize_pokemon_stats[self.player].value == 1:
|
||||
stats = [mon_data["hp"], mon_data["atk"], mon_data["def"], mon_data["spd"], mon_data["spc"]]
|
||||
self.world.random.shuffle(stats)
|
||||
mon_data["hp"] = stats[0]
|
||||
mon_data["atk"] = stats[1]
|
||||
mon_data["def"] = stats[2]
|
||||
mon_data["spd"] = stats[3]
|
||||
mon_data["spc"] = stats[4]
|
||||
elif self.world.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.world.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]
|
||||
if self.world.randomize_pokemon_types[self.player].value:
|
||||
if self.world.randomize_pokemon_types[self.player].value == 1 and mon in poke_data.evolves_from:
|
||||
type1 = local_poke_data[poke_data.evolves_from[mon]]["type1"]
|
||||
type2 = local_poke_data[poke_data.evolves_from[mon]]["type2"]
|
||||
if type1 == type2:
|
||||
if self.world.secondary_type_chance[self.player].value == -1:
|
||||
if mon_data["type1"] != mon_data["type2"]:
|
||||
while type2 == type1:
|
||||
type2 = self.world.random.choice(list(poke_data.type_names.values()))
|
||||
elif self.world.random.randint(1, 100) <= self.world.secondary_type_chance[self.player].value:
|
||||
type2 = self.world.random.choice(list(poke_data.type_names.values()))
|
||||
else:
|
||||
type1 = self.world.random.choice(list(poke_data.type_names.values()))
|
||||
type2 = type1
|
||||
if ((self.world.secondary_type_chance[self.player].value == -1 and mon_data["type1"]
|
||||
!= mon_data["type2"]) or self.world.random.randint(1, 100)
|
||||
<= self.world.secondary_type_chance[self.player].value):
|
||||
while type2 == type1:
|
||||
type2 = self.world.random.choice(list(poke_data.type_names.values()))
|
||||
|
||||
mon_data["type1"] = type1
|
||||
mon_data["type2"] = type2
|
||||
if self.world.randomize_pokemon_movesets[self.player].value:
|
||||
if self.world.randomize_pokemon_movesets[self.player].value == 1:
|
||||
if mon_data["type1"] == "Normal" and mon_data["type2"] == "Normal":
|
||||
chances = [[75, "Normal"]]
|
||||
elif mon_data["type1"] == "Normal" or mon_data["type2"] == "Normal":
|
||||
if mon_data["type1"] == "Normal":
|
||||
second_type = mon_data["type2"]
|
||||
else:
|
||||
second_type = mon_data["type1"]
|
||||
chances = [[30, "Normal"], [85, second_type]]
|
||||
elif mon_data["type1"] == mon_data["type2"]:
|
||||
chances = [[60, mon_data["type1"]], [80, "Normal"]]
|
||||
else:
|
||||
chances = [[50, mon_data["type1"]], [80, mon_data["type2"]], [85, "Normal"]]
|
||||
else:
|
||||
chances = []
|
||||
moves = set(poke_data.moves.keys())
|
||||
moves -= set(["No Move"] + poke_data.hm_moves)
|
||||
mon_data["start move 1"] = get_move(moves, chances, self.world.random, True)
|
||||
for i in range(2, 5):
|
||||
if mon_data[f"start move {i}"] != "No Move" or self.world.start_with_four_moves[
|
||||
self.player].value == 1:
|
||||
mon_data[f"start move {i}"] = get_move(moves, chances, self.world.random)
|
||||
if mon in learnsets:
|
||||
for move_num in range(0, len(learnsets[mon])):
|
||||
learnsets[mon][move_num] = get_move(moves, chances, self.world.random)
|
||||
if self.world.randomize_pokemon_catch_rates[self.player].value:
|
||||
mon_data["catch rate"] = self.world.random.randint(self.world.minimum_catch_rate[self.player], 255)
|
||||
else:
|
||||
mon_data["catch rate"] = max(self.world.minimum_catch_rate[self.player], mon_data["catch rate"])
|
||||
|
||||
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"]:
|
||||
mon_data["tms"] = local_poke_data[poke_data.evolves_from[mon]]["tms"]
|
||||
elif mon != "Mew":
|
||||
tms_hms = poke_data.tm_moves + poke_data.hm_moves
|
||||
for flag, tm_move in enumerate(tms_hms):
|
||||
if (flag < 50 and self.world.tm_compatibility[self.player].value == 1) or (flag >= 50 and self.world.hm_compatibility[self.player].value == 1):
|
||||
type_match = poke_data.moves[tm_move]["type"] in [mon_data["type1"], mon_data["type2"]]
|
||||
bit = int(self.world.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.world.tm_compatibility[self.player].value == 2) or (flag >= 50 and self.world.hm_compatibility[self.player].value == 2):
|
||||
bit = [0, 1][self.world.random.randint(0, 1)]
|
||||
elif (flag < 50 and self.world.tm_compatibility[self.player].value == 3) or (flag >= 50 and self.world.hm_compatibility[self.player].value == 3):
|
||||
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))
|
||||
|
||||
self.local_poke_data = local_poke_data
|
||||
self.learnsets = learnsets
|
||||
|
||||
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
random = self.world.slot_seeds[self.player]
|
||||
game_version = self.world.game_version[self.player].current_key
|
||||
data = bytearray(get_base_rom_bytes(game_version))
|
||||
|
||||
basemd5 = hashlib.md5()
|
||||
basemd5.update(data)
|
||||
|
||||
for location in self.world.get_locations():
|
||||
if location.player != self.player or location.rom_address is None:
|
||||
continue
|
||||
if location.item and location.item.player == self.player:
|
||||
if location.rom_address:
|
||||
rom_address = location.rom_address
|
||||
if not isinstance(rom_address, list):
|
||||
rom_address = [rom_address]
|
||||
for address in rom_address:
|
||||
if location.item.name in poke_data.pokemon_data.keys():
|
||||
data[address] = poke_data.pokemon_data[location.item.name]["id"]
|
||||
elif " ".join(location.item.name.split()[1:]) in poke_data.pokemon_data.keys():
|
||||
data[address] = poke_data.pokemon_data[" ".join(location.item.name.split()[1:])]["id"]
|
||||
else:
|
||||
data[address] = self.item_name_to_id[location.item.name] - 172000000
|
||||
|
||||
else:
|
||||
data[location.rom_address] = 0x2C # AP Item
|
||||
data[rom_addresses['Fly_Location']] = self.fly_map_code
|
||||
|
||||
if self.world.tea[self.player].value:
|
||||
data[rom_addresses["Option_Tea"]] = 1
|
||||
data[rom_addresses["Guard_Drink_List"]] = 0x54
|
||||
data[rom_addresses["Guard_Drink_List"] + 1] = 0
|
||||
data[rom_addresses["Guard_Drink_List"] + 2] = 0
|
||||
|
||||
if self.world.extra_key_items[self.player].value:
|
||||
data[rom_addresses['Options']] |= 4
|
||||
data[rom_addresses["Option_Blind_Trainers"]] = round(self.world.blind_trainers[self.player].value * 2.55)
|
||||
data[rom_addresses['Option_Cerulean_Cave_Condition']] = self.world.cerulean_cave_condition[self.player].value
|
||||
data[rom_addresses['Option_Encounter_Minimum_Steps']] = self.world.minimum_steps_between_encounters[self.player].value
|
||||
data[rom_addresses['Option_Victory_Road_Badges']] = self.world.victory_road_condition[self.player].value
|
||||
data[rom_addresses['Option_Pokemon_League_Badges']] = self.world.elite_four_condition[self.player].value
|
||||
data[rom_addresses['Option_Viridian_Gym_Badges']] = self.world.viridian_gym_condition[self.player].value
|
||||
data[rom_addresses['Option_EXP_Modifier']] = self.world.exp_modifier[self.player].value
|
||||
if not self.world.require_item_finder[self.player].value:
|
||||
data[rom_addresses['Option_Itemfinder']] = 0
|
||||
if self.world.extra_strength_boulders[self.player].value:
|
||||
for i in range(0, 3):
|
||||
data[rom_addresses['Option_Boulders'] + (i * 3)] = 0x15
|
||||
if self.world.extra_key_items[self.player].value:
|
||||
for i in range(0, 4):
|
||||
data[rom_addresses['Option_Rock_Tunnel_Extra_Items'] + (i * 3)] = 0x15
|
||||
if self.world.old_man[self.player].value == 2:
|
||||
data[rom_addresses['Option_Old_Man']] = 0x11
|
||||
data[rom_addresses['Option_Old_Man_Lying']] = 0x15
|
||||
money = str(self.world.starting_money[self.player].value)
|
||||
while len(money) < 6:
|
||||
money = "0" + money
|
||||
data[rom_addresses["Starting_Money_High"]] = int(money[:2], 16)
|
||||
data[rom_addresses["Starting_Money_Middle"]] = int(money[2:4], 16)
|
||||
data[rom_addresses["Starting_Money_Low"]] = int(money[4:], 16)
|
||||
data[rom_addresses["Text_Badges_Needed"]] = encode_text(
|
||||
str(max(self.world.victory_road_condition[self.player].value,
|
||||
self.world.elite_four_condition[self.player].value)))[0]
|
||||
if self.world.badges_needed_for_hm_moves[self.player].value == 0:
|
||||
for hm_move in poke_data.hm_moves:
|
||||
write_bytes(data, bytearray([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
|
||||
rom_addresses["HM_" + hm_move + "_Badge_a"])
|
||||
elif self.extra_badges:
|
||||
written_badges = {}
|
||||
for hm_move, badge in self.extra_badges.items():
|
||||
data[rom_addresses["HM_" + hm_move + "_Badge_b"]] = {"Boulder Badge": 0x47, "Cascade Badge": 0x4F,
|
||||
"Thunder Badge": 0x57, "Rainbow Badge": 0x5F,
|
||||
"Soul Badge": 0x67, "Marsh Badge": 0x6F,
|
||||
"Volcano Badge": 0x77, "Earth Badge": 0x7F}[badge]
|
||||
move_text = hm_move
|
||||
if badge not in ["Marsh Badge", "Volcano Badge", "Earth Badge"]:
|
||||
move_text = ", " + move_text
|
||||
rom_address = rom_addresses["Badge_Text_" + badge.replace(" ", "_")]
|
||||
if badge in written_badges:
|
||||
rom_address += len(written_badges[badge])
|
||||
move_text = ", " + move_text
|
||||
write_bytes(data, encode_text(move_text.upper()), rom_address)
|
||||
written_badges[badge] = move_text
|
||||
for badge in ["Marsh Badge", "Volcano Badge", "Earth Badge"]:
|
||||
if badge not in written_badges:
|
||||
write_bytes(data, encode_text("Nothing"), rom_addresses["Badge_Text_" + badge.replace(" ", "_")])
|
||||
|
||||
chart = deepcopy(poke_data.type_chart)
|
||||
if self.world.randomize_type_matchup_types[self.player].value == 1:
|
||||
attacking_types = []
|
||||
defending_types = []
|
||||
for matchup in chart:
|
||||
attacking_types.append(matchup[0])
|
||||
defending_types.append(matchup[1])
|
||||
random.shuffle(attacking_types)
|
||||
random.shuffle(defending_types)
|
||||
matchups = []
|
||||
while len(attacking_types) > 0:
|
||||
if [attacking_types[0], defending_types[0]] not in matchups:
|
||||
matchups.append([attacking_types.pop(0), defending_types.pop(0)])
|
||||
else:
|
||||
matchup = matchups.pop(0)
|
||||
attacking_types.append(matchup[0])
|
||||
defending_types.append(matchup[1])
|
||||
random.shuffle(attacking_types)
|
||||
random.shuffle(defending_types)
|
||||
for matchup, chart_row in zip(matchups, chart):
|
||||
chart_row[0] = matchup[0]
|
||||
chart_row[1] = matchup[1]
|
||||
elif self.world.randomize_type_matchup_types[self.player].value == 2:
|
||||
used_matchups = []
|
||||
for matchup in chart:
|
||||
matchup[0] = random.choice(list(poke_data.type_names.values()))
|
||||
matchup[1] = random.choice(list(poke_data.type_names.values()))
|
||||
while [matchup[0], matchup[1]] in used_matchups:
|
||||
matchup[0] = random.choice(list(poke_data.type_names.values()))
|
||||
matchup[1] = random.choice(list(poke_data.type_names.values()))
|
||||
used_matchups.append([matchup[0], matchup[1]])
|
||||
if self.world.randomize_type_matchup_type_effectiveness[self.player].value == 1:
|
||||
effectiveness_list = []
|
||||
for matchup in chart:
|
||||
effectiveness_list.append(matchup[2])
|
||||
random.shuffle(effectiveness_list)
|
||||
for (matchup, effectiveness) in zip(chart, effectiveness_list):
|
||||
matchup[2] = effectiveness
|
||||
elif self.world.randomize_type_matchup_type_effectiveness[self.player].value == 2:
|
||||
for matchup in chart:
|
||||
matchup[2] = random.choice([0] + ([5, 20] * 5))
|
||||
elif self.world.randomize_type_matchup_type_effectiveness[self.player].value == 3:
|
||||
for matchup in chart:
|
||||
matchup[2] = self.world.random.choice([i for i in range(0, 21) if i != 10])
|
||||
type_loc = rom_addresses["Type_Chart"]
|
||||
for matchup in chart:
|
||||
data[type_loc] = poke_data.type_ids[matchup[0]]
|
||||
data[type_loc + 1] = poke_data.type_ids[matchup[1]]
|
||||
data[type_loc + 2] = matchup[2]
|
||||
type_loc += 3
|
||||
# sort so that super-effective matchups occur first, to prevent dual "not very effective" / "super effective"
|
||||
# matchups from leading to damage being ultimately divided by 2 and then multiplied by 2, which can lead to
|
||||
# damage being reduced by 1 which leads to a "not very effective" message appearing due to my changes
|
||||
# to the way effectiveness messages are generated.
|
||||
self.type_chart = sorted(chart, key=lambda matchup: 0 - matchup[2])
|
||||
|
||||
if self.world.normalize_encounter_chances[self.player].value:
|
||||
chances = [25, 51, 77, 103, 129, 155, 180, 205, 230, 255]
|
||||
for i, chance in enumerate(chances):
|
||||
data[rom_addresses['Encounter_Chances'] + (i * 2)] = chance
|
||||
|
||||
for mon, mon_data in self.local_poke_data.items():
|
||||
if mon == "Mew":
|
||||
address = rom_addresses["Base_Stats_Mew"]
|
||||
else:
|
||||
address = rom_addresses["Base_Stats"] + (28 * (mon_data["dex"] - 1))
|
||||
data[address + 1] = self.local_poke_data[mon]["hp"]
|
||||
data[address + 2] = self.local_poke_data[mon]["atk"]
|
||||
data[address + 3] = self.local_poke_data[mon]["def"]
|
||||
data[address + 4] = self.local_poke_data[mon]["spd"]
|
||||
data[address + 5] = self.local_poke_data[mon]["spc"]
|
||||
data[address + 6] = poke_data.type_ids[self.local_poke_data[mon]["type1"]]
|
||||
data[address + 7] = poke_data.type_ids[self.local_poke_data[mon]["type2"]]
|
||||
data[address + 8] = self.local_poke_data[mon]["catch rate"]
|
||||
data[address + 15] = poke_data.moves[self.local_poke_data[mon]["start move 1"]]["id"]
|
||||
data[address + 16] = poke_data.moves[self.local_poke_data[mon]["start move 2"]]["id"]
|
||||
data[address + 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"]
|
||||
|
||||
data[rom_addresses["Option_Aide_Rt2"]] = self.world.oaks_aide_rt_2[self.player]
|
||||
data[rom_addresses["Option_Aide_Rt11"]] = self.world.oaks_aide_rt_11[self.player]
|
||||
data[rom_addresses["Option_Aide_Rt15"]] = self.world.oaks_aide_rt_15[self.player]
|
||||
|
||||
if self.world.safari_zone_normal_battles[self.player].value == 1:
|
||||
data[rom_addresses["Option_Safari_Zone_Battle_Type"]] = 255
|
||||
|
||||
if self.world.reusable_tms[self.player].value:
|
||||
data[rom_addresses["Option_Reusable_TMs"]] = 0xC9
|
||||
|
||||
process_trainer_data(self, data)
|
||||
|
||||
mons = [mon["id"] for mon in poke_data.pokemon_data.values()]
|
||||
random.shuffle(mons)
|
||||
data[rom_addresses['Title_Mon_First']] = mons.pop()
|
||||
for mon in range(0, 16):
|
||||
data[rom_addresses['Title_Mons'] + mon] = mons.pop()
|
||||
if self.world.game_version[self.player].value:
|
||||
mons.sort(key=lambda mon: 0 if mon == self.world.get_location("Pallet Town - Starter 1", self.player).item.name
|
||||
else 1 if mon == self.world.get_location("Pallet Town - Starter 2", self.player).item.name else
|
||||
2 if mon == self.world.get_location("Pallet Town - Starter 3", self.player).item.name else 3)
|
||||
else:
|
||||
mons.sort(key=lambda mon: 0 if mon == self.world.get_location("Pallet Town - Starter 2", self.player).item.name
|
||||
else 1 if mon == self.world.get_location("Pallet Town - Starter 1", self.player).item.name else
|
||||
2 if mon == self.world.get_location("Pallet Town - Starter 3", self.player).item.name else 3)
|
||||
write_bytes(data, encode_text(self.world.seed_name, 20, True), rom_addresses['Title_Seed'])
|
||||
|
||||
slot_name = self.world.player_name[self.player]
|
||||
slot_name.replace("@", " ")
|
||||
slot_name.replace("<", " ")
|
||||
slot_name.replace(">", " ")
|
||||
write_bytes(data, encode_text(slot_name, 16, True, True), rom_addresses['Title_Slot_Name'])
|
||||
|
||||
write_bytes(data, self.trainer_name, rom_addresses['Player_Name'])
|
||||
write_bytes(data, self.rival_name, rom_addresses['Rival_Name'])
|
||||
|
||||
write_bytes(data, basemd5.digest(), 0xFFCC)
|
||||
write_bytes(data, self.world.seed_name.encode(), 0xFFDC)
|
||||
write_bytes(data, self.world.player_name[self.player].encode(), 0xFFF0)
|
||||
|
||||
|
||||
|
||||
outfilepname = f'_P{self.player}'
|
||||
outfilepname += f"_{self.world.get_file_safe_player_name(self.player).replace(' ', '_')}" \
|
||||
if self.world.player_name[self.player] != 'Player%d' % self.player else ''
|
||||
rompath = os.path.join(output_directory, f'AP_{self.world.seed_name}{outfilepname}.gb')
|
||||
with open(rompath, 'wb') as outfile:
|
||||
outfile.write(data)
|
||||
if self.world.game_version[self.player].current_key == "red":
|
||||
patch = RedDeltaPatch(os.path.splitext(rompath)[0] + RedDeltaPatch.patch_file_ending, player=self.player,
|
||||
player_name=self.world.player_name[self.player], patched_path=rompath)
|
||||
else:
|
||||
patch = BlueDeltaPatch(os.path.splitext(rompath)[0] + BlueDeltaPatch.patch_file_ending, player=self.player,
|
||||
player_name=self.world.player_name[self.player], patched_path=rompath)
|
||||
|
||||
patch.write()
|
||||
os.unlink(rompath)
|
||||
|
||||
|
||||
def write_bytes(data, byte_array, address):
|
||||
for byte in byte_array:
|
||||
data[address] = byte
|
||||
address += 1
|
||||
|
||||
|
||||
def get_base_rom_bytes(game_version: str, hash: str="") -> bytes:
|
||||
file_name = get_base_rom_path(game_version)
|
||||
with open(file_name, "rb") as file:
|
||||
base_rom_bytes = bytes(file.read())
|
||||
if hash:
|
||||
basemd5 = hashlib.md5()
|
||||
basemd5.update(base_rom_bytes)
|
||||
if hash != basemd5.hexdigest():
|
||||
raise Exception('Supplied Base Rom does not match known MD5 for US(1.0) release. '
|
||||
'Get the correct game and version, then dump it')
|
||||
with open(os.path.join(os.path.dirname(__file__), f'basepatch_{game_version}.bsdiff4'), 'rb') as stream:
|
||||
base_patch = bytes(stream.read())
|
||||
base_rom_bytes = bsdiff4.patch(base_rom_bytes, base_patch)
|
||||
return base_rom_bytes
|
||||
|
||||
|
||||
def get_base_rom_path(game_version: str) -> str:
|
||||
options = Utils.get_options()
|
||||
file_name = options["pokemon_rb_options"][f"{game_version}_rom_file"]
|
||||
if not os.path.exists(file_name):
|
||||
file_name = Utils.local_path(file_name)
|
||||
return file_name
|
||||
|
||||
|
||||
class BlueDeltaPatch(APDeltaPatch):
|
||||
patch_file_ending = ".apblue"
|
||||
hash = "50927e843568814f7ed45ec4f944bd8b"
|
||||
game_version = "blue"
|
||||
game = "Pokemon Red and Blue"
|
||||
result_file_ending = ".gb"
|
||||
@classmethod
|
||||
def get_source_data(cls) -> bytes:
|
||||
return get_base_rom_bytes(cls.game_version, cls.hash)
|
||||
|
||||
|
||||
class RedDeltaPatch(APDeltaPatch):
|
||||
patch_file_ending = ".apred"
|
||||
hash = "3d45c1ee9abd5738df46d2bdda8b57dc"
|
||||
game_version = "red"
|
||||
game = "Pokemon Red and Blue"
|
||||
result_file_ending = ".gb"
|
||||
@classmethod
|
||||
def get_source_data(cls) -> bytes:
|
||||
return get_base_rom_bytes(cls.game_version, cls.hash)
|
|
@ -0,0 +1,588 @@
|
|||
rom_addresses = {
|
||||
"Option_Encounter_Minimum_Steps": 0x3c3,
|
||||
"Option_Blind_Trainers": 0x317e,
|
||||
"Base_Stats_Mew": 0x425b,
|
||||
"Title_Mon_First": 0x436e,
|
||||
"Title_Mons": 0x4547,
|
||||
"Player_Name": 0x4569,
|
||||
"Rival_Name": 0x4571,
|
||||
"Title_Seed": 0x5dfe,
|
||||
"Title_Slot_Name": 0x5e1e,
|
||||
"PC_Item": 0x61ec,
|
||||
"PC_Item_Quantity": 0x61f1,
|
||||
"Options": 0x61f9,
|
||||
"Fly_Location": 0x61fe,
|
||||
"Option_Old_Man": 0xcaef,
|
||||
"Option_Old_Man_Lying": 0xcaf2,
|
||||
"Option_Boulders": 0xcd98,
|
||||
"Option_Rock_Tunnel_Extra_Items": 0xcda1,
|
||||
"Wild_Route1": 0xd0fb,
|
||||
"Wild_Route2": 0xd111,
|
||||
"Wild_Route22": 0xd127,
|
||||
"Wild_ViridianForest": 0xd13d,
|
||||
"Wild_Route3": 0xd153,
|
||||
"Wild_MtMoon1F": 0xd169,
|
||||
"Wild_MtMoonB1F": 0xd17f,
|
||||
"Wild_MtMoonB2F": 0xd195,
|
||||
"Wild_Route4": 0xd1ab,
|
||||
"Wild_Route24": 0xd1c1,
|
||||
"Wild_Route25": 0xd1d7,
|
||||
"Wild_Route9": 0xd1ed,
|
||||
"Wild_Route5": 0xd203,
|
||||
"Wild_Route6": 0xd219,
|
||||
"Wild_Route11": 0xd22f,
|
||||
"Wild_RockTunnel1F": 0xd245,
|
||||
"Wild_RockTunnelB1F": 0xd25b,
|
||||
"Wild_Route10": 0xd271,
|
||||
"Wild_Route12": 0xd287,
|
||||
"Wild_Route8": 0xd29d,
|
||||
"Wild_Route7": 0xd2b3,
|
||||
"Wild_PokemonTower3F": 0xd2cd,
|
||||
"Wild_PokemonTower4F": 0xd2e3,
|
||||
"Wild_PokemonTower5F": 0xd2f9,
|
||||
"Wild_PokemonTower6F": 0xd30f,
|
||||
"Wild_PokemonTower7F": 0xd325,
|
||||
"Wild_Route13": 0xd33b,
|
||||
"Wild_Route14": 0xd351,
|
||||
"Wild_Route15": 0xd367,
|
||||
"Wild_Route16": 0xd37d,
|
||||
"Wild_Route17": 0xd393,
|
||||
"Wild_Route18": 0xd3a9,
|
||||
"Wild_SafariZoneCenter": 0xd3bf,
|
||||
"Wild_SafariZoneEast": 0xd3d5,
|
||||
"Wild_SafariZoneNorth": 0xd3eb,
|
||||
"Wild_SafariZoneWest": 0xd401,
|
||||
"Wild_SeaRoutes": 0xd418,
|
||||
"Wild_SeafoamIslands1F": 0xd42d,
|
||||
"Wild_SeafoamIslandsB1F": 0xd443,
|
||||
"Wild_SeafoamIslandsB2F": 0xd459,
|
||||
"Wild_SeafoamIslandsB3F": 0xd46f,
|
||||
"Wild_SeafoamIslandsB4F": 0xd485,
|
||||
"Wild_PokemonMansion1F": 0xd49b,
|
||||
"Wild_PokemonMansion2F": 0xd4b1,
|
||||
"Wild_PokemonMansion3F": 0xd4c7,
|
||||
"Wild_PokemonMansionB1F": 0xd4dd,
|
||||
"Wild_Route21": 0xd4f3,
|
||||
"Wild_Surf_Route21": 0xd508,
|
||||
"Wild_CeruleanCave1F": 0xd51d,
|
||||
"Wild_CeruleanCave2F": 0xd533,
|
||||
"Wild_CeruleanCaveB1F": 0xd549,
|
||||
"Wild_PowerPlant": 0xd55f,
|
||||
"Wild_Route23": 0xd575,
|
||||
"Wild_VictoryRoad2F": 0xd58b,
|
||||
"Wild_VictoryRoad3F": 0xd5a1,
|
||||
"Wild_VictoryRoad1F": 0xd5b7,
|
||||
"Wild_DiglettsCave": 0xd5cd,
|
||||
"Ghost_Battle5": 0xd723,
|
||||
"HM_Surf_Badge_a": 0xda11,
|
||||
"HM_Surf_Badge_b": 0xda16,
|
||||
"Wild_Old_Rod": 0xe313,
|
||||
"Wild_Good_Rod": 0xe340,
|
||||
"Option_Reusable_TMs": 0xe60c,
|
||||
"Wild_Super_Rod_A": 0xea40,
|
||||
"Wild_Super_Rod_B": 0xea45,
|
||||
"Wild_Super_Rod_C": 0xea4a,
|
||||
"Wild_Super_Rod_D": 0xea51,
|
||||
"Wild_Super_Rod_E": 0xea56,
|
||||
"Wild_Super_Rod_F": 0xea5b,
|
||||
"Wild_Super_Rod_G": 0xea64,
|
||||
"Wild_Super_Rod_H": 0xea6d,
|
||||
"Wild_Super_Rod_I": 0xea76,
|
||||
"Wild_Super_Rod_J": 0xea7f,
|
||||
"Starting_Money_High": 0xf949,
|
||||
"Starting_Money_Middle": 0xf94c,
|
||||
"Starting_Money_Low": 0xf94f,
|
||||
"HM_Fly_Badge_a": 0x1318e,
|
||||
"HM_Fly_Badge_b": 0x13193,
|
||||
"HM_Cut_Badge_a": 0x131c4,
|
||||
"HM_Cut_Badge_b": 0x131c9,
|
||||
"HM_Strength_Badge_a": 0x131f4,
|
||||
"HM_Strength_Badge_b": 0x131f9,
|
||||
"HM_Flash_Badge_a": 0x13208,
|
||||
"HM_Flash_Badge_b": 0x1320d,
|
||||
"Encounter_Chances": 0x13911,
|
||||
"Option_Viridian_Gym_Badges": 0x1901d,
|
||||
"Event_Sleepy_Guy": 0x191bc,
|
||||
"Starter2_K": 0x195a8,
|
||||
"Starter3_K": 0x195b0,
|
||||
"Event_Rocket_Thief": 0x196cc,
|
||||
"Option_Cerulean_Cave_Condition": 0x1986c,
|
||||
"Event_Stranded_Man": 0x19b2b,
|
||||
"Event_Rivals_Sister": 0x19cf9,
|
||||
"Option_Pokemon_League_Badges": 0x19e16,
|
||||
"Missable_Silph_Co_4F_Item_1": 0x1a0d7,
|
||||
"Missable_Silph_Co_4F_Item_2": 0x1a0de,
|
||||
"Missable_Silph_Co_4F_Item_3": 0x1a0e5,
|
||||
"Missable_Silph_Co_5F_Item_1": 0x1a337,
|
||||
"Missable_Silph_Co_5F_Item_2": 0x1a33e,
|
||||
"Missable_Silph_Co_5F_Item_3": 0x1a345,
|
||||
"Missable_Silph_Co_6F_Item_1": 0x1a5ad,
|
||||
"Missable_Silph_Co_6F_Item_2": 0x1a5b4,
|
||||
"Event_Free_Sample": 0x1cade,
|
||||
"Starter1_F": 0x1cca5,
|
||||
"Starter2_F": 0x1cca9,
|
||||
"Starter2_G": 0x1cde2,
|
||||
"Starter3_G": 0x1cdea,
|
||||
"Starter2_H": 0x1d0e5,
|
||||
"Starter1_H": 0x1d0ef,
|
||||
"Starter3_I": 0x1d0f6,
|
||||
"Starter2_I": 0x1d100,
|
||||
"Starter1_D": 0x1d107,
|
||||
"Starter3_D": 0x1d111,
|
||||
"Starter2_E": 0x1d2eb,
|
||||
"Starter3_E": 0x1d2f3,
|
||||
"Event_Oaks_Gift": 0x1d373,
|
||||
"Event_Pokemart_Quest": 0x1d566,
|
||||
"Event_Bicycle_Shop": 0x1d805,
|
||||
"Text_Bicycle": 0x1d898,
|
||||
"Event_Fuji": 0x1d9cd,
|
||||
"Static_Encounter_Mew": 0x1dc4e,
|
||||
"Gift_Eevee": 0x1dcc7,
|
||||
"Event_Mr_Psychic": 0x1ddcf,
|
||||
"Static_Encounter_Voltorb_A": 0x1e397,
|
||||
"Static_Encounter_Voltorb_B": 0x1e39f,
|
||||
"Static_Encounter_Voltorb_C": 0x1e3a7,
|
||||
"Static_Encounter_Electrode_A": 0x1e3af,
|
||||
"Static_Encounter_Voltorb_D": 0x1e3b7,
|
||||
"Static_Encounter_Voltorb_E": 0x1e3bf,
|
||||
"Static_Encounter_Electrode_B": 0x1e3c7,
|
||||
"Static_Encounter_Voltorb_F": 0x1e3cf,
|
||||
"Static_Encounter_Zapdos": 0x1e3d7,
|
||||
"Missable_Power_Plant_Item_1": 0x1e3df,
|
||||
"Missable_Power_Plant_Item_2": 0x1e3e6,
|
||||
"Missable_Power_Plant_Item_3": 0x1e3ed,
|
||||
"Missable_Power_Plant_Item_4": 0x1e3f4,
|
||||
"Missable_Power_Plant_Item_5": 0x1e3fb,
|
||||
"Event_Rt16_House_Woman": 0x1e5d4,
|
||||
"Option_Victory_Road_Badges": 0x1e6a5,
|
||||
"Event_Bill": 0x1e8d6,
|
||||
"Starter1_O": 0x372b0,
|
||||
"Starter2_O": 0x372b4,
|
||||
"Starter3_O": 0x372b8,
|
||||
"Base_Stats": 0x383de,
|
||||
"Starter3_C": 0x39cf2,
|
||||
"Starter1_C": 0x39cf8,
|
||||
"Trainer_Data": 0x39d99,
|
||||
"Rival_Starter2_A": 0x3a1e5,
|
||||
"Rival_Starter3_A": 0x3a1e8,
|
||||
"Rival_Starter1_A": 0x3a1eb,
|
||||
"Rival_Starter2_B": 0x3a1f1,
|
||||
"Rival_Starter3_B": 0x3a1f7,
|
||||
"Rival_Starter1_B": 0x3a1fd,
|
||||
"Rival_Starter2_C": 0x3a207,
|
||||
"Rival_Starter3_C": 0x3a211,
|
||||
"Rival_Starter1_C": 0x3a21b,
|
||||
"Rival_Starter2_D": 0x3a409,
|
||||
"Rival_Starter3_D": 0x3a413,
|
||||
"Rival_Starter1_D": 0x3a41d,
|
||||
"Rival_Starter2_E": 0x3a429,
|
||||
"Rival_Starter3_E": 0x3a435,
|
||||
"Rival_Starter1_E": 0x3a441,
|
||||
"Rival_Starter2_F": 0x3a44d,
|
||||
"Rival_Starter3_F": 0x3a459,
|
||||
"Rival_Starter1_F": 0x3a465,
|
||||
"Rival_Starter2_G": 0x3a473,
|
||||
"Rival_Starter3_G": 0x3a481,
|
||||
"Rival_Starter1_G": 0x3a48f,
|
||||
"Rival_Starter2_H": 0x3a49d,
|
||||
"Rival_Starter3_H": 0x3a4ab,
|
||||
"Rival_Starter1_H": 0x3a4b9,
|
||||
"Trainer_Data_End": 0x3a52e,
|
||||
"Learnset_Rhydon": 0x3b1d9,
|
||||
"Learnset_Kangaskhan": 0x3b1e7,
|
||||
"Learnset_NidoranM": 0x3b1f6,
|
||||
"Learnset_Clefairy": 0x3b208,
|
||||
"Learnset_Spearow": 0x3b219,
|
||||
"Learnset_Voltorb": 0x3b228,
|
||||
"Learnset_Nidoking": 0x3b234,
|
||||
"Learnset_Slowbro": 0x3b23c,
|
||||
"Learnset_Ivysaur": 0x3b24f,
|
||||
"Learnset_Exeggutor": 0x3b25f,
|
||||
"Learnset_Lickitung": 0x3b263,
|
||||
"Learnset_Exeggcute": 0x3b273,
|
||||
"Learnset_Grimer": 0x3b284,
|
||||
"Learnset_Gengar": 0x3b292,
|
||||
"Learnset_NidoranF": 0x3b29b,
|
||||
"Learnset_Nidoqueen": 0x3b2a9,
|
||||
"Learnset_Cubone": 0x3b2b4,
|
||||
"Learnset_Rhyhorn": 0x3b2c3,
|
||||
"Learnset_Lapras": 0x3b2d1,
|
||||
"Learnset_Mew": 0x3b2e1,
|
||||
"Learnset_Gyarados": 0x3b2eb,
|
||||
"Learnset_Shellder": 0x3b2fb,
|
||||
"Learnset_Tentacool": 0x3b30a,
|
||||
"Learnset_Gastly": 0x3b31f,
|
||||
"Learnset_Scyther": 0x3b325,
|
||||
"Learnset_Staryu": 0x3b337,
|
||||
"Learnset_Blastoise": 0x3b347,
|
||||
"Learnset_Pinsir": 0x3b355,
|
||||
"Learnset_Tangela": 0x3b363,
|
||||
"Learnset_Growlithe": 0x3b379,
|
||||
"Learnset_Onix": 0x3b385,
|
||||
"Learnset_Fearow": 0x3b391,
|
||||
"Learnset_Pidgey": 0x3b3a0,
|
||||
"Learnset_Slowpoke": 0x3b3b1,
|
||||
"Learnset_Kadabra": 0x3b3c9,
|
||||
"Learnset_Graveler": 0x3b3e1,
|
||||
"Learnset_Chansey": 0x3b3ef,
|
||||
"Learnset_Machoke": 0x3b407,
|
||||
"Learnset_MrMime": 0x3b413,
|
||||
"Learnset_Hitmonlee": 0x3b41f,
|
||||
"Learnset_Hitmonchan": 0x3b42b,
|
||||
"Learnset_Arbok": 0x3b437,
|
||||
"Learnset_Parasect": 0x3b443,
|
||||
"Learnset_Psyduck": 0x3b452,
|
||||
"Learnset_Drowzee": 0x3b461,
|
||||
"Learnset_Golem": 0x3b46f,
|
||||
"Learnset_Magmar": 0x3b47f,
|
||||
"Learnset_Electabuzz": 0x3b48f,
|
||||
"Learnset_Magneton": 0x3b49b,
|
||||
"Learnset_Koffing": 0x3b4ac,
|
||||
"Learnset_Mankey": 0x3b4bd,
|
||||
"Learnset_Seel": 0x3b4cc,
|
||||
"Learnset_Diglett": 0x3b4db,
|
||||
"Learnset_Tauros": 0x3b4e7,
|
||||
"Learnset_Farfetchd": 0x3b4f9,
|
||||
"Learnset_Venonat": 0x3b508,
|
||||
"Learnset_Dragonite": 0x3b516,
|
||||
"Learnset_Doduo": 0x3b52b,
|
||||
"Learnset_Poliwag": 0x3b53c,
|
||||
"Learnset_Jynx": 0x3b54a,
|
||||
"Learnset_Moltres": 0x3b558,
|
||||
"Learnset_Articuno": 0x3b560,
|
||||
"Learnset_Zapdos": 0x3b568,
|
||||
"Learnset_Meowth": 0x3b575,
|
||||
"Learnset_Krabby": 0x3b584,
|
||||
"Learnset_Vulpix": 0x3b59a,
|
||||
"Learnset_Pikachu": 0x3b5ac,
|
||||
"Learnset_Dratini": 0x3b5c1,
|
||||
"Learnset_Dragonair": 0x3b5d0,
|
||||
"Learnset_Kabuto": 0x3b5df,
|
||||
"Learnset_Kabutops": 0x3b5e9,
|
||||
"Learnset_Horsea": 0x3b5f6,
|
||||
"Learnset_Seadra": 0x3b602,
|
||||
"Learnset_Sandshrew": 0x3b615,
|
||||
"Learnset_Sandslash": 0x3b621,
|
||||
"Learnset_Omanyte": 0x3b630,
|
||||
"Learnset_Omastar": 0x3b63a,
|
||||
"Learnset_Jigglypuff": 0x3b648,
|
||||
"Learnset_Eevee": 0x3b666,
|
||||
"Learnset_Flareon": 0x3b670,
|
||||
"Learnset_Jolteon": 0x3b682,
|
||||
"Learnset_Vaporeon": 0x3b694,
|
||||
"Learnset_Machop": 0x3b6a9,
|
||||
"Learnset_Zubat": 0x3b6b8,
|
||||
"Learnset_Ekans": 0x3b6c7,
|
||||
"Learnset_Paras": 0x3b6d6,
|
||||
"Learnset_Poliwhirl": 0x3b6e6,
|
||||
"Learnset_Poliwrath": 0x3b6f4,
|
||||
"Learnset_Beedrill": 0x3b704,
|
||||
"Learnset_Dodrio": 0x3b714,
|
||||
"Learnset_Primeape": 0x3b722,
|
||||
"Learnset_Dugtrio": 0x3b72e,
|
||||
"Learnset_Venomoth": 0x3b73a,
|
||||
"Learnset_Dewgong": 0x3b748,
|
||||
"Learnset_Butterfree": 0x3b762,
|
||||
"Learnset_Machamp": 0x3b772,
|
||||
"Learnset_Golduck": 0x3b780,
|
||||
"Learnset_Hypno": 0x3b78c,
|
||||
"Learnset_Golbat": 0x3b79a,
|
||||
"Learnset_Mewtwo": 0x3b7a6,
|
||||
"Learnset_Snorlax": 0x3b7b2,
|
||||
"Learnset_Magikarp": 0x3b7bf,
|
||||
"Learnset_Muk": 0x3b7c7,
|
||||
"Learnset_Kingler": 0x3b7d7,
|
||||
"Learnset_Cloyster": 0x3b7e3,
|
||||
"Learnset_Electrode": 0x3b7e9,
|
||||
"Learnset_Weezing": 0x3b7f7,
|
||||
"Learnset_Persian": 0x3b803,
|
||||
"Learnset_Marowak": 0x3b80f,
|
||||
"Learnset_Haunter": 0x3b827,
|
||||
"Learnset_Alakazam": 0x3b832,
|
||||
"Learnset_Pidgeotto": 0x3b843,
|
||||
"Learnset_Pidgeot": 0x3b851,
|
||||
"Learnset_Bulbasaur": 0x3b864,
|
||||
"Learnset_Venusaur": 0x3b874,
|
||||
"Learnset_Tentacruel": 0x3b884,
|
||||
"Learnset_Goldeen": 0x3b89b,
|
||||
"Learnset_Seaking": 0x3b8a9,
|
||||
"Learnset_Ponyta": 0x3b8c2,
|
||||
"Learnset_Rapidash": 0x3b8d0,
|
||||
"Learnset_Rattata": 0x3b8e1,
|
||||
"Learnset_Raticate": 0x3b8eb,
|
||||
"Learnset_Nidorino": 0x3b8f9,
|
||||
"Learnset_Nidorina": 0x3b90b,
|
||||
"Learnset_Geodude": 0x3b91c,
|
||||
"Learnset_Porygon": 0x3b92a,
|
||||
"Learnset_Aerodactyl": 0x3b934,
|
||||
"Learnset_Magnemite": 0x3b942,
|
||||
"Learnset_Charmander": 0x3b957,
|
||||
"Learnset_Squirtle": 0x3b968,
|
||||
"Learnset_Charmeleon": 0x3b979,
|
||||
"Learnset_Wartortle": 0x3b98a,
|
||||
"Learnset_Charizard": 0x3b998,
|
||||
"Learnset_Oddish": 0x3b9b1,
|
||||
"Learnset_Gloom": 0x3b9c3,
|
||||
"Learnset_Vileplume": 0x3b9d1,
|
||||
"Learnset_Bellsprout": 0x3b9dc,
|
||||
"Learnset_Weepinbell": 0x3b9f0,
|
||||
"Learnset_Victreebel": 0x3ba00,
|
||||
"Type_Chart": 0x3e4b6,
|
||||
"Type_Chart_Divider": 0x3e5ac,
|
||||
"Ghost_Battle3": 0x3efd9,
|
||||
"Missable_Pokemon_Mansion_1F_Item_1": 0x443d6,
|
||||
"Missable_Pokemon_Mansion_1F_Item_2": 0x443dd,
|
||||
"Map_Rock_TunnelF": 0x44676,
|
||||
"Missable_Victory_Road_3F_Item_1": 0x44b07,
|
||||
"Missable_Victory_Road_3F_Item_2": 0x44b0e,
|
||||
"Missable_Rocket_Hideout_B1F_Item_1": 0x44d2d,
|
||||
"Missable_Rocket_Hideout_B1F_Item_2": 0x44d34,
|
||||
"Missable_Rocket_Hideout_B2F_Item_1": 0x4511d,
|
||||
"Missable_Rocket_Hideout_B2F_Item_2": 0x45124,
|
||||
"Missable_Rocket_Hideout_B2F_Item_3": 0x4512b,
|
||||
"Missable_Rocket_Hideout_B2F_Item_4": 0x45132,
|
||||
"Missable_Rocket_Hideout_B3F_Item_1": 0x4536f,
|
||||
"Missable_Rocket_Hideout_B3F_Item_2": 0x45376,
|
||||
"Missable_Rocket_Hideout_B4F_Item_1": 0x45627,
|
||||
"Missable_Rocket_Hideout_B4F_Item_2": 0x4562e,
|
||||
"Missable_Rocket_Hideout_B4F_Item_3": 0x45635,
|
||||
"Missable_Rocket_Hideout_B4F_Item_4": 0x4563c,
|
||||
"Missable_Rocket_Hideout_B4F_Item_5": 0x45643,
|
||||
"Missable_Safari_Zone_East_Item_1": 0x458b2,
|
||||
"Missable_Safari_Zone_East_Item_2": 0x458b9,
|
||||
"Missable_Safari_Zone_East_Item_3": 0x458c0,
|
||||
"Missable_Safari_Zone_East_Item_4": 0x458c7,
|
||||
"Missable_Safari_Zone_North_Item_1": 0x45a12,
|
||||
"Missable_Safari_Zone_North_Item_2": 0x45a19,
|
||||
"Missable_Safari_Zone_Center_Item": 0x45bf9,
|
||||
"Missable_Cerulean_Cave_2F_Item_1": 0x45e36,
|
||||
"Missable_Cerulean_Cave_2F_Item_2": 0x45e3d,
|
||||
"Missable_Cerulean_Cave_2F_Item_3": 0x45e44,
|
||||
"Static_Encounter_Mewtwo": 0x45f44,
|
||||
"Missable_Cerulean_Cave_B1F_Item_1": 0x45f4c,
|
||||
"Missable_Cerulean_Cave_B1F_Item_2": 0x45f53,
|
||||
"Missable_Rock_Tunnel_B1F_Item_1": 0x4619f,
|
||||
"Missable_Rock_Tunnel_B1F_Item_2": 0x461a6,
|
||||
"Missable_Rock_Tunnel_B1F_Item_3": 0x461ad,
|
||||
"Missable_Rock_Tunnel_B1F_Item_4": 0x461b4,
|
||||
"Static_Encounter_Articuno": 0x4690c,
|
||||
"Hidden_Item_Viridian_Forest_1": 0x46e6d,
|
||||
"Hidden_Item_Viridian_Forest_2": 0x46e73,
|
||||
"Hidden_Item_MtMoonB2F_1": 0x46e7a,
|
||||
"Hidden_Item_MtMoonB2F_2": 0x46e80,
|
||||
"Hidden_Item_Route_25_1": 0x46e94,
|
||||
"Hidden_Item_Route_25_2": 0x46e9a,
|
||||
"Hidden_Item_Route_9": 0x46ea1,
|
||||
"Hidden_Item_SS_Anne_Kitchen": 0x46eb4,
|
||||
"Hidden_Item_SS_Anne_B1F": 0x46ebb,
|
||||
"Hidden_Item_Route_10_1": 0x46ec2,
|
||||
"Hidden_Item_Route_10_2": 0x46ec8,
|
||||
"Hidden_Item_Rocket_Hideout_B1F": 0x46ecf,
|
||||
"Hidden_Item_Rocket_Hideout_B3F": 0x46ed6,
|
||||
"Hidden_Item_Rocket_Hideout_B4F": 0x46edd,
|
||||
"Hidden_Item_Pokemon_Tower_5F": 0x46ef1,
|
||||
"Hidden_Item_Route_13_1": 0x46ef8,
|
||||
"Hidden_Item_Route_13_2": 0x46efe,
|
||||
"Hidden_Item_Safari_Zone_West": 0x46f0c,
|
||||
"Hidden_Item_Silph_Co_5F": 0x46f13,
|
||||
"Hidden_Item_Silph_Co_9F": 0x46f1a,
|
||||
"Hidden_Item_Copycats_House": 0x46f21,
|
||||
"Hidden_Item_Cerulean_Cave_1F": 0x46f28,
|
||||
"Hidden_Item_Cerulean_Cave_B1F": 0x46f2f,
|
||||
"Hidden_Item_Power_Plant_1": 0x46f36,
|
||||
"Hidden_Item_Power_Plant_2": 0x46f3c,
|
||||
"Hidden_Item_Seafoam_Islands_B2F": 0x46f43,
|
||||
"Hidden_Item_Seafoam_Islands_B4F": 0x46f4a,
|
||||
"Hidden_Item_Pokemon_Mansion_1F": 0x46f51,
|
||||
"Hidden_Item_Pokemon_Mansion_3F": 0x46f65,
|
||||
"Hidden_Item_Pokemon_Mansion_B1F": 0x46f72,
|
||||
"Hidden_Item_Route_23_1": 0x46f85,
|
||||
"Hidden_Item_Route_23_2": 0x46f8b,
|
||||
"Hidden_Item_Route_23_3": 0x46f91,
|
||||
"Hidden_Item_Victory_Road_2F_1": 0x46f98,
|
||||
"Hidden_Item_Victory_Road_2F_2": 0x46f9e,
|
||||
"Hidden_Item_Unused_6F": 0x46fa5,
|
||||
"Hidden_Item_Viridian_City": 0x46fb3,
|
||||
"Hidden_Item_Route_11": 0x47060,
|
||||
"Hidden_Item_Route_12": 0x47067,
|
||||
"Hidden_Item_Route_17_1": 0x47075,
|
||||
"Hidden_Item_Route_17_2": 0x4707b,
|
||||
"Hidden_Item_Route_17_3": 0x47081,
|
||||
"Hidden_Item_Route_17_4": 0x47087,
|
||||
"Hidden_Item_Route_17_5": 0x4708d,
|
||||
"Hidden_Item_Underground_Path_NS_1": 0x47094,
|
||||
"Hidden_Item_Underground_Path_NS_2": 0x4709a,
|
||||
"Hidden_Item_Underground_Path_WE_1": 0x470a1,
|
||||
"Hidden_Item_Underground_Path_WE_2": 0x470a7,
|
||||
"Hidden_Item_Celadon_City": 0x470ae,
|
||||
"Hidden_Item_Seafoam_Islands_B3F": 0x470b5,
|
||||
"Hidden_Item_Vermilion_City": 0x470bc,
|
||||
"Hidden_Item_Cerulean_City": 0x470c3,
|
||||
"Hidden_Item_Route_4": 0x470ca,
|
||||
"Event_Counter": 0x482d3,
|
||||
"Event_Thirsty_Girl_Lemonade": 0x484f9,
|
||||
"Event_Thirsty_Girl_Soda": 0x4851d,
|
||||
"Event_Thirsty_Girl_Water": 0x48541,
|
||||
"Option_Tea": 0x4871d,
|
||||
"Event_Mansion_Lady": 0x4872a,
|
||||
"Badge_Celadon_Gym": 0x48a1b,
|
||||
"Event_Celadon_Gym": 0x48a2f,
|
||||
"Event_Gambling_Addict": 0x49293,
|
||||
"Gift_Magikarp": 0x49430,
|
||||
"Option_Aide_Rt11": 0x4958d,
|
||||
"Event_Rt11_Oaks_Aide": 0x49591,
|
||||
"Event_Mourning_Girl": 0x4968b,
|
||||
"Option_Aide_Rt15": 0x49776,
|
||||
"Event_Rt_15_Oaks_Aide": 0x4977a,
|
||||
"Missable_Mt_Moon_1F_Item_1": 0x49c75,
|
||||
"Missable_Mt_Moon_1F_Item_2": 0x49c7c,
|
||||
"Missable_Mt_Moon_1F_Item_3": 0x49c83,
|
||||
"Missable_Mt_Moon_1F_Item_4": 0x49c8a,
|
||||
"Missable_Mt_Moon_1F_Item_5": 0x49c91,
|
||||
"Missable_Mt_Moon_1F_Item_6": 0x49c98,
|
||||
"Dome_Fossil_Text": 0x4a001,
|
||||
"Event_Dome_Fossil": 0x4a021,
|
||||
"Helix_Fossil_Text": 0x4a05d,
|
||||
"Event_Helix_Fossil": 0x4a07d,
|
||||
"Missable_Mt_Moon_B2F_Item_1": 0x4a166,
|
||||
"Missable_Mt_Moon_B2F_Item_2": 0x4a16d,
|
||||
"Missable_Safari_Zone_West_Item_1": 0x4a34f,
|
||||
"Missable_Safari_Zone_West_Item_2": 0x4a356,
|
||||
"Missable_Safari_Zone_West_Item_3": 0x4a35d,
|
||||
"Missable_Safari_Zone_West_Item_4": 0x4a364,
|
||||
"Event_Safari_Zone_Secret_House": 0x4a469,
|
||||
"Missable_Route_24_Item": 0x506e6,
|
||||
"Missable_Route_25_Item": 0x5080b,
|
||||
"Starter2_B": 0x50fce,
|
||||
"Starter3_B": 0x50fd0,
|
||||
"Starter1_B": 0x50fd2,
|
||||
"Starter2_A": 0x510f1,
|
||||
"Starter3_A": 0x510f3,
|
||||
"Starter1_A": 0x510f5,
|
||||
"Option_Badge_Goal": 0x51317,
|
||||
"Event_Nugget_Bridge": 0x5148f,
|
||||
"Static_Encounter_Moltres": 0x51939,
|
||||
"Missable_Victory_Road_2F_Item_1": 0x51941,
|
||||
"Missable_Victory_Road_2F_Item_2": 0x51948,
|
||||
"Missable_Victory_Road_2F_Item_3": 0x5194f,
|
||||
"Missable_Victory_Road_2F_Item_4": 0x51956,
|
||||
"Starter2_L": 0x51c85,
|
||||
"Starter3_L": 0x51c8d,
|
||||
"Gift_Lapras": 0x51d83,
|
||||
"Missable_Silph_Co_7F_Item_1": 0x51f0d,
|
||||
"Missable_Silph_Co_7F_Item_2": 0x51f14,
|
||||
"Missable_Pokemon_Mansion_2F_Item": 0x520c9,
|
||||
"Missable_Pokemon_Mansion_3F_Item_1": 0x522e2,
|
||||
"Missable_Pokemon_Mansion_3F_Item_2": 0x522e9,
|
||||
"Missable_Pokemon_Mansion_B1F_Item_1": 0x5248c,
|
||||
"Missable_Pokemon_Mansion_B1F_Item_2": 0x52493,
|
||||
"Missable_Pokemon_Mansion_B1F_Item_3": 0x5249a,
|
||||
"Missable_Pokemon_Mansion_B1F_Item_4": 0x524a1,
|
||||
"Missable_Pokemon_Mansion_B1F_Item_5": 0x524ae,
|
||||
"Option_Safari_Zone_Battle_Type": 0x525c3,
|
||||
"Prize_Mon_A2": 0x5282f,
|
||||
"Prize_Mon_B2": 0x52830,
|
||||
"Prize_Mon_C2": 0x52831,
|
||||
"Prize_Mon_D2": 0x5283a,
|
||||
"Prize_Mon_E2": 0x5283b,
|
||||
"Prize_Mon_F2": 0x5283c,
|
||||
"Prize_Mon_A": 0x52960,
|
||||
"Prize_Mon_B": 0x52962,
|
||||
"Prize_Mon_C": 0x52964,
|
||||
"Prize_Mon_D": 0x52966,
|
||||
"Prize_Mon_E": 0x52968,
|
||||
"Prize_Mon_F": 0x5296a,
|
||||
"Missable_Route_2_Item_1": 0x5404a,
|
||||
"Missable_Route_2_Item_2": 0x54051,
|
||||
"Missable_Route_4_Item": 0x543df,
|
||||
"Missable_Route_9_Item": 0x546fd,
|
||||
"Option_EXP_Modifier": 0x552c5,
|
||||
"Rod_Vermilion_City_Fishing_Guru": 0x560df,
|
||||
"Rod_Fuchsia_City_Fishing_Brother": 0x561eb,
|
||||
"Rod_Route12_Fishing_Brother": 0x564ee,
|
||||
"Missable_Route_12_Item_1": 0x58704,
|
||||
"Missable_Route_12_Item_2": 0x5870b,
|
||||
"Missable_Route_15_Item": 0x589c7,
|
||||
"Ghost_Battle6": 0x58df0,
|
||||
"Static_Encounter_Snorlax_A": 0x5969b,
|
||||
"Static_Encounter_Snorlax_B": 0x599db,
|
||||
"Event_Pokemon_Fan_Club": 0x59c8b,
|
||||
"Event_Scared_Woman": 0x59e1f,
|
||||
"Missable_Silph_Co_3F_Item": 0x5a0cb,
|
||||
"Missable_Silph_Co_10F_Item_1": 0x5a281,
|
||||
"Missable_Silph_Co_10F_Item_2": 0x5a288,
|
||||
"Missable_Silph_Co_10F_Item_3": 0x5a28f,
|
||||
"Guard_Drink_List": 0x5a600,
|
||||
"Event_Museum": 0x5c266,
|
||||
"Badge_Pewter_Gym": 0x5c3ed,
|
||||
"Event_Pewter_Gym": 0x5c401,
|
||||
"Badge_Cerulean_Gym": 0x5c716,
|
||||
"Event_Cerulean_Gym": 0x5c72a,
|
||||
"Badge_Vermilion_Gym": 0x5caba,
|
||||
"Event_Vermillion_Gym": 0x5cace,
|
||||
"Event_Copycat": 0x5cca9,
|
||||
"Gift_Hitmonlee": 0x5cf1a,
|
||||
"Gift_Hitmonchan": 0x5cf62,
|
||||
"Badge_Saffron_Gym": 0x5d079,
|
||||
"Event_Saffron_Gym": 0x5d08d,
|
||||
"Option_Aide_Rt2": 0x5d5f2,
|
||||
"Event_Route_2_Oaks_Aide": 0x5d5f6,
|
||||
"Missable_Victory_Road_1F_Item_1": 0x5dae6,
|
||||
"Missable_Victory_Road_1F_Item_2": 0x5daed,
|
||||
"Starter2_J": 0x6060e,
|
||||
"Starter3_J": 0x60616,
|
||||
"Missable_Pokemon_Tower_3F_Item": 0x60787,
|
||||
"Missable_Pokemon_Tower_4F_Item_1": 0x608b5,
|
||||
"Missable_Pokemon_Tower_4F_Item_2": 0x608bc,
|
||||
"Missable_Pokemon_Tower_4F_Item_3": 0x608c3,
|
||||
"Missable_Pokemon_Tower_5F_Item": 0x60a80,
|
||||
"Ghost_Battle1": 0x60b33,
|
||||
"Ghost_Battle2": 0x60c0a,
|
||||
"Missable_Pokemon_Tower_6F_Item_1": 0x60c85,
|
||||
"Missable_Pokemon_Tower_6F_Item_2": 0x60c8c,
|
||||
"Gift_Aerodactyl": 0x61064,
|
||||
"Gift_Omanyte": 0x61068,
|
||||
"Gift_Kabuto": 0x6106c,
|
||||
"Missable_Viridian_Forest_Item_1": 0x6122c,
|
||||
"Missable_Viridian_Forest_Item_2": 0x61233,
|
||||
"Missable_Viridian_Forest_Item_3": 0x6123a,
|
||||
"Starter2_M": 0x61450,
|
||||
"Starter3_M": 0x61458,
|
||||
"Event_SS_Anne_Captain": 0x618c3,
|
||||
"Missable_SS_Anne_1F_Item": 0x61ac0,
|
||||
"Missable_SS_Anne_2F_Item_1": 0x61ced,
|
||||
"Missable_SS_Anne_2F_Item_2": 0x61d00,
|
||||
"Missable_SS_Anne_B1F_Item_1": 0x61ee3,
|
||||
"Missable_SS_Anne_B1F_Item_2": 0x61eea,
|
||||
"Missable_SS_Anne_B1F_Item_3": 0x61ef1,
|
||||
"Event_Silph_Co_President": 0x622ed,
|
||||
"Ghost_Battle4": 0x708e1,
|
||||
"Badge_Viridian_Gym": 0x749ca,
|
||||
"Event_Viridian_Gym": 0x749de,
|
||||
"Missable_Viridian_Gym_Item": 0x74c63,
|
||||
"Missable_Cerulean_Cave_1F_Item_1": 0x74d68,
|
||||
"Missable_Cerulean_Cave_1F_Item_2": 0x74d6f,
|
||||
"Missable_Cerulean_Cave_1F_Item_3": 0x74d76,
|
||||
"Event_Warden": 0x7512a,
|
||||
"Missable_Wardens_House_Item": 0x751b7,
|
||||
"Badge_Fuchsia_Gym": 0x755cd,
|
||||
"Event_Fuschia_Gym": 0x755e1,
|
||||
"Badge_Cinnabar_Gym": 0x75995,
|
||||
"Event_Cinnabar_Gym": 0x759a9,
|
||||
"Event_Lab_Scientist": 0x75dd6,
|
||||
"Fossils_Needed_For_Second_Item": 0x75ea3,
|
||||
"Event_Dome_Fossil_B": 0x75f20,
|
||||
"Event_Helix_Fossil_B": 0x75f40,
|
||||
"Starter2_N": 0x76169,
|
||||
"Starter3_N": 0x76171,
|
||||
"Option_Itemfinder": 0x76864,
|
||||
"Text_Badges_Needed": 0x92304,
|
||||
"Badge_Text_Boulder_Badge": 0x990b3,
|
||||
"Badge_Text_Cascade_Badge": 0x990cb,
|
||||
"Badge_Text_Thunder_Badge": 0x99111,
|
||||
"Badge_Text_Rainbow_Badge": 0x9912e,
|
||||
"Badge_Text_Soul_Badge": 0x99177,
|
||||
"Badge_Text_Marsh_Badge": 0x9918c,
|
||||
"Badge_Text_Volcano_Badge": 0x991d6,
|
||||
"Badge_Text_Earth_Badge": 0x991f3,
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
from ..generic.Rules import add_item_rule, add_rule
|
||||
|
||||
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)
|
||||
|
||||
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_has_pokemon(state.world.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),
|
||||
"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_has_pokemon(state.world.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),
|
||||
"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 - Item": lambda state: state.pokemon_rb_can_cut(player),
|
||||
"Route 25 - Item": lambda state: state.pokemon_rb_can_cut(player),
|
||||
"Fuchsia City - Warden's House Item": lambda state: state.pokemon_rb_can_strength(player),
|
||||
"Rocket Hideout B4F - Southwest Item (Lift Key)": lambda state: state.has("Lift Key", player),
|
||||
"Rocket Hideout B4F - Giovanni Item (Lift Key)": lambda state: state.has("Lift Key", player),
|
||||
"Silph Co 3F - Item (Card Key)": lambda state: state.has("Card Key", player),
|
||||
"Silph Co 4F - Left Item (Card Key)": lambda state: state.has("Card Key", player),
|
||||
"Silph Co 4F - Middle Item (Card Key)": lambda state: state.has("Card Key", player),
|
||||
"Silph Co 4F - Right Item (Card Key)": lambda state: state.has("Card Key", player),
|
||||
"Silph Co 5F - Northwest Item (Card Key)": lambda state: state.has("Card Key", player),
|
||||
"Silph Co 6F - West Item (Card Key)": lambda state: state.has("Card Key", player),
|
||||
"Silph Co 6F - Southwest Item (Card Key)": lambda state: state.has("Card Key", player),
|
||||
"Silph Co 7F - East Item (Card Key)": lambda state: state.has("Card Key", player),
|
||||
"Safari Zone Center - Island Item": lambda state: state.pokemon_rb_can_surf(player),
|
||||
|
||||
"Silph Co 11F - Silph Co Liberated": lambda state: state.has("Card Key", player),
|
||||
|
||||
"Pallet Town - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
|
||||
"Pallet Town - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
|
||||
"Route 22 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
|
||||
"Route 22 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
|
||||
"Route 24 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
|
||||
"Route 24 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
|
||||
"Route 24 - Super Rod Pokemon - 3": lambda state: state.has("Super Rod", player),
|
||||
"Route 6 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
|
||||
"Route 6 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
|
||||
"Route 10 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
|
||||
"Route 10 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
|
||||
"Safari Zone Center - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
|
||||
"Safari Zone Center - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
|
||||
"Safari Zone Center - Super Rod Pokemon - 3": lambda state: state.has("Super Rod", player),
|
||||
"Safari Zone Center - Super Rod Pokemon - 4": lambda state: state.has("Super Rod", player),
|
||||
"Route 12 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
|
||||
"Route 12 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
|
||||
"Route 12 - Super Rod Pokemon - 3": lambda state: state.has("Super Rod", player),
|
||||
"Route 12 - Super Rod Pokemon - 4": lambda state: state.has("Super Rod", player),
|
||||
"Route 19 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
|
||||
"Route 19 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
|
||||
"Route 19 - Super Rod Pokemon - 3": lambda state: state.has("Super Rod", player),
|
||||
"Route 19 - Super Rod Pokemon - 4": lambda state: state.has("Super Rod", player),
|
||||
"Route 23 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
|
||||
"Route 23 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
|
||||
"Route 23 - Super Rod Pokemon - 3": lambda state: state.has("Super Rod", player),
|
||||
"Route 23 - Super Rod Pokemon - 4": lambda state: state.has("Super Rod", player),
|
||||
"Fuchsia City - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
|
||||
"Fuchsia City - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
|
||||
"Fuchsia City - Super Rod Pokemon - 3": lambda state: state.has("Super Rod", player),
|
||||
"Fuchsia City - Super Rod Pokemon - 4": lambda state: state.has("Super Rod", player),
|
||||
"Anywhere - Good Rod Pokemon - 1": lambda state: state.has("Good Rod", player),
|
||||
"Anywhere - Good Rod Pokemon - 2": lambda state: state.has("Good Rod", player),
|
||||
"Anywhere - Old Rod Pokemon": lambda state: state.has("Old Rod", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 1": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 2": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 3": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 4": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 5": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 6": lambda state: state.has("Coin Case", player),
|
||||
"Cinnabar Island - Old Amber Pokemon": lambda state: state.has("Old Amber", player),
|
||||
"Cinnabar Island - Helix Fossil Pokemon": lambda state: state.has("Helix Fossil", player),
|
||||
"Cinnabar Island - Dome Fossil Pokemon": lambda state: state.has("Dome Fossil", player),
|
||||
"Route 12 - Sleeping Pokemon": lambda state: state.has("Poke Flute", player),
|
||||
"Route 16 - Sleeping Pokemon": lambda state: state.has("Poke Flute", player),
|
||||
"Seafoam Islands B4F - Legendary Pokemon": lambda state: state.pokemon_rb_can_strength(player),
|
||||
"Vermilion City - Legendary Pokemon": lambda state: state.pokemon_rb_can_surf(player) and state.has("S.S. Ticket", player)
|
||||
}
|
||||
|
||||
hidden_item_access_rules = {
|
||||
"Viridian Forest - Hidden Item Northwest by Trainer": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Viridian Forest - Hidden Item Entrance Tree": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Mt Moon B2F - Hidden Item Dead End Before Fossils": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Route 25 - Hidden Item Fence Outside Bill's House": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Route 9 - Hidden Item Rock By Grass": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"S.S. Anne 1F - Hidden Item Kitchen Trash": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"S.S. Anne B1F - Hidden Item Under Pillow": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 10 - Hidden Item Behind Rock Tunnel Entrance Tree": lambda
|
||||
state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 10 - Hidden Item Rock": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Rocket Hideout B1F - Hidden Item Pot Plant": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Rocket Hideout B3F - Hidden Item Near East Item": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Rocket Hideout B4F - Hidden Item Behind Giovanni": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Pokemon Tower 5F - Hidden Item Near West Staircase": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Route 13 - Hidden Item Dead End Boulder": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 13 - Hidden Item Dead End By Water Corner": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Pokemon Mansion B1F - Hidden Item Secret Key Room Corner": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Safari Zone West - Hidden Item Secret House Statue": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Silph Co 5F - Hidden Item Pot Plant": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Silph Co 9F - Hidden Item Nurse Bed": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Copycat's House - Hidden Item Desk": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Cerulean Cave 1F - Hidden Item Center Rocks": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Cerulean Cave B1F - Hidden Item Northeast Rocks": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Power Plant - Hidden Item Central Dead End": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Power Plant - Hidden Item Before Zapdos": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Seafoam Islands B2F - Hidden Item Rock": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Seafoam Islands B4F - Hidden Item Corner Island": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"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(
|
||||
player),
|
||||
"Route 23 - Hidden Item East Tree After Water": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Route 23 - Hidden Item On Island": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Victory Road 2F - Hidden Item Rock Before Moltres": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Victory Road 2F - Hidden Item Rock In Final Room": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Viridian City - Hidden Item Cuttable Tree": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 11 - Hidden Item Isolated Tree Near Gate": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 12 - Hidden Item Tree Near Gate": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 17 - Hidden Item In Grass": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 17 - Hidden Item Near Northernmost Sign": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 17 - Hidden Item East Center": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 17 - Hidden Item West Center": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 17 - Hidden Item Before Final Bridge": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Underground Tunnel North-South - Hidden Item Near Northern Stairs": lambda
|
||||
state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Underground Tunnel North-South - Hidden Item Near Southern Stairs": lambda
|
||||
state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Underground Tunnel West-East - Hidden Item West": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Underground Tunnel West-East - Hidden Item East": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Celadon City - Hidden Item Dead End Near Cuttable Tree": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Route 25 - Hidden Item Northeast Of Grass": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Mt Moon B2F - Hidden Item Lone Rock": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Seafoam Islands B3F - Hidden Item Rock": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Vermilion City - Hidden Item In Water Near Fan Club": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Cerulean City - Hidden Item Gym Badge Guy's Backyard": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Route 4 - Hidden Item Plateau East Of Mt Moon": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
}
|
||||
for loc, rule in access_rules.items():
|
||||
add_rule(world.get_location(loc, player), rule)
|
||||
if world.randomize_hidden_items[player].value != 0:
|
||||
for loc, rule in hidden_item_access_rules.items():
|
||||
add_rule(world.get_location(loc, player), rule)
|
|
@ -0,0 +1,147 @@
|
|||
special_chars = {
|
||||
"PKMN": 0x4A,
|
||||
"'d": 0xBB,
|
||||
"'l": 0xBC,
|
||||
"'t": 0xBE,
|
||||
"'v": 0xBF,
|
||||
"PK": 0xE1,
|
||||
"MN": 0xE2,
|
||||
"'r": 0xE4,
|
||||
"'m": 0xE5,
|
||||
"MALE": 0xEF,
|
||||
"FEMALE": 0xF5,
|
||||
}
|
||||
|
||||
char_map = {
|
||||
"@": 0x50, # String terminator
|
||||
"#": 0x54, # Poké
|
||||
"‘": 0x70,
|
||||
"’": 0x71,
|
||||
"“": 0x72,
|
||||
"”": 0x73,
|
||||
"·": 0x74,
|
||||
"…": 0x75,
|
||||
" ": 0x7F,
|
||||
"A": 0x80,
|
||||
"B": 0x81,
|
||||
"C": 0x82,
|
||||
"D": 0x83,
|
||||
"E": 0x84,
|
||||
"F": 0x85,
|
||||
"G": 0x86,
|
||||
"H": 0x87,
|
||||
"I": 0x88,
|
||||
"J": 0x89,
|
||||
"K": 0x8A,
|
||||
"L": 0x8B,
|
||||
"M": 0x8C,
|
||||
"N": 0x8D,
|
||||
"O": 0x8E,
|
||||
"P": 0x8F,
|
||||
"Q": 0x90,
|
||||
"R": 0x91,
|
||||
"S": 0x92,
|
||||
"T": 0x93,
|
||||
"U": 0x94,
|
||||
"V": 0x95,
|
||||
"W": 0x96,
|
||||
"X": 0x97,
|
||||
"Y": 0x98,
|
||||
"Z": 0x99,
|
||||
"(": 0x9A,
|
||||
")": 0x9B,
|
||||
":": 0x9C,
|
||||
";": 0x9D,
|
||||
"[": 0x9E,
|
||||
"]": 0x9F,
|
||||
"a": 0xA0,
|
||||
"b": 0xA1,
|
||||
"c": 0xA2,
|
||||
"d": 0xA3,
|
||||
"e": 0xA4,
|
||||
"f": 0xA5,
|
||||
"g": 0xA6,
|
||||
"h": 0xA7,
|
||||
"i": 0xA8,
|
||||
"j": 0xA9,
|
||||
"k": 0xAA,
|
||||
"l": 0xAB,
|
||||
"m": 0xAC,
|
||||
"n": 0xAD,
|
||||
"o": 0xAE,
|
||||
"p": 0xAF,
|
||||
"q": 0xB0,
|
||||
"r": 0xB1,
|
||||
"s": 0xB2,
|
||||
"t": 0xB3,
|
||||
"u": 0xB4,
|
||||
"v": 0xB5,
|
||||
"w": 0xB6,
|
||||
"x": 0xB7,
|
||||
"y": 0xB8,
|
||||
"z": 0xB9,
|
||||
"é": 0xBA,
|
||||
"'": 0xE0,
|
||||
"-": 0xE3,
|
||||
"?": 0xE6,
|
||||
"!": 0xE7,
|
||||
".": 0xE8,
|
||||
"♂": 0xEF,
|
||||
"¥": 0xF0,
|
||||
"$": 0xF0,
|
||||
"×": 0xF1,
|
||||
"/": 0xF3,
|
||||
",": 0xF4,
|
||||
"♀": 0xF5,
|
||||
"0": 0xF6,
|
||||
"1": 0xF7,
|
||||
"2": 0xF8,
|
||||
"3": 0xF9,
|
||||
"4": 0xFA,
|
||||
"5": 0xFB,
|
||||
"6": 0xFC,
|
||||
"7": 0xFD,
|
||||
"8": 0xFE,
|
||||
"9": 0xFF,
|
||||
}
|
||||
|
||||
unsafe_chars = ["@", "#", "PKMN"]
|
||||
|
||||
|
||||
def encode_text(text: str, length: int=0, whitespace=False, force=False, safety=False):
|
||||
encoded_text = bytearray()
|
||||
spec_char = ""
|
||||
special = False
|
||||
for char in text:
|
||||
if char == ">":
|
||||
if spec_char in unsafe_chars and safety:
|
||||
raise KeyError(f"Disallowed Pokemon text special character '<{spec_char}>'")
|
||||
try:
|
||||
encoded_text.append(special_chars[spec_char])
|
||||
except KeyError:
|
||||
if force:
|
||||
encoded_text.append(char_map[" "])
|
||||
else:
|
||||
raise KeyError(f"Invalid Pokemon text special character '<{spec_char}>'")
|
||||
spec_char = ""
|
||||
special = False
|
||||
elif char == "<":
|
||||
spec_char = ""
|
||||
special = True
|
||||
elif special is True:
|
||||
spec_char += char
|
||||
else:
|
||||
if char in unsafe_chars and safety:
|
||||
raise KeyError(f"Disallowed Pokemon text character '{char}'")
|
||||
try:
|
||||
encoded_text.append(char_map[char])
|
||||
except KeyError:
|
||||
if force:
|
||||
encoded_text.append(char_map[" "])
|
||||
else:
|
||||
raise KeyError(f"Invalid Pokemon text character '{char}'")
|
||||
if length > 0:
|
||||
encoded_text = encoded_text[:length]
|
||||
while whitespace and len(encoded_text) < length:
|
||||
encoded_text.append(char_map[" " if whitespace is True else whitespace])
|
||||
return encoded_text
|
Loading…
Reference in New Issue