mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-29 05:53:22 -07:00
Merge branch 'main' into tunc-portal-direction-pairing
This commit is contained in:
@@ -342,6 +342,8 @@ class MultiWorld():
|
||||
region = Region("Menu", group_id, self, "ItemLink")
|
||||
self.regions.append(region)
|
||||
locations = region.locations
|
||||
# ensure that progression items are linked first, then non-progression
|
||||
self.itempool.sort(key=lambda item: item.advancement)
|
||||
for item in self.itempool:
|
||||
count = common_item_count.get(item.player, {}).get(item.name, 0)
|
||||
if count:
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import ModuleUpdate
|
||||
ModuleUpdate.update()
|
||||
|
||||
from worlds._bizhawk.context import launch
|
||||
|
||||
if __name__ == "__main__":
|
||||
launch()
|
||||
launch(*sys.argv[1:])
|
||||
|
||||
20
Fill.py
20
Fill.py
@@ -475,28 +475,26 @@ def distribute_items_restrictive(multiworld: MultiWorld,
|
||||
nonlocal lock_later
|
||||
lock_later.append(location)
|
||||
|
||||
single_player = multiworld.players == 1 and not multiworld.groups
|
||||
|
||||
if prioritylocations:
|
||||
# "priority fill"
|
||||
fill_restrictive(multiworld, multiworld.state, prioritylocations, progitempool,
|
||||
single_player_placement=multiworld.players == 1, swap=False, on_place=mark_for_locking,
|
||||
name="Priority")
|
||||
single_player_placement=single_player, swap=False, on_place=mark_for_locking, name="Priority")
|
||||
accessibility_corrections(multiworld, multiworld.state, prioritylocations, progitempool)
|
||||
defaultlocations = prioritylocations + defaultlocations
|
||||
|
||||
if progitempool:
|
||||
# "advancement/progression fill"
|
||||
if panic_method == "swap":
|
||||
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool,
|
||||
swap=True,
|
||||
name="Progression", single_player_placement=multiworld.players == 1)
|
||||
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool, swap=True,
|
||||
name="Progression", single_player_placement=single_player)
|
||||
elif panic_method == "raise":
|
||||
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool,
|
||||
swap=False,
|
||||
name="Progression", single_player_placement=multiworld.players == 1)
|
||||
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool, swap=False,
|
||||
name="Progression", single_player_placement=single_player)
|
||||
elif panic_method == "start_inventory":
|
||||
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool,
|
||||
swap=False, allow_partial=True,
|
||||
name="Progression", single_player_placement=multiworld.players == 1)
|
||||
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool, swap=False,
|
||||
allow_partial=True, name="Progression", single_player_placement=single_player)
|
||||
if progitempool:
|
||||
for item in progitempool:
|
||||
logging.debug(f"Moved {item} to start_inventory to prevent fill failure.")
|
||||
|
||||
31
Generate.py
31
Generate.py
@@ -43,10 +43,10 @@ def mystery_argparse():
|
||||
parser.add_argument('--race', action='store_true', default=defaults.race)
|
||||
parser.add_argument('--meta_file_path', default=defaults.meta_file_path)
|
||||
parser.add_argument('--log_level', default='info', help='Sets log level')
|
||||
parser.add_argument('--yaml_output', default=0, type=lambda value: max(int(value), 0),
|
||||
help='Output rolled mystery results to yaml up to specified number (made for async multiworld)')
|
||||
parser.add_argument('--plando', default=defaults.plando_options,
|
||||
help='List of options that can be set manually. Can be combined, for example "bosses, items"')
|
||||
parser.add_argument("--csv_output", action="store_true",
|
||||
help="Output rolled player options to csv (made for async multiworld).")
|
||||
parser.add_argument("--plando", default=defaults.plando_options,
|
||||
help="List of options that can be set manually. Can be combined, for example \"bosses, items\"")
|
||||
parser.add_argument("--skip_prog_balancing", action="store_true",
|
||||
help="Skip progression balancing step during generation.")
|
||||
parser.add_argument("--skip_output", action="store_true",
|
||||
@@ -156,6 +156,7 @@ def main(args=None) -> Tuple[argparse.Namespace, int]:
|
||||
erargs.skip_prog_balancing = args.skip_prog_balancing
|
||||
erargs.skip_output = args.skip_output
|
||||
erargs.name = {}
|
||||
erargs.csv_output = args.csv_output
|
||||
|
||||
settings_cache: Dict[str, Tuple[argparse.Namespace, ...]] = \
|
||||
{fname: (tuple(roll_settings(yaml, args.plando) for yaml in yamls) if args.sameoptions else None)
|
||||
@@ -216,28 +217,6 @@ def main(args=None) -> Tuple[argparse.Namespace, int]:
|
||||
if len(set(name.lower() for name in erargs.name.values())) != len(erargs.name):
|
||||
raise Exception(f"Names have to be unique. Names: {Counter(name.lower() for name in erargs.name.values())}")
|
||||
|
||||
if args.yaml_output:
|
||||
import yaml
|
||||
important = {}
|
||||
for option, player_settings in vars(erargs).items():
|
||||
if type(player_settings) == dict:
|
||||
if all(type(value) != list for value in player_settings.values()):
|
||||
if len(player_settings.values()) > 1:
|
||||
important[option] = {player: value for player, value in player_settings.items() if
|
||||
player <= args.yaml_output}
|
||||
else:
|
||||
logging.debug(f"No player settings defined for option '{option}'")
|
||||
|
||||
else:
|
||||
if player_settings != "": # is not empty name
|
||||
important[option] = player_settings
|
||||
else:
|
||||
logging.debug(f"No player settings defined for option '{option}'")
|
||||
if args.outputpath:
|
||||
os.makedirs(args.outputpath, exist_ok=True)
|
||||
with open(os.path.join(args.outputpath if args.outputpath else ".", f"generate_{seed_name}.yaml"), "wt") as f:
|
||||
yaml.dump(important, f)
|
||||
|
||||
return erargs, seed
|
||||
|
||||
|
||||
|
||||
@@ -467,6 +467,8 @@ class LinksAwakeningContext(CommonContext):
|
||||
|
||||
def __init__(self, server_address: typing.Optional[str], password: typing.Optional[str], magpie: typing.Optional[bool]) -> None:
|
||||
self.client = LinksAwakeningClient()
|
||||
self.slot_data = {}
|
||||
|
||||
if magpie:
|
||||
self.magpie_enabled = True
|
||||
self.magpie = MagpieBridge()
|
||||
@@ -564,6 +566,8 @@ class LinksAwakeningContext(CommonContext):
|
||||
def on_package(self, cmd: str, args: dict):
|
||||
if cmd == "Connected":
|
||||
self.game = self.slot_info[self.slot].game
|
||||
self.slot_data = args.get("slot_data", {})
|
||||
|
||||
# TODO - use watcher_event
|
||||
if cmd == "ReceivedItems":
|
||||
for index, item in enumerate(args["items"], start=args["index"]):
|
||||
@@ -628,6 +632,7 @@ class LinksAwakeningContext(CommonContext):
|
||||
self.magpie.set_checks(self.client.tracker.all_checks)
|
||||
await self.magpie.set_item_tracker(self.client.item_tracker)
|
||||
await self.magpie.send_gps(self.client.gps_tracker)
|
||||
self.magpie.slot_data = self.slot_data
|
||||
except Exception:
|
||||
# Don't let magpie errors take out the client
|
||||
pass
|
||||
|
||||
3
Main.py
3
Main.py
@@ -46,6 +46,9 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
||||
multiworld.sprite_pool = args.sprite_pool.copy()
|
||||
|
||||
multiworld.set_options(args)
|
||||
if args.csv_output:
|
||||
from Options import dump_player_options
|
||||
dump_player_options(multiworld)
|
||||
multiworld.set_item_links()
|
||||
multiworld.state = CollectionState(multiworld)
|
||||
logger.info('Archipelago Version %s - Seed: %s\n', __version__, multiworld.seed)
|
||||
|
||||
@@ -273,7 +273,8 @@ class RawJSONtoTextParser(JSONtoTextParser):
|
||||
|
||||
color_codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34,
|
||||
'magenta': 35, 'cyan': 36, 'white': 37, 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43,
|
||||
'blue_bg': 44, 'magenta_bg': 45, 'cyan_bg': 46, 'white_bg': 47}
|
||||
'blue_bg': 44, 'magenta_bg': 45, 'cyan_bg': 46, 'white_bg': 47,
|
||||
'plum': 35, 'slateblue': 34, 'salmon': 31,} # convert ui colors to terminal colors
|
||||
|
||||
|
||||
def color_code(*args):
|
||||
|
||||
46
Options.py
46
Options.py
@@ -8,16 +8,17 @@ import numbers
|
||||
import random
|
||||
import typing
|
||||
import enum
|
||||
from collections import defaultdict
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass
|
||||
|
||||
from schema import And, Optional, Or, Schema
|
||||
from typing_extensions import Self
|
||||
|
||||
from Utils import get_fuzzy_results, is_iterable_except_str
|
||||
from Utils import get_fuzzy_results, is_iterable_except_str, output_path
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from BaseClasses import PlandoOptions
|
||||
from BaseClasses import MultiWorld, PlandoOptions
|
||||
from worlds.AutoWorld import World
|
||||
import pathlib
|
||||
|
||||
@@ -1335,7 +1336,7 @@ class PriorityLocations(LocationSet):
|
||||
|
||||
|
||||
class DeathLink(Toggle):
|
||||
"""When you die, everyone dies. Of course the reverse is true too."""
|
||||
"""When you die, everyone who enabled death link dies. Of course, the reverse is true too."""
|
||||
display_name = "Death Link"
|
||||
rich_text_doc = True
|
||||
|
||||
@@ -1532,3 +1533,42 @@ def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], ge
|
||||
|
||||
with open(os.path.join(target_folder, game_name + ".yaml"), "w", encoding="utf-8-sig") as f:
|
||||
f.write(res)
|
||||
|
||||
|
||||
def dump_player_options(multiworld: MultiWorld) -> None:
|
||||
from csv import DictWriter
|
||||
|
||||
game_players = defaultdict(list)
|
||||
for player, game in multiworld.game.items():
|
||||
game_players[game].append(player)
|
||||
game_players = dict(sorted(game_players.items()))
|
||||
|
||||
output = []
|
||||
per_game_option_names = [
|
||||
getattr(option, "display_name", option_key)
|
||||
for option_key, option in PerGameCommonOptions.type_hints.items()
|
||||
]
|
||||
all_option_names = per_game_option_names.copy()
|
||||
for game, players in game_players.items():
|
||||
game_option_names = per_game_option_names.copy()
|
||||
for player in players:
|
||||
world = multiworld.worlds[player]
|
||||
player_output = {
|
||||
"Game": multiworld.game[player],
|
||||
"Name": multiworld.get_player_name(player),
|
||||
}
|
||||
output.append(player_output)
|
||||
for option_key, option in world.options_dataclass.type_hints.items():
|
||||
if issubclass(Removed, option):
|
||||
continue
|
||||
display_name = getattr(option, "display_name", option_key)
|
||||
player_output[display_name] = getattr(world.options, option_key).current_option_name
|
||||
if display_name not in game_option_names:
|
||||
all_option_names.append(display_name)
|
||||
game_option_names.append(display_name)
|
||||
|
||||
with open(output_path(f"generate_{multiworld.seed_name}.csv"), mode="w", newline="") as file:
|
||||
fields = ["Game", "Name", *all_option_names]
|
||||
writer = DictWriter(file, fields)
|
||||
writer.writeheader()
|
||||
writer.writerows(output)
|
||||
|
||||
2
Utils.py
2
Utils.py
@@ -46,7 +46,7 @@ class Version(typing.NamedTuple):
|
||||
return ".".join(str(item) for item in self)
|
||||
|
||||
|
||||
__version__ = "0.5.0"
|
||||
__version__ = "0.5.1"
|
||||
version_tuple = tuplize_version(__version__)
|
||||
|
||||
is_linux = sys.platform.startswith("linux")
|
||||
|
||||
@@ -1,51 +1,15 @@
|
||||
"""API endpoints package."""
|
||||
from typing import List, Tuple
|
||||
from uuid import UUID
|
||||
|
||||
from flask import Blueprint, abort, url_for
|
||||
from flask import Blueprint
|
||||
|
||||
import worlds.Files
|
||||
from ..models import Room, Seed
|
||||
from ..models import Seed
|
||||
|
||||
api_endpoints = Blueprint('api', __name__, url_prefix="/api")
|
||||
|
||||
# unsorted/misc endpoints
|
||||
|
||||
|
||||
def get_players(seed: Seed) -> List[Tuple[str, str]]:
|
||||
return [(slot.player_name, slot.game) for slot in seed.slots]
|
||||
|
||||
|
||||
@api_endpoints.route('/room_status/<suuid:room>')
|
||||
def room_info(room: UUID):
|
||||
room = Room.get(id=room)
|
||||
if room is None:
|
||||
return abort(404)
|
||||
|
||||
def supports_apdeltapatch(game: str):
|
||||
return game in worlds.Files.AutoPatchRegister.patch_types
|
||||
downloads = []
|
||||
for slot in sorted(room.seed.slots):
|
||||
if slot.data and not supports_apdeltapatch(slot.game):
|
||||
slot_download = {
|
||||
"slot": slot.player_id,
|
||||
"download": url_for("download_slot_file", room_id=room.id, player_id=slot.player_id)
|
||||
}
|
||||
downloads.append(slot_download)
|
||||
elif slot.data:
|
||||
slot_download = {
|
||||
"slot": slot.player_id,
|
||||
"download": url_for("download_patch", patch_id=slot.id, room_id=room.id)
|
||||
}
|
||||
downloads.append(slot_download)
|
||||
return {
|
||||
"tracker": room.tracker,
|
||||
"players": get_players(room.seed),
|
||||
"last_port": room.last_port,
|
||||
"last_activity": room.last_activity,
|
||||
"timeout": room.timeout,
|
||||
"downloads": downloads,
|
||||
}
|
||||
|
||||
|
||||
from . import generate, user, datapackage # trigger registration
|
||||
from . import datapackage, generate, room, user # trigger registration
|
||||
|
||||
42
WebHostLib/api/room.py
Normal file
42
WebHostLib/api/room.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from typing import Any, Dict
|
||||
from uuid import UUID
|
||||
|
||||
from flask import abort, url_for
|
||||
|
||||
import worlds.Files
|
||||
from . import api_endpoints, get_players
|
||||
from ..models import Room
|
||||
|
||||
|
||||
@api_endpoints.route('/room_status/<suuid:room_id>')
|
||||
def room_info(room_id: UUID) -> Dict[str, Any]:
|
||||
room = Room.get(id=room_id)
|
||||
if room is None:
|
||||
return abort(404)
|
||||
|
||||
def supports_apdeltapatch(game: str) -> bool:
|
||||
return game in worlds.Files.AutoPatchRegister.patch_types
|
||||
|
||||
downloads = []
|
||||
for slot in sorted(room.seed.slots):
|
||||
if slot.data and not supports_apdeltapatch(slot.game):
|
||||
slot_download = {
|
||||
"slot": slot.player_id,
|
||||
"download": url_for("download_slot_file", room_id=room.id, player_id=slot.player_id)
|
||||
}
|
||||
downloads.append(slot_download)
|
||||
elif slot.data:
|
||||
slot_download = {
|
||||
"slot": slot.player_id,
|
||||
"download": url_for("download_patch", patch_id=slot.id, room_id=room.id)
|
||||
}
|
||||
downloads.append(slot_download)
|
||||
|
||||
return {
|
||||
"tracker": room.tracker,
|
||||
"players": get_players(room.seed),
|
||||
"last_port": room.last_port,
|
||||
"last_activity": room.last_activity,
|
||||
"timeout": room.timeout,
|
||||
"downloads": downloads,
|
||||
}
|
||||
@@ -134,6 +134,7 @@ def gen_game(gen_options: dict, meta: Optional[Dict[str, Any]] = None, owner=Non
|
||||
{"bosses", "items", "connections", "texts"}))
|
||||
erargs.skip_prog_balancing = False
|
||||
erargs.skip_output = False
|
||||
erargs.csv_output = False
|
||||
|
||||
name_counter = Counter()
|
||||
for player, (playerfile, settings) in enumerate(gen_options.items(), 1):
|
||||
|
||||
@@ -132,26 +132,41 @@ def display_log(room: UUID) -> Union[str, Response, Tuple[str, int]]:
|
||||
return "Access Denied", 403
|
||||
|
||||
|
||||
@app.route('/room/<suuid:room>', methods=['GET', 'POST'])
|
||||
@app.post("/room/<suuid:room>")
|
||||
def host_room_command(room: UUID):
|
||||
room: Room = Room.get(id=room)
|
||||
if room is None:
|
||||
return abort(404)
|
||||
|
||||
if room.owner == session["_id"]:
|
||||
cmd = request.form["cmd"]
|
||||
if cmd:
|
||||
Command(room=room, commandtext=cmd)
|
||||
commit()
|
||||
return redirect(url_for("host_room", room=room.id))
|
||||
|
||||
|
||||
@app.get("/room/<suuid:room>")
|
||||
def host_room(room: UUID):
|
||||
room: Room = Room.get(id=room)
|
||||
if room is None:
|
||||
return abort(404)
|
||||
if request.method == "POST":
|
||||
if room.owner == session["_id"]:
|
||||
cmd = request.form["cmd"]
|
||||
if cmd:
|
||||
Command(room=room, commandtext=cmd)
|
||||
commit()
|
||||
return redirect(url_for("host_room", room=room.id))
|
||||
|
||||
now = datetime.datetime.utcnow()
|
||||
# indicate that the page should reload to get the assigned port
|
||||
should_refresh = not room.last_port and now - room.creation_time < datetime.timedelta(seconds=3)
|
||||
should_refresh = ((not room.last_port and now - room.creation_time < datetime.timedelta(seconds=3))
|
||||
or room.last_activity < now - datetime.timedelta(seconds=room.timeout))
|
||||
with db_session:
|
||||
room.last_activity = now # will trigger a spinup, if it's not already running
|
||||
|
||||
def get_log(max_size: int = 1024000) -> str:
|
||||
browser_tokens = "Mozilla", "Chrome", "Safari"
|
||||
automated = ("update" in request.args
|
||||
or "Discordbot" in request.user_agent.string
|
||||
or not any(browser_token in request.user_agent.string for browser_token in browser_tokens))
|
||||
|
||||
def get_log(max_size: int = 0 if automated else 1024000) -> str:
|
||||
if max_size == 0:
|
||||
return "…"
|
||||
try:
|
||||
with open(os.path.join("logs", str(room.id) + ".txt"), "rb") as log:
|
||||
raw_size = 0
|
||||
|
||||
@@ -58,3 +58,28 @@
|
||||
overflow-y: auto;
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
.loader{
|
||||
display: inline-block;
|
||||
visibility: hidden;
|
||||
margin-left: 5px;
|
||||
width: 40px;
|
||||
aspect-ratio: 4;
|
||||
--_g: no-repeat radial-gradient(circle closest-side,#fff 90%,#fff0);
|
||||
background:
|
||||
var(--_g) 0 50%,
|
||||
var(--_g) 50% 50%,
|
||||
var(--_g) 100% 50%;
|
||||
background-size: calc(100%/3) 100%;
|
||||
animation: l7 1s infinite linear;
|
||||
}
|
||||
|
||||
.loader.loading{
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
@keyframes l7{
|
||||
33%{background-size:calc(100%/3) 0% ,calc(100%/3) 100%,calc(100%/3) 100%}
|
||||
50%{background-size:calc(100%/3) 100%,calc(100%/3) 0 ,calc(100%/3) 100%}
|
||||
66%{background-size:calc(100%/3) 100%,calc(100%/3) 100%,calc(100%/3) 0 }
|
||||
}
|
||||
|
||||
@@ -19,28 +19,30 @@
|
||||
{% block body %}
|
||||
{% include 'header/grassHeader.html' %}
|
||||
<div id="host-room">
|
||||
{% if room.owner == session["_id"] %}
|
||||
Room created from <a href="{{ url_for("view_seed", seed=room.seed.id) }}">Seed #{{ room.seed.id|suuid }}</a>
|
||||
<br />
|
||||
{% endif %}
|
||||
{% if room.tracker %}
|
||||
This room has a <a href="{{ url_for("get_multiworld_tracker", tracker=room.tracker) }}">Multiworld Tracker</a>
|
||||
and a <a href="{{ url_for("get_multiworld_sphere_tracker", tracker=room.tracker) }}">Sphere Tracker</a> enabled.
|
||||
<br />
|
||||
{% endif %}
|
||||
The server for this room will be paused after {{ room.timeout//60//60 }} hours of inactivity.
|
||||
Should you wish to continue later,
|
||||
anyone can simply refresh this page and the server will resume.<br>
|
||||
{% if room.last_port == -1 %}
|
||||
There was an error hosting this Room. Another attempt will be made on refreshing this page.
|
||||
The most likely failure reason is that the multiworld is too old to be loaded now.
|
||||
{% elif room.last_port %}
|
||||
You can connect to this room by using <span class="interactive"
|
||||
data-tooltip="This means address/ip is {{ config['HOST_ADDRESS'] }} and port is {{ room.last_port }}.">
|
||||
'/connect {{ config['HOST_ADDRESS'] }}:{{ room.last_port }}'
|
||||
</span>
|
||||
in the <a href="{{ url_for("tutorial_landing")}}">client</a>.<br>
|
||||
{% endif %}
|
||||
<span id="host-room-info">
|
||||
{% if room.owner == session["_id"] %}
|
||||
Room created from <a href="{{ url_for("view_seed", seed=room.seed.id) }}">Seed #{{ room.seed.id|suuid }}</a>
|
||||
<br />
|
||||
{% endif %}
|
||||
{% if room.tracker %}
|
||||
This room has a <a href="{{ url_for("get_multiworld_tracker", tracker=room.tracker) }}">Multiworld Tracker</a>
|
||||
and a <a href="{{ url_for("get_multiworld_sphere_tracker", tracker=room.tracker) }}">Sphere Tracker</a> enabled.
|
||||
<br />
|
||||
{% endif %}
|
||||
The server for this room will be paused after {{ room.timeout//60//60 }} hours of inactivity.
|
||||
Should you wish to continue later,
|
||||
anyone can simply refresh this page and the server will resume.<br>
|
||||
{% if room.last_port == -1 %}
|
||||
There was an error hosting this Room. Another attempt will be made on refreshing this page.
|
||||
The most likely failure reason is that the multiworld is too old to be loaded now.
|
||||
{% elif room.last_port %}
|
||||
You can connect to this room by using <span class="interactive"
|
||||
data-tooltip="This means address/ip is {{ config['HOST_ADDRESS'] }} and port is {{ room.last_port }}.">
|
||||
'/connect {{ config['HOST_ADDRESS'] }}:{{ room.last_port }}'
|
||||
</span>
|
||||
in the <a href="{{ url_for("tutorial_landing")}}">client</a>.<br>
|
||||
{% endif %}
|
||||
</span>
|
||||
{{ macros.list_patches_room(room) }}
|
||||
{% if room.owner == session["_id"] %}
|
||||
<div style="display: flex; align-items: center;">
|
||||
@@ -49,6 +51,7 @@
|
||||
<label for="cmd"></label>
|
||||
<input class="form-control" type="text" id="cmd" name="cmd"
|
||||
placeholder="Server Command. /help to list them, list gets appended to log.">
|
||||
<span class="loader"></span>
|
||||
</div>
|
||||
</form>
|
||||
<a href="{{ url_for("display_log", room=room.id) }}">
|
||||
@@ -62,6 +65,7 @@
|
||||
let url = '{{ url_for('display_log', room = room.id) }}';
|
||||
let bytesReceived = {{ log_len }};
|
||||
let updateLogTimeout;
|
||||
let updateLogImmediately = false;
|
||||
let awaitingCommandResponse = false;
|
||||
let logger = document.getElementById("logger");
|
||||
|
||||
@@ -78,29 +82,36 @@
|
||||
|
||||
async function updateLog() {
|
||||
try {
|
||||
let res = await fetch(url, {
|
||||
headers: {
|
||||
'Range': `bytes=${bytesReceived}-`,
|
||||
}
|
||||
});
|
||||
if (res.ok) {
|
||||
let text = await res.text();
|
||||
if (text.length > 0) {
|
||||
awaitingCommandResponse = false;
|
||||
if (bytesReceived === 0 || res.status !== 206) {
|
||||
logger.innerHTML = '';
|
||||
}
|
||||
if (res.status !== 206) {
|
||||
bytesReceived = 0;
|
||||
} else {
|
||||
bytesReceived += new Blob([text]).size;
|
||||
}
|
||||
if (logger.innerHTML.endsWith('…')) {
|
||||
logger.innerHTML = logger.innerHTML.substring(0, logger.innerHTML.length - 1);
|
||||
}
|
||||
logger.appendChild(document.createTextNode(text));
|
||||
scrollToBottom(logger);
|
||||
if (!document.hidden) {
|
||||
updateLogImmediately = false;
|
||||
let res = await fetch(url, {
|
||||
headers: {
|
||||
'Range': `bytes=${bytesReceived}-`,
|
||||
}
|
||||
});
|
||||
if (res.ok) {
|
||||
let text = await res.text();
|
||||
if (text.length > 0) {
|
||||
awaitingCommandResponse = false;
|
||||
if (bytesReceived === 0 || res.status !== 206) {
|
||||
logger.innerHTML = '';
|
||||
}
|
||||
if (res.status !== 206) {
|
||||
bytesReceived = 0;
|
||||
} else {
|
||||
bytesReceived += new Blob([text]).size;
|
||||
}
|
||||
if (logger.innerHTML.endsWith('…')) {
|
||||
logger.innerHTML = logger.innerHTML.substring(0, logger.innerHTML.length - 1);
|
||||
}
|
||||
logger.appendChild(document.createTextNode(text));
|
||||
scrollToBottom(logger);
|
||||
let loader = document.getElementById("command-form").getElementsByClassName("loader")[0];
|
||||
loader.classList.remove("loading");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
updateLogImmediately = true;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
@@ -125,20 +136,62 @@
|
||||
});
|
||||
ev.preventDefault(); // has to happen before first await
|
||||
form.reset();
|
||||
let res = await req;
|
||||
if (res.ok || res.type === 'opaqueredirect') {
|
||||
awaitingCommandResponse = true;
|
||||
window.clearTimeout(updateLogTimeout);
|
||||
updateLogTimeout = window.setTimeout(updateLog, 100);
|
||||
} else {
|
||||
window.alert(res.statusText);
|
||||
let loader = form.getElementsByClassName("loader")[0];
|
||||
loader.classList.add("loading");
|
||||
try {
|
||||
let res = await req;
|
||||
if (res.ok || res.type === 'opaqueredirect') {
|
||||
awaitingCommandResponse = true;
|
||||
window.clearTimeout(updateLogTimeout);
|
||||
updateLogTimeout = window.setTimeout(updateLog, 100);
|
||||
} else {
|
||||
loader.classList.remove("loading");
|
||||
window.alert(res.statusText);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
loader.classList.remove("loading");
|
||||
window.alert(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("command-form").addEventListener("submit", postForm);
|
||||
updateLogTimeout = window.setTimeout(updateLog, 1000);
|
||||
logger.scrollTop = logger.scrollHeight;
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (!document.hidden && updateLogImmediately) {
|
||||
updateLog();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
{% endif %}
|
||||
<script>
|
||||
function updateInfo() {
|
||||
let url = new URL(window.location.href);
|
||||
url.search = "?update";
|
||||
fetch(url)
|
||||
.then(res => {
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP error ${res.status}`);
|
||||
}
|
||||
return res.text()
|
||||
})
|
||||
.then(text => new DOMParser().parseFromString(text, 'text/html'))
|
||||
.then(newDocument => {
|
||||
let el = newDocument.getElementById("host-room-info");
|
||||
document.getElementById("host-room-info").innerHTML = el.innerHTML;
|
||||
});
|
||||
}
|
||||
|
||||
if (document.querySelector("meta[http-equiv='refresh']")) {
|
||||
console.log("Refresh!");
|
||||
window.addEventListener('load', function () {
|
||||
for (let i=0; i<3; i++) {
|
||||
window.setTimeout(updateInfo, Math.pow(2, i) * 2000); // 2, 4, 8s
|
||||
}
|
||||
window.stop(); // cancel meta refresh
|
||||
})
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -131,7 +131,8 @@ class TestHostFakeRoom(TestBase):
|
||||
f.write(text)
|
||||
|
||||
with self.app.app_context(), self.app.test_request_context():
|
||||
response = self.client.get(url_for("host_room", room=self.room_id))
|
||||
response = self.client.get(url_for("host_room", room=self.room_id),
|
||||
headers={"User-Agent": "Mozilla/5.0"})
|
||||
response_text = response.get_data(True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("href=\"/seed/", response_text)
|
||||
|
||||
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
|
||||
|
||||
def launch_client(*args) -> None:
|
||||
from .context import launch
|
||||
launch_subprocess(launch, name="BizHawkClient")
|
||||
launch_subprocess(launch, name="BizHawkClient", args=args)
|
||||
|
||||
|
||||
component = Component("BizHawk Client", "BizHawkClient", component_type=Type.CLIENT, func=launch_client,
|
||||
|
||||
@@ -239,11 +239,11 @@ async def _patch_and_run_game(patch_file: str):
|
||||
logger.exception(exc)
|
||||
|
||||
|
||||
def launch() -> None:
|
||||
def launch(*launch_args) -> None:
|
||||
async def main():
|
||||
parser = get_base_parser()
|
||||
parser.add_argument("patch_file", default="", type=str, nargs="?", help="Path to an Archipelago patch file")
|
||||
args = parser.parse_args()
|
||||
args = parser.parse_args(launch_args)
|
||||
|
||||
ctx = BizHawkClientContext(args.connect, args.password)
|
||||
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
|
||||
|
||||
@@ -199,8 +199,6 @@ class BlasphemousWorld(World):
|
||||
|
||||
self.multiworld.itempool += pool
|
||||
|
||||
|
||||
def pre_fill(self):
|
||||
self.place_items_from_dict(unrandomized_dict)
|
||||
|
||||
if self.options.thorn_shuffle == "vanilla":
|
||||
@@ -335,4 +333,4 @@ class BlasphemousItem(Item):
|
||||
|
||||
|
||||
class BlasphemousLocation(Location):
|
||||
game: str = "Blasphemous"
|
||||
game: str = "Blasphemous"
|
||||
|
||||
@@ -63,6 +63,9 @@ all_bosses = [
|
||||
DS3BossInfo("Deacons of the Deep", 3500800, locations = {
|
||||
"CD: Soul of the Deacons of the Deep",
|
||||
"CD: Small Doll - boss drop",
|
||||
"CD: Archdeacon White Crown - boss room after killing boss",
|
||||
"CD: Archdeacon Holy Garb - boss room after killing boss",
|
||||
"CD: Archdeacon Skirt - boss room after killing boss",
|
||||
"FS: Hawkwood's Shield - gravestone after Hawkwood leaves",
|
||||
}),
|
||||
DS3BossInfo("Abyss Watchers", 3300801, before_storm_ruler = True, locations = {
|
||||
|
||||
@@ -612,9 +612,7 @@ class DarkSouls3World(World):
|
||||
self._add_entrance_rule("Painted World of Ariandel (Before Contraption)", "Basin of Vows")
|
||||
|
||||
# Define the access rules to some specific locations
|
||||
if self._is_location_available("FS: Lift Chamber Key - Leonhard"):
|
||||
self._add_location_rule("HWL: Red Eye Orb - wall tower, miniboss",
|
||||
"Lift Chamber Key")
|
||||
self._add_location_rule("HWL: Red Eye Orb - wall tower, miniboss", "Lift Chamber Key")
|
||||
self._add_location_rule("ID: Bellowing Dragoncrest Ring - drop from B1 towards pit",
|
||||
"Jailbreaker's Key")
|
||||
self._add_location_rule("ID: Covetous Gold Serpent Ring - Siegward's cell", "Old Cell Key")
|
||||
|
||||
@@ -1470,7 +1470,7 @@ location_table: Dict[int, LocationDict] = {
|
||||
'map': 6,
|
||||
'index': 102,
|
||||
'doom_type': 2006,
|
||||
'region': "Tenements (MAP17) Main"},
|
||||
'region': "Tenements (MAP17) Yellow"},
|
||||
361243: {'name': 'Tenements (MAP17) - Plasma gun',
|
||||
'episode': 2,
|
||||
'map': 6,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Outputs a Factorio Mod to facilitate integration with Archipelago"""
|
||||
|
||||
import dataclasses
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
@@ -88,6 +89,8 @@ class FactorioModFile(worlds.Files.APContainer):
|
||||
def generate_mod(world: "Factorio", output_directory: str):
|
||||
player = world.player
|
||||
multiworld = world.multiworld
|
||||
random = world.random
|
||||
|
||||
global data_final_template, locale_template, control_template, data_template, settings_template
|
||||
with template_load_lock:
|
||||
if not data_final_template:
|
||||
@@ -110,8 +113,6 @@ def generate_mod(world: "Factorio", output_directory: str):
|
||||
mod_name = f"AP-{multiworld.seed_name}-P{player}-{multiworld.get_file_safe_player_name(player)}"
|
||||
versioned_mod_name = mod_name + "_" + Utils.__version__
|
||||
|
||||
random = multiworld.per_slot_randoms[player]
|
||||
|
||||
def flop_random(low, high, base=None):
|
||||
"""Guarantees 50% below base and 50% above base, uniform distribution in each direction."""
|
||||
if base:
|
||||
@@ -129,43 +130,43 @@ def generate_mod(world: "Factorio", output_directory: str):
|
||||
"base_tech_table": base_tech_table,
|
||||
"tech_to_progressive_lookup": tech_to_progressive_lookup,
|
||||
"mod_name": mod_name,
|
||||
"allowed_science_packs": multiworld.max_science_pack[player].get_allowed_packs(),
|
||||
"custom_technologies": multiworld.worlds[player].custom_technologies,
|
||||
"allowed_science_packs": world.options.max_science_pack.get_allowed_packs(),
|
||||
"custom_technologies": world.custom_technologies,
|
||||
"tech_tree_layout_prerequisites": world.tech_tree_layout_prerequisites,
|
||||
"slot_name": multiworld.player_name[player], "seed_name": multiworld.seed_name,
|
||||
"slot_name": world.player_name, "seed_name": multiworld.seed_name,
|
||||
"slot_player": player,
|
||||
"starting_items": multiworld.starting_items[player], "recipes": recipes,
|
||||
"starting_items": world.options.starting_items, "recipes": recipes,
|
||||
"random": random, "flop_random": flop_random,
|
||||
"recipe_time_scale": recipe_time_scales.get(multiworld.recipe_time[player].value, None),
|
||||
"recipe_time_range": recipe_time_ranges.get(multiworld.recipe_time[player].value, None),
|
||||
"recipe_time_scale": recipe_time_scales.get(world.options.recipe_time.value, None),
|
||||
"recipe_time_range": recipe_time_ranges.get(world.options.recipe_time.value, None),
|
||||
"free_sample_blacklist": {item: 1 for item in free_sample_exclusions},
|
||||
"progressive_technology_table": {tech.name: tech.progressive for tech in
|
||||
progressive_technology_table.values()},
|
||||
"custom_recipes": world.custom_recipes,
|
||||
"max_science_pack": multiworld.max_science_pack[player].value,
|
||||
"max_science_pack": world.options.max_science_pack.value,
|
||||
"liquids": fluids,
|
||||
"goal": multiworld.goal[player].value,
|
||||
"energy_link": multiworld.energy_link[player].value,
|
||||
"goal": world.options.goal.value,
|
||||
"energy_link": world.options.energy_link.value,
|
||||
"useless_technologies": useless_technologies,
|
||||
"chunk_shuffle": multiworld.chunk_shuffle[player].value if hasattr(multiworld, "chunk_shuffle") else 0,
|
||||
"chunk_shuffle": 0,
|
||||
}
|
||||
|
||||
for factorio_option in Options.factorio_options:
|
||||
for factorio_option, factorio_option_instance in dataclasses.asdict(world.options).items():
|
||||
if factorio_option in ["free_sample_blacklist", "free_sample_whitelist"]:
|
||||
continue
|
||||
template_data[factorio_option] = getattr(multiworld, factorio_option)[player].value
|
||||
template_data[factorio_option] = factorio_option_instance.value
|
||||
|
||||
if getattr(multiworld, "silo")[player].value == Options.Silo.option_randomize_recipe:
|
||||
if world.options.silo == Options.Silo.option_randomize_recipe:
|
||||
template_data["free_sample_blacklist"]["rocket-silo"] = 1
|
||||
|
||||
if getattr(multiworld, "satellite")[player].value == Options.Satellite.option_randomize_recipe:
|
||||
if world.options.satellite == Options.Satellite.option_randomize_recipe:
|
||||
template_data["free_sample_blacklist"]["satellite"] = 1
|
||||
|
||||
template_data["free_sample_blacklist"].update({item: 1 for item in multiworld.free_sample_blacklist[player].value})
|
||||
template_data["free_sample_blacklist"].update({item: 0 for item in multiworld.free_sample_whitelist[player].value})
|
||||
template_data["free_sample_blacklist"].update({item: 1 for item in world.options.free_sample_blacklist.value})
|
||||
template_data["free_sample_blacklist"].update({item: 0 for item in world.options.free_sample_whitelist.value})
|
||||
|
||||
zf_path = os.path.join(output_directory, versioned_mod_name + ".zip")
|
||||
mod = FactorioModFile(zf_path, player=player, player_name=multiworld.player_name[player])
|
||||
mod = FactorioModFile(zf_path, player=player, player_name=world.player_name)
|
||||
|
||||
if world.zip_path:
|
||||
with zipfile.ZipFile(world.zip_path) as zf:
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
from __future__ import annotations
|
||||
import typing
|
||||
|
||||
from dataclasses import dataclass
|
||||
import datetime
|
||||
import typing
|
||||
|
||||
from schema import Schema, Optional, And, Or
|
||||
|
||||
from Options import Choice, OptionDict, OptionSet, Option, DefaultOnToggle, Range, DeathLink, Toggle, \
|
||||
StartInventoryPool
|
||||
from schema import Schema, Optional, And, Or
|
||||
StartInventoryPool, PerGameCommonOptions
|
||||
|
||||
# schema helpers
|
||||
FloatRange = lambda low, high: And(Or(int, float), lambda f: low <= f <= high)
|
||||
@@ -422,50 +425,37 @@ class EnergyLink(Toggle):
|
||||
display_name = "EnergyLink"
|
||||
|
||||
|
||||
factorio_options: typing.Dict[str, type(Option)] = {
|
||||
"max_science_pack": MaxSciencePack,
|
||||
"goal": Goal,
|
||||
"tech_tree_layout": TechTreeLayout,
|
||||
"min_tech_cost": MinTechCost,
|
||||
"max_tech_cost": MaxTechCost,
|
||||
"tech_cost_distribution": TechCostDistribution,
|
||||
"tech_cost_mix": TechCostMix,
|
||||
"ramping_tech_costs": RampingTechCosts,
|
||||
"silo": Silo,
|
||||
"satellite": Satellite,
|
||||
"free_samples": FreeSamples,
|
||||
"tech_tree_information": TechTreeInformation,
|
||||
"starting_items": FactorioStartItems,
|
||||
"free_sample_blacklist": FactorioFreeSampleBlacklist,
|
||||
"free_sample_whitelist": FactorioFreeSampleWhitelist,
|
||||
"recipe_time": RecipeTime,
|
||||
"recipe_ingredients": RecipeIngredients,
|
||||
"recipe_ingredients_offset": RecipeIngredientsOffset,
|
||||
"imported_blueprints": ImportedBlueprint,
|
||||
"world_gen": FactorioWorldGen,
|
||||
"progressive": Progressive,
|
||||
"teleport_traps": TeleportTrapCount,
|
||||
"grenade_traps": GrenadeTrapCount,
|
||||
"cluster_grenade_traps": ClusterGrenadeTrapCount,
|
||||
"artillery_traps": ArtilleryTrapCount,
|
||||
"atomic_rocket_traps": AtomicRocketTrapCount,
|
||||
"attack_traps": AttackTrapCount,
|
||||
"evolution_traps": EvolutionTrapCount,
|
||||
"evolution_trap_increase": EvolutionTrapIncrease,
|
||||
"death_link": DeathLink,
|
||||
"energy_link": EnergyLink,
|
||||
"start_inventory_from_pool": StartInventoryPool,
|
||||
}
|
||||
|
||||
# spoilers below. If you spoil it for yourself, please at least don't spoil it for anyone else.
|
||||
if datetime.datetime.today().month == 4:
|
||||
|
||||
class ChunkShuffle(Toggle):
|
||||
"""Entrance Randomizer."""
|
||||
display_name = "Chunk Shuffle"
|
||||
|
||||
|
||||
if datetime.datetime.today().day > 1:
|
||||
ChunkShuffle.__doc__ += """
|
||||
2023 April Fool's option. Shuffles chunk border transitions."""
|
||||
factorio_options["chunk_shuffle"] = ChunkShuffle
|
||||
@dataclass
|
||||
class FactorioOptions(PerGameCommonOptions):
|
||||
max_science_pack: MaxSciencePack
|
||||
goal: Goal
|
||||
tech_tree_layout: TechTreeLayout
|
||||
min_tech_cost: MinTechCost
|
||||
max_tech_cost: MaxTechCost
|
||||
tech_cost_distribution: TechCostDistribution
|
||||
tech_cost_mix: TechCostMix
|
||||
ramping_tech_costs: RampingTechCosts
|
||||
silo: Silo
|
||||
satellite: Satellite
|
||||
free_samples: FreeSamples
|
||||
tech_tree_information: TechTreeInformation
|
||||
starting_items: FactorioStartItems
|
||||
free_sample_blacklist: FactorioFreeSampleBlacklist
|
||||
free_sample_whitelist: FactorioFreeSampleWhitelist
|
||||
recipe_time: RecipeTime
|
||||
recipe_ingredients: RecipeIngredients
|
||||
recipe_ingredients_offset: RecipeIngredientsOffset
|
||||
imported_blueprints: ImportedBlueprint
|
||||
world_gen: FactorioWorldGen
|
||||
progressive: Progressive
|
||||
teleport_traps: TeleportTrapCount
|
||||
grenade_traps: GrenadeTrapCount
|
||||
cluster_grenade_traps: ClusterGrenadeTrapCount
|
||||
artillery_traps: ArtilleryTrapCount
|
||||
atomic_rocket_traps: AtomicRocketTrapCount
|
||||
attack_traps: AttackTrapCount
|
||||
evolution_traps: EvolutionTrapCount
|
||||
evolution_trap_increase: EvolutionTrapIncrease
|
||||
death_link: DeathLink
|
||||
energy_link: EnergyLink
|
||||
start_inventory_from_pool: StartInventoryPool
|
||||
|
||||
@@ -19,12 +19,10 @@ def _sorter(location: "FactorioScienceLocation"):
|
||||
return location.complexity, location.rel_cost
|
||||
|
||||
|
||||
def get_shapes(factorio_world: "Factorio") -> Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]]:
|
||||
world = factorio_world.multiworld
|
||||
player = factorio_world.player
|
||||
def get_shapes(world: "Factorio") -> Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]]:
|
||||
prerequisites: Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]] = {}
|
||||
layout = world.tech_tree_layout[player].value
|
||||
locations: List["FactorioScienceLocation"] = sorted(factorio_world.science_locations, key=lambda loc: loc.name)
|
||||
layout = world.options.tech_tree_layout.value
|
||||
locations: List["FactorioScienceLocation"] = sorted(world.science_locations, key=lambda loc: loc.name)
|
||||
world.random.shuffle(locations)
|
||||
|
||||
if layout == TechTreeLayout.option_single:
|
||||
@@ -247,5 +245,5 @@ def get_shapes(factorio_world: "Factorio") -> Dict["FactorioScienceLocation", Se
|
||||
else:
|
||||
raise NotImplementedError(f"Layout {layout} is not implemented.")
|
||||
|
||||
factorio_world.tech_tree_layout_prerequisites = prerequisites
|
||||
world.tech_tree_layout_prerequisites = prerequisites
|
||||
return prerequisites
|
||||
|
||||
@@ -13,12 +13,11 @@ import Utils
|
||||
from . import Options
|
||||
|
||||
factorio_tech_id = factorio_base_id = 2 ** 17
|
||||
# Factorio technologies are imported from a .json document in /data
|
||||
source_folder = os.path.join(os.path.dirname(__file__), "data")
|
||||
|
||||
pool = ThreadPoolExecutor(1)
|
||||
|
||||
|
||||
# Factorio technologies are imported from a .json document in /data
|
||||
def load_json_data(data_name: str) -> Union[List[str], Dict[str, Any]]:
|
||||
return orjson.loads(pkgutil.get_data(__name__, "data/" + data_name + ".json"))
|
||||
|
||||
@@ -99,7 +98,7 @@ class CustomTechnology(Technology):
|
||||
and ((ingredients & {"chemical-science-pack", "production-science-pack", "utility-science-pack"})
|
||||
or origin.name == "rocket-silo")
|
||||
self.player = player
|
||||
if origin.name not in world.worlds[player].special_nodes:
|
||||
if origin.name not in world.special_nodes:
|
||||
if military_allowed:
|
||||
ingredients.add("military-science-pack")
|
||||
ingredients = list(ingredients)
|
||||
|
||||
@@ -11,7 +11,7 @@ from worlds.LauncherComponents import Component, components, Type, launch_subpro
|
||||
from worlds.generic import Rules
|
||||
from .Locations import location_pools, location_table
|
||||
from .Mod import generate_mod
|
||||
from .Options import factorio_options, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal, TechCostDistribution
|
||||
from .Options import FactorioOptions, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal, TechCostDistribution
|
||||
from .Shapes import get_shapes
|
||||
from .Technologies import base_tech_table, recipe_sources, base_technology_table, \
|
||||
all_ingredient_names, all_product_sources, required_technologies, get_rocket_requirements, \
|
||||
@@ -89,13 +89,15 @@ class Factorio(World):
|
||||
advancement_technologies: typing.Set[str]
|
||||
|
||||
web = FactorioWeb()
|
||||
options_dataclass = FactorioOptions
|
||||
options: FactorioOptions
|
||||
|
||||
item_name_to_id = all_items
|
||||
location_name_to_id = location_table
|
||||
item_name_groups = {
|
||||
"Progressive": set(progressive_tech_table.keys()),
|
||||
}
|
||||
required_client_version = (0, 4, 2)
|
||||
required_client_version = (0, 5, 0)
|
||||
|
||||
ordered_science_packs: typing.List[str] = MaxSciencePack.get_ordered_science_packs()
|
||||
tech_tree_layout_prerequisites: typing.Dict[FactorioScienceLocation, typing.Set[FactorioScienceLocation]]
|
||||
@@ -117,32 +119,32 @@ class Factorio(World):
|
||||
|
||||
def generate_early(self) -> None:
|
||||
# if max < min, then swap max and min
|
||||
if self.multiworld.max_tech_cost[self.player] < self.multiworld.min_tech_cost[self.player]:
|
||||
self.multiworld.min_tech_cost[self.player].value, self.multiworld.max_tech_cost[self.player].value = \
|
||||
self.multiworld.max_tech_cost[self.player].value, self.multiworld.min_tech_cost[self.player].value
|
||||
self.tech_mix = self.multiworld.tech_cost_mix[self.player]
|
||||
self.skip_silo = self.multiworld.silo[self.player].value == Silo.option_spawn
|
||||
if self.options.max_tech_cost < self.options.min_tech_cost:
|
||||
self.options.min_tech_cost.value, self.options.max_tech_cost.value = \
|
||||
self.options.max_tech_cost.value, self.options.min_tech_cost.value
|
||||
self.tech_mix = self.options.tech_cost_mix.value
|
||||
self.skip_silo = self.options.silo.value == Silo.option_spawn
|
||||
|
||||
def create_regions(self):
|
||||
player = self.player
|
||||
random = self.multiworld.random
|
||||
random = self.random
|
||||
nauvis = Region("Nauvis", player, self.multiworld)
|
||||
|
||||
location_count = len(base_tech_table) - len(useless_technologies) - self.skip_silo + \
|
||||
self.multiworld.evolution_traps[player] + \
|
||||
self.multiworld.attack_traps[player] + \
|
||||
self.multiworld.teleport_traps[player] + \
|
||||
self.multiworld.grenade_traps[player] + \
|
||||
self.multiworld.cluster_grenade_traps[player] + \
|
||||
self.multiworld.atomic_rocket_traps[player] + \
|
||||
self.multiworld.artillery_traps[player]
|
||||
self.options.evolution_traps + \
|
||||
self.options.attack_traps + \
|
||||
self.options.teleport_traps + \
|
||||
self.options.grenade_traps + \
|
||||
self.options.cluster_grenade_traps + \
|
||||
self.options.atomic_rocket_traps + \
|
||||
self.options.artillery_traps
|
||||
|
||||
location_pool = []
|
||||
|
||||
for pack in sorted(self.multiworld.max_science_pack[self.player].get_allowed_packs()):
|
||||
for pack in sorted(self.options.max_science_pack.get_allowed_packs()):
|
||||
location_pool.extend(location_pools[pack])
|
||||
try:
|
||||
location_names = self.multiworld.random.sample(location_pool, location_count)
|
||||
location_names = random.sample(location_pool, location_count)
|
||||
except ValueError as e:
|
||||
# should be "ValueError: Sample larger than population or is negative"
|
||||
raise Exception("Too many traps for too few locations. Either decrease the trap count, "
|
||||
@@ -150,9 +152,9 @@ class Factorio(World):
|
||||
|
||||
self.science_locations = [FactorioScienceLocation(player, loc_name, self.location_name_to_id[loc_name], nauvis)
|
||||
for loc_name in location_names]
|
||||
distribution: TechCostDistribution = self.multiworld.tech_cost_distribution[self.player]
|
||||
min_cost = self.multiworld.min_tech_cost[self.player]
|
||||
max_cost = self.multiworld.max_tech_cost[self.player]
|
||||
distribution: TechCostDistribution = self.options.tech_cost_distribution
|
||||
min_cost = self.options.min_tech_cost.value
|
||||
max_cost = self.options.max_tech_cost.value
|
||||
if distribution == distribution.option_even:
|
||||
rand_values = (random.randint(min_cost, max_cost) for _ in self.science_locations)
|
||||
else:
|
||||
@@ -161,7 +163,7 @@ class Factorio(World):
|
||||
distribution.option_high: max_cost}[distribution.value]
|
||||
rand_values = (random.triangular(min_cost, max_cost, mode) for _ in self.science_locations)
|
||||
rand_values = sorted(rand_values)
|
||||
if self.multiworld.ramping_tech_costs[self.player]:
|
||||
if self.options.ramping_tech_costs:
|
||||
def sorter(loc: FactorioScienceLocation):
|
||||
return loc.complexity, loc.rel_cost
|
||||
else:
|
||||
@@ -176,7 +178,7 @@ class Factorio(World):
|
||||
event = FactorioItem("Victory", ItemClassification.progression, None, player)
|
||||
location.place_locked_item(event)
|
||||
|
||||
for ingredient in sorted(self.multiworld.max_science_pack[self.player].get_allowed_packs()):
|
||||
for ingredient in sorted(self.options.max_science_pack.get_allowed_packs()):
|
||||
location = FactorioLocation(player, f"Automate {ingredient}", None, nauvis)
|
||||
nauvis.locations.append(location)
|
||||
event = FactorioItem(f"Automated {ingredient}", ItemClassification.progression, None, player)
|
||||
@@ -185,24 +187,23 @@ class Factorio(World):
|
||||
self.multiworld.regions.append(nauvis)
|
||||
|
||||
def create_items(self) -> None:
|
||||
player = self.player
|
||||
self.custom_technologies = self.set_custom_technologies()
|
||||
self.set_custom_recipes()
|
||||
traps = ("Evolution", "Attack", "Teleport", "Grenade", "Cluster Grenade", "Artillery", "Atomic Rocket")
|
||||
for trap_name in traps:
|
||||
self.multiworld.itempool.extend(self.create_item(f"{trap_name} Trap") for _ in
|
||||
range(getattr(self.multiworld,
|
||||
f"{trap_name.lower().replace(' ', '_')}_traps")[player]))
|
||||
range(getattr(self.options,
|
||||
f"{trap_name.lower().replace(' ', '_')}_traps")))
|
||||
|
||||
want_progressives = collections.defaultdict(lambda: self.multiworld.progressive[player].
|
||||
want_progressives(self.multiworld.random))
|
||||
want_progressives = collections.defaultdict(lambda: self.options.progressive.
|
||||
want_progressives(self.random))
|
||||
|
||||
cost_sorted_locations = sorted(self.science_locations, key=lambda location: location.name)
|
||||
special_index = {"automation": 0,
|
||||
"logistics": 1,
|
||||
"rocket-silo": -1}
|
||||
loc: FactorioScienceLocation
|
||||
if self.multiworld.tech_tree_information[player] == TechTreeInformation.option_full:
|
||||
if self.options.tech_tree_information == TechTreeInformation.option_full:
|
||||
# mark all locations as pre-hinted
|
||||
for loc in self.science_locations:
|
||||
loc.revealed = True
|
||||
@@ -229,14 +230,13 @@ class Factorio(World):
|
||||
loc.revealed = True
|
||||
|
||||
def set_rules(self):
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
shapes = get_shapes(self)
|
||||
|
||||
for ingredient in self.multiworld.max_science_pack[self.player].get_allowed_packs():
|
||||
location = world.get_location(f"Automate {ingredient}", player)
|
||||
for ingredient in self.options.max_science_pack.get_allowed_packs():
|
||||
location = self.get_location(f"Automate {ingredient}")
|
||||
|
||||
if self.multiworld.recipe_ingredients[self.player]:
|
||||
if self.options.recipe_ingredients:
|
||||
custom_recipe = self.custom_recipes[ingredient]
|
||||
|
||||
location.access_rule = lambda state, ingredient=ingredient, custom_recipe=custom_recipe: \
|
||||
@@ -257,30 +257,30 @@ class Factorio(World):
|
||||
prerequisites: all(state.can_reach(loc) for loc in locations))
|
||||
|
||||
silo_recipe = None
|
||||
if self.multiworld.silo[self.player] == Silo.option_spawn:
|
||||
if self.options.silo == Silo.option_spawn:
|
||||
silo_recipe = self.custom_recipes["rocket-silo"] if "rocket-silo" in self.custom_recipes \
|
||||
else next(iter(all_product_sources.get("rocket-silo")))
|
||||
part_recipe = self.custom_recipes["rocket-part"]
|
||||
satellite_recipe = None
|
||||
if self.multiworld.goal[self.player] == Goal.option_satellite:
|
||||
if self.options.goal == Goal.option_satellite:
|
||||
satellite_recipe = self.custom_recipes["satellite"] if "satellite" in self.custom_recipes \
|
||||
else next(iter(all_product_sources.get("satellite")))
|
||||
victory_tech_names = get_rocket_requirements(silo_recipe, part_recipe, satellite_recipe)
|
||||
if self.multiworld.silo[self.player] != Silo.option_spawn:
|
||||
if self.options.silo != Silo.option_spawn:
|
||||
victory_tech_names.add("rocket-silo")
|
||||
world.get_location("Rocket Launch", player).access_rule = lambda state: all(state.has(technology, player)
|
||||
for technology in
|
||||
victory_tech_names)
|
||||
self.get_location("Rocket Launch").access_rule = lambda state: all(state.has(technology, player)
|
||||
for technology in
|
||||
victory_tech_names)
|
||||
|
||||
world.completion_condition[player] = lambda state: state.has('Victory', player)
|
||||
self.multiworld.completion_condition[player] = lambda state: state.has('Victory', player)
|
||||
|
||||
def generate_basic(self):
|
||||
map_basic_settings = self.multiworld.world_gen[self.player].value["basic"]
|
||||
map_basic_settings = self.options.world_gen.value["basic"]
|
||||
if map_basic_settings.get("seed", None) is None: # allow seed 0
|
||||
# 32 bit uint
|
||||
map_basic_settings["seed"] = self.multiworld.per_slot_randoms[self.player].randint(0, 2 ** 32 - 1)
|
||||
map_basic_settings["seed"] = self.random.randint(0, 2 ** 32 - 1)
|
||||
|
||||
start_location_hints: typing.Set[str] = self.multiworld.start_location_hints[self.player].value
|
||||
start_location_hints: typing.Set[str] = self.options.start_location_hints.value
|
||||
|
||||
for loc in self.science_locations:
|
||||
# show start_location_hints ingame
|
||||
@@ -304,8 +304,6 @@ class Factorio(World):
|
||||
|
||||
return super(Factorio, self).collect_item(state, item, remove)
|
||||
|
||||
option_definitions = factorio_options
|
||||
|
||||
@classmethod
|
||||
def stage_write_spoiler(cls, world, spoiler_handle):
|
||||
factorio_players = world.get_game_players(cls.game)
|
||||
@@ -345,7 +343,7 @@ class Factorio(World):
|
||||
# have to first sort for determinism, while filtering out non-stacking items
|
||||
pool: typing.List[str] = sorted(pool & valid_ingredients)
|
||||
# then sort with random data to shuffle
|
||||
self.multiworld.random.shuffle(pool)
|
||||
self.random.shuffle(pool)
|
||||
target_raw = int(sum((count for ingredient, count in original.base_cost.items())) * factor)
|
||||
target_energy = original.total_energy * factor
|
||||
target_num_ingredients = len(original.ingredients) + ingredients_offset
|
||||
@@ -389,7 +387,7 @@ class Factorio(World):
|
||||
if min_num > max_num:
|
||||
fallback_pool.append(ingredient)
|
||||
continue # can't use that ingredient
|
||||
num = self.multiworld.random.randint(min_num, max_num)
|
||||
num = self.random.randint(min_num, max_num)
|
||||
new_ingredients[ingredient] = num
|
||||
remaining_raw -= num * ingredient_raw
|
||||
remaining_energy -= num * ingredient_energy
|
||||
@@ -433,66 +431,66 @@ class Factorio(World):
|
||||
|
||||
def set_custom_technologies(self):
|
||||
custom_technologies = {}
|
||||
allowed_packs = self.multiworld.max_science_pack[self.player].get_allowed_packs()
|
||||
allowed_packs = self.options.max_science_pack.get_allowed_packs()
|
||||
for technology_name, technology in base_technology_table.items():
|
||||
custom_technologies[technology_name] = technology.get_custom(self.multiworld, allowed_packs, self.player)
|
||||
custom_technologies[technology_name] = technology.get_custom(self, allowed_packs, self.player)
|
||||
return custom_technologies
|
||||
|
||||
def set_custom_recipes(self):
|
||||
ingredients_offset = self.multiworld.recipe_ingredients_offset[self.player]
|
||||
ingredients_offset = self.options.recipe_ingredients_offset
|
||||
original_rocket_part = recipes["rocket-part"]
|
||||
science_pack_pools = get_science_pack_pools()
|
||||
valid_pool = sorted(science_pack_pools[self.multiworld.max_science_pack[self.player].get_max_pack()] & valid_ingredients)
|
||||
self.multiworld.random.shuffle(valid_pool)
|
||||
valid_pool = sorted(science_pack_pools[self.options.max_science_pack.get_max_pack()] & valid_ingredients)
|
||||
self.random.shuffle(valid_pool)
|
||||
self.custom_recipes = {"rocket-part": Recipe("rocket-part", original_rocket_part.category,
|
||||
{valid_pool[x]: 10 for x in range(3 + ingredients_offset)},
|
||||
original_rocket_part.products,
|
||||
original_rocket_part.energy)}
|
||||
|
||||
if self.multiworld.recipe_ingredients[self.player]:
|
||||
if self.options.recipe_ingredients:
|
||||
valid_pool = []
|
||||
for pack in self.multiworld.max_science_pack[self.player].get_ordered_science_packs():
|
||||
for pack in self.options.max_science_pack.get_ordered_science_packs():
|
||||
valid_pool += sorted(science_pack_pools[pack])
|
||||
self.multiworld.random.shuffle(valid_pool)
|
||||
self.random.shuffle(valid_pool)
|
||||
if pack in recipes: # skips over space science pack
|
||||
new_recipe = self.make_quick_recipe(recipes[pack], valid_pool, ingredients_offset=
|
||||
ingredients_offset)
|
||||
ingredients_offset.value)
|
||||
self.custom_recipes[pack] = new_recipe
|
||||
|
||||
if self.multiworld.silo[self.player].value == Silo.option_randomize_recipe \
|
||||
or self.multiworld.satellite[self.player].value == Satellite.option_randomize_recipe:
|
||||
if self.options.silo.value == Silo.option_randomize_recipe \
|
||||
or self.options.satellite.value == Satellite.option_randomize_recipe:
|
||||
valid_pool = set()
|
||||
for pack in sorted(self.multiworld.max_science_pack[self.player].get_allowed_packs()):
|
||||
for pack in sorted(self.options.max_science_pack.get_allowed_packs()):
|
||||
valid_pool |= science_pack_pools[pack]
|
||||
|
||||
if self.multiworld.silo[self.player].value == Silo.option_randomize_recipe:
|
||||
if self.options.silo.value == Silo.option_randomize_recipe:
|
||||
new_recipe = self.make_balanced_recipe(
|
||||
recipes["rocket-silo"], valid_pool,
|
||||
factor=(self.multiworld.max_science_pack[self.player].value + 1) / 7,
|
||||
ingredients_offset=ingredients_offset)
|
||||
factor=(self.options.max_science_pack.value + 1) / 7,
|
||||
ingredients_offset=ingredients_offset.value)
|
||||
self.custom_recipes["rocket-silo"] = new_recipe
|
||||
|
||||
if self.multiworld.satellite[self.player].value == Satellite.option_randomize_recipe:
|
||||
if self.options.satellite.value == Satellite.option_randomize_recipe:
|
||||
new_recipe = self.make_balanced_recipe(
|
||||
recipes["satellite"], valid_pool,
|
||||
factor=(self.multiworld.max_science_pack[self.player].value + 1) / 7,
|
||||
ingredients_offset=ingredients_offset)
|
||||
factor=(self.options.max_science_pack.value + 1) / 7,
|
||||
ingredients_offset=ingredients_offset.value)
|
||||
self.custom_recipes["satellite"] = new_recipe
|
||||
bridge = "ap-energy-bridge"
|
||||
new_recipe = self.make_quick_recipe(
|
||||
Recipe(bridge, "crafting", {"replace_1": 1, "replace_2": 1, "replace_3": 1,
|
||||
"replace_4": 1, "replace_5": 1, "replace_6": 1},
|
||||
{bridge: 1}, 10),
|
||||
sorted(science_pack_pools[self.multiworld.max_science_pack[self.player].get_ordered_science_packs()[0]]),
|
||||
ingredients_offset=ingredients_offset)
|
||||
sorted(science_pack_pools[self.options.max_science_pack.get_ordered_science_packs()[0]]),
|
||||
ingredients_offset=ingredients_offset.value)
|
||||
for ingredient_name in new_recipe.ingredients:
|
||||
new_recipe.ingredients[ingredient_name] = self.multiworld.random.randint(50, 500)
|
||||
new_recipe.ingredients[ingredient_name] = self.random.randint(50, 500)
|
||||
self.custom_recipes[bridge] = new_recipe
|
||||
|
||||
needed_recipes = self.multiworld.max_science_pack[self.player].get_allowed_packs() | {"rocket-part"}
|
||||
if self.multiworld.silo[self.player] != Silo.option_spawn:
|
||||
needed_recipes = self.options.max_science_pack.get_allowed_packs() | {"rocket-part"}
|
||||
if self.options.silo != Silo.option_spawn:
|
||||
needed_recipes |= {"rocket-silo"}
|
||||
if self.multiworld.goal[self.player].value == Goal.option_satellite:
|
||||
if self.options.goal.value == Goal.option_satellite:
|
||||
needed_recipes |= {"satellite"}
|
||||
|
||||
for recipe in needed_recipes:
|
||||
@@ -542,7 +540,8 @@ class FactorioScienceLocation(FactorioLocation):
|
||||
|
||||
self.ingredients = {Factorio.ordered_science_packs[self.complexity]: 1}
|
||||
for complexity in range(self.complexity):
|
||||
if parent.multiworld.tech_cost_mix[self.player] > parent.multiworld.random.randint(0, 99):
|
||||
if (parent.multiworld.worlds[self.player].options.tech_cost_mix >
|
||||
parent.multiworld.worlds[self.player].random.randint(0, 99)):
|
||||
self.ingredients[Factorio.ordered_science_packs[complexity]] = 1
|
||||
|
||||
@property
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
### What to do if Lumafly fails to find your installation directory
|
||||
1. Find the directory manually.
|
||||
* Xbox Game Pass:
|
||||
1. Enter the XBox app and move your mouse over "Hollow Knight" on the left sidebar.
|
||||
1. Enter the Xbox app and move your mouse over "Hollow Knight" on the left sidebar.
|
||||
2. Click the three points then click "Manage".
|
||||
3. Go to the "Files" tab and select "Browse...".
|
||||
4. Click "Hollow Knight", then "Content", then click the path bar and copy it.
|
||||
|
||||
@@ -149,6 +149,8 @@ class MagpieBridge:
|
||||
item_tracker = None
|
||||
ws = None
|
||||
features = []
|
||||
slot_data = {}
|
||||
|
||||
async def handler(self, websocket):
|
||||
self.ws = websocket
|
||||
while True:
|
||||
@@ -163,6 +165,9 @@ class MagpieBridge:
|
||||
await self.send_all_inventory()
|
||||
if "checks" in self.features:
|
||||
await self.send_all_checks()
|
||||
if "slot_data" in self.features:
|
||||
await self.send_slot_data(self.slot_data)
|
||||
|
||||
# Translate renamed IDs back to LADXR IDs
|
||||
@staticmethod
|
||||
def fixup_id(the_id):
|
||||
@@ -222,6 +227,18 @@ class MagpieBridge:
|
||||
return
|
||||
await gps.send_location(self.ws)
|
||||
|
||||
async def send_slot_data(self, slot_data):
|
||||
if not self.ws:
|
||||
return
|
||||
|
||||
logger.debug("Sending slot_data to magpie.")
|
||||
message = {
|
||||
"type": "slot_data",
|
||||
"slot_data": slot_data
|
||||
}
|
||||
|
||||
await self.ws.send(json.dumps(message))
|
||||
|
||||
async def serve(self):
|
||||
async with websockets.serve(lambda w: self.handler(w), "", 17026, logger=logger):
|
||||
await asyncio.Future() # run forever
|
||||
@@ -237,4 +254,3 @@ class MagpieBridge:
|
||||
await self.send_all_inventory()
|
||||
else:
|
||||
await self.send_inventory_diffs()
|
||||
|
||||
|
||||
@@ -512,3 +512,31 @@ class LinksAwakeningWorld(World):
|
||||
if change and item.name in self.rupees:
|
||||
state.prog_items[self.player]["RUPEES"] -= self.rupees[item.name]
|
||||
return change
|
||||
|
||||
def fill_slot_data(self):
|
||||
slot_data = {}
|
||||
|
||||
if not self.multiworld.is_race:
|
||||
# all of these option are NOT used by the LADX- or Text-Client.
|
||||
# they are used by Magpie tracker (https://github.com/kbranch/Magpie/wiki/Autotracker-API)
|
||||
# for convenient auto-tracking of the generated settings and adjusting the tracker accordingly
|
||||
|
||||
slot_options = ["instrument_count"]
|
||||
|
||||
slot_options_display_name = [
|
||||
"goal", "logic", "tradequest", "rooster",
|
||||
"experimental_dungeon_shuffle", "experimental_entrance_shuffle", "trendy_game", "gfxmod",
|
||||
"shuffle_nightmare_keys", "shuffle_small_keys", "shuffle_maps",
|
||||
"shuffle_compasses", "shuffle_stone_beaks", "shuffle_instruments", "nag_messages"
|
||||
]
|
||||
|
||||
# use the default behaviour to grab options
|
||||
slot_data = self.options.as_dict(*slot_options)
|
||||
|
||||
# for options which should not get the internal int value but the display name use the extra handling
|
||||
slot_data.update({
|
||||
option: value.current_key
|
||||
for option, value in dataclasses.asdict(self.options).items() if option in slot_options_display_name
|
||||
})
|
||||
|
||||
return slot_data
|
||||
|
||||
@@ -482,7 +482,9 @@
|
||||
Crossroads:
|
||||
door: Crossroads Entrance
|
||||
The Tenacious:
|
||||
door: Tenacious Entrance
|
||||
- door: Tenacious Entrance
|
||||
- room: The Tenacious
|
||||
door: Shortcut to Hub Room
|
||||
Near Far Area: True
|
||||
Hedge Maze:
|
||||
door: Shortcut to Hedge Maze
|
||||
|
||||
Binary file not shown.
@@ -215,13 +215,13 @@ def shuffle_portals(world: "MessengerWorld") -> None:
|
||||
|
||||
if "Portal" in warp:
|
||||
exit_string += "Portal"
|
||||
world.portal_mapping.append(int(f"{REGION_ORDER.index(parent)}00"))
|
||||
world.portal_mapping.insert(PORTALS.index(in_portal), int(f"{REGION_ORDER.index(parent)}00"))
|
||||
elif warp in SHOP_POINTS[parent]:
|
||||
exit_string += f"{warp} Shop"
|
||||
world.portal_mapping.append(int(f"{REGION_ORDER.index(parent)}1{SHOP_POINTS[parent].index(warp)}"))
|
||||
world.portal_mapping.insert(PORTALS.index(in_portal), int(f"{REGION_ORDER.index(parent)}1{SHOP_POINTS[parent].index(warp)}"))
|
||||
else:
|
||||
exit_string += f"{warp} Checkpoint"
|
||||
world.portal_mapping.append(int(f"{REGION_ORDER.index(parent)}2{CHECKPOINTS[parent].index(warp)}"))
|
||||
world.portal_mapping.insert(PORTALS.index(in_portal), int(f"{REGION_ORDER.index(parent)}2{CHECKPOINTS[parent].index(warp)}"))
|
||||
|
||||
world.spoiler_portal_mapping[in_portal] = exit_string
|
||||
connect_portal(world, in_portal, exit_string)
|
||||
@@ -230,12 +230,15 @@ def shuffle_portals(world: "MessengerWorld") -> None:
|
||||
|
||||
def handle_planned_portals(plando_connections: List[PlandoConnection]) -> None:
|
||||
"""checks the provided plando connections for portals and connects them"""
|
||||
nonlocal available_portals
|
||||
|
||||
for connection in plando_connections:
|
||||
if connection.entrance not in PORTALS:
|
||||
continue
|
||||
# let it crash here if input is invalid
|
||||
create_mapping(connection.entrance, connection.exit)
|
||||
available_portals.remove(connection.exit)
|
||||
parent = create_mapping(connection.entrance, connection.exit)
|
||||
world.plando_portals.append(connection.entrance)
|
||||
if shuffle_type < ShufflePortals.option_anywhere:
|
||||
available_portals = [port for port in available_portals if port not in shop_points[parent]]
|
||||
|
||||
shuffle_type = world.options.shuffle_portals
|
||||
shop_points = deepcopy(SHOP_POINTS)
|
||||
@@ -251,8 +254,13 @@ def shuffle_portals(world: "MessengerWorld") -> None:
|
||||
plando = world.options.portal_plando.value
|
||||
if not plando:
|
||||
plando = world.options.plando_connections.value
|
||||
if plando and world.multiworld.plando_options & PlandoOptions.connections:
|
||||
handle_planned_portals(plando)
|
||||
if plando and world.multiworld.plando_options & PlandoOptions.connections and not world.plando_portals:
|
||||
try:
|
||||
handle_planned_portals(plando)
|
||||
# any failure i expect will trigger on available_portals.remove
|
||||
except ValueError:
|
||||
raise ValueError(f"Unable to complete portal plando for Player {world.player_name}. "
|
||||
f"If you attempted to plando a checkpoint, checkpoints must be shuffled.")
|
||||
|
||||
for portal in PORTALS:
|
||||
if portal in world.plando_portals:
|
||||
@@ -276,8 +284,13 @@ def disconnect_portals(world: "MessengerWorld") -> None:
|
||||
entrance.connected_region = None
|
||||
if portal in world.spoiler_portal_mapping:
|
||||
del world.spoiler_portal_mapping[portal]
|
||||
if len(world.portal_mapping) > len(world.spoiler_portal_mapping):
|
||||
world.portal_mapping = world.portal_mapping[:len(world.spoiler_portal_mapping)]
|
||||
if world.plando_portals:
|
||||
indexes = [PORTALS.index(portal) for portal in world.plando_portals]
|
||||
planned_portals = []
|
||||
for index, portal_coord in enumerate(world.portal_mapping):
|
||||
if index in indexes:
|
||||
planned_portals.append(portal_coord)
|
||||
world.portal_mapping = planned_portals
|
||||
|
||||
|
||||
def validate_portals(world: "MessengerWorld") -> bool:
|
||||
|
||||
@@ -85,7 +85,7 @@ class MLSSClient(BizHawkClient):
|
||||
if not self.seed_verify:
|
||||
seed = await bizhawk.read(ctx.bizhawk_ctx, [(0xDF00A0, len(ctx.seed_name), "ROM")])
|
||||
seed = seed[0].decode("UTF-8")
|
||||
if seed != ctx.seed_name:
|
||||
if seed not in ctx.seed_name:
|
||||
logger.info(
|
||||
"ERROR: The ROM you loaded is for a different game of AP. "
|
||||
"Please make sure the host has sent you the correct patch file,"
|
||||
@@ -143,17 +143,30 @@ class MLSSClient(BizHawkClient):
|
||||
# If RAM address isn't 0x0 yet break out and try again later to give the rest of the items
|
||||
for i in range(len(ctx.items_received) - received_index):
|
||||
item_data = items_by_id[ctx.items_received[received_index + i].item]
|
||||
b = await bizhawk.guarded_read(ctx.bizhawk_ctx, [(0x3057, 1, "EWRAM")], [(0x3057, [0x0], "EWRAM")])
|
||||
if b is None:
|
||||
result = False
|
||||
total = 0
|
||||
while not result:
|
||||
await asyncio.sleep(0.05)
|
||||
total += 0.05
|
||||
result = await bizhawk.guarded_write(
|
||||
ctx.bizhawk_ctx,
|
||||
[
|
||||
(0x3057, [id_to_RAM(item_data.itemID)], "EWRAM")
|
||||
],
|
||||
[(0x3057, [0x0], "EWRAM")]
|
||||
)
|
||||
if result:
|
||||
total = 0
|
||||
if total >= 1:
|
||||
break
|
||||
if not result:
|
||||
break
|
||||
await bizhawk.write(
|
||||
ctx.bizhawk_ctx,
|
||||
[
|
||||
(0x3057, [id_to_RAM(item_data.itemID)], "EWRAM"),
|
||||
(0x4808, [(received_index + i + 1) // 0x100, (received_index + i + 1) % 0x100], "EWRAM"),
|
||||
],
|
||||
]
|
||||
)
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
# Early return and location send if you are currently in a shop,
|
||||
# since other flags aren't going to change
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
flying = [
|
||||
0x14,
|
||||
0x1D,
|
||||
0x32,
|
||||
0x33,
|
||||
0x40,
|
||||
0x4C
|
||||
]
|
||||
|
||||
@@ -23,7 +26,6 @@ enemies = [
|
||||
0x5032AC,
|
||||
0x5032CC,
|
||||
0x5032EC,
|
||||
0x50330C,
|
||||
0x50332C,
|
||||
0x50334C,
|
||||
0x50336C,
|
||||
@@ -151,7 +153,7 @@ enemies = [
|
||||
0x50458C,
|
||||
0x5045AC,
|
||||
0x50468C,
|
||||
0x5046CC,
|
||||
# 0x5046CC, 6 enemy formation
|
||||
0x5046EC,
|
||||
0x50470C
|
||||
]
|
||||
|
||||
@@ -78,21 +78,21 @@ itemList: typing.List[ItemData] = [
|
||||
ItemData(77771060, "Beanstar Piece 3", ItemClassification.progression, 0x67),
|
||||
ItemData(77771061, "Beanstar Piece 4", ItemClassification.progression, 0x70),
|
||||
ItemData(77771062, "Spangle", ItemClassification.progression, 0x72),
|
||||
ItemData(77771063, "Beanlet 1", ItemClassification.filler, 0x73),
|
||||
ItemData(77771064, "Beanlet 2", ItemClassification.filler, 0x74),
|
||||
ItemData(77771065, "Beanlet 3", ItemClassification.filler, 0x75),
|
||||
ItemData(77771066, "Beanlet 4", ItemClassification.filler, 0x76),
|
||||
ItemData(77771067, "Beanlet 5", ItemClassification.filler, 0x77),
|
||||
ItemData(77771068, "Beanstone 1", ItemClassification.filler, 0x80),
|
||||
ItemData(77771069, "Beanstone 2", ItemClassification.filler, 0x81),
|
||||
ItemData(77771070, "Beanstone 3", ItemClassification.filler, 0x82),
|
||||
ItemData(77771071, "Beanstone 4", ItemClassification.filler, 0x83),
|
||||
ItemData(77771072, "Beanstone 5", ItemClassification.filler, 0x84),
|
||||
ItemData(77771073, "Beanstone 6", ItemClassification.filler, 0x85),
|
||||
ItemData(77771074, "Beanstone 7", ItemClassification.filler, 0x86),
|
||||
ItemData(77771075, "Beanstone 8", ItemClassification.filler, 0x87),
|
||||
ItemData(77771076, "Beanstone 9", ItemClassification.filler, 0x90),
|
||||
ItemData(77771077, "Beanstone 10", ItemClassification.filler, 0x91),
|
||||
ItemData(77771063, "Beanlet 1", ItemClassification.useful, 0x73),
|
||||
ItemData(77771064, "Beanlet 2", ItemClassification.useful, 0x74),
|
||||
ItemData(77771065, "Beanlet 3", ItemClassification.useful, 0x75),
|
||||
ItemData(77771066, "Beanlet 4", ItemClassification.useful, 0x76),
|
||||
ItemData(77771067, "Beanlet 5", ItemClassification.useful, 0x77),
|
||||
ItemData(77771068, "Beanstone 1", ItemClassification.useful, 0x80),
|
||||
ItemData(77771069, "Beanstone 2", ItemClassification.useful, 0x81),
|
||||
ItemData(77771070, "Beanstone 3", ItemClassification.useful, 0x82),
|
||||
ItemData(77771071, "Beanstone 4", ItemClassification.useful, 0x83),
|
||||
ItemData(77771072, "Beanstone 5", ItemClassification.useful, 0x84),
|
||||
ItemData(77771073, "Beanstone 6", ItemClassification.useful, 0x85),
|
||||
ItemData(77771074, "Beanstone 7", ItemClassification.useful, 0x86),
|
||||
ItemData(77771075, "Beanstone 8", ItemClassification.useful, 0x87),
|
||||
ItemData(77771076, "Beanstone 9", ItemClassification.useful, 0x90),
|
||||
ItemData(77771077, "Beanstone 10", ItemClassification.useful, 0x91),
|
||||
ItemData(77771078, "Secret Scroll 1", ItemClassification.useful, 0x92),
|
||||
ItemData(77771079, "Secret Scroll 2", ItemClassification.useful, 0x93),
|
||||
ItemData(77771080, "Castle Badge", ItemClassification.useful, 0x9F),
|
||||
|
||||
@@ -4,9 +4,6 @@ from BaseClasses import Location
|
||||
|
||||
|
||||
class LocationData:
|
||||
name: str = ""
|
||||
id: int = 0x00
|
||||
|
||||
def __init__(self, name, id_, itemType):
|
||||
self.name = name
|
||||
self.itemType = itemType
|
||||
@@ -93,8 +90,8 @@ mainArea: typing.List[LocationData] = [
|
||||
LocationData("Hoohoo Mountain Below Summit Block 1", 0x39D873, 0),
|
||||
LocationData("Hoohoo Mountain Below Summit Block 2", 0x39D87B, 0),
|
||||
LocationData("Hoohoo Mountain Below Summit Block 3", 0x39D883, 0),
|
||||
LocationData("Hoohoo Mountain After Hoohooros Block 1", 0x39D890, 0),
|
||||
LocationData("Hoohoo Mountain After Hoohooros Block 2", 0x39D8A0, 0),
|
||||
LocationData("Hoohoo Mountain Past Hoohooros Block 1", 0x39D890, 0),
|
||||
LocationData("Hoohoo Mountain Past Hoohooros Block 2", 0x39D8A0, 0),
|
||||
LocationData("Hoohoo Mountain Hoohooros Room Block 1", 0x39D8AD, 0),
|
||||
LocationData("Hoohoo Mountain Hoohooros Room Block 2", 0x39D8B5, 0),
|
||||
LocationData("Hoohoo Mountain Before Hoohooros Block", 0x39D8D2, 0),
|
||||
@@ -104,7 +101,7 @@ mainArea: typing.List[LocationData] = [
|
||||
LocationData("Hoohoo Mountain Room 1 Block 2", 0x39D924, 0),
|
||||
LocationData("Hoohoo Mountain Room 1 Block 3", 0x39D92C, 0),
|
||||
LocationData("Hoohoo Mountain Base Room 1 Block", 0x39D939, 0),
|
||||
LocationData("Hoohoo Village Right Side Block", 0x39D957, 0),
|
||||
LocationData("Hoohoo Village Eastside Block", 0x39D957, 0),
|
||||
LocationData("Hoohoo Village Bridge Room Block 1", 0x39D96F, 0),
|
||||
LocationData("Hoohoo Village Bridge Room Block 2", 0x39D97F, 0),
|
||||
LocationData("Hoohoo Village Bridge Room Block 3", 0x39D98F, 0),
|
||||
@@ -119,8 +116,8 @@ mainArea: typing.List[LocationData] = [
|
||||
LocationData("Hoohoo Mountain Base Boostatue Room Digspot 2", 0x39D9E1, 0),
|
||||
LocationData("Hoohoo Mountain Base Grassy Area Block 1", 0x39D9FE, 0),
|
||||
LocationData("Hoohoo Mountain Base Grassy Area Block 2", 0x39D9F6, 0),
|
||||
LocationData("Hoohoo Mountain Base After Minecart Minigame Block 1", 0x39DA35, 0),
|
||||
LocationData("Hoohoo Mountain Base After Minecart Minigame Block 2", 0x39DA2D, 0),
|
||||
LocationData("Hoohoo Mountain Base Past Minecart Minigame Block 1", 0x39DA35, 0),
|
||||
LocationData("Hoohoo Mountain Base Past Minecart Minigame Block 2", 0x39DA2D, 0),
|
||||
LocationData("Cave Connecting Stardust Fields and Hoohoo Village Block 1", 0x39DA77, 0),
|
||||
LocationData("Cave Connecting Stardust Fields and Hoohoo Village Block 2", 0x39DA7F, 0),
|
||||
LocationData("Hoohoo Village South Cave Block", 0x39DACD, 0),
|
||||
@@ -143,14 +140,14 @@ mainArea: typing.List[LocationData] = [
|
||||
LocationData("Shop Starting Flag 3", 0x3C05F4, 3),
|
||||
LocationData("Hoohoo Mountain Summit Digspot", 0x39D85E, 0),
|
||||
LocationData("Hoohoo Mountain Below Summit Digspot", 0x39D86B, 0),
|
||||
LocationData("Hoohoo Mountain After Hoohooros Digspot", 0x39D898, 0),
|
||||
LocationData("Hoohoo Mountain Past Hoohooros Digspot", 0x39D898, 0),
|
||||
LocationData("Hoohoo Mountain Hoohooros Room Digspot 1", 0x39D8BD, 0),
|
||||
LocationData("Hoohoo Mountain Hoohooros Room Digspot 2", 0x39D8C5, 0),
|
||||
LocationData("Hoohoo Mountain Before Hoohooros Digspot", 0x39D8E2, 0),
|
||||
LocationData("Hoohoo Mountain Room 2 Digspot 1", 0x39D907, 0),
|
||||
LocationData("Hoohoo Mountain Room 2 Digspot 2", 0x39D90F, 0),
|
||||
LocationData("Hoohoo Mountain Base Room 1 Digspot", 0x39D941, 0),
|
||||
LocationData("Hoohoo Village Right Side Digspot", 0x39D95F, 0),
|
||||
LocationData("Hoohoo Village Eastside Digspot", 0x39D95F, 0),
|
||||
LocationData("Hoohoo Village Super Hammer Cave Digspot", 0x39DB02, 0),
|
||||
LocationData("Hoohoo Village Super Hammer Cave Block", 0x39DAEA, 0),
|
||||
LocationData("Hoohoo Village North Cave Room 2 Digspot", 0x39DAB5, 0),
|
||||
@@ -267,7 +264,7 @@ coins: typing.List[LocationData] = [
|
||||
LocationData("Chucklehuck Woods Cave Room 3 Coin Block", 0x39DDB4, 0),
|
||||
LocationData("Chucklehuck Woods Pipe 5 Room Coin Block", 0x39DDE6, 0),
|
||||
LocationData("Chucklehuck Woods Room 7 Coin Block", 0x39DE31, 0),
|
||||
LocationData("Chucklehuck Woods After Chuckleroot Coin Block", 0x39DF14, 0),
|
||||
LocationData("Chucklehuck Woods Past Chuckleroot Coin Block", 0x39DF14, 0),
|
||||
LocationData("Chucklehuck Woods Koopa Room Coin Block", 0x39DF53, 0),
|
||||
LocationData("Chucklehuck Woods Winkle Area Cave Coin Block", 0x39DF80, 0),
|
||||
LocationData("Sewers Prison Room Coin Block", 0x39E01E, 0),
|
||||
@@ -286,11 +283,12 @@ baseUltraRocks: typing.List[LocationData] = [
|
||||
LocationData("Hoohoo Mountain Base Past Ultra Hammer Rocks Block 1", 0x39DA42, 0),
|
||||
LocationData("Hoohoo Mountain Base Past Ultra Hammer Rocks Block 2", 0x39DA4A, 0),
|
||||
LocationData("Hoohoo Mountain Base Past Ultra Hammer Rocks Block 3", 0x39DA52, 0),
|
||||
LocationData("Hoohoo Mountain Base Boostatue Room Digspot 3 (Rightside)", 0x39D9E9, 0),
|
||||
LocationData("Hoohoo Mountain Base Boostatue Room Digspot 3 (Right Side)", 0x39D9E9, 0),
|
||||
LocationData("Hoohoo Mountain Base Mole Near Teehee Valley", 0x277A45, 1),
|
||||
LocationData("Teehee Valley Entrance To Hoohoo Mountain Digspot", 0x39E5B5, 0),
|
||||
LocationData("Teehee Valley Solo Luigi Maze Room 2 Digspot 1", 0x39E5C8, 0),
|
||||
LocationData("Teehee Valley Solo Luigi Maze Room 2 Digspot 2", 0x39E5D0, 0),
|
||||
LocationData("Teehee Valley Upper Maze Room 1 Block", 0x39E5E0, 0),
|
||||
LocationData("Teehee Valley Upper Maze Room 2 Digspot 1", 0x39E5C8, 0),
|
||||
LocationData("Teehee Valley Upper Maze Room 2 Digspot 2", 0x39E5D0, 0),
|
||||
LocationData("Hoohoo Mountain Base Guffawha Ruins Entrance Digspot", 0x39DA0B, 0),
|
||||
LocationData("Hoohoo Mountain Base Teehee Valley Entrance Digspot", 0x39DA20, 0),
|
||||
LocationData("Hoohoo Mountain Base Teehee Valley Entrance Block", 0x39DA18, 0),
|
||||
@@ -345,12 +343,12 @@ chucklehuck: typing.List[LocationData] = [
|
||||
LocationData("Chucklehuck Woods Southwest of Chuckleroot Block", 0x39DEC2, 0),
|
||||
LocationData("Chucklehuck Woods Wiggler room Digspot 1", 0x39DECF, 0),
|
||||
LocationData("Chucklehuck Woods Wiggler room Digspot 2", 0x39DED7, 0),
|
||||
LocationData("Chucklehuck Woods After Chuckleroot Block 1", 0x39DEE4, 0),
|
||||
LocationData("Chucklehuck Woods After Chuckleroot Block 2", 0x39DEEC, 0),
|
||||
LocationData("Chucklehuck Woods After Chuckleroot Block 3", 0x39DEF4, 0),
|
||||
LocationData("Chucklehuck Woods After Chuckleroot Block 4", 0x39DEFC, 0),
|
||||
LocationData("Chucklehuck Woods After Chuckleroot Block 5", 0x39DF04, 0),
|
||||
LocationData("Chucklehuck Woods After Chuckleroot Block 6", 0x39DF0C, 0),
|
||||
LocationData("Chucklehuck Woods Past Chuckleroot Block 1", 0x39DEE4, 0),
|
||||
LocationData("Chucklehuck Woods Past Chuckleroot Block 2", 0x39DEEC, 0),
|
||||
LocationData("Chucklehuck Woods Past Chuckleroot Block 3", 0x39DEF4, 0),
|
||||
LocationData("Chucklehuck Woods Past Chuckleroot Block 4", 0x39DEFC, 0),
|
||||
LocationData("Chucklehuck Woods Past Chuckleroot Block 5", 0x39DF04, 0),
|
||||
LocationData("Chucklehuck Woods Past Chuckleroot Block 6", 0x39DF0C, 0),
|
||||
LocationData("Chucklehuck Woods Koopa Room Block 1", 0x39DF4B, 0),
|
||||
LocationData("Chucklehuck Woods Koopa Room Block 2", 0x39DF5B, 0),
|
||||
LocationData("Chucklehuck Woods Koopa Room Digspot", 0x39DF63, 0),
|
||||
@@ -367,14 +365,14 @@ chucklehuck: typing.List[LocationData] = [
|
||||
]
|
||||
|
||||
castleTown: typing.List[LocationData] = [
|
||||
LocationData("Beanbean Castle Town Left Side House Block 1", 0x39D7A4, 0),
|
||||
LocationData("Beanbean Castle Town Left Side House Block 2", 0x39D7AC, 0),
|
||||
LocationData("Beanbean Castle Town Left Side House Block 3", 0x39D7B4, 0),
|
||||
LocationData("Beanbean Castle Town Left Side House Block 4", 0x39D7BC, 0),
|
||||
LocationData("Beanbean Castle Town Right Side House Block 1", 0x39D7D8, 0),
|
||||
LocationData("Beanbean Castle Town Right Side House Block 2", 0x39D7E0, 0),
|
||||
LocationData("Beanbean Castle Town Right Side House Block 3", 0x39D7E8, 0),
|
||||
LocationData("Beanbean Castle Town Right Side House Block 4", 0x39D7F0, 0),
|
||||
LocationData("Beanbean Castle Town West Side House Block 1", 0x39D7A4, 0),
|
||||
LocationData("Beanbean Castle Town West Side House Block 2", 0x39D7AC, 0),
|
||||
LocationData("Beanbean Castle Town West Side House Block 3", 0x39D7B4, 0),
|
||||
LocationData("Beanbean Castle Town West Side House Block 4", 0x39D7BC, 0),
|
||||
LocationData("Beanbean Castle Town East Side House Block 1", 0x39D7D8, 0),
|
||||
LocationData("Beanbean Castle Town East Side House Block 2", 0x39D7E0, 0),
|
||||
LocationData("Beanbean Castle Town East Side House Block 3", 0x39D7E8, 0),
|
||||
LocationData("Beanbean Castle Town East Side House Block 4", 0x39D7F0, 0),
|
||||
LocationData("Beanbean Castle Peach's Extra Dress", 0x1E9433, 2),
|
||||
LocationData("Beanbean Castle Fake Beanstar", 0x1E9432, 2),
|
||||
LocationData("Beanbean Castle Town Beanlet 1", 0x251347, 1),
|
||||
@@ -444,14 +442,14 @@ piranhaFlag: typing.List[LocationData] = [
|
||||
]
|
||||
|
||||
kidnappedFlag: typing.List[LocationData] = [
|
||||
LocationData("Badge Shop Enter Fungitown Flag 1", 0x3C0640, 2),
|
||||
LocationData("Badge Shop Enter Fungitown Flag 2", 0x3C0642, 2),
|
||||
LocationData("Badge Shop Enter Fungitown Flag 3", 0x3C0644, 2),
|
||||
LocationData("Pants Shop Enter Fungitown Flag 1", 0x3C0646, 2),
|
||||
LocationData("Pants Shop Enter Fungitown Flag 2", 0x3C0648, 2),
|
||||
LocationData("Pants Shop Enter Fungitown Flag 3", 0x3C064A, 2),
|
||||
LocationData("Shop Enter Fungitown Flag 1", 0x3C0606, 3),
|
||||
LocationData("Shop Enter Fungitown Flag 2", 0x3C0608, 3),
|
||||
LocationData("Badge Shop Trunkle Flag 1", 0x3C0640, 2),
|
||||
LocationData("Badge Shop Trunkle Flag 2", 0x3C0642, 2),
|
||||
LocationData("Badge Shop Trunkle Flag 3", 0x3C0644, 2),
|
||||
LocationData("Pants Shop Trunkle Flag 1", 0x3C0646, 2),
|
||||
LocationData("Pants Shop Trunkle Flag 2", 0x3C0648, 2),
|
||||
LocationData("Pants Shop Trunkle Flag 3", 0x3C064A, 2),
|
||||
LocationData("Shop Trunkle Flag 1", 0x3C0606, 3),
|
||||
LocationData("Shop Trunkle Flag 2", 0x3C0608, 3),
|
||||
]
|
||||
|
||||
beanstarFlag: typing.List[LocationData] = [
|
||||
@@ -553,21 +551,21 @@ surfable: typing.List[LocationData] = [
|
||||
airport: typing.List[LocationData] = [
|
||||
LocationData("Airport Entrance Digspot", 0x39E2DC, 0),
|
||||
LocationData("Airport Lobby Digspot", 0x39E2E9, 0),
|
||||
LocationData("Airport Leftside Digspot 1", 0x39E2F6, 0),
|
||||
LocationData("Airport Leftside Digspot 2", 0x39E2FE, 0),
|
||||
LocationData("Airport Leftside Digspot 3", 0x39E306, 0),
|
||||
LocationData("Airport Leftside Digspot 4", 0x39E30E, 0),
|
||||
LocationData("Airport Leftside Digspot 5", 0x39E316, 0),
|
||||
LocationData("Airport Westside Digspot 1", 0x39E2F6, 0),
|
||||
LocationData("Airport Westside Digspot 2", 0x39E2FE, 0),
|
||||
LocationData("Airport Westside Digspot 3", 0x39E306, 0),
|
||||
LocationData("Airport Westside Digspot 4", 0x39E30E, 0),
|
||||
LocationData("Airport Westside Digspot 5", 0x39E316, 0),
|
||||
LocationData("Airport Center Digspot 1", 0x39E323, 0),
|
||||
LocationData("Airport Center Digspot 2", 0x39E32B, 0),
|
||||
LocationData("Airport Center Digspot 3", 0x39E333, 0),
|
||||
LocationData("Airport Center Digspot 4", 0x39E33B, 0),
|
||||
LocationData("Airport Center Digspot 5", 0x39E343, 0),
|
||||
LocationData("Airport Rightside Digspot 1", 0x39E350, 0),
|
||||
LocationData("Airport Rightside Digspot 2", 0x39E358, 0),
|
||||
LocationData("Airport Rightside Digspot 3", 0x39E360, 0),
|
||||
LocationData("Airport Rightside Digspot 4", 0x39E368, 0),
|
||||
LocationData("Airport Rightside Digspot 5", 0x39E370, 0),
|
||||
LocationData("Airport Eastside Digspot 1", 0x39E350, 0),
|
||||
LocationData("Airport Eastside Digspot 2", 0x39E358, 0),
|
||||
LocationData("Airport Eastside Digspot 3", 0x39E360, 0),
|
||||
LocationData("Airport Eastside Digspot 4", 0x39E368, 0),
|
||||
LocationData("Airport Eastside Digspot 5", 0x39E370, 0),
|
||||
]
|
||||
|
||||
gwarharEntrance: typing.List[LocationData] = [
|
||||
@@ -617,7 +615,6 @@ teeheeValley: typing.List[LocationData] = [
|
||||
LocationData("Teehee Valley Past Ultra Hammer Rock Block 2", 0x39E590, 0),
|
||||
LocationData("Teehee Valley Past Ultra Hammer Rock Digspot 1", 0x39E598, 0),
|
||||
LocationData("Teehee Valley Past Ultra Hammer Rock Digspot 3", 0x39E5A8, 0),
|
||||
LocationData("Teehee Valley Solo Luigi Maze Room 1 Block", 0x39E5E0, 0),
|
||||
LocationData("Teehee Valley Before Trunkle Digspot", 0x39E5F0, 0),
|
||||
LocationData("S.S. Chuckola Storage Room Block 1", 0x39E610, 0),
|
||||
LocationData("S.S. Chuckola Storage Room Block 2", 0x39E628, 0),
|
||||
@@ -667,7 +664,7 @@ bowsers: typing.List[LocationData] = [
|
||||
LocationData("Bowser's Castle Iggy & Morton Hallway Block 1", 0x39E9EF, 0),
|
||||
LocationData("Bowser's Castle Iggy & Morton Hallway Block 2", 0x39E9F7, 0),
|
||||
LocationData("Bowser's Castle Iggy & Morton Hallway Digspot", 0x39E9FF, 0),
|
||||
LocationData("Bowser's Castle After Morton Block", 0x39EA0C, 0),
|
||||
LocationData("Bowser's Castle Past Morton Block", 0x39EA0C, 0),
|
||||
LocationData("Bowser's Castle Morton Room 1 Digspot", 0x39EA89, 0),
|
||||
LocationData("Bowser's Castle Lemmy Room 1 Block", 0x39EA9C, 0),
|
||||
LocationData("Bowser's Castle Lemmy Room 1 Digspot", 0x39EAA4, 0),
|
||||
@@ -705,16 +702,16 @@ jokesEntrance: typing.List[LocationData] = [
|
||||
LocationData("Joke's End Second Floor West Room Block 4", 0x39E781, 0),
|
||||
LocationData("Joke's End Mole Reward 1", 0x27788E, 1),
|
||||
LocationData("Joke's End Mole Reward 2", 0x2778D2, 1),
|
||||
]
|
||||
|
||||
jokesMain: typing.List[LocationData] = [
|
||||
LocationData("Joke's End Furnace Room 1 Block 1", 0x39E70F, 0),
|
||||
LocationData("Joke's End Furnace Room 1 Block 2", 0x39E717, 0),
|
||||
LocationData("Joke's End Furnace Room 1 Block 3", 0x39E71F, 0),
|
||||
LocationData("Joke's End Northeast of Boiler Room 1 Block", 0x39E732, 0),
|
||||
LocationData("Joke's End Northeast of Boiler Room 3 Digspot", 0x39E73F, 0),
|
||||
LocationData("Joke's End Northeast of Boiler Room 2 Block", 0x39E74C, 0),
|
||||
LocationData("Joke's End Northeast of Boiler Room 2 Digspot", 0x39E754, 0),
|
||||
LocationData("Joke's End Northeast of Boiler Room 3 Digspot", 0x39E73F, 0),
|
||||
]
|
||||
|
||||
jokesMain: typing.List[LocationData] = [
|
||||
LocationData("Joke's End Second Floor East Room Digspot", 0x39E794, 0),
|
||||
LocationData("Joke's End Final Split up Room Digspot", 0x39E7A7, 0),
|
||||
LocationData("Joke's End South of Bridge Room Block", 0x39E7B4, 0),
|
||||
@@ -740,10 +737,10 @@ jokesMain: typing.List[LocationData] = [
|
||||
|
||||
postJokes: typing.List[LocationData] = [
|
||||
LocationData("Teehee Valley Past Ultra Hammer Rock Digspot 2 (Post-Birdo)", 0x39E5A0, 0),
|
||||
LocationData("Teehee Valley Before Popple Digspot 1", 0x39E55B, 0),
|
||||
LocationData("Teehee Valley Before Popple Digspot 2", 0x39E563, 0),
|
||||
LocationData("Teehee Valley Before Popple Digspot 3", 0x39E56B, 0),
|
||||
LocationData("Teehee Valley Before Popple Digspot 4", 0x39E573, 0),
|
||||
LocationData("Teehee Valley Before Birdo Digspot 1", 0x39E55B, 0),
|
||||
LocationData("Teehee Valley Before Birdo Digspot 2", 0x39E563, 0),
|
||||
LocationData("Teehee Valley Before Birdo Digspot 3", 0x39E56B, 0),
|
||||
LocationData("Teehee Valley Before Birdo Digspot 4", 0x39E573, 0),
|
||||
]
|
||||
|
||||
theater: typing.List[LocationData] = [
|
||||
@@ -766,6 +763,10 @@ oasis: typing.List[LocationData] = [
|
||||
LocationData("Oho Oasis Thunderhand", 0x1E9409, 2),
|
||||
]
|
||||
|
||||
cacklettas_soul: typing.List[LocationData] = [
|
||||
LocationData("Cackletta's Soul", None, 0),
|
||||
]
|
||||
|
||||
nonBlock = [
|
||||
(0x434B, 0x1, 0x243844), # Farm Mole 1
|
||||
(0x434B, 0x1, 0x24387D), # Farm Mole 2
|
||||
@@ -1171,15 +1172,15 @@ all_locations: typing.List[LocationData] = (
|
||||
+ fungitownBeanstar
|
||||
+ fungitownBirdo
|
||||
+ bowsers
|
||||
+ bowsersMini
|
||||
+ jokesEntrance
|
||||
+ jokesMain
|
||||
+ postJokes
|
||||
+ theater
|
||||
+ oasis
|
||||
+ gwarharMain
|
||||
+ bowsersMini
|
||||
+ baseUltraRocks
|
||||
+ coins
|
||||
)
|
||||
|
||||
location_table: typing.Dict[str, int] = {locData.name: locData.id for locData in all_locations}
|
||||
location_table: typing.Dict[str, int] = {location.name: location.id for location in all_locations}
|
||||
|
||||
@@ -8,14 +8,14 @@ class LocationName:
|
||||
StardustFields4Block3 = "Stardust Fields Room 4 Block 3"
|
||||
StardustFields5Block = "Stardust Fields Room 5 Block"
|
||||
HoohooVillageHammerHouseBlock = "Hoohoo Village Hammer House Block"
|
||||
BeanbeanCastleTownLeftSideHouseBlock1 = "Beanbean Castle Town Left Side House Block 1"
|
||||
BeanbeanCastleTownLeftSideHouseBlock2 = "Beanbean Castle Town Left Side House Block 2"
|
||||
BeanbeanCastleTownLeftSideHouseBlock3 = "Beanbean Castle Town Left Side House Block 3"
|
||||
BeanbeanCastleTownLeftSideHouseBlock4 = "Beanbean Castle Town Left Side House Block 4"
|
||||
BeanbeanCastleTownRightSideHouseBlock1 = "Beanbean Castle Town Right Side House Block 1"
|
||||
BeanbeanCastleTownRightSideHouseBlock2 = "Beanbean Castle Town Right Side House Block 2"
|
||||
BeanbeanCastleTownRightSideHouseBlock3 = "Beanbean Castle Town Right Side House Block 3"
|
||||
BeanbeanCastleTownRightSideHouseBlock4 = "Beanbean Castle Town Right Side House Block 4"
|
||||
BeanbeanCastleTownWestsideHouseBlock1 = "Beanbean Castle Town Westside House Block 1"
|
||||
BeanbeanCastleTownWestsideHouseBlock2 = "Beanbean Castle Town Westside House Block 2"
|
||||
BeanbeanCastleTownWestsideHouseBlock3 = "Beanbean Castle Town Westside House Block 3"
|
||||
BeanbeanCastleTownWestsideHouseBlock4 = "Beanbean Castle Town Westside House Block 4"
|
||||
BeanbeanCastleTownEastsideHouseBlock1 = "Beanbean Castle Town Eastside House Block 1"
|
||||
BeanbeanCastleTownEastsideHouseBlock2 = "Beanbean Castle Town Eastside House Block 2"
|
||||
BeanbeanCastleTownEastsideHouseBlock3 = "Beanbean Castle Town Eastside House Block 3"
|
||||
BeanbeanCastleTownEastsideHouseBlock4 = "Beanbean Castle Town Eastside House Block 4"
|
||||
BeanbeanCastleTownMiniMarioBlock1 = "Beanbean Castle Town Mini Mario Block 1"
|
||||
BeanbeanCastleTownMiniMarioBlock2 = "Beanbean Castle Town Mini Mario Block 2"
|
||||
BeanbeanCastleTownMiniMarioBlock3 = "Beanbean Castle Town Mini Mario Block 3"
|
||||
@@ -26,9 +26,9 @@ class LocationName:
|
||||
HoohooMountainBelowSummitBlock1 = "Hoohoo Mountain Below Summit Block 1"
|
||||
HoohooMountainBelowSummitBlock2 = "Hoohoo Mountain Below Summit Block 2"
|
||||
HoohooMountainBelowSummitBlock3 = "Hoohoo Mountain Below Summit Block 3"
|
||||
HoohooMountainAfterHoohoorosBlock1 = "Hoohoo Mountain After Hoohooros Block 1"
|
||||
HoohooMountainAfterHoohoorosDigspot = "Hoohoo Mountain After Hoohooros Digspot"
|
||||
HoohooMountainAfterHoohoorosBlock2 = "Hoohoo Mountain After Hoohooros Block 2"
|
||||
HoohooMountainPastHoohoorosBlock1 = "Hoohoo Mountain Past Hoohooros Block 1"
|
||||
HoohooMountainPastHoohoorosDigspot = "Hoohoo Mountain Past Hoohooros Digspot"
|
||||
HoohooMountainPastHoohoorosBlock2 = "Hoohoo Mountain Past Hoohooros Block 2"
|
||||
HoohooMountainHoohoorosRoomBlock1 = "Hoohoo Mountain Hoohooros Room Block 1"
|
||||
HoohooMountainHoohoorosRoomBlock2 = "Hoohoo Mountain Hoohooros Room Block 2"
|
||||
HoohooMountainHoohoorosRoomDigspot1 = "Hoohoo Mountain Hoohooros Room Digspot 1"
|
||||
@@ -44,8 +44,8 @@ class LocationName:
|
||||
HoohooMountainRoom1Block3 = "Hoohoo Mountain Room 1 Block 3"
|
||||
HoohooMountainBaseRoom1Block = "Hoohoo Mountain Base Room 1 Block"
|
||||
HoohooMountainBaseRoom1Digspot = "Hoohoo Mountain Base Room 1 Digspot"
|
||||
HoohooVillageRightSideBlock = "Hoohoo Village Right Side Block"
|
||||
HoohooVillageRightSideDigspot = "Hoohoo Village Right Side Digspot"
|
||||
HoohooVillageEastsideBlock = "Hoohoo Village Eastside Block"
|
||||
HoohooVillageEastsideDigspot = "Hoohoo Village Eastside Digspot"
|
||||
HoohooVillageBridgeRoomBlock1 = "Hoohoo Village Bridge Room Block 1"
|
||||
HoohooVillageBridgeRoomBlock2 = "Hoohoo Village Bridge Room Block 2"
|
||||
HoohooVillageBridgeRoomBlock3 = "Hoohoo Village Bridge Room Block 3"
|
||||
@@ -65,8 +65,8 @@ class LocationName:
|
||||
HoohooMountainBaseGuffawhaRuinsEntranceDigspot = "Hoohoo Mountain Base Guffawha Ruins Entrance Digspot"
|
||||
HoohooMountainBaseTeeheeValleyEntranceDigspot = "Hoohoo Mountain Base Teehee Valley Entrance Digspot"
|
||||
HoohooMountainBaseTeeheeValleyEntranceBlock = "Hoohoo Mountain Base Teehee Valley Entrance Block"
|
||||
HoohooMountainBaseAfterMinecartMinigameBlock1 = "Hoohoo Mountain Base After Minecart Minigame Block 1"
|
||||
HoohooMountainBaseAfterMinecartMinigameBlock2 = "Hoohoo Mountain Base After Minecart Minigame Block 2"
|
||||
HoohooMountainBasePastMinecartMinigameBlock1 = "Hoohoo Mountain Base Past Minecart Minigame Block 1"
|
||||
HoohooMountainBasePastMinecartMinigameBlock2 = "Hoohoo Mountain Base Past Minecart Minigame Block 2"
|
||||
HoohooMountainBasePastUltraHammerRocksBlock1 = "Hoohoo Mountain Base Past Ultra Hammer Rocks Block 1"
|
||||
HoohooMountainBasePastUltraHammerRocksBlock2 = "Hoohoo Mountain Base Past Ultra Hammer Rocks Block 2"
|
||||
HoohooMountainBasePastUltraHammerRocksBlock3 = "Hoohoo Mountain Base Past Ultra Hammer Rocks Block 3"
|
||||
@@ -148,12 +148,12 @@ class LocationName:
|
||||
ChucklehuckWoodsSouthwestOfChucklerootBlock = "Chucklehuck Woods Southwest of Chuckleroot Block"
|
||||
ChucklehuckWoodsWigglerRoomDigspot1 = "Chucklehuck Woods Wiggler Room Digspot 1"
|
||||
ChucklehuckWoodsWigglerRoomDigspot2 = "Chucklehuck Woods Wiggler Room Digspot 2"
|
||||
ChucklehuckWoodsAfterChucklerootBlock1 = "Chucklehuck Woods After Chuckleroot Block 1"
|
||||
ChucklehuckWoodsAfterChucklerootBlock2 = "Chucklehuck Woods After Chuckleroot Block 2"
|
||||
ChucklehuckWoodsAfterChucklerootBlock3 = "Chucklehuck Woods After Chuckleroot Block 3"
|
||||
ChucklehuckWoodsAfterChucklerootBlock4 = "Chucklehuck Woods After Chuckleroot Block 4"
|
||||
ChucklehuckWoodsAfterChucklerootBlock5 = "Chucklehuck Woods After Chuckleroot Block 5"
|
||||
ChucklehuckWoodsAfterChucklerootBlock6 = "Chucklehuck Woods After Chuckleroot Block 6"
|
||||
ChucklehuckWoodsPastChucklerootBlock1 = "Chucklehuck Woods Past Chuckleroot Block 1"
|
||||
ChucklehuckWoodsPastChucklerootBlock2 = "Chucklehuck Woods Past Chuckleroot Block 2"
|
||||
ChucklehuckWoodsPastChucklerootBlock3 = "Chucklehuck Woods Past Chuckleroot Block 3"
|
||||
ChucklehuckWoodsPastChucklerootBlock4 = "Chucklehuck Woods Past Chuckleroot Block 4"
|
||||
ChucklehuckWoodsPastChucklerootBlock5 = "Chucklehuck Woods Past Chuckleroot Block 5"
|
||||
ChucklehuckWoodsPastChucklerootBlock6 = "Chucklehuck Woods Past Chuckleroot Block 6"
|
||||
WinkleAreaBeanstarRoomBlock = "Winkle Area Beanstar Room Block"
|
||||
WinkleAreaDigspot = "Winkle Area Digspot"
|
||||
WinkleAreaOutsideColosseumBlock = "Winkle Area Outside Colosseum Block"
|
||||
@@ -232,21 +232,21 @@ class LocationName:
|
||||
WoohooHooniversityPastCacklettaRoom2Digspot = "Woohoo Hooniversity Past Cackletta Room 2 Digspot"
|
||||
AirportEntranceDigspot = "Airport Entrance Digspot"
|
||||
AirportLobbyDigspot = "Airport Lobby Digspot"
|
||||
AirportLeftsideDigspot1 = "Airport Leftside Digspot 1"
|
||||
AirportLeftsideDigspot2 = "Airport Leftside Digspot 2"
|
||||
AirportLeftsideDigspot3 = "Airport Leftside Digspot 3"
|
||||
AirportLeftsideDigspot4 = "Airport Leftside Digspot 4"
|
||||
AirportLeftsideDigspot5 = "Airport Leftside Digspot 5"
|
||||
AirportWestsideDigspot1 = "Airport Westside Digspot 1"
|
||||
AirportWestsideDigspot2 = "Airport Westside Digspot 2"
|
||||
AirportWestsideDigspot3 = "Airport Westside Digspot 3"
|
||||
AirportWestsideDigspot4 = "Airport Westside Digspot 4"
|
||||
AirportWestsideDigspot5 = "Airport Westside Digspot 5"
|
||||
AirportCenterDigspot1 = "Airport Center Digspot 1"
|
||||
AirportCenterDigspot2 = "Airport Center Digspot 2"
|
||||
AirportCenterDigspot3 = "Airport Center Digspot 3"
|
||||
AirportCenterDigspot4 = "Airport Center Digspot 4"
|
||||
AirportCenterDigspot5 = "Airport Center Digspot 5"
|
||||
AirportRightsideDigspot1 = "Airport Rightside Digspot 1"
|
||||
AirportRightsideDigspot2 = "Airport Rightside Digspot 2"
|
||||
AirportRightsideDigspot3 = "Airport Rightside Digspot 3"
|
||||
AirportRightsideDigspot4 = "Airport Rightside Digspot 4"
|
||||
AirportRightsideDigspot5 = "Airport Rightside Digspot 5"
|
||||
AirportEastsideDigspot1 = "Airport Eastside Digspot 1"
|
||||
AirportEastsideDigspot2 = "Airport Eastside Digspot 2"
|
||||
AirportEastsideDigspot3 = "Airport Eastside Digspot 3"
|
||||
AirportEastsideDigspot4 = "Airport Eastside Digspot 4"
|
||||
AirportEastsideDigspot5 = "Airport Eastside Digspot 5"
|
||||
GwarharLagoonPipeRoomDigspot = "Gwarhar Lagoon Pipe Room Digspot"
|
||||
GwarharLagoonMassageParlorEntranceDigspot = "Gwarhar Lagoon Massage Parlor Entrance Digspot"
|
||||
GwarharLagoonPastHermieDigspot = "Gwarhar Lagoon Past Hermie Digspot"
|
||||
@@ -276,10 +276,10 @@ class LocationName:
|
||||
WoohooHooniversityBasementRoom4Block = "Woohoo Hooniversity Basement Room 4 Block"
|
||||
WoohooHooniversityPoppleRoomDigspot1 = "Woohoo Hooniversity Popple Room Digspot 1"
|
||||
WoohooHooniversityPoppleRoomDigspot2 = "Woohoo Hooniversity Popple Room Digspot 2"
|
||||
TeeheeValleyBeforePoppleDigspot1 = "Teehee Valley Before Popple Digspot 1"
|
||||
TeeheeValleyBeforePoppleDigspot2 = "Teehee Valley Before Popple Digspot 2"
|
||||
TeeheeValleyBeforePoppleDigspot3 = "Teehee Valley Before Popple Digspot 3"
|
||||
TeeheeValleyBeforePoppleDigspot4 = "Teehee Valley Before Popple Digspot 4"
|
||||
TeeheeValleyBeforeBirdoDigspot1 = "Teehee Valley Before Birdo Digspot 1"
|
||||
TeeheeValleyBeforeBirdoDigspot2 = "Teehee Valley Before Birdo Digspot 2"
|
||||
TeeheeValleyBeforeBirdoDigspot3 = "Teehee Valley Before Birdo Digspot 3"
|
||||
TeeheeValleyBeforeBirdoDigspot4 = "Teehee Valley Before Birdo Digspot 4"
|
||||
TeeheeValleyRoom1Digspot1 = "Teehee Valley Room 1 Digspot 1"
|
||||
TeeheeValleyRoom1Digspot2 = "Teehee Valley Room 1 Digspot 2"
|
||||
TeeheeValleyRoom1Digspot3 = "Teehee Valley Room 1 Digspot 3"
|
||||
@@ -296,9 +296,9 @@ class LocationName:
|
||||
TeeheeValleyPastUltraHammersDigspot2 = "Teehee Valley Past Ultra Hammer Rock Digspot 2 (Post-Birdo)"
|
||||
TeeheeValleyPastUltraHammersDigspot3 = "Teehee Valley Past Ultra Hammer Rock Digspot 3"
|
||||
TeeheeValleyEntranceToHoohooMountainDigspot = "Teehee Valley Entrance To Hoohoo Mountain Digspot"
|
||||
TeeheeValleySoloLuigiMazeRoom2Digspot1 = "Teehee Valley Solo Luigi Maze Room 2 Digspot 1"
|
||||
TeeheeValleySoloLuigiMazeRoom2Digspot2 = "Teehee Valley Solo Luigi Maze Room 2 Digspot 2"
|
||||
TeeheeValleySoloLuigiMazeRoom1Block = "Teehee Valley Solo Luigi Maze Room 1 Block"
|
||||
TeeheeValleyUpperMazeRoom2Digspot1 = "Teehee Valley Upper Maze Room 2 Digspot 1"
|
||||
TeeheeValleyUpperMazeRoom2Digspot2 = "Teehee Valley Upper Maze Room 2 Digspot 2"
|
||||
TeeheeValleyUpperMazeRoom1Block = "Teehee Valley Upper Maze Room 1 Block"
|
||||
TeeheeValleyBeforeTrunkleDigspot = "Teehee Valley Before Trunkle Digspot"
|
||||
TeeheeValleyTrunkleRoomDigspot = "Teehee Valley Trunkle Room Digspot"
|
||||
SSChuckolaStorageRoomBlock1 = "S.S. Chuckola Storage Room Block 1"
|
||||
@@ -314,10 +314,10 @@ class LocationName:
|
||||
JokesEndFurnaceRoom1Block1 = "Joke's End Furnace Room 1 Block 1"
|
||||
JokesEndFurnaceRoom1Block2 = "Joke's End Furnace Room 1 Block 2"
|
||||
JokesEndFurnaceRoom1Block3 = "Joke's End Furnace Room 1 Block 3"
|
||||
JokesEndNortheastOfBoilerRoom1Block = "Joke's End Northeast Of Boiler Room 1 Block"
|
||||
JokesEndNortheastOfBoilerRoom3Digspot = "Joke's End Northeast Of Boiler Room 3 Digspot"
|
||||
JokesEndNortheastOfBoilerRoom2Block1 = "Joke's End Northeast Of Boiler Room 2 Block"
|
||||
JokesEndNortheastOfBoilerRoom2Block2 = "Joke's End Northeast Of Boiler Room 2 Digspot"
|
||||
JokesEndNortheastOfBoilerRoom1Block = "Joke's End Northeast of Boiler Room 1 Block"
|
||||
JokesEndNortheastOfBoilerRoom3Digspot = "Joke's End Northeast of Boiler Room 3 Digspot"
|
||||
JokesEndNortheastOfBoilerRoom2Block1 = "Joke's End Northeast of Boiler Room 2 Block"
|
||||
JokesEndNortheastOfBoilerRoom2Digspot = "Joke's End Northeast of Boiler Room 2 Digspot"
|
||||
JokesEndSecondFloorWestRoomBlock1 = "Joke's End Second Floor West Room Block 1"
|
||||
JokesEndSecondFloorWestRoomBlock2 = "Joke's End Second Floor West Room Block 2"
|
||||
JokesEndSecondFloorWestRoomBlock3 = "Joke's End Second Floor West Room Block 3"
|
||||
@@ -505,7 +505,7 @@ class LocationName:
|
||||
BowsersCastleIggyMortonHallwayBlock1 = "Bowser's Castle Iggy & Morton Hallway Block 1"
|
||||
BowsersCastleIggyMortonHallwayBlock2 = "Bowser's Castle Iggy & Morton Hallway Block 2"
|
||||
BowsersCastleIggyMortonHallwayDigspot = "Bowser's Castle Iggy & Morton Hallway Digspot"
|
||||
BowsersCastleAfterMortonBlock = "Bowser's Castle After Morton Block"
|
||||
BowsersCastlePastMortonBlock = "Bowser's Castle Past Morton Block"
|
||||
BowsersCastleLudwigRoyHallwayBlock1 = "Bowser's Castle Ludwig & Roy Hallway Block 1"
|
||||
BowsersCastleLudwigRoyHallwayBlock2 = "Bowser's Castle Ludwig & Roy Hallway Block 2"
|
||||
BowsersCastleRoyCorridorBlock1 = "Bowser's Castle Roy Corridor Block 1"
|
||||
@@ -546,7 +546,7 @@ class LocationName:
|
||||
ChucklehuckWoodsCaveRoom3CoinBlock = "Chucklehuck Woods Cave Room 3 Coin Block"
|
||||
ChucklehuckWoodsPipe5RoomCoinBlock = "Chucklehuck Woods Pipe 5 Room Coin Block"
|
||||
ChucklehuckWoodsRoom7CoinBlock = "Chucklehuck Woods Room 7 Coin Block"
|
||||
ChucklehuckWoodsAfterChucklerootCoinBlock = "Chucklehuck Woods After Chuckleroot Coin Block"
|
||||
ChucklehuckWoodsPastChucklerootCoinBlock = "Chucklehuck Woods Past Chuckleroot Coin Block"
|
||||
ChucklehuckWoodsKoopaRoomCoinBlock = "Chucklehuck Woods Koopa Room Coin Block"
|
||||
ChucklehuckWoodsWinkleAreaCaveCoinBlock = "Chucklehuck Woods Winkle Area Cave Coin Block"
|
||||
SewersPrisonRoomCoinBlock = "Sewers Prison Room Coin Block"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from Options import Choice, Toggle, StartInventoryPool, PerGameCommonOptions, Range
|
||||
from Options import Choice, Toggle, StartInventoryPool, PerGameCommonOptions, Range, Removed
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@@ -282,7 +282,8 @@ class MLSSOptions(PerGameCommonOptions):
|
||||
extra_pipes: ExtraPipes
|
||||
skip_minecart: SkipMinecart
|
||||
disable_surf: DisableSurf
|
||||
harhalls_pants: HarhallsPants
|
||||
disable_harhalls_pants: HarhallsPants
|
||||
harhalls_pants: Removed
|
||||
block_visibility: HiddenVisible
|
||||
chuckle_beans: ChuckleBeans
|
||||
music_options: MusicOptions
|
||||
|
||||
@@ -33,6 +33,7 @@ from .Locations import (
|
||||
postJokes,
|
||||
baseUltraRocks,
|
||||
coins,
|
||||
cacklettas_soul,
|
||||
)
|
||||
from . import StateLogic
|
||||
|
||||
@@ -40,44 +41,45 @@ if typing.TYPE_CHECKING:
|
||||
from . import MLSSWorld
|
||||
|
||||
|
||||
def create_regions(world: "MLSSWorld", excluded: typing.List[str]):
|
||||
def create_regions(world: "MLSSWorld"):
|
||||
menu_region = Region("Menu", world.player, world.multiworld)
|
||||
world.multiworld.regions.append(menu_region)
|
||||
|
||||
create_region(world, "Main Area", mainArea, excluded)
|
||||
create_region(world, "Chucklehuck Woods", chucklehuck, excluded)
|
||||
create_region(world, "Beanbean Castle Town", castleTown, excluded)
|
||||
create_region(world, "Shop Starting Flag", startingFlag, excluded)
|
||||
create_region(world, "Shop Chuckolator Flag", chuckolatorFlag, excluded)
|
||||
create_region(world, "Shop Mom Piranha Flag", piranhaFlag, excluded)
|
||||
create_region(world, "Shop Enter Fungitown Flag", kidnappedFlag, excluded)
|
||||
create_region(world, "Shop Beanstar Complete Flag", beanstarFlag, excluded)
|
||||
create_region(world, "Shop Birdo Flag", birdoFlag, excluded)
|
||||
create_region(world, "Surfable", surfable, excluded)
|
||||
create_region(world, "Hooniversity", hooniversity, excluded)
|
||||
create_region(world, "GwarharEntrance", gwarharEntrance, excluded)
|
||||
create_region(world, "GwarharMain", gwarharMain, excluded)
|
||||
create_region(world, "TeeheeValley", teeheeValley, excluded)
|
||||
create_region(world, "Winkle", winkle, excluded)
|
||||
create_region(world, "Sewers", sewers, excluded)
|
||||
create_region(world, "Airport", airport, excluded)
|
||||
create_region(world, "JokesEntrance", jokesEntrance, excluded)
|
||||
create_region(world, "JokesMain", jokesMain, excluded)
|
||||
create_region(world, "PostJokes", postJokes, excluded)
|
||||
create_region(world, "Theater", theater, excluded)
|
||||
create_region(world, "Fungitown", fungitown, excluded)
|
||||
create_region(world, "Fungitown Shop Beanstar Complete Flag", fungitownBeanstar, excluded)
|
||||
create_region(world, "Fungitown Shop Birdo Flag", fungitownBirdo, excluded)
|
||||
create_region(world, "BooStatue", booStatue, excluded)
|
||||
create_region(world, "Oasis", oasis, excluded)
|
||||
create_region(world, "BaseUltraRocks", baseUltraRocks, excluded)
|
||||
create_region(world, "Main Area", mainArea)
|
||||
create_region(world, "Chucklehuck Woods", chucklehuck)
|
||||
create_region(world, "Beanbean Castle Town", castleTown)
|
||||
create_region(world, "Shop Starting Flag", startingFlag)
|
||||
create_region(world, "Shop Chuckolator Flag", chuckolatorFlag)
|
||||
create_region(world, "Shop Mom Piranha Flag", piranhaFlag)
|
||||
create_region(world, "Shop Enter Fungitown Flag", kidnappedFlag)
|
||||
create_region(world, "Shop Beanstar Complete Flag", beanstarFlag)
|
||||
create_region(world, "Shop Birdo Flag", birdoFlag)
|
||||
create_region(world, "Surfable", surfable)
|
||||
create_region(world, "Hooniversity", hooniversity)
|
||||
create_region(world, "GwarharEntrance", gwarharEntrance)
|
||||
create_region(world, "GwarharMain", gwarharMain)
|
||||
create_region(world, "TeeheeValley", teeheeValley)
|
||||
create_region(world, "Winkle", winkle)
|
||||
create_region(world, "Sewers", sewers)
|
||||
create_region(world, "Airport", airport)
|
||||
create_region(world, "JokesEntrance", jokesEntrance)
|
||||
create_region(world, "JokesMain", jokesMain)
|
||||
create_region(world, "PostJokes", postJokes)
|
||||
create_region(world, "Theater", theater)
|
||||
create_region(world, "Fungitown", fungitown)
|
||||
create_region(world, "Fungitown Shop Beanstar Complete Flag", fungitownBeanstar)
|
||||
create_region(world, "Fungitown Shop Birdo Flag", fungitownBirdo)
|
||||
create_region(world, "BooStatue", booStatue)
|
||||
create_region(world, "Oasis", oasis)
|
||||
create_region(world, "BaseUltraRocks", baseUltraRocks)
|
||||
create_region(world, "Cackletta's Soul", cacklettas_soul)
|
||||
|
||||
if world.options.coins:
|
||||
create_region(world, "Coins", coins, excluded)
|
||||
create_region(world, "Coins", coins)
|
||||
|
||||
if not world.options.castle_skip:
|
||||
create_region(world, "Bowser's Castle", bowsers, excluded)
|
||||
create_region(world, "Bowser's Castle Mini", bowsersMini, excluded)
|
||||
create_region(world, "Bowser's Castle", bowsers)
|
||||
create_region(world, "Bowser's Castle Mini", bowsersMini)
|
||||
|
||||
|
||||
def connect_regions(world: "MLSSWorld"):
|
||||
@@ -221,6 +223,9 @@ def connect_regions(world: "MLSSWorld"):
|
||||
"Bowser's Castle Mini",
|
||||
lambda state: StateLogic.canMini(state, world.player) and StateLogic.thunder(state, world.player),
|
||||
)
|
||||
connect(world, names, "Bowser's Castle Mini", "Cackletta's Soul")
|
||||
else:
|
||||
connect(world, names, "PostJokes", "Cackletta's Soul")
|
||||
connect(world, names, "Chucklehuck Woods", "Winkle", lambda state: StateLogic.canDash(state, world.player))
|
||||
connect(
|
||||
world,
|
||||
@@ -282,11 +287,11 @@ def connect_regions(world: "MLSSWorld"):
|
||||
)
|
||||
|
||||
|
||||
def create_region(world: "MLSSWorld", name, locations, excluded):
|
||||
def create_region(world: "MLSSWorld", name, locations):
|
||||
ret = Region(name, world.player, world.multiworld)
|
||||
for location in locations:
|
||||
loc = MLSSLocation(world.player, location.name, location.id, ret)
|
||||
if location.name in excluded:
|
||||
if location.name in world.disabled_locations:
|
||||
continue
|
||||
ret.locations.append(loc)
|
||||
world.multiworld.regions.append(ret)
|
||||
|
||||
@@ -8,7 +8,7 @@ from BaseClasses import Item, Location
|
||||
from settings import get_settings
|
||||
from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes, APPatchExtension
|
||||
from .Items import item_table
|
||||
from .Locations import shop, badge, pants, location_table, hidden, all_locations
|
||||
from .Locations import shop, badge, pants, location_table, all_locations
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import MLSSWorld
|
||||
@@ -88,7 +88,7 @@ class MLSSPatchExtension(APPatchExtension):
|
||||
return rom
|
||||
stream = io.BytesIO(rom)
|
||||
|
||||
for location in all_locations:
|
||||
for location in [location for location in all_locations if location.itemType == 0]:
|
||||
stream.seek(location.id - 6)
|
||||
b = stream.read(1)
|
||||
if b[0] == 0x10 and options["block_visibility"] == 1:
|
||||
@@ -133,7 +133,7 @@ class MLSSPatchExtension(APPatchExtension):
|
||||
stream = io.BytesIO(rom)
|
||||
random.seed(options["seed"] + options["player"])
|
||||
|
||||
if options["randomize_bosses"] == 1 or (options["randomize_bosses"] == 2) and options["randomize_enemies"] == 0:
|
||||
if options["randomize_bosses"] == 1 or (options["randomize_bosses"] == 2 and options["randomize_enemies"] == 0):
|
||||
raw = []
|
||||
for pos in bosses:
|
||||
stream.seek(pos + 1)
|
||||
@@ -164,6 +164,7 @@ class MLSSPatchExtension(APPatchExtension):
|
||||
|
||||
enemies_raw = []
|
||||
groups = []
|
||||
boss_groups = []
|
||||
|
||||
if options["randomize_enemies"] == 0:
|
||||
return stream.getvalue()
|
||||
@@ -171,7 +172,7 @@ class MLSSPatchExtension(APPatchExtension):
|
||||
if options["randomize_bosses"] == 2:
|
||||
for pos in bosses:
|
||||
stream.seek(pos + 1)
|
||||
groups += [stream.read(0x1F)]
|
||||
boss_groups += [stream.read(0x1F)]
|
||||
|
||||
for pos in enemies:
|
||||
stream.seek(pos + 8)
|
||||
@@ -221,12 +222,19 @@ class MLSSPatchExtension(APPatchExtension):
|
||||
groups += [raw]
|
||||
chomp = False
|
||||
|
||||
random.shuffle(groups)
|
||||
arr = enemies
|
||||
if options["randomize_bosses"] == 2:
|
||||
arr += bosses
|
||||
groups += boss_groups
|
||||
|
||||
random.shuffle(groups)
|
||||
|
||||
for pos in arr:
|
||||
if arr[-1] in boss_groups:
|
||||
stream.seek(pos)
|
||||
temp = stream.read(1)
|
||||
stream.seek(pos)
|
||||
stream.write(bytes([temp[0] | 0x8]))
|
||||
stream.seek(pos + 1)
|
||||
stream.write(groups.pop())
|
||||
|
||||
@@ -320,20 +328,9 @@ def write_tokens(world: "MLSSWorld", patch: MLSSProcedurePatch) -> None:
|
||||
patch.write_token(APTokenTypes.WRITE, address + 3, bytes([world.random.randint(0x0, 0x26)]))
|
||||
|
||||
for location_name in location_table.keys():
|
||||
if (
|
||||
(world.options.skip_minecart and "Minecart" in location_name and "After" not in location_name)
|
||||
or (world.options.castle_skip and "Bowser" in location_name)
|
||||
or (world.options.disable_surf and "Surf Minigame" in location_name)
|
||||
or (world.options.harhalls_pants and "Harhall's" in location_name)
|
||||
):
|
||||
if location_name in world.disabled_locations:
|
||||
continue
|
||||
if (world.options.chuckle_beans == 0 and "Digspot" in location_name) or (
|
||||
world.options.chuckle_beans == 1 and location_table[location_name] in hidden
|
||||
):
|
||||
continue
|
||||
if not world.options.coins and "Coin" in location_name:
|
||||
continue
|
||||
location = world.multiworld.get_location(location_name, world.player)
|
||||
location = world.get_location(location_name)
|
||||
item = location.item
|
||||
address = [address for address in all_locations if address.name == location.name]
|
||||
item_inject(world, patch, location.address, address[0].itemType, item)
|
||||
|
||||
@@ -13,7 +13,7 @@ def set_rules(world: "MLSSWorld", excluded):
|
||||
for location in all_locations:
|
||||
if "Digspot" in location.name:
|
||||
if (world.options.skip_minecart and "Minecart" in location.name) or (
|
||||
world.options.castle_skip and "Bowser" in location.name
|
||||
world.options.castle_skip and "Bowser" in location.name
|
||||
):
|
||||
continue
|
||||
if world.options.chuckle_beans == 0 or world.options.chuckle_beans == 1 and location.id in hidden:
|
||||
@@ -218,9 +218,9 @@ def set_rules(world: "MLSSWorld", excluded):
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsUltraHammerUpgrade),
|
||||
lambda state: StateLogic.thunder(state, world.player)
|
||||
and StateLogic.pieces(state, world.player)
|
||||
and StateLogic.castleTown(state, world.player)
|
||||
and StateLogic.rose(state, world.player),
|
||||
and StateLogic.pieces(state, world.player)
|
||||
and StateLogic.castleTown(state, world.player)
|
||||
and StateLogic.rose(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsSoloLuigiCaveMole),
|
||||
@@ -235,27 +235,27 @@ def set_rules(world: "MLSSWorld", excluded):
|
||||
lambda state: StateLogic.canDig(state, world.player) and StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootBlock1),
|
||||
world.get_location(LocationName.ChucklehuckWoodsPastChucklerootBlock1),
|
||||
lambda state: StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootBlock2),
|
||||
world.get_location(LocationName.ChucklehuckWoodsPastChucklerootBlock2),
|
||||
lambda state: StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootBlock3),
|
||||
world.get_location(LocationName.ChucklehuckWoodsPastChucklerootBlock3),
|
||||
lambda state: StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootBlock4),
|
||||
world.get_location(LocationName.ChucklehuckWoodsPastChucklerootBlock4),
|
||||
lambda state: StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootBlock5),
|
||||
world.get_location(LocationName.ChucklehuckWoodsPastChucklerootBlock5),
|
||||
lambda state: StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootBlock6),
|
||||
world.get_location(LocationName.ChucklehuckWoodsPastChucklerootBlock6),
|
||||
lambda state: StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
@@ -350,10 +350,6 @@ def set_rules(world: "MLSSWorld", excluded):
|
||||
world.get_location(LocationName.TeeheeValleyPastUltraHammersBlock2),
|
||||
lambda state: StateLogic.ultra(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.TeeheeValleySoloLuigiMazeRoom1Block),
|
||||
lambda state: StateLogic.ultra(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.OhoOasisFirebrand),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
@@ -462,6 +458,143 @@ def set_rules(world: "MLSSWorld", excluded):
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
|
||||
if world.options.randomize_bosses.value != 0:
|
||||
if world.options.chuckle_beans != 0:
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainHoohoorosRoomDigspot1),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainPastHoohoorosDigspot),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainPastHoohoorosConnectorRoomDigspot1),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainBelowSummitDigspot),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainSummitDigspot),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
if world.options.chuckle_beans == 2:
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainHoohoorosRoomDigspot2),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainPastHoohoorosConnectorRoomDigspot2),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooVillageHammers),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainPeasleysRose),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainHoohoorosRoomBlock1),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainHoohoorosRoomBlock2),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainBelowSummitBlock1),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainBelowSummitBlock2),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainBelowSummitBlock3),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainPastHoohoorosBlock1),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainPastHoohoorosBlock2),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainPastHoohoorosConnectorRoomBlock),
|
||||
lambda state: StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player),
|
||||
)
|
||||
|
||||
if not world.options.difficult_logic:
|
||||
if world.options.chuckle_beans != 0:
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndNortheastOfBoilerRoom2Digspot),
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndNortheastOfBoilerRoom3Digspot),
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndNortheastOfBoilerRoom1Block),
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndNortheastOfBoilerRoom2Block1),
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndFurnaceRoom1Block1),
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndFurnaceRoom1Block2),
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndFurnaceRoom1Block3),
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
|
||||
if world.options.coins:
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainBaseBooStatueCaveCoinBlock1),
|
||||
@@ -516,7 +649,7 @@ def set_rules(world: "MLSSWorld", excluded):
|
||||
lambda state: StateLogic.brooch(state, world.player) and StateLogic.hammers(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootCoinBlock),
|
||||
world.get_location(LocationName.ChucklehuckWoodsPastChucklerootCoinBlock),
|
||||
lambda state: StateLogic.brooch(state, world.player) and StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
@@ -546,23 +679,23 @@ def set_rules(world: "MLSSWorld", excluded):
|
||||
add_rule(
|
||||
world.get_location(LocationName.GwarharLagoonFirstUnderwaterAreaRoom2CoinBlock),
|
||||
lambda state: StateLogic.canDash(state, world.player)
|
||||
and (StateLogic.membership(state, world.player) or StateLogic.surfable(state, world.player)),
|
||||
and (StateLogic.membership(state, world.player) or StateLogic.surfable(state, world.player)),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndSecondFloorWestRoomCoinBlock),
|
||||
lambda state: StateLogic.ultra(state, world.player)
|
||||
and StateLogic.fire(state, world.player)
|
||||
and (
|
||||
StateLogic.membership(state, world.player)
|
||||
or (StateLogic.canDig(state, world.player) and StateLogic.canMini(state, world.player))
|
||||
),
|
||||
and StateLogic.fire(state, world.player)
|
||||
and (StateLogic.membership(state, world.player)
|
||||
or (StateLogic.canDig(state, world.player)
|
||||
and StateLogic.canMini(state, world.player))),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndNorthofBridgeRoomCoinBlock),
|
||||
lambda state: StateLogic.ultra(state, world.player)
|
||||
and StateLogic.fire(state, world.player)
|
||||
and StateLogic.canDig(state, world.player)
|
||||
and (StateLogic.membership(state, world.player) or StateLogic.canMini(state, world.player)),
|
||||
and StateLogic.fire(state, world.player)
|
||||
and StateLogic.canDig(state, world.player)
|
||||
and (StateLogic.membership(state, world.player)
|
||||
or StateLogic.canMini(state, world.player)),
|
||||
)
|
||||
if not world.options.difficult_logic:
|
||||
add_rule(
|
||||
|
||||
@@ -4,7 +4,7 @@ import typing
|
||||
import settings
|
||||
from BaseClasses import Tutorial, ItemClassification
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from typing import List, Dict, Any
|
||||
from typing import Set, Dict, Any
|
||||
from .Locations import all_locations, location_table, bowsers, bowsersMini, hidden, coins
|
||||
from .Options import MLSSOptions
|
||||
from .Items import MLSSItem, itemList, item_frequencies, item_table
|
||||
@@ -55,29 +55,29 @@ class MLSSWorld(World):
|
||||
settings: typing.ClassVar[MLSSSettings]
|
||||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||||
location_name_to_id = {loc_data.name: loc_data.id for loc_data in all_locations}
|
||||
required_client_version = (0, 4, 5)
|
||||
required_client_version = (0, 5, 0)
|
||||
|
||||
disabled_locations: List[str]
|
||||
disabled_locations: Set[str]
|
||||
|
||||
def generate_early(self) -> None:
|
||||
self.disabled_locations = []
|
||||
if self.options.chuckle_beans == 0:
|
||||
self.disabled_locations += [location.name for location in all_locations if "Digspot" in location.name]
|
||||
if self.options.castle_skip:
|
||||
self.disabled_locations += [location.name for location in all_locations if "Bowser" in location.name]
|
||||
if self.options.chuckle_beans == 1:
|
||||
self.disabled_locations = [location.name for location in all_locations if location.id in hidden]
|
||||
self.disabled_locations = set()
|
||||
if self.options.skip_minecart:
|
||||
self.disabled_locations += [LocationName.HoohooMountainBaseMinecartCaveDigspot]
|
||||
self.disabled_locations.update([LocationName.HoohooMountainBaseMinecartCaveDigspot])
|
||||
if self.options.disable_surf:
|
||||
self.disabled_locations += [LocationName.SurfMinigame]
|
||||
if self.options.harhalls_pants:
|
||||
self.disabled_locations += [LocationName.HarhallsPants]
|
||||
self.disabled_locations.update([LocationName.SurfMinigame])
|
||||
if self.options.disable_harhalls_pants:
|
||||
self.disabled_locations.update([LocationName.HarhallsPants])
|
||||
if self.options.chuckle_beans == 0:
|
||||
self.disabled_locations.update([location.name for location in all_locations if "Digspot" in location.name])
|
||||
if self.options.chuckle_beans == 1:
|
||||
self.disabled_locations.update([location.name for location in all_locations if location.id in hidden])
|
||||
if self.options.castle_skip:
|
||||
self.disabled_locations.update([location.name for location in bowsers + bowsersMini])
|
||||
if not self.options.coins:
|
||||
self.disabled_locations += [location.name for location in all_locations if location in coins]
|
||||
self.disabled_locations.update([location.name for location in coins])
|
||||
|
||||
def create_regions(self) -> None:
|
||||
create_regions(self, self.disabled_locations)
|
||||
create_regions(self)
|
||||
connect_regions(self)
|
||||
|
||||
item = self.create_item("Mushroom")
|
||||
@@ -90,13 +90,15 @@ class MLSSWorld(World):
|
||||
self.get_location(LocationName.PantsShopStartingFlag1).place_locked_item(item)
|
||||
item = self.create_item("Chuckle Bean")
|
||||
self.get_location(LocationName.PantsShopStartingFlag2).place_locked_item(item)
|
||||
item = MLSSItem("Victory", ItemClassification.progression, None, self.player)
|
||||
self.get_location("Cackletta's Soul").place_locked_item(item)
|
||||
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"CastleSkip": self.options.castle_skip.value,
|
||||
"SkipMinecart": self.options.skip_minecart.value,
|
||||
"DisableSurf": self.options.disable_surf.value,
|
||||
"HarhallsPants": self.options.harhalls_pants.value,
|
||||
"HarhallsPants": self.options.disable_harhalls_pants.value,
|
||||
"ChuckleBeans": self.options.chuckle_beans.value,
|
||||
"DifficultLogic": self.options.difficult_logic.value,
|
||||
"Coins": self.options.coins.value,
|
||||
@@ -111,7 +113,7 @@ class MLSSWorld(World):
|
||||
freq = item_frequencies.get(item.itemName, 1)
|
||||
if item in precollected:
|
||||
freq = max(freq - precollected.count(item), 0)
|
||||
if self.options.harhalls_pants and "Harhall's" in item.itemName:
|
||||
if self.options.disable_harhalls_pants and "Harhall's" in item.itemName:
|
||||
continue
|
||||
required_items += [item.itemName for _ in range(freq)]
|
||||
|
||||
@@ -135,21 +137,7 @@ class MLSSWorld(World):
|
||||
filler_items += [item.itemName for _ in range(freq)]
|
||||
|
||||
# And finally take as many fillers as we need to have the same amount of items and locations.
|
||||
remaining = len(all_locations) - len(required_items) - 5
|
||||
if self.options.castle_skip:
|
||||
remaining -= len(bowsers) + len(bowsersMini) - (5 if self.options.chuckle_beans == 0 else 0)
|
||||
if self.options.skip_minecart and self.options.chuckle_beans == 2:
|
||||
remaining -= 1
|
||||
if self.options.disable_surf:
|
||||
remaining -= 1
|
||||
if self.options.harhalls_pants:
|
||||
remaining -= 1
|
||||
if self.options.chuckle_beans == 0:
|
||||
remaining -= 192
|
||||
if self.options.chuckle_beans == 1:
|
||||
remaining -= 59
|
||||
if not self.options.coins:
|
||||
remaining -= len(coins)
|
||||
remaining = len(all_locations) - len(required_items) - len(self.disabled_locations) - 5
|
||||
|
||||
self.multiworld.itempool += [
|
||||
self.create_item(filler_item_name) for filler_item_name in self.random.sample(filler_items, remaining)
|
||||
@@ -157,21 +145,14 @@ class MLSSWorld(World):
|
||||
|
||||
def set_rules(self) -> None:
|
||||
set_rules(self, self.disabled_locations)
|
||||
if self.options.castle_skip:
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.can_reach(
|
||||
"PostJokes", "Region", self.player
|
||||
)
|
||||
else:
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.can_reach(
|
||||
"Bowser's Castle Mini", "Region", self.player
|
||||
)
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
|
||||
|
||||
def create_item(self, name: str) -> MLSSItem:
|
||||
item = item_table[name]
|
||||
return MLSSItem(item.itemName, item.classification, item.code, self.player)
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.random.choice(list(filter(lambda item: item.classification == ItemClassification.filler, itemList)))
|
||||
return self.random.choice(list(filter(lambda item: item.classification == ItemClassification.filler, itemList))).itemName
|
||||
|
||||
def generate_output(self, output_directory: str) -> None:
|
||||
patch = MLSSProcedurePatch(player=self.player, player_name=self.multiworld.player_name[self.player])
|
||||
|
||||
Binary file not shown.
@@ -37,7 +37,7 @@ weapons_to_name: Dict[int, str] = {
|
||||
minimum_weakness_requirement: Dict[int, int] = {
|
||||
0: 1, # Mega Buster is free
|
||||
1: 14, # 2 shots of Atomic Fire
|
||||
2: 1, # 14 shots of Air Shooter, although you likely hit more than one shot
|
||||
2: 2, # 14 shots of Air Shooter
|
||||
3: 4, # 9 uses of Leaf Shield, 3 ends up 1 damage off
|
||||
4: 1, # 56 uses of Bubble Lead
|
||||
5: 1, # 224 uses of Quick Boomerang
|
||||
|
||||
@@ -97,6 +97,28 @@ class MMBN3World(World):
|
||||
add_item_rule(loc, lambda item: not item.advancement)
|
||||
region.locations.append(loc)
|
||||
self.multiworld.regions.append(region)
|
||||
|
||||
# Regions which contribute to explore score when accessible.
|
||||
explore_score_region_names = (
|
||||
RegionName.WWW_Island,
|
||||
RegionName.SciLab_Overworld,
|
||||
RegionName.SciLab_Cyberworld,
|
||||
RegionName.Yoka_Overworld,
|
||||
RegionName.Yoka_Cyberworld,
|
||||
RegionName.Beach_Overworld,
|
||||
RegionName.Beach_Cyberworld,
|
||||
RegionName.Undernet,
|
||||
RegionName.Deep_Undernet,
|
||||
RegionName.Secret_Area,
|
||||
)
|
||||
explore_score_regions = [self.get_region(region_name) for region_name in explore_score_region_names]
|
||||
|
||||
# Entrances which use explore score in their logic need to register all the explore score regions as indirect
|
||||
# conditions.
|
||||
def register_explore_score_indirect_conditions(entrance):
|
||||
for explore_score_region in explore_score_regions:
|
||||
self.multiworld.register_indirect_condition(explore_score_region, entrance)
|
||||
|
||||
for region_info in regions:
|
||||
region = name_to_region[region_info.name]
|
||||
for connection in region_info.connections:
|
||||
@@ -119,6 +141,7 @@ class MMBN3World(World):
|
||||
entrance.access_rule = lambda state: \
|
||||
state.has(ItemName.CSciPas, self.player) or \
|
||||
state.can_reach(RegionName.SciLab_Overworld, "Region", self.player)
|
||||
self.multiworld.register_indirect_condition(self.get_region(RegionName.SciLab_Overworld), entrance)
|
||||
if connection == RegionName.Yoka_Cyberworld:
|
||||
entrance.access_rule = lambda state: \
|
||||
state.has(ItemName.CYokaPas, self.player) or \
|
||||
@@ -126,16 +149,19 @@ class MMBN3World(World):
|
||||
state.can_reach(RegionName.SciLab_Overworld, "Region", self.player) and
|
||||
state.has(ItemName.Press, self.player)
|
||||
)
|
||||
self.multiworld.register_indirect_condition(self.get_region(RegionName.SciLab_Overworld), entrance)
|
||||
if connection == RegionName.Beach_Cyberworld:
|
||||
entrance.access_rule = lambda state: state.has(ItemName.CBeacPas, self.player) and\
|
||||
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player)
|
||||
|
||||
self.multiworld.register_indirect_condition(self.get_region(RegionName.Yoka_Overworld), entrance)
|
||||
if connection == RegionName.Undernet:
|
||||
entrance.access_rule = lambda state: self.explore_score(state) > 8 and\
|
||||
state.has(ItemName.Press, self.player)
|
||||
register_explore_score_indirect_conditions(entrance)
|
||||
if connection == RegionName.Secret_Area:
|
||||
entrance.access_rule = lambda state: self.explore_score(state) > 12 and\
|
||||
state.has(ItemName.Hammer, self.player)
|
||||
register_explore_score_indirect_conditions(entrance)
|
||||
if connection == RegionName.WWW_Island:
|
||||
entrance.access_rule = lambda state:\
|
||||
state.has(ItemName.Progressive_Undernet_Rank, self.player, 8)
|
||||
|
||||
@@ -64,7 +64,7 @@ class OOTRegion(Region):
|
||||
return None
|
||||
|
||||
def can_reach(self, state):
|
||||
if state.stale[self.player]:
|
||||
if state._oot_stale[self.player]:
|
||||
stored_age = state.age[self.player]
|
||||
state._oot_update_age_reachable_regions(self.player)
|
||||
state.age[self.player] = stored_age
|
||||
|
||||
@@ -8,12 +8,17 @@ from .Hints import HintArea
|
||||
from .Items import oot_is_item_of_type
|
||||
from .LocationList import dungeon_song_locations
|
||||
|
||||
from BaseClasses import CollectionState
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from worlds.generic.Rules import set_rule, add_rule, add_item_rule, forbid_item
|
||||
from ..AutoWorld import LogicMixin
|
||||
|
||||
|
||||
class OOTLogic(LogicMixin):
|
||||
def init_mixin(self, parent: MultiWorld):
|
||||
# Separate stale state for OOTRegion.can_reach() to use because CollectionState.update_reachable_regions() sets
|
||||
# `self.state[player] = False` for all players without updating OOT's age region accessibility.
|
||||
self._oot_stale = {player: True for player, world in parent.worlds.items()
|
||||
if parent.worlds[player].game == "Ocarina of Time"}
|
||||
|
||||
def _oot_has_stones(self, count, player):
|
||||
return self.has_group("stones", player, count)
|
||||
@@ -92,9 +97,9 @@ class OOTLogic(LogicMixin):
|
||||
return False
|
||||
|
||||
# Store the age before calling this!
|
||||
def _oot_update_age_reachable_regions(self, player):
|
||||
self.stale[player] = False
|
||||
for age in ['child', 'adult']:
|
||||
def _oot_update_age_reachable_regions(self, player):
|
||||
self._oot_stale[player] = False
|
||||
for age in ['child', 'adult']:
|
||||
self.age[player] = age
|
||||
rrp = getattr(self, f'{age}_reachable_regions')[player]
|
||||
bc = getattr(self, f'{age}_blocked_connections')[player]
|
||||
|
||||
@@ -1301,6 +1301,7 @@ class OOTWorld(World):
|
||||
# the appropriate number of keys in the collection state when they are
|
||||
# picked up.
|
||||
def collect(self, state: CollectionState, item: OOTItem) -> bool:
|
||||
state._oot_stale[self.player] = True
|
||||
if item.advancement and item.special and item.special.get('alias', False):
|
||||
alt_item_name, count = item.special.get('alias')
|
||||
state.prog_items[self.player][alt_item_name] += count
|
||||
@@ -1313,8 +1314,12 @@ class OOTWorld(World):
|
||||
state.prog_items[self.player][alt_item_name] -= count
|
||||
if state.prog_items[self.player][alt_item_name] < 1:
|
||||
del (state.prog_items[self.player][alt_item_name])
|
||||
state._oot_stale[self.player] = True
|
||||
return True
|
||||
return super().remove(state, item)
|
||||
changed = super().remove(state, item)
|
||||
if changed:
|
||||
state._oot_stale[self.player] = True
|
||||
return changed
|
||||
|
||||
|
||||
# Helper functions
|
||||
@@ -1389,7 +1394,7 @@ class OOTWorld(World):
|
||||
# If free_scarecrow give Scarecrow Song
|
||||
if self.free_scarecrow:
|
||||
all_state.collect(self.create_item("Scarecrow Song"), prevent_sweep=True)
|
||||
all_state.stale[self.player] = True
|
||||
all_state._oot_stale[self.player] = True
|
||||
|
||||
return all_state
|
||||
|
||||
|
||||
@@ -33,6 +33,12 @@ class OSRSWeb(WebWorld):
|
||||
|
||||
|
||||
class OSRSWorld(World):
|
||||
"""
|
||||
The best retro fantasy MMORPG on the planet. Old School is RuneScape but… older! This is the open world you know and love, but as it was in 2007.
|
||||
The Randomizer takes the form of a Chunk-Restricted f2p Ironman that takes a brand new account up through defeating
|
||||
the Green Dragon of Crandor and earning a spot in the fabled Champion's Guild!
|
||||
"""
|
||||
|
||||
game = "Old School Runescape"
|
||||
options_dataclass = OSRSOptions
|
||||
options: OSRSOptions
|
||||
@@ -635,7 +641,7 @@ class OSRSWorld(World):
|
||||
else:
|
||||
return lambda state: can_tan(state) or (can_silver(state) and can_smelt_silver(state)) or \
|
||||
(can_gold(state) and can_smelt_gold(state))
|
||||
if skill.lower() == "Cooking":
|
||||
if skill.lower() == "cooking":
|
||||
if self.options.brutal_grinds or level < 15:
|
||||
return lambda state: state.can_reach(RegionNames.Milk, "Region", self.player) or \
|
||||
state.can_reach(RegionNames.Egg, "Region", self.player) or \
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
# 2.3.0
|
||||
|
||||
### Features
|
||||
|
||||
- Added a Swedish translation of the setup guide.
|
||||
- The client communicates map transitions to any trackers connected to the slot.
|
||||
- Added the player's Normalize Encounter Rates option to slot data for trackers.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fixed a logic issue where the "Mauville City - Coin Case from Lady in House" location only required a Harbor Mail if
|
||||
the player randomized NPC gifts.
|
||||
- The Dig tutor has its compatibility percentage raised to 50% if the player's TM/tutor compatibility is set lower.
|
||||
- A Team Magma Grunt in the Space Center which could become unreachable while trainersanity is active by overlapping
|
||||
with another NPC was moved to an unoccupied space.
|
||||
- Fixed a problem where the client would crash on certain operating systems while using certain python versions if the
|
||||
player tried to wonder trade.
|
||||
|
||||
# 2.2.0
|
||||
|
||||
### Features
|
||||
@@ -175,6 +193,7 @@ turn to face you when you run.
|
||||
species equally likely to appear, but makes rare encounters less rare.
|
||||
- Added `Trick House` location group.
|
||||
- Removed `Postgame Locations` location group.
|
||||
- Added a Spanish translation of the setup guide.
|
||||
|
||||
### QoL
|
||||
|
||||
|
||||
@@ -133,6 +133,7 @@ class PokemonEmeraldClient(BizHawkClient):
|
||||
latest_wonder_trade_reply: dict
|
||||
wonder_trade_cooldown: int
|
||||
wonder_trade_cooldown_timer: int
|
||||
queued_received_trade: Optional[str]
|
||||
|
||||
death_counter: Optional[int]
|
||||
previous_death_link: float
|
||||
@@ -153,6 +154,7 @@ class PokemonEmeraldClient(BizHawkClient):
|
||||
self.previous_death_link = 0
|
||||
self.ignore_next_death_link = False
|
||||
self.current_map = None
|
||||
self.queued_received_trade = None
|
||||
|
||||
async def validate_rom(self, ctx: "BizHawkClientContext") -> bool:
|
||||
from CommonClient import logger
|
||||
@@ -350,6 +352,7 @@ class PokemonEmeraldClient(BizHawkClient):
|
||||
|
||||
# Send game clear
|
||||
if not ctx.finished_game and game_clear:
|
||||
ctx.finished_game = True
|
||||
await ctx.send_msgs([{
|
||||
"cmd": "StatusUpdate",
|
||||
"status": ClientStatus.CLIENT_GOAL,
|
||||
@@ -548,22 +551,29 @@ class PokemonEmeraldClient(BizHawkClient):
|
||||
(sb1_address + 0x37CC, [1], "System Bus"),
|
||||
])
|
||||
elif trade_is_sent != 0 and wonder_trade_pokemon_data[19] != 2:
|
||||
# Game is waiting on receiving a trade. See if there are any available trades that were not
|
||||
# sent by this player, and if so, try to receive one.
|
||||
if self.wonder_trade_cooldown_timer <= 0 and f"pokemon_wonder_trades_{ctx.team}" in ctx.stored_data:
|
||||
# Game is waiting on receiving a trade.
|
||||
if self.queued_received_trade is not None:
|
||||
# Client is holding a trade, ready to write it into the game
|
||||
success = await bizhawk.guarded_write(ctx.bizhawk_ctx, [
|
||||
(sb1_address + 0x377C, json_to_pokemon_data(self.queued_received_trade), "System Bus"),
|
||||
], [guards["SAVE BLOCK 1"]])
|
||||
|
||||
# Notify the player if it was written, otherwise hold it for the next loop
|
||||
if success:
|
||||
logger.info("Wonder trade received!")
|
||||
self.queued_received_trade = None
|
||||
|
||||
elif self.wonder_trade_cooldown_timer <= 0 and f"pokemon_wonder_trades_{ctx.team}" in ctx.stored_data:
|
||||
# See if there are any available trades that were not sent by this player. If so, try to receive one.
|
||||
if any(item[0] != ctx.slot
|
||||
for key, item in ctx.stored_data.get(f"pokemon_wonder_trades_{ctx.team}", {}).items()
|
||||
if key != "_lock" and orjson.loads(item[1])["species"] <= 386):
|
||||
received_trade = await self.wonder_trade_receive(ctx)
|
||||
if received_trade is None:
|
||||
self.queued_received_trade = await self.wonder_trade_receive(ctx)
|
||||
if self.queued_received_trade is None:
|
||||
self.wonder_trade_cooldown_timer = self.wonder_trade_cooldown
|
||||
self.wonder_trade_cooldown *= 2
|
||||
self.wonder_trade_cooldown += random.randrange(0, 500)
|
||||
else:
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [
|
||||
(sb1_address + 0x377C, json_to_pokemon_data(received_trade), "System Bus"),
|
||||
])
|
||||
logger.info("Wonder trade received!")
|
||||
self.wonder_trade_cooldown = 5000
|
||||
|
||||
else:
|
||||
|
||||
@@ -3,6 +3,7 @@ import settings
|
||||
import typing
|
||||
import threading
|
||||
import base64
|
||||
import random
|
||||
from copy import deepcopy
|
||||
from typing import TextIO
|
||||
|
||||
@@ -14,7 +15,7 @@ from worlds.generic.Rules import add_item_rule
|
||||
from .items import item_table, item_groups
|
||||
from .locations import location_data, PokemonRBLocation
|
||||
from .regions import create_regions
|
||||
from .options import pokemon_rb_options
|
||||
from .options import PokemonRBOptions
|
||||
from .rom_addresses import rom_addresses
|
||||
from .text import encode_text
|
||||
from .rom import generate_output, get_base_rom_bytes, get_base_rom_path, RedDeltaPatch, BlueDeltaPatch
|
||||
@@ -71,7 +72,10 @@ class PokemonRedBlueWorld(World):
|
||||
Elite Four to become the champion!"""
|
||||
# -MuffinJets#4559
|
||||
game = "Pokemon Red and Blue"
|
||||
option_definitions = pokemon_rb_options
|
||||
|
||||
options_dataclass = PokemonRBOptions
|
||||
options: PokemonRBOptions
|
||||
|
||||
settings: typing.ClassVar[PokemonSettings]
|
||||
|
||||
required_client_version = (0, 4, 2)
|
||||
@@ -85,8 +89,8 @@ class PokemonRedBlueWorld(World):
|
||||
|
||||
web = PokemonWebWorld()
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
super().__init__(world, player)
|
||||
def __init__(self, multiworld: MultiWorld, player: int):
|
||||
super().__init__(multiworld, player)
|
||||
self.item_pool = []
|
||||
self.total_key_items = None
|
||||
self.fly_map = None
|
||||
@@ -101,11 +105,11 @@ class PokemonRedBlueWorld(World):
|
||||
self.learnsets = None
|
||||
self.trainer_name = None
|
||||
self.rival_name = None
|
||||
self.type_chart = None
|
||||
self.traps = None
|
||||
self.trade_mons = {}
|
||||
self.finished_level_scaling = threading.Event()
|
||||
self.dexsanity_table = []
|
||||
self.trainersanity_table = []
|
||||
self.local_locs = []
|
||||
|
||||
@classmethod
|
||||
@@ -113,11 +117,109 @@ class PokemonRedBlueWorld(World):
|
||||
versions = set()
|
||||
for player in multiworld.player_ids:
|
||||
if multiworld.worlds[player].game == "Pokemon Red and Blue":
|
||||
versions.add(multiworld.game_version[player].current_key)
|
||||
versions.add(multiworld.worlds[player].options.game_version.current_key)
|
||||
for version in versions:
|
||||
if not os.path.exists(get_base_rom_path(version)):
|
||||
raise FileNotFoundError(get_base_rom_path(version))
|
||||
|
||||
@classmethod
|
||||
def stage_generate_early(cls, multiworld: MultiWorld):
|
||||
|
||||
seed_groups = {}
|
||||
pokemon_rb_worlds = multiworld.get_game_worlds("Pokemon Red and Blue")
|
||||
|
||||
for world in pokemon_rb_worlds:
|
||||
if not (world.options.type_chart_seed.value.isdigit() or world.options.type_chart_seed.value == "random"):
|
||||
seed_groups[world.options.type_chart_seed.value] = seed_groups.get(world.options.type_chart_seed.value,
|
||||
[]) + [world]
|
||||
|
||||
copy_chart_worlds = {}
|
||||
|
||||
for worlds in seed_groups.values():
|
||||
chosen_world = multiworld.random.choice(worlds)
|
||||
for world in worlds:
|
||||
if world is not chosen_world:
|
||||
copy_chart_worlds[world.player] = chosen_world
|
||||
|
||||
for world in pokemon_rb_worlds:
|
||||
if world.player in copy_chart_worlds:
|
||||
continue
|
||||
tc_random = world.random
|
||||
if world.options.type_chart_seed.value.isdigit():
|
||||
tc_random = random.Random()
|
||||
tc_random.seed(int(world.options.type_chart_seed.value))
|
||||
|
||||
if world.options.randomize_type_chart == "vanilla":
|
||||
chart = deepcopy(poke_data.type_chart)
|
||||
elif world.options.randomize_type_chart == "randomize":
|
||||
types = poke_data.type_names.values()
|
||||
matchups = []
|
||||
for type1 in types:
|
||||
for type2 in types:
|
||||
matchups.append([type1, type2])
|
||||
tc_random.shuffle(matchups)
|
||||
immunities = world.options.immunity_matchups.value
|
||||
super_effectives = world.options.super_effective_matchups.value
|
||||
not_very_effectives = world.options.not_very_effective_matchups.value
|
||||
normals = world.options.normal_matchups.value
|
||||
while super_effectives + not_very_effectives + normals < 225 - immunities:
|
||||
if super_effectives == not_very_effectives == normals == 0:
|
||||
super_effectives = 225
|
||||
not_very_effectives = 225
|
||||
normals = 225
|
||||
else:
|
||||
super_effectives += world.options.super_effective_matchups.value
|
||||
not_very_effectives += world.options.not_very_effective_matchups.value
|
||||
normals += world.options.normal_matchups.value
|
||||
if super_effectives + not_very_effectives + normals > 225 - immunities:
|
||||
total = super_effectives + not_very_effectives + normals
|
||||
excess = total - (225 - immunities)
|
||||
subtract_amounts = (
|
||||
int((excess / (super_effectives + not_very_effectives + normals)) * super_effectives),
|
||||
int((excess / (super_effectives + not_very_effectives + normals)) * not_very_effectives),
|
||||
int((excess / (super_effectives + not_very_effectives + normals)) * normals))
|
||||
super_effectives -= subtract_amounts[0]
|
||||
not_very_effectives -= subtract_amounts[1]
|
||||
normals -= subtract_amounts[2]
|
||||
while super_effectives + not_very_effectives + normals > 225 - immunities:
|
||||
r = tc_random.randint(0, 2)
|
||||
if r == 0 and super_effectives:
|
||||
super_effectives -= 1
|
||||
elif r == 1 and not_very_effectives:
|
||||
not_very_effectives -= 1
|
||||
elif normals:
|
||||
normals -= 1
|
||||
chart = []
|
||||
for matchup_list, matchup_value in zip([immunities, normals, super_effectives, not_very_effectives],
|
||||
[0, 10, 20, 5]):
|
||||
for _ in range(matchup_list):
|
||||
matchup = matchups.pop()
|
||||
matchup.append(matchup_value)
|
||||
chart.append(matchup)
|
||||
elif world.options.randomize_type_chart == "chaos":
|
||||
types = poke_data.type_names.values()
|
||||
matchups = []
|
||||
for type1 in types:
|
||||
for type2 in types:
|
||||
matchups.append([type1, type2])
|
||||
chart = []
|
||||
values = list(range(21))
|
||||
tc_random.shuffle(matchups)
|
||||
tc_random.shuffle(values)
|
||||
for matchup in matchups:
|
||||
value = values.pop(0)
|
||||
values.append(value)
|
||||
matchup.append(value)
|
||||
chart.append(matchup)
|
||||
# sort so that super-effective matchups occur first, to prevent dual "not very effective" / "super effective"
|
||||
# matchups from leading to damage being ultimately divided by 2 and then multiplied by 2, which can lead to
|
||||
# damage being reduced by 1 which leads to a "not very effective" message appearing due to my changes
|
||||
# to the way effectiveness messages are generated.
|
||||
world.type_chart = sorted(chart, key=lambda matchup: -matchup[2])
|
||||
|
||||
for player in copy_chart_worlds:
|
||||
multiworld.worlds[player].type_chart = copy_chart_worlds[player].type_chart
|
||||
|
||||
def generate_early(self):
|
||||
def encode_name(name, t):
|
||||
try:
|
||||
@@ -126,33 +228,33 @@ class PokemonRedBlueWorld(World):
|
||||
return encode_text(name, length=8, whitespace="@", safety=True)
|
||||
except KeyError as e:
|
||||
raise KeyError(f"Invalid character(s) in {t} name for player {self.multiworld.player_name[self.player]}") from e
|
||||
if self.multiworld.trainer_name[self.player] == "choose_in_game":
|
||||
if self.options.trainer_name == "choose_in_game":
|
||||
self.trainer_name = "choose_in_game"
|
||||
else:
|
||||
self.trainer_name = encode_name(self.multiworld.trainer_name[self.player].value, "Player")
|
||||
if self.multiworld.rival_name[self.player] == "choose_in_game":
|
||||
self.trainer_name = encode_name(self.options.trainer_name.value, "Player")
|
||||
if self.options.rival_name == "choose_in_game":
|
||||
self.rival_name = "choose_in_game"
|
||||
else:
|
||||
self.rival_name = encode_name(self.multiworld.rival_name[self.player].value, "Rival")
|
||||
self.rival_name = encode_name(self.options.rival_name.value, "Rival")
|
||||
|
||||
if not self.multiworld.badgesanity[self.player]:
|
||||
self.multiworld.non_local_items[self.player].value -= self.item_name_groups["Badges"]
|
||||
if not self.options.badgesanity:
|
||||
self.options.non_local_items.value -= self.item_name_groups["Badges"]
|
||||
|
||||
if self.multiworld.key_items_only[self.player]:
|
||||
self.multiworld.trainersanity[self.player] = self.multiworld.trainersanity[self.player].from_text("off")
|
||||
self.multiworld.dexsanity[self.player].value = 0
|
||||
self.multiworld.randomize_hidden_items[self.player] = \
|
||||
self.multiworld.randomize_hidden_items[self.player].from_text("off")
|
||||
if self.options.key_items_only:
|
||||
self.options.trainersanity.value = 0
|
||||
self.options.dexsanity.value = 0
|
||||
self.options.randomize_hidden_items = \
|
||||
self.options.randomize_hidden_items.from_text("off")
|
||||
|
||||
if self.multiworld.badges_needed_for_hm_moves[self.player].value >= 2:
|
||||
if self.options.badges_needed_for_hm_moves.value >= 2:
|
||||
badges_to_add = ["Marsh Badge", "Volcano Badge", "Earth Badge"]
|
||||
if self.multiworld.badges_needed_for_hm_moves[self.player].value == 3:
|
||||
if self.options.badges_needed_for_hm_moves.value == 3:
|
||||
badges = ["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Marsh Badge",
|
||||
"Soul Badge", "Volcano Badge", "Earth Badge"]
|
||||
self.multiworld.random.shuffle(badges)
|
||||
self.random.shuffle(badges)
|
||||
badges_to_add += [badges.pop(), badges.pop()]
|
||||
hm_moves = ["Cut", "Fly", "Surf", "Strength", "Flash"]
|
||||
self.multiworld.random.shuffle(hm_moves)
|
||||
self.random.shuffle(hm_moves)
|
||||
self.extra_badges = {}
|
||||
for badge in badges_to_add:
|
||||
self.extra_badges[hm_moves.pop()] = badge
|
||||
@@ -160,79 +262,17 @@ class PokemonRedBlueWorld(World):
|
||||
process_move_data(self)
|
||||
process_pokemon_data(self)
|
||||
|
||||
if self.multiworld.randomize_type_chart[self.player] == "vanilla":
|
||||
chart = deepcopy(poke_data.type_chart)
|
||||
elif self.multiworld.randomize_type_chart[self.player] == "randomize":
|
||||
types = poke_data.type_names.values()
|
||||
matchups = []
|
||||
for type1 in types:
|
||||
for type2 in types:
|
||||
matchups.append([type1, type2])
|
||||
self.multiworld.random.shuffle(matchups)
|
||||
immunities = self.multiworld.immunity_matchups[self.player].value
|
||||
super_effectives = self.multiworld.super_effective_matchups[self.player].value
|
||||
not_very_effectives = self.multiworld.not_very_effective_matchups[self.player].value
|
||||
normals = self.multiworld.normal_matchups[self.player].value
|
||||
while super_effectives + not_very_effectives + normals < 225 - immunities:
|
||||
if super_effectives == not_very_effectives == normals == 0:
|
||||
super_effectives = 225
|
||||
not_very_effectives = 225
|
||||
normals = 225
|
||||
else:
|
||||
super_effectives += self.multiworld.super_effective_matchups[self.player].value
|
||||
not_very_effectives += self.multiworld.not_very_effective_matchups[self.player].value
|
||||
normals += self.multiworld.normal_matchups[self.player].value
|
||||
if super_effectives + not_very_effectives + normals > 225 - immunities:
|
||||
total = super_effectives + not_very_effectives + normals
|
||||
excess = total - (225 - immunities)
|
||||
subtract_amounts = (
|
||||
int((excess / (super_effectives + not_very_effectives + normals)) * super_effectives),
|
||||
int((excess / (super_effectives + not_very_effectives + normals)) * not_very_effectives),
|
||||
int((excess / (super_effectives + not_very_effectives + normals)) * normals))
|
||||
super_effectives -= subtract_amounts[0]
|
||||
not_very_effectives -= subtract_amounts[1]
|
||||
normals -= subtract_amounts[2]
|
||||
while super_effectives + not_very_effectives + normals > 225 - immunities:
|
||||
r = self.multiworld.random.randint(0, 2)
|
||||
if r == 0 and super_effectives:
|
||||
super_effectives -= 1
|
||||
elif r == 1 and not_very_effectives:
|
||||
not_very_effectives -= 1
|
||||
elif normals:
|
||||
normals -= 1
|
||||
chart = []
|
||||
for matchup_list, matchup_value in zip([immunities, normals, super_effectives, not_very_effectives],
|
||||
[0, 10, 20, 5]):
|
||||
for _ in range(matchup_list):
|
||||
matchup = matchups.pop()
|
||||
matchup.append(matchup_value)
|
||||
chart.append(matchup)
|
||||
elif self.multiworld.randomize_type_chart[self.player] == "chaos":
|
||||
types = poke_data.type_names.values()
|
||||
matchups = []
|
||||
for type1 in types:
|
||||
for type2 in types:
|
||||
matchups.append([type1, type2])
|
||||
chart = []
|
||||
values = list(range(21))
|
||||
self.multiworld.random.shuffle(matchups)
|
||||
self.multiworld.random.shuffle(values)
|
||||
for matchup in matchups:
|
||||
value = values.pop(0)
|
||||
values.append(value)
|
||||
matchup.append(value)
|
||||
chart.append(matchup)
|
||||
# sort so that super-effective matchups occur first, to prevent dual "not very effective" / "super effective"
|
||||
# matchups from leading to damage being ultimately divided by 2 and then multiplied by 2, which can lead to
|
||||
# damage being reduced by 1 which leads to a "not very effective" message appearing due to my changes
|
||||
# to the way effectiveness messages are generated.
|
||||
self.type_chart = sorted(chart, key=lambda matchup: -matchup[2])
|
||||
|
||||
self.dexsanity_table = [
|
||||
*(True for _ in range(round(self.multiworld.dexsanity[self.player].value * 1.51))),
|
||||
*(False for _ in range(151 - round(self.multiworld.dexsanity[self.player].value * 1.51)))
|
||||
*(True for _ in range(round(self.options.dexsanity.value))),
|
||||
*(False for _ in range(151 - round(self.options.dexsanity.value)))
|
||||
]
|
||||
self.multiworld.random.shuffle(self.dexsanity_table)
|
||||
self.random.shuffle(self.dexsanity_table)
|
||||
|
||||
self.trainersanity_table = [
|
||||
*(True for _ in range(self.options.trainersanity.value)),
|
||||
*(False for _ in range(317 - self.options.trainersanity.value))
|
||||
]
|
||||
self.random.shuffle(self.trainersanity_table)
|
||||
|
||||
def create_items(self):
|
||||
self.multiworld.itempool += self.item_pool
|
||||
@@ -275,9 +315,9 @@ class PokemonRedBlueWorld(World):
|
||||
filleritempool += [item for item in unplaced_items if (not item.advancement) and (not item.useful)]
|
||||
|
||||
def fill_hook(self, progitempool, usefulitempool, filleritempool, fill_locations):
|
||||
if not self.multiworld.badgesanity[self.player]:
|
||||
if not self.options.badgesanity:
|
||||
# Door Shuffle options besides Simple place badges during door shuffling
|
||||
if self.multiworld.door_shuffle[self.player] in ("off", "simple"):
|
||||
if self.options.door_shuffle in ("off", "simple"):
|
||||
badges = [item for item in progitempool if "Badge" in item.name and item.player == self.player]
|
||||
for badge in badges:
|
||||
self.multiworld.itempool.remove(badge)
|
||||
@@ -297,8 +337,8 @@ class PokemonRedBlueWorld(World):
|
||||
for mon in poke_data.pokemon_data.keys():
|
||||
state.collect(self.create_item(mon), True)
|
||||
state.sweep_for_advancements()
|
||||
self.multiworld.random.shuffle(badges)
|
||||
self.multiworld.random.shuffle(badgelocs)
|
||||
self.random.shuffle(badges)
|
||||
self.random.shuffle(badgelocs)
|
||||
badgelocs_copy = badgelocs.copy()
|
||||
# allow_partial so that unplaced badges aren't lost, for debugging purposes
|
||||
fill_restrictive(self.multiworld, state, badgelocs_copy, badges, True, True, allow_partial=True)
|
||||
@@ -318,7 +358,7 @@ class PokemonRedBlueWorld(World):
|
||||
raise FillError(f"Failed to place badges for player {self.player}")
|
||||
verify_hm_moves(self.multiworld, self, self.player)
|
||||
|
||||
if self.multiworld.key_items_only[self.player]:
|
||||
if self.options.key_items_only:
|
||||
return
|
||||
|
||||
tms = [item for item in usefulitempool + filleritempool if item.name.startswith("TM") and (item.player ==
|
||||
@@ -340,7 +380,7 @@ class PokemonRedBlueWorld(World):
|
||||
int((int(tm.name[2:4]) - 1) / 8)] & 1 << ((int(tm.name[2:4]) - 1) % 8)]
|
||||
if not learnable_tms:
|
||||
learnable_tms = tms
|
||||
tm = self.multiworld.random.choice(learnable_tms)
|
||||
tm = self.random.choice(learnable_tms)
|
||||
|
||||
loc.place_locked_item(tm)
|
||||
fill_locations.remove(loc)
|
||||
@@ -370,9 +410,9 @@ class PokemonRedBlueWorld(World):
|
||||
if not all_state.can_reach(location, player=self.player):
|
||||
evolutions_region.locations.remove(location)
|
||||
|
||||
if self.multiworld.old_man[self.player] == "early_parcel":
|
||||
if self.options.old_man == "early_parcel":
|
||||
self.multiworld.local_early_items[self.player]["Oak's Parcel"] = 1
|
||||
if self.multiworld.dexsanity[self.player]:
|
||||
if self.options.dexsanity:
|
||||
for i, mon in enumerate(poke_data.pokemon_data):
|
||||
if self.dexsanity_table[i]:
|
||||
location = self.multiworld.get_location(f"Pokedex - {mon}", self.player)
|
||||
@@ -384,13 +424,13 @@ class PokemonRedBlueWorld(World):
|
||||
locs = {self.multiworld.get_location("Fossil - Choice A", self.player),
|
||||
self.multiworld.get_location("Fossil - Choice B", self.player)}
|
||||
|
||||
if not self.multiworld.key_items_only[self.player]:
|
||||
if not self.options.key_items_only:
|
||||
rule = None
|
||||
if self.multiworld.fossil_check_item_types[self.player] == "key_items":
|
||||
if self.options.fossil_check_item_types == "key_items":
|
||||
rule = lambda i: i.advancement
|
||||
elif self.multiworld.fossil_check_item_types[self.player] == "unique_items":
|
||||
elif self.options.fossil_check_item_types == "unique_items":
|
||||
rule = lambda i: i.name in item_groups["Unique"]
|
||||
elif self.multiworld.fossil_check_item_types[self.player] == "no_key_items":
|
||||
elif self.options.fossil_check_item_types == "no_key_items":
|
||||
rule = lambda i: not i.advancement
|
||||
if rule:
|
||||
for loc in locs:
|
||||
@@ -406,16 +446,16 @@ class PokemonRedBlueWorld(World):
|
||||
if loc.item is None:
|
||||
locs.add(loc)
|
||||
|
||||
if not self.multiworld.key_items_only[self.player]:
|
||||
if not self.options.key_items_only:
|
||||
loc = self.multiworld.get_location("Player's House 2F - Player's PC", self.player)
|
||||
if loc.item is None:
|
||||
locs.add(loc)
|
||||
|
||||
for loc in sorted(locs):
|
||||
if loc.name in self.multiworld.priority_locations[self.player].value:
|
||||
if loc.name in self.options.priority_locations.value:
|
||||
add_item_rule(loc, lambda i: i.advancement)
|
||||
add_item_rule(loc, lambda i: i.player == self.player)
|
||||
if self.multiworld.old_man[self.player] == "early_parcel" and loc.name != "Player's House 2F - Player's PC":
|
||||
if self.options.old_man == "early_parcel" and loc.name != "Player's House 2F - Player's PC":
|
||||
add_item_rule(loc, lambda i: i.name != "Oak's Parcel")
|
||||
|
||||
self.local_locs = locs
|
||||
@@ -440,10 +480,10 @@ class PokemonRedBlueWorld(World):
|
||||
else:
|
||||
region_mons.add(location.item.name)
|
||||
|
||||
self.multiworld.elite_four_pokedex_condition[self.player].total = \
|
||||
int((len(reachable_mons) / 100) * self.multiworld.elite_four_pokedex_condition[self.player].value)
|
||||
self.options.elite_four_pokedex_condition.total = \
|
||||
int((len(reachable_mons) / 100) * self.options.elite_four_pokedex_condition.value)
|
||||
|
||||
if self.multiworld.accessibility[self.player] == "full":
|
||||
if self.options.accessibility == "full":
|
||||
balls = [self.create_item(ball) for ball in ["Poke Ball", "Great Ball", "Ultra Ball"]]
|
||||
traps = [self.create_item(trap) for trap in item_groups["Traps"]]
|
||||
locations = [location for location in self.multiworld.get_locations(self.player) if "Pokedex - " in
|
||||
@@ -469,7 +509,7 @@ class PokemonRedBlueWorld(World):
|
||||
else:
|
||||
break
|
||||
else:
|
||||
self.multiworld.random.shuffle(traps)
|
||||
self.random.shuffle(traps)
|
||||
for trap in traps:
|
||||
try:
|
||||
self.multiworld.itempool.remove(trap)
|
||||
@@ -497,22 +537,22 @@ class PokemonRedBlueWorld(World):
|
||||
found_mons.add(key)
|
||||
|
||||
def create_regions(self):
|
||||
if (self.multiworld.old_man[self.player] == "vanilla" or
|
||||
self.multiworld.door_shuffle[self.player] in ("full", "insanity")):
|
||||
fly_map_codes = self.multiworld.random.sample(range(2, 11), 2)
|
||||
elif (self.multiworld.door_shuffle[self.player] == "simple" or
|
||||
self.multiworld.route_3_condition[self.player] == "boulder_badge" or
|
||||
(self.multiworld.route_3_condition[self.player] == "any_badge" and
|
||||
self.multiworld.badgesanity[self.player])):
|
||||
fly_map_codes = self.multiworld.random.sample(range(3, 11), 2)
|
||||
if (self.options.old_man == "vanilla" or
|
||||
self.options.door_shuffle in ("full", "insanity")):
|
||||
fly_map_codes = self.random.sample(range(2, 11), 2)
|
||||
elif (self.options.door_shuffle == "simple" or
|
||||
self.options.route_3_condition == "boulder_badge" or
|
||||
(self.options.route_3_condition == "any_badge" and
|
||||
self.options.badgesanity)):
|
||||
fly_map_codes = self.random.sample(range(3, 11), 2)
|
||||
|
||||
else:
|
||||
fly_map_codes = self.multiworld.random.sample([4, 6, 7, 8, 9, 10], 2)
|
||||
if self.multiworld.free_fly_location[self.player]:
|
||||
fly_map_codes = self.random.sample([4, 6, 7, 8, 9, 10], 2)
|
||||
if self.options.free_fly_location:
|
||||
fly_map_code = fly_map_codes[0]
|
||||
else:
|
||||
fly_map_code = 0
|
||||
if self.multiworld.town_map_fly_location[self.player]:
|
||||
if self.options.town_map_fly_location:
|
||||
town_map_fly_map_code = fly_map_codes[1]
|
||||
else:
|
||||
town_map_fly_map_code = 0
|
||||
@@ -528,7 +568,7 @@ class PokemonRedBlueWorld(World):
|
||||
self.multiworld.completion_condition[self.player] = lambda state, player=self.player: state.has("Become Champion", player=player)
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.multiworld, self.player)
|
||||
set_rules(self.multiworld, self, self.player)
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return PokemonRBItem(name, self.player)
|
||||
@@ -548,19 +588,19 @@ class PokemonRedBlueWorld(World):
|
||||
multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]]
|
||||
|
||||
def write_spoiler_header(self, spoiler_handle: TextIO):
|
||||
spoiler_handle.write(f"Cerulean Cave Total Key Items: {self.multiworld.cerulean_cave_key_items_condition[self.player].total}\n")
|
||||
spoiler_handle.write(f"Elite Four Total Key Items: {self.multiworld.elite_four_key_items_condition[self.player].total}\n")
|
||||
spoiler_handle.write(f"Elite Four Total Pokemon: {self.multiworld.elite_four_pokedex_condition[self.player].total}\n")
|
||||
if self.multiworld.free_fly_location[self.player]:
|
||||
spoiler_handle.write(f"Cerulean Cave Total Key Items: {self.options.cerulean_cave_key_items_condition.total}\n")
|
||||
spoiler_handle.write(f"Elite Four Total Key Items: {self.options.elite_four_key_items_condition.total}\n")
|
||||
spoiler_handle.write(f"Elite Four Total Pokemon: {self.options.elite_four_pokedex_condition.total}\n")
|
||||
if self.options.free_fly_location:
|
||||
spoiler_handle.write(f"Free Fly Location: {self.fly_map}\n")
|
||||
if self.multiworld.town_map_fly_location[self.player]:
|
||||
if self.options.town_map_fly_location:
|
||||
spoiler_handle.write(f"Town Map Fly Location: {self.town_map_fly_map}\n")
|
||||
if self.extra_badges:
|
||||
for hm_move, badge in self.extra_badges.items():
|
||||
spoiler_handle.write(hm_move + " enabled by: " + (" " * 20)[:20 - len(hm_move)] + badge + "\n")
|
||||
|
||||
def write_spoiler(self, spoiler_handle):
|
||||
if self.multiworld.randomize_type_chart[self.player].value:
|
||||
if self.options.randomize_type_chart:
|
||||
spoiler_handle.write(f"\n\nType matchups ({self.multiworld.player_name[self.player]}):\n\n")
|
||||
for matchup in self.type_chart:
|
||||
spoiler_handle.write(f"{matchup[0]} deals {matchup[2] * 10}% damage to {matchup[1]}\n")
|
||||
@@ -571,39 +611,39 @@ class PokemonRedBlueWorld(World):
|
||||
spoiler_handle.write(location.name + ": " + location.item.name + "\n")
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
combined_traps = (self.multiworld.poison_trap_weight[self.player].value
|
||||
+ self.multiworld.fire_trap_weight[self.player].value
|
||||
+ self.multiworld.paralyze_trap_weight[self.player].value
|
||||
+ self.multiworld.ice_trap_weight[self.player].value
|
||||
+ self.multiworld.sleep_trap_weight[self.player].value)
|
||||
combined_traps = (self.options.poison_trap_weight.value
|
||||
+ self.options.fire_trap_weight.value
|
||||
+ self.options.paralyze_trap_weight.value
|
||||
+ self.options.ice_trap_weight.value
|
||||
+ self.options.sleep_trap_weight.value)
|
||||
if (combined_traps > 0 and
|
||||
self.multiworld.random.randint(1, 100) <= self.multiworld.trap_percentage[self.player].value):
|
||||
self.random.randint(1, 100) <= self.options.trap_percentage.value):
|
||||
return self.select_trap()
|
||||
banned_items = item_groups["Unique"]
|
||||
if (((not self.multiworld.tea[self.player]) or "Saffron City" not in [self.fly_map, self.town_map_fly_map])
|
||||
and (not self.multiworld.door_shuffle[self.player])):
|
||||
if (((not self.options.tea) or "Saffron City" not in [self.fly_map, self.town_map_fly_map])
|
||||
and (not self.options.door_shuffle)):
|
||||
# under these conditions, you should never be able to reach the Copycat or Pokémon Tower without being
|
||||
# able to reach the Celadon Department Store, so Poké Dolls would not allow early access to anything
|
||||
banned_items.append("Poke Doll")
|
||||
if not self.multiworld.tea[self.player]:
|
||||
if not self.options.tea:
|
||||
banned_items += item_groups["Vending Machine Drinks"]
|
||||
return self.multiworld.random.choice([item for item in item_table if item_table[item].id and item_table[
|
||||
return self.random.choice([item for item in item_table if item_table[item].id and item_table[
|
||||
item].classification == ItemClassification.filler and item not in banned_items])
|
||||
|
||||
def select_trap(self):
|
||||
if self.traps is None:
|
||||
self.traps = []
|
||||
self.traps += ["Poison Trap"] * self.multiworld.poison_trap_weight[self.player].value
|
||||
self.traps += ["Fire Trap"] * self.multiworld.fire_trap_weight[self.player].value
|
||||
self.traps += ["Paralyze Trap"] * self.multiworld.paralyze_trap_weight[self.player].value
|
||||
self.traps += ["Ice Trap"] * self.multiworld.ice_trap_weight[self.player].value
|
||||
self.traps += ["Sleep Trap"] * self.multiworld.sleep_trap_weight[self.player].value
|
||||
return self.multiworld.random.choice(self.traps)
|
||||
self.traps += ["Poison Trap"] * self.options.poison_trap_weight.value
|
||||
self.traps += ["Fire Trap"] * self.options.fire_trap_weight.value
|
||||
self.traps += ["Paralyze Trap"] * self.options.paralyze_trap_weight.value
|
||||
self.traps += ["Ice Trap"] * self.options.ice_trap_weight.value
|
||||
self.traps += ["Sleep Trap"] * self.options.sleep_trap_weight.value
|
||||
return self.random.choice(self.traps)
|
||||
|
||||
def extend_hint_information(self, hint_data):
|
||||
if self.multiworld.dexsanity[self.player] or self.multiworld.door_shuffle[self.player]:
|
||||
if self.options.dexsanity or self.options.door_shuffle:
|
||||
hint_data[self.player] = {}
|
||||
if self.multiworld.dexsanity[self.player]:
|
||||
if self.options.dexsanity:
|
||||
mon_locations = {mon: set() for mon in poke_data.pokemon_data.keys()}
|
||||
for loc in location_data:
|
||||
if loc.type in ["Wild Encounter", "Static Pokemon", "Legendary Pokemon", "Missable Pokemon"]:
|
||||
@@ -616,57 +656,59 @@ class PokemonRedBlueWorld(World):
|
||||
hint_data[self.player][self.multiworld.get_location(f"Pokedex - {mon}", self.player).address] =\
|
||||
", ".join(mon_locations[mon])
|
||||
|
||||
if self.multiworld.door_shuffle[self.player]:
|
||||
if self.options.door_shuffle:
|
||||
for location in self.multiworld.get_locations(self.player):
|
||||
if location.parent_region.entrance_hint and location.address:
|
||||
hint_data[self.player][location.address] = location.parent_region.entrance_hint
|
||||
|
||||
def fill_slot_data(self) -> dict:
|
||||
return {
|
||||
"second_fossil_check_condition": self.multiworld.second_fossil_check_condition[self.player].value,
|
||||
"require_item_finder": self.multiworld.require_item_finder[self.player].value,
|
||||
"randomize_hidden_items": self.multiworld.randomize_hidden_items[self.player].value,
|
||||
"badges_needed_for_hm_moves": self.multiworld.badges_needed_for_hm_moves[self.player].value,
|
||||
"oaks_aide_rt_2": self.multiworld.oaks_aide_rt_2[self.player].value,
|
||||
"oaks_aide_rt_11": self.multiworld.oaks_aide_rt_11[self.player].value,
|
||||
"oaks_aide_rt_15": self.multiworld.oaks_aide_rt_15[self.player].value,
|
||||
"extra_key_items": self.multiworld.extra_key_items[self.player].value,
|
||||
"extra_strength_boulders": self.multiworld.extra_strength_boulders[self.player].value,
|
||||
"tea": self.multiworld.tea[self.player].value,
|
||||
"old_man": self.multiworld.old_man[self.player].value,
|
||||
"elite_four_badges_condition": self.multiworld.elite_four_badges_condition[self.player].value,
|
||||
"elite_four_key_items_condition": self.multiworld.elite_four_key_items_condition[self.player].total,
|
||||
"elite_four_pokedex_condition": self.multiworld.elite_four_pokedex_condition[self.player].total,
|
||||
"victory_road_condition": self.multiworld.victory_road_condition[self.player].value,
|
||||
"route_22_gate_condition": self.multiworld.route_22_gate_condition[self.player].value,
|
||||
"route_3_condition": self.multiworld.route_3_condition[self.player].value,
|
||||
"robbed_house_officer": self.multiworld.robbed_house_officer[self.player].value,
|
||||
"viridian_gym_condition": self.multiworld.viridian_gym_condition[self.player].value,
|
||||
"cerulean_cave_badges_condition": self.multiworld.cerulean_cave_badges_condition[self.player].value,
|
||||
"cerulean_cave_key_items_condition": self.multiworld.cerulean_cave_key_items_condition[self.player].total,
|
||||
ret = {
|
||||
"second_fossil_check_condition": self.options.second_fossil_check_condition.value,
|
||||
"require_item_finder": self.options.require_item_finder.value,
|
||||
"randomize_hidden_items": self.options.randomize_hidden_items.value,
|
||||
"badges_needed_for_hm_moves": self.options.badges_needed_for_hm_moves.value,
|
||||
"oaks_aide_rt_2": self.options.oaks_aide_rt_2.value,
|
||||
"oaks_aide_rt_11": self.options.oaks_aide_rt_11.value,
|
||||
"oaks_aide_rt_15": self.options.oaks_aide_rt_15.value,
|
||||
"extra_key_items": self.options.extra_key_items.value,
|
||||
"extra_strength_boulders": self.options.extra_strength_boulders.value,
|
||||
"tea": self.options.tea.value,
|
||||
"old_man": self.options.old_man.value,
|
||||
"elite_four_badges_condition": self.options.elite_four_badges_condition.value,
|
||||
"elite_four_key_items_condition": self.options.elite_four_key_items_condition.total,
|
||||
"elite_four_pokedex_condition": self.options.elite_four_pokedex_condition.total,
|
||||
"victory_road_condition": self.options.victory_road_condition.value,
|
||||
"route_22_gate_condition": self.options.route_22_gate_condition.value,
|
||||
"route_3_condition": self.options.route_3_condition.value,
|
||||
"robbed_house_officer": self.options.robbed_house_officer.value,
|
||||
"viridian_gym_condition": self.options.viridian_gym_condition.value,
|
||||
"cerulean_cave_badges_condition": self.options.cerulean_cave_badges_condition.value,
|
||||
"cerulean_cave_key_items_condition": self.options.cerulean_cave_key_items_condition.total,
|
||||
"free_fly_map": self.fly_map_code,
|
||||
"town_map_fly_map": self.town_map_fly_map_code,
|
||||
"extra_badges": self.extra_badges,
|
||||
"type_chart": self.type_chart,
|
||||
"randomize_pokedex": self.multiworld.randomize_pokedex[self.player].value,
|
||||
"trainersanity": self.multiworld.trainersanity[self.player].value,
|
||||
"death_link": self.multiworld.death_link[self.player].value,
|
||||
"prizesanity": self.multiworld.prizesanity[self.player].value,
|
||||
"key_items_only": self.multiworld.key_items_only[self.player].value,
|
||||
"poke_doll_skip": self.multiworld.poke_doll_skip[self.player].value,
|
||||
"bicycle_gate_skips": self.multiworld.bicycle_gate_skips[self.player].value,
|
||||
"stonesanity": self.multiworld.stonesanity[self.player].value,
|
||||
"door_shuffle": self.multiworld.door_shuffle[self.player].value,
|
||||
"warp_tile_shuffle": self.multiworld.warp_tile_shuffle[self.player].value,
|
||||
"dark_rock_tunnel_logic": self.multiworld.dark_rock_tunnel_logic[self.player].value,
|
||||
"split_card_key": self.multiworld.split_card_key[self.player].value,
|
||||
"all_elevators_locked": self.multiworld.all_elevators_locked[self.player].value,
|
||||
"require_pokedex": self.multiworld.require_pokedex[self.player].value,
|
||||
"area_1_to_1_mapping": self.multiworld.area_1_to_1_mapping[self.player].value,
|
||||
"blind_trainers": self.multiworld.blind_trainers[self.player].value,
|
||||
"randomize_pokedex": self.options.randomize_pokedex.value,
|
||||
"trainersanity": self.options.trainersanity.value,
|
||||
"death_link": self.options.death_link.value,
|
||||
"prizesanity": self.options.prizesanity.value,
|
||||
"key_items_only": self.options.key_items_only.value,
|
||||
"poke_doll_skip": self.options.poke_doll_skip.value,
|
||||
"bicycle_gate_skips": self.options.bicycle_gate_skips.value,
|
||||
"stonesanity": self.options.stonesanity.value,
|
||||
"door_shuffle": self.options.door_shuffle.value,
|
||||
"warp_tile_shuffle": self.options.warp_tile_shuffle.value,
|
||||
"dark_rock_tunnel_logic": self.options.dark_rock_tunnel_logic.value,
|
||||
"split_card_key": self.options.split_card_key.value,
|
||||
"all_elevators_locked": self.options.all_elevators_locked.value,
|
||||
"require_pokedex": self.options.require_pokedex.value,
|
||||
"area_1_to_1_mapping": self.options.area_1_to_1_mapping.value,
|
||||
"blind_trainers": self.options.blind_trainers.value,
|
||||
|
||||
}
|
||||
if self.options.type_chart_seed == "random" or self.options.type_chart_seed.value.isdigit():
|
||||
ret["type_chart"] = self.type_chart
|
||||
|
||||
return ret
|
||||
|
||||
class PokemonRBItem(Item):
|
||||
game = "Pokemon Red and Blue"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -60,11 +60,12 @@ and Safari Zone. Adds 4 extra item locations to Rock Tunnel B1F
|
||||
* Split Card Key: Splits the Card Key into 10 different Card Keys, one for each floor of Silph Co that has locked doors.
|
||||
Adds 9 location checks to friendly NPCs in Silph Co. You can also choose Progressive Card Keys to always obtain the
|
||||
keys in order from Card Key 2F to Card Key 11F.
|
||||
* Trainersanity: Adds location checks to 317 trainers. Does not include scripted trainers, most of which disappear
|
||||
* Trainersanity: Adds location checks to trainers. You may choose between 0 and 317 trainersanity checks. Trainers
|
||||
will be randomly selected to be given checks. Does not include scripted trainers, most of which disappear
|
||||
after battling them, but also includes Gym Leaders. You must talk to the trainer after defeating them to receive
|
||||
your prize. Adds 317 random filler items to the item pool
|
||||
* Dexsanity: Location checks occur when registering Pokémon as owned in the Pokédex. You can choose a percentage
|
||||
of Pokémon to have checks added to, chosen randomly. You can identify which Pokémon have location checks by an empty
|
||||
your prize. Adds random filler items to the item pool.
|
||||
* Dexsanity: Location checks occur when registering Pokémon as owned in the Pokédex. You can choose between 0 and 151
|
||||
Pokémon to have checks added to, chosen randomly. You can identify which Pokémon have location checks by an empty
|
||||
Poké Ball icon shown in battle or in the Pokédex menu.
|
||||
|
||||
## Which items can be in another player's world?
|
||||
|
||||
@@ -8,7 +8,7 @@ def get_encounter_slots(self):
|
||||
|
||||
for location in encounter_slots:
|
||||
if isinstance(location.original_item, list):
|
||||
location.original_item = location.original_item[not self.multiworld.game_version[self.player].value]
|
||||
location.original_item = location.original_item[not self.options.game_version.value]
|
||||
return encounter_slots
|
||||
|
||||
|
||||
@@ -39,16 +39,16 @@ def randomize_pokemon(self, mon, mons_list, randomize_type, random):
|
||||
return mon
|
||||
|
||||
|
||||
def process_trainer_data(self):
|
||||
def process_trainer_data(world):
|
||||
mons_list = [pokemon for pokemon in poke_data.pokemon_data.keys() if pokemon not in poke_data.legendary_pokemon
|
||||
or self.multiworld.trainer_legendaries[self.player].value]
|
||||
or world.options.trainer_legendaries.value]
|
||||
unevolved_mons = [pokemon for pokemon in poke_data.first_stage_pokemon if pokemon not in poke_data.legendary_pokemon
|
||||
or self.multiworld.randomize_legendary_pokemon[self.player].value == 3]
|
||||
or world.options.randomize_legendary_pokemon.value == 3]
|
||||
evolved_mons = [mon for mon in mons_list if mon not in unevolved_mons]
|
||||
rival_map = {
|
||||
"Charmander": self.multiworld.get_location("Oak's Lab - Starter 1", self.player).item.name[9:], # strip the
|
||||
"Squirtle": self.multiworld.get_location("Oak's Lab - Starter 2", self.player).item.name[9:], # 'Missable'
|
||||
"Bulbasaur": self.multiworld.get_location("Oak's Lab - Starter 3", self.player).item.name[9:], # from the name
|
||||
"Charmander": world.multiworld.get_location("Oak's Lab - Starter 1", world.player).item.name[9:], # strip the
|
||||
"Squirtle": world.multiworld.get_location("Oak's Lab - Starter 2", world.player).item.name[9:], # 'Missable'
|
||||
"Bulbasaur": world.multiworld.get_location("Oak's Lab - Starter 3", world.player).item.name[9:], # from the name
|
||||
}
|
||||
|
||||
def add_evolutions():
|
||||
@@ -60,7 +60,7 @@ def process_trainer_data(self):
|
||||
rival_map[poke_data.evolves_to[a]] = b
|
||||
add_evolutions()
|
||||
add_evolutions()
|
||||
parties_objs = [location for location in self.multiworld.get_locations(self.player)
|
||||
parties_objs = [location for location in world.multiworld.get_locations(world.player)
|
||||
if location.type == "Trainer Parties"]
|
||||
# Process Rival parties in order "Route 22 " is not a typo
|
||||
parties_objs.sort(key=lambda i: 0 if "Oak's Lab" in i.name else 1 if "Route 22 " in i.name else 2 if "Cerulean City"
|
||||
@@ -75,25 +75,25 @@ def process_trainer_data(self):
|
||||
for i, mon in enumerate(rival_party):
|
||||
if mon in ("Bulbasaur", "Ivysaur", "Venusaur", "Charmander", "Charmeleon", "Charizard",
|
||||
"Squirtle", "Wartortle", "Blastoise"):
|
||||
if self.multiworld.randomize_starter_pokemon[self.player]:
|
||||
if world.options.randomize_starter_pokemon:
|
||||
rival_party[i] = rival_map[mon]
|
||||
elif self.multiworld.randomize_trainer_parties[self.player]:
|
||||
elif world.options.randomize_trainer_parties:
|
||||
if mon in rival_map:
|
||||
rival_party[i] = rival_map[mon]
|
||||
else:
|
||||
new_mon = randomize_pokemon(self, mon,
|
||||
new_mon = randomize_pokemon(world, mon,
|
||||
unevolved_mons if mon in unevolved_mons else evolved_mons,
|
||||
self.multiworld.randomize_trainer_parties[self.player].value,
|
||||
self.multiworld.random)
|
||||
world.options.randomize_trainer_parties.value,
|
||||
world.random)
|
||||
rival_map[mon] = new_mon
|
||||
rival_party[i] = new_mon
|
||||
add_evolutions()
|
||||
else:
|
||||
if self.multiworld.randomize_trainer_parties[self.player]:
|
||||
if world.options.randomize_trainer_parties:
|
||||
for i, mon in enumerate(party["party"]):
|
||||
party["party"][i] = randomize_pokemon(self, mon, mons_list,
|
||||
self.multiworld.randomize_trainer_parties[self.player].value,
|
||||
self.multiworld.random)
|
||||
party["party"][i] = randomize_pokemon(world, mon, mons_list,
|
||||
world.options.randomize_trainer_parties.value,
|
||||
world.random)
|
||||
|
||||
|
||||
def process_pokemon_locations(self):
|
||||
@@ -106,21 +106,21 @@ def process_pokemon_locations(self):
|
||||
placed_mons = {pokemon: 0 for pokemon in poke_data.pokemon_data.keys()}
|
||||
|
||||
mons_list = [pokemon for pokemon in poke_data.first_stage_pokemon if pokemon not in poke_data.legendary_pokemon
|
||||
or self.multiworld.randomize_legendary_pokemon[self.player].value == 3]
|
||||
if self.multiworld.randomize_legendary_pokemon[self.player] == "vanilla":
|
||||
or self.options.randomize_legendary_pokemon.value == 3]
|
||||
if self.options.randomize_legendary_pokemon == "vanilla":
|
||||
for slot in legendary_slots:
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
location.place_locked_item(self.create_item("Static " + slot.original_item))
|
||||
elif self.multiworld.randomize_legendary_pokemon[self.player] == "shuffle":
|
||||
self.multiworld.random.shuffle(legendary_mons)
|
||||
elif self.options.randomize_legendary_pokemon == "shuffle":
|
||||
self.random.shuffle(legendary_mons)
|
||||
for slot in legendary_slots:
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
mon = legendary_mons.pop()
|
||||
location.place_locked_item(self.create_item("Static " + mon))
|
||||
placed_mons[mon] += 1
|
||||
elif self.multiworld.randomize_legendary_pokemon[self.player] == "static":
|
||||
elif self.options.randomize_legendary_pokemon == "static":
|
||||
static_slots = static_slots + legendary_slots
|
||||
self.multiworld.random.shuffle(static_slots)
|
||||
self.random.shuffle(static_slots)
|
||||
static_slots.sort(key=lambda s: s.name != "Pokemon Tower 6F - Restless Soul")
|
||||
while legendary_slots:
|
||||
swap_slot = legendary_slots.pop()
|
||||
@@ -131,12 +131,12 @@ def process_pokemon_locations(self):
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
location.place_locked_item(self.create_item(slot_type + " " + swap_slot.original_item))
|
||||
swap_slot.original_item = slot.original_item
|
||||
elif self.multiworld.randomize_legendary_pokemon[self.player] == "any":
|
||||
elif self.options.randomize_legendary_pokemon == "any":
|
||||
static_slots = static_slots + legendary_slots
|
||||
|
||||
for slot in static_slots:
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
randomize_type = self.multiworld.randomize_static_pokemon[self.player].value
|
||||
randomize_type = self.options.randomize_static_pokemon.value
|
||||
slot_type = slot.type.split()[0]
|
||||
if slot_type == "Legendary":
|
||||
slot_type = "Static"
|
||||
@@ -145,7 +145,7 @@ def process_pokemon_locations(self):
|
||||
else:
|
||||
mon = self.create_item(slot_type + " " +
|
||||
randomize_pokemon(self, slot.original_item, mons_list, randomize_type,
|
||||
self.multiworld.random))
|
||||
self.random))
|
||||
location.place_locked_item(mon)
|
||||
if slot_type != "Missable":
|
||||
placed_mons[mon.name.replace("Static ", "")] += 1
|
||||
@@ -153,16 +153,16 @@ def process_pokemon_locations(self):
|
||||
chosen_mons = set()
|
||||
for slot in starter_slots:
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
randomize_type = self.multiworld.randomize_starter_pokemon[self.player].value
|
||||
randomize_type = self.options.randomize_starter_pokemon.value
|
||||
slot_type = "Missable"
|
||||
if not randomize_type:
|
||||
location.place_locked_item(self.create_item(slot_type + " " + slot.original_item))
|
||||
else:
|
||||
mon = self.create_item(slot_type + " " + randomize_pokemon(self, slot.original_item, mons_list,
|
||||
randomize_type, self.multiworld.random))
|
||||
randomize_type, self.random))
|
||||
while mon.name in chosen_mons:
|
||||
mon = self.create_item(slot_type + " " + randomize_pokemon(self, slot.original_item, mons_list,
|
||||
randomize_type, self.multiworld.random))
|
||||
randomize_type, self.random))
|
||||
chosen_mons.add(mon.name)
|
||||
location.place_locked_item(mon)
|
||||
|
||||
@@ -170,10 +170,10 @@ def process_pokemon_locations(self):
|
||||
encounter_slots = encounter_slots_master.copy()
|
||||
|
||||
zone_mapping = {}
|
||||
if self.multiworld.randomize_wild_pokemon[self.player]:
|
||||
if self.options.randomize_wild_pokemon:
|
||||
mons_list = [pokemon for pokemon in poke_data.pokemon_data.keys() if pokemon not in poke_data.legendary_pokemon
|
||||
or self.multiworld.randomize_legendary_pokemon[self.player].value == 3]
|
||||
self.multiworld.random.shuffle(encounter_slots)
|
||||
or self.options.randomize_legendary_pokemon.value == 3]
|
||||
self.random.shuffle(encounter_slots)
|
||||
locations = []
|
||||
for slot in encounter_slots:
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
@@ -181,11 +181,11 @@ def process_pokemon_locations(self):
|
||||
if zone not in zone_mapping:
|
||||
zone_mapping[zone] = {}
|
||||
original_mon = slot.original_item
|
||||
if self.multiworld.area_1_to_1_mapping[self.player] and original_mon in zone_mapping[zone]:
|
||||
if self.options.area_1_to_1_mapping and original_mon in zone_mapping[zone]:
|
||||
mon = zone_mapping[zone][original_mon]
|
||||
else:
|
||||
mon = randomize_pokemon(self, original_mon, mons_list,
|
||||
self.multiworld.randomize_wild_pokemon[self.player].value, self.multiworld.random)
|
||||
self.options.randomize_wild_pokemon.value, self.random)
|
||||
#
|
||||
while ("Pokemon Tower 6F" in slot.name and
|
||||
self.multiworld.get_location("Pokemon Tower 6F - Restless Soul", self.player).item.name
|
||||
@@ -194,7 +194,7 @@ def process_pokemon_locations(self):
|
||||
# the battle is treates as the Restless Soul battle and you cannot catch it. So, prevent any wild mons
|
||||
# from being the same species as the Restless Soul.
|
||||
# to account for the possibility that only one ground type Pokemon exists, match only stats for this fix
|
||||
mon = randomize_pokemon(self, original_mon, mons_list, 2, self.multiworld.random)
|
||||
mon = randomize_pokemon(self, original_mon, mons_list, 2, self.random)
|
||||
placed_mons[mon] += 1
|
||||
location.item = self.create_item(mon)
|
||||
location.locked = True
|
||||
@@ -204,28 +204,28 @@ def process_pokemon_locations(self):
|
||||
|
||||
mons_to_add = []
|
||||
remaining_pokemon = [pokemon for pokemon in poke_data.pokemon_data.keys() if placed_mons[pokemon] == 0 and
|
||||
(pokemon not in poke_data.legendary_pokemon or self.multiworld.randomize_legendary_pokemon[self.player].value == 3)]
|
||||
if self.multiworld.catch_em_all[self.player] == "first_stage":
|
||||
(pokemon not in poke_data.legendary_pokemon or self.options.randomize_legendary_pokemon.value == 3)]
|
||||
if self.options.catch_em_all == "first_stage":
|
||||
mons_to_add = [pokemon for pokemon in poke_data.first_stage_pokemon if placed_mons[pokemon] == 0 and
|
||||
(pokemon not in poke_data.legendary_pokemon or self.multiworld.randomize_legendary_pokemon[self.player].value == 3)]
|
||||
elif self.multiworld.catch_em_all[self.player] == "all_pokemon":
|
||||
(pokemon not in poke_data.legendary_pokemon or self.options.randomize_legendary_pokemon.value == 3)]
|
||||
elif self.options.catch_em_all == "all_pokemon":
|
||||
mons_to_add = remaining_pokemon.copy()
|
||||
logic_needed_mons = max(self.multiworld.oaks_aide_rt_2[self.player].value,
|
||||
self.multiworld.oaks_aide_rt_11[self.player].value,
|
||||
self.multiworld.oaks_aide_rt_15[self.player].value)
|
||||
if self.multiworld.accessibility[self.player] == "minimal":
|
||||
logic_needed_mons = max(self.options.oaks_aide_rt_2.value,
|
||||
self.options.oaks_aide_rt_11.value,
|
||||
self.options.oaks_aide_rt_15.value)
|
||||
if self.options.accessibility == "minimal":
|
||||
logic_needed_mons = 0
|
||||
|
||||
self.multiworld.random.shuffle(remaining_pokemon)
|
||||
self.random.shuffle(remaining_pokemon)
|
||||
while (len([pokemon for pokemon in placed_mons if placed_mons[pokemon] > 0])
|
||||
+ len(mons_to_add) < logic_needed_mons):
|
||||
mons_to_add.append(remaining_pokemon.pop())
|
||||
for mon in mons_to_add:
|
||||
stat_base = get_base_stat_total(mon)
|
||||
candidate_locations = encounter_slots_master.copy()
|
||||
if self.multiworld.randomize_wild_pokemon[self.player].current_key in ["match_base_stats", "match_types_and_base_stats"]:
|
||||
if self.options.randomize_wild_pokemon.current_key in ["match_base_stats", "match_types_and_base_stats"]:
|
||||
candidate_locations.sort(key=lambda slot: abs(get_base_stat_total(slot.original_item) - stat_base))
|
||||
if self.multiworld.randomize_wild_pokemon[self.player].current_key in ["match_types", "match_types_and_base_stats"]:
|
||||
if self.options.randomize_wild_pokemon.current_key in ["match_types", "match_types_and_base_stats"]:
|
||||
candidate_locations.sort(key=lambda slot: not any([poke_data.pokemon_data[slot.original_item]["type1"] in
|
||||
[self.local_poke_data[mon]["type1"], self.local_poke_data[mon]["type2"]],
|
||||
poke_data.pokemon_data[slot.original_item]["type2"] in
|
||||
@@ -233,12 +233,12 @@ def process_pokemon_locations(self):
|
||||
candidate_locations = [self.multiworld.get_location(location.name, self.player) for location in candidate_locations]
|
||||
for location in candidate_locations:
|
||||
zone = " - ".join(location.name.split(" - ")[:-1])
|
||||
if self.multiworld.catch_em_all[self.player] == "all_pokemon" and self.multiworld.area_1_to_1_mapping[self.player]:
|
||||
if self.options.catch_em_all == "all_pokemon" and self.options.area_1_to_1_mapping:
|
||||
if not [self.multiworld.get_location(l.name, self.player) for l in encounter_slots_master
|
||||
if (not l.name.startswith(zone)) and
|
||||
self.multiworld.get_location(l.name, self.player).item.name == location.item.name]:
|
||||
continue
|
||||
if self.multiworld.catch_em_all[self.player] == "first_stage" and self.multiworld.area_1_to_1_mapping[self.player]:
|
||||
if self.options.catch_em_all == "first_stage" and self.options.area_1_to_1_mapping:
|
||||
if not [self.multiworld.get_location(l.name, self.player) for l in encounter_slots_master
|
||||
if (not l.name.startswith(zone)) and
|
||||
self.multiworld.get_location(l.name, self.player).item.name == location.item.name and l.name
|
||||
@@ -246,10 +246,10 @@ def process_pokemon_locations(self):
|
||||
continue
|
||||
|
||||
if placed_mons[location.item.name] < 2 and (location.item.name in poke_data.first_stage_pokemon
|
||||
or self.multiworld.catch_em_all[self.player]):
|
||||
or self.options.catch_em_all):
|
||||
continue
|
||||
|
||||
if self.multiworld.area_1_to_1_mapping[self.player]:
|
||||
if self.options.area_1_to_1_mapping:
|
||||
place_locations = [place_location for place_location in candidate_locations if
|
||||
place_location.name.startswith(zone) and
|
||||
place_location.item.name == location.item.name]
|
||||
|
||||
@@ -194,6 +194,8 @@ item_table = {
|
||||
"Fuji Saved": ItemData(None, ItemClassification.progression, []),
|
||||
"Silph Co Liberated": ItemData(None, ItemClassification.progression, []),
|
||||
"Become Champion": ItemData(None, ItemClassification.progression, []),
|
||||
"Mt Moon Fossils": ItemData(None, ItemClassification.progression, []),
|
||||
"Cinnabar Lab": ItemData(None, ItemClassification.progression, []),
|
||||
|
||||
"Trainer Parties": ItemData(None, ItemClassification.filler, [])
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ def level_scaling(multiworld):
|
||||
while locations:
|
||||
sphere = set()
|
||||
for world in multiworld.get_game_worlds("Pokemon Red and Blue"):
|
||||
if (multiworld.level_scaling[world.player] != "by_spheres_and_distance"
|
||||
and (multiworld.level_scaling[world.player] != "auto" or multiworld.door_shuffle[world.player]
|
||||
in ("off", "simple"))):
|
||||
if (world.options.level_scaling != "by_spheres_and_distance"
|
||||
and (world.options.level_scaling != "auto"
|
||||
or world.options.door_shuffle in ("off", "simple"))):
|
||||
continue
|
||||
regions = {multiworld.get_region("Menu", world.player)}
|
||||
checked_regions = set()
|
||||
@@ -41,7 +41,8 @@ def level_scaling(multiworld):
|
||||
# reach them earlier. We treat them both as reachable right away for this purpose
|
||||
return True
|
||||
if (location.name == "Route 25 - Item" and state.can_reach("Route 25", "Region", location.player)
|
||||
and multiworld.blind_trainers[location.player].value < 100):
|
||||
and multiworld.worlds[location.player].options.blind_trainers.value < 100
|
||||
and "Route 25 - Jr. Trainer M" not in multiworld.regions.location_cache[location.player]):
|
||||
# Assume they will take their one chance to get the trainer to walk out of the way to reach
|
||||
# the item behind them
|
||||
return True
|
||||
@@ -95,9 +96,9 @@ def level_scaling(multiworld):
|
||||
if (location.item.game == "Pokemon Red and Blue" and (location.item.name.startswith("Missable ") or
|
||||
location.item.name.startswith("Static ")) and location.name !=
|
||||
"Pokemon Tower 6F - Restless Soul"):
|
||||
# Normally, missable Pokemon (starters, the dojo rewards) are not considered in logic static Pokemon
|
||||
# are not considered for moves or evolutions, as you could release them and potentially soft lock
|
||||
# the game. However, for level scaling purposes, we will treat them as not missable or static.
|
||||
# Normally, missable Pokemon (starters, the dojo rewards) are not considered in logic, and static
|
||||
# Pokemon are not considered for moves or evolutions, as you could release them and potentially soft
|
||||
# lock the game. However, for level scaling purposes, we will treat them as not missable or static.
|
||||
# We would not want someone playing a minimal accessibility Dexsanity game to get what would be
|
||||
# technically an "out of logic" Mansion Key from selecting Bulbasaur at the beginning of the game
|
||||
# and end up in the Mansion early and encountering level 67 Pokémon
|
||||
@@ -106,7 +107,7 @@ def level_scaling(multiworld):
|
||||
else:
|
||||
state.collect(location.item, True, location)
|
||||
for world in multiworld.get_game_worlds("Pokemon Red and Blue"):
|
||||
if multiworld.level_scaling[world.player] == "off":
|
||||
if world.options.level_scaling == "off":
|
||||
continue
|
||||
level_list_copy = level_list.copy()
|
||||
for sphere in spheres:
|
||||
@@ -136,4 +137,4 @@ def level_scaling(multiworld):
|
||||
else:
|
||||
sphere_objects[object].level = level_list_copy.pop(0)
|
||||
for world in multiworld.get_game_worlds("Pokemon Red and Blue"):
|
||||
world.finished_level_scaling.set()
|
||||
world.finished_level_scaling.set()
|
||||
@@ -5,46 +5,48 @@ from . import poke_data
|
||||
loc_id_start = 172000000
|
||||
|
||||
|
||||
def trainersanity(multiworld, player):
|
||||
return multiworld.trainersanity[player]
|
||||
|
||||
|
||||
def dexsanity(multiworld, player):
|
||||
include = multiworld.worlds[player].dexsanity_table.pop(0)
|
||||
multiworld.worlds[player].dexsanity_table.append(include)
|
||||
def trainersanity(world, player):
|
||||
include = world.trainersanity_table.pop(0)
|
||||
world.trainersanity_table.append(include)
|
||||
return include
|
||||
|
||||
|
||||
def hidden_items(multiworld, player):
|
||||
return multiworld.randomize_hidden_items[player]
|
||||
def dexsanity(world, player):
|
||||
include = world.dexsanity_table.pop(0)
|
||||
world.dexsanity_table.append(include)
|
||||
return include
|
||||
|
||||
|
||||
def hidden_moon_stones(multiworld, player):
|
||||
return multiworld.randomize_hidden_items[player] or multiworld.stonesanity[player]
|
||||
def hidden_items(world, player):
|
||||
return world.options.randomize_hidden_items
|
||||
|
||||
|
||||
def tea(multiworld, player):
|
||||
return multiworld.tea[player]
|
||||
def hidden_moon_stones(world, player):
|
||||
return world.options.randomize_hidden_items or world.options.stonesanity
|
||||
|
||||
|
||||
def extra_key_items(multiworld, player):
|
||||
return multiworld.extra_key_items[player]
|
||||
def tea(world, player):
|
||||
return world.options.tea
|
||||
|
||||
|
||||
def always_on(multiworld, player):
|
||||
def extra_key_items(world, player):
|
||||
return world.options.extra_key_items
|
||||
|
||||
|
||||
def always_on(world, player):
|
||||
return True
|
||||
|
||||
|
||||
def prizesanity(multiworld, player):
|
||||
return multiworld.prizesanity[player]
|
||||
def prizesanity(world, player):
|
||||
return world.options.prizesanity
|
||||
|
||||
|
||||
def split_card_key(multiworld, player):
|
||||
return multiworld.split_card_key[player].value > 0
|
||||
def split_card_key(world, player):
|
||||
return world.options.split_card_key.value > 0
|
||||
|
||||
|
||||
def not_stonesanity(multiworld, player):
|
||||
return not multiworld.stonesanity[player]
|
||||
def not_stonesanity(world, player):
|
||||
return not world.options.stonesanity
|
||||
|
||||
|
||||
class LocationData:
|
||||
@@ -395,7 +397,7 @@ location_data = [
|
||||
LocationData("Silph Co 5F", "Hidden Item Pot Plant", "Elixir", rom_addresses['Hidden_Item_Silph_Co_5F'], Hidden(18), inclusion=hidden_items),
|
||||
LocationData("Silph Co 9F-SW", "Hidden Item Nurse Bed", "Max Potion", rom_addresses['Hidden_Item_Silph_Co_9F'], Hidden(19), inclusion=hidden_items),
|
||||
LocationData("Saffron Copycat's House 2F", "Hidden Item Desk", "Nugget", rom_addresses['Hidden_Item_Copycats_House'], Hidden(20), inclusion=hidden_items),
|
||||
LocationData("Cerulean Cave 1F-NW", "Hidden Item Center Rocks", "Rare Candy", rom_addresses['Hidden_Item_Cerulean_Cave_1F'], Hidden(21), inclusion=hidden_items),
|
||||
LocationData("Cerulean Cave 1F-SW", "Hidden Item Center Rocks", "Rare Candy", rom_addresses['Hidden_Item_Cerulean_Cave_1F'], Hidden(21), inclusion=hidden_items),
|
||||
LocationData("Cerulean Cave B1F-E", "Hidden Item Northeast Rocks", "Ultra Ball", rom_addresses['Hidden_Item_Cerulean_Cave_B1F'], Hidden(22), inclusion=hidden_items),
|
||||
LocationData("Power Plant", "Hidden Item Central Dead End", "Max Elixir", rom_addresses['Hidden_Item_Power_Plant_1'], Hidden(23), inclusion=hidden_items),
|
||||
LocationData("Power Plant", "Hidden Item Before Zapdos", "PP Up", rom_addresses['Hidden_Item_Power_Plant_2'], Hidden(24), inclusion=hidden_items),
|
||||
@@ -786,6 +788,8 @@ location_data = [
|
||||
|
||||
LocationData("Celadon Game Corner", "", "Game Corner", event=True),
|
||||
LocationData("Cinnabar Island", "", "Cinnabar Island", event=True),
|
||||
LocationData("Cinnabar Lab", "", "Cinnabar Lab", event=True),
|
||||
LocationData("Mt Moon B2F", "Mt Moon Fossils", "Mt Moon Fossils", event=True),
|
||||
LocationData("Celadon Department Store 4F", "Buy Poke Doll", "Buy Poke Doll", event=True),
|
||||
LocationData("Celadon Department Store 4F", "Buy Fire Stone", "Fire Stone", event=True, inclusion=not_stonesanity),
|
||||
LocationData("Celadon Department Store 4F", "Buy Water Stone", "Water Stone", event=True, inclusion=not_stonesanity),
|
||||
|
||||
@@ -1,49 +1,47 @@
|
||||
from . import poke_data
|
||||
|
||||
|
||||
def can_surf(state, player):
|
||||
return (((state.has("HM03 Surf", player) and can_learn_hm(state, "Surf", player))
|
||||
or state.has("Flippers", player)) and (state.has("Soul Badge", player) or
|
||||
state.has(state.multiworld.worlds[player].extra_badges.get("Surf"), player)
|
||||
or state.multiworld.badges_needed_for_hm_moves[player].value == 0))
|
||||
def can_surf(state, world, player):
|
||||
return (((state.has("HM03 Surf", player) and can_learn_hm(state, world, "Surf", player))) and (state.has("Soul Badge", player) or
|
||||
state.has(world.extra_badges.get("Surf"), player)
|
||||
or world.options.badges_needed_for_hm_moves.value == 0))
|
||||
|
||||
|
||||
def can_cut(state, player):
|
||||
return ((state.has("HM01 Cut", player) and can_learn_hm(state, "Cut", player) or state.has("Master Sword", player))
|
||||
and (state.has("Cascade Badge", player) or
|
||||
state.has(state.multiworld.worlds[player].extra_badges.get("Cut"), player) or
|
||||
state.multiworld.badges_needed_for_hm_moves[player].value == 0))
|
||||
def can_cut(state, world, player):
|
||||
return ((state.has("HM01 Cut", player) and can_learn_hm(state, world, "Cut", player))
|
||||
and (state.has("Cascade Badge", player) or state.has(world.extra_badges.get("Cut"), player) or
|
||||
world.options.badges_needed_for_hm_moves.value == 0))
|
||||
|
||||
|
||||
def can_fly(state, player):
|
||||
return (((state.has("HM02 Fly", player) and can_learn_hm(state, "Fly", player)) or state.has("Flute", player)) and
|
||||
(state.has("Thunder Badge", player) or state.has(state.multiworld.worlds[player].extra_badges.get("Fly"), player)
|
||||
or state.multiworld.badges_needed_for_hm_moves[player].value == 0))
|
||||
def can_fly(state, world, player):
|
||||
return (((state.has("HM02 Fly", player) and can_learn_hm(state, world, "Fly", player)) or state.has("Flute", player)) and
|
||||
(state.has("Thunder Badge", player) or state.has(world.extra_badges.get("Fly"), player)
|
||||
or world.options.badges_needed_for_hm_moves.value == 0))
|
||||
|
||||
|
||||
def can_strength(state, player):
|
||||
return ((state.has("HM04 Strength", player) and can_learn_hm(state, "Strength", player)) or
|
||||
def can_strength(state, world, player):
|
||||
return ((state.has("HM04 Strength", player) and can_learn_hm(state, world, "Strength", player)) or
|
||||
state.has("Titan's Mitt", player)) and (state.has("Rainbow Badge", player) or
|
||||
state.has(state.multiworld.worlds[player].extra_badges.get("Strength"), player)
|
||||
or state.multiworld.badges_needed_for_hm_moves[player].value == 0)
|
||||
state.has(world.extra_badges.get("Strength"), player)
|
||||
or world.options.badges_needed_for_hm_moves.value == 0)
|
||||
|
||||
|
||||
def can_flash(state, player):
|
||||
return (((state.has("HM05 Flash", player) and can_learn_hm(state, "Flash", player)) or state.has("Lamp", player))
|
||||
and (state.has("Boulder Badge", player) or state.has(state.multiworld.worlds[player].extra_badges.get("Flash"),
|
||||
player) or state.multiworld.badges_needed_for_hm_moves[player].value == 0))
|
||||
def can_flash(state, world, player):
|
||||
return (((state.has("HM05 Flash", player) and can_learn_hm(state, world, "Flash", player)) or state.has("Lamp", player))
|
||||
and (state.has("Boulder Badge", player) or state.has(world.extra_badges.get("Flash"),
|
||||
player) or world.options.badges_needed_for_hm_moves.value == 0))
|
||||
|
||||
|
||||
def can_learn_hm(state, move, player):
|
||||
for pokemon, data in state.multiworld.worlds[player].local_poke_data.items():
|
||||
def can_learn_hm(state, world, move, player):
|
||||
for pokemon, data in world.local_poke_data.items():
|
||||
if state.has(pokemon, player) and data["tms"][6] & 1 << (["Cut", "Fly", "Surf", "Strength",
|
||||
"Flash"].index(move) + 2):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def can_get_hidden_items(state, player):
|
||||
return state.has("Item Finder", player) or not state.multiworld.require_item_finder[player].value
|
||||
def can_get_hidden_items(state, world, player):
|
||||
return state.has("Item Finder", player) or not world.options.require_item_finder.value
|
||||
|
||||
|
||||
def has_key_items(state, count, player):
|
||||
@@ -53,13 +51,14 @@ def has_key_items(state, count, player):
|
||||
"Hideout Key", "Card Key 2F", "Card Key 3F", "Card Key 4F", "Card Key 5F",
|
||||
"Card Key 6F", "Card Key 7F", "Card Key 8F", "Card Key 9F", "Card Key 10F",
|
||||
"Card Key 11F", "Exp. All", "Fire Stone", "Thunder Stone", "Water Stone",
|
||||
"Leaf Stone", "Moon Stone"] if state.has(item, player)])
|
||||
"Leaf Stone", "Moon Stone", "Oak's Parcel", "Helix Fossil", "Dome Fossil",
|
||||
"Old Amber", "Tea", "Gold Teeth", "Bike Voucher"] if state.has(item, player)])
|
||||
+ min(state.count("Progressive Card Key", player), 10))
|
||||
return key_items >= count
|
||||
|
||||
|
||||
def can_pass_guards(state, player):
|
||||
if state.multiworld.tea[player]:
|
||||
def can_pass_guards(state, world, player):
|
||||
if world.options.tea:
|
||||
return state.has("Tea", player)
|
||||
else:
|
||||
return state.has("Vending Machine Drinks", player)
|
||||
@@ -70,8 +69,8 @@ def has_badges(state, count, player):
|
||||
"Soul Badge", "Volcano Badge", "Earth Badge"] if state.has(item, player)]) >= count
|
||||
|
||||
|
||||
def oaks_aide(state, count, player):
|
||||
return ((not state.multiworld.require_pokedex[player] or state.has("Pokedex", player))
|
||||
def oaks_aide(state, world, count, player):
|
||||
return ((not world.options.require_pokedex or state.has("Pokedex", player))
|
||||
and has_pokemon(state, count, player))
|
||||
|
||||
|
||||
@@ -85,9 +84,7 @@ def has_pokemon(state, count, player):
|
||||
|
||||
|
||||
def fossil_checks(state, count, player):
|
||||
return (state.can_reach('Mt Moon B2F', 'Region', player) and
|
||||
state.can_reach('Cinnabar Lab Fossil Room', 'Region', player) and
|
||||
state.can_reach('Cinnabar Island', 'Region', player) and len(
|
||||
return (state.has_all(["Mt Moon Fossils", "Cinnabar Lab", "Cinnabar Island"], player) and len(
|
||||
[item for item in ["Dome Fossil", "Helix Fossil", "Old Amber"] if state.has(item, player)]) >= count)
|
||||
|
||||
|
||||
@@ -96,19 +93,19 @@ def card_key(state, floor, player):
|
||||
state.has("Progressive Card Key", player, floor - 1)
|
||||
|
||||
|
||||
def rock_tunnel(state, player):
|
||||
return can_flash(state, player) or not state.multiworld.dark_rock_tunnel_logic[player]
|
||||
def rock_tunnel(state, world, player):
|
||||
return can_flash(state, world, player) or not world.options.dark_rock_tunnel_logic
|
||||
|
||||
|
||||
def route_3(state, player):
|
||||
if state.multiworld.route_3_condition[player] == "defeat_brock":
|
||||
def route(state, world, player):
|
||||
if world.options.route_3_condition == "defeat_brock":
|
||||
return state.has("Defeat Brock", player)
|
||||
elif state.multiworld.route_3_condition[player] == "defeat_any_gym":
|
||||
elif world.options.route_3_condition == "defeat_any_gym":
|
||||
return state.has_any(["Defeat Brock", "Defeat Misty", "Defeat Lt. Surge", "Defeat Erika", "Defeat Koga",
|
||||
"Defeat Blaine", "Defeat Sabrina", "Defeat Viridian Gym Giovanni"], player)
|
||||
elif state.multiworld.route_3_condition[player] == "boulder_badge":
|
||||
elif world.options.route_3_condition == "boulder_badge":
|
||||
return state.has("Boulder Badge", player)
|
||||
elif state.multiworld.route_3_condition[player] == "any_badge":
|
||||
elif world.options.route_3_condition == "any_badge":
|
||||
return state.has_any(["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Marsh Badge",
|
||||
"Soul Badge", "Volcano Badge", "Earth Badge"], player)
|
||||
# open
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from Options import Toggle, Choice, Range, NamedRange, TextChoice, DeathLink, ItemsAccessibility
|
||||
from dataclasses import dataclass
|
||||
from Options import (PerGameCommonOptions, Toggle, Choice, Range, NamedRange, FreeText, TextChoice, DeathLink,
|
||||
ItemsAccessibility)
|
||||
|
||||
|
||||
class GameVersion(Choice):
|
||||
@@ -263,12 +265,18 @@ class PrizeSanity(Toggle):
|
||||
default = 0
|
||||
|
||||
|
||||
class TrainerSanity(Toggle):
|
||||
"""Add a location check to every trainer in the game, which can be obtained by talking to a trainer after defeating
|
||||
them. Does not affect gym leaders and some scripted event battles (including all Rival, Giovanni, and
|
||||
Cinnabar Gym battles)."""
|
||||
class TrainerSanity(NamedRange):
|
||||
"""Add location checks to trainers, which can be obtained by talking to a trainer after defeating them. Does not
|
||||
affect gym leaders and some scripted event battles. You may specify a number of trainers to have checks, and in
|
||||
this case they will be randomly selected. There is no in-game indication as to which trainers have checks."""
|
||||
display_name = "Trainersanity"
|
||||
default = 0
|
||||
range_start = 0
|
||||
range_end = 317
|
||||
special_range_names = {
|
||||
"disabled": 0,
|
||||
"full": 317
|
||||
}
|
||||
|
||||
|
||||
class RequirePokedex(Toggle):
|
||||
@@ -286,19 +294,19 @@ class AllPokemonSeen(Toggle):
|
||||
|
||||
|
||||
class DexSanity(NamedRange):
|
||||
"""Adds location checks for Pokemon flagged "owned" on your Pokedex. You may specify a percentage of Pokemon to
|
||||
have checks added. If Accessibility is set to full, this will be the percentage of all logically reachable
|
||||
Pokemon that will get a location check added to it. With items or minimal Accessibility, it will be the percentage
|
||||
of all 151 Pokemon.
|
||||
If Pokedex is required, the items for Pokemon acquired before acquiring the Pokedex can be found by talking to
|
||||
Professor Oak or evaluating the Pokedex via Oak's PC."""
|
||||
"""Adds location checks for Pokemon flagged "owned" on your Pokedex. You may specify the exact number of Dexsanity
|
||||
checks to add, and they will be distributed to Pokemon randomly.
|
||||
If Accessibility is set to Full, Dexsanity checks for Pokemon that are not logically reachable will be removed,
|
||||
so the number could be lower than you specified.
|
||||
If Pokedex is required, the Dexsanity checks for Pokemon you acquired before acquiring the Pokedex can be found by
|
||||
talking to Professor Oak or evaluating the Pokedex via Oak's PC."""
|
||||
display_name = "Dexsanity"
|
||||
default = 0
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
range_end = 151
|
||||
special_range_names = {
|
||||
"disabled": 0,
|
||||
"full": 100
|
||||
"full": 151
|
||||
}
|
||||
|
||||
|
||||
@@ -519,7 +527,8 @@ class TrainerLegendaries(Toggle):
|
||||
|
||||
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."""
|
||||
battle. If you move into and out of their line of sight without stopping, this chance will only trigger once.
|
||||
Trainers which have Trainersanity location checks ignore the Blind Trainers setting."""
|
||||
display_name = "Blind Trainers"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
@@ -704,6 +713,15 @@ class RandomizeTypeChart(Choice):
|
||||
default = 0
|
||||
|
||||
|
||||
class TypeChartSeed(FreeText):
|
||||
"""You can enter a number to use as a seed for the type chart. If you enter anything besides a number or "random",
|
||||
it will be used as a type chart group name, and everyone using the same group name will get the same type chart,
|
||||
made using the type chart options of one random player within the group. If a group name is used, the type matchup
|
||||
information will not be made available for trackers."""
|
||||
display_name = "Type Chart Seed"
|
||||
default = "random"
|
||||
|
||||
|
||||
class NormalMatchups(Range):
|
||||
"""If 'randomize' is chosen for Randomize Type Chart, this will be the weight for neutral matchups.
|
||||
No effect if 'chaos' is chosen"""
|
||||
@@ -850,8 +868,8 @@ class BicycleGateSkips(Choice):
|
||||
|
||||
|
||||
class RandomizePokemonPalettes(Choice):
|
||||
"""Modify palettes of Pokemon. Primary Type will set Pokemons' palettes based on their primary type, Follow
|
||||
Evolutions will randomize palettes but palettes will remain the same through evolutions (except Eeveelutions),
|
||||
"""Modify Super Gameboy palettes of Pokemon. Primary Type will set Pokemons' palettes based on their primary type,
|
||||
Follow Evolutions will randomize palettes but they will remain the same through evolutions (except Eeveelutions),
|
||||
Completely Random will randomize all Pokemons' palettes individually"""
|
||||
display_name = "Randomize Pokemon Palettes"
|
||||
option_vanilla = 0
|
||||
@@ -860,104 +878,105 @@ class RandomizePokemonPalettes(Choice):
|
||||
option_completely_random = 3
|
||||
|
||||
|
||||
pokemon_rb_options = {
|
||||
"accessibility": ItemsAccessibility,
|
||||
"game_version": GameVersion,
|
||||
"trainer_name": TrainerName,
|
||||
"rival_name": RivalName,
|
||||
#"goal": Goal,
|
||||
"elite_four_badges_condition": EliteFourBadgesCondition,
|
||||
"elite_four_key_items_condition": EliteFourKeyItemsCondition,
|
||||
"elite_four_pokedex_condition": EliteFourPokedexCondition,
|
||||
"victory_road_condition": VictoryRoadCondition,
|
||||
"route_22_gate_condition": Route22GateCondition,
|
||||
"viridian_gym_condition": ViridianGymCondition,
|
||||
"cerulean_cave_badges_condition": CeruleanCaveBadgesCondition,
|
||||
"cerulean_cave_key_items_condition": CeruleanCaveKeyItemsCondition,
|
||||
"route_3_condition": Route3Condition,
|
||||
"robbed_house_officer": RobbedHouseOfficer,
|
||||
"second_fossil_check_condition": SecondFossilCheckCondition,
|
||||
"fossil_check_item_types": FossilCheckItemTypes,
|
||||
"exp_all": ExpAll,
|
||||
"old_man": OldMan,
|
||||
"badgesanity": BadgeSanity,
|
||||
"badges_needed_for_hm_moves": BadgesNeededForHMMoves,
|
||||
"key_items_only": KeyItemsOnly,
|
||||
"tea": Tea,
|
||||
"extra_key_items": ExtraKeyItems,
|
||||
"split_card_key": SplitCardKey,
|
||||
"all_elevators_locked": AllElevatorsLocked,
|
||||
"extra_strength_boulders": ExtraStrengthBoulders,
|
||||
"require_item_finder": RequireItemFinder,
|
||||
"randomize_hidden_items": RandomizeHiddenItems,
|
||||
"prizesanity": PrizeSanity,
|
||||
"trainersanity": TrainerSanity,
|
||||
"dexsanity": DexSanity,
|
||||
"randomize_pokedex": RandomizePokedex,
|
||||
"require_pokedex": RequirePokedex,
|
||||
"all_pokemon_seen": AllPokemonSeen,
|
||||
"oaks_aide_rt_2": OaksAidRt2,
|
||||
"oaks_aide_rt_11": OaksAidRt11,
|
||||
"oaks_aide_rt_15": OaksAidRt15,
|
||||
"stonesanity": Stonesanity,
|
||||
"door_shuffle": DoorShuffle,
|
||||
"warp_tile_shuffle": WarpTileShuffle,
|
||||
"randomize_rock_tunnel": RandomizeRockTunnel,
|
||||
"dark_rock_tunnel_logic": DarkRockTunnelLogic,
|
||||
"free_fly_location": FreeFlyLocation,
|
||||
"town_map_fly_location": TownMapFlyLocation,
|
||||
"blind_trainers": BlindTrainers,
|
||||
"minimum_steps_between_encounters": MinimumStepsBetweenEncounters,
|
||||
"level_scaling": LevelScaling,
|
||||
"exp_modifier": ExpModifier,
|
||||
"randomize_wild_pokemon": RandomizeWildPokemon,
|
||||
"area_1_to_1_mapping": Area1To1Mapping,
|
||||
"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,
|
||||
"move_balancing": MoveBalancing,
|
||||
"fix_combat_bugs": FixCombatBugs,
|
||||
"randomize_pokemon_movesets": RandomizePokemonMovesets,
|
||||
"confine_transform_to_ditto": ConfineTranstormToDitto,
|
||||
"start_with_four_moves": StartWithFourMoves,
|
||||
"same_type_attack_bonus": SameTypeAttackBonus,
|
||||
"randomize_tm_moves": RandomizeTMMoves,
|
||||
"tm_same_type_compatibility": TMSameTypeCompatibility,
|
||||
"tm_normal_type_compatibility": TMNormalTypeCompatibility,
|
||||
"tm_other_type_compatibility": TMOtherTypeCompatibility,
|
||||
"hm_same_type_compatibility": HMSameTypeCompatibility,
|
||||
"hm_normal_type_compatibility": HMNormalTypeCompatibility,
|
||||
"hm_other_type_compatibility": HMOtherTypeCompatibility,
|
||||
"inherit_tm_hm_compatibility": InheritTMHMCompatibility,
|
||||
"randomize_move_types": RandomizeMoveTypes,
|
||||
"randomize_pokemon_types": RandomizePokemonTypes,
|
||||
"secondary_type_chance": SecondaryTypeChance,
|
||||
"randomize_type_chart": RandomizeTypeChart,
|
||||
"normal_matchups": NormalMatchups,
|
||||
"super_effective_matchups": SuperEffectiveMatchups,
|
||||
"not_very_effective_matchups": NotVeryEffectiveMatchups,
|
||||
"immunity_matchups": ImmunityMatchups,
|
||||
"safari_zone_normal_battles": SafariZoneNormalBattles,
|
||||
"normalize_encounter_chances": NormalizeEncounterChances,
|
||||
"reusable_tms": ReusableTMs,
|
||||
"better_shops": BetterShops,
|
||||
"master_ball_price": MasterBallPrice,
|
||||
"starting_money": StartingMoney,
|
||||
"lose_money_on_blackout": LoseMoneyOnBlackout,
|
||||
"poke_doll_skip": PokeDollSkip,
|
||||
"bicycle_gate_skips": BicycleGateSkips,
|
||||
"trap_percentage": TrapPercentage,
|
||||
"poison_trap_weight": PoisonTrapWeight,
|
||||
"fire_trap_weight": FireTrapWeight,
|
||||
"paralyze_trap_weight": ParalyzeTrapWeight,
|
||||
"sleep_trap_weight": SleepTrapWeight,
|
||||
"ice_trap_weight": IceTrapWeight,
|
||||
"randomize_pokemon_palettes": RandomizePokemonPalettes,
|
||||
"death_link": DeathLink
|
||||
}
|
||||
@dataclass
|
||||
class PokemonRBOptions(PerGameCommonOptions):
|
||||
accessibility: ItemsAccessibility
|
||||
game_version: GameVersion
|
||||
trainer_name: TrainerName
|
||||
rival_name: RivalName
|
||||
# goal: Goal
|
||||
elite_four_badges_condition: EliteFourBadgesCondition
|
||||
elite_four_key_items_condition: EliteFourKeyItemsCondition
|
||||
elite_four_pokedex_condition: EliteFourPokedexCondition
|
||||
victory_road_condition: VictoryRoadCondition
|
||||
route_22_gate_condition: Route22GateCondition
|
||||
viridian_gym_condition: ViridianGymCondition
|
||||
cerulean_cave_badges_condition: CeruleanCaveBadgesCondition
|
||||
cerulean_cave_key_items_condition: CeruleanCaveKeyItemsCondition
|
||||
route_3_condition: Route3Condition
|
||||
robbed_house_officer: RobbedHouseOfficer
|
||||
second_fossil_check_condition: SecondFossilCheckCondition
|
||||
fossil_check_item_types: FossilCheckItemTypes
|
||||
exp_all: ExpAll
|
||||
old_man: OldMan
|
||||
badgesanity: BadgeSanity
|
||||
badges_needed_for_hm_moves: BadgesNeededForHMMoves
|
||||
key_items_only: KeyItemsOnly
|
||||
tea: Tea
|
||||
extra_key_items: ExtraKeyItems
|
||||
split_card_key: SplitCardKey
|
||||
all_elevators_locked: AllElevatorsLocked
|
||||
extra_strength_boulders: ExtraStrengthBoulders
|
||||
require_item_finder: RequireItemFinder
|
||||
randomize_hidden_items: RandomizeHiddenItems
|
||||
prizesanity: PrizeSanity
|
||||
trainersanity: TrainerSanity
|
||||
dexsanity: DexSanity
|
||||
randomize_pokedex: RandomizePokedex
|
||||
require_pokedex: RequirePokedex
|
||||
all_pokemon_seen: AllPokemonSeen
|
||||
oaks_aide_rt_2: OaksAidRt2
|
||||
oaks_aide_rt_11: OaksAidRt11
|
||||
oaks_aide_rt_15: OaksAidRt15
|
||||
stonesanity: Stonesanity
|
||||
door_shuffle: DoorShuffle
|
||||
warp_tile_shuffle: WarpTileShuffle
|
||||
randomize_rock_tunnel: RandomizeRockTunnel
|
||||
dark_rock_tunnel_logic: DarkRockTunnelLogic
|
||||
free_fly_location: FreeFlyLocation
|
||||
town_map_fly_location: TownMapFlyLocation
|
||||
blind_trainers: BlindTrainers
|
||||
minimum_steps_between_encounters: MinimumStepsBetweenEncounters
|
||||
level_scaling: LevelScaling
|
||||
exp_modifier: ExpModifier
|
||||
randomize_wild_pokemon: RandomizeWildPokemon
|
||||
area_1_to_1_mapping: Area1To1Mapping
|
||||
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
|
||||
move_balancing: MoveBalancing
|
||||
fix_combat_bugs: FixCombatBugs
|
||||
randomize_pokemon_movesets: RandomizePokemonMovesets
|
||||
confine_transform_to_ditto: ConfineTranstormToDitto
|
||||
start_with_four_moves: StartWithFourMoves
|
||||
same_type_attack_bonus: SameTypeAttackBonus
|
||||
randomize_tm_moves: RandomizeTMMoves
|
||||
tm_same_type_compatibility: TMSameTypeCompatibility
|
||||
tm_normal_type_compatibility: TMNormalTypeCompatibility
|
||||
tm_other_type_compatibility: TMOtherTypeCompatibility
|
||||
hm_same_type_compatibility: HMSameTypeCompatibility
|
||||
hm_normal_type_compatibility: HMNormalTypeCompatibility
|
||||
hm_other_type_compatibility: HMOtherTypeCompatibility
|
||||
inherit_tm_hm_compatibility: InheritTMHMCompatibility
|
||||
randomize_move_types: RandomizeMoveTypes
|
||||
randomize_pokemon_types: RandomizePokemonTypes
|
||||
secondary_type_chance: SecondaryTypeChance
|
||||
randomize_type_chart: RandomizeTypeChart
|
||||
normal_matchups: NormalMatchups
|
||||
super_effective_matchups: SuperEffectiveMatchups
|
||||
not_very_effective_matchups: NotVeryEffectiveMatchups
|
||||
immunity_matchups: ImmunityMatchups
|
||||
type_chart_seed: TypeChartSeed
|
||||
safari_zone_normal_battles: SafariZoneNormalBattles
|
||||
normalize_encounter_chances: NormalizeEncounterChances
|
||||
reusable_tms: ReusableTMs
|
||||
better_shops: BetterShops
|
||||
master_ball_price: MasterBallPrice
|
||||
starting_money: StartingMoney
|
||||
lose_money_on_blackout: LoseMoneyOnBlackout
|
||||
poke_doll_skip: PokeDollSkip
|
||||
bicycle_gate_skips: BicycleGateSkips
|
||||
trap_percentage: TrapPercentage
|
||||
poison_trap_weight: PoisonTrapWeight
|
||||
fire_trap_weight: FireTrapWeight
|
||||
paralyze_trap_weight: ParalyzeTrapWeight
|
||||
sleep_trap_weight: SleepTrapWeight
|
||||
ice_trap_weight: IceTrapWeight
|
||||
randomize_pokemon_palettes: RandomizePokemonPalettes
|
||||
death_link: DeathLink
|
||||
|
||||
@@ -3,8 +3,8 @@ from . import poke_data, logic
|
||||
from .rom_addresses import rom_addresses
|
||||
|
||||
|
||||
def set_mon_palettes(self, random, data):
|
||||
if self.multiworld.randomize_pokemon_palettes[self.player] == "vanilla":
|
||||
def set_mon_palettes(world, random, data):
|
||||
if world.options.randomize_pokemon_palettes == "vanilla":
|
||||
return
|
||||
pallet_map = {
|
||||
"Poison": 0x0F,
|
||||
@@ -25,9 +25,9 @@ def set_mon_palettes(self, random, data):
|
||||
}
|
||||
palettes = []
|
||||
for mon in poke_data.pokemon_data:
|
||||
if self.multiworld.randomize_pokemon_palettes[self.player] == "primary_type":
|
||||
pallet = pallet_map[self.local_poke_data[mon]["type1"]]
|
||||
elif (self.multiworld.randomize_pokemon_palettes[self.player] == "follow_evolutions" and mon in
|
||||
if world.options.randomize_pokemon_palettes == "primary_type":
|
||||
pallet = pallet_map[world.local_poke_data[mon]["type1"]]
|
||||
elif (world.options.randomize_pokemon_palettes == "follow_evolutions" and mon in
|
||||
poke_data.evolves_from and poke_data.evolves_from[mon] != "Eevee"):
|
||||
pallet = palettes[-1]
|
||||
else: # completely_random or follow_evolutions and it is not an evolved form (except eeveelutions)
|
||||
@@ -93,40 +93,41 @@ def move_power(move_data):
|
||||
return power
|
||||
|
||||
|
||||
def process_move_data(self):
|
||||
self.local_move_data = deepcopy(poke_data.moves)
|
||||
def process_move_data(world):
|
||||
world.local_move_data = deepcopy(poke_data.moves)
|
||||
|
||||
if self.multiworld.randomize_move_types[self.player]:
|
||||
for move, data in self.local_move_data.items():
|
||||
if world.options.randomize_move_types:
|
||||
for move, data in world.local_move_data.items():
|
||||
if move == "No Move":
|
||||
continue
|
||||
# The chance of randomized moves choosing a normal type move is high, so we want to retain having a higher
|
||||
# rate of normal type moves
|
||||
data["type"] = self.multiworld.random.choice(list(poke_data.type_ids) + (["Normal"] * 4))
|
||||
data["type"] = world.random.choice(list(poke_data.type_ids) + (["Normal"] * 4))
|
||||
|
||||
if self.multiworld.move_balancing[self.player]:
|
||||
self.local_move_data["Sing"]["accuracy"] = 30
|
||||
self.local_move_data["Sleep Powder"]["accuracy"] = 40
|
||||
self.local_move_data["Spore"]["accuracy"] = 50
|
||||
self.local_move_data["Sonicboom"]["effect"] = 0
|
||||
self.local_move_data["Sonicboom"]["power"] = 50
|
||||
self.local_move_data["Dragon Rage"]["effect"] = 0
|
||||
self.local_move_data["Dragon Rage"]["power"] = 80
|
||||
self.local_move_data["Horn Drill"]["effect"] = 0
|
||||
self.local_move_data["Horn Drill"]["power"] = 70
|
||||
self.local_move_data["Horn Drill"]["accuracy"] = 90
|
||||
self.local_move_data["Guillotine"]["effect"] = 0
|
||||
self.local_move_data["Guillotine"]["power"] = 70
|
||||
self.local_move_data["Guillotine"]["accuracy"] = 90
|
||||
self.local_move_data["Fissure"]["effect"] = 0
|
||||
self.local_move_data["Fissure"]["power"] = 70
|
||||
self.local_move_data["Fissure"]["accuracy"] = 90
|
||||
self.local_move_data["Blizzard"]["accuracy"] = 70
|
||||
if self.multiworld.randomize_tm_moves[self.player]:
|
||||
self.local_tms = self.multiworld.random.sample([move for move in poke_data.moves.keys() if move not in
|
||||
["No Move"] + poke_data.hm_moves], 50)
|
||||
if world.options.move_balancing:
|
||||
world.local_move_data["Sing"]["accuracy"] = 30
|
||||
world.local_move_data["Sleep Powder"]["accuracy"] = 40
|
||||
world.local_move_data["Spore"]["accuracy"] = 50
|
||||
world.local_move_data["Sonicboom"]["effect"] = 0
|
||||
world.local_move_data["Sonicboom"]["power"] = 50
|
||||
world.local_move_data["Dragon Rage"]["effect"] = 0
|
||||
world.local_move_data["Dragon Rage"]["power"] = 80
|
||||
world.local_move_data["Horn Drill"]["effect"] = 0
|
||||
world.local_move_data["Horn Drill"]["power"] = 70
|
||||
world.local_move_data["Horn Drill"]["accuracy"] = 90
|
||||
world.local_move_data["Guillotine"]["effect"] = 0
|
||||
world.local_move_data["Guillotine"]["power"] = 70
|
||||
world.local_move_data["Guillotine"]["accuracy"] = 90
|
||||
world.local_move_data["Fissure"]["effect"] = 0
|
||||
world.local_move_data["Fissure"]["power"] = 70
|
||||
world.local_move_data["Fissure"]["accuracy"] = 90
|
||||
world.local_move_data["Blizzard"]["accuracy"] = 70
|
||||
|
||||
if world.options.randomize_tm_moves:
|
||||
world.local_tms = world.random.sample([move for move in poke_data.moves.keys() if move not in
|
||||
["No Move"] + poke_data.hm_moves], 50)
|
||||
else:
|
||||
self.local_tms = poke_data.tm_moves.copy()
|
||||
world.local_tms = poke_data.tm_moves.copy()
|
||||
|
||||
|
||||
def process_pokemon_data(self):
|
||||
@@ -138,12 +139,12 @@ def process_pokemon_data(self):
|
||||
compat_hms = set()
|
||||
|
||||
for mon, mon_data in local_poke_data.items():
|
||||
if self.multiworld.randomize_pokemon_stats[self.player] == "shuffle":
|
||||
if self.options.randomize_pokemon_stats == "shuffle":
|
||||
stats = [mon_data["hp"], mon_data["atk"], mon_data["def"], mon_data["spd"], mon_data["spc"]]
|
||||
if mon in poke_data.evolves_from:
|
||||
stat_shuffle_map = local_poke_data[poke_data.evolves_from[mon]]["stat_shuffle_map"]
|
||||
else:
|
||||
stat_shuffle_map = self.multiworld.random.sample(range(0, 5), 5)
|
||||
stat_shuffle_map = self.random.sample(range(0, 5), 5)
|
||||
|
||||
mon_data["stat_shuffle_map"] = stat_shuffle_map
|
||||
mon_data["hp"] = stats[stat_shuffle_map[0]]
|
||||
@@ -151,7 +152,7 @@ def process_pokemon_data(self):
|
||||
mon_data["def"] = stats[stat_shuffle_map[2]]
|
||||
mon_data["spd"] = stats[stat_shuffle_map[3]]
|
||||
mon_data["spc"] = stats[stat_shuffle_map[4]]
|
||||
elif self.multiworld.randomize_pokemon_stats[self.player] == "randomize":
|
||||
elif self.options.randomize_pokemon_stats == "randomize":
|
||||
first_run = True
|
||||
while (mon_data["hp"] > 255 or mon_data["atk"] > 255 or mon_data["def"] > 255 or mon_data["spd"] > 255
|
||||
or mon_data["spc"] > 255 or first_run):
|
||||
@@ -168,9 +169,9 @@ def process_pokemon_data(self):
|
||||
mon_data[stat] = 10
|
||||
total_stats -= 10
|
||||
assert total_stats >= 0, f"Error distributing stats for {mon} for player {self.player}"
|
||||
dist = [self.multiworld.random.randint(1, 101) / 100, self.multiworld.random.randint(1, 101) / 100,
|
||||
self.multiworld.random.randint(1, 101) / 100, self.multiworld.random.randint(1, 101) / 100,
|
||||
self.multiworld.random.randint(1, 101) / 100]
|
||||
dist = [self.random.randint(1, 101) / 100, self.random.randint(1, 101) / 100,
|
||||
self.random.randint(1, 101) / 100, self.random.randint(1, 101) / 100,
|
||||
self.random.randint(1, 101) / 100]
|
||||
total_dist = sum(dist)
|
||||
|
||||
mon_data["hp"] += int(round(dist[0] / total_dist * total_stats))
|
||||
@@ -178,30 +179,30 @@ def process_pokemon_data(self):
|
||||
mon_data["def"] += int(round(dist[2] / total_dist * total_stats))
|
||||
mon_data["spd"] += int(round(dist[3] / total_dist * total_stats))
|
||||
mon_data["spc"] += int(round(dist[4] / total_dist * total_stats))
|
||||
if self.multiworld.randomize_pokemon_types[self.player]:
|
||||
if self.multiworld.randomize_pokemon_types[self.player].value == 1 and mon in poke_data.evolves_from:
|
||||
if self.options.randomize_pokemon_types:
|
||||
if self.options.randomize_pokemon_types.value == 1 and mon in poke_data.evolves_from:
|
||||
type1 = local_poke_data[poke_data.evolves_from[mon]]["type1"]
|
||||
type2 = local_poke_data[poke_data.evolves_from[mon]]["type2"]
|
||||
if type1 == type2:
|
||||
if self.multiworld.secondary_type_chance[self.player].value == -1:
|
||||
if self.options.secondary_type_chance.value == -1:
|
||||
if mon_data["type1"] != mon_data["type2"]:
|
||||
while type2 == type1:
|
||||
type2 = self.multiworld.random.choice(list(poke_data.type_names.values()))
|
||||
elif self.multiworld.random.randint(1, 100) <= self.multiworld.secondary_type_chance[self.player].value:
|
||||
type2 = self.multiworld.random.choice(list(poke_data.type_names.values()))
|
||||
type2 = self.random.choice(list(poke_data.type_names.values()))
|
||||
elif self.random.randint(1, 100) <= self.options.secondary_type_chance.value:
|
||||
type2 = self.random.choice(list(poke_data.type_names.values()))
|
||||
else:
|
||||
type1 = self.multiworld.random.choice(list(poke_data.type_names.values()))
|
||||
type1 = self.random.choice(list(poke_data.type_names.values()))
|
||||
type2 = type1
|
||||
if ((self.multiworld.secondary_type_chance[self.player].value == -1 and mon_data["type1"]
|
||||
!= mon_data["type2"]) or self.multiworld.random.randint(1, 100)
|
||||
<= self.multiworld.secondary_type_chance[self.player].value):
|
||||
if ((self.options.secondary_type_chance.value == -1 and mon_data["type1"]
|
||||
!= mon_data["type2"]) or self.random.randint(1, 100)
|
||||
<= self.options.secondary_type_chance.value):
|
||||
while type2 == type1:
|
||||
type2 = self.multiworld.random.choice(list(poke_data.type_names.values()))
|
||||
type2 = self.random.choice(list(poke_data.type_names.values()))
|
||||
|
||||
mon_data["type1"] = type1
|
||||
mon_data["type2"] = type2
|
||||
if self.multiworld.randomize_pokemon_movesets[self.player]:
|
||||
if self.multiworld.randomize_pokemon_movesets[self.player] == "prefer_types":
|
||||
if self.options.randomize_pokemon_movesets:
|
||||
if self.options.randomize_pokemon_movesets == "prefer_types":
|
||||
if mon_data["type1"] == "Normal" and mon_data["type2"] == "Normal":
|
||||
chances = [[75, "Normal"]]
|
||||
elif mon_data["type1"] == "Normal" or mon_data["type2"] == "Normal":
|
||||
@@ -219,9 +220,9 @@ def process_pokemon_data(self):
|
||||
moves = list(poke_data.moves.keys())
|
||||
for move in ["No Move"] + poke_data.hm_moves:
|
||||
moves.remove(move)
|
||||
if self.multiworld.confine_transform_to_ditto[self.player]:
|
||||
if self.options.confine_transform_to_ditto:
|
||||
moves.remove("Transform")
|
||||
if self.multiworld.start_with_four_moves[self.player]:
|
||||
if self.options.start_with_four_moves:
|
||||
num_moves = 4
|
||||
else:
|
||||
num_moves = len([i for i in [mon_data["start move 1"], mon_data["start move 2"],
|
||||
@@ -231,12 +232,12 @@ def process_pokemon_data(self):
|
||||
non_power_moves = []
|
||||
learnsets[mon] = []
|
||||
for i in range(num_moves):
|
||||
if i == 0 and mon == "Ditto" and self.multiworld.confine_transform_to_ditto[self.player]:
|
||||
if i == 0 and mon == "Ditto" and self.options.confine_transform_to_ditto:
|
||||
move = "Transform"
|
||||
else:
|
||||
move = get_move(self.local_move_data, moves, chances, self.multiworld.random)
|
||||
while move == "Transform" and self.multiworld.confine_transform_to_ditto[self.player]:
|
||||
move = get_move(self.local_move_data, moves, chances, self.multiworld.random)
|
||||
move = get_move(self.local_move_data, moves, chances, self.random)
|
||||
while move == "Transform" and self.options.confine_transform_to_ditto:
|
||||
move = get_move(self.local_move_data, moves, chances, self.random)
|
||||
if self.local_move_data[move]["power"] < 5:
|
||||
non_power_moves.append(move)
|
||||
else:
|
||||
@@ -244,59 +245,58 @@ def process_pokemon_data(self):
|
||||
learnsets[mon].sort(key=lambda move: move_power(self.local_move_data[move]))
|
||||
if learnsets[mon]:
|
||||
for move in non_power_moves:
|
||||
learnsets[mon].insert(self.multiworld.random.randint(1, len(learnsets[mon])), move)
|
||||
learnsets[mon].insert(self.random.randint(1, len(learnsets[mon])), move)
|
||||
else:
|
||||
learnsets[mon] = non_power_moves
|
||||
for i in range(1, 5):
|
||||
if mon_data[f"start move {i}"] != "No Move" or self.multiworld.start_with_four_moves[self.player]:
|
||||
if mon_data[f"start move {i}"] != "No Move" or self.options.start_with_four_moves:
|
||||
mon_data[f"start move {i}"] = learnsets[mon].pop(0)
|
||||
|
||||
if self.multiworld.randomize_pokemon_catch_rates[self.player]:
|
||||
mon_data["catch rate"] = self.multiworld.random.randint(self.multiworld.minimum_catch_rate[self.player],
|
||||
255)
|
||||
if self.options.randomize_pokemon_catch_rates:
|
||||
mon_data["catch rate"] = self.random.randint(self.options.minimum_catch_rate, 255)
|
||||
else:
|
||||
mon_data["catch rate"] = max(self.multiworld.minimum_catch_rate[self.player], mon_data["catch rate"])
|
||||
mon_data["catch rate"] = max(self.options.minimum_catch_rate, mon_data["catch rate"])
|
||||
|
||||
def roll_tm_compat(roll_move):
|
||||
if self.local_move_data[roll_move]["type"] in [mon_data["type1"], mon_data["type2"]]:
|
||||
if roll_move in poke_data.hm_moves:
|
||||
if self.multiworld.hm_same_type_compatibility[self.player].value == -1:
|
||||
if self.options.hm_same_type_compatibility.value == -1:
|
||||
return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8)
|
||||
r = self.multiworld.random.randint(1, 100) <= self.multiworld.hm_same_type_compatibility[self.player].value
|
||||
r = self.random.randint(1, 100) <= self.options.hm_same_type_compatibility.value
|
||||
if r and mon not in poke_data.legendary_pokemon:
|
||||
compat_hms.add(roll_move)
|
||||
return r
|
||||
else:
|
||||
if self.multiworld.tm_same_type_compatibility[self.player].value == -1:
|
||||
if self.options.tm_same_type_compatibility.value == -1:
|
||||
return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8)
|
||||
return self.multiworld.random.randint(1, 100) <= self.multiworld.tm_same_type_compatibility[self.player].value
|
||||
return self.random.randint(1, 100) <= self.options.tm_same_type_compatibility.value
|
||||
elif self.local_move_data[roll_move]["type"] == "Normal" and "Normal" not in [mon_data["type1"], mon_data["type2"]]:
|
||||
if roll_move in poke_data.hm_moves:
|
||||
if self.multiworld.hm_normal_type_compatibility[self.player].value == -1:
|
||||
if self.options.hm_normal_type_compatibility.value == -1:
|
||||
return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8)
|
||||
r = self.multiworld.random.randint(1, 100) <= self.multiworld.hm_normal_type_compatibility[self.player].value
|
||||
r = self.random.randint(1, 100) <= self.options.hm_normal_type_compatibility.value
|
||||
if r and mon not in poke_data.legendary_pokemon:
|
||||
compat_hms.add(roll_move)
|
||||
return r
|
||||
else:
|
||||
if self.multiworld.tm_normal_type_compatibility[self.player].value == -1:
|
||||
if self.options.tm_normal_type_compatibility.value == -1:
|
||||
return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8)
|
||||
return self.multiworld.random.randint(1, 100) <= self.multiworld.tm_normal_type_compatibility[self.player].value
|
||||
return self.random.randint(1, 100) <= self.options.tm_normal_type_compatibility.value
|
||||
else:
|
||||
if roll_move in poke_data.hm_moves:
|
||||
if self.multiworld.hm_other_type_compatibility[self.player].value == -1:
|
||||
if self.options.hm_other_type_compatibility.value == -1:
|
||||
return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8)
|
||||
r = self.multiworld.random.randint(1, 100) <= self.multiworld.hm_other_type_compatibility[self.player].value
|
||||
r = self.random.randint(1, 100) <= self.options.hm_other_type_compatibility.value
|
||||
if r and mon not in poke_data.legendary_pokemon:
|
||||
compat_hms.add(roll_move)
|
||||
return r
|
||||
else:
|
||||
if self.multiworld.tm_other_type_compatibility[self.player].value == -1:
|
||||
if self.options.tm_other_type_compatibility.value == -1:
|
||||
return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8)
|
||||
return self.multiworld.random.randint(1, 100) <= self.multiworld.tm_other_type_compatibility[self.player].value
|
||||
return self.random.randint(1, 100) <= self.options.tm_other_type_compatibility.value
|
||||
|
||||
for flag, tm_move in enumerate(tms_hms):
|
||||
if mon in poke_data.evolves_from and self.multiworld.inherit_tm_hm_compatibility[self.player]:
|
||||
if mon in poke_data.evolves_from and self.options.inherit_tm_hm_compatibility:
|
||||
|
||||
if local_poke_data[poke_data.evolves_from[mon]]["tms"][int(flag / 8)] & 1 << (flag % 8):
|
||||
# always inherit learnable tms/hms
|
||||
@@ -310,7 +310,7 @@ def process_pokemon_data(self):
|
||||
# so this gets full chance roll
|
||||
bit = roll_tm_compat(tm_move)
|
||||
# otherwise 50% reduced chance to add compatibility over pre-evolved form
|
||||
elif self.multiworld.random.randint(1, 100) > 50 and roll_tm_compat(tm_move):
|
||||
elif self.random.randint(1, 100) > 50 and roll_tm_compat(tm_move):
|
||||
bit = 1
|
||||
else:
|
||||
bit = 0
|
||||
@@ -322,15 +322,13 @@ def process_pokemon_data(self):
|
||||
mon_data["tms"][int(flag / 8)] &= ~(1 << (flag % 8))
|
||||
|
||||
hm_verify = ["Surf", "Strength"]
|
||||
if self.multiworld.accessibility[self.player] != "minimal" or ((not
|
||||
self.multiworld.badgesanity[self.player]) and max(self.multiworld.elite_four_badges_condition[self.player],
|
||||
self.multiworld.route_22_gate_condition[self.player], self.multiworld.victory_road_condition[self.player])
|
||||
> 7) or (self.multiworld.door_shuffle[self.player] not in ("off", "simple")):
|
||||
if self.options.accessibility != "minimal" or ((not
|
||||
self.options.badgesanity) and max(self.options.elite_four_badges_condition,
|
||||
self.options.route_22_gate_condition, self.options.victory_road_condition)
|
||||
> 7) or (self.options.door_shuffle not in ("off", "simple")):
|
||||
hm_verify += ["Cut"]
|
||||
if self.multiworld.accessibility[self.player] != "minimal" or (not
|
||||
self.multiworld.dark_rock_tunnel_logic[self.player]) and ((self.multiworld.trainersanity[self.player] or
|
||||
self.multiworld.extra_key_items[self.player])
|
||||
or self.multiworld.door_shuffle[self.player]):
|
||||
if (self.options.accessibility != "minimal" or (not self.options.dark_rock_tunnel_logic) and
|
||||
((self.options.trainersanity or self.options.extra_key_items) or self.options.door_shuffle)):
|
||||
hm_verify += ["Flash"]
|
||||
# Fly does not need to be verified. Full/Insanity/Decoupled door shuffle connects reachable regions to unreachable
|
||||
# regions, so if Fly is available and can be learned, the towns you can fly to would be considered reachable for
|
||||
@@ -339,8 +337,7 @@ def process_pokemon_data(self):
|
||||
|
||||
for hm_move in hm_verify:
|
||||
if hm_move not in compat_hms:
|
||||
mon = self.multiworld.random.choice([mon for mon in poke_data.pokemon_data if mon not in
|
||||
poke_data.legendary_pokemon])
|
||||
mon = self.random.choice([mon for mon in poke_data.pokemon_data if mon not in poke_data.legendary_pokemon])
|
||||
flag = tms_hms.index(hm_move)
|
||||
local_poke_data[mon]["tms"][int(flag / 8)] |= 1 << (flag % 8)
|
||||
|
||||
@@ -352,7 +349,7 @@ def verify_hm_moves(multiworld, world, player):
|
||||
def intervene(move, test_state):
|
||||
move_bit = pow(2, poke_data.hm_moves.index(move) + 2)
|
||||
viable_mons = [mon for mon in world.local_poke_data if world.local_poke_data[mon]["tms"][6] & move_bit]
|
||||
if multiworld.randomize_wild_pokemon[player] and viable_mons:
|
||||
if world.options.randomize_wild_pokemon and viable_mons:
|
||||
accessible_slots = [loc for loc in multiworld.get_reachable_locations(test_state, player) if
|
||||
loc.type == "Wild Encounter"]
|
||||
|
||||
@@ -364,7 +361,7 @@ def verify_hm_moves(multiworld, world, player):
|
||||
|
||||
placed_mons = [slot.item.name for slot in accessible_slots]
|
||||
|
||||
if multiworld.area_1_to_1_mapping[player]:
|
||||
if world.options.area_1_to_1_mapping:
|
||||
placed_mons.sort(key=lambda i: number_of_zones(i))
|
||||
else:
|
||||
# this sort method doesn't work if you reference the same list being sorted in the lambda
|
||||
@@ -372,10 +369,10 @@ def verify_hm_moves(multiworld, world, player):
|
||||
placed_mons.sort(key=lambda i: placed_mons_copy.count(i))
|
||||
|
||||
placed_mon = placed_mons.pop()
|
||||
replace_mon = multiworld.random.choice(viable_mons)
|
||||
replace_slot = multiworld.random.choice([slot for slot in accessible_slots if slot.item.name
|
||||
replace_mon = world.random.choice(viable_mons)
|
||||
replace_slot = world.random.choice([slot for slot in accessible_slots if slot.item.name
|
||||
== placed_mon])
|
||||
if multiworld.area_1_to_1_mapping[player]:
|
||||
if world.options.area_1_to_1_mapping:
|
||||
zone = " - ".join(replace_slot.name.split(" - ")[:-1])
|
||||
replace_slots = [slot for slot in accessible_slots if slot.name.startswith(zone) and slot.item.name
|
||||
== placed_mon]
|
||||
@@ -387,7 +384,7 @@ def verify_hm_moves(multiworld, world, player):
|
||||
tms_hms = world.local_tms + poke_data.hm_moves
|
||||
flag = tms_hms.index(move)
|
||||
mon_list = [mon for mon in poke_data.pokemon_data.keys() if test_state.has(mon, player)]
|
||||
multiworld.random.shuffle(mon_list)
|
||||
world.random.shuffle(mon_list)
|
||||
mon_list.sort(key=lambda mon: world.local_move_data[move]["type"] not in
|
||||
[world.local_poke_data[mon]["type1"], world.local_poke_data[mon]["type2"]])
|
||||
for mon in mon_list:
|
||||
@@ -399,31 +396,31 @@ def verify_hm_moves(multiworld, world, player):
|
||||
while True:
|
||||
intervene_move = None
|
||||
test_state = multiworld.get_all_state(False)
|
||||
if not logic.can_learn_hm(test_state, "Surf", player):
|
||||
if not logic.can_learn_hm(test_state, world, "Surf", player):
|
||||
intervene_move = "Surf"
|
||||
elif not logic.can_learn_hm(test_state, "Strength", player):
|
||||
elif not logic.can_learn_hm(test_state, world, "Strength", player):
|
||||
intervene_move = "Strength"
|
||||
# cut may not be needed if accessibility is minimal, unless you need all 8 badges and badgesanity is off,
|
||||
# as you will require cut to access celadon gyn
|
||||
elif ((not logic.can_learn_hm(test_state, "Cut", player)) and
|
||||
(multiworld.accessibility[player] != "minimal" or ((not
|
||||
multiworld.badgesanity[player]) and max(
|
||||
multiworld.elite_four_badges_condition[player],
|
||||
multiworld.route_22_gate_condition[player],
|
||||
multiworld.victory_road_condition[player])
|
||||
> 7) or (multiworld.door_shuffle[player] not in ("off", "simple")))):
|
||||
elif ((not logic.can_learn_hm(test_state, world, "Cut", player)) and
|
||||
(world.options.accessibility != "minimal" or ((not
|
||||
world.options.badgesanity) and max(
|
||||
world.options.elite_four_badges_condition,
|
||||
world.options.route_22_gate_condition,
|
||||
world.options.victory_road_condition)
|
||||
> 7) or (world.options.door_shuffle not in ("off", "simple")))):
|
||||
intervene_move = "Cut"
|
||||
elif ((not logic.can_learn_hm(test_state, "Flash", player))
|
||||
and multiworld.dark_rock_tunnel_logic[player]
|
||||
and (multiworld.accessibility[player] != "minimal"
|
||||
or multiworld.door_shuffle[player])):
|
||||
elif ((not logic.can_learn_hm(test_state, world, "Flash", player))
|
||||
and world.options.dark_rock_tunnel_logic
|
||||
and (world.options.accessibility != "minimal"
|
||||
or world.options.door_shuffle)):
|
||||
intervene_move = "Flash"
|
||||
# If no Pokémon can learn Fly, then during door shuffle it would simply not treat the free fly maps
|
||||
# as reachable, and if on no door shuffle or simple, fly is simply never necessary.
|
||||
# We only intervene if a Pokémon is able to learn fly but none are reachable, as that would have been
|
||||
# considered in door shuffle.
|
||||
elif ((not logic.can_learn_hm(test_state, "Fly", player))
|
||||
and multiworld.door_shuffle[player] not in
|
||||
elif ((not logic.can_learn_hm(test_state, world, "Fly", player))
|
||||
and world.options.door_shuffle not in
|
||||
("off", "simple") and [world.fly_map, world.town_map_fly_map] != ["Pallet Town", "Pallet Town"]):
|
||||
intervene_move = "Fly"
|
||||
if intervene_move:
|
||||
@@ -432,4 +429,4 @@ def verify_hm_moves(multiworld, world, player):
|
||||
intervene(intervene_move, test_state)
|
||||
last_intervene = intervene_move
|
||||
else:
|
||||
break
|
||||
break
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,22 +13,22 @@ from .regions import PokemonRBWarp, map_ids, town_map_coords
|
||||
from . import poke_data
|
||||
|
||||
|
||||
def write_quizzes(self, data, random):
|
||||
def write_quizzes(world, data, random):
|
||||
|
||||
def get_quiz(q, a):
|
||||
if q == 0:
|
||||
r = random.randint(0, 3)
|
||||
if r == 0:
|
||||
mon = self.trade_mons["Trade_Dux"]
|
||||
mon = world.trade_mons["Trade_Dux"]
|
||||
text = "A woman in<LINE>Vermilion City<CONT>"
|
||||
elif r == 1:
|
||||
mon = self.trade_mons["Trade_Lola"]
|
||||
mon = world.trade_mons["Trade_Lola"]
|
||||
text = "A man in<LINE>Cerulean City<CONT>"
|
||||
elif r == 2:
|
||||
mon = self.trade_mons["Trade_Marcel"]
|
||||
mon = world.trade_mons["Trade_Marcel"]
|
||||
text = "Someone on Route 2<LINE>"
|
||||
elif r == 3:
|
||||
mon = self.trade_mons["Trade_Spot"]
|
||||
mon = world.trade_mons["Trade_Spot"]
|
||||
text = "Someone on Route 5<LINE>"
|
||||
if not a:
|
||||
answers.append(0)
|
||||
@@ -38,21 +38,30 @@ def write_quizzes(self, data, random):
|
||||
|
||||
return encode_text(f"{text}was looking for<CONT>{mon}?<DONE>")
|
||||
elif q == 1:
|
||||
for location in self.multiworld.get_filled_locations():
|
||||
if location.item.name == "Secret Key" and location.item.player == self.player:
|
||||
for location in world.multiworld.get_filled_locations():
|
||||
if location.item.name == "Secret Key" and location.item.player == world.player:
|
||||
break
|
||||
player_name = self.multiworld.player_name[location.player]
|
||||
player_name = world.multiworld.player_name[location.player]
|
||||
if not a:
|
||||
if len(self.multiworld.player_name) > 1:
|
||||
if len(world.multiworld.player_name) > 1:
|
||||
old_name = player_name
|
||||
while old_name == player_name:
|
||||
player_name = random.choice(list(self.multiworld.player_name.values()))
|
||||
player_name = random.choice(list(world.multiworld.player_name.values()))
|
||||
else:
|
||||
return encode_text("You're playing<LINE>in a multiworld<CONT>with other<CONT>players?<DONE>")
|
||||
if player_name == self.multiworld.player_name[self.player]:
|
||||
player_name = "yourself"
|
||||
player_name = encode_text(player_name, force=True, safety=True)
|
||||
return encode_text(f"The Secret Key was<LINE>found by<CONT>") + player_name + encode_text("<DONE>")
|
||||
if world.multiworld.get_entrance(
|
||||
"Cinnabar Island-G to Cinnabar Gym", world.player).connected_region.name == "Cinnabar Gym":
|
||||
if player_name == world.multiworld.player_name[world.player]:
|
||||
player_name = "yourself"
|
||||
player_name = encode_text(player_name, force=True, safety=True)
|
||||
return encode_text(f"The Secret Key was<LINE>found by<CONT>") + player_name + encode_text("?<DONE>")
|
||||
else:
|
||||
# Might not have found it yet
|
||||
if player_name == world.multiworld.player_name[world.player]:
|
||||
return encode_text(f"The Secret Key was<LINE>placed in<CONT>your own world?<DONE>")
|
||||
player_name = encode_text(player_name, force=True, safety=True)
|
||||
return (encode_text(f"The Secret Key was<LINE>placed in<CONT>") + player_name
|
||||
+ encode_text("'s<CONT>world?<DONE>"))
|
||||
elif q == 2:
|
||||
if a:
|
||||
return encode_text(f"#mon is<LINE>pronounced<CONT>Po-kay-mon?<DONE>")
|
||||
@@ -62,8 +71,8 @@ def write_quizzes(self, data, random):
|
||||
else:
|
||||
return encode_text(f"#mon is<LINE>pronounced<CONT>Po-kuh-mon?<DONE>")
|
||||
elif q == 3:
|
||||
starters = [" ".join(self.multiworld.get_location(
|
||||
f"Oak's Lab - Starter {i}", self.player).item.name.split(" ")[1:]) for i in range(1, 4)]
|
||||
starters = [" ".join(world.multiworld.get_location(
|
||||
f"Oak's Lab - Starter {i}", world.player).item.name.split(" ")[1:]) for i in range(1, 4)]
|
||||
mon = random.choice(starters)
|
||||
nots = random.choice(range(8, 16, 2))
|
||||
if random.randint(0, 1):
|
||||
@@ -82,10 +91,10 @@ def write_quizzes(self, data, random):
|
||||
return encode_text(text)
|
||||
elif q == 4:
|
||||
if a:
|
||||
tm_text = self.local_tms[27]
|
||||
tm_text = world.local_tms[27]
|
||||
else:
|
||||
if self.multiworld.randomize_tm_moves[self.player]:
|
||||
wrong_tms = self.local_tms.copy()
|
||||
if world.options.randomize_tm_moves:
|
||||
wrong_tms = world.local_tms.copy()
|
||||
wrong_tms.pop(27)
|
||||
tm_text = random.choice(wrong_tms)
|
||||
else:
|
||||
@@ -102,12 +111,36 @@ def write_quizzes(self, data, random):
|
||||
i = random.randint(0, random.choice([9, 99]))
|
||||
return encode_text(f"POLIWAG evolves {i}<LINE>times?<DONE>")
|
||||
elif q == 7:
|
||||
entity = "Motor Carrier"
|
||||
if not a:
|
||||
entity = random.choice(["Driver", "Shipper"])
|
||||
return encode_text("Title 49 of the<LINE>U.S. Code of<CONT>Federal<CONT>Regulations part<CONT>397.67 states"
|
||||
f"<CONT>that the<CONT>{entity}<CONT>is responsible<CONT>for planning<CONT>routes when"
|
||||
"<CONT>hazardous<CONT>materials are<CONT>transported?<DONE>")
|
||||
q2 = random.randint(0, 2)
|
||||
if q2 == 0:
|
||||
entity = "Motor Carrier"
|
||||
if not a:
|
||||
entity = random.choice(["Driver", "Shipper"])
|
||||
return encode_text("Title 49 of the<LINE>U.S. Code of<CONT>Federal<CONT>Regulations part<CONT>397.67 "
|
||||
f"states<CONT>that the<CONT>{entity}<CONT>is responsible<CONT>for planning<CONT>"
|
||||
"routes when<CONT>hazardous<CONT>materials are<CONT>transported?<DONE>")
|
||||
elif q2 == 1:
|
||||
if a:
|
||||
state = random.choice(
|
||||
['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut',
|
||||
'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas',
|
||||
'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota',
|
||||
'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Jersey', 'New Mexico',
|
||||
'New York', 'North Carolina', 'North Dakota', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania',
|
||||
'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont',
|
||||
'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming'])
|
||||
else:
|
||||
state = "New Hampshire"
|
||||
return encode_text(
|
||||
f"As of 2024,<LINE>{state}<CONT>has a law<CONT>requiring all<CONT>front seat vehicle<CONT>occupants to use<CONT>seatbelts?<DONE>")
|
||||
elif q2 == 2:
|
||||
if a:
|
||||
country = random.choice(["The United States", "Mexico", "Canada", "Germany", "France", "China",
|
||||
"Russia", "Spain", "Brazil", "Ukraine", "Saudi Arabia", "Egypt"])
|
||||
else:
|
||||
country = random.choice(["The U.K.", "Pakistan", "India", "Japan", "Australia",
|
||||
"New Zealand", "Thailand"])
|
||||
return encode_text(f"As of 2020,<LINE>drivers in<CONT>{country}<CONT>drive on the<CONT>right side of<CONT>the road?<DONE>")
|
||||
elif q == 8:
|
||||
mon = random.choice(list(poke_data.evolution_levels.keys()))
|
||||
level = poke_data.evolution_levels[mon]
|
||||
@@ -115,17 +148,17 @@ def write_quizzes(self, data, random):
|
||||
level += random.choice(range(1, 6)) * random.choice((-1, 1))
|
||||
return encode_text(f"{mon} evolves<LINE>at level {level}?<DONE>")
|
||||
elif q == 9:
|
||||
move = random.choice(list(self.local_move_data.keys()))
|
||||
actual_type = self.local_move_data[move]["type"]
|
||||
move = random.choice(list(world.local_move_data.keys()))
|
||||
actual_type = world.local_move_data[move]["type"]
|
||||
question_type = actual_type
|
||||
while question_type == actual_type and not a:
|
||||
question_type = random.choice(list(poke_data.type_ids.keys()))
|
||||
return encode_text(f"{move} is<LINE>{question_type} type?<DONE>")
|
||||
elif q == 10:
|
||||
mon = random.choice(list(poke_data.pokemon_data.keys()))
|
||||
actual_type = self.local_poke_data[mon][random.choice(("type1", "type2"))]
|
||||
actual_type = world.local_poke_data[mon][random.choice(("type1", "type2"))]
|
||||
question_type = actual_type
|
||||
while question_type in [self.local_poke_data[mon]["type1"], self.local_poke_data[mon]["type2"]] and not a:
|
||||
while question_type in [world.local_poke_data[mon]["type1"], world.local_poke_data[mon]["type2"]] and not a:
|
||||
question_type = random.choice(list(poke_data.type_ids.keys()))
|
||||
return encode_text(f"{mon} is<LINE>{question_type} type?<DONE>")
|
||||
elif q == 11:
|
||||
@@ -147,8 +180,8 @@ def write_quizzes(self, data, random):
|
||||
return encode_text(f"{equation}<LINE>= {question_result}?<DONE>")
|
||||
elif q == 12:
|
||||
route = random.choice((12, 16))
|
||||
actual_mon = self.multiworld.get_location(f"Route {route} - Sleeping Pokemon",
|
||||
self.player).item.name.split("Static ")[1]
|
||||
actual_mon = world.multiworld.get_location(f"Route {route} - Sleeping Pokemon",
|
||||
world.player).item.name.split("Static ")[1]
|
||||
question_mon = actual_mon
|
||||
while question_mon == actual_mon and not a:
|
||||
question_mon = random.choice(list(poke_data.pokemon_data.keys()))
|
||||
@@ -157,7 +190,7 @@ def write_quizzes(self, data, random):
|
||||
type1 = random.choice(list(poke_data.type_ids.keys()))
|
||||
type2 = random.choice(list(poke_data.type_ids.keys()))
|
||||
eff_msgs = ["super effective<CONT>", "no ", "not very<CONT>effective<CONT>", "normal "]
|
||||
for matchup in self.type_chart:
|
||||
for matchup in world.type_chart:
|
||||
if matchup[0] == type1 and matchup[1] == type2:
|
||||
if matchup[2] > 10:
|
||||
eff = eff_msgs[0]
|
||||
@@ -175,15 +208,25 @@ def write_quizzes(self, data, random):
|
||||
eff = random.choice(eff_msgs)
|
||||
return encode_text(f"{type1} deals<LINE>{eff}damage to<CONT>{type2} type?<DONE>")
|
||||
elif q == 14:
|
||||
fossil_level = self.multiworld.get_location("Fossil Level - Trainer Parties",
|
||||
self.player).party_data[0]['level']
|
||||
fossil_level = world.multiworld.get_location("Fossil Level - Trainer Parties",
|
||||
world.player).party_data[0]['level']
|
||||
if not a:
|
||||
fossil_level += random.choice((-5, 5))
|
||||
return encode_text(f"Fossil #MON<LINE>revive at level<CONT>{fossil_level}?<DONE>")
|
||||
elif q == 15:
|
||||
if a:
|
||||
fodmap = random.choice(["garlic", "onion", "milk", "watermelon", "cherries", "wheat", "barley",
|
||||
"pistachios", "cashews", "kidney beans", "apples", "honey"])
|
||||
else:
|
||||
fodmap = random.choice(["carrots", "potatoes", "oranges", "pineapple", "blueberries", "parmesan",
|
||||
"eggs", "beef", "chicken", "oat", "rice", "maple syrup", "peanuts"])
|
||||
are_is = "are" if fodmap[-1] == "s" else "is"
|
||||
return encode_text(f"According to<LINE>Monash Uni.,<CONT>{fodmap} {are_is}<CONT>considered high<CONT>in FODMAPs?<DONE>")
|
||||
|
||||
answers = [random.randint(0, 1) for _ in range(6)]
|
||||
|
||||
questions = random.sample((range(0, 15)), 6)
|
||||
questions = random.sample((range(0, 16)), 6)
|
||||
|
||||
question_texts = []
|
||||
for i, question in enumerate(questions):
|
||||
question_texts.append(get_quiz(question, answers[i]))
|
||||
@@ -193,9 +236,9 @@ def write_quizzes(self, data, random):
|
||||
write_bytes(data, question_texts[i], rom_addresses[f"Text_Quiz_{quiz}"])
|
||||
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
random = self.multiworld.per_slot_randoms[self.player]
|
||||
game_version = self.multiworld.game_version[self.player].current_key
|
||||
def generate_output(world, output_directory: str):
|
||||
random = world.random
|
||||
game_version = world.options.game_version.current_key
|
||||
data = bytes(get_base_rom_bytes(game_version))
|
||||
|
||||
base_patch = pkgutil.get_data(__name__, f'basepatch_{game_version}.bsdiff4')
|
||||
@@ -205,8 +248,8 @@ def generate_output(self, output_directory: str):
|
||||
basemd5 = hashlib.md5()
|
||||
basemd5.update(data)
|
||||
|
||||
pallet_connections = {entrance: self.multiworld.get_entrance(f"Pallet Town to {entrance}",
|
||||
self.player).connected_region.name for
|
||||
pallet_connections = {entrance: world.multiworld.get_entrance(f"Pallet Town to {entrance}",
|
||||
world.player).connected_region.name for
|
||||
entrance in ["Player's House 1F", "Oak's Lab",
|
||||
"Rival's House"]}
|
||||
paths = None
|
||||
@@ -222,11 +265,11 @@ def generate_output(self, output_directory: str):
|
||||
elif pallet_connections["Oak's Lab"] == "Player's House 1F":
|
||||
write_bytes(data, [0x5F, 0xC7, 0x0C, 0x0C, 0x00, 0x00], rom_addresses["Pallet_Fly_Coords"])
|
||||
|
||||
for region in self.multiworld.get_regions(self.player):
|
||||
for region in world.multiworld.get_regions(world.player):
|
||||
for entrance in region.exits:
|
||||
if isinstance(entrance, PokemonRBWarp):
|
||||
self.multiworld.spoiler.set_entrance(entrance.name, entrance.connected_region.name, "entrance",
|
||||
self.player)
|
||||
world.multiworld.spoiler.set_entrance(entrance.name, entrance.connected_region.name, "entrance",
|
||||
world.player)
|
||||
warp_ids = (entrance.warp_id,) if isinstance(entrance.warp_id, int) else entrance.warp_id
|
||||
warp_to_ids = (entrance.target,) if isinstance(entrance.target, int) else entrance.target
|
||||
for i, warp_id in enumerate(warp_ids):
|
||||
@@ -241,32 +284,32 @@ def generate_output(self, output_directory: str):
|
||||
data[address] = 0 if "Elevator" in connected_map_name else warp_to_ids[i]
|
||||
data[address + 1] = map_ids[connected_map_name]
|
||||
|
||||
if self.multiworld.door_shuffle[self.player] == "simple":
|
||||
if world.options.door_shuffle == "simple":
|
||||
for (entrance, _, _, map_coords_entries, map_name, _) in town_map_coords.values():
|
||||
destination = self.multiworld.get_entrance(entrance, self.player).connected_region.name
|
||||
destination = world.multiworld.get_entrance(entrance, world.player).connected_region.name
|
||||
(_, x, y, _, _, map_order_entry) = town_map_coords[destination]
|
||||
for map_coord_entry in map_coords_entries:
|
||||
data[rom_addresses["Town_Map_Coords"] + (map_coord_entry * 4) + 1] = (y << 4) | x
|
||||
data[rom_addresses["Town_Map_Order"] + map_order_entry] = map_ids[map_name]
|
||||
|
||||
if not self.multiworld.key_items_only[self.player]:
|
||||
if not world.options.key_items_only:
|
||||
for i, gym_leader in enumerate(("Pewter Gym - Brock TM", "Cerulean Gym - Misty TM",
|
||||
"Vermilion Gym - Lt. Surge TM", "Celadon Gym - Erika TM",
|
||||
"Fuchsia Gym - Koga TM", "Saffron Gym - Sabrina TM",
|
||||
"Cinnabar Gym - Blaine TM", "Viridian Gym - Giovanni TM")):
|
||||
item_name = self.multiworld.get_location(gym_leader, self.player).item.name
|
||||
item_name = world.multiworld.get_location(gym_leader, world.player).item.name
|
||||
if item_name.startswith("TM"):
|
||||
try:
|
||||
tm = int(item_name[2:4])
|
||||
move = poke_data.moves[self.local_tms[tm - 1]]["id"]
|
||||
move = poke_data.moves[world.local_tms[tm - 1]]["id"]
|
||||
data[rom_addresses["Gym_Leader_Moves"] + (2 * i)] = move
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def set_trade_mon(address, loc):
|
||||
mon = self.multiworld.get_location(loc, self.player).item.name
|
||||
mon = world.multiworld.get_location(loc, world.player).item.name
|
||||
data[rom_addresses[address]] = poke_data.pokemon_data[mon]["id"]
|
||||
self.trade_mons[address] = mon
|
||||
world.trade_mons[address] = mon
|
||||
|
||||
if game_version == "red":
|
||||
set_trade_mon("Trade_Terry", "Safari Zone Center - Wild Pokemon - 5")
|
||||
@@ -282,10 +325,10 @@ def generate_output(self, output_directory: str):
|
||||
set_trade_mon("Trade_Doris", "Cerulean Cave 1F - Wild Pokemon - 9")
|
||||
set_trade_mon("Trade_Crinkles", "Route 12 - Wild Pokemon - 4")
|
||||
|
||||
data[rom_addresses['Fly_Location']] = self.fly_map_code
|
||||
data[rom_addresses['Map_Fly_Location']] = self.town_map_fly_map_code
|
||||
data[rom_addresses['Fly_Location']] = world.fly_map_code
|
||||
data[rom_addresses['Map_Fly_Location']] = world.town_map_fly_map_code
|
||||
|
||||
if self.multiworld.fix_combat_bugs[self.player]:
|
||||
if world.options.fix_combat_bugs:
|
||||
data[rom_addresses["Option_Fix_Combat_Bugs"]] = 1
|
||||
data[rom_addresses["Option_Fix_Combat_Bugs_Focus_Energy"]] = 0x28 # jr z
|
||||
data[rom_addresses["Option_Fix_Combat_Bugs_HP_Drain_Dream_Eater"]] = 0x1A # ld a, (de)
|
||||
@@ -298,25 +341,25 @@ def generate_output(self, output_directory: str):
|
||||
data[rom_addresses["Option_Fix_Combat_Bugs_Heal_Effect"] + 1] = 5 # 5 bytes ahead
|
||||
data[rom_addresses["Option_Fix_Combat_Bugs_Heal_Stat_Modifiers"]] = 1
|
||||
|
||||
if self.multiworld.poke_doll_skip[self.player] == "in_logic":
|
||||
if world.options.poke_doll_skip == "in_logic":
|
||||
data[rom_addresses["Option_Silph_Scope_Skip"]] = 0x00 # nop
|
||||
data[rom_addresses["Option_Silph_Scope_Skip"] + 1] = 0x00 # nop
|
||||
data[rom_addresses["Option_Silph_Scope_Skip"] + 2] = 0x00 # nop
|
||||
|
||||
if self.multiworld.bicycle_gate_skips[self.player] == "patched":
|
||||
if world.options.bicycle_gate_skips == "patched":
|
||||
data[rom_addresses["Option_Route_16_Gate_Fix"]] = 0x00 # nop
|
||||
data[rom_addresses["Option_Route_16_Gate_Fix"] + 1] = 0x00 # nop
|
||||
data[rom_addresses["Option_Route_18_Gate_Fix"]] = 0x00 # nop
|
||||
data[rom_addresses["Option_Route_18_Gate_Fix"] + 1] = 0x00 # nop
|
||||
|
||||
if self.multiworld.door_shuffle[self.player]:
|
||||
if world.options.door_shuffle:
|
||||
data[rom_addresses["Entrance_Shuffle_Fuji_Warp"]] = 1 # prevent warping to Fuji's House from Pokemon Tower 7F
|
||||
|
||||
if self.multiworld.all_elevators_locked[self.player]:
|
||||
if world.options.all_elevators_locked:
|
||||
data[rom_addresses["Option_Locked_Elevator_Celadon"]] = 0x20 # jr nz
|
||||
data[rom_addresses["Option_Locked_Elevator_Silph"]] = 0x20 # jr nz
|
||||
|
||||
if self.multiworld.tea[self.player].value:
|
||||
if world.options.tea:
|
||||
data[rom_addresses["Option_Tea"]] = 1
|
||||
data[rom_addresses["Guard_Drink_List"]] = 0x54
|
||||
data[rom_addresses["Guard_Drink_List"] + 1] = 0
|
||||
@@ -325,90 +368,94 @@ def generate_output(self, output_directory: str):
|
||||
"<PARA>Oh wait there,<LINE>the road's closed.<DONE>"),
|
||||
rom_addresses["Text_Saffron_Gate"])
|
||||
|
||||
data[rom_addresses["Tea_Key_Item_A"]] = 0x28 # jr .z
|
||||
data[rom_addresses["Tea_Key_Item_B"]] = 0x28 # jr .z
|
||||
data[rom_addresses["Tea_Key_Item_C"]] = 0x28 # jr .z
|
||||
|
||||
data[rom_addresses["Fossils_Needed_For_Second_Item"]] = (
|
||||
self.multiworld.second_fossil_check_condition[self.player].value)
|
||||
world.options.second_fossil_check_condition.value)
|
||||
|
||||
data[rom_addresses["Option_Lose_Money"]] = int(not self.multiworld.lose_money_on_blackout[self.player].value)
|
||||
data[rom_addresses["Option_Lose_Money"]] = int(not world.options.lose_money_on_blackout.value)
|
||||
|
||||
if self.multiworld.extra_key_items[self.player]:
|
||||
if world.options.extra_key_items:
|
||||
data[rom_addresses['Option_Extra_Key_Items_A']] = 1
|
||||
data[rom_addresses['Option_Extra_Key_Items_B']] = 1
|
||||
data[rom_addresses['Option_Extra_Key_Items_C']] = 1
|
||||
data[rom_addresses['Option_Extra_Key_Items_D']] = 1
|
||||
data[rom_addresses["Option_Split_Card_Key"]] = self.multiworld.split_card_key[self.player].value
|
||||
data[rom_addresses["Option_Blind_Trainers"]] = round(self.multiworld.blind_trainers[self.player].value * 2.55)
|
||||
data[rom_addresses["Option_Cerulean_Cave_Badges"]] = self.multiworld.cerulean_cave_badges_condition[self.player].value
|
||||
data[rom_addresses["Option_Cerulean_Cave_Key_Items"]] = self.multiworld.cerulean_cave_key_items_condition[self.player].total
|
||||
write_bytes(data, encode_text(str(self.multiworld.cerulean_cave_badges_condition[self.player].value)), rom_addresses["Text_Cerulean_Cave_Badges"])
|
||||
write_bytes(data, encode_text(str(self.multiworld.cerulean_cave_key_items_condition[self.player].total) + " key items."), rom_addresses["Text_Cerulean_Cave_Key_Items"])
|
||||
data[rom_addresses['Option_Encounter_Minimum_Steps']] = self.multiworld.minimum_steps_between_encounters[self.player].value
|
||||
data[rom_addresses['Option_Route23_Badges']] = self.multiworld.victory_road_condition[self.player].value
|
||||
data[rom_addresses['Option_Victory_Road_Badges']] = self.multiworld.route_22_gate_condition[self.player].value
|
||||
data[rom_addresses['Option_Elite_Four_Pokedex']] = self.multiworld.elite_four_pokedex_condition[self.player].total
|
||||
data[rom_addresses['Option_Elite_Four_Key_Items']] = self.multiworld.elite_four_key_items_condition[self.player].total
|
||||
data[rom_addresses['Option_Elite_Four_Badges']] = self.multiworld.elite_four_badges_condition[self.player].value
|
||||
write_bytes(data, encode_text(str(self.multiworld.elite_four_badges_condition[self.player].value)), rom_addresses["Text_Elite_Four_Badges"])
|
||||
write_bytes(data, encode_text(str(self.multiworld.elite_four_key_items_condition[self.player].total) + " key items, and"), rom_addresses["Text_Elite_Four_Key_Items"])
|
||||
write_bytes(data, encode_text(str(self.multiworld.elite_four_pokedex_condition[self.player].total) + " #MON"), rom_addresses["Text_Elite_Four_Pokedex"])
|
||||
write_bytes(data, encode_text(str(self.total_key_items), length=2), rom_addresses["Trainer_Screen_Total_Key_Items"])
|
||||
data[rom_addresses["Option_Split_Card_Key"]] = world.options.split_card_key.value
|
||||
data[rom_addresses["Option_Blind_Trainers"]] = round(world.options.blind_trainers.value * 2.55)
|
||||
data[rom_addresses["Option_Cerulean_Cave_Badges"]] = world.options.cerulean_cave_badges_condition.value
|
||||
data[rom_addresses["Option_Cerulean_Cave_Key_Items"]] = world.options.cerulean_cave_key_items_condition.total
|
||||
write_bytes(data, encode_text(str(world.options.cerulean_cave_badges_condition.value)), rom_addresses["Text_Cerulean_Cave_Badges"])
|
||||
write_bytes(data, encode_text(str(world.options.cerulean_cave_key_items_condition.total) + " key items."), rom_addresses["Text_Cerulean_Cave_Key_Items"])
|
||||
data[rom_addresses['Option_Encounter_Minimum_Steps']] = world.options.minimum_steps_between_encounters.value
|
||||
data[rom_addresses['Option_Route23_Badges']] = world.options.victory_road_condition.value
|
||||
data[rom_addresses['Option_Victory_Road_Badges']] = world.options.route_22_gate_condition.value
|
||||
data[rom_addresses['Option_Elite_Four_Pokedex']] = world.options.elite_four_pokedex_condition.total
|
||||
data[rom_addresses['Option_Elite_Four_Key_Items']] = world.options.elite_four_key_items_condition.total
|
||||
data[rom_addresses['Option_Elite_Four_Badges']] = world.options.elite_four_badges_condition.value
|
||||
write_bytes(data, encode_text(str(world.options.elite_four_badges_condition.value)), rom_addresses["Text_Elite_Four_Badges"])
|
||||
write_bytes(data, encode_text(str(world.options.elite_four_key_items_condition.total) + " key items, and"), rom_addresses["Text_Elite_Four_Key_Items"])
|
||||
write_bytes(data, encode_text(str(world.options.elite_four_pokedex_condition.total) + " #MON"), rom_addresses["Text_Elite_Four_Pokedex"])
|
||||
write_bytes(data, encode_text(str(world.total_key_items), length=2), rom_addresses["Trainer_Screen_Total_Key_Items"])
|
||||
|
||||
data[rom_addresses['Option_Viridian_Gym_Badges']] = self.multiworld.viridian_gym_condition[self.player].value
|
||||
data[rom_addresses['Option_EXP_Modifier']] = self.multiworld.exp_modifier[self.player].value
|
||||
if not self.multiworld.require_item_finder[self.player]:
|
||||
data[rom_addresses['Option_Viridian_Gym_Badges']] = world.options.viridian_gym_condition.value
|
||||
data[rom_addresses['Option_EXP_Modifier']] = world.options.exp_modifier.value
|
||||
if not world.options.require_item_finder:
|
||||
data[rom_addresses['Option_Itemfinder']] = 0 # nop
|
||||
if self.multiworld.extra_strength_boulders[self.player]:
|
||||
if world.options.extra_strength_boulders:
|
||||
for i in range(0, 3):
|
||||
data[rom_addresses['Option_Boulders'] + (i * 3)] = 0x15
|
||||
if self.multiworld.extra_key_items[self.player]:
|
||||
if world.options.extra_key_items:
|
||||
for i in range(0, 4):
|
||||
data[rom_addresses['Option_Rock_Tunnel_Extra_Items'] + (i * 3)] = 0x15
|
||||
if self.multiworld.old_man[self.player] == "open_viridian_city":
|
||||
if world.options.old_man == "open_viridian_city":
|
||||
data[rom_addresses['Option_Old_Man']] = 0x11
|
||||
data[rom_addresses['Option_Old_Man_Lying']] = 0x15
|
||||
data[rom_addresses['Option_Route3_Guard_B']] = self.multiworld.route_3_condition[self.player].value
|
||||
if self.multiworld.route_3_condition[self.player] == "open":
|
||||
data[rom_addresses['Option_Route3_Guard_B']] = world.options.route_3_condition.value
|
||||
if world.options.route_3_condition == "open":
|
||||
data[rom_addresses['Option_Route3_Guard_A']] = 0x11
|
||||
if not self.multiworld.robbed_house_officer[self.player]:
|
||||
if not world.options.robbed_house_officer:
|
||||
data[rom_addresses['Option_Trashed_House_Guard_A']] = 0x15
|
||||
data[rom_addresses['Option_Trashed_House_Guard_B']] = 0x11
|
||||
if self.multiworld.require_pokedex[self.player]:
|
||||
if world.options.require_pokedex:
|
||||
data[rom_addresses["Require_Pokedex_A"]] = 1
|
||||
data[rom_addresses["Require_Pokedex_B"]] = 1
|
||||
data[rom_addresses["Require_Pokedex_C"]] = 1
|
||||
else:
|
||||
data[rom_addresses["Require_Pokedex_D"]] = 0x18 # jr
|
||||
if self.multiworld.dexsanity[self.player]:
|
||||
if world.options.dexsanity:
|
||||
data[rom_addresses["Option_Dexsanity_A"]] = 1
|
||||
data[rom_addresses["Option_Dexsanity_B"]] = 1
|
||||
if self.multiworld.all_pokemon_seen[self.player]:
|
||||
if world.options.all_pokemon_seen:
|
||||
data[rom_addresses["Option_Pokedex_Seen"]] = 1
|
||||
money = str(self.multiworld.starting_money[self.player].value).zfill(6)
|
||||
money = str(world.options.starting_money.value).zfill(6)
|
||||
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_Viridian_Gym"]] = encode_text(
|
||||
str(self.multiworld.viridian_gym_condition[self.player].value))[0]
|
||||
str(world.options.viridian_gym_condition.value))[0]
|
||||
data[rom_addresses["Text_Rt23_Badges_A"]] = encode_text(
|
||||
str(self.multiworld.victory_road_condition[self.player].value))[0]
|
||||
str(world.options.victory_road_condition.value))[0]
|
||||
data[rom_addresses["Text_Rt23_Badges_B"]] = encode_text(
|
||||
str(self.multiworld.victory_road_condition[self.player].value))[0]
|
||||
str(world.options.victory_road_condition.value))[0]
|
||||
data[rom_addresses["Text_Rt23_Badges_C"]] = encode_text(
|
||||
str(self.multiworld.victory_road_condition[self.player].value))[0]
|
||||
str(world.options.victory_road_condition.value))[0]
|
||||
data[rom_addresses["Text_Rt23_Badges_D"]] = encode_text(
|
||||
str(self.multiworld.victory_road_condition[self.player].value))[0]
|
||||
str(world.options.victory_road_condition.value))[0]
|
||||
data[rom_addresses["Text_Badges_Needed"]] = encode_text(
|
||||
str(self.multiworld.elite_four_badges_condition[self.player].value))[0]
|
||||
str(world.options.elite_four_badges_condition.value))[0]
|
||||
write_bytes(data, encode_text(
|
||||
" ".join(self.multiworld.get_location("Route 4 Pokemon Center - Pokemon For Sale", self.player).item.name.upper().split()[1:])),
|
||||
" ".join(world.multiworld.get_location("Route 4 Pokemon Center - Pokemon For Sale", world.player).item.name.upper().split()[1:])),
|
||||
rom_addresses["Text_Magikarp_Salesman"])
|
||||
|
||||
if self.multiworld.badges_needed_for_hm_moves[self.player].value == 0:
|
||||
if world.options.badges_needed_for_hm_moves.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:
|
||||
elif world.extra_badges:
|
||||
written_badges = {}
|
||||
for hm_move, badge in self.extra_badges.items():
|
||||
for hm_move, badge in world.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,
|
||||
@@ -427,7 +474,7 @@ def generate_output(self, output_directory: str):
|
||||
write_bytes(data, encode_text("Nothing"), rom_addresses["Badge_Text_" + badge.replace(" ", "_")])
|
||||
|
||||
type_loc = rom_addresses["Type_Chart"]
|
||||
for matchup in self.type_chart:
|
||||
for matchup in world.type_chart:
|
||||
if matchup[2] != 10: # don't needlessly divide damage by 10 and multiply by 10
|
||||
data[type_loc] = poke_data.type_ids[matchup[0]]
|
||||
data[type_loc + 1] = poke_data.type_ids[matchup[1]]
|
||||
@@ -437,52 +484,49 @@ def generate_output(self, output_directory: str):
|
||||
data[type_loc + 1] = 0xFF
|
||||
data[type_loc + 2] = 0xFF
|
||||
|
||||
if self.multiworld.normalize_encounter_chances[self.player].value:
|
||||
if world.options.normalize_encounter_chances.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():
|
||||
for mon, mon_data in world.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 and self.learnsets[mon]:
|
||||
data[address + 1] = world.local_poke_data[mon]["hp"]
|
||||
data[address + 2] = world.local_poke_data[mon]["atk"]
|
||||
data[address + 3] = world.local_poke_data[mon]["def"]
|
||||
data[address + 4] = world.local_poke_data[mon]["spd"]
|
||||
data[address + 5] = world.local_poke_data[mon]["spc"]
|
||||
data[address + 6] = poke_data.type_ids[world.local_poke_data[mon]["type1"]]
|
||||
data[address + 7] = poke_data.type_ids[world.local_poke_data[mon]["type2"]]
|
||||
data[address + 8] = world.local_poke_data[mon]["catch rate"]
|
||||
data[address + 15] = poke_data.moves[world.local_poke_data[mon]["start move 1"]]["id"]
|
||||
data[address + 16] = poke_data.moves[world.local_poke_data[mon]["start move 2"]]["id"]
|
||||
data[address + 17] = poke_data.moves[world.local_poke_data[mon]["start move 3"]]["id"]
|
||||
data[address + 18] = poke_data.moves[world.local_poke_data[mon]["start move 4"]]["id"]
|
||||
write_bytes(data, world.local_poke_data[mon]["tms"], address + 20)
|
||||
if mon in world.learnsets and world.learnsets[mon]:
|
||||
address = rom_addresses["Learnset_" + mon.replace(" ", "")]
|
||||
for i, move in enumerate(self.learnsets[mon]):
|
||||
for i, move in enumerate(world.learnsets[mon]):
|
||||
data[(address + 1) + i * 2] = poke_data.moves[move]["id"]
|
||||
|
||||
data[rom_addresses["Option_Aide_Rt2"]] = self.multiworld.oaks_aide_rt_2[self.player].value
|
||||
data[rom_addresses["Option_Aide_Rt11"]] = self.multiworld.oaks_aide_rt_11[self.player].value
|
||||
data[rom_addresses["Option_Aide_Rt15"]] = self.multiworld.oaks_aide_rt_15[self.player].value
|
||||
data[rom_addresses["Option_Aide_Rt2"]] = world.options.oaks_aide_rt_2.value
|
||||
data[rom_addresses["Option_Aide_Rt11"]] = world.options.oaks_aide_rt_11.value
|
||||
data[rom_addresses["Option_Aide_Rt15"]] = world.options.oaks_aide_rt_15.value
|
||||
|
||||
if self.multiworld.safari_zone_normal_battles[self.player].value == 1:
|
||||
if world.options.safari_zone_normal_battles.value == 1:
|
||||
data[rom_addresses["Option_Safari_Zone_Battle_Type"]] = 255
|
||||
|
||||
if self.multiworld.reusable_tms[self.player].value:
|
||||
if world.options.reusable_tms.value:
|
||||
data[rom_addresses["Option_Reusable_TMs"]] = 0xC9
|
||||
|
||||
for i in range(1, 10):
|
||||
data[rom_addresses[f"Option_Trainersanity{i}"]] = self.multiworld.trainersanity[self.player].value
|
||||
data[rom_addresses["Option_Always_Half_STAB"]] = int(not world.options.same_type_attack_bonus.value)
|
||||
|
||||
data[rom_addresses["Option_Always_Half_STAB"]] = int(not self.multiworld.same_type_attack_bonus[self.player].value)
|
||||
|
||||
if self.multiworld.better_shops[self.player]:
|
||||
if world.options.better_shops:
|
||||
inventory = ["Poke Ball", "Great Ball", "Ultra Ball"]
|
||||
if self.multiworld.better_shops[self.player].value == 2:
|
||||
if world.options.better_shops.value == 2:
|
||||
inventory.append("Master Ball")
|
||||
inventory += ["Potion", "Super Potion", "Hyper Potion", "Max Potion", "Full Restore", "Revive", "Antidote",
|
||||
"Awakening", "Burn Heal", "Ice Heal", "Paralyze Heal", "Full Heal", "Repel", "Super Repel",
|
||||
@@ -492,30 +536,30 @@ def generate_output(self, output_directory: str):
|
||||
shop_data.append(0xFF)
|
||||
for shop in range(1, 11):
|
||||
write_bytes(data, shop_data, rom_addresses[f"Shop{shop}"])
|
||||
if self.multiworld.stonesanity[self.player]:
|
||||
if world.options.stonesanity:
|
||||
write_bytes(data, bytearray([0xFE, 1, item_table["Poke Doll"].id - 172000000, 0xFF]), rom_addresses[f"Shop_Stones"])
|
||||
|
||||
price = str(self.multiworld.master_ball_price[self.player].value).zfill(6)
|
||||
price = str(world.options.master_ball_price.value).zfill(6)
|
||||
price = bytearray([int(price[:2], 16), int(price[2:4], 16), int(price[4:], 16)])
|
||||
write_bytes(data, price, rom_addresses["Price_Master_Ball"]) # Money values in Red and Blue are weird
|
||||
|
||||
for item in reversed(self.multiworld.precollected_items[self.player]):
|
||||
for item in reversed(world.multiworld.precollected_items[world.player]):
|
||||
if data[rom_addresses["Start_Inventory"] + item.code - 172000000] < 255:
|
||||
data[rom_addresses["Start_Inventory"] + item.code - 172000000] += 1
|
||||
|
||||
set_mon_palettes(self, random, data)
|
||||
set_mon_palettes(world, random, data)
|
||||
|
||||
for move_data in self.local_move_data.values():
|
||||
for move_data in world.local_move_data.values():
|
||||
if move_data["id"] == 0:
|
||||
continue
|
||||
address = rom_addresses["Move_Data"] + ((move_data["id"] - 1) * 6)
|
||||
write_bytes(data, bytearray([move_data["id"], move_data["effect"], move_data["power"],
|
||||
poke_data.type_ids[move_data["type"]], round(move_data["accuracy"] * 2.55), move_data["pp"]]), address)
|
||||
|
||||
TM_IDs = bytearray([poke_data.moves[move]["id"] for move in self.local_tms])
|
||||
TM_IDs = bytearray([poke_data.moves[move]["id"] for move in world.local_tms])
|
||||
write_bytes(data, TM_IDs, rom_addresses["TM_Moves"])
|
||||
|
||||
if self.multiworld.randomize_rock_tunnel[self.player]:
|
||||
if world.options.randomize_rock_tunnel:
|
||||
seed = randomize_rock_tunnel(data, random)
|
||||
write_bytes(data, encode_text(f"SEED: <LINE>{seed}"), rom_addresses["Text_Rock_Tunnel_Sign"])
|
||||
|
||||
@@ -524,44 +568,44 @@ def generate_output(self, output_directory: str):
|
||||
data[rom_addresses['Title_Mon_First']] = mons.pop()
|
||||
for mon in range(0, 16):
|
||||
data[rom_addresses['Title_Mons'] + mon] = mons.pop()
|
||||
if self.multiworld.game_version[self.player].value:
|
||||
mons.sort(key=lambda mon: 0 if mon == self.multiworld.get_location("Oak's Lab - Starter 1", self.player).item.name
|
||||
else 1 if mon == self.multiworld.get_location("Oak's Lab - Starter 2", self.player).item.name else
|
||||
2 if mon == self.multiworld.get_location("Oak's Lab - Starter 3", self.player).item.name else 3)
|
||||
if world.options.game_version.value:
|
||||
mons.sort(key=lambda mon: 0 if mon == world.multiworld.get_location("Oak's Lab - Starter 1", world.player).item.name
|
||||
else 1 if mon == world.multiworld.get_location("Oak's Lab - Starter 2", world.player).item.name else
|
||||
2 if mon == world.multiworld.get_location("Oak's Lab - Starter 3", world.player).item.name else 3)
|
||||
else:
|
||||
mons.sort(key=lambda mon: 0 if mon == self.multiworld.get_location("Oak's Lab - Starter 2", self.player).item.name
|
||||
else 1 if mon == self.multiworld.get_location("Oak's Lab - Starter 1", self.player).item.name else
|
||||
2 if mon == self.multiworld.get_location("Oak's Lab - Starter 3", self.player).item.name else 3)
|
||||
write_bytes(data, encode_text(self.multiworld.seed_name[-20:], 20, True), rom_addresses['Title_Seed'])
|
||||
mons.sort(key=lambda mon: 0 if mon == world.multiworld.get_location("Oak's Lab - Starter 2", world.player).item.name
|
||||
else 1 if mon == world.multiworld.get_location("Oak's Lab - Starter 1", world.player).item.name else
|
||||
2 if mon == world.multiworld.get_location("Oak's Lab - Starter 3", world.player).item.name else 3)
|
||||
write_bytes(data, encode_text(world.multiworld.seed_name[-20:], 20, True), rom_addresses['Title_Seed'])
|
||||
|
||||
slot_name = self.multiworld.player_name[self.player]
|
||||
slot_name = world.multiworld.player_name[world.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'])
|
||||
|
||||
if self.trainer_name == "choose_in_game":
|
||||
if world.trainer_name == "choose_in_game":
|
||||
data[rom_addresses["Skip_Player_Name"]] = 0
|
||||
else:
|
||||
write_bytes(data, self.trainer_name, rom_addresses['Player_Name'])
|
||||
if self.rival_name == "choose_in_game":
|
||||
write_bytes(data, world.trainer_name, rom_addresses['Player_Name'])
|
||||
if world.rival_name == "choose_in_game":
|
||||
data[rom_addresses["Skip_Rival_Name"]] = 0
|
||||
else:
|
||||
write_bytes(data, self.rival_name, rom_addresses['Rival_Name'])
|
||||
write_bytes(data, world.rival_name, rom_addresses['Rival_Name'])
|
||||
|
||||
data[0xFF00] = 2 # client compatibility version
|
||||
rom_name = bytearray(f'AP{Utils.__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed:11}\0',
|
||||
rom_name = bytearray(f'AP{Utils.__version__.replace(".", "")[0:3]}_{world.player}_{world.multiworld.seed:11}\0',
|
||||
'utf8')[:21]
|
||||
rom_name.extend([0] * (21 - len(rom_name)))
|
||||
write_bytes(data, rom_name, 0xFFC6)
|
||||
write_bytes(data, self.multiworld.seed_name.encode(), 0xFFDB)
|
||||
write_bytes(data, self.multiworld.player_name[self.player].encode(), 0xFFF0)
|
||||
write_bytes(data, world.multiworld.seed_name.encode(), 0xFFDB)
|
||||
write_bytes(data, world.multiworld.player_name[world.player].encode(), 0xFFF0)
|
||||
|
||||
self.finished_level_scaling.wait()
|
||||
world.finished_level_scaling.wait()
|
||||
|
||||
write_quizzes(self, data, random)
|
||||
write_quizzes(world, data, random)
|
||||
|
||||
for location in self.multiworld.get_locations(self.player):
|
||||
for location in world.multiworld.get_locations(world.player):
|
||||
if location.party_data:
|
||||
for party in location.party_data:
|
||||
if not isinstance(party["party_address"], list):
|
||||
@@ -588,7 +632,7 @@ def generate_output(self, output_directory: str):
|
||||
continue
|
||||
elif location.rom_address is None:
|
||||
continue
|
||||
if location.item and location.item.player == self.player:
|
||||
if location.item and location.item.player == world.player:
|
||||
if location.rom_address:
|
||||
rom_address = location.rom_address
|
||||
if not isinstance(rom_address, list):
|
||||
@@ -599,7 +643,7 @@ def generate_output(self, output_directory: str):
|
||||
elif " ".join(location.item.name.split()[1:]) in poke_data.pokemon_data.keys():
|
||||
data[address] = poke_data.pokemon_data[" ".join(location.item.name.split()[1:])]["id"]
|
||||
else:
|
||||
item_id = self.item_name_to_id[location.item.name] - 172000000
|
||||
item_id = world.item_name_to_id[location.item.name] - 172000000
|
||||
if item_id > 255:
|
||||
item_id -= 256
|
||||
data[address] = item_id
|
||||
@@ -613,18 +657,18 @@ def generate_output(self, output_directory: str):
|
||||
for address in rom_address:
|
||||
data[address] = 0x2C # AP Item
|
||||
|
||||
outfilepname = f'_P{self.player}'
|
||||
outfilepname += f"_{self.multiworld.get_file_safe_player_name(self.player).replace(' ', '_')}" \
|
||||
if self.multiworld.player_name[self.player] != 'Player%d' % self.player else ''
|
||||
rompath = os.path.join(output_directory, f'AP_{self.multiworld.seed_name}{outfilepname}.gb')
|
||||
outfilepname = f'_P{world.player}'
|
||||
outfilepname += f"_{world.multiworld.get_file_safe_player_name(world.player).replace(' ', '_')}" \
|
||||
if world.multiworld.player_name[world.player] != 'Player%d' % world.player else ''
|
||||
rompath = os.path.join(output_directory, f'AP_{world.multiworld.seed_name}{outfilepname}.gb')
|
||||
with open(rompath, 'wb') as outfile:
|
||||
outfile.write(data)
|
||||
if self.multiworld.game_version[self.player].current_key == "red":
|
||||
patch = RedDeltaPatch(os.path.splitext(rompath)[0] + RedDeltaPatch.patch_file_ending, player=self.player,
|
||||
player_name=self.multiworld.player_name[self.player], patched_path=rompath)
|
||||
if world.options.game_version.current_key == "red":
|
||||
patch = RedDeltaPatch(os.path.splitext(rompath)[0] + RedDeltaPatch.patch_file_ending, player=world.player,
|
||||
player_name=world.multiworld.player_name[world.player], patched_path=rompath)
|
||||
else:
|
||||
patch = BlueDeltaPatch(os.path.splitext(rompath)[0] + BlueDeltaPatch.patch_file_ending, player=self.player,
|
||||
player_name=self.multiworld.player_name[self.player], patched_path=rompath)
|
||||
patch = BlueDeltaPatch(os.path.splitext(rompath)[0] + BlueDeltaPatch.patch_file_ending, player=world.player,
|
||||
player_name=world.multiworld.player_name[world.player], patched_path=rompath)
|
||||
|
||||
patch.write()
|
||||
os.unlink(rompath)
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
rom_addresses = {
|
||||
"Option_Encounter_Minimum_Steps": 0x3c1,
|
||||
"Option_Pitch_Black_Rock_Tunnel": 0x76a,
|
||||
"Option_Blind_Trainers": 0x30d5,
|
||||
"Option_Trainersanity1": 0x3165,
|
||||
"Option_Split_Card_Key": 0x3e1e,
|
||||
"Option_Fix_Combat_Bugs": 0x3e1f,
|
||||
"Option_Blind_Trainers": 0x32f0,
|
||||
"Option_Split_Card_Key": 0x3e19,
|
||||
"Option_Fix_Combat_Bugs": 0x3e1a,
|
||||
"Option_Lose_Money": 0x40d4,
|
||||
"Base_Stats_Mew": 0x4260,
|
||||
"Title_Mon_First": 0x4373,
|
||||
@@ -115,9 +114,10 @@ rom_addresses = {
|
||||
"HM_Strength_Badge_b": 0x131ed,
|
||||
"HM_Flash_Badge_a": 0x131fc,
|
||||
"HM_Flash_Badge_b": 0x13201,
|
||||
"Trainer_Screen_Total_Key_Items": 0x135dc,
|
||||
"TM_Moves": 0x137b1,
|
||||
"Encounter_Chances": 0x13950,
|
||||
"Tea_Key_Item_A": 0x135ac,
|
||||
"Trainer_Screen_Total_Key_Items": 0x1361b,
|
||||
"TM_Moves": 0x137f0,
|
||||
"Encounter_Chances": 0x1398f,
|
||||
"Warps_CeladonCity": 0x18026,
|
||||
"Warps_PalletTown": 0x182c7,
|
||||
"Warps_ViridianCity": 0x18388,
|
||||
@@ -128,52 +128,54 @@ rom_addresses = {
|
||||
"Option_Viridian_Gym_Badges": 0x1901d,
|
||||
"Event_Sleepy_Guy": 0x191d1,
|
||||
"Option_Route3_Guard_B": 0x1928a,
|
||||
"Starter2_K": 0x19611,
|
||||
"Starter3_K": 0x19619,
|
||||
"Event_Rocket_Thief": 0x19733,
|
||||
"Option_Cerulean_Cave_Badges": 0x19861,
|
||||
"Option_Cerulean_Cave_Key_Items": 0x19868,
|
||||
"Text_Cerulean_Cave_Badges": 0x198d7,
|
||||
"Text_Cerulean_Cave_Key_Items": 0x198e5,
|
||||
"Event_Stranded_Man": 0x19b3c,
|
||||
"Event_Rivals_Sister": 0x19d0f,
|
||||
"Warps_BluesHouse": 0x19d65,
|
||||
"Warps_VermilionTradeHouse": 0x19dbc,
|
||||
"Require_Pokedex_D": 0x19e53,
|
||||
"Option_Elite_Four_Key_Items": 0x19e9d,
|
||||
"Option_Elite_Four_Pokedex": 0x19ea4,
|
||||
"Option_Elite_Four_Badges": 0x19eab,
|
||||
"Text_Elite_Four_Badges": 0x19f47,
|
||||
"Text_Elite_Four_Key_Items": 0x19f51,
|
||||
"Text_Elite_Four_Pokedex": 0x19f64,
|
||||
"Shop10": 0x1a018,
|
||||
"Warps_IndigoPlateauLobby": 0x1a044,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_0_ITEM": 0x1a16c,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_1_ITEM": 0x1a17a,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_2_ITEM": 0x1a188,
|
||||
"Event_SKC4F": 0x1a19b,
|
||||
"Warps_SilphCo4F": 0x1a21d,
|
||||
"Missable_Silph_Co_4F_Item_1": 0x1a25d,
|
||||
"Missable_Silph_Co_4F_Item_2": 0x1a264,
|
||||
"Missable_Silph_Co_4F_Item_3": 0x1a26b,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_0_ITEM": 0x1a3c3,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_1_ITEM": 0x1a3d1,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_2_ITEM": 0x1a3df,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_3_ITEM": 0x1a3ed,
|
||||
"Event_SKC5F": 0x1a400,
|
||||
"Warps_SilphCo5F": 0x1a4aa,
|
||||
"Missable_Silph_Co_5F_Item_1": 0x1a4f2,
|
||||
"Missable_Silph_Co_5F_Item_2": 0x1a4f9,
|
||||
"Missable_Silph_Co_5F_Item_3": 0x1a500,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_0_ITEM": 0x1a630,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_1_ITEM": 0x1a63e,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_2_ITEM": 0x1a64c,
|
||||
"Event_SKC6F": 0x1a66d,
|
||||
"Warps_SilphCo6F": 0x1a74b,
|
||||
"Missable_Silph_Co_6F_Item_1": 0x1a79b,
|
||||
"Missable_Silph_Co_6F_Item_2": 0x1a7a2,
|
||||
"Path_Pallet_Oak": 0x1a928,
|
||||
"Path_Pallet_Player": 0x1a935,
|
||||
"Starter2_K": 0x19618,
|
||||
"Starter3_K": 0x19620,
|
||||
"Event_Rocket_Thief": 0x1973a,
|
||||
"Tea_Key_Item_C": 0x1988f,
|
||||
"Option_Cerulean_Cave_Badges": 0x198a0,
|
||||
"Option_Cerulean_Cave_Key_Items": 0x198a7,
|
||||
"Text_Cerulean_Cave_Badges": 0x19916,
|
||||
"Text_Cerulean_Cave_Key_Items": 0x19924,
|
||||
"Event_Stranded_Man": 0x19b7b,
|
||||
"Event_Rivals_Sister": 0x19d4e,
|
||||
"Warps_BluesHouse": 0x19da4,
|
||||
"Warps_VermilionTradeHouse": 0x19dfb,
|
||||
"Require_Pokedex_D": 0x19e99,
|
||||
"Tea_Key_Item_B": 0x19f13,
|
||||
"Option_Elite_Four_Key_Items": 0x19f1b,
|
||||
"Option_Elite_Four_Pokedex": 0x19f22,
|
||||
"Option_Elite_Four_Badges": 0x19f29,
|
||||
"Text_Elite_Four_Badges": 0x19fc5,
|
||||
"Text_Elite_Four_Key_Items": 0x19fcf,
|
||||
"Text_Elite_Four_Pokedex": 0x19fe2,
|
||||
"Shop10": 0x1a096,
|
||||
"Warps_IndigoPlateauLobby": 0x1a0c2,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_0_ITEM": 0x1a1ea,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_1_ITEM": 0x1a1f8,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_2_ITEM": 0x1a206,
|
||||
"Event_SKC4F": 0x1a219,
|
||||
"Warps_SilphCo4F": 0x1a29b,
|
||||
"Missable_Silph_Co_4F_Item_1": 0x1a2db,
|
||||
"Missable_Silph_Co_4F_Item_2": 0x1a2e2,
|
||||
"Missable_Silph_Co_4F_Item_3": 0x1a2e9,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_0_ITEM": 0x1a441,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_1_ITEM": 0x1a44f,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_2_ITEM": 0x1a45d,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_3_ITEM": 0x1a46b,
|
||||
"Event_SKC5F": 0x1a47e,
|
||||
"Warps_SilphCo5F": 0x1a528,
|
||||
"Missable_Silph_Co_5F_Item_1": 0x1a570,
|
||||
"Missable_Silph_Co_5F_Item_2": 0x1a577,
|
||||
"Missable_Silph_Co_5F_Item_3": 0x1a57e,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_0_ITEM": 0x1a6ae,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_1_ITEM": 0x1a6bc,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_2_ITEM": 0x1a6ca,
|
||||
"Event_SKC6F": 0x1a6eb,
|
||||
"Warps_SilphCo6F": 0x1a7c9,
|
||||
"Missable_Silph_Co_6F_Item_1": 0x1a819,
|
||||
"Missable_Silph_Co_6F_Item_2": 0x1a820,
|
||||
"Path_Pallet_Oak": 0x1a9a6,
|
||||
"Path_Pallet_Player": 0x1a9b3,
|
||||
"Warps_CinnabarIsland": 0x1c026,
|
||||
"Warps_Route1": 0x1c0e9,
|
||||
"Option_Extra_Key_Items_B": 0x1ca46,
|
||||
@@ -191,75 +193,75 @@ rom_addresses = {
|
||||
"Starter2_E": 0x1d2f7,
|
||||
"Starter3_E": 0x1d2ff,
|
||||
"Event_Pokedex": 0x1d363,
|
||||
"Event_Oaks_Gift": 0x1d393,
|
||||
"Starter2_P": 0x1d481,
|
||||
"Starter3_P": 0x1d489,
|
||||
"Warps_OaksLab": 0x1d6af,
|
||||
"Event_Pokemart_Quest": 0x1d76b,
|
||||
"Shop1": 0x1d795,
|
||||
"Warps_ViridianMart": 0x1d7d8,
|
||||
"Warps_ViridianSchoolHouse": 0x1d82b,
|
||||
"Warps_ViridianNicknameHouse": 0x1d889,
|
||||
"Warps_PewterNidoranHouse": 0x1d8e4,
|
||||
"Warps_PewterSpeechHouse": 0x1d927,
|
||||
"Warps_CeruleanTrashedHouse": 0x1d98d,
|
||||
"Warps_CeruleanTradeHouse": 0x1d9de,
|
||||
"Event_Bicycle_Shop": 0x1da2f,
|
||||
"Bike_Shop_Item_Display": 0x1da8a,
|
||||
"Warps_BikeShop": 0x1db45,
|
||||
"Event_Fuji": 0x1dbfd,
|
||||
"Warps_MrFujisHouse": 0x1dc44,
|
||||
"Warps_LavenderCuboneHouse": 0x1dcc0,
|
||||
"Warps_NameRatersHouse": 0x1ddae,
|
||||
"Warps_VermilionPidgeyHouse": 0x1ddf8,
|
||||
"Trainersanity_EVENT_BEAT_MEW_ITEM": 0x1de4e,
|
||||
"Warps_VermilionDock": 0x1de70,
|
||||
"Static_Encounter_Mew": 0x1de7e,
|
||||
"Gift_Eevee": 0x1def7,
|
||||
"Warps_CeladonMansionRoofHouse": 0x1df0e,
|
||||
"Shop7": 0x1df49,
|
||||
"Warps_FuchsiaMart": 0x1df74,
|
||||
"Warps_SaffronPidgeyHouse": 0x1dfdd,
|
||||
"Event_Mr_Psychic": 0x1e020,
|
||||
"Warps_MrPsychicsHouse": 0x1e05d,
|
||||
"Warps_DiglettsCaveRoute2": 0x1e092,
|
||||
"Warps_Route2TradeHouse": 0x1e0da,
|
||||
"Warps_Route5Gate": 0x1e1db,
|
||||
"Warps_Route6Gate": 0x1e2ad,
|
||||
"Warps_Route7Gate": 0x1e383,
|
||||
"Warps_Route8Gate": 0x1e454,
|
||||
"Warps_UndergroundPathRoute8": 0x1e4a5,
|
||||
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_0_ITEM": 0x1e511,
|
||||
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_1_ITEM": 0x1e51f,
|
||||
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_2_ITEM": 0x1e52d,
|
||||
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_3_ITEM": 0x1e53b,
|
||||
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_4_ITEM": 0x1e549,
|
||||
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_5_ITEM": 0x1e557,
|
||||
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_6_ITEM": 0x1e565,
|
||||
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_7_ITEM": 0x1e573,
|
||||
"Trainersanity_EVENT_BEAT_ZAPDOS_ITEM": 0x1e581,
|
||||
"Warps_PowerPlant": 0x1e5de,
|
||||
"Static_Encounter_Voltorb_A": 0x1e5f0,
|
||||
"Static_Encounter_Voltorb_B": 0x1e5f8,
|
||||
"Static_Encounter_Voltorb_C": 0x1e600,
|
||||
"Static_Encounter_Electrode_A": 0x1e608,
|
||||
"Static_Encounter_Voltorb_D": 0x1e610,
|
||||
"Static_Encounter_Voltorb_E": 0x1e618,
|
||||
"Static_Encounter_Electrode_B": 0x1e620,
|
||||
"Static_Encounter_Voltorb_F": 0x1e628,
|
||||
"Static_Encounter_Zapdos": 0x1e630,
|
||||
"Missable_Power_Plant_Item_1": 0x1e638,
|
||||
"Missable_Power_Plant_Item_2": 0x1e63f,
|
||||
"Missable_Power_Plant_Item_3": 0x1e646,
|
||||
"Missable_Power_Plant_Item_4": 0x1e64d,
|
||||
"Missable_Power_Plant_Item_5": 0x1e654,
|
||||
"Warps_DiglettsCaveRoute11": 0x1e7e9,
|
||||
"Event_Rt16_House_Woman": 0x1e827,
|
||||
"Warps_Route16FlyHouse": 0x1e870,
|
||||
"Option_Victory_Road_Badges": 0x1e8f3,
|
||||
"Warps_Route22Gate": 0x1e9e3,
|
||||
"Event_Bill": 0x1eb24,
|
||||
"Warps_BillsHouse": 0x1eb83,
|
||||
"Event_Oaks_Gift": 0x1d398,
|
||||
"Starter2_P": 0x1d486,
|
||||
"Starter3_P": 0x1d48e,
|
||||
"Warps_OaksLab": 0x1d6b4,
|
||||
"Event_Pokemart_Quest": 0x1d770,
|
||||
"Shop1": 0x1d79a,
|
||||
"Warps_ViridianMart": 0x1d7dd,
|
||||
"Warps_ViridianSchoolHouse": 0x1d830,
|
||||
"Warps_ViridianNicknameHouse": 0x1d88e,
|
||||
"Warps_PewterNidoranHouse": 0x1d8e9,
|
||||
"Warps_PewterSpeechHouse": 0x1d92c,
|
||||
"Warps_CeruleanTrashedHouse": 0x1d992,
|
||||
"Warps_CeruleanTradeHouse": 0x1d9e3,
|
||||
"Event_Bicycle_Shop": 0x1da34,
|
||||
"Bike_Shop_Item_Display": 0x1da8f,
|
||||
"Warps_BikeShop": 0x1db4a,
|
||||
"Event_Fuji": 0x1dc02,
|
||||
"Warps_MrFujisHouse": 0x1dc49,
|
||||
"Warps_LavenderCuboneHouse": 0x1dcc5,
|
||||
"Warps_NameRatersHouse": 0x1ddb3,
|
||||
"Warps_VermilionPidgeyHouse": 0x1ddfd,
|
||||
"Trainersanity_EVENT_BEAT_MEW_ITEM": 0x1de53,
|
||||
"Warps_VermilionDock": 0x1de75,
|
||||
"Static_Encounter_Mew": 0x1de83,
|
||||
"Gift_Eevee": 0x1defc,
|
||||
"Warps_CeladonMansionRoofHouse": 0x1df13,
|
||||
"Shop7": 0x1df4e,
|
||||
"Warps_FuchsiaMart": 0x1df79,
|
||||
"Warps_SaffronPidgeyHouse": 0x1dfe2,
|
||||
"Event_Mr_Psychic": 0x1e025,
|
||||
"Warps_MrPsychicsHouse": 0x1e062,
|
||||
"Warps_DiglettsCaveRoute2": 0x1e097,
|
||||
"Warps_Route2TradeHouse": 0x1e0df,
|
||||
"Warps_Route5Gate": 0x1e1e0,
|
||||
"Warps_Route6Gate": 0x1e2b2,
|
||||
"Warps_Route7Gate": 0x1e388,
|
||||
"Warps_Route8Gate": 0x1e459,
|
||||
"Warps_UndergroundPathRoute8": 0x1e4aa,
|
||||
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_0_ITEM": 0x1e516,
|
||||
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_1_ITEM": 0x1e524,
|
||||
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_2_ITEM": 0x1e532,
|
||||
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_3_ITEM": 0x1e540,
|
||||
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_4_ITEM": 0x1e54e,
|
||||
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_5_ITEM": 0x1e55c,
|
||||
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_6_ITEM": 0x1e56a,
|
||||
"Trainersanity_EVENT_BEAT_POWER_PLANT_VOLTORB_7_ITEM": 0x1e578,
|
||||
"Trainersanity_EVENT_BEAT_ZAPDOS_ITEM": 0x1e586,
|
||||
"Warps_PowerPlant": 0x1e5e3,
|
||||
"Static_Encounter_Voltorb_A": 0x1e5f5,
|
||||
"Static_Encounter_Voltorb_B": 0x1e5fd,
|
||||
"Static_Encounter_Voltorb_C": 0x1e605,
|
||||
"Static_Encounter_Electrode_A": 0x1e60d,
|
||||
"Static_Encounter_Voltorb_D": 0x1e615,
|
||||
"Static_Encounter_Voltorb_E": 0x1e61d,
|
||||
"Static_Encounter_Electrode_B": 0x1e625,
|
||||
"Static_Encounter_Voltorb_F": 0x1e62d,
|
||||
"Static_Encounter_Zapdos": 0x1e635,
|
||||
"Missable_Power_Plant_Item_1": 0x1e63d,
|
||||
"Missable_Power_Plant_Item_2": 0x1e644,
|
||||
"Missable_Power_Plant_Item_3": 0x1e64b,
|
||||
"Missable_Power_Plant_Item_4": 0x1e652,
|
||||
"Missable_Power_Plant_Item_5": 0x1e659,
|
||||
"Warps_DiglettsCaveRoute11": 0x1e7ee,
|
||||
"Event_Rt16_House_Woman": 0x1e82c,
|
||||
"Warps_Route16FlyHouse": 0x1e875,
|
||||
"Option_Victory_Road_Badges": 0x1e8f8,
|
||||
"Warps_Route22Gate": 0x1e9e8,
|
||||
"Event_Bill": 0x1eb29,
|
||||
"Warps_BillsHouse": 0x1eb88,
|
||||
"Starter1_O": 0x372b0,
|
||||
"Starter2_O": 0x372b4,
|
||||
"Starter3_O": 0x372b8,
|
||||
@@ -1470,74 +1472,73 @@ rom_addresses = {
|
||||
"Trainersanity_EVENT_BEAT_POKEMONTOWER_5_TRAINER_3_ITEM": 0x609ea,
|
||||
"Warps_PokemonTower5F": 0x60a5e,
|
||||
"Missable_Pokemon_Tower_5F_Item": 0x60a92,
|
||||
"Option_Trainersanity2": 0x60b2a,
|
||||
"Ghost_Battle1": 0x60b83,
|
||||
"Ghost_Battle_Level": 0x60b88,
|
||||
"Trainersanity_EVENT_BEAT_POKEMONTOWER_6_TRAINER_0_ITEM": 0x60c25,
|
||||
"Trainersanity_EVENT_BEAT_POKEMONTOWER_6_TRAINER_1_ITEM": 0x60c33,
|
||||
"Trainersanity_EVENT_BEAT_POKEMONTOWER_6_TRAINER_2_ITEM": 0x60c41,
|
||||
"Ghost_Battle2": 0x60c69,
|
||||
"Warps_PokemonTower6F": 0x60cbe,
|
||||
"Missable_Pokemon_Tower_6F_Item_1": 0x60ce4,
|
||||
"Missable_Pokemon_Tower_6F_Item_2": 0x60ceb,
|
||||
"Entrance_Shuffle_Fuji_Warp": 0x60deb,
|
||||
"Trainersanity_EVENT_BEAT_POKEMONTOWER_7_TRAINER_0_ITEM": 0x60edf,
|
||||
"Trainersanity_EVENT_BEAT_POKEMONTOWER_7_TRAINER_1_ITEM": 0x60eed,
|
||||
"Trainersanity_EVENT_BEAT_POKEMONTOWER_7_TRAINER_2_ITEM": 0x60efb,
|
||||
"Warps_PokemonTower7F": 0x60f8b,
|
||||
"Warps_CeladonMart1F": 0x61033,
|
||||
"Gift_Aerodactyl": 0x610f5,
|
||||
"Gift_Omanyte": 0x610f9,
|
||||
"Gift_Kabuto": 0x610fd,
|
||||
"Trainersanity_EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_0_ITEM": 0x611de,
|
||||
"Trainersanity_EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_1_ITEM": 0x611ec,
|
||||
"Trainersanity_EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_2_ITEM": 0x611fa,
|
||||
"Warps_ViridianForest": 0x61273,
|
||||
"Missable_Viridian_Forest_Item_1": 0x612c1,
|
||||
"Missable_Viridian_Forest_Item_2": 0x612c8,
|
||||
"Missable_Viridian_Forest_Item_3": 0x612cf,
|
||||
"Warps_SSAnne1F": 0x61310,
|
||||
"Starter2_M": 0x614e5,
|
||||
"Starter3_M": 0x614ed,
|
||||
"Warps_SSAnne2F": 0x615ab,
|
||||
"Warps_SSAnneB1F": 0x616c9,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_5_TRAINER_0_ITEM": 0x61771,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_5_TRAINER_1_ITEM": 0x6177f,
|
||||
"Warps_SSAnneBow": 0x617c6,
|
||||
"Warps_SSAnneKitchen": 0x618b6,
|
||||
"Event_SS_Anne_Captain": 0x6194e,
|
||||
"Warps_SSAnneCaptainsRoom": 0x619d5,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_8_TRAINER_0_ITEM": 0x61a3d,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_8_TRAINER_1_ITEM": 0x61a4b,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_8_TRAINER_2_ITEM": 0x61a59,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_8_TRAINER_3_ITEM": 0x61a67,
|
||||
"Warps_SSAnne1FRooms": 0x61af7,
|
||||
"Missable_SS_Anne_1F_Item": 0x61b53,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_9_TRAINER_0_ITEM": 0x61c24,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_9_TRAINER_1_ITEM": 0x61c32,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_9_TRAINER_2_ITEM": 0x61c40,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_9_TRAINER_3_ITEM": 0x61c4e,
|
||||
"Warps_SSAnne2FRooms": 0x61d2c,
|
||||
"Missable_SS_Anne_2F_Item_1": 0x61d88,
|
||||
"Missable_SS_Anne_2F_Item_2": 0x61d9b,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_0_ITEM": 0x61e2c,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_1_ITEM": 0x61e3a,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_2_ITEM": 0x61e48,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_3_ITEM": 0x61e56,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_4_ITEM": 0x61e64,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_5_ITEM": 0x61e72,
|
||||
"Warps_SSAnneB1FRooms": 0x61f20,
|
||||
"Missable_SS_Anne_B1F_Item_1": 0x61f8a,
|
||||
"Missable_SS_Anne_B1F_Item_2": 0x61f91,
|
||||
"Missable_SS_Anne_B1F_Item_3": 0x61f98,
|
||||
"Warps_UndergroundPathNorthSouth": 0x61fd5,
|
||||
"Warps_UndergroundPathWestEast": 0x61ff9,
|
||||
"Warps_DiglettsCave": 0x6201d,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_11F_TRAINER_0_ITEM": 0x62358,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_11F_TRAINER_1_ITEM": 0x62366,
|
||||
"Event_Silph_Co_President": 0x62373,
|
||||
"Event_SKC11F": 0x623bd,
|
||||
"Warps_SilphCo11F": 0x62446,
|
||||
"Ghost_Battle1": 0x60b93,
|
||||
"Ghost_Battle_Level": 0x60b98,
|
||||
"Trainersanity_EVENT_BEAT_POKEMONTOWER_6_TRAINER_0_ITEM": 0x60c35,
|
||||
"Trainersanity_EVENT_BEAT_POKEMONTOWER_6_TRAINER_1_ITEM": 0x60c43,
|
||||
"Trainersanity_EVENT_BEAT_POKEMONTOWER_6_TRAINER_2_ITEM": 0x60c51,
|
||||
"Ghost_Battle2": 0x60c79,
|
||||
"Warps_PokemonTower6F": 0x60cce,
|
||||
"Missable_Pokemon_Tower_6F_Item_1": 0x60cf4,
|
||||
"Missable_Pokemon_Tower_6F_Item_2": 0x60cfb,
|
||||
"Entrance_Shuffle_Fuji_Warp": 0x60dfb,
|
||||
"Trainersanity_EVENT_BEAT_POKEMONTOWER_7_TRAINER_0_ITEM": 0x60eef,
|
||||
"Trainersanity_EVENT_BEAT_POKEMONTOWER_7_TRAINER_1_ITEM": 0x60efd,
|
||||
"Trainersanity_EVENT_BEAT_POKEMONTOWER_7_TRAINER_2_ITEM": 0x60f0b,
|
||||
"Warps_PokemonTower7F": 0x60f9b,
|
||||
"Warps_CeladonMart1F": 0x61043,
|
||||
"Gift_Aerodactyl": 0x61105,
|
||||
"Gift_Omanyte": 0x61109,
|
||||
"Gift_Kabuto": 0x6110d,
|
||||
"Trainersanity_EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_0_ITEM": 0x61209,
|
||||
"Trainersanity_EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_1_ITEM": 0x61217,
|
||||
"Trainersanity_EVENT_BEAT_VIRIDIAN_FOREST_TRAINER_2_ITEM": 0x61225,
|
||||
"Warps_ViridianForest": 0x6129e,
|
||||
"Missable_Viridian_Forest_Item_1": 0x612ec,
|
||||
"Missable_Viridian_Forest_Item_2": 0x612f3,
|
||||
"Missable_Viridian_Forest_Item_3": 0x612fa,
|
||||
"Warps_SSAnne1F": 0x6133b,
|
||||
"Starter2_M": 0x61510,
|
||||
"Starter3_M": 0x61518,
|
||||
"Warps_SSAnne2F": 0x615d6,
|
||||
"Warps_SSAnneB1F": 0x616f4,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_5_TRAINER_0_ITEM": 0x6179c,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_5_TRAINER_1_ITEM": 0x617aa,
|
||||
"Warps_SSAnneBow": 0x617f1,
|
||||
"Warps_SSAnneKitchen": 0x618e1,
|
||||
"Event_SS_Anne_Captain": 0x61979,
|
||||
"Warps_SSAnneCaptainsRoom": 0x61a00,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_8_TRAINER_0_ITEM": 0x61a68,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_8_TRAINER_1_ITEM": 0x61a76,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_8_TRAINER_2_ITEM": 0x61a84,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_8_TRAINER_3_ITEM": 0x61a92,
|
||||
"Warps_SSAnne1FRooms": 0x61b22,
|
||||
"Missable_SS_Anne_1F_Item": 0x61b7e,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_9_TRAINER_0_ITEM": 0x61c4f,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_9_TRAINER_1_ITEM": 0x61c5d,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_9_TRAINER_2_ITEM": 0x61c6b,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_9_TRAINER_3_ITEM": 0x61c79,
|
||||
"Warps_SSAnne2FRooms": 0x61d57,
|
||||
"Missable_SS_Anne_2F_Item_1": 0x61db3,
|
||||
"Missable_SS_Anne_2F_Item_2": 0x61dc6,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_0_ITEM": 0x61e57,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_1_ITEM": 0x61e65,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_2_ITEM": 0x61e73,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_3_ITEM": 0x61e81,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_4_ITEM": 0x61e8f,
|
||||
"Trainersanity_EVENT_BEAT_SS_ANNE_10_TRAINER_5_ITEM": 0x61e9d,
|
||||
"Warps_SSAnneB1FRooms": 0x61f4b,
|
||||
"Missable_SS_Anne_B1F_Item_1": 0x61fb5,
|
||||
"Missable_SS_Anne_B1F_Item_2": 0x61fbc,
|
||||
"Missable_SS_Anne_B1F_Item_3": 0x61fc3,
|
||||
"Warps_UndergroundPathNorthSouth": 0x62000,
|
||||
"Warps_UndergroundPathWestEast": 0x62024,
|
||||
"Warps_DiglettsCave": 0x62048,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_11F_TRAINER_0_ITEM": 0x62383,
|
||||
"Trainersanity_EVENT_BEAT_SILPH_CO_11F_TRAINER_1_ITEM": 0x62391,
|
||||
"Event_Silph_Co_President": 0x6239e,
|
||||
"Event_SKC11F": 0x623e8,
|
||||
"Warps_SilphCo11F": 0x62471,
|
||||
"Ghost_Battle4": 0x708e1,
|
||||
"Town_Map_Order": 0x70f0f,
|
||||
"Town_Map_Coords": 0x71381,
|
||||
@@ -1589,44 +1590,37 @@ rom_addresses = {
|
||||
"Warps_FuchsiaMeetingRoom": 0x75879,
|
||||
"Badge_Cinnabar_Gym": 0x759de,
|
||||
"Event_Cinnabar_Gym": 0x759f2,
|
||||
"Option_Trainersanity4": 0x75ace,
|
||||
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_B_ITEM": 0x75ada,
|
||||
"Option_Trainersanity3": 0x75b1e,
|
||||
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_A_ITEM": 0x75b2a,
|
||||
"Option_Trainersanity5": 0x75b85,
|
||||
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_2_ITEM": 0x75b91,
|
||||
"Option_Trainersanity6": 0x75bd5,
|
||||
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_3_ITEM": 0x75be1,
|
||||
"Option_Trainersanity7": 0x75c25,
|
||||
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_4_ITEM": 0x75c31,
|
||||
"Option_Trainersanity8": 0x75c75,
|
||||
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_5_ITEM": 0x75c81,
|
||||
"Option_Trainersanity9": 0x75cc5,
|
||||
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_6_ITEM": 0x75cd1,
|
||||
"Warps_CinnabarGym": 0x75d1b,
|
||||
"Warps_CinnabarLab": 0x75e02,
|
||||
"Warps_CinnabarLabTradeRoom": 0x75e94,
|
||||
"Event_Lab_Scientist": 0x75ee9,
|
||||
"Warps_CinnabarLabMetronomeRoom": 0x75f35,
|
||||
"Fossils_Needed_For_Second_Item": 0x75fb6,
|
||||
"Fossil_Level": 0x76017,
|
||||
"Event_Dome_Fossil_B": 0x76031,
|
||||
"Event_Helix_Fossil_B": 0x76051,
|
||||
"Warps_CinnabarLabFossilRoom": 0x760d2,
|
||||
"Warps_CinnabarPokecenter": 0x76128,
|
||||
"Shop8": 0x7616f,
|
||||
"Warps_CinnabarMart": 0x7619b,
|
||||
"Warps_CopycatsHouse1F": 0x761ed,
|
||||
"Starter2_N": 0x762a2,
|
||||
"Starter3_N": 0x762aa,
|
||||
"Warps_ChampionsRoom": 0x764d5,
|
||||
"Trainersanity_EVENT_BEAT_LORELEIS_ROOM_TRAINER_0_ITEM": 0x76604,
|
||||
"Warps_LoreleisRoom": 0x76628,
|
||||
"Trainersanity_EVENT_BEAT_BRUNOS_ROOM_TRAINER_0_ITEM": 0x7675d,
|
||||
"Warps_BrunosRoom": 0x76781,
|
||||
"Trainersanity_EVENT_BEAT_AGATHAS_ROOM_TRAINER_0_ITEM": 0x768bc,
|
||||
"Warps_AgathasRoom": 0x768e0,
|
||||
"Option_Itemfinder": 0x76a33,
|
||||
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_B_ITEM": 0x75adc,
|
||||
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_A_ITEM": 0x75b2e,
|
||||
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_2_ITEM": 0x75b97,
|
||||
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_3_ITEM": 0x75be9,
|
||||
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_4_ITEM": 0x75c3b,
|
||||
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_5_ITEM": 0x75c8d,
|
||||
"Trainersanity_EVENT_BEAT_CINNABAR_GYM_TRAINER_6_ITEM": 0x75cdf,
|
||||
"Warps_CinnabarGym": 0x75d29,
|
||||
"Warps_CinnabarLab": 0x75e10,
|
||||
"Warps_CinnabarLabTradeRoom": 0x75ea2,
|
||||
"Event_Lab_Scientist": 0x75ef7,
|
||||
"Warps_CinnabarLabMetronomeRoom": 0x75f43,
|
||||
"Fossils_Needed_For_Second_Item": 0x75fc4,
|
||||
"Fossil_Level": 0x76025,
|
||||
"Event_Dome_Fossil_B": 0x7603f,
|
||||
"Event_Helix_Fossil_B": 0x7605f,
|
||||
"Warps_CinnabarLabFossilRoom": 0x760e0,
|
||||
"Warps_CinnabarPokecenter": 0x76136,
|
||||
"Shop8": 0x7617d,
|
||||
"Warps_CinnabarMart": 0x761a9,
|
||||
"Warps_CopycatsHouse1F": 0x761fb,
|
||||
"Starter2_N": 0x762b0,
|
||||
"Starter3_N": 0x762b8,
|
||||
"Warps_ChampionsRoom": 0x764e3,
|
||||
"Trainersanity_EVENT_BEAT_LORELEIS_ROOM_TRAINER_0_ITEM": 0x76612,
|
||||
"Warps_LoreleisRoom": 0x76636,
|
||||
"Trainersanity_EVENT_BEAT_BRUNOS_ROOM_TRAINER_0_ITEM": 0x7676b,
|
||||
"Warps_BrunosRoom": 0x7678f,
|
||||
"Trainersanity_EVENT_BEAT_AGATHAS_ROOM_TRAINER_0_ITEM": 0x768ca,
|
||||
"Warps_AgathasRoom": 0x768ee,
|
||||
"Option_Itemfinder": 0x76a41,
|
||||
"Text_Quiz_A": 0x88806,
|
||||
"Text_Quiz_B": 0x8893a,
|
||||
"Text_Quiz_C": 0x88a6e,
|
||||
|
||||
@@ -3,7 +3,7 @@ from .items import item_groups
|
||||
from . import logic
|
||||
|
||||
|
||||
def set_rules(multiworld, player):
|
||||
def set_rules(multiworld, world, player):
|
||||
|
||||
item_rules = {
|
||||
# Some items do special things when they are passed into the GiveItem function in the game, but
|
||||
@@ -15,54 +15,46 @@ def set_rules(multiworld, player):
|
||||
not in i.name)
|
||||
}
|
||||
|
||||
if multiworld.prizesanity[player]:
|
||||
if world.options.prizesanity:
|
||||
def prize_rule(i):
|
||||
return i.player != player or i.name in item_groups["Unique"]
|
||||
item_rules["Celadon Prize Corner - Item Prize 1"] = prize_rule
|
||||
item_rules["Celadon Prize Corner - Item Prize 2"] = prize_rule
|
||||
item_rules["Celadon Prize Corner - Item Prize 3"] = prize_rule
|
||||
|
||||
if multiworld.accessibility[player] != "full":
|
||||
multiworld.get_location("Cerulean Bicycle Shop", player).always_allow = (lambda state, item:
|
||||
item.name == "Bike Voucher"
|
||||
and item.player == player)
|
||||
multiworld.get_location("Fuchsia Warden's House - Safari Zone Warden", player).always_allow = (lambda state, item:
|
||||
item.name == "Gold Teeth" and
|
||||
item.player == player)
|
||||
|
||||
access_rules = {
|
||||
"Rival's House - Rival's Sister": lambda state: state.has("Oak's Parcel", player),
|
||||
"Oak's Lab - Oak's Post-Route-22-Rival Gift": lambda state: state.has("Oak's Parcel", player),
|
||||
"Viridian City - Sleepy Guy": lambda state: logic.can_cut(state, player) or logic.can_surf(state, player),
|
||||
"Route 2 Gate - Oak's Aide": lambda state: logic.oaks_aide(state, state.multiworld.oaks_aide_rt_2[player].value + 5, player),
|
||||
"Viridian City - Sleepy Guy": lambda state: logic.can_cut(state, world, player) or logic.can_surf(state, world, player),
|
||||
"Route 2 Gate - Oak's Aide": lambda state: logic.oaks_aide(state, world, world.options.oaks_aide_rt_2.value + 5, player),
|
||||
"Cerulean Bicycle Shop": lambda state: state.has("Bike Voucher", player)
|
||||
or location_item_name(state, "Cerulean Bicycle Shop", player) == ("Bike Voucher", player),
|
||||
"Lavender Mr. Fuji's House - Mr. Fuji": lambda state: state.has("Fuji Saved", player),
|
||||
"Route 11 Gate 2F - Oak's Aide": lambda state: logic.oaks_aide(state, state.multiworld.oaks_aide_rt_11[player].value + 5, player),
|
||||
"Celadon City - Stranded Man": lambda state: logic.can_surf(state, player),
|
||||
"Route 11 Gate 2F - Oak's Aide": lambda state: logic.oaks_aide(state, world, world.options.oaks_aide_rt_11.value + 5, player),
|
||||
"Celadon City - Stranded Man": lambda state: logic.can_surf(state, world, player),
|
||||
"Fuchsia Warden's House - Safari Zone Warden": lambda state: state.has("Gold Teeth", player)
|
||||
or location_item_name(state, "Fuchsia Warden's House - Safari Zone Warden", player) == ("Gold Teeth", player),
|
||||
"Route 12 - Island Item": lambda state: logic.can_surf(state, player),
|
||||
"Route 15 Gate 2F - Oak's Aide": lambda state: logic.oaks_aide(state, state.multiworld.oaks_aide_rt_15[player].value + 5, player),
|
||||
"Route 25 - Item": lambda state: logic.can_cut(state, player),
|
||||
"Fuchsia Warden's House - Behind Boulder Item": lambda state: logic.can_strength(state, player),
|
||||
"Safari Zone Center - Island Item": lambda state: logic.can_surf(state, player),
|
||||
"Route 12 - Island Item": lambda state: logic.can_surf(state, world, player),
|
||||
"Route 15 Gate 2F - Oak's Aide": lambda state: logic.oaks_aide(state, world, world.options.oaks_aide_rt_15.value + 5, player),
|
||||
"Route 25 - Item": lambda state: logic.can_cut(state, world, player),
|
||||
"Fuchsia Warden's House - Behind Boulder Item": lambda state: logic.can_strength(state, world, player),
|
||||
"Safari Zone Center - Island Item": lambda state: logic.can_surf(state, world, player),
|
||||
"Saffron Copycat's House 2F - Copycat": lambda state: state.has("Buy Poke Doll", player),
|
||||
|
||||
"Celadon Game Corner - West Gambler's Gift": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Game Corner - Center Gambler's Gift": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Game Corner - East Gambler's Gift": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Game Corner - Hidden Item Northwest By Counter": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player),
|
||||
"Celadon Game Corner - Hidden Item Southwest Corner": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player),
|
||||
"Celadon Game Corner - Hidden Item Near Rumor Man": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player),
|
||||
"Celadon Game Corner - Hidden Item Near Speculating Woman": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player),
|
||||
"Celadon Game Corner - Hidden Item Near West Gifting Gambler": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player),
|
||||
"Celadon Game Corner - Hidden Item Near Wonderful Time Woman": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player),
|
||||
"Celadon Game Corner - Hidden Item Near Failing Gym Information Guy": lambda state: state.has( "Coin Case", player) and logic.can_get_hidden_items(state, player),
|
||||
"Celadon Game Corner - Hidden Item Near East Gifting Gambler": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player),
|
||||
"Celadon Game Corner - Hidden Item Near Hooked Guy": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player),
|
||||
"Celadon Game Corner - Hidden Item at End of Horizontal Machine Row": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player),
|
||||
"Celadon Game Corner - Hidden Item in Front of Horizontal Machine Row": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, player),
|
||||
"Celadon Game Corner - Hidden Item Northwest By Counter": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
|
||||
"Celadon Game Corner - Hidden Item Southwest Corner": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
|
||||
"Celadon Game Corner - Hidden Item Near Rumor Man": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
|
||||
"Celadon Game Corner - Hidden Item Near Speculating Woman": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
|
||||
"Celadon Game Corner - Hidden Item Near West Gifting Gambler": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
|
||||
"Celadon Game Corner - Hidden Item Near Wonderful Time Woman": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
|
||||
"Celadon Game Corner - Hidden Item Near Failing Gym Information Guy": lambda state: state.has( "Coin Case", player) and logic.can_get_hidden_items(state, world, player),
|
||||
"Celadon Game Corner - Hidden Item Near East Gifting Gambler": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
|
||||
"Celadon Game Corner - Hidden Item Near Hooked Guy": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
|
||||
"Celadon Game Corner - Hidden Item at End of Horizontal Machine Row": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
|
||||
"Celadon Game Corner - Hidden Item in Front of Horizontal Machine Row": lambda state: state.has("Coin Case", player) and logic.can_get_hidden_items(state, world, player),
|
||||
|
||||
"Celadon Prize Corner - Item Prize 1": lambda state: state.has("Coin Case", player) and state.has("Game Corner", player),
|
||||
"Celadon Prize Corner - Item Prize 2": lambda state: state.has("Coin Case", player) and state.has("Game Corner", player),
|
||||
@@ -79,9 +71,9 @@ def set_rules(multiworld, player):
|
||||
"Cinnabar Lab Fossil Room - Dome Fossil Pokemon": lambda state: state.has("Dome Fossil", player) and state.has("Cinnabar Island", player),
|
||||
"Route 12 - Sleeping Pokemon": lambda state: state.has("Poke Flute", player),
|
||||
"Route 16 - Sleeping Pokemon": lambda state: state.has("Poke Flute", player),
|
||||
"Seafoam Islands B4F - Legendary Pokemon": lambda state: logic.can_strength(state, player) and state.has("Seafoam Boss Boulders", player),
|
||||
"Vermilion Dock - Legendary Pokemon": lambda state: logic.can_surf(state, player),
|
||||
"Cerulean Cave B1F - Legendary Pokemon": lambda state: logic.can_surf(state, player),
|
||||
"Seafoam Islands B4F - Legendary Pokemon": lambda state: logic.can_strength(state, world, player) and state.has("Seafoam Boss Boulders", player),
|
||||
"Vermilion Dock - Legendary Pokemon": lambda state: logic.can_surf(state, world, player),
|
||||
"Cerulean Cave B1F - Legendary Pokemon": lambda state: logic.can_surf(state, world, player),
|
||||
|
||||
**{f"Pokemon Tower {floor}F - Wild Pokemon - {slot}": lambda state: state.has("Silph Scope", player) for floor in range(3, 8) for slot in range(1, 11)},
|
||||
"Pokemon Tower 6F - Restless Soul": lambda state: state.has("Silph Scope", player), # just for level scaling
|
||||
@@ -103,101 +95,101 @@ def set_rules(multiworld, player):
|
||||
"Route 22 - Trainer Parties": lambda state: state.has("Oak's Parcel", player),
|
||||
|
||||
# # Rock Tunnel
|
||||
"Rock Tunnel 1F - PokeManiac": lambda state: logic.rock_tunnel(state, player),
|
||||
"Rock Tunnel 1F - Hiker 1": lambda state: logic.rock_tunnel(state, player),
|
||||
"Rock Tunnel 1F - Hiker 2": lambda state: logic.rock_tunnel(state, player),
|
||||
"Rock Tunnel 1F - Hiker 3": lambda state: logic.rock_tunnel(state, player),
|
||||
"Rock Tunnel 1F - Jr. Trainer F 1": lambda state: logic.rock_tunnel(state, player),
|
||||
"Rock Tunnel 1F - Jr. Trainer F 2": lambda state: logic.rock_tunnel(state, player),
|
||||
"Rock Tunnel 1F - Jr. Trainer F 3": lambda state: logic.rock_tunnel(state, player),
|
||||
"Rock Tunnel B1F - PokeManiac 1": lambda state: logic.rock_tunnel(state, player),
|
||||
"Rock Tunnel B1F - PokeManiac 2": lambda state: logic.rock_tunnel(state, player),
|
||||
"Rock Tunnel B1F - PokeManiac 3": lambda state: logic.rock_tunnel(state, player),
|
||||
"Rock Tunnel B1F - Jr. Trainer F 1": lambda state: logic.rock_tunnel(state, player),
|
||||
"Rock Tunnel B1F - Jr. Trainer F 2": lambda state: logic.rock_tunnel(state, player),
|
||||
"Rock Tunnel B1F - Hiker 1": lambda state: logic.rock_tunnel(state, player),
|
||||
"Rock Tunnel B1F - Hiker 2": lambda state: logic.rock_tunnel(state, player),
|
||||
"Rock Tunnel B1F - Hiker 3": lambda state: logic.rock_tunnel(state, player),
|
||||
"Rock Tunnel B1F - North Item": lambda state: logic.rock_tunnel(state, player),
|
||||
"Rock Tunnel B1F - Northwest Item": lambda state: logic.rock_tunnel(state, player),
|
||||
"Rock Tunnel B1F - Southwest Item": lambda state: logic.rock_tunnel(state, player),
|
||||
"Rock Tunnel B1F - West Item": lambda state: logic.rock_tunnel(state, player),
|
||||
"Rock Tunnel 1F - PokeManiac": lambda state: logic.rock_tunnel(state, world, player),
|
||||
"Rock Tunnel 1F - Hiker 1": lambda state: logic.rock_tunnel(state, world, player),
|
||||
"Rock Tunnel 1F - Hiker 2": lambda state: logic.rock_tunnel(state, world, player),
|
||||
"Rock Tunnel 1F - Hiker 3": lambda state: logic.rock_tunnel(state, world, player),
|
||||
"Rock Tunnel 1F - Jr. Trainer F 1": lambda state: logic.rock_tunnel(state, world, player),
|
||||
"Rock Tunnel 1F - Jr. Trainer F 2": lambda state: logic.rock_tunnel(state, world, player),
|
||||
"Rock Tunnel 1F - Jr. Trainer F 3": lambda state: logic.rock_tunnel(state, world, player),
|
||||
"Rock Tunnel B1F - PokeManiac 1": lambda state: logic.rock_tunnel(state, world, player),
|
||||
"Rock Tunnel B1F - PokeManiac 2": lambda state: logic.rock_tunnel(state, world, player),
|
||||
"Rock Tunnel B1F - PokeManiac 3": lambda state: logic.rock_tunnel(state, world, player),
|
||||
"Rock Tunnel B1F - Jr. Trainer F 1": lambda state: logic.rock_tunnel(state, world, player),
|
||||
"Rock Tunnel B1F - Jr. Trainer F 2": lambda state: logic.rock_tunnel(state, world, player),
|
||||
"Rock Tunnel B1F - Hiker 1": lambda state: logic.rock_tunnel(state, world, player),
|
||||
"Rock Tunnel B1F - Hiker 2": lambda state: logic.rock_tunnel(state, world, player),
|
||||
"Rock Tunnel B1F - Hiker 3": lambda state: logic.rock_tunnel(state, world, player),
|
||||
"Rock Tunnel B1F - North Item": lambda state: logic.rock_tunnel(state, world, player),
|
||||
"Rock Tunnel B1F - Northwest Item": lambda state: logic.rock_tunnel(state, world, player),
|
||||
"Rock Tunnel B1F - Southwest Item": lambda state: logic.rock_tunnel(state, world, player),
|
||||
"Rock Tunnel B1F - West Item": lambda state: logic.rock_tunnel(state, world, player),
|
||||
|
||||
# Pokédex check
|
||||
"Oak's Lab - Oak's Parcel Reward": lambda state: state.has("Oak's Parcel", player),
|
||||
|
||||
# Hidden items
|
||||
"Viridian Forest - Hidden Item Northwest by Trainer": lambda state: logic.can_get_hidden_items(state,
|
||||
"Viridian Forest - Hidden Item Northwest by Trainer": lambda state: logic.can_get_hidden_items(state, world,
|
||||
player),
|
||||
"Viridian Forest - Hidden Item Entrance Tree": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Mt Moon B2F - Hidden Item Dead End Before Fossils": lambda state: logic.can_get_hidden_items(state,
|
||||
"Viridian Forest - Hidden Item Entrance Tree": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Mt Moon B2F - Hidden Item Dead End Before Fossils": lambda state: logic.can_get_hidden_items(state, world,
|
||||
player),
|
||||
"Route 25 - Hidden Item Fence Outside Bill's House": lambda state: logic.can_get_hidden_items(state,
|
||||
"Route 25 - Hidden Item Fence Outside Bill's House": lambda state: logic.can_get_hidden_items(state, world,
|
||||
player),
|
||||
"Route 9 - Hidden Item Bush By Grass": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"S.S. Anne Kitchen - Hidden Item Kitchen Trash": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"S.S. Anne B1F Rooms - Hidden Item Under Pillow": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Route 9 - Hidden Item Bush By Grass": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"S.S. Anne Kitchen - Hidden Item Kitchen Trash": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"S.S. Anne B1F Rooms - Hidden Item Under Pillow": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Route 10 - Hidden Item Behind Rock Tunnel Entrance Cuttable Tree": lambda
|
||||
state: logic.can_get_hidden_items(state, player) and logic.can_cut(state, player),
|
||||
"Route 10 - Hidden Item Bush": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Rocket Hideout B1F - Hidden Item Pot Plant": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Rocket Hideout B3F - Hidden Item Near East Item": lambda state: logic.can_get_hidden_items(state, player),
|
||||
state: logic.can_get_hidden_items(state, world, player) and logic.can_cut(state, world, player),
|
||||
"Route 10 - Hidden Item Bush": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Rocket Hideout B1F - Hidden Item Pot Plant": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Rocket Hideout B3F - Hidden Item Near East Item": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Rocket Hideout B4F - Hidden Item Behind Giovanni": lambda state:
|
||||
logic.can_get_hidden_items(state, player),
|
||||
"Pokemon Tower 5F - Hidden Item Near West Staircase": lambda state: logic.can_get_hidden_items(state,
|
||||
logic.can_get_hidden_items(state, world, player),
|
||||
"Pokemon Tower 5F - Hidden Item Near West Staircase": lambda state: logic.can_get_hidden_items(state, world,
|
||||
player),
|
||||
"Route 13 - Hidden Item Dead End Bush": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Route 13 - Hidden Item Dead End By Water Corner": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Pokemon Mansion B1F - Hidden Item Secret Key Room Corner": lambda state: logic.can_get_hidden_items(state,
|
||||
"Route 13 - Hidden Item Dead End Bush": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Route 13 - Hidden Item Dead End By Water Corner": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Pokemon Mansion B1F - Hidden Item Secret Key Room Corner": lambda state: logic.can_get_hidden_items(state, world,
|
||||
player),
|
||||
"Safari Zone West - Hidden Item Secret House Statue": lambda state: logic.can_get_hidden_items(state,
|
||||
"Safari Zone West - Hidden Item Secret House Statue": lambda state: logic.can_get_hidden_items(state, world,
|
||||
player),
|
||||
"Silph Co 5F - Hidden Item Pot Plant": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Silph Co 9F - Hidden Item Nurse Bed": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Saffron Copycat's House 2F - Hidden Item Desk": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Cerulean Cave 1F - Hidden Item Center Rocks": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Cerulean Cave B1F - Hidden Item Northeast Rocks": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Power Plant - Hidden Item Central Dead End": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Power Plant - Hidden Item Before Zapdos": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Seafoam Islands B2F - Hidden Item Rock": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Seafoam Islands B3F - Hidden Item Rock": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Silph Co 5F - Hidden Item Pot Plant": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Silph Co 9F - Hidden Item Nurse Bed": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Saffron Copycat's House 2F - Hidden Item Desk": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Cerulean Cave 1F - Hidden Item Center Rocks": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Cerulean Cave B1F - Hidden Item Northeast Rocks": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Power Plant - Hidden Item Central Dead End": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Power Plant - Hidden Item Before Zapdos": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Seafoam Islands B2F - Hidden Item Rock": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Seafoam Islands B3F - Hidden Item Rock": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
# if you can reach any exit boulders, that means you can drop into the water tunnel and auto-surf
|
||||
"Seafoam Islands B4F - Hidden Item Corner Island": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Seafoam Islands B4F - Hidden Item Corner Island": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Pokemon Mansion 1F - Hidden Item Block Near Entrance Carpet": lambda
|
||||
state: logic.can_get_hidden_items(state, player),
|
||||
"Pokemon Mansion 3F - Hidden Item Behind Burglar": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Route 23 - Hidden Item Rocks Before Victory Road": lambda state: logic.can_get_hidden_items(state,
|
||||
state: logic.can_get_hidden_items(state, world, player),
|
||||
"Pokemon Mansion 3F - Hidden Item Behind Burglar": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Route 23 - Hidden Item Rocks Before Victory Road": lambda state: logic.can_get_hidden_items(state, world,
|
||||
player),
|
||||
"Route 23 - Hidden Item East Bush After Water": lambda state: logic.can_get_hidden_items(state,
|
||||
"Route 23 - Hidden Item East Bush After Water": lambda state: logic.can_get_hidden_items(state, world,
|
||||
player),
|
||||
"Route 23 - Hidden Item On Island": lambda state: logic.can_get_hidden_items(state,
|
||||
player) and logic.can_surf(state, player),
|
||||
"Victory Road 2F - Hidden Item Rock Before Moltres": lambda state: logic.can_get_hidden_items(state,
|
||||
"Route 23 - Hidden Item On Island": lambda state: logic.can_get_hidden_items(state, world,
|
||||
player) and logic.can_surf(state, world, player),
|
||||
"Victory Road 2F - Hidden Item Rock Before Moltres": lambda state: logic.can_get_hidden_items(state, world,
|
||||
player),
|
||||
"Victory Road 2F - Hidden Item Rock In Final Room": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Viridian City - Hidden Item Cuttable Tree": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Route 11 - Hidden Item Isolated Bush Near Gate": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Route 12 - Hidden Item Bush Near Gate": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Route 17 - Hidden Item In Grass": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Route 17 - Hidden Item Near Northernmost Sign": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Route 17 - Hidden Item East Center": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Route 17 - Hidden Item West Center": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Route 17 - Hidden Item Before Final Bridge": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Victory Road 2F - Hidden Item Rock In Final Room": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Viridian City - Hidden Item Cuttable Tree": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Route 11 - Hidden Item Isolated Bush Near Gate": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Route 12 - Hidden Item Bush Near Gate": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Route 17 - Hidden Item In Grass": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Route 17 - Hidden Item Near Northernmost Sign": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Route 17 - Hidden Item East Center": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Route 17 - Hidden Item West Center": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Route 17 - Hidden Item Before Final Bridge": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Underground Path North South - Hidden Item Near Northern Stairs": lambda
|
||||
state: logic.can_get_hidden_items(state, player),
|
||||
state: logic.can_get_hidden_items(state, world, player),
|
||||
"Underground Path North South - Hidden Item Near Southern Stairs": lambda
|
||||
state: logic.can_get_hidden_items(state, player),
|
||||
"Underground Path West East - Hidden Item West": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Underground Path West East - Hidden Item East": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Celadon City - Hidden Item Dead End Near Cuttable Tree": lambda state: logic.can_get_hidden_items(state,
|
||||
state: logic.can_get_hidden_items(state, world, player),
|
||||
"Underground Path West East - Hidden Item West": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Underground Path West East - Hidden Item East": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Celadon City - Hidden Item Dead End Near Cuttable Tree": lambda state: logic.can_get_hidden_items(state, world,
|
||||
player),
|
||||
"Route 25 - Hidden Item Northeast Of Grass": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Mt Moon B2F - Hidden Item Lone Rock": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Vermilion City - Hidden Item In Water Near Fan Club": lambda state: logic.can_get_hidden_items(state,
|
||||
player) and logic.can_surf(state, player),
|
||||
"Cerulean City - Hidden Item Gym Badge Guy's Backyard": lambda state: logic.can_get_hidden_items(state,
|
||||
"Route 25 - Hidden Item Northeast Of Grass": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Mt Moon B2F - Hidden Item Lone Rock": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
"Vermilion City - Hidden Item In Water Near Fan Club": lambda state: logic.can_get_hidden_items(state, world,
|
||||
player) and logic.can_surf(state, world, player),
|
||||
"Cerulean City - Hidden Item Gym Badge Guy's Backyard": lambda state: logic.can_get_hidden_items(state, world,
|
||||
player),
|
||||
"Route 4 - Hidden Item Plateau East Of Mt Moon": lambda state: logic.can_get_hidden_items(state, player),
|
||||
"Route 4 - Hidden Item Plateau East Of Mt Moon": lambda state: logic.can_get_hidden_items(state, world, player),
|
||||
|
||||
# Evolutions
|
||||
"Evolution - Ivysaur": lambda state: state.has("Bulbasaur", player) and logic.evolve_level(state, 16, player),
|
||||
@@ -281,5 +273,4 @@ def set_rules(multiworld, player):
|
||||
if loc.name.startswith("Pokedex"):
|
||||
mon = loc.name.split(" - ")[1]
|
||||
add_rule(loc, lambda state, i=mon: (state.has("Pokedex", player) or not
|
||||
state.multiworld.require_pokedex[player]) and (state.has(i, player)
|
||||
or state.has(f"Static {i}", player)))
|
||||
world.options.require_pokedex) and (state.has(i, player) or state.has(f"Static {i}", player)))
|
||||
|
||||
@@ -97,12 +97,12 @@ class ConfigurableOptionInfo(typing.NamedTuple):
|
||||
|
||||
|
||||
class ColouredMessage:
|
||||
def __init__(self, text: str = '') -> None:
|
||||
def __init__(self, text: str = '', *, keep_markup: bool = False) -> None:
|
||||
self.parts: typing.List[dict] = []
|
||||
if text:
|
||||
self(text)
|
||||
def __call__(self, text: str) -> 'ColouredMessage':
|
||||
add_json_text(self.parts, text)
|
||||
self(text, keep_markup=keep_markup)
|
||||
def __call__(self, text: str, *, keep_markup: bool = False) -> 'ColouredMessage':
|
||||
add_json_text(self.parts, text, keep_markup=keep_markup)
|
||||
return self
|
||||
def coloured(self, text: str, colour: str) -> 'ColouredMessage':
|
||||
add_json_text(self.parts, text, type="color", color=colour)
|
||||
@@ -128,7 +128,7 @@ class StarcraftClientProcessor(ClientCommandProcessor):
|
||||
# Note(mm): Bold/underline can help readability, but unfortunately the CommonClient does not filter bold tags from command-line output.
|
||||
# Regardless, using `on_print_json` to get formatted text in the GUI and output in the command-line and in the logs,
|
||||
# without having to branch code from CommonClient
|
||||
self.ctx.on_print_json({"data": [{"text": text}]})
|
||||
self.ctx.on_print_json({"data": [{"text": text, "keep_markup": True}]})
|
||||
|
||||
def _cmd_difficulty(self, difficulty: str = "") -> bool:
|
||||
"""Overrides the current difficulty set for the world. Takes the argument casual, normal, hard, or brutal"""
|
||||
@@ -257,7 +257,7 @@ class StarcraftClientProcessor(ClientCommandProcessor):
|
||||
print_faction_title()
|
||||
has_printed_faction_title = True
|
||||
(ColouredMessage('* ').item(item.item, self.ctx.slot, flags=item.flags)
|
||||
(" from ").location(item.location, self.ctx.slot)
|
||||
(" from ").location(item.location, item.player)
|
||||
(" by ").player(item.player)
|
||||
).send(self.ctx)
|
||||
|
||||
@@ -278,7 +278,7 @@ class StarcraftClientProcessor(ClientCommandProcessor):
|
||||
for item in received_items_of_this_type:
|
||||
filter_match_count += len(received_items_of_this_type)
|
||||
(ColouredMessage(' * ').item(item.item, self.ctx.slot, flags=item.flags)
|
||||
(" from ").location(item.location, self.ctx.slot)
|
||||
(" from ").location(item.location, item.player)
|
||||
(" by ").player(item.player)
|
||||
).send(self.ctx)
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from typing import *
|
||||
import asyncio
|
||||
|
||||
from kvui import GameManager, HoverBehavior, ServerToolTip
|
||||
from NetUtils import JSONMessagePart
|
||||
from kvui import GameManager, HoverBehavior, ServerToolTip, KivyJSONtoTextParser
|
||||
from kivy.app import App
|
||||
from kivy.clock import Clock
|
||||
from kivy.uix.tabbedpanel import TabbedPanelItem
|
||||
@@ -69,6 +70,18 @@ class MissionLayout(GridLayout):
|
||||
class MissionCategory(GridLayout):
|
||||
pass
|
||||
|
||||
|
||||
class SC2JSONtoKivyParser(KivyJSONtoTextParser):
|
||||
def _handle_text(self, node: JSONMessagePart):
|
||||
if node.get("keep_markup", False):
|
||||
for ref in node.get("refs", []):
|
||||
node["text"] = f"[ref={self.ref_count}|{ref}]{node['text']}[/ref]"
|
||||
self.ref_count += 1
|
||||
return super(KivyJSONtoTextParser, self)._handle_text(node)
|
||||
else:
|
||||
return super()._handle_text(node)
|
||||
|
||||
|
||||
class SC2Manager(GameManager):
|
||||
logging_pairs = [
|
||||
("Client", "Archipelago"),
|
||||
@@ -87,6 +100,7 @@ class SC2Manager(GameManager):
|
||||
|
||||
def __init__(self, ctx) -> None:
|
||||
super().__init__(ctx)
|
||||
self.json_to_kivy_parser = SC2JSONtoKivyParser(ctx)
|
||||
|
||||
def clear_tooltip(self) -> None:
|
||||
if self.ctx.current_tooltip:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from ..game_content import ContentPack
|
||||
from ...data import villagers_data, fish_data
|
||||
from ...data.game_item import GenericSource, ItemTag, Tag, CustomRuleSource
|
||||
from ...data.game_item import GenericSource, ItemTag, Tag, CustomRuleSource, CompoundSource
|
||||
from ...data.harvest import ForagingSource, SeasonalForagingSource, ArtifactSpotSource
|
||||
from ...data.requirement import ToolRequirement, BookRequirement, SkillRequirement, SeasonRequirement
|
||||
from ...data.shop import ShopSource, MysteryBoxSource, ArtifactTroveSource, PrizeMachineSource, FishingTreasureChestSource
|
||||
@@ -229,8 +229,10 @@ pelican_town = ContentPack(
|
||||
ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),),
|
||||
Book.mapping_cave_systems: (
|
||||
Tag(ItemTag.BOOK, ItemTag.BOOK_POWER),
|
||||
GenericSource(regions=(Region.adventurer_guild_bedroom,)),
|
||||
ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),),
|
||||
CompoundSource(sources=(
|
||||
GenericSource(regions=(Region.adventurer_guild_bedroom,)),
|
||||
ShopSource(money_price=20000, shop_region=LogicRegion.bookseller_3),
|
||||
))),
|
||||
Book.monster_compendium: (
|
||||
Tag(ItemTag.BOOK, ItemTag.BOOK_POWER),
|
||||
CustomRuleSource(create_rule=lambda logic: logic.monster.can_kill_many(Generic.any)),
|
||||
|
||||
@@ -59,6 +59,11 @@ class CustomRuleSource(ItemSource):
|
||||
create_rule: Callable[[Any], StardewRule]
|
||||
|
||||
|
||||
@dataclass(frozen=True, **kw_only)
|
||||
class CompoundSource(ItemSource):
|
||||
sources: Tuple[ItemSource, ...] = ()
|
||||
|
||||
|
||||
class Tag(ItemSource):
|
||||
"""Not a real source, just a way to add tags to an item. Will be removed from the item sources during unpacking."""
|
||||
tag: Tuple[ItemTag, ...]
|
||||
|
||||
@@ -12,7 +12,7 @@ from .region_logic import RegionLogicMixin
|
||||
from .requirement_logic import RequirementLogicMixin
|
||||
from .tool_logic import ToolLogicMixin
|
||||
from ..data.artisan import MachineSource
|
||||
from ..data.game_item import GenericSource, ItemSource, GameItem, CustomRuleSource
|
||||
from ..data.game_item import GenericSource, ItemSource, GameItem, CustomRuleSource, CompoundSource
|
||||
from ..data.harvest import ForagingSource, FruitBatsSource, MushroomCaveSource, SeasonalForagingSource, \
|
||||
HarvestCropSource, HarvestFruitTreeSource, ArtifactSpotSource
|
||||
from ..data.shop import ShopSource, MysteryBoxSource, ArtifactTroveSource, PrizeMachineSource, FishingTreasureChestSource
|
||||
@@ -25,7 +25,7 @@ class SourceLogicMixin(BaseLogicMixin):
|
||||
|
||||
|
||||
class SourceLogic(BaseLogic[Union[SourceLogicMixin, HasLogicMixin, ReceivedLogicMixin, HarvestingLogicMixin, MoneyLogicMixin, RegionLogicMixin,
|
||||
ArtisanLogicMixin, ToolLogicMixin, RequirementLogicMixin, GrindLogicMixin]]):
|
||||
ArtisanLogicMixin, ToolLogicMixin, RequirementLogicMixin, GrindLogicMixin]]):
|
||||
|
||||
def has_access_to_item(self, item: GameItem):
|
||||
rules = []
|
||||
@@ -40,6 +40,10 @@ ArtisanLogicMixin, ToolLogicMixin, RequirementLogicMixin, GrindLogicMixin]]):
|
||||
return self.logic.or_(*(self.logic.source.has_access_to(source) & self.logic.requirement.meet_all_requirements(source.other_requirements)
|
||||
for source in sources))
|
||||
|
||||
def has_access_to_all(self, sources: Iterable[ItemSource]):
|
||||
return self.logic.and_(*(self.logic.source.has_access_to(source) & self.logic.requirement.meet_all_requirements(source.other_requirements)
|
||||
for source in sources))
|
||||
|
||||
@functools.singledispatchmethod
|
||||
def has_access_to(self, source: Any):
|
||||
raise ValueError(f"Sources of type{type(source)} have no rule registered.")
|
||||
@@ -52,6 +56,10 @@ ArtisanLogicMixin, ToolLogicMixin, RequirementLogicMixin, GrindLogicMixin]]):
|
||||
def _(self, source: CustomRuleSource):
|
||||
return source.create_rule(self.logic)
|
||||
|
||||
@has_access_to.register
|
||||
def _(self, source: CompoundSource):
|
||||
return self.logic.source.has_access_to_all(source.sources)
|
||||
|
||||
@has_access_to.register
|
||||
def _(self, source: ForagingSource):
|
||||
return self.logic.harvesting.can_forage_from(source)
|
||||
|
||||
@@ -57,8 +57,6 @@ all_random_settings = {
|
||||
}
|
||||
|
||||
easy_settings = {
|
||||
"progression_balancing": ProgressionBalancing.default,
|
||||
"accessibility": Accessibility.option_full,
|
||||
Goal.internal_name: Goal.option_community_center,
|
||||
FarmType.internal_name: "random",
|
||||
StartingMoney.internal_name: "very rich",
|
||||
@@ -103,8 +101,6 @@ easy_settings = {
|
||||
}
|
||||
|
||||
medium_settings = {
|
||||
"progression_balancing": 25,
|
||||
"accessibility": Accessibility.option_full,
|
||||
Goal.internal_name: Goal.option_community_center,
|
||||
FarmType.internal_name: "random",
|
||||
StartingMoney.internal_name: "rich",
|
||||
@@ -149,8 +145,6 @@ medium_settings = {
|
||||
}
|
||||
|
||||
hard_settings = {
|
||||
"progression_balancing": 0,
|
||||
"accessibility": Accessibility.option_full,
|
||||
Goal.internal_name: Goal.option_grandpa_evaluation,
|
||||
FarmType.internal_name: "random",
|
||||
StartingMoney.internal_name: "extra",
|
||||
@@ -195,8 +189,6 @@ hard_settings = {
|
||||
}
|
||||
|
||||
nightmare_settings = {
|
||||
"progression_balancing": 0,
|
||||
"accessibility": Accessibility.option_full,
|
||||
Goal.internal_name: Goal.option_community_center,
|
||||
FarmType.internal_name: "random",
|
||||
StartingMoney.internal_name: "vanilla",
|
||||
@@ -241,8 +233,6 @@ nightmare_settings = {
|
||||
}
|
||||
|
||||
short_settings = {
|
||||
"progression_balancing": ProgressionBalancing.default,
|
||||
"accessibility": Accessibility.option_full,
|
||||
Goal.internal_name: Goal.option_bottom_of_the_mines,
|
||||
FarmType.internal_name: "random",
|
||||
StartingMoney.internal_name: "filthy rich",
|
||||
@@ -287,8 +277,6 @@ short_settings = {
|
||||
}
|
||||
|
||||
minsanity_settings = {
|
||||
"progression_balancing": ProgressionBalancing.default,
|
||||
"accessibility": Accessibility.option_minimal,
|
||||
Goal.internal_name: Goal.default,
|
||||
FarmType.internal_name: "random",
|
||||
StartingMoney.internal_name: StartingMoney.default,
|
||||
@@ -333,8 +321,6 @@ minsanity_settings = {
|
||||
}
|
||||
|
||||
allsanity_settings = {
|
||||
"progression_balancing": ProgressionBalancing.default,
|
||||
"accessibility": Accessibility.option_full,
|
||||
Goal.internal_name: Goal.default,
|
||||
FarmType.internal_name: "random",
|
||||
StartingMoney.internal_name: StartingMoney.default,
|
||||
|
||||
@@ -39,6 +39,7 @@ from .strings.crop_names import Fruit, Vegetable
|
||||
from .strings.entrance_names import dig_to_mines_floor, dig_to_skull_floor, Entrance, move_to_woods_depth, DeepWoodsEntrance, AlecEntrance, \
|
||||
SVEEntrance, LaceyEntrance, BoardingHouseEntrance, LogicEntrance
|
||||
from .strings.forageable_names import Forageable
|
||||
from .strings.generic_names import Generic
|
||||
from .strings.geode_names import Geode
|
||||
from .strings.material_names import Material
|
||||
from .strings.metal_names import MetalBar, Mineral
|
||||
@@ -263,6 +264,7 @@ def set_entrance_rules(logic: StardewLogic, multiworld, player, world_options: S
|
||||
set_entrance_rule(multiworld, player, LogicEntrance.buy_experience_books, logic.time.has_lived_months(2))
|
||||
set_entrance_rule(multiworld, player, LogicEntrance.buy_year1_books, logic.time.has_year_two)
|
||||
set_entrance_rule(multiworld, player, LogicEntrance.buy_year3_books, logic.time.has_year_three)
|
||||
set_entrance_rule(multiworld, player, Entrance.adventurer_guild_to_bedroom, logic.monster.can_kill_max(Generic.any))
|
||||
|
||||
|
||||
def set_dangerous_mine_rules(logic, multiworld, player, world_options: StardewValleyOptions):
|
||||
|
||||
@@ -256,10 +256,10 @@ class SVTestBase(RuleAssertMixin, WorldTestBase, SVTestCase):
|
||||
return False
|
||||
return super().run_default_tests
|
||||
|
||||
def collect_lots_of_money(self):
|
||||
def collect_lots_of_money(self, percent: float = 0.25):
|
||||
self.multiworld.state.collect(self.world.create_item("Shipping Bin"), prevent_sweep=False)
|
||||
real_total_prog_items = self.multiworld.worlds[self.player].total_progression_items
|
||||
required_prog_items = int(round(real_total_prog_items * 0.25))
|
||||
required_prog_items = int(round(real_total_prog_items * percent))
|
||||
for i in range(required_prog_items):
|
||||
self.multiworld.state.collect(self.world.create_item("Stardrop"), prevent_sweep=False)
|
||||
self.multiworld.worlds[self.player].total_progression_items = real_total_prog_items
|
||||
|
||||
26
worlds/stardew_valley/test/rules/TestBooks.py
Normal file
26
worlds/stardew_valley/test/rules/TestBooks.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from ... import options
|
||||
from ...test import SVTestBase
|
||||
|
||||
|
||||
class TestBooksLogic(SVTestBase):
|
||||
options = {
|
||||
options.Booksanity.internal_name: options.Booksanity.option_all,
|
||||
}
|
||||
|
||||
def test_need_weapon_for_mapping_cave_systems(self):
|
||||
self.collect_lots_of_money(0.5)
|
||||
|
||||
location = self.multiworld.get_location("Read Mapping Cave Systems", self.player)
|
||||
|
||||
self.assert_reach_location_false(location, self.multiworld.state)
|
||||
|
||||
self.collect("Progressive Mine Elevator")
|
||||
self.collect("Progressive Mine Elevator")
|
||||
self.collect("Progressive Mine Elevator")
|
||||
self.collect("Progressive Mine Elevator")
|
||||
self.assert_reach_location_false(location, self.multiworld.state)
|
||||
|
||||
self.collect("Progressive Weapon")
|
||||
self.assert_reach_location_true(location, self.multiworld.state)
|
||||
|
||||
|
||||
@@ -1616,13 +1616,26 @@ def set_er_location_rules(world: "TunicWorld") -> None:
|
||||
set_rule(world.get_location("Frog's Domain - Escape Chest"),
|
||||
lambda state: state.has_any({grapple, laurels}, player))
|
||||
|
||||
# Library Lab
|
||||
set_rule(world.get_location("Library Lab - Page 1"),
|
||||
lambda state: has_stick(state, player) or state.has_any((fire_wand, gun), player))
|
||||
set_rule(world.get_location("Library Lab - Page 2"),
|
||||
lambda state: has_stick(state, player) or state.has_any((fire_wand, gun), player))
|
||||
set_rule(world.get_location("Library Lab - Page 3"),
|
||||
lambda state: has_stick(state, player) or state.has_any((fire_wand, gun), player))
|
||||
|
||||
# Eastern Vault Fortress
|
||||
set_rule(world.get_location("Fortress Arena - Hexagon Red"),
|
||||
lambda state: state.has(vault_key, player))
|
||||
# yes, you can clear the leaves with dagger
|
||||
# gun isn't included since it can only break one leaf pile at a time, and we don't check how much mana you have
|
||||
# but really, I expect the player to just throw a bomb at them if they don't have melee
|
||||
set_rule(world.get_location("Fortress Leaf Piles - Secret Chest"),
|
||||
lambda state: has_stick(state, player) or state.has(ice_dagger, player))
|
||||
|
||||
# Beneath the Vault
|
||||
set_rule(world.get_location("Beneath the Fortress - Bridge"),
|
||||
lambda state: state.has_group("Melee Weapons", player, 1) or state.has_any({laurels, fire_wand}, player))
|
||||
lambda state: has_stick(state, player) or state.has_any({laurels, fire_wand}, player))
|
||||
|
||||
# Quarry
|
||||
set_rule(world.get_location("Quarry - [Central] Above Ladder Dash Chest"),
|
||||
|
||||
@@ -295,9 +295,20 @@ def set_location_rules(world: "TunicWorld") -> None:
|
||||
set_rule(world.get_location("Frog's Domain - Escape Chest"),
|
||||
lambda state: state.has_any({grapple, laurels}, player))
|
||||
|
||||
# Library Lab
|
||||
set_rule(world.get_location("Library Lab - Page 1"),
|
||||
lambda state: has_stick(state, player) or state.has_any((fire_wand, gun), player))
|
||||
set_rule(world.get_location("Library Lab - Page 2"),
|
||||
lambda state: has_stick(state, player) or state.has_any((fire_wand, gun), player))
|
||||
set_rule(world.get_location("Library Lab - Page 3"),
|
||||
lambda state: has_stick(state, player) or state.has_any((fire_wand, gun), player))
|
||||
|
||||
# Eastern Vault Fortress
|
||||
# yes, you can clear the leaves with dagger
|
||||
# gun isn't included since it can only break one leaf pile at a time, and we don't check how much mana you have
|
||||
# but really, I expect the player to just throw a bomb at them if they don't have melee
|
||||
set_rule(world.get_location("Fortress Leaf Piles - Secret Chest"),
|
||||
lambda state: state.has(laurels, player))
|
||||
lambda state: state.has(laurels, player) and (has_stick(state, player) or state.has(ice_dagger, player)))
|
||||
set_rule(world.get_location("Fortress Arena - Siege Engine/Vault Key Pickup"),
|
||||
lambda state: has_sword(state, player)
|
||||
and (has_ability(prayer, state, world)
|
||||
|
||||
@@ -430,7 +430,7 @@ class Yugioh06World(World):
|
||||
"final_campaign_boss_campaign_opponents":
|
||||
self.options.final_campaign_boss_campaign_opponents.value,
|
||||
"fourth_tier_5_campaign_boss_campaign_opponents":
|
||||
self.options.fourth_tier_5_campaign_boss_unlock_condition.value,
|
||||
self.options.fourth_tier_5_campaign_boss_campaign_opponents.value,
|
||||
"third_tier_5_campaign_boss_campaign_opponents":
|
||||
self.options.third_tier_5_campaign_boss_campaign_opponents.value,
|
||||
"number_of_challenges": self.options.number_of_challenges.value,
|
||||
|
||||
@@ -39,10 +39,10 @@ def set_rules(world):
|
||||
"No Trap Cards Bonus": lambda state: yugioh06_difficulty(state, player, 2),
|
||||
"No Damage Bonus": lambda state: state.has_group("Campaign Boss Beaten", player, 3),
|
||||
"Low Deck Bonus": lambda state: state.has_any(["Reasoning", "Monster Gate", "Magical Merchant"], player) and
|
||||
yugioh06_difficulty(state, player, 3),
|
||||
yugioh06_difficulty(state, player, 2),
|
||||
"Extremely Low Deck Bonus":
|
||||
lambda state: state.has_any(["Reasoning", "Monster Gate", "Magical Merchant"], player) and
|
||||
yugioh06_difficulty(state, player, 2),
|
||||
yugioh06_difficulty(state, player, 3),
|
||||
"Opponent's Turn Finish Bonus": lambda state: yugioh06_difficulty(state, player, 2),
|
||||
"Exactly 0 LP Bonus": lambda state: yugioh06_difficulty(state, player, 2),
|
||||
"Reversal Finish Bonus": lambda state: yugioh06_difficulty(state, player, 2),
|
||||
|
||||
Reference in New Issue
Block a user