mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-05-27 16:50:02 -07:00
Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 80e89db812 | |||
| 8d0f1be791 | |||
| febcd84995 | |||
| d0a555a185 | |||
| da72d40f14 | |||
| f78f906ced | |||
| 245a66828d | |||
| 798556fd3f | |||
| 14609100cf | |||
| fe366e2985 | |||
| 819710ff61 | |||
| c0cd03f436 | |||
| e694804509 | |||
| c21c10a602 | |||
| 54ab181514 | |||
| 88de34dfd7 | |||
| 555b7211ef | |||
| 7b097c918e | |||
| 4eeac945fb | |||
| db577ad899 | |||
| 8aed3efe97 | |||
| f7d6f689fb | |||
| 910d4b93c9 | |||
| 584dd8fe75 | |||
| 259d010a86 | |||
| 98d808bb87 | |||
| f9108f4331 | |||
| 4b6ad12192 | |||
| a08fbc66a8 | |||
| 41ddb96b24 | |||
| ba8f03516e | |||
| 0095eecf2b | |||
| 79942c09c2 | |||
| 1b15c6920d | |||
| 499d79f089 | |||
| 926e08513c | |||
| 025c550991 | |||
| fced9050a4 | |||
| 2ee8b7535d | |||
| 0d35cd4679 | |||
| db5d9fbf70 | |||
| 51a6dc150c | |||
| 710609fa60 | |||
| da781bb4ac | |||
| 69487661dd | |||
| f73c0d9894 | |||
| 6fac83b84c | |||
| debb936618 | |||
| 8c5b65ff26 | |||
| a7c96436d9 | |||
| 4e60f3cc54 | |||
| 30a0b337a2 | |||
| 4ea1dddd2f | |||
| dc218b7997 | |||
| 78c5489189 | |||
| d1a7bc66e6 | |||
| b982e9ebb4 | |||
| 8f7e0dc441 | |||
| 5aea8d4ab5 | |||
| 97be5f1dde | |||
| dae3fe188d | |||
| 96542fb2d8 | |||
| ec50b0716a | |||
| f8d3c26e3c | |||
| 1c0cec0de2 | |||
| 4692e6f08a | |||
| b8d23ec595 | |||
| ce42e42af7 | |||
| ee12dda361 | |||
| 84805a4e54 | |||
| 5530d181da | |||
| ed948e3e5b |
+9
-20
@@ -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:
|
||||
@@ -1204,26 +1206,13 @@ class Location:
|
||||
|
||||
|
||||
class ItemClassification(IntFlag):
|
||||
filler = 0b0000
|
||||
""" aka trash, as in filler items like ammo, currency etc """
|
||||
|
||||
progression = 0b0001
|
||||
""" Item that is logically relevant.
|
||||
Protects this item from being placed on excluded or unreachable locations. """
|
||||
|
||||
useful = 0b0010
|
||||
""" Item that is especially useful.
|
||||
Protects this item from being placed on excluded or unreachable locations.
|
||||
When combined with another flag like "progression", it means "an especially useful progression item". """
|
||||
|
||||
trap = 0b0100
|
||||
""" Item that is detrimental in some way. """
|
||||
|
||||
skip_balancing = 0b1000
|
||||
""" should technically never occur on its own
|
||||
Item that is logically relevant, but progression balancing should not touch.
|
||||
Typically currency or other counted items. """
|
||||
|
||||
filler = 0b0000 # aka trash, as in filler items like ammo, currency etc,
|
||||
progression = 0b0001 # Item that is logically relevant
|
||||
useful = 0b0010 # Item that is generally quite useful, but not required for anything logical
|
||||
trap = 0b0100 # detrimental item
|
||||
skip_balancing = 0b1000 # should technically never occur on its own
|
||||
# Item that is logically relevant, but progression balancing should not touch.
|
||||
# Typically currency or other counted items.
|
||||
progression_skip_balancing = 0b1001 # only progression gets balanced
|
||||
|
||||
def as_flag(self) -> int:
|
||||
|
||||
+2
-1
@@ -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:])
|
||||
|
||||
@@ -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.")
|
||||
@@ -529,7 +527,7 @@ def distribute_items_restrictive(multiworld: MultiWorld,
|
||||
if excludedlocations:
|
||||
raise FillError(
|
||||
f"Not enough filler items for excluded locations. "
|
||||
f"There are {len(excludedlocations)} more excluded locations than excludable items.",
|
||||
f"There are {len(excludedlocations)} more excluded locations than filler or trap items.",
|
||||
multiworld=multiworld,
|
||||
)
|
||||
|
||||
|
||||
+5
-26
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
+2
-1
@@ -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):
|
||||
|
||||
+43
-3
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
+25
-10
@@ -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 %}
|
||||
|
||||
@@ -510,7 +510,7 @@ In JSON this may look like:
|
||||
| ----- | ----- |
|
||||
| 0 | Nothing special about this item |
|
||||
| 0b001 | If set, indicates the item can unlock logical advancement |
|
||||
| 0b010 | If set, indicates the item is especially useful |
|
||||
| 0b010 | If set, indicates the item is important but not in a way that unlocks advancement |
|
||||
| 0b100 | If set, indicates the item is a trap |
|
||||
|
||||
### JSONMessagePart
|
||||
|
||||
+87
-5
@@ -248,8 +248,7 @@ will all have the same ID. Name must not be numeric (must contain at least 1 let
|
||||
Other classifications include:
|
||||
|
||||
* `filler`: a regular item or trash item
|
||||
* `useful`: item that is especially useful. Cannot be placed on excluded or unreachable locations. When combined with
|
||||
another flag like "progression", it means "an especially useful progression item".
|
||||
* `useful`: generally quite useful, but not required for anything logical. Cannot be placed on excluded locations
|
||||
* `trap`: negative impact on the player
|
||||
* `skip_balancing`: denotes that an item should not be moved to an earlier sphere for the purpose of balancing (to be
|
||||
combined with `progression`; see below)
|
||||
@@ -697,9 +696,92 @@ When importing a file that defines a class that inherits from `worlds.AutoWorld.
|
||||
is automatically extended by the mixin's members. These members should be prefixed with the name of the implementing
|
||||
world since the namespace is shared with all other logic mixins.
|
||||
|
||||
Some uses could be to add additional variables to the state object, or to have a custom state machine that gets modified
|
||||
with the state.
|
||||
Please do this with caution and only when necessary.
|
||||
LogicMixin is handy when your logic is more complex than one-to-one location-item relationships.
|
||||
A game in which "The red key opens the red door" can just express this relationship through a one-line access rule.
|
||||
But now, consider a game with a heavy focus on combat, where the main logical consideration is which enemies you can
|
||||
defeat with your current items.
|
||||
There could be dozens of weapons, armor pieces, or consumables that each improve your ability to defeat
|
||||
specific enemies to varying degrees. It would be useful to be able to keep track of "defeatable enemies" as a state variable,
|
||||
and have this variable be recalculated as necessary based on newly collected/removed items.
|
||||
This is the capability of LogicMixin: Adding custom variables to state that get recalculated as necessary.
|
||||
|
||||
In general, a LogicMixin class should have at least one mutable variable that is tracking some custom state per player,
|
||||
as well as `init_mixin` and `copy_mixin` functions so that this variable gets initialized and copied correctly when
|
||||
`CollectionState()` and `CollectionState.copy()` are called respectively.
|
||||
|
||||
```python
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from worlds.AutoWorld import LogicMixin
|
||||
|
||||
class MyGameState(LogicMixin):
|
||||
mygame_defeatable_enemies: Dict[int, Set[str]] # per player
|
||||
|
||||
def init_mixin(self, multiworld: MultiWorld) -> None:
|
||||
# Initialize per player with the corresponding "nothing" value, such as 0 or an empty set.
|
||||
# You can also use something like Collections.defaultdict
|
||||
self.mygame_defeatable_enemies = {
|
||||
player: set() for player in multiworld.get_game_players("My Game")
|
||||
}
|
||||
|
||||
def copy_mixin(self, new_state: CollectionState) -> CollectionState:
|
||||
# Be careful to make a "deep enough" copy here!
|
||||
new_state.mygame_defeatable_enemies = {
|
||||
player: enemies.copy() for player, enemies in self.mygame_defeatable_enemies.items()
|
||||
}
|
||||
```
|
||||
|
||||
After doing this, you can now access `state.mygame_defeatable_enemies[player]` from your access rules.
|
||||
|
||||
Usually, doing this coincides with an override of `World.collect` and `World.remove`, where the custom state variable
|
||||
gets recalculated when a relevant item is collected or removed.
|
||||
|
||||
```python
|
||||
# __init__.py
|
||||
|
||||
def collect(self, state: CollectionState, item: Item) -> bool:
|
||||
change = super().collect(state, item)
|
||||
if change and item in COMBAT_ITEMS:
|
||||
state.mygame_defeatable_enemies[self.player] |= get_newly_unlocked_enemies(state)
|
||||
return change
|
||||
|
||||
def remove(self, state: CollectionState, item: Item) -> bool:
|
||||
change = super().remove(state, item)
|
||||
if change and item in COMBAT_ITEMS:
|
||||
state.mygame_defeatable_enemies[self.player] -= get_newly_locked_enemies(state)
|
||||
return change
|
||||
```
|
||||
|
||||
Using LogicMixin can greatly slow down your code if you don't use it intelligently. This is because `collect`
|
||||
and `remove` are called very frequently during fill. If your `collect` & `remove` cause a heavy calculation
|
||||
every time, your code might end up being *slower* than just doing calculations in your access rules.
|
||||
|
||||
One way to optimise recalculations is to make use of the fact that `collect` should only unlock things,
|
||||
and `remove` should only lock things.
|
||||
In our example, we have two different functions: `get_newly_unlocked_enemies` and `get_newly_locked_enemies`.
|
||||
`get_newly_unlocked_enemies` should only consider enemies that are *not already in the set*
|
||||
and check whether they were **unlocked**.
|
||||
`get_newly_locked_enemies` should only consider enemies that are *already in the set*
|
||||
and check whether they **became locked**.
|
||||
|
||||
Another impactful way to optimise LogicMixin is to use caching.
|
||||
Your custom state variables don't actually need to be recalculated on every `collect` / `remove`, because there are
|
||||
often multiple calls to `collect` / `remove` between access rule calls. Thus, it would be much more efficient to hold
|
||||
off on recaculating until the an actual access rule call happens.
|
||||
A common way to realize this is to define a `mygame_state_is_stale` variable that is set to True in `collect`, `remove`,
|
||||
and `init_mixin`. The calls to the actual recalculating functions are then moved to the start of the relevant
|
||||
access rules like this:
|
||||
|
||||
```python
|
||||
def can_defeat_enemy(state: CollectionState, player: int, enemy: str) -> bool:
|
||||
if state.mygame_state_is_stale[player]:
|
||||
state.mygame_defeatable_enemies[player] = recalculate_defeatable_enemies(state)
|
||||
state.mygame_state_is_stale[player] = False
|
||||
|
||||
return enemy in state.mygame_defeatable_enemies[player]
|
||||
```
|
||||
|
||||
Only use LogicMixin if necessary. There are often other ways to achieve what it does, like making clever use of
|
||||
`state.prog_items`, using event items, pseudo-regions, etc.
|
||||
|
||||
#### pre_fill
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
+20
-19
@@ -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:
|
||||
|
||||
+40
-50
@@ -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)
|
||||
|
||||
+70
-71
@@ -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
|
||||
|
||||
@@ -131,8 +131,8 @@ guide: [Archipelago Plando Guide](/tutorial/Archipelago/plando/en)
|
||||
the location without using any hint points.
|
||||
* `start_location_hints` is the same as `start_hints` but for locations, allowing you to hint for the item contained
|
||||
there without using any hint points.
|
||||
* `exclude_locations` lets you define any locations that you don't want to do and prevents items classified as
|
||||
"progression" or "useful" from being placed on them.
|
||||
* `exclude_locations` lets you define any locations that you don't want to do and forces a filler or trap item which
|
||||
isn't necessary for progression into these locations.
|
||||
* `priority_locations` lets you define any locations that you want to do and forces a progression item into these
|
||||
locations.
|
||||
* `item_links` allows players to link their items into a group with the same item link name and game. The items declared
|
||||
|
||||
+26
-2
@@ -21,6 +21,16 @@ from .Charms import names as charm_names
|
||||
from BaseClasses import Region, Location, MultiWorld, Item, LocationProgressType, Tutorial, ItemClassification, CollectionState
|
||||
from worlds.AutoWorld import World, LogicMixin, WebWorld
|
||||
|
||||
from settings import Group, Bool
|
||||
|
||||
|
||||
class HollowKnightSettings(Group):
|
||||
class DisableMapModSpoilers(Bool):
|
||||
"""Disallows the APMapMod from showing spoiler placements."""
|
||||
|
||||
disable_spoilers: typing.Union[DisableMapModSpoilers, bool] = False
|
||||
|
||||
|
||||
path_of_pain_locations = {
|
||||
"Soul_Totem-Path_of_Pain_Below_Thornskip",
|
||||
"Lore_Tablet-Path_of_Pain_Entrance",
|
||||
@@ -124,14 +134,25 @@ shop_cost_types: typing.Dict[str, typing.Tuple[str, ...]] = {
|
||||
|
||||
|
||||
class HKWeb(WebWorld):
|
||||
tutorials = [Tutorial(
|
||||
setup_en = Tutorial(
|
||||
"Mod Setup and Use Guide",
|
||||
"A guide to playing Hollow Knight with Archipelago.",
|
||||
"English",
|
||||
"setup_en.md",
|
||||
"setup/en",
|
||||
["Ijwu"]
|
||||
)]
|
||||
)
|
||||
|
||||
setup_pt_br = Tutorial(
|
||||
setup_en.tutorial_name,
|
||||
setup_en.description,
|
||||
"Português Brasileiro",
|
||||
"setup_pt_br.md",
|
||||
"setup/pt_br",
|
||||
["JoaoVictor-FA"]
|
||||
)
|
||||
|
||||
tutorials = [setup_en, setup_pt_br]
|
||||
|
||||
bug_report_page = "https://github.com/Ijwu/Archipelago.HollowKnight/issues/new?assignees=&labels=bug%2C+needs+investigation&template=bug_report.md&title="
|
||||
|
||||
@@ -145,6 +166,7 @@ class HKWorld(World):
|
||||
game: str = "Hollow Knight"
|
||||
options_dataclass = HKOptions
|
||||
options: HKOptions
|
||||
settings: typing.ClassVar[HollowKnightSettings]
|
||||
|
||||
web = HKWeb()
|
||||
|
||||
@@ -544,6 +566,8 @@ class HKWorld(World):
|
||||
|
||||
slot_data["grub_count"] = self.grub_count
|
||||
|
||||
slot_data["is_race"] = int(self.settings.disable_spoilers or self.multiworld.is_race)
|
||||
|
||||
return slot_data
|
||||
|
||||
def create_item(self, name: str) -> HKItem:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# Guia de configuração para Hollow Knight no Archipelago
|
||||
|
||||
## Programas obrigatórios
|
||||
* Baixe e extraia o Lumafly Mod Manager (gerenciador de mods Lumafly) do [Site Lumafly](https://themulhima.github.io/Lumafly/).
|
||||
* Uma cópia legal de Hollow Knight.
|
||||
* Versões Steam, Gog, e Xbox Game Pass do jogo são suportadas.
|
||||
* Windows, Mac, e Linux (incluindo Steam Deck) são suportados.
|
||||
|
||||
## Instalando o mod Archipelago Mod usando Lumafly
|
||||
1. Abra o Lumafly e confirme que ele localizou sua pasta de instalação do Hollow Knight.
|
||||
2. Clique em "Install (instalar)" perto da opção "Archipelago" mod.
|
||||
* Se quiser, instale também o "Archipelago Map Mod (mod do mapa do archipelago)" para usá-lo como rastreador dentro do jogo.
|
||||
3. Abra o jogo, tudo preparado!
|
||||
|
||||
### O que fazer se o Lumafly falha em encontrar a sua pasta de instalação
|
||||
1. Encontre a pasta manualmente.
|
||||
* Xbox Game Pass:
|
||||
1. Entre no seu aplicativo Xbox e mova seu mouse em cima de "Hollow Knight" na sua barra da esquerda.
|
||||
2. Clique nos 3 pontos depois clique gerenciar.
|
||||
3. Vá nos arquivos e selecione procurar.
|
||||
4. Clique em "Hollow Knight", depois em "Content (Conteúdo)", depois clique na barra com o endereço e a copie.
|
||||
* Steam:
|
||||
1. Você provavelmente colocou sua biblioteca Steam num local não padrão. Se esse for o caso você provavelmente sabe onde está.
|
||||
. Encontre sua biblioteca Steam, depois encontre a pasta do Hollow Knight e copie seu endereço.
|
||||
* Windows - `C:\Program Files (x86)\Steam\steamapps\common\Hollow Knight`
|
||||
* Linux/Steam Deck - `~/.local/share/Steam/steamapps/common/Hollow Knight`
|
||||
* Mac - `~/Library/Application Support/Steam/steamapps/common/Hollow Knight/hollow_knight.app`
|
||||
2. Rode o Lumafly como administrador e, quando ele perguntar pelo endereço do arquivo, cole o endereço do arquivo que você copiou.
|
||||
|
||||
## Configurando seu arquivo YAML
|
||||
### O que é um YAML e por que eu preciso de um?
|
||||
Um arquivo YAML é a forma que você informa suas configurações do jogador para o Archipelago.
|
||||
Olhe o [guia de configuração básica de um multiworld](/tutorial/Archipelago/setup/en) aqui no site do Archipelago para aprender mais.
|
||||
|
||||
### Onde eu consigo o YAML?
|
||||
Você pode usar a [página de configurações do jogador para Hollow Knight](/games/Hollow%20Knight/player-options) aqui no site do Archipelago
|
||||
para gerar o YAML usando a interface gráfica.
|
||||
|
||||
### Entrando numa partida de Archipelago no Hollow Knight
|
||||
1. Começe o jogo depois de instalar todos os mods necessários.
|
||||
2. Crie um **novo jogo salvo.**
|
||||
3. Selecione o modo de jogo **Archipelago** do menu de seleção.
|
||||
4. Coloque as configurações corretas do seu servidor Archipelago.
|
||||
5. Aperte em **Começar**. O jogo vai travar por uns segundos enquanto ele coloca todos itens.
|
||||
6. O jogo vai te colocar imediatamente numa partida randomizada.
|
||||
* Se você está esperando uma contagem então espere ele cair antes de apertar começar.
|
||||
* Ou clique em começar e pause o jogo enquanto estiver nele.
|
||||
|
||||
## Dicas e outros comandos
|
||||
Enquanto jogar um multiworld, você pode interagir com o servidor usando vários comandos listados no
|
||||
[Guia de comandos](/tutorial/Archipelago/commands/en). Você pode usar o cliente de texto do Archipelago para isso,
|
||||
que está incluido na ultima versão do [Archipelago software](https://github.com/ArchipelagoMW/Archipelago/releases/latest).
|
||||
+47
-5
@@ -31,6 +31,9 @@ def check_stdin() -> None:
|
||||
print("WARNING: Console input is not routed reliably on Windows, use the GUI instead.")
|
||||
|
||||
class KH1ClientCommandProcessor(ClientCommandProcessor):
|
||||
def __init__(self, ctx):
|
||||
super().__init__(ctx)
|
||||
|
||||
def _cmd_deathlink(self):
|
||||
"""Toggles Deathlink"""
|
||||
global death_link
|
||||
@@ -40,6 +43,40 @@ class KH1ClientCommandProcessor(ClientCommandProcessor):
|
||||
else:
|
||||
death_link = True
|
||||
self.output(f"Death Link turned on")
|
||||
|
||||
def _cmd_goal(self):
|
||||
"""Prints goal setting"""
|
||||
if "goal" in self.ctx.slot_data.keys():
|
||||
self.output(str(self.ctx.slot_data["goal"]))
|
||||
else:
|
||||
self.output("Unknown")
|
||||
|
||||
def _cmd_eotw_unlock(self):
|
||||
"""Prints End of the World Unlock setting"""
|
||||
if "required_reports_door" in self.ctx.slot_data.keys():
|
||||
if self.ctx.slot_data["required_reports_door"] > 13:
|
||||
self.output("Item")
|
||||
else:
|
||||
self.output(str(self.ctx.slot_data["required_reports_eotw"]) + " reports")
|
||||
else:
|
||||
self.output("Unknown")
|
||||
|
||||
def _cmd_door_unlock(self):
|
||||
"""Prints Final Rest Door Unlock setting"""
|
||||
if "door" in self.ctx.slot_data.keys():
|
||||
if self.ctx.slot_data["door"] == "reports":
|
||||
self.output(str(self.ctx.slot_data["required_reports_door"]) + " reports")
|
||||
else:
|
||||
self.output(str(self.ctx.slot_data["door"]))
|
||||
else:
|
||||
self.output("Unknown")
|
||||
|
||||
def _cmd_advanced_logic(self):
|
||||
"""Prints advanced logic setting"""
|
||||
if "advanced_logic" in self.ctx.slot_data.keys():
|
||||
self.output(str(self.ctx.slot_data["advanced_logic"]))
|
||||
else:
|
||||
self.output("Unknown")
|
||||
|
||||
class KH1Context(CommonContext):
|
||||
command_processor: int = KH1ClientCommandProcessor
|
||||
@@ -51,6 +88,8 @@ class KH1Context(CommonContext):
|
||||
self.send_index: int = 0
|
||||
self.syncing = False
|
||||
self.awaiting_bridge = False
|
||||
self.hinted_synth_location_ids = False
|
||||
self.slot_data = {}
|
||||
# self.game_communication_path: files go in this path to pass data between us and the actual game
|
||||
if "localappdata" in os.environ:
|
||||
self.game_communication_path = os.path.expandvars(r"%localappdata%/KH1FM")
|
||||
@@ -104,6 +143,7 @@ class KH1Context(CommonContext):
|
||||
f.close()
|
||||
|
||||
#Handle Slot Data
|
||||
self.slot_data = args['slot_data']
|
||||
for key in list(args['slot_data'].keys()):
|
||||
with open(os.path.join(self.game_communication_path, key + ".cfg"), 'w') as f:
|
||||
f.write(str(args['slot_data'][key]))
|
||||
@@ -217,11 +257,13 @@ async def game_watcher(ctx: KH1Context):
|
||||
if timegm(time.strptime(st, '%Y%m%d%H%M%S')) > ctx.last_death_link and int(time.time()) % int(timegm(time.strptime(st, '%Y%m%d%H%M%S'))) < 10:
|
||||
await ctx.send_death(death_text = "Sora was defeated!")
|
||||
if file.find("insynthshop") > -1:
|
||||
await ctx.send_msgs([{
|
||||
"cmd": "LocationScouts",
|
||||
"locations": [2656401,2656402,2656403,2656404,2656405,2656406],
|
||||
"create_as_hint": 2
|
||||
}])
|
||||
if not ctx.hinted_synth_location_ids:
|
||||
await ctx.send_msgs([{
|
||||
"cmd": "LocationScouts",
|
||||
"locations": [2656401,2656402,2656403,2656404,2656405,2656406],
|
||||
"create_as_hint": 2
|
||||
}])
|
||||
ctx.hinted_synth_location_ids = True
|
||||
ctx.locations_checked = sending
|
||||
message = [{"cmd": 'LocationChecks', "locations": sending}]
|
||||
await ctx.send_msgs(message)
|
||||
|
||||
@@ -83,8 +83,8 @@ class ItemName:
|
||||
RUPEES_200 = "200 Rupees"
|
||||
RUPEES_500 = "500 Rupees"
|
||||
SEASHELL = "Seashell"
|
||||
MESSAGE = "Master Stalfos' Message"
|
||||
GEL = "Gel"
|
||||
MESSAGE = "Nothing"
|
||||
GEL = "Zol Attack"
|
||||
BOOMERANG = "Boomerang"
|
||||
HEART_PIECE = "Heart Piece"
|
||||
BOWWOW = "BowWow"
|
||||
|
||||
@@ -29,6 +29,7 @@ def fixGoldenLeaf(rom):
|
||||
rom.patch(0x03, 0x0980, ASM("ld a, [$DB15]"), ASM("ld a, [wGoldenLeaves]")) # If leaves >= 6 move richard
|
||||
rom.patch(0x06, 0x0059, ASM("ld a, [$DB15]"), ASM("ld a, [wGoldenLeaves]")) # If leaves >= 6 move richard
|
||||
rom.patch(0x06, 0x007D, ASM("ld a, [$DB15]"), ASM("ld a, [wGoldenLeaves]")) # Richard message if no leaves
|
||||
rom.patch(0x06, 0x00B8, ASM("ld [$DB15], a"), ASM("ld [wGoldenLeaves], a")) # Stores FF in the leaf counter if we opened the path
|
||||
rom.patch(0x06, 0x00B6, ASM("ld a, $FF"), ASM("ld a, $06"))
|
||||
rom.patch(0x06, 0x00B8, ASM("ld [$DB15], a"), ASM("ld [wGoldenLeaves], a")) # Stores 6 in the leaf counter if we opened the path (instead of FF, so that nothing breaks if we get more for some reason)
|
||||
# 6:40EE uses leaves == 6 to check if we have collected the key, but only to change the message.
|
||||
# rom.patch(0x06, 0x2AEF, ASM("ld a, [$DB15]"), ASM("ld a, [wGoldenLeaves]")) # Telephone message handler
|
||||
|
||||
+17
-1
@@ -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()
|
||||
|
||||
|
||||
+32
-1
@@ -216,7 +216,7 @@ class LinksAwakeningWorld(World):
|
||||
for _ in range(count):
|
||||
if item_name in exclude:
|
||||
exclude.remove(item_name) # this is destructive. create unique list above
|
||||
self.multiworld.itempool.append(self.create_item("Master Stalfos' Message"))
|
||||
self.multiworld.itempool.append(self.create_item("Nothing"))
|
||||
else:
|
||||
item = self.create_item(item_name)
|
||||
|
||||
@@ -512,3 +512,34 @@ class LinksAwakeningWorld(World):
|
||||
if change and item.name in self.rupees:
|
||||
state.prog_items[self.player]["RUPEES"] -= self.rupees[item.name]
|
||||
return change
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return "Nothing"
|
||||
|
||||
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.
+23
-10
@@ -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:
|
||||
|
||||
+19
-6
@@ -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
|
||||
|
||||
+4
-2
@@ -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
|
||||
]
|
||||
|
||||
+15
-15
@@ -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),
|
||||
|
||||
+59
-58
@@ -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
|
||||
|
||||
+38
-33
@@ -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)
|
||||
|
||||
+15
-18
@@ -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)
|
||||
|
||||
+157
-24
@@ -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(
|
||||
|
||||
+23
-42
@@ -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.
+1
-1
@@ -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)
|
||||
|
||||
+20
-21
@@ -1,9 +1,9 @@
|
||||
from .Utils import data_path, __version__
|
||||
from .Colors import *
|
||||
import logging
|
||||
import worlds.oot.Music as music
|
||||
import worlds.oot.Sounds as sfx
|
||||
import worlds.oot.IconManip as icon
|
||||
from . import Music as music
|
||||
from . import Sounds as sfx
|
||||
from . import IconManip as icon
|
||||
from .JSONDump import dump_obj, CollapseList, CollapseDict, AlignedDict, SortedDict
|
||||
import json
|
||||
|
||||
@@ -105,7 +105,7 @@ def patch_tunic_colors(rom, ootworld, symbols):
|
||||
|
||||
# handle random
|
||||
if tunic_option == 'Random Choice':
|
||||
tunic_option = random.choice(tunic_color_list)
|
||||
tunic_option = ootworld.random.choice(tunic_color_list)
|
||||
# handle completely random
|
||||
if tunic_option == 'Completely Random':
|
||||
color = generate_random_color()
|
||||
@@ -156,9 +156,9 @@ def patch_navi_colors(rom, ootworld, symbols):
|
||||
|
||||
# choose a random choice for the whole group
|
||||
if navi_option_inner == 'Random Choice':
|
||||
navi_option_inner = random.choice(navi_color_list)
|
||||
navi_option_inner = ootworld.random.choice(navi_color_list)
|
||||
if navi_option_outer == 'Random Choice':
|
||||
navi_option_outer = random.choice(navi_color_list)
|
||||
navi_option_outer = ootworld.random.choice(navi_color_list)
|
||||
|
||||
if navi_option_outer == 'Match Inner':
|
||||
navi_option_outer = navi_option_inner
|
||||
@@ -233,9 +233,9 @@ def patch_sword_trails(rom, ootworld, symbols):
|
||||
|
||||
# handle random choice
|
||||
if option_inner == 'Random Choice':
|
||||
option_inner = random.choice(sword_trail_color_list)
|
||||
option_inner = ootworld.random.choice(sword_trail_color_list)
|
||||
if option_outer == 'Random Choice':
|
||||
option_outer = random.choice(sword_trail_color_list)
|
||||
option_outer = ootworld.random.choice(sword_trail_color_list)
|
||||
|
||||
if option_outer == 'Match Inner':
|
||||
option_outer = option_inner
|
||||
@@ -326,9 +326,9 @@ def patch_trails(rom, ootworld, trails):
|
||||
|
||||
# handle random choice
|
||||
if option_inner == 'Random Choice':
|
||||
option_inner = random.choice(trail_color_list)
|
||||
option_inner = ootworld.random.choice(trail_color_list)
|
||||
if option_outer == 'Random Choice':
|
||||
option_outer = random.choice(trail_color_list)
|
||||
option_outer = ootworld.random.choice(trail_color_list)
|
||||
|
||||
if option_outer == 'Match Inner':
|
||||
option_outer = option_inner
|
||||
@@ -393,7 +393,7 @@ def patch_gauntlet_colors(rom, ootworld, symbols):
|
||||
|
||||
# handle random
|
||||
if gauntlet_option == 'Random Choice':
|
||||
gauntlet_option = random.choice(gauntlet_color_list)
|
||||
gauntlet_option = ootworld.random.choice(gauntlet_color_list)
|
||||
# handle completely random
|
||||
if gauntlet_option == 'Completely Random':
|
||||
color = generate_random_color()
|
||||
@@ -424,10 +424,10 @@ def patch_shield_frame_colors(rom, ootworld, symbols):
|
||||
|
||||
# handle random
|
||||
if shield_frame_option == 'Random Choice':
|
||||
shield_frame_option = random.choice(shield_frame_color_list)
|
||||
shield_frame_option = ootworld.random.choice(shield_frame_color_list)
|
||||
# handle completely random
|
||||
if shield_frame_option == 'Completely Random':
|
||||
color = [random.getrandbits(8), random.getrandbits(8), random.getrandbits(8)]
|
||||
color = [ootworld.random.getrandbits(8), ootworld.random.getrandbits(8), ootworld.random.getrandbits(8)]
|
||||
# grab the color from the list
|
||||
elif shield_frame_option in shield_frame_colors:
|
||||
color = list(shield_frame_colors[shield_frame_option])
|
||||
@@ -458,7 +458,7 @@ def patch_heart_colors(rom, ootworld, symbols):
|
||||
|
||||
# handle random
|
||||
if heart_option == 'Random Choice':
|
||||
heart_option = random.choice(heart_color_list)
|
||||
heart_option = ootworld.random.choice(heart_color_list)
|
||||
# handle completely random
|
||||
if heart_option == 'Completely Random':
|
||||
color = generate_random_color()
|
||||
@@ -495,7 +495,7 @@ def patch_magic_colors(rom, ootworld, symbols):
|
||||
magic_option = format_cosmetic_option_result(ootworld.__dict__[magic_setting])
|
||||
|
||||
if magic_option == 'Random Choice':
|
||||
magic_option = random.choice(magic_color_list)
|
||||
magic_option = ootworld.random.choice(magic_color_list)
|
||||
|
||||
if magic_option == 'Completely Random':
|
||||
color = generate_random_color()
|
||||
@@ -559,7 +559,7 @@ def patch_button_colors(rom, ootworld, symbols):
|
||||
|
||||
# handle random
|
||||
if button_option == 'Random Choice':
|
||||
button_option = random.choice(list(button_colors.keys()))
|
||||
button_option = ootworld.random.choice(list(button_colors.keys()))
|
||||
# handle completely random
|
||||
if button_option == 'Completely Random':
|
||||
fixed_font_color = [10, 10, 10]
|
||||
@@ -618,11 +618,11 @@ def patch_sfx(rom, ootworld, symbols):
|
||||
rom.write_int16(loc, sound_id)
|
||||
else:
|
||||
if selection == 'random-choice':
|
||||
selection = random.choice(sfx.get_hook_pool(hook)).value.keyword
|
||||
selection = ootworld.random.choice(sfx.get_hook_pool(hook)).value.keyword
|
||||
elif selection == 'random-ear-safe':
|
||||
selection = random.choice(sfx.get_hook_pool(hook, "TRUE")).value.keyword
|
||||
selection = ootworld.random.choice(sfx.get_hook_pool(hook, "TRUE")).value.keyword
|
||||
elif selection == 'completely-random':
|
||||
selection = random.choice(sfx.standard).value.keyword
|
||||
selection = ootworld.random.choice(sfx.standard).value.keyword
|
||||
sound_id = sound_dict[selection]
|
||||
for loc in hook.value.locations:
|
||||
rom.write_int16(loc, sound_id)
|
||||
@@ -644,7 +644,7 @@ def patch_instrument(rom, ootworld, symbols):
|
||||
|
||||
choice = ootworld.sfx_ocarina
|
||||
if choice == 'random-choice':
|
||||
choice = random.choice(list(instruments.keys()))
|
||||
choice = ootworld.random.choice(list(instruments.keys()))
|
||||
|
||||
rom.write_byte(0x00B53C7B, instruments[choice])
|
||||
rom.write_byte(0x00B4BF6F, instruments[choice]) # For Lost Woods Skull Kids' minigame in Lost Woods
|
||||
@@ -769,7 +769,6 @@ patch_sets[0x1F073FD9] = {
|
||||
|
||||
def patch_cosmetics(ootworld, rom):
|
||||
# Use the world's slot seed for cosmetics
|
||||
random.seed(ootworld.multiworld.per_slot_randoms[ootworld.player].random())
|
||||
|
||||
# try to detect the cosmetic patch data format
|
||||
versioned_patch_set = None
|
||||
|
||||
@@ -3,9 +3,9 @@ from BaseClasses import Entrance
|
||||
class OOTEntrance(Entrance):
|
||||
game: str = 'Ocarina of Time'
|
||||
|
||||
def __init__(self, player, world, name='', parent=None):
|
||||
def __init__(self, player, multiworld, name='', parent=None):
|
||||
super(OOTEntrance, self).__init__(player, name, parent)
|
||||
self.multiworld = world
|
||||
self.multiworld = multiworld
|
||||
self.access_rules = []
|
||||
self.reverse = None
|
||||
self.replaces = None
|
||||
|
||||
@@ -440,16 +440,16 @@ class EntranceShuffleError(Exception):
|
||||
|
||||
|
||||
def shuffle_random_entrances(ootworld):
|
||||
world = ootworld.multiworld
|
||||
multiworld = ootworld.multiworld
|
||||
player = ootworld.player
|
||||
|
||||
# Gather locations to keep reachable for validation
|
||||
all_state = ootworld.get_state_with_complete_itempool()
|
||||
all_state.sweep_for_advancements(locations=ootworld.get_locations())
|
||||
locations_to_ensure_reachable = {loc for loc in world.get_reachable_locations(all_state, player) if not (loc.type == 'Drop' or (loc.type == 'Event' and 'Subrule' in loc.name))}
|
||||
locations_to_ensure_reachable = {loc for loc in multiworld.get_reachable_locations(all_state, player) if not (loc.type == 'Drop' or (loc.type == 'Event' and 'Subrule' in loc.name))}
|
||||
|
||||
# Set entrance data for all entrances
|
||||
set_all_entrances_data(world, player)
|
||||
set_all_entrances_data(multiworld, player)
|
||||
|
||||
# Determine entrance pools based on settings
|
||||
one_way_entrance_pools = {}
|
||||
@@ -547,10 +547,10 @@ def shuffle_random_entrances(ootworld):
|
||||
none_state = CollectionState(ootworld.multiworld)
|
||||
|
||||
# Plando entrances
|
||||
if world.plando_connections[player]:
|
||||
if ootworld.options.plando_connections:
|
||||
rollbacks = []
|
||||
all_targets = {**one_way_target_entrance_pools, **target_entrance_pools}
|
||||
for conn in world.plando_connections[player]:
|
||||
for conn in ootworld.options.plando_connections:
|
||||
try:
|
||||
entrance = ootworld.get_entrance(conn.entrance)
|
||||
exit = ootworld.get_entrance(conn.exit)
|
||||
@@ -628,7 +628,7 @@ def shuffle_random_entrances(ootworld):
|
||||
logging.getLogger('').error(f'Root has too many entrances left after shuffling entrances')
|
||||
# Game is beatable
|
||||
new_all_state = ootworld.get_state_with_complete_itempool()
|
||||
if not world.has_beaten_game(new_all_state, player):
|
||||
if not multiworld.has_beaten_game(new_all_state, player):
|
||||
raise EntranceShuffleError('Cannot beat game')
|
||||
# Validate world
|
||||
validate_world(ootworld, None, locations_to_ensure_reachable, all_state, none_state)
|
||||
@@ -675,7 +675,7 @@ def place_one_way_priority_entrance(ootworld, priority_name, allowed_regions, al
|
||||
all_state, none_state, one_way_entrance_pools, one_way_target_entrance_pools):
|
||||
|
||||
avail_pool = list(chain.from_iterable(one_way_entrance_pools[t] for t in allowed_types if t in one_way_entrance_pools))
|
||||
ootworld.multiworld.random.shuffle(avail_pool)
|
||||
ootworld.random.shuffle(avail_pool)
|
||||
|
||||
for entrance in avail_pool:
|
||||
if entrance.replaces:
|
||||
@@ -725,11 +725,11 @@ def shuffle_entrance_pool(ootworld, pool_type, entrance_pool, target_entrances,
|
||||
raise EntranceShuffleError(f'Entrance placement attempt count exceeded for world {ootworld.player}')
|
||||
|
||||
def shuffle_entrances(ootworld, pool_type, entrances, target_entrances, rollbacks, locations_to_ensure_reachable, all_state, none_state):
|
||||
ootworld.multiworld.random.shuffle(entrances)
|
||||
ootworld.random.shuffle(entrances)
|
||||
for entrance in entrances:
|
||||
if entrance.connected_region != None:
|
||||
continue
|
||||
ootworld.multiworld.random.shuffle(target_entrances)
|
||||
ootworld.random.shuffle(target_entrances)
|
||||
# Here we deliberately introduce bias by prioritizing certain interiors, i.e. the ones most likely to cause problems.
|
||||
# success rate over randomization
|
||||
if pool_type in {'InteriorSoft', 'MixedSoft'}:
|
||||
@@ -785,7 +785,7 @@ def split_entrances_by_requirements(ootworld, entrances_to_split, assumed_entran
|
||||
# TODO: improve this function
|
||||
def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all_state_orig, none_state_orig):
|
||||
|
||||
world = ootworld.multiworld
|
||||
multiworld = ootworld.multiworld
|
||||
player = ootworld.player
|
||||
|
||||
all_state = all_state_orig.copy()
|
||||
@@ -828,8 +828,8 @@ def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all
|
||||
if ootworld.shuffle_interior_entrances and (ootworld.misc_hints or ootworld.hints != 'none') and \
|
||||
(entrance_placed == None or entrance_placed.type in ['Interior', 'SpecialInterior']):
|
||||
# Ensure Kak Potion Shop entrances are in the same hint area so there is no ambiguity as to which entrance is used for hints
|
||||
potion_front = get_entrance_replacing(world.get_region('Kak Potion Shop Front', player), 'Kakariko Village -> Kak Potion Shop Front', player)
|
||||
potion_back = get_entrance_replacing(world.get_region('Kak Potion Shop Back', player), 'Kak Backyard -> Kak Potion Shop Back', player)
|
||||
potion_front = get_entrance_replacing(multiworld.get_region('Kak Potion Shop Front', player), 'Kakariko Village -> Kak Potion Shop Front', player)
|
||||
potion_back = get_entrance_replacing(multiworld.get_region('Kak Potion Shop Back', player), 'Kak Backyard -> Kak Potion Shop Back', player)
|
||||
if potion_front is not None and potion_back is not None and not same_hint_area(potion_front, potion_back):
|
||||
raise EntranceShuffleError('Kak Potion Shop entrances are not in the same hint area')
|
||||
elif (potion_front and not potion_back) or (not potion_front and potion_back):
|
||||
@@ -840,8 +840,8 @@ def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all
|
||||
|
||||
# When cows are shuffled, ensure the same thing for Impa's House, since the cow is reachable from both sides
|
||||
if ootworld.shuffle_cows:
|
||||
impas_front = get_entrance_replacing(world.get_region('Kak Impas House', player), 'Kakariko Village -> Kak Impas House', player)
|
||||
impas_back = get_entrance_replacing(world.get_region('Kak Impas House Back', player), 'Kak Impas Ledge -> Kak Impas House Back', player)
|
||||
impas_front = get_entrance_replacing(multiworld.get_region('Kak Impas House', player), 'Kakariko Village -> Kak Impas House', player)
|
||||
impas_back = get_entrance_replacing(multiworld.get_region('Kak Impas House Back', player), 'Kak Impas Ledge -> Kak Impas House Back', player)
|
||||
if impas_front is not None and impas_back is not None and not same_hint_area(impas_front, impas_back):
|
||||
raise EntranceShuffleError('Kak Impas House entrances are not in the same hint area')
|
||||
elif (impas_front and not impas_back) or (not impas_front and impas_back):
|
||||
@@ -861,25 +861,25 @@ def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all
|
||||
any(region for region in time_travel_state.adult_reachable_regions[player] if region.time_passes)):
|
||||
raise EntranceShuffleError('Time passing is not guaranteed as both ages')
|
||||
|
||||
if ootworld.starting_age == 'child' and (world.get_region('Temple of Time', player) not in time_travel_state.adult_reachable_regions[player]):
|
||||
if ootworld.starting_age == 'child' and (multiworld.get_region('Temple of Time', player) not in time_travel_state.adult_reachable_regions[player]):
|
||||
raise EntranceShuffleError('Path to ToT as adult not guaranteed')
|
||||
if ootworld.starting_age == 'adult' and (world.get_region('Temple of Time', player) not in time_travel_state.child_reachable_regions[player]):
|
||||
if ootworld.starting_age == 'adult' and (multiworld.get_region('Temple of Time', player) not in time_travel_state.child_reachable_regions[player]):
|
||||
raise EntranceShuffleError('Path to ToT as child not guaranteed')
|
||||
|
||||
if (ootworld.shuffle_interior_entrances or ootworld.shuffle_overworld_entrances) and \
|
||||
(entrance_placed == None or entrance_placed.type in ['Interior', 'SpecialInterior', 'Overworld', 'Spawn', 'WarpSong', 'OwlDrop']):
|
||||
# Ensure big poe shop is always reachable as adult
|
||||
if world.get_region('Market Guard House', player) not in time_travel_state.adult_reachable_regions[player]:
|
||||
if multiworld.get_region('Market Guard House', player) not in time_travel_state.adult_reachable_regions[player]:
|
||||
raise EntranceShuffleError('Big Poe Shop access not guaranteed as adult')
|
||||
if ootworld.shopsanity == 'off':
|
||||
# Ensure that Goron and Zora shops are accessible as adult
|
||||
if world.get_region('GC Shop', player) not in all_state.adult_reachable_regions[player]:
|
||||
if multiworld.get_region('GC Shop', player) not in all_state.adult_reachable_regions[player]:
|
||||
raise EntranceShuffleError('Goron City Shop not accessible as adult')
|
||||
if world.get_region('ZD Shop', player) not in all_state.adult_reachable_regions[player]:
|
||||
if multiworld.get_region('ZD Shop', player) not in all_state.adult_reachable_regions[player]:
|
||||
raise EntranceShuffleError('Zora\'s Domain Shop not accessible as adult')
|
||||
if ootworld.open_forest == 'closed':
|
||||
# Ensure that Kokiri Shop is reachable as child with no items
|
||||
if world.get_region('KF Kokiri Shop', player) not in none_state.child_reachable_regions[player]:
|
||||
if multiworld.get_region('KF Kokiri Shop', player) not in none_state.child_reachable_regions[player]:
|
||||
raise EntranceShuffleError('Kokiri Forest Shop not accessible as child in closed forest')
|
||||
|
||||
|
||||
|
||||
+11
-13
@@ -1,5 +1,3 @@
|
||||
import random
|
||||
|
||||
from BaseClasses import LocationProgressType
|
||||
from .Items import OOTItem
|
||||
|
||||
@@ -28,7 +26,7 @@ class Hint(object):
|
||||
text = ""
|
||||
type = []
|
||||
|
||||
def __init__(self, name, text, type, choice=None):
|
||||
def __init__(self, name, text, type, rand, choice=None):
|
||||
self.name = name
|
||||
self.type = [type] if not isinstance(type, list) else type
|
||||
|
||||
@@ -36,31 +34,31 @@ class Hint(object):
|
||||
self.text = text
|
||||
else:
|
||||
if choice == None:
|
||||
self.text = random.choice(text)
|
||||
self.text = rand.choice(text)
|
||||
else:
|
||||
self.text = text[choice]
|
||||
|
||||
|
||||
def getHint(item, clearer_hint=False):
|
||||
def getHint(item, rand, clearer_hint=False):
|
||||
if item in hintTable:
|
||||
textOptions, clearText, hintType = hintTable[item]
|
||||
if clearer_hint:
|
||||
if clearText == None:
|
||||
return Hint(item, textOptions, hintType, 0)
|
||||
return Hint(item, clearText, hintType)
|
||||
return Hint(item, textOptions, hintType, rand, 0)
|
||||
return Hint(item, clearText, hintType, rand)
|
||||
else:
|
||||
return Hint(item, textOptions, hintType)
|
||||
return Hint(item, textOptions, hintType, rand)
|
||||
elif isinstance(item, str):
|
||||
return Hint(item, item, 'generic')
|
||||
return Hint(item, item, 'generic', rand)
|
||||
else: # is an Item
|
||||
return Hint(item.name, item.hint_text, 'item')
|
||||
return Hint(item.name, item.hint_text, 'item', rand)
|
||||
|
||||
|
||||
def getHintGroup(group, world):
|
||||
ret = []
|
||||
for name in hintTable:
|
||||
|
||||
hint = getHint(name, world.clearer_hints)
|
||||
hint = getHint(name, world.random, world.clearer_hints)
|
||||
|
||||
if hint.name in world.always_hints and group == 'always':
|
||||
hint.type = 'always'
|
||||
@@ -95,7 +93,7 @@ def getHintGroup(group, world):
|
||||
def getRequiredHints(world):
|
||||
ret = []
|
||||
for name in hintTable:
|
||||
hint = getHint(name)
|
||||
hint = getHint(name, world.random)
|
||||
if 'always' in hint.type or hint.name in conditional_always and conditional_always[hint.name](world):
|
||||
ret.append(hint)
|
||||
return ret
|
||||
@@ -1689,7 +1687,7 @@ def hintExclusions(world, clear_cache=False):
|
||||
|
||||
location_hints = []
|
||||
for name in hintTable:
|
||||
hint = getHint(name, world.clearer_hints)
|
||||
hint = getHint(name, world.random, world.clearer_hints)
|
||||
if any(item in hint.type for item in
|
||||
['always',
|
||||
'dual_always',
|
||||
|
||||
+33
-33
@@ -136,13 +136,13 @@ def getItemGenericName(item):
|
||||
def isRestrictedDungeonItem(dungeon, item):
|
||||
if not isinstance(item, OOTItem):
|
||||
return False
|
||||
if (item.map or item.compass) and dungeon.multiworld.shuffle_mapcompass == 'dungeon':
|
||||
if (item.map or item.compass) and dungeon.world.options.shuffle_mapcompass == 'dungeon':
|
||||
return item in dungeon.dungeon_items
|
||||
if item.type == 'SmallKey' and dungeon.multiworld.shuffle_smallkeys == 'dungeon':
|
||||
if item.type == 'SmallKey' and dungeon.world.options.shuffle_smallkeys == 'dungeon':
|
||||
return item in dungeon.small_keys
|
||||
if item.type == 'BossKey' and dungeon.multiworld.shuffle_bosskeys == 'dungeon':
|
||||
if item.type == 'BossKey' and dungeon.world.options.shuffle_bosskeys == 'dungeon':
|
||||
return item in dungeon.boss_key
|
||||
if item.type == 'GanonBossKey' and dungeon.multiworld.shuffle_ganon_bosskey == 'dungeon':
|
||||
if item.type == 'GanonBossKey' and dungeon.world.options.shuffle_ganon_bosskey == 'dungeon':
|
||||
return item in dungeon.boss_key
|
||||
return False
|
||||
|
||||
@@ -261,8 +261,8 @@ hintPrefixes = [
|
||||
'',
|
||||
]
|
||||
|
||||
def getSimpleHintNoPrefix(item):
|
||||
hint = getHint(item.name, True).text
|
||||
def getSimpleHintNoPrefix(item, rand):
|
||||
hint = getHint(item.name, rand, True).text
|
||||
|
||||
for prefix in hintPrefixes:
|
||||
if hint.startswith(prefix):
|
||||
@@ -417,9 +417,9 @@ class HintArea(Enum):
|
||||
|
||||
# Formats the hint text for this area with proper grammar.
|
||||
# Dungeons are hinted differently depending on the clearer_hints setting.
|
||||
def text(self, clearer_hints, preposition=False, world=None):
|
||||
def text(self, rand, clearer_hints, preposition=False, world=None):
|
||||
if self.is_dungeon:
|
||||
text = getHint(self.dungeon_name, clearer_hints).text
|
||||
text = getHint(self.dungeon_name, rand, clearer_hints).text
|
||||
else:
|
||||
text = str(self)
|
||||
prefix, suffix = text.replace('#', '').split(' ', 1)
|
||||
@@ -489,7 +489,7 @@ def get_woth_hint(world, checked):
|
||||
|
||||
if getattr(location.parent_region, "dungeon", None):
|
||||
world.woth_dungeon += 1
|
||||
location_text = getHint(location.parent_region.dungeon.name, world.clearer_hints).text
|
||||
location_text = getHint(location.parent_region.dungeon.name, world.random, world.clearer_hints).text
|
||||
else:
|
||||
location_text = get_hint_area(location)
|
||||
|
||||
@@ -570,9 +570,9 @@ def get_good_item_hint(world, checked):
|
||||
location = world.hint_rng.choice(locations)
|
||||
checked[location.player].add(location.name)
|
||||
|
||||
item_text = getHint(getItemGenericName(location.item), world.clearer_hints).text
|
||||
item_text = getHint(getItemGenericName(location.item), world.hint_rng, world.clearer_hints).text
|
||||
if getattr(location.parent_region, "dungeon", None):
|
||||
location_text = getHint(location.parent_region.dungeon.name, world.clearer_hints).text
|
||||
location_text = getHint(location.parent_region.dungeon.name, world.hint_rng, world.clearer_hints).text
|
||||
return (GossipText('#%s# hoards #%s#.' % (attach_name(location_text, location, world), attach_name(item_text, location.item, world)),
|
||||
['Green', 'Red']), location)
|
||||
else:
|
||||
@@ -613,10 +613,10 @@ def get_specific_item_hint(world, checked):
|
||||
|
||||
location = world.hint_rng.choice(locations)
|
||||
checked[location.player].add(location.name)
|
||||
item_text = getHint(getItemGenericName(location.item), world.clearer_hints).text
|
||||
item_text = getHint(getItemGenericName(location.item), world.hint_rng, world.clearer_hints).text
|
||||
|
||||
if getattr(location.parent_region, "dungeon", None):
|
||||
location_text = getHint(location.parent_region.dungeon.name, world.clearer_hints).text
|
||||
location_text = getHint(location.parent_region.dungeon.name, world.hint_rng, world.clearer_hints).text
|
||||
if world.hint_dist_user.get('vague_named_items', False):
|
||||
return (GossipText('#%s# may be on the hero\'s path.' % (location_text), ['Green']), location)
|
||||
else:
|
||||
@@ -648,9 +648,9 @@ def get_random_location_hint(world, checked):
|
||||
checked[location.player].add(location.name)
|
||||
dungeon = location.parent_region.dungeon
|
||||
|
||||
item_text = getHint(getItemGenericName(location.item), world.clearer_hints).text
|
||||
item_text = getHint(getItemGenericName(location.item), world.hint_rng, world.clearer_hints).text
|
||||
if dungeon:
|
||||
location_text = getHint(dungeon.name, world.clearer_hints).text
|
||||
location_text = getHint(dungeon.name, world.hint_rng, world.clearer_hints).text
|
||||
return (GossipText('#%s# hoards #%s#.' % (attach_name(location_text, location, world), attach_name(item_text, location.item, world)),
|
||||
['Green', 'Red']), location)
|
||||
else:
|
||||
@@ -675,7 +675,7 @@ def get_specific_hint(world, checked, type):
|
||||
location_text = hint.text
|
||||
if '#' not in location_text:
|
||||
location_text = '#%s#' % location_text
|
||||
item_text = getHint(getItemGenericName(location.item), world.clearer_hints).text
|
||||
item_text = getHint(getItemGenericName(location.item), world.hint_rng, world.clearer_hints).text
|
||||
|
||||
return (GossipText('%s #%s#.' % (attach_name(location_text, location, world), attach_name(item_text, location.item, world)),
|
||||
['Green', 'Red']), location)
|
||||
@@ -724,9 +724,9 @@ def get_entrance_hint(world, checked):
|
||||
|
||||
connected_region = entrance.connected_region
|
||||
if connected_region.dungeon:
|
||||
region_text = getHint(connected_region.dungeon.name, world.clearer_hints).text
|
||||
region_text = getHint(connected_region.dungeon.name, world.hint_rng, world.clearer_hints).text
|
||||
else:
|
||||
region_text = getHint(connected_region.name, world.clearer_hints).text
|
||||
region_text = getHint(connected_region.name, world.hint_rng, world.clearer_hints).text
|
||||
|
||||
if '#' not in region_text:
|
||||
region_text = '#%s#' % region_text
|
||||
@@ -882,10 +882,10 @@ def buildWorldGossipHints(world, checkedLocations=None):
|
||||
if location.name in world.hint_text_overrides:
|
||||
location_text = world.hint_text_overrides[location.name]
|
||||
else:
|
||||
location_text = getHint(location.name, world.clearer_hints).text
|
||||
location_text = getHint(location.name, world.hint_rng, world.clearer_hints).text
|
||||
if '#' not in location_text:
|
||||
location_text = '#%s#' % location_text
|
||||
item_text = getHint(getItemGenericName(location.item), world.clearer_hints).text
|
||||
item_text = getHint(getItemGenericName(location.item), world.hint_rng, world.clearer_hints).text
|
||||
add_hint(world, stoneGroups, GossipText('%s #%s#.' % (attach_name(location_text, location, world), attach_name(item_text, location.item, world)),
|
||||
['Green', 'Red']), hint_dist['always'][1], location, force_reachable=True)
|
||||
logging.getLogger('').debug('Placed always hint for %s.', location.name)
|
||||
@@ -1003,16 +1003,16 @@ def buildAltarHints(world, messages, include_rewards=True, include_wincons=True)
|
||||
('Goron Ruby', 'Red'),
|
||||
('Zora Sapphire', 'Blue'),
|
||||
]
|
||||
child_text += getHint('Spiritual Stone Text Start', world.clearer_hints).text + '\x04'
|
||||
child_text += getHint('Spiritual Stone Text Start', world.hint_rng, world.clearer_hints).text + '\x04'
|
||||
for (reward, color) in bossRewardsSpiritualStones:
|
||||
child_text += buildBossString(reward, color, world)
|
||||
child_text += getHint('Child Altar Text End', world.clearer_hints).text
|
||||
child_text += getHint('Child Altar Text End', world.hint_rng, world.clearer_hints).text
|
||||
child_text += '\x0B'
|
||||
update_message_by_id(messages, 0x707A, get_raw_text(child_text), 0x20)
|
||||
|
||||
# text that appears at altar as an adult.
|
||||
adult_text = '\x08'
|
||||
adult_text += getHint('Adult Altar Text Start', world.clearer_hints).text + '\x04'
|
||||
adult_text += getHint('Adult Altar Text Start', world.hint_rng, world.clearer_hints).text + '\x04'
|
||||
if include_rewards:
|
||||
bossRewardsMedallions = [
|
||||
('Light Medallion', 'Light Blue'),
|
||||
@@ -1029,7 +1029,7 @@ def buildAltarHints(world, messages, include_rewards=True, include_wincons=True)
|
||||
adult_text += '\x04'
|
||||
adult_text += buildGanonBossKeyString(world)
|
||||
else:
|
||||
adult_text += getHint('Adult Altar Text End', world.clearer_hints).text
|
||||
adult_text += getHint('Adult Altar Text End', world.hint_rng, world.clearer_hints).text
|
||||
adult_text += '\x0B'
|
||||
update_message_by_id(messages, 0x7057, get_raw_text(adult_text), 0x20)
|
||||
|
||||
@@ -1044,7 +1044,7 @@ def buildBossString(reward, color, world):
|
||||
text = GossipText(f"\x08\x13{item_icon}One in #@'s pocket#...", [color], prefix='')
|
||||
else:
|
||||
location = world.hinted_dungeon_reward_locations[reward]
|
||||
location_text = HintArea.at(location).text(world.clearer_hints, preposition=True)
|
||||
location_text = HintArea.at(location).text(world.hint_rng, world.clearer_hints, preposition=True)
|
||||
text = GossipText(f"\x08\x13{item_icon}One {location_text}...", [color], prefix='')
|
||||
return str(text) + '\x04'
|
||||
|
||||
@@ -1054,7 +1054,7 @@ def buildBridgeReqsString(world):
|
||||
if world.bridge == 'open':
|
||||
string += "The awakened ones will have #already created a bridge# to the castle where the evil dwells."
|
||||
else:
|
||||
item_req_string = getHint('bridge_' + world.bridge, world.clearer_hints).text
|
||||
item_req_string = getHint('bridge_' + world.bridge, world.hint_rng, world.clearer_hints).text
|
||||
if world.bridge == 'medallions':
|
||||
item_req_string = str(world.bridge_medallions) + ' ' + item_req_string
|
||||
elif world.bridge == 'stones':
|
||||
@@ -1077,7 +1077,7 @@ def buildGanonBossKeyString(world):
|
||||
string += "And the door to the \x05\x41evil one\x05\x40's chamber will be left #unlocked#."
|
||||
else:
|
||||
if world.shuffle_ganon_bosskey == 'on_lacs':
|
||||
item_req_string = getHint('lacs_' + world.lacs_condition, world.clearer_hints).text
|
||||
item_req_string = getHint('lacs_' + world.lacs_condition, world.hint_rng, world.clearer_hints).text
|
||||
if world.lacs_condition == 'medallions':
|
||||
item_req_string = str(world.lacs_medallions) + ' ' + item_req_string
|
||||
elif world.lacs_condition == 'stones':
|
||||
@@ -1092,7 +1092,7 @@ def buildGanonBossKeyString(world):
|
||||
item_req_string = '#%s#' % item_req_string
|
||||
bk_location_string = "provided by Zelda once %s are retrieved" % item_req_string
|
||||
elif world.shuffle_ganon_bosskey in ['stones', 'medallions', 'dungeons', 'tokens', 'hearts']:
|
||||
item_req_string = getHint('ganonBK_' + world.shuffle_ganon_bosskey, world.clearer_hints).text
|
||||
item_req_string = getHint('ganonBK_' + world.shuffle_ganon_bosskey, world.hint_rng, world.clearer_hints).text
|
||||
if world.shuffle_ganon_bosskey == 'medallions':
|
||||
item_req_string = str(world.ganon_bosskey_medallions) + ' ' + item_req_string
|
||||
elif world.shuffle_ganon_bosskey == 'stones':
|
||||
@@ -1107,7 +1107,7 @@ def buildGanonBossKeyString(world):
|
||||
item_req_string = '#%s#' % item_req_string
|
||||
bk_location_string = "automatically granted once %s are retrieved" % item_req_string
|
||||
else:
|
||||
bk_location_string = getHint('ganonBK_' + world.shuffle_ganon_bosskey, world.clearer_hints).text
|
||||
bk_location_string = getHint('ganonBK_' + world.shuffle_ganon_bosskey, world.hint_rng, world.clearer_hints).text
|
||||
string += "And the \x05\x41evil one\x05\x40's key will be %s." % bk_location_string
|
||||
return str(GossipText(string, ['Yellow'], prefix=''))
|
||||
|
||||
@@ -1142,16 +1142,16 @@ def buildMiscItemHints(world, messages):
|
||||
if location.player != world.player:
|
||||
player_text = world.multiworld.get_player_name(location.player) + "'s "
|
||||
if location.game == 'Ocarina of Time':
|
||||
area = HintArea.at(location, use_alt_hint=data['use_alt_hint']).text(world.clearer_hints, world=None)
|
||||
area = HintArea.at(location, use_alt_hint=data['use_alt_hint']).text(world.hint_rng, world.clearer_hints, world=None)
|
||||
else:
|
||||
area = location.name
|
||||
text = data['default_item_text'].format(area=rom_safe_text(player_text + area))
|
||||
elif 'default_item_fallback' in data:
|
||||
text = data['default_item_fallback']
|
||||
else:
|
||||
text = getHint('Validation Line', world.clearer_hints).text
|
||||
text = getHint('Validation Line', world.hint_rng, world.clearer_hints).text
|
||||
location = world.get_location('Ganons Tower Boss Key Chest')
|
||||
text += f"#{getHint(getItemGenericName(location.item), world.clearer_hints).text}#"
|
||||
text += f"#{getHint(getItemGenericName(location.item), world.hint_rng, world.clearer_hints).text}#"
|
||||
for find, replace in data.get('replace', {}).items():
|
||||
text = text.replace(find, replace)
|
||||
|
||||
@@ -1165,7 +1165,7 @@ def buildMiscLocationHints(world, messages):
|
||||
if hint_type in world.misc_hints:
|
||||
location = world.get_location(data['item_location'])
|
||||
item = location.item
|
||||
item_text = getHint(getItemGenericName(item), world.clearer_hints).text
|
||||
item_text = getHint(getItemGenericName(item), world.hint_rng, world.clearer_hints).text
|
||||
if item.player != world.player:
|
||||
item_text += f' for {world.multiworld.get_player_name(item.player)}'
|
||||
text = data['location_text'].format(item=rom_safe_text(item_text))
|
||||
|
||||
+22
-24
@@ -295,16 +295,14 @@ random = None
|
||||
|
||||
def get_junk_pool(ootworld):
|
||||
junk_pool[:] = list(junk_pool_base)
|
||||
if ootworld.junk_ice_traps == 'on':
|
||||
if ootworld.options.junk_ice_traps == 'on':
|
||||
junk_pool.append(('Ice Trap', 10))
|
||||
elif ootworld.junk_ice_traps in ['mayhem', 'onslaught']:
|
||||
elif ootworld.options.junk_ice_traps in ['mayhem', 'onslaught']:
|
||||
junk_pool[:] = [('Ice Trap', 1)]
|
||||
return junk_pool
|
||||
|
||||
|
||||
def get_junk_item(count=1, pool=None, plando_pool=None):
|
||||
global random
|
||||
|
||||
def get_junk_item(rand, count=1, pool=None, plando_pool=None):
|
||||
if count < 1:
|
||||
raise ValueError("get_junk_item argument 'count' must be greater than 0.")
|
||||
|
||||
@@ -323,17 +321,17 @@ def get_junk_item(count=1, pool=None, plando_pool=None):
|
||||
raise RuntimeError("Not enough junk is available in the item pool to replace removed items.")
|
||||
else:
|
||||
junk_items, junk_weights = zip(*junk_pool)
|
||||
return_pool.extend(random.choices(junk_items, weights=junk_weights, k=count))
|
||||
return_pool.extend(rand.choices(junk_items, weights=junk_weights, k=count))
|
||||
|
||||
return return_pool
|
||||
|
||||
|
||||
def replace_max_item(items, item, max):
|
||||
def replace_max_item(items, item, max, rand):
|
||||
count = 0
|
||||
for i,val in enumerate(items):
|
||||
if val == item:
|
||||
if count >= max:
|
||||
items[i] = get_junk_item()[0]
|
||||
items[i] = get_junk_item(rand)[0]
|
||||
count += 1
|
||||
|
||||
|
||||
@@ -375,7 +373,7 @@ def get_pool_core(world):
|
||||
pending_junk_pool.append('Kokiri Sword')
|
||||
if world.shuffle_ocarinas:
|
||||
pending_junk_pool.append('Ocarina')
|
||||
if world.shuffle_beans and world.multiworld.start_inventory[world.player].value.get('Magic Bean Pack', 0):
|
||||
if world.shuffle_beans and world.options.start_inventory.value.get('Magic Bean Pack', 0):
|
||||
pending_junk_pool.append('Magic Bean Pack')
|
||||
if (world.gerudo_fortress != "open"
|
||||
and world.shuffle_hideoutkeys in ['any_dungeon', 'overworld', 'keysanity', 'regional']):
|
||||
@@ -450,7 +448,7 @@ def get_pool_core(world):
|
||||
else:
|
||||
item = deku_scrubs_items[location.vanilla_item]
|
||||
if isinstance(item, list):
|
||||
item = random.choices([i[0] for i in item], weights=[i[1] for i in item], k=1)[0]
|
||||
item = world.random.choices([i[0] for i in item], weights=[i[1] for i in item], k=1)[0]
|
||||
shuffle_item = True
|
||||
|
||||
# Kokiri Sword
|
||||
@@ -489,7 +487,7 @@ def get_pool_core(world):
|
||||
# Cows
|
||||
elif location.vanilla_item == 'Milk':
|
||||
if world.shuffle_cows:
|
||||
item = get_junk_item()[0]
|
||||
item = get_junk_item(world.random)[0]
|
||||
shuffle_item = world.shuffle_cows
|
||||
if not shuffle_item:
|
||||
location.show_in_spoiler = False
|
||||
@@ -508,13 +506,13 @@ def get_pool_core(world):
|
||||
item = 'Rutos Letter'
|
||||
ruto_bottles -= 1
|
||||
else:
|
||||
item = random.choice(normal_bottles)
|
||||
item = world.random.choice(normal_bottles)
|
||||
shuffle_item = True
|
||||
|
||||
# Magic Beans
|
||||
elif location.vanilla_item == 'Buy Magic Bean':
|
||||
if world.shuffle_beans:
|
||||
item = 'Magic Bean Pack' if not world.multiworld.start_inventory[world.player].value.get('Magic Bean Pack', 0) else get_junk_item()[0]
|
||||
item = 'Magic Bean Pack' if not world.options.start_inventory.value.get('Magic Bean Pack', 0) else get_junk_item(world.random)[0]
|
||||
shuffle_item = world.shuffle_beans
|
||||
if not shuffle_item:
|
||||
location.show_in_spoiler = False
|
||||
@@ -528,7 +526,7 @@ def get_pool_core(world):
|
||||
# Adult Trade Item
|
||||
elif location.vanilla_item == 'Pocket Egg':
|
||||
potential_trade_items = world.adult_trade_start if world.adult_trade_start else trade_items
|
||||
item = random.choice(sorted(potential_trade_items))
|
||||
item = world.random.choice(sorted(potential_trade_items))
|
||||
world.selected_adult_trade_item = item
|
||||
shuffle_item = True
|
||||
|
||||
@@ -541,7 +539,7 @@ def get_pool_core(world):
|
||||
shuffle_item = False
|
||||
location.show_in_spoiler = False
|
||||
if shuffle_item and world.gerudo_fortress == 'normal' and 'Thieves Hideout' in world.key_rings:
|
||||
item = get_junk_item()[0] if location.name != 'Hideout 1 Torch Jail Gerudo Key' else 'Small Key Ring (Thieves Hideout)'
|
||||
item = get_junk_item(world.random)[0] if location.name != 'Hideout 1 Torch Jail Gerudo Key' else 'Small Key Ring (Thieves Hideout)'
|
||||
|
||||
# Freestanding Rupees and Hearts
|
||||
elif location.type in ['ActorOverride', 'Freestanding', 'RupeeTower']:
|
||||
@@ -618,7 +616,7 @@ def get_pool_core(world):
|
||||
elif dungeon.name in world.key_rings and not dungeon.small_keys:
|
||||
item = dungeon.item_name("Small Key Ring")
|
||||
elif dungeon.name in world.key_rings:
|
||||
item = get_junk_item()[0]
|
||||
item = get_junk_item(world.random)[0]
|
||||
shuffle_item = True
|
||||
# Any other item in a dungeon.
|
||||
elif location.type in ["Chest", "NPC", "Song", "Collectable", "Cutscene", "BossHeart"]:
|
||||
@@ -630,7 +628,7 @@ def get_pool_core(world):
|
||||
if shuffle_setting in ['remove', 'startwith']:
|
||||
world.multiworld.push_precollected(dungeon_collection[-1])
|
||||
world.remove_from_start_inventory.append(dungeon_collection[-1].name)
|
||||
item = get_junk_item()[0]
|
||||
item = get_junk_item(world.random)[0]
|
||||
shuffle_item = True
|
||||
elif shuffle_setting in ['any_dungeon', 'overworld', 'regional']:
|
||||
dungeon_collection[-1].priority = True
|
||||
@@ -658,9 +656,9 @@ def get_pool_core(world):
|
||||
shop_non_item_count = len(world.shop_prices)
|
||||
shop_item_count = shop_slots_count - shop_non_item_count
|
||||
|
||||
pool.extend(random.sample(remain_shop_items, shop_item_count))
|
||||
pool.extend(world.random.sample(remain_shop_items, shop_item_count))
|
||||
if shop_non_item_count:
|
||||
pool.extend(get_junk_item(shop_non_item_count))
|
||||
pool.extend(get_junk_item(world.random, shop_non_item_count))
|
||||
|
||||
# Extra rupees for shopsanity.
|
||||
if world.shopsanity not in ['off', '0']:
|
||||
@@ -706,19 +704,19 @@ def get_pool_core(world):
|
||||
|
||||
if world.shuffle_ganon_bosskey in ['stones', 'medallions', 'dungeons', 'tokens', 'hearts', 'triforce']:
|
||||
placed_items['Gift from Sages'] = 'Boss Key (Ganons Castle)'
|
||||
pool.extend(get_junk_item())
|
||||
pool.extend(get_junk_item(world.random))
|
||||
else:
|
||||
placed_items['Gift from Sages'] = IGNORE_LOCATION
|
||||
world.get_location('Gift from Sages').show_in_spoiler = False
|
||||
|
||||
if world.junk_ice_traps == 'off':
|
||||
replace_max_item(pool, 'Ice Trap', 0)
|
||||
replace_max_item(pool, 'Ice Trap', 0, world.random)
|
||||
elif world.junk_ice_traps == 'onslaught':
|
||||
for item in [item for item, weight in junk_pool_base] + ['Recovery Heart', 'Bombs (20)', 'Arrows (30)']:
|
||||
replace_max_item(pool, item, 0)
|
||||
replace_max_item(pool, item, 0, world.random)
|
||||
|
||||
for item, maximum in item_difficulty_max[world.item_pool_value].items():
|
||||
replace_max_item(pool, item, maximum)
|
||||
replace_max_item(pool, item, maximum, world.random)
|
||||
|
||||
# world.distribution.alter_pool(world, pool)
|
||||
|
||||
@@ -748,7 +746,7 @@ def get_pool_core(world):
|
||||
pending_item = pending_junk_pool.pop()
|
||||
if not junk_candidates:
|
||||
raise RuntimeError("Not enough junk exists in item pool for %s (+%d others) to be added." % (pending_item, len(pending_junk_pool) - 1))
|
||||
junk_item = random.choice(junk_candidates)
|
||||
junk_item = world.random.choice(junk_candidates)
|
||||
junk_candidates.remove(junk_item)
|
||||
pool.remove(junk_item)
|
||||
pool.append(pending_item)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# text details: https://wiki.cloudmodding.com/oot/Text_Format
|
||||
|
||||
import random
|
||||
from .HintList import misc_item_hint_table, misc_location_hint_table
|
||||
from .TextBox import line_wrap
|
||||
from .Utils import find_last
|
||||
@@ -969,7 +968,7 @@ def repack_messages(rom, messages, permutation=None, always_allow_skip=True, spe
|
||||
rom.write_bytes(entry_offset, [0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
|
||||
|
||||
# shuffles the messages in the game, making sure to keep various message types in their own group
|
||||
def shuffle_messages(messages, except_hints=True, always_allow_skip=True):
|
||||
def shuffle_messages(messages, rand, except_hints=True, always_allow_skip=True):
|
||||
|
||||
permutation = [i for i, _ in enumerate(messages)]
|
||||
|
||||
@@ -1002,7 +1001,7 @@ def shuffle_messages(messages, except_hints=True, always_allow_skip=True):
|
||||
|
||||
def shuffle_group(group):
|
||||
group_permutation = [i for i, _ in enumerate(group)]
|
||||
random.shuffle(group_permutation)
|
||||
rand.shuffle(group_permutation)
|
||||
|
||||
for index_from, index_to in enumerate(group_permutation):
|
||||
permutation[group[index_to].index] = group[index_from].index
|
||||
|
||||
+8
-9
@@ -1,6 +1,5 @@
|
||||
#Much of this is heavily inspired from and/or based on az64's / Deathbasket's MM randomizer
|
||||
|
||||
import random
|
||||
import os
|
||||
from .Utils import compare_version, data_path
|
||||
|
||||
@@ -175,7 +174,7 @@ def process_sequences(rom, sequences, target_sequences, disabled_source_sequence
|
||||
return sequences, target_sequences
|
||||
|
||||
|
||||
def shuffle_music(sequences, target_sequences, music_mapping, log):
|
||||
def shuffle_music(sequences, target_sequences, music_mapping, log, rand):
|
||||
sequence_dict = {}
|
||||
sequence_ids = []
|
||||
|
||||
@@ -191,7 +190,7 @@ def shuffle_music(sequences, target_sequences, music_mapping, log):
|
||||
# Shuffle the sequences
|
||||
if len(sequences) < len(target_sequences):
|
||||
raise Exception(f"Not enough custom music/fanfares ({len(sequences)}) to omit base Ocarina of Time sequences ({len(target_sequences)}).")
|
||||
random.shuffle(sequence_ids)
|
||||
rand.shuffle(sequence_ids)
|
||||
|
||||
sequences = []
|
||||
for target_sequence in target_sequences:
|
||||
@@ -328,7 +327,7 @@ def rebuild_sequences(rom, sequences):
|
||||
rom.write_byte(base, j.instrument_set)
|
||||
|
||||
|
||||
def shuffle_pointers_table(rom, ids, music_mapping, log):
|
||||
def shuffle_pointers_table(rom, ids, music_mapping, log, rand):
|
||||
# Read in all the Music data
|
||||
bgm_data = {}
|
||||
bgm_ids = []
|
||||
@@ -341,7 +340,7 @@ def shuffle_pointers_table(rom, ids, music_mapping, log):
|
||||
bgm_ids.append(bgm[0])
|
||||
|
||||
# shuffle data
|
||||
random.shuffle(bgm_ids)
|
||||
rand.shuffle(bgm_ids)
|
||||
|
||||
# Write Music data back in random ordering
|
||||
for bgm in ids:
|
||||
@@ -424,13 +423,13 @@ def randomize_music(rom, ootworld, music_mapping):
|
||||
# process_sequences(rom, sequences, target_sequences, disabled_source_sequences, disabled_target_sequences, bgm_ids)
|
||||
# if ootworld.background_music == 'random_custom_only':
|
||||
# sequences = [seq for seq in sequences if seq.cosmetic_name not in [x[0] for x in bgm_ids] or seq.cosmetic_name in music_mapping.values()]
|
||||
# sequences, log = shuffle_music(sequences, target_sequences, music_mapping, log)
|
||||
# sequences, log = shuffle_music(sequences, target_sequences, music_mapping, log, ootworld.random)
|
||||
|
||||
# if ootworld.fanfares in ['random', 'random_custom_only'] or ff_mapped or ocarina_mapped:
|
||||
# process_sequences(rom, fanfare_sequences, fanfare_target_sequences, disabled_source_sequences, disabled_target_sequences, ff_ids, 'fanfare')
|
||||
# if ootworld.fanfares == 'random_custom_only':
|
||||
# fanfare_sequences = [seq for seq in fanfare_sequences if seq.cosmetic_name not in [x[0] for x in fanfare_sequence_ids] or seq.cosmetic_name in music_mapping.values()]
|
||||
# fanfare_sequences, log = shuffle_music(fanfare_sequences, fanfare_target_sequences, music_mapping, log)
|
||||
# fanfare_sequences, log = shuffle_music(fanfare_sequences, fanfare_target_sequences, music_mapping, log, ootworld.random)
|
||||
|
||||
# if disabled_source_sequences:
|
||||
# log = disable_music(rom, disabled_source_sequences.values(), log)
|
||||
@@ -438,10 +437,10 @@ def randomize_music(rom, ootworld, music_mapping):
|
||||
# rebuild_sequences(rom, sequences + fanfare_sequences)
|
||||
# else:
|
||||
if ootworld.background_music == 'randomized' or bgm_mapped:
|
||||
log = shuffle_pointers_table(rom, bgm_ids, music_mapping, log)
|
||||
log = shuffle_pointers_table(rom, bgm_ids, music_mapping, log, ootworld.random)
|
||||
|
||||
if ootworld.fanfares == 'randomized' or ff_mapped or ocarina_mapped:
|
||||
log = shuffle_pointers_table(rom, ff_ids, music_mapping, log)
|
||||
log = shuffle_pointers_table(rom, ff_ids, music_mapping, log, ootworld.random)
|
||||
# end_else
|
||||
if disabled_target_sequences:
|
||||
log = disable_music(rom, disabled_target_sequences.values(), log)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import struct
|
||||
import random
|
||||
import io
|
||||
import array
|
||||
import zlib
|
||||
@@ -88,7 +87,7 @@ def write_block_section(start, key_skip, in_data, patch_data, is_continue):
|
||||
# xor_range is the range the XOR key will read from. This range is not
|
||||
# too important, but I tried to choose from a section that didn't really
|
||||
# have big gaps of 0s which we want to avoid.
|
||||
def create_patch_file(rom, xor_range=(0x00B8AD30, 0x00F029A0)):
|
||||
def create_patch_file(rom, rand, xor_range=(0x00B8AD30, 0x00F029A0)):
|
||||
dma_start, dma_end = rom.get_dma_table_range()
|
||||
|
||||
# add header
|
||||
@@ -100,7 +99,7 @@ def create_patch_file(rom, xor_range=(0x00B8AD30, 0x00F029A0)):
|
||||
|
||||
# get random xor key. This range is chosen because it generally
|
||||
# doesn't have many sections of 0s
|
||||
xor_address = random.Random().randint(*xor_range)
|
||||
xor_address = rand.randint(*xor_range)
|
||||
patch_data.append_int32(xor_address)
|
||||
|
||||
new_buffer = copy.copy(rom.original.buffer)
|
||||
|
||||
+166
-19
@@ -1,6 +1,8 @@
|
||||
import typing
|
||||
import random
|
||||
from Options import Option, DefaultOnToggle, Toggle, Range, OptionList, OptionSet, DeathLink, PlandoConnections
|
||||
from dataclasses import dataclass
|
||||
from Options import Option, DefaultOnToggle, Toggle, Range, OptionList, OptionSet, DeathLink, PlandoConnections, \
|
||||
PerGameCommonOptions, OptionGroup
|
||||
from .EntranceShuffle import entrance_shuffle_table
|
||||
from .LogicTricks import normalized_name_tricks
|
||||
from .ColorSFXOptions import *
|
||||
@@ -1281,21 +1283,166 @@ class LogicTricks(OptionList):
|
||||
valid_keys_casefold = True
|
||||
|
||||
|
||||
# All options assembled into a single dict
|
||||
oot_options: typing.Dict[str, type(Option)] = {
|
||||
"plando_connections": OoTPlandoConnections,
|
||||
"logic_rules": Logic,
|
||||
"logic_no_night_tokens_without_suns_song": NightTokens,
|
||||
**open_options,
|
||||
**world_options,
|
||||
**bridge_options,
|
||||
**dungeon_items_options,
|
||||
**shuffle_options,
|
||||
**timesavers_options,
|
||||
**misc_options,
|
||||
**itempool_options,
|
||||
**cosmetic_options,
|
||||
**sfx_options,
|
||||
"logic_tricks": LogicTricks,
|
||||
"death_link": DeathLink,
|
||||
}
|
||||
@dataclass
|
||||
class OoTOptions(PerGameCommonOptions):
|
||||
plando_connections: OoTPlandoConnections
|
||||
death_link: DeathLink
|
||||
logic_rules: Logic
|
||||
logic_no_night_tokens_without_suns_song: NightTokens
|
||||
logic_tricks: LogicTricks
|
||||
open_forest: Forest
|
||||
open_kakariko: Gate
|
||||
open_door_of_time: DoorOfTime
|
||||
zora_fountain: Fountain
|
||||
gerudo_fortress: Fortress
|
||||
bridge: Bridge
|
||||
trials: Trials
|
||||
starting_age: StartingAge
|
||||
shuffle_interior_entrances: InteriorEntrances
|
||||
shuffle_grotto_entrances: GrottoEntrances
|
||||
shuffle_dungeon_entrances: DungeonEntrances
|
||||
shuffle_overworld_entrances: OverworldEntrances
|
||||
owl_drops: OwlDrops
|
||||
warp_songs: WarpSongs
|
||||
spawn_positions: SpawnPositions
|
||||
shuffle_bosses: BossEntrances
|
||||
# mix_entrance_pools: MixEntrancePools
|
||||
# decouple_entrances: DecoupleEntrances
|
||||
triforce_hunt: TriforceHunt
|
||||
triforce_goal: TriforceGoal
|
||||
extra_triforce_percentage: ExtraTriforces
|
||||
bombchus_in_logic: LogicalChus
|
||||
dungeon_shortcuts: DungeonShortcuts
|
||||
dungeon_shortcuts_list: DungeonShortcutsList
|
||||
mq_dungeons_mode: MQDungeons
|
||||
mq_dungeons_list: MQDungeonList
|
||||
mq_dungeons_count: MQDungeonCount
|
||||
# empty_dungeons_mode: EmptyDungeons
|
||||
# empty_dungeons_list: EmptyDungeonList
|
||||
# empty_dungeon_count: EmptyDungeonCount
|
||||
bridge_stones: BridgeStones
|
||||
bridge_medallions: BridgeMedallions
|
||||
bridge_rewards: BridgeRewards
|
||||
bridge_tokens: BridgeTokens
|
||||
bridge_hearts: BridgeHearts
|
||||
shuffle_mapcompass: ShuffleMapCompass
|
||||
shuffle_smallkeys: ShuffleKeys
|
||||
shuffle_hideoutkeys: ShuffleGerudoKeys
|
||||
shuffle_bosskeys: ShuffleBossKeys
|
||||
enhance_map_compass: EnhanceMC
|
||||
shuffle_ganon_bosskey: ShuffleGanonBK
|
||||
ganon_bosskey_medallions: GanonBKMedallions
|
||||
ganon_bosskey_stones: GanonBKStones
|
||||
ganon_bosskey_rewards: GanonBKRewards
|
||||
ganon_bosskey_tokens: GanonBKTokens
|
||||
ganon_bosskey_hearts: GanonBKHearts
|
||||
key_rings: KeyRings
|
||||
key_rings_list: KeyRingList
|
||||
shuffle_song_items: SongShuffle
|
||||
shopsanity: ShopShuffle
|
||||
shop_slots: ShopSlots
|
||||
shopsanity_prices: ShopPrices
|
||||
tokensanity: TokenShuffle
|
||||
shuffle_scrubs: ScrubShuffle
|
||||
shuffle_child_trade: ShuffleChildTrade
|
||||
shuffle_freestanding_items: ShuffleFreestanding
|
||||
shuffle_pots: ShufflePots
|
||||
shuffle_crates: ShuffleCrates
|
||||
shuffle_cows: ShuffleCows
|
||||
shuffle_beehives: ShuffleBeehives
|
||||
shuffle_kokiri_sword: ShuffleSword
|
||||
shuffle_ocarinas: ShuffleOcarinas
|
||||
shuffle_gerudo_card: ShuffleCard
|
||||
shuffle_beans: ShuffleBeans
|
||||
shuffle_medigoron_carpet_salesman: ShuffleMedigoronCarpet
|
||||
shuffle_frog_song_rupees: ShuffleFrogRupees
|
||||
no_escape_sequence: SkipEscape
|
||||
no_guard_stealth: SkipStealth
|
||||
no_epona_race: SkipEponaRace
|
||||
skip_some_minigame_phases: SkipMinigamePhases
|
||||
complete_mask_quest: CompleteMaskQuest
|
||||
useful_cutscenes: UsefulCutscenes
|
||||
fast_chests: FastChests
|
||||
free_scarecrow: FreeScarecrow
|
||||
fast_bunny_hood: FastBunny
|
||||
plant_beans: PlantBeans
|
||||
chicken_count: ChickenCount
|
||||
big_poe_count: BigPoeCount
|
||||
fae_torch_count: FAETorchCount
|
||||
correct_chest_appearances: CorrectChestAppearance
|
||||
minor_items_as_major_chest: MinorInMajor
|
||||
invisible_chests: InvisibleChests
|
||||
correct_potcrate_appearances: CorrectPotCrateAppearance
|
||||
hints: Hints
|
||||
misc_hints: MiscHints
|
||||
hint_dist: HintDistribution
|
||||
text_shuffle: TextShuffle
|
||||
damage_multiplier: DamageMultiplier
|
||||
deadly_bonks: DeadlyBonks
|
||||
no_collectible_hearts: HeroMode
|
||||
starting_tod: StartingToD
|
||||
blue_fire_arrows: BlueFireArrows
|
||||
fix_broken_drops: FixBrokenDrops
|
||||
start_with_consumables: ConsumableStart
|
||||
start_with_rupees: RupeeStart
|
||||
item_pool_value: ItemPoolValue
|
||||
junk_ice_traps: IceTraps
|
||||
ice_trap_appearance: IceTrapVisual
|
||||
adult_trade_start: AdultTradeStart
|
||||
default_targeting: Targeting
|
||||
display_dpad: DisplayDpad
|
||||
dpad_dungeon_menu: DpadDungeonMenu
|
||||
correct_model_colors: CorrectColors
|
||||
background_music: BackgroundMusic
|
||||
fanfares: Fanfares
|
||||
ocarina_fanfares: OcarinaFanfares
|
||||
kokiri_color: kokiri_color
|
||||
goron_color: goron_color
|
||||
zora_color: zora_color
|
||||
silver_gauntlets_color: silver_gauntlets_color
|
||||
golden_gauntlets_color: golden_gauntlets_color
|
||||
mirror_shield_frame_color: mirror_shield_frame_color
|
||||
navi_color_default_inner: navi_color_default_inner
|
||||
navi_color_default_outer: navi_color_default_outer
|
||||
navi_color_enemy_inner: navi_color_enemy_inner
|
||||
navi_color_enemy_outer: navi_color_enemy_outer
|
||||
navi_color_npc_inner: navi_color_npc_inner
|
||||
navi_color_npc_outer: navi_color_npc_outer
|
||||
navi_color_prop_inner: navi_color_prop_inner
|
||||
navi_color_prop_outer: navi_color_prop_outer
|
||||
sword_trail_duration: SwordTrailDuration
|
||||
sword_trail_color_inner: sword_trail_color_inner
|
||||
sword_trail_color_outer: sword_trail_color_outer
|
||||
bombchu_trail_color_inner: bombchu_trail_color_inner
|
||||
bombchu_trail_color_outer: bombchu_trail_color_outer
|
||||
boomerang_trail_color_inner: boomerang_trail_color_inner
|
||||
boomerang_trail_color_outer: boomerang_trail_color_outer
|
||||
heart_color: heart_color
|
||||
magic_color: magic_color
|
||||
a_button_color: a_button_color
|
||||
b_button_color: b_button_color
|
||||
c_button_color: c_button_color
|
||||
start_button_color: start_button_color
|
||||
sfx_navi_overworld: sfx_navi_overworld
|
||||
sfx_navi_enemy: sfx_navi_enemy
|
||||
sfx_low_hp: sfx_low_hp
|
||||
sfx_menu_cursor: sfx_menu_cursor
|
||||
sfx_menu_select: sfx_menu_select
|
||||
sfx_nightfall: sfx_nightfall
|
||||
sfx_horse_neigh: sfx_horse_neigh
|
||||
sfx_hover_boots: sfx_hover_boots
|
||||
sfx_ocarina: SfxOcarina
|
||||
|
||||
|
||||
oot_option_groups: typing.List[OptionGroup] = [
|
||||
OptionGroup("Open", [option for option in open_options.values()]),
|
||||
OptionGroup("World", [*[option for option in world_options.values()],
|
||||
*[option for option in bridge_options.values()]]),
|
||||
OptionGroup("Shuffle", [option for option in shuffle_options.values()]),
|
||||
OptionGroup("Dungeon Items", [option for option in dungeon_items_options.values()]),
|
||||
OptionGroup("Timesavers", [option for option in timesavers_options.values()]),
|
||||
OptionGroup("Misc", [option for option in misc_options.values()]),
|
||||
OptionGroup("Item Pool", [option for option in itempool_options.values()]),
|
||||
OptionGroup("Cosmetics", [option for option in cosmetic_options.values()]),
|
||||
OptionGroup("SFX", [option for option in sfx_options.values()])
|
||||
]
|
||||
|
||||
+13
-13
@@ -208,8 +208,8 @@ def patch_rom(world, rom):
|
||||
|
||||
# Fix Ice Cavern Alcove Camera
|
||||
if not world.dungeon_mq['Ice Cavern']:
|
||||
rom.write_byte(0x2BECA25,0x01);
|
||||
rom.write_byte(0x2BECA2D,0x01);
|
||||
rom.write_byte(0x2BECA25,0x01)
|
||||
rom.write_byte(0x2BECA2D,0x01)
|
||||
|
||||
# Fix GS rewards to be static
|
||||
rom.write_int32(0xEA3934, 0)
|
||||
@@ -944,7 +944,7 @@ def patch_rom(world, rom):
|
||||
|
||||
scene_table = 0x00B71440
|
||||
for scene in range(0x00, 0x65):
|
||||
scene_start = rom.read_int32(scene_table + (scene * 0x14));
|
||||
scene_start = rom.read_int32(scene_table + (scene * 0x14))
|
||||
add_scene_exits(scene_start)
|
||||
|
||||
return exit_table
|
||||
@@ -1632,10 +1632,10 @@ def patch_rom(world, rom):
|
||||
reward_text = None
|
||||
elif getattr(location.item, 'looks_like_item', None) is not None:
|
||||
jabu_item = location.item.looks_like_item
|
||||
reward_text = create_fake_name(getHint(getItemGenericName(location.item.looks_like_item), True).text)
|
||||
reward_text = create_fake_name(getHint(getItemGenericName(location.item.looks_like_item), world.hint_rng, True).text)
|
||||
else:
|
||||
jabu_item = location.item
|
||||
reward_text = getHint(getItemGenericName(location.item), True).text
|
||||
reward_text = getHint(getItemGenericName(location.item), world.hint_rng, True).text
|
||||
|
||||
# Update "Princess Ruto got the Spiritual Stone!" text before the midboss in Jabu
|
||||
if reward_text is None:
|
||||
@@ -1687,7 +1687,7 @@ def patch_rom(world, rom):
|
||||
|
||||
# Sets hooks for gossip stone changes
|
||||
|
||||
symbol = rom.sym("GOSSIP_HINT_CONDITION");
|
||||
symbol = rom.sym("GOSSIP_HINT_CONDITION")
|
||||
|
||||
if world.hints == 'none':
|
||||
rom.write_int32(symbol, 0)
|
||||
@@ -2264,9 +2264,9 @@ def patch_rom(world, rom):
|
||||
|
||||
# text shuffle
|
||||
if world.text_shuffle == 'except_hints':
|
||||
permutation = shuffle_messages(messages, except_hints=True)
|
||||
permutation = shuffle_messages(messages, world.random, except_hints=True)
|
||||
elif world.text_shuffle == 'complete':
|
||||
permutation = shuffle_messages(messages, except_hints=False)
|
||||
permutation = shuffle_messages(messages, world.random, except_hints=False)
|
||||
|
||||
# update warp song preview text boxes
|
||||
update_warp_song_text(messages, world)
|
||||
@@ -2358,7 +2358,7 @@ def patch_rom(world, rom):
|
||||
|
||||
# Write numeric seed truncated to 32 bits for rng seeding
|
||||
# Overwritten with new seed every time a new rng value is generated
|
||||
rng_seed = world.multiworld.per_slot_randoms[world.player].getrandbits(32)
|
||||
rng_seed = world.random.getrandbits(32)
|
||||
rom.write_int32(rom.sym('RNG_SEED_INT'), rng_seed)
|
||||
# Static initial seed value for one-time random actions like the Hylian Shield discount
|
||||
rom.write_int32(rom.sym('RANDOMIZER_RNG_SEED'), rng_seed)
|
||||
@@ -2560,7 +2560,7 @@ def scene_get_actors(rom, actor_func, scene_data, scene, alternate=None, process
|
||||
room_count = rom.read_byte(scene_data + 1)
|
||||
room_list = scene_start + (rom.read_int32(scene_data + 4) & 0x00FFFFFF)
|
||||
for _ in range(0, room_count):
|
||||
room_data = rom.read_int32(room_list);
|
||||
room_data = rom.read_int32(room_list)
|
||||
|
||||
if not room_data in processed_rooms:
|
||||
actors.update(room_get_actors(rom, actor_func, room_data, scene))
|
||||
@@ -2591,7 +2591,7 @@ def get_actor_list(rom, actor_func):
|
||||
actors = {}
|
||||
scene_table = 0x00B71440
|
||||
for scene in range(0x00, 0x65):
|
||||
scene_data = rom.read_int32(scene_table + (scene * 0x14));
|
||||
scene_data = rom.read_int32(scene_table + (scene * 0x14))
|
||||
actors.update(scene_get_actors(rom, actor_func, scene_data, scene))
|
||||
return actors
|
||||
|
||||
@@ -2605,7 +2605,7 @@ def get_override_itemid(override_table, scene, type, flags):
|
||||
def remove_entrance_blockers(rom):
|
||||
def remove_entrance_blockers_do(rom, actor_id, actor, scene):
|
||||
if actor_id == 0x014E and scene == 97:
|
||||
actor_var = rom.read_int16(actor + 14);
|
||||
actor_var = rom.read_int16(actor + 14)
|
||||
if actor_var == 0xFF01:
|
||||
rom.write_int16(actor + 14, 0x0700)
|
||||
get_actor_list(rom, remove_entrance_blockers_do)
|
||||
@@ -2789,7 +2789,7 @@ def place_shop_items(rom, world, shop_items, messages, locations, init_shop_id=F
|
||||
purchase_text = '\x08%s %d Rupees\x09\x01%s\x01\x1B\x05\x42Buy\x01Don\'t buy\x05\x40\x02' % (split_item_name[0], location.price, split_item_name[1])
|
||||
else:
|
||||
if item_display.game == "Ocarina of Time":
|
||||
shop_item_name = getSimpleHintNoPrefix(item_display)
|
||||
shop_item_name = getSimpleHintNoPrefix(item_display, world.random)
|
||||
else:
|
||||
shop_item_name = item_display.name
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+14
-14
@@ -53,7 +53,7 @@ def isliteral(expr):
|
||||
class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
|
||||
def __init__(self, world, player):
|
||||
self.multiworld = world
|
||||
self.world = world
|
||||
self.player = player
|
||||
self.events = set()
|
||||
# map Region -> rule ast string -> item name
|
||||
@@ -86,9 +86,9 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
ctx=ast.Load()),
|
||||
args=[ast.Str(escaped_items[node.id]), ast.Constant(self.player)],
|
||||
keywords=[])
|
||||
elif node.id in self.multiworld.__dict__:
|
||||
elif node.id in self.world.__dict__:
|
||||
# Settings are constant
|
||||
return ast.parse('%r' % self.multiworld.__dict__[node.id], mode='eval').body
|
||||
return ast.parse('%r' % self.world.__dict__[node.id], mode='eval').body
|
||||
elif node.id in State.__dict__:
|
||||
return self.make_call(node, node.id, [], [])
|
||||
elif node.id in self.kwarg_defaults or node.id in allowed_globals:
|
||||
@@ -137,7 +137,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
|
||||
if isinstance(count, ast.Name):
|
||||
# Must be a settings constant
|
||||
count = ast.parse('%r' % self.multiworld.__dict__[count.id], mode='eval').body
|
||||
count = ast.parse('%r' % self.world.__dict__[count.id], mode='eval').body
|
||||
|
||||
if iname in escaped_items:
|
||||
iname = escaped_items[iname]
|
||||
@@ -182,7 +182,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
new_args = []
|
||||
for child in node.args:
|
||||
if isinstance(child, ast.Name):
|
||||
if child.id in self.multiworld.__dict__:
|
||||
if child.id in self.world.__dict__:
|
||||
# child = ast.Attribute(
|
||||
# value=ast.Attribute(
|
||||
# value=ast.Name(id='state', ctx=ast.Load()),
|
||||
@@ -190,7 +190,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
# ctx=ast.Load()),
|
||||
# attr=child.id,
|
||||
# ctx=ast.Load())
|
||||
child = ast.Constant(getattr(self.multiworld, child.id))
|
||||
child = ast.Constant(getattr(self.world, child.id))
|
||||
elif child.id in rule_aliases:
|
||||
child = self.visit(child)
|
||||
elif child.id in escaped_items:
|
||||
@@ -242,7 +242,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
# Fast check for json can_use
|
||||
if (len(node.ops) == 1 and isinstance(node.ops[0], ast.Eq)
|
||||
and isinstance(node.left, ast.Name) and isinstance(node.comparators[0], ast.Name)
|
||||
and node.left.id not in self.multiworld.__dict__ and node.comparators[0].id not in self.multiworld.__dict__):
|
||||
and node.left.id not in self.world.__dict__ and node.comparators[0].id not in self.world.__dict__):
|
||||
return ast.NameConstant(node.left.id == node.comparators[0].id)
|
||||
|
||||
node.left = escape_or_string(node.left)
|
||||
@@ -378,7 +378,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
# Requires the target regions have been defined in the world.
|
||||
def create_delayed_rules(self):
|
||||
for region_name, node, subrule_name in self.delayed_rules:
|
||||
region = self.multiworld.multiworld.get_region(region_name, self.player)
|
||||
region = self.world.multiworld.get_region(region_name, self.player)
|
||||
event = OOTLocation(self.player, subrule_name, type='Event', parent=region, internal=True)
|
||||
event.show_in_spoiler = False
|
||||
|
||||
@@ -395,7 +395,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
set_rule(event, access_rule)
|
||||
region.locations.append(event)
|
||||
|
||||
self.multiworld.make_event_item(subrule_name, event)
|
||||
self.world.make_event_item(subrule_name, event)
|
||||
# Safeguard in case this is called multiple times per world
|
||||
self.delayed_rules.clear()
|
||||
|
||||
@@ -448,7 +448,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
## Handlers for compile-time optimizations (former State functions)
|
||||
|
||||
def at_day(self, node):
|
||||
if self.multiworld.ensure_tod_access:
|
||||
if self.world.ensure_tod_access:
|
||||
# tod has DAY or (tod == NONE and (ss or find a path from a provider))
|
||||
# parsing is better than constructing this expression by hand
|
||||
r = self.current_spot if type(self.current_spot) == OOTRegion else self.current_spot.parent_region
|
||||
@@ -456,7 +456,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
return ast.NameConstant(True)
|
||||
|
||||
def at_dampe_time(self, node):
|
||||
if self.multiworld.ensure_tod_access:
|
||||
if self.world.ensure_tod_access:
|
||||
# tod has DAMPE or (tod == NONE and (find a path from a provider))
|
||||
# parsing is better than constructing this expression by hand
|
||||
r = self.current_spot if type(self.current_spot) == OOTRegion else self.current_spot.parent_region
|
||||
@@ -464,10 +464,10 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
return ast.NameConstant(True)
|
||||
|
||||
def at_night(self, node):
|
||||
if self.current_spot.type == 'GS Token' and self.multiworld.logic_no_night_tokens_without_suns_song:
|
||||
if self.current_spot.type == 'GS Token' and self.world.logic_no_night_tokens_without_suns_song:
|
||||
# Using visit here to resolve 'can_play' rule
|
||||
return self.visit(ast.parse('can_play(Suns_Song)', mode='eval').body)
|
||||
if self.multiworld.ensure_tod_access:
|
||||
if self.world.ensure_tod_access:
|
||||
# tod has DAMPE or (tod == NONE and (ss or find a path from a provider))
|
||||
# parsing is better than constructing this expression by hand
|
||||
r = self.current_spot if type(self.current_spot) == OOTRegion else self.current_spot.parent_region
|
||||
@@ -501,7 +501,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
return ast.parse(f"state._oot_reach_as_age('{r.name}', 'adult', {self.player})", mode='eval').body
|
||||
|
||||
def current_spot_starting_age_access(self, node):
|
||||
return self.current_spot_child_access(node) if self.multiworld.starting_age == 'child' else self.current_spot_adult_access(node)
|
||||
return self.current_spot_child_access(node) if self.world.starting_age == 'child' else self.current_spot_adult_access(node)
|
||||
|
||||
def has_bottle(self, node):
|
||||
return ast.parse(f"state._oot_has_bottle({self.player})", mode='eval').body
|
||||
|
||||
+18
-13
@@ -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
|
||||
from worlds.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]
|
||||
@@ -127,17 +132,17 @@ class OOTLogic(LogicMixin):
|
||||
def set_rules(ootworld):
|
||||
logger = logging.getLogger('')
|
||||
|
||||
world = ootworld.multiworld
|
||||
multiworld = ootworld.multiworld
|
||||
player = ootworld.player
|
||||
|
||||
if ootworld.logic_rules != 'no_logic':
|
||||
if ootworld.triforce_hunt:
|
||||
world.completion_condition[player] = lambda state: state.has('Triforce Piece', player, ootworld.triforce_goal)
|
||||
multiworld.completion_condition[player] = lambda state: state.has('Triforce Piece', player, ootworld.triforce_goal)
|
||||
else:
|
||||
world.completion_condition[player] = lambda state: state.has('Triforce', player)
|
||||
multiworld.completion_condition[player] = lambda state: state.has('Triforce', player)
|
||||
|
||||
# ganon can only carry triforce
|
||||
world.get_location('Ganon', player).item_rule = lambda item: item.name == 'Triforce'
|
||||
multiworld.get_location('Ganon', player).item_rule = lambda item: item.name == 'Triforce'
|
||||
|
||||
# is_child = ootworld.parser.parse_rule('is_child')
|
||||
guarantee_hint = ootworld.parser.parse_rule('guarantee_hint')
|
||||
@@ -151,22 +156,22 @@ def set_rules(ootworld):
|
||||
if (ootworld.dungeon_mq['Forest Temple'] and ootworld.shuffle_bosskeys == 'dungeon'
|
||||
and ootworld.shuffle_smallkeys == 'dungeon' and ootworld.tokensanity == 'off'):
|
||||
# First room chest needs to be a small key. Make sure the boss key isn't placed here.
|
||||
location = world.get_location('Forest Temple MQ First Room Chest', player)
|
||||
location = multiworld.get_location('Forest Temple MQ First Room Chest', player)
|
||||
forbid_item(location, 'Boss Key (Forest Temple)', ootworld.player)
|
||||
|
||||
if ootworld.shuffle_song_items in {'song', 'dungeon'} and not ootworld.songs_as_items:
|
||||
# Sheik in Ice Cavern is the only song location in a dungeon; need to ensure that it cannot be anything else.
|
||||
# This is required if map/compass included, or any_dungeon shuffle.
|
||||
location = world.get_location('Sheik in Ice Cavern', player)
|
||||
location = multiworld.get_location('Sheik in Ice Cavern', player)
|
||||
add_item_rule(location, lambda item: oot_is_item_of_type(item, 'Song'))
|
||||
|
||||
if ootworld.shuffle_child_trade == 'skip_child_zelda':
|
||||
# Song from Impa must be local
|
||||
location = world.get_location('Song from Impa', player)
|
||||
location = multiworld.get_location('Song from Impa', player)
|
||||
add_item_rule(location, lambda item: item.player == player)
|
||||
|
||||
for name in ootworld.always_hints:
|
||||
add_rule(world.get_location(name, player), guarantee_hint)
|
||||
add_rule(multiworld.get_location(name, player), guarantee_hint)
|
||||
|
||||
# TODO: re-add hints once they are working
|
||||
# if location.type == 'HintStone' and ootworld.hints == 'mask':
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import worlds.oot.Messages as Messages
|
||||
from . import Messages
|
||||
|
||||
# Least common multiple of all possible character widths. A line wrap must occur when the combined widths of all of the
|
||||
# characters on a line reach this value.
|
||||
|
||||
+51
-44
@@ -20,7 +20,7 @@ from .ItemPool import generate_itempool, get_junk_item, get_junk_pool
|
||||
from .Regions import OOTRegion, TimeOfDay
|
||||
from .Rules import set_rules, set_shop_rules, set_entrances_based_rules
|
||||
from .RuleParser import Rule_AST_Transformer
|
||||
from .Options import oot_options
|
||||
from .Options import OoTOptions, oot_option_groups
|
||||
from .Utils import data_path, read_json
|
||||
from .LocationList import business_scrubs, set_drop_location_names, dungeon_song_locations
|
||||
from .DungeonList import dungeon_table, create_dungeons
|
||||
@@ -30,12 +30,12 @@ from .Patches import OoTContainer, patch_rom
|
||||
from .N64Patch import create_patch_file
|
||||
from .Cosmetics import patch_cosmetics
|
||||
|
||||
from Utils import get_options
|
||||
from settings import get_settings
|
||||
from BaseClasses import MultiWorld, CollectionState, Tutorial, LocationProgressType
|
||||
from Options import Range, Toggle, VerifyKeys, Accessibility, PlandoConnections
|
||||
from Fill import fill_restrictive, fast_fill, FillError
|
||||
from worlds.generic.Rules import exclusion_rules, add_item_rule
|
||||
from ..AutoWorld import World, AutoLogicRegister, WebWorld
|
||||
from worlds.AutoWorld import World, AutoLogicRegister, WebWorld
|
||||
|
||||
# OoT's generate_output doesn't benefit from more than 2 threads, instead it uses a lot of memory.
|
||||
i_o_limiter = threading.Semaphore(2)
|
||||
@@ -128,6 +128,7 @@ class OOTWeb(WebWorld):
|
||||
)
|
||||
|
||||
tutorials = [setup, setup_es, setup_fr, setup_de]
|
||||
option_groups = oot_option_groups
|
||||
|
||||
|
||||
class OOTWorld(World):
|
||||
@@ -137,7 +138,8 @@ class OOTWorld(World):
|
||||
to rescue the Seven Sages, and then confront Ganondorf to save Hyrule!
|
||||
"""
|
||||
game: str = "Ocarina of Time"
|
||||
option_definitions: dict = oot_options
|
||||
options_dataclass = OoTOptions
|
||||
options: OoTOptions
|
||||
settings: typing.ClassVar[OOTSettings]
|
||||
topology_present: bool = True
|
||||
item_name_to_id = {item_name: oot_data_to_ap_id(data, False) for item_name, data in item_table.items() if
|
||||
@@ -195,15 +197,15 @@ class OOTWorld(World):
|
||||
|
||||
@classmethod
|
||||
def stage_assert_generate(cls, multiworld: MultiWorld):
|
||||
rom = Rom(file=get_options()['oot_options']['rom_file'])
|
||||
rom = Rom(file=get_settings()['oot_options']['rom_file'])
|
||||
|
||||
|
||||
# Option parsing, handling incompatible options, building useful-item table
|
||||
def generate_early(self):
|
||||
self.parser = Rule_AST_Transformer(self, self.player)
|
||||
|
||||
for (option_name, option) in oot_options.items():
|
||||
result = getattr(self.multiworld, option_name)[self.player]
|
||||
for option_name in self.options_dataclass.type_hints:
|
||||
result = getattr(self.options, option_name)
|
||||
if isinstance(result, Range):
|
||||
option_value = int(result)
|
||||
elif isinstance(result, Toggle):
|
||||
@@ -223,8 +225,8 @@ class OOTWorld(World):
|
||||
self.remove_from_start_inventory = [] # some items will be precollected but not in the inventory
|
||||
self.starting_items = Counter()
|
||||
self.songs_as_items = False
|
||||
self.file_hash = [self.multiworld.random.randint(0, 31) for i in range(5)]
|
||||
self.connect_name = ''.join(self.multiworld.random.choices(printable, k=16))
|
||||
self.file_hash = [self.random.randint(0, 31) for i in range(5)]
|
||||
self.connect_name = ''.join(self.random.choices(printable, k=16))
|
||||
self.collectible_flag_addresses = {}
|
||||
|
||||
# Incompatible option handling
|
||||
@@ -283,7 +285,7 @@ class OOTWorld(World):
|
||||
local_types.append('BossKey')
|
||||
if self.shuffle_ganon_bosskey != 'keysanity':
|
||||
local_types.append('GanonBossKey')
|
||||
self.multiworld.local_items[self.player].value |= set(name for name, data in item_table.items() if data[0] in local_types)
|
||||
self.options.local_items.value |= set(name for name, data in item_table.items() if data[0] in local_types)
|
||||
|
||||
# If any songs are itemlinked, set songs_as_items
|
||||
for group in self.multiworld.groups.values():
|
||||
@@ -297,7 +299,7 @@ class OOTWorld(World):
|
||||
# Determine skipped trials in GT
|
||||
# This needs to be done before the logic rules in GT are parsed
|
||||
trial_list = ['Forest', 'Fire', 'Water', 'Spirit', 'Shadow', 'Light']
|
||||
chosen_trials = self.multiworld.random.sample(trial_list, self.trials) # chooses a list of trials to NOT skip
|
||||
chosen_trials = self.random.sample(trial_list, self.trials) # chooses a list of trials to NOT skip
|
||||
self.skipped_trials = {trial: (trial not in chosen_trials) for trial in trial_list}
|
||||
|
||||
# Determine tricks in logic
|
||||
@@ -311,8 +313,8 @@ class OOTWorld(World):
|
||||
|
||||
# No Logic forces all tricks on, prog balancing off and beatable-only
|
||||
elif self.logic_rules == 'no_logic':
|
||||
self.multiworld.progression_balancing[self.player].value = False
|
||||
self.multiworld.accessibility[self.player].value = Accessibility.option_minimal
|
||||
self.options.progression_balancing.value = False
|
||||
self.options.accessibility.value = Accessibility.option_minimal
|
||||
for trick in normalized_name_tricks.values():
|
||||
setattr(self, trick['name'], True)
|
||||
|
||||
@@ -333,8 +335,8 @@ class OOTWorld(World):
|
||||
|
||||
# Set internal names used by the OoT generator
|
||||
self.keysanity = self.shuffle_smallkeys in ['keysanity', 'remove', 'any_dungeon', 'overworld']
|
||||
self.trials_random = self.multiworld.trials[self.player].randomized
|
||||
self.mq_dungeons_random = self.multiworld.mq_dungeons_count[self.player].randomized
|
||||
self.trials_random = self.options.trials.randomized
|
||||
self.mq_dungeons_random = self.options.mq_dungeons_count.randomized
|
||||
self.easier_fire_arrow_entry = self.fae_torch_count < 24
|
||||
|
||||
if self.misc_hints:
|
||||
@@ -393,8 +395,8 @@ class OOTWorld(World):
|
||||
elif self.key_rings == 'choose':
|
||||
self.key_rings = self.key_rings_list
|
||||
elif self.key_rings == 'random_dungeons':
|
||||
self.key_rings = self.multiworld.random.sample(keyring_dungeons,
|
||||
self.multiworld.random.randint(0, len(keyring_dungeons)))
|
||||
self.key_rings = self.random.sample(keyring_dungeons,
|
||||
self.random.randint(0, len(keyring_dungeons)))
|
||||
|
||||
# Determine which dungeons are MQ. Not compatible with glitched logic.
|
||||
mq_dungeons = set()
|
||||
@@ -405,7 +407,7 @@ class OOTWorld(World):
|
||||
elif self.mq_dungeons_mode == 'specific':
|
||||
mq_dungeons = self.mq_dungeons_specific
|
||||
elif self.mq_dungeons_mode == 'count':
|
||||
mq_dungeons = self.multiworld.random.sample(all_dungeons, self.mq_dungeons_count)
|
||||
mq_dungeons = self.random.sample(all_dungeons, self.mq_dungeons_count)
|
||||
else:
|
||||
self.mq_dungeons_mode = 'count'
|
||||
self.mq_dungeons_count = 0
|
||||
@@ -425,8 +427,8 @@ class OOTWorld(World):
|
||||
elif self.dungeon_shortcuts_choice == 'all':
|
||||
self.dungeon_shortcuts = set(shortcut_dungeons)
|
||||
elif self.dungeon_shortcuts_choice == 'random':
|
||||
self.dungeon_shortcuts = self.multiworld.random.sample(shortcut_dungeons,
|
||||
self.multiworld.random.randint(0, len(shortcut_dungeons)))
|
||||
self.dungeon_shortcuts = self.random.sample(shortcut_dungeons,
|
||||
self.random.randint(0, len(shortcut_dungeons)))
|
||||
# == 'choice', leave as previous
|
||||
else:
|
||||
self.dungeon_shortcuts = set()
|
||||
@@ -576,7 +578,7 @@ class OOTWorld(World):
|
||||
new_exit = OOTEntrance(self.player, self.multiworld, '%s -> %s' % (new_region.name, exit), new_region)
|
||||
new_exit.vanilla_connected_region = exit
|
||||
new_exit.rule_string = rule
|
||||
if self.multiworld.logic_rules != 'none':
|
||||
if self.options.logic_rules != 'no_logic':
|
||||
self.parser.parse_spot_rule(new_exit)
|
||||
if new_exit.never:
|
||||
logger.debug('Dropping unreachable exit: %s', new_exit.name)
|
||||
@@ -607,7 +609,7 @@ class OOTWorld(World):
|
||||
elif self.shuffle_scrubs == 'random':
|
||||
# this is a random value between 0-99
|
||||
# average value is ~33 rupees
|
||||
price = int(self.multiworld.random.betavariate(1, 2) * 99)
|
||||
price = int(self.random.betavariate(1, 2) * 99)
|
||||
|
||||
# Set price in the dictionary as well as the location.
|
||||
self.scrub_prices[scrub_item] = price
|
||||
@@ -624,7 +626,7 @@ class OOTWorld(World):
|
||||
self.shop_prices = {}
|
||||
for region in self.regions:
|
||||
if self.shopsanity == 'random':
|
||||
shop_item_count = self.multiworld.random.randint(0, 4)
|
||||
shop_item_count = self.random.randint(0, 4)
|
||||
else:
|
||||
shop_item_count = int(self.shopsanity)
|
||||
|
||||
@@ -632,17 +634,17 @@ class OOTWorld(World):
|
||||
if location.type == 'Shop':
|
||||
if location.name[-1:] in shop_item_indexes[:shop_item_count]:
|
||||
if self.shopsanity_prices == 'normal':
|
||||
self.shop_prices[location.name] = int(self.multiworld.random.betavariate(1.5, 2) * 60) * 5
|
||||
self.shop_prices[location.name] = int(self.random.betavariate(1.5, 2) * 60) * 5
|
||||
elif self.shopsanity_prices == 'affordable':
|
||||
self.shop_prices[location.name] = 10
|
||||
elif self.shopsanity_prices == 'starting_wallet':
|
||||
self.shop_prices[location.name] = self.multiworld.random.randrange(0,100,5)
|
||||
self.shop_prices[location.name] = self.random.randrange(0,100,5)
|
||||
elif self.shopsanity_prices == 'adults_wallet':
|
||||
self.shop_prices[location.name] = self.multiworld.random.randrange(0,201,5)
|
||||
self.shop_prices[location.name] = self.random.randrange(0,201,5)
|
||||
elif self.shopsanity_prices == 'giants_wallet':
|
||||
self.shop_prices[location.name] = self.multiworld.random.randrange(0,501,5)
|
||||
self.shop_prices[location.name] = self.random.randrange(0,501,5)
|
||||
elif self.shopsanity_prices == 'tycoons_wallet':
|
||||
self.shop_prices[location.name] = self.multiworld.random.randrange(0,1000,5)
|
||||
self.shop_prices[location.name] = self.random.randrange(0,1000,5)
|
||||
|
||||
|
||||
# Fill boss prizes
|
||||
@@ -667,8 +669,8 @@ class OOTWorld(World):
|
||||
|
||||
while bossCount:
|
||||
bossCount -= 1
|
||||
self.multiworld.random.shuffle(prizepool)
|
||||
self.multiworld.random.shuffle(prize_locs)
|
||||
self.random.shuffle(prizepool)
|
||||
self.random.shuffle(prize_locs)
|
||||
item = prizepool.pop()
|
||||
loc = prize_locs.pop()
|
||||
loc.place_locked_item(item)
|
||||
@@ -778,7 +780,7 @@ class OOTWorld(World):
|
||||
# Call the junk fill and get a replacement
|
||||
if item in self.itempool:
|
||||
self.itempool.remove(item)
|
||||
self.itempool.append(self.create_item(*get_junk_item(pool=junk_pool)))
|
||||
self.itempool.append(self.create_item(*get_junk_item(self.random, pool=junk_pool)))
|
||||
if self.start_with_consumables:
|
||||
self.starting_items['Deku Sticks'] = 30
|
||||
self.starting_items['Deku Nuts'] = 40
|
||||
@@ -881,7 +883,7 @@ class OOTWorld(World):
|
||||
# Prefill shops, songs, and dungeon items
|
||||
items = self.get_pre_fill_items()
|
||||
locations = list(self.multiworld.get_unfilled_locations(self.player))
|
||||
self.multiworld.random.shuffle(locations)
|
||||
self.random.shuffle(locations)
|
||||
|
||||
# Set up initial state
|
||||
state = CollectionState(self.multiworld)
|
||||
@@ -910,7 +912,7 @@ class OOTWorld(World):
|
||||
if isinstance(locations, list):
|
||||
for item in stage_items:
|
||||
self.pre_fill_items.remove(item)
|
||||
self.multiworld.random.shuffle(locations)
|
||||
self.random.shuffle(locations)
|
||||
fill_restrictive(self.multiworld, prefill_state(state), locations, stage_items,
|
||||
single_player_placement=True, lock=True, allow_excluded=True)
|
||||
else:
|
||||
@@ -923,7 +925,7 @@ class OOTWorld(World):
|
||||
if isinstance(locations, list):
|
||||
for item in dungeon_items:
|
||||
self.pre_fill_items.remove(item)
|
||||
self.multiworld.random.shuffle(locations)
|
||||
self.random.shuffle(locations)
|
||||
fill_restrictive(self.multiworld, prefill_state(state), locations, dungeon_items,
|
||||
single_player_placement=True, lock=True, allow_excluded=True)
|
||||
|
||||
@@ -964,7 +966,7 @@ class OOTWorld(World):
|
||||
|
||||
while tries:
|
||||
try:
|
||||
self.multiworld.random.shuffle(song_locations)
|
||||
self.random.shuffle(song_locations)
|
||||
fill_restrictive(self.multiworld, prefill_state(state), song_locations[:], songs[:],
|
||||
single_player_placement=True, lock=True, allow_excluded=True)
|
||||
logger.debug(f"Successfully placed songs for player {self.player} after {6 - tries} attempt(s)")
|
||||
@@ -996,7 +998,7 @@ class OOTWorld(World):
|
||||
'Buy Goron Tunic': 1,
|
||||
'Buy Zora Tunic': 1,
|
||||
}.get(item.name, 0)) # place Deku Shields if needed, then tunics, then other advancement
|
||||
self.multiworld.random.shuffle(shop_locations)
|
||||
self.random.shuffle(shop_locations)
|
||||
self.pre_fill_items = [] # all prefill should be done
|
||||
fill_restrictive(self.multiworld, prefill_state(state), shop_locations, shop_prog,
|
||||
single_player_placement=True, lock=True, allow_excluded=True)
|
||||
@@ -1028,7 +1030,7 @@ class OOTWorld(World):
|
||||
ganon_junk_fill = min(1, ganon_junk_fill)
|
||||
gc = next(filter(lambda dungeon: dungeon.name == 'Ganons Castle', self.dungeons))
|
||||
locations = [loc.name for region in gc.regions for loc in region.locations if loc.item is None]
|
||||
junk_fill_locations = self.multiworld.random.sample(locations, round(len(locations) * ganon_junk_fill))
|
||||
junk_fill_locations = self.random.sample(locations, round(len(locations) * ganon_junk_fill))
|
||||
exclusion_rules(self.multiworld, self.player, junk_fill_locations)
|
||||
|
||||
# Locations which are not sendable must be converted to events
|
||||
@@ -1074,13 +1076,13 @@ class OOTWorld(World):
|
||||
trap_location_ids = [loc.address for loc in self.get_locations() if loc.item.trap]
|
||||
self.trap_appearances = {}
|
||||
for loc_id in trap_location_ids:
|
||||
self.trap_appearances[loc_id] = self.create_item(self.multiworld.per_slot_randoms[self.player].choice(self.fake_items).name)
|
||||
self.trap_appearances[loc_id] = self.create_item(self.random.choice(self.fake_items).name)
|
||||
|
||||
# Seed hint RNG, used for ganon text lines also
|
||||
self.hint_rng = self.multiworld.per_slot_randoms[self.player]
|
||||
self.hint_rng = self.random
|
||||
|
||||
outfile_name = self.multiworld.get_out_file_name_base(self.player)
|
||||
rom = Rom(file=get_options()['oot_options']['rom_file'])
|
||||
rom = Rom(file=get_settings()['oot_options']['rom_file'])
|
||||
try:
|
||||
if self.hints != 'none':
|
||||
buildWorldGossipHints(self)
|
||||
@@ -1092,7 +1094,7 @@ class OOTWorld(World):
|
||||
finally:
|
||||
self.collectible_flags_available.set()
|
||||
rom.update_header()
|
||||
patch_data = create_patch_file(rom)
|
||||
patch_data = create_patch_file(rom, self.random)
|
||||
rom.restore()
|
||||
|
||||
apz5 = OoTContainer(patch_data, outfile_name, output_directory,
|
||||
@@ -1301,6 +1303,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 +1316,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,12 +1396,12 @@ 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
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return get_junk_item(count=1, pool=get_junk_pool(self))[0]
|
||||
return get_junk_item(self.random, count=1, pool=get_junk_pool(self))[0]
|
||||
|
||||
|
||||
def valid_dungeon_item_location(world: OOTWorld, option: str, dungeon: str, loc: OOTLocation) -> bool:
|
||||
|
||||
@@ -63,6 +63,7 @@ class MaxCombatLevel(Range):
|
||||
The highest combat level of monster to possibly be assigned as a task.
|
||||
If set to 0, no combat tasks will be generated.
|
||||
"""
|
||||
display_name = "Max Required Enemy Combat Level"
|
||||
range_start = 0
|
||||
range_end = 1520
|
||||
default = 50
|
||||
@@ -74,6 +75,7 @@ class MaxCombatTasks(Range):
|
||||
If set to 0, no combat tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
display_name = "Max Combat Task Count"
|
||||
range_start = 0
|
||||
range_end = MAX_COMBAT_TASKS
|
||||
default = MAX_COMBAT_TASKS
|
||||
@@ -85,6 +87,7 @@ class CombatTaskWeight(Range):
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
display_name = "Combat Task Weight"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -95,6 +98,7 @@ class MaxPrayerLevel(Range):
|
||||
The highest Prayer requirement of any task generated.
|
||||
If set to 0, no Prayer tasks will be generated.
|
||||
"""
|
||||
display_name = "Max Required Prayer Level"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -106,6 +110,7 @@ class MaxPrayerTasks(Range):
|
||||
If set to 0, no Prayer tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
display_name = "Max Prayer Task Count"
|
||||
range_start = 0
|
||||
range_end = MAX_PRAYER_TASKS
|
||||
default = MAX_PRAYER_TASKS
|
||||
@@ -117,6 +122,7 @@ class PrayerTaskWeight(Range):
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
display_name = "Prayer Task Weight"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -127,6 +133,7 @@ class MaxMagicLevel(Range):
|
||||
The highest Magic requirement of any task generated.
|
||||
If set to 0, no Magic tasks will be generated.
|
||||
"""
|
||||
display_name = "Max Required Magic Level"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -138,6 +145,7 @@ class MaxMagicTasks(Range):
|
||||
If set to 0, no Magic tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
display_name = "Max Magic Task Count"
|
||||
range_start = 0
|
||||
range_end = MAX_MAGIC_TASKS
|
||||
default = MAX_MAGIC_TASKS
|
||||
@@ -149,6 +157,7 @@ class MagicTaskWeight(Range):
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
display_name = "Magic Task Weight"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -159,6 +168,7 @@ class MaxRunecraftLevel(Range):
|
||||
The highest Runecraft requirement of any task generated.
|
||||
If set to 0, no Runecraft tasks will be generated.
|
||||
"""
|
||||
display_name = "Max Required Runecraft Level"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -170,6 +180,7 @@ class MaxRunecraftTasks(Range):
|
||||
If set to 0, no Runecraft tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
display_name = "Max Runecraft Task Count"
|
||||
range_start = 0
|
||||
range_end = MAX_RUNECRAFT_TASKS
|
||||
default = MAX_RUNECRAFT_TASKS
|
||||
@@ -181,6 +192,7 @@ class RunecraftTaskWeight(Range):
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
display_name = "Runecraft Task Weight"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -191,6 +203,7 @@ class MaxCraftingLevel(Range):
|
||||
The highest Crafting requirement of any task generated.
|
||||
If set to 0, no Crafting tasks will be generated.
|
||||
"""
|
||||
display_name = "Max Required Crafting Level"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -202,6 +215,7 @@ class MaxCraftingTasks(Range):
|
||||
If set to 0, no Crafting tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
display_name = "Max Crafting Task Count"
|
||||
range_start = 0
|
||||
range_end = MAX_CRAFTING_TASKS
|
||||
default = MAX_CRAFTING_TASKS
|
||||
@@ -213,6 +227,7 @@ class CraftingTaskWeight(Range):
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
display_name = "Crafting Task Weight"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -223,6 +238,7 @@ class MaxMiningLevel(Range):
|
||||
The highest Mining requirement of any task generated.
|
||||
If set to 0, no Mining tasks will be generated.
|
||||
"""
|
||||
display_name = "Max Required Mining Level"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -234,6 +250,7 @@ class MaxMiningTasks(Range):
|
||||
If set to 0, no Mining tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
display_name = "Max Mining Task Count"
|
||||
range_start = 0
|
||||
range_end = MAX_MINING_TASKS
|
||||
default = MAX_MINING_TASKS
|
||||
@@ -245,6 +262,7 @@ class MiningTaskWeight(Range):
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
display_name = "Mining Task Weight"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -255,6 +273,7 @@ class MaxSmithingLevel(Range):
|
||||
The highest Smithing requirement of any task generated.
|
||||
If set to 0, no Smithing tasks will be generated.
|
||||
"""
|
||||
display_name = "Max Required Smithing Level"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -266,6 +285,7 @@ class MaxSmithingTasks(Range):
|
||||
If set to 0, no Smithing tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
display_name = "Max Smithing Task Count"
|
||||
range_start = 0
|
||||
range_end = MAX_SMITHING_TASKS
|
||||
default = MAX_SMITHING_TASKS
|
||||
@@ -277,6 +297,7 @@ class SmithingTaskWeight(Range):
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
display_name = "Smithing Task Weight"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -287,6 +308,7 @@ class MaxFishingLevel(Range):
|
||||
The highest Fishing requirement of any task generated.
|
||||
If set to 0, no Fishing tasks will be generated.
|
||||
"""
|
||||
display_name = "Max Required Fishing Level"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -298,6 +320,7 @@ class MaxFishingTasks(Range):
|
||||
If set to 0, no Fishing tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
display_name = "Max Fishing Task Count"
|
||||
range_start = 0
|
||||
range_end = MAX_FISHING_TASKS
|
||||
default = MAX_FISHING_TASKS
|
||||
@@ -309,6 +332,7 @@ class FishingTaskWeight(Range):
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
display_name = "Fishing Task Weight"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -319,6 +343,7 @@ class MaxCookingLevel(Range):
|
||||
The highest Cooking requirement of any task generated.
|
||||
If set to 0, no Cooking tasks will be generated.
|
||||
"""
|
||||
display_name = "Max Required Cooking Level"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -330,6 +355,7 @@ class MaxCookingTasks(Range):
|
||||
If set to 0, no Cooking tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
display_name = "Max Cooking Task Count"
|
||||
range_start = 0
|
||||
range_end = MAX_COOKING_TASKS
|
||||
default = MAX_COOKING_TASKS
|
||||
@@ -341,6 +367,7 @@ class CookingTaskWeight(Range):
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
display_name = "Cooking Task Weight"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -351,6 +378,7 @@ class MaxFiremakingLevel(Range):
|
||||
The highest Firemaking requirement of any task generated.
|
||||
If set to 0, no Firemaking tasks will be generated.
|
||||
"""
|
||||
display_name = "Max Required Firemaking Level"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -362,6 +390,7 @@ class MaxFiremakingTasks(Range):
|
||||
If set to 0, no Firemaking tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
display_name = "Max Firemaking Task Count"
|
||||
range_start = 0
|
||||
range_end = MAX_FIREMAKING_TASKS
|
||||
default = MAX_FIREMAKING_TASKS
|
||||
@@ -373,6 +402,7 @@ class FiremakingTaskWeight(Range):
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
display_name = "Firemaking Task Weight"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -383,6 +413,7 @@ class MaxWoodcuttingLevel(Range):
|
||||
The highest Woodcutting requirement of any task generated.
|
||||
If set to 0, no Woodcutting tasks will be generated.
|
||||
"""
|
||||
display_name = "Max Required Woodcutting Level"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -394,6 +425,7 @@ class MaxWoodcuttingTasks(Range):
|
||||
If set to 0, no Woodcutting tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
display_name = "Max Woodcutting Task Count"
|
||||
range_start = 0
|
||||
range_end = MAX_WOODCUTTING_TASKS
|
||||
default = MAX_WOODCUTTING_TASKS
|
||||
@@ -405,6 +437,7 @@ class WoodcuttingTaskWeight(Range):
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
display_name = "Woodcutting Task Weight"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
@@ -416,6 +449,7 @@ class MinimumGeneralTasks(Range):
|
||||
General progression tasks will be used to fill out any holes caused by having fewer possible tasks than needed, so
|
||||
there is no maximum.
|
||||
"""
|
||||
display_name = "Minimum General Task Count"
|
||||
range_start = 0
|
||||
range_end = NON_QUEST_LOCATION_COUNT
|
||||
default = 10
|
||||
@@ -427,6 +461,7 @@ class GeneralTaskWeight(Range):
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
display_name = "General Task Weight"
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
+41
-17
@@ -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
|
||||
@@ -84,16 +90,18 @@ class OSRSWorld(World):
|
||||
|
||||
rnd = self.random
|
||||
starting_area = self.options.starting_area
|
||||
|
||||
#UT specific override, if we are in normal gen, resolve starting area, we will get it from slot_data in UT
|
||||
if not hasattr(self.multiworld, "generation_is_fake"):
|
||||
if starting_area.value == StartingArea.option_any_bank:
|
||||
self.starting_area_item = rnd.choice(starting_area_dict)
|
||||
elif starting_area.value < StartingArea.option_chunksanity:
|
||||
self.starting_area_item = starting_area_dict[starting_area.value]
|
||||
else:
|
||||
self.starting_area_item = rnd.choice(chunksanity_starting_chunks)
|
||||
|
||||
if starting_area.value == StartingArea.option_any_bank:
|
||||
self.starting_area_item = rnd.choice(starting_area_dict)
|
||||
elif starting_area.value < StartingArea.option_chunksanity:
|
||||
self.starting_area_item = starting_area_dict[starting_area.value]
|
||||
else:
|
||||
self.starting_area_item = rnd.choice(chunksanity_starting_chunks)
|
||||
|
||||
# Set Starting Chunk
|
||||
self.multiworld.push_precollected(self.create_item(self.starting_area_item))
|
||||
# Set Starting Chunk
|
||||
self.multiworld.push_precollected(self.create_item(self.starting_area_item))
|
||||
|
||||
"""
|
||||
This function pulls from LogicCSVToPython so that it sends the correct tag of the repository to the client.
|
||||
@@ -103,8 +111,23 @@ class OSRSWorld(World):
|
||||
def fill_slot_data(self):
|
||||
data = self.options.as_dict("brutal_grinds")
|
||||
data["data_csv_tag"] = data_csv_tag
|
||||
data["starting_area"] = str(self.starting_area_item) #these aren't actually strings, they just play them on tv
|
||||
return data
|
||||
|
||||
def interpret_slot_data(self, slot_data: typing.Dict[str, typing.Any]) -> None:
|
||||
if "starting_area" in slot_data:
|
||||
self.starting_area_item = slot_data["starting_area"]
|
||||
menu_region = self.multiworld.get_region("Menu",self.player)
|
||||
menu_region.exits.clear() #prevent making extra exits if players just reconnect to a differnet slot
|
||||
if self.starting_area_item in chunksanity_special_region_names:
|
||||
starting_area_region = chunksanity_special_region_names[self.starting_area_item]
|
||||
else:
|
||||
starting_area_region = self.starting_area_item[6:] # len("Area: ")
|
||||
starting_entrance = menu_region.create_exit(f"Start->{starting_area_region}")
|
||||
starting_entrance.access_rule = lambda state: state.has(self.starting_area_item, self.player)
|
||||
starting_entrance.connect(self.region_name_to_data[starting_area_region])
|
||||
|
||||
|
||||
def create_regions(self) -> None:
|
||||
"""
|
||||
called to place player's regions into the MultiWorld's regions list. If it's hard to separate, this can be done
|
||||
@@ -122,13 +145,14 @@ class OSRSWorld(World):
|
||||
|
||||
# Removes the word "Area: " from the item name to get the region it applies to.
|
||||
# I figured tacking "Area: " at the beginning would make it _easier_ to tell apart. Turns out it made it worse
|
||||
if self.starting_area_item in chunksanity_special_region_names:
|
||||
starting_area_region = chunksanity_special_region_names[self.starting_area_item]
|
||||
else:
|
||||
starting_area_region = self.starting_area_item[6:] # len("Area: ")
|
||||
starting_entrance = menu_region.create_exit(f"Start->{starting_area_region}")
|
||||
starting_entrance.access_rule = lambda state: state.has(self.starting_area_item, self.player)
|
||||
starting_entrance.connect(self.region_name_to_data[starting_area_region])
|
||||
if self.starting_area_item != "": #if area hasn't been set, then we shouldn't connect it
|
||||
if self.starting_area_item in chunksanity_special_region_names:
|
||||
starting_area_region = chunksanity_special_region_names[self.starting_area_item]
|
||||
else:
|
||||
starting_area_region = self.starting_area_item[6:] # len("Area: ")
|
||||
starting_entrance = menu_region.create_exit(f"Start->{starting_area_region}")
|
||||
starting_entrance.access_rule = lambda state: state.has(self.starting_area_item, self.player)
|
||||
starting_entrance.connect(self.region_name_to_data[starting_area_region])
|
||||
|
||||
# Create entrances between regions
|
||||
for region_row in region_rows:
|
||||
@@ -635,7 +659,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:
|
||||
|
||||
+229
-187
@@ -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),
|
||||
|
||||
+38
-41
@@ -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
|
||||
|
||||
+136
-117
@@ -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
|
||||
|
||||
+106
-109
@@ -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
|
||||
|
||||
+301
-293
File diff suppressed because it is too large
Load Diff
+210
-166
@@ -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)
|
||||
|
||||
+222
-228
@@ -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,
|
||||
|
||||
+99
-108
@@ -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)
|
||||
|
||||
|
||||
+15
-1
@@ -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:
|
||||
|
||||
@@ -246,10 +246,10 @@ def create_regions(world: MultiWorld, options: SM64Options, player: int):
|
||||
regBitS.subregions = [bits_top]
|
||||
|
||||
|
||||
def connect_regions(world: MultiWorld, player: int, source: str, target: str, rule=None):
|
||||
def connect_regions(world: MultiWorld, player: int, source: str, target: str, rule=None) -> Entrance:
|
||||
sourceRegion = world.get_region(source, player)
|
||||
targetRegion = world.get_region(target, player)
|
||||
sourceRegion.connect(targetRegion, rule=rule)
|
||||
return sourceRegion.connect(targetRegion, rule=rule)
|
||||
|
||||
|
||||
def create_region(name: str, player: int, world: MultiWorld) -> SM64Region:
|
||||
|
||||
@@ -92,9 +92,12 @@ def set_rules(world, options: SM64Options, player: int, area_connections: dict,
|
||||
connect_regions(world, player, "Hazy Maze Cave", randomized_entrances_s["Cavern of the Metal Cap"])
|
||||
connect_regions(world, player, "Basement", randomized_entrances_s["Vanish Cap under the Moat"],
|
||||
rf.build_rule("GP"))
|
||||
connect_regions(world, player, "Basement", randomized_entrances_s["Bowser in the Fire Sea"],
|
||||
lambda state: state.has("Power Star", player, star_costs["BasementDoorCost"]) and
|
||||
state.can_reach("DDD: Board Bowser's Sub", 'Location', player))
|
||||
entrance = connect_regions(world, player, "Basement", randomized_entrances_s["Bowser in the Fire Sea"],
|
||||
lambda state: state.has("Power Star", player, star_costs["BasementDoorCost"]) and
|
||||
state.can_reach("DDD: Board Bowser's Sub", 'Location', player))
|
||||
# Access to "DDD: Board Bowser's Sub" does not require access to other locations or regions, so the only region that
|
||||
# needs to be registered is its parent region.
|
||||
world.register_indirect_condition(world.get_location("DDD: Board Bowser's Sub", player).parent_region, entrance)
|
||||
|
||||
connect_regions(world, player, "Menu", "Second Floor", lambda state: state.has("Second Floor Key", player) or state.has("Progressive Key", player, 2))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user