mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-10 01:23:48 -07:00
Compare commits
49 Commits
0.4.6
...
NewSoupVi-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d04239504 | ||
|
|
59a000033e | ||
|
|
76962b8b3b | ||
|
|
e04db57dce | ||
|
|
12b8fef1aa | ||
|
|
0ac8844f6f | ||
|
|
23eca7d747 | ||
|
|
1a563a14fc | ||
|
|
5935093615 | ||
|
|
2aa3ef372d | ||
|
|
d94cf8dcb2 | ||
|
|
5fae1c087e | ||
|
|
baca95d49d | ||
|
|
7e61211365 | ||
|
|
7603b4a88f | ||
|
|
005fc4e864 | ||
|
|
28262a31b8 | ||
|
|
660b068f5a | ||
|
|
879c3407d8 | ||
|
|
d5683c4326 | ||
|
|
f27d1d635b | ||
|
|
298c9fc159 | ||
|
|
26188230b7 | ||
|
|
b68be7360c | ||
|
|
255e52642e | ||
|
|
49862dca1f | ||
|
|
0d586a4467 | ||
|
|
8c8b29ae92 | ||
|
|
9d478ba2bc | ||
|
|
3cc434cd78 | ||
|
|
31a5696526 | ||
|
|
7bdf9a643c | ||
|
|
c64c80aac0 | ||
|
|
07d9d6165e | ||
|
|
fc571ba356 | ||
|
|
ea6235e0d9 | ||
|
|
6f8b8fc9c9 | ||
|
|
0ed0de3daa | ||
|
|
487a067d10 | ||
|
|
fc4e6adff5 | ||
|
|
9cdc90513b | ||
|
|
9afe45166c | ||
|
|
9e20fa48e1 | ||
|
|
e76ba928a8 | ||
|
|
4f1e696243 | ||
|
|
4756c76541 | ||
|
|
2f78860d8c | ||
|
|
cca9778871 | ||
|
|
bb16fe284a |
@@ -11,8 +11,8 @@ from argparse import Namespace
|
||||
from collections import Counter, deque
|
||||
from collections.abc import Collection, MutableSequence
|
||||
from enum import IntEnum, IntFlag
|
||||
from typing import Any, Callable, Dict, Iterable, Iterator, List, NamedTuple, Optional, Set, Tuple, TypedDict, Union, \
|
||||
Type, ClassVar
|
||||
from typing import Any, Callable, Dict, Iterable, Iterator, List, Mapping, NamedTuple, Optional, Set, Tuple, \
|
||||
TypedDict, Union, Type, ClassVar
|
||||
|
||||
import NetUtils
|
||||
import Options
|
||||
@@ -707,6 +707,14 @@ class CollectionState():
|
||||
"""Returns True if at least one item name of items is in state at least once."""
|
||||
return any(self.prog_items[player][item] for item in items)
|
||||
|
||||
def has_all_counts(self, item_counts: Mapping[str, int], player: int) -> bool:
|
||||
"""Returns True if each item name is in the state at least as many times as specified."""
|
||||
return all(self.prog_items[player][item] >= count for item, count in item_counts.items())
|
||||
|
||||
def has_any_count(self, item_counts: Mapping[str, int], player: int) -> bool:
|
||||
"""Returns True if at least one item name is in the state at least as many times as specified."""
|
||||
return any(self.prog_items[player][item] >= count for item, count in item_counts.items())
|
||||
|
||||
def count(self, item: str, player: int) -> int:
|
||||
return self.prog_items[player][item]
|
||||
|
||||
@@ -714,8 +722,23 @@ class CollectionState():
|
||||
Utils.deprecate("Use count instead.")
|
||||
return self.count(item, player)
|
||||
|
||||
def has_from_list(self, items: Iterable[str], player: int, count: int) -> bool:
|
||||
"""Returns True if the state contains at least `count` items matching any of the item names from a list."""
|
||||
found: int = 0
|
||||
player_prog_items = self.prog_items[player]
|
||||
for item_name in items:
|
||||
found += player_prog_items[item_name]
|
||||
if found >= count:
|
||||
return True
|
||||
return False
|
||||
|
||||
def count_from_list(self, items: Iterable[str], player: int) -> int:
|
||||
"""Returns the cumulative count of items from a list present in state."""
|
||||
return sum(self.prog_items[player][item_name] for item_name in items)
|
||||
|
||||
# item name group related
|
||||
def has_group(self, item_name_group: str, player: int, count: int = 1) -> bool:
|
||||
"""Returns True if the state contains at least `count` items present in a specified item group."""
|
||||
found: int = 0
|
||||
player_prog_items = self.prog_items[player]
|
||||
for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]:
|
||||
@@ -725,11 +748,12 @@ class CollectionState():
|
||||
return False
|
||||
|
||||
def count_group(self, item_name_group: str, player: int) -> int:
|
||||
found: int = 0
|
||||
"""Returns the cumulative count of items from an item group present in state."""
|
||||
player_prog_items = self.prog_items[player]
|
||||
for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]:
|
||||
found += player_prog_items[item_name]
|
||||
return found
|
||||
return sum(
|
||||
player_prog_items[item_name]
|
||||
for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]
|
||||
)
|
||||
|
||||
# Item related
|
||||
def collect(self, item: Item, event: bool = False, location: Optional[Location] = None) -> bool:
|
||||
|
||||
@@ -207,6 +207,8 @@ class CommonContext:
|
||||
|
||||
finished_game: bool
|
||||
ready: bool
|
||||
team: typing.Optional[int]
|
||||
slot: typing.Optional[int]
|
||||
auth: typing.Optional[str]
|
||||
seed_name: typing.Optional[str]
|
||||
|
||||
|
||||
22
Fill.py
22
Fill.py
@@ -19,11 +19,12 @@ def _log_fill_progress(name: str, placed: int, total_items: int) -> None:
|
||||
logging.info(f"Current fill step ({name}) at {placed}/{total_items} items placed.")
|
||||
|
||||
|
||||
def sweep_from_pool(base_state: CollectionState, itempool: typing.Sequence[Item] = tuple()) -> CollectionState:
|
||||
def sweep_from_pool(base_state: CollectionState, itempool: typing.Sequence[Item] = tuple(),
|
||||
locations: typing.Optional[typing.List[Location]] = None) -> CollectionState:
|
||||
new_state = base_state.copy()
|
||||
for item in itempool:
|
||||
new_state.collect(item, True)
|
||||
new_state.sweep_for_events()
|
||||
new_state.sweep_for_events(locations=locations)
|
||||
return new_state
|
||||
|
||||
|
||||
@@ -66,7 +67,8 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati
|
||||
item_pool.pop(p)
|
||||
break
|
||||
maximum_exploration_state = sweep_from_pool(
|
||||
base_state, item_pool + unplaced_items)
|
||||
base_state, item_pool + unplaced_items, multiworld.get_filled_locations(item.player)
|
||||
if single_player_placement else None)
|
||||
|
||||
has_beaten_game = multiworld.has_beaten_game(maximum_exploration_state)
|
||||
|
||||
@@ -112,7 +114,9 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati
|
||||
|
||||
location.item = None
|
||||
placed_item.location = None
|
||||
swap_state = sweep_from_pool(base_state, [placed_item, *item_pool] if unsafe else item_pool)
|
||||
swap_state = sweep_from_pool(base_state, [placed_item, *item_pool] if unsafe else item_pool,
|
||||
multiworld.get_filled_locations(item.player)
|
||||
if single_player_placement else None)
|
||||
# unsafe means swap_state assumes we can somehow collect placed_item before item_to_place
|
||||
# by continuing to swap, which is not guaranteed. This is unsafe because there is no mechanic
|
||||
# to clean that up later, so there is a chance generation fails.
|
||||
@@ -170,7 +174,9 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati
|
||||
|
||||
if cleanup_required:
|
||||
# validate all placements and remove invalid ones
|
||||
state = sweep_from_pool(base_state, [])
|
||||
state = sweep_from_pool(
|
||||
base_state, [], multiworld.get_filled_locations(item.player)
|
||||
if single_player_placement else None)
|
||||
for placement in placements:
|
||||
if multiworld.worlds[placement.item.player].options.accessibility != "minimal" and not placement.can_reach(state):
|
||||
placement.item.location = None
|
||||
@@ -456,14 +462,16 @@ def distribute_items_restrictive(multiworld: MultiWorld) -> None:
|
||||
|
||||
if prioritylocations:
|
||||
# "priority fill"
|
||||
fill_restrictive(multiworld, multiworld.state, prioritylocations, progitempool, swap=False, on_place=mark_for_locking,
|
||||
fill_restrictive(multiworld, multiworld.state, prioritylocations, progitempool,
|
||||
single_player_placement=multiworld.players == 1, swap=False, on_place=mark_for_locking,
|
||||
name="Priority")
|
||||
accessibility_corrections(multiworld, multiworld.state, prioritylocations, progitempool)
|
||||
defaultlocations = prioritylocations + defaultlocations
|
||||
|
||||
if progitempool:
|
||||
# "advancement/progression fill"
|
||||
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool, name="Progression")
|
||||
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool, single_player_placement=multiworld.players == 1,
|
||||
name="Progression")
|
||||
if progitempool:
|
||||
raise FillError(
|
||||
f"Not enough locations for progression items. "
|
||||
|
||||
18
Generate.py
18
Generate.py
@@ -120,7 +120,7 @@ def main(args=None, callback=ERmain):
|
||||
raise ValueError(f"File {fname} is invalid. Please fix your yaml.") from e
|
||||
|
||||
# sort dict for consistent results across platforms:
|
||||
weights_cache = {key: value for key, value in sorted(weights_cache.items())}
|
||||
weights_cache = {key: value for key, value in sorted(weights_cache.items(), key=lambda k: k[0].casefold())}
|
||||
for filename, yaml_data in weights_cache.items():
|
||||
if filename not in {args.meta_file_path, args.weights_file_path}:
|
||||
for yaml in yaml_data:
|
||||
@@ -353,7 +353,7 @@ def roll_meta_option(option_key, game: str, category_dict: Dict) -> Any:
|
||||
if options[option_key].supports_weighting:
|
||||
return get_choice(option_key, category_dict)
|
||||
return category_dict[option_key]
|
||||
raise Exception(f"Error generating meta option {option_key} for {game}.")
|
||||
raise Options.OptionError(f"Error generating meta option {option_key} for {game}.")
|
||||
|
||||
|
||||
def roll_linked_options(weights: dict) -> dict:
|
||||
@@ -409,19 +409,19 @@ def roll_triggers(weights: dict, triggers: list) -> dict:
|
||||
|
||||
|
||||
def handle_option(ret: argparse.Namespace, game_weights: dict, option_key: str, option: type(Options.Option), plando_options: PlandoOptions):
|
||||
if option_key in game_weights:
|
||||
try:
|
||||
try:
|
||||
if option_key in game_weights:
|
||||
if not option.supports_weighting:
|
||||
player_option = option.from_any(game_weights[option_key])
|
||||
else:
|
||||
player_option = option.from_any(get_choice(option_key, game_weights))
|
||||
setattr(ret, option_key, player_option)
|
||||
except Exception as e:
|
||||
raise Exception(f"Error generating option {option_key} in {ret.game}") from e
|
||||
else:
|
||||
player_option.verify(AutoWorldRegister.world_types[ret.game], ret.name, plando_options)
|
||||
player_option = option.from_any(option.default) # call the from_any here to support default "random"
|
||||
setattr(ret, option_key, player_option)
|
||||
except Exception as e:
|
||||
raise Options.OptionError(f"Error generating option {option_key} in {ret.game}") from e
|
||||
else:
|
||||
setattr(ret, option_key, option.from_any(option.default)) # call the from_any here to support default "random"
|
||||
player_option.verify(AutoWorldRegister.world_types[ret.game], ret.name, plando_options)
|
||||
|
||||
|
||||
def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.bosses):
|
||||
|
||||
@@ -688,7 +688,7 @@ class Context:
|
||||
clients = self.clients[team].get(slot)
|
||||
if not clients:
|
||||
continue
|
||||
client_hints = [datum[1] for datum in sorted(hint_data, key=lambda x: x[0].finding_player == slot)]
|
||||
client_hints = [datum[1] for datum in sorted(hint_data, key=lambda x: x[0].finding_player != slot)]
|
||||
for client in clients:
|
||||
async_start(self.send_msgs(client, client_hints))
|
||||
|
||||
@@ -803,14 +803,25 @@ async def on_client_disconnected(ctx: Context, client: Client):
|
||||
await on_client_left(ctx, client)
|
||||
|
||||
|
||||
_non_game_messages = {"HintGame": "hinting", "Tracker": "tracking", "TextOnly": "viewing"}
|
||||
""" { tag: ui_message } """
|
||||
|
||||
|
||||
async def on_client_joined(ctx: Context, client: Client):
|
||||
if ctx.client_game_state[client.team, client.slot] == ClientStatus.CLIENT_UNKNOWN:
|
||||
update_client_status(ctx, client, ClientStatus.CLIENT_CONNECTED)
|
||||
version_str = '.'.join(str(x) for x in client.version)
|
||||
verb = "tracking" if "Tracker" in client.tags else "playing"
|
||||
|
||||
for tag, verb in _non_game_messages.items():
|
||||
if tag in client.tags:
|
||||
final_verb = verb
|
||||
break
|
||||
else:
|
||||
final_verb = "playing"
|
||||
|
||||
ctx.broadcast_text_all(
|
||||
f"{ctx.get_aliased_name(client.team, client.slot)} (Team #{client.team + 1}) "
|
||||
f"{verb} {ctx.games[client.slot]} has joined. "
|
||||
f"{final_verb} {ctx.games[client.slot]} has joined. "
|
||||
f"Client({version_str}), {client.tags}.",
|
||||
{"type": "Join", "team": client.team, "slot": client.slot, "tags": client.tags})
|
||||
ctx.notify_client(client, "Now that you are connected, "
|
||||
@@ -825,8 +836,19 @@ async def on_client_left(ctx: Context, client: Client):
|
||||
if len(ctx.clients[client.team][client.slot]) < 1:
|
||||
update_client_status(ctx, client, ClientStatus.CLIENT_UNKNOWN)
|
||||
ctx.client_connection_timers[client.team, client.slot] = datetime.datetime.now(datetime.timezone.utc)
|
||||
|
||||
version_str = '.'.join(str(x) for x in client.version)
|
||||
|
||||
for tag, verb in _non_game_messages.items():
|
||||
if tag in client.tags:
|
||||
final_verb = f"stopped {verb}"
|
||||
break
|
||||
else:
|
||||
final_verb = "left"
|
||||
|
||||
ctx.broadcast_text_all(
|
||||
"%s (Team #%d) has left the game" % (ctx.get_aliased_name(client.team, client.slot), client.team + 1),
|
||||
f"{ctx.get_aliased_name(client.team, client.slot)} (Team #{client.team + 1}) has {final_verb} the game. "
|
||||
f"Client({version_str}), {client.tags}.",
|
||||
{"type": "Part", "team": client.team, "slot": client.slot})
|
||||
|
||||
|
||||
@@ -1507,15 +1529,13 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
||||
|
||||
if hints:
|
||||
new_hints = set(hints) - self.ctx.hints[self.client.team, self.client.slot]
|
||||
old_hints = set(hints) - new_hints
|
||||
if old_hints:
|
||||
self.ctx.notify_hints(self.client.team, list(old_hints))
|
||||
if not new_hints:
|
||||
self.output("Hint was previously used, no points deducted.")
|
||||
old_hints = list(set(hints) - new_hints)
|
||||
if old_hints and not new_hints:
|
||||
self.ctx.notify_hints(self.client.team, old_hints)
|
||||
self.output("Hint was previously used, no points deducted.")
|
||||
if new_hints:
|
||||
found_hints = [hint for hint in new_hints if hint.found]
|
||||
not_found_hints = [hint for hint in new_hints if not hint.found]
|
||||
|
||||
if not not_found_hints: # everything's been found, no need to pay
|
||||
can_pay = 1000
|
||||
elif cost:
|
||||
@@ -1527,7 +1547,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
||||
# By popular vote, make hints prefer non-local placements
|
||||
not_found_hints.sort(key=lambda hint: int(hint.receiving_player != hint.finding_player))
|
||||
|
||||
hints = found_hints
|
||||
hints = found_hints + old_hints
|
||||
while can_pay > 0:
|
||||
if not not_found_hints:
|
||||
break
|
||||
@@ -1537,6 +1557,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
||||
self.ctx.hints_used[self.client.team, self.client.slot] += 1
|
||||
points_available = get_client_points(self.ctx, self.client)
|
||||
|
||||
self.ctx.notify_hints(self.client.team, hints)
|
||||
if not_found_hints:
|
||||
if hints and cost and int((points_available // cost) == 0):
|
||||
self.output(
|
||||
@@ -1550,7 +1571,6 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
||||
self.output(f"You can't afford the hint. "
|
||||
f"You have {points_available} points and need at least "
|
||||
f"{self.ctx.get_hint_cost(self.client.slot)}.")
|
||||
self.ctx.notify_hints(self.client.team, hints)
|
||||
self.ctx.save()
|
||||
return True
|
||||
|
||||
@@ -1631,7 +1651,9 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
|
||||
else:
|
||||
team, slot = ctx.connect_names[args['name']]
|
||||
game = ctx.games[slot]
|
||||
ignore_game = ("TextOnly" in args["tags"] or "Tracker" in args["tags"]) and not args.get("game")
|
||||
|
||||
ignore_game = not args.get("game") and any(tag in _non_game_messages for tag in args["tags"])
|
||||
|
||||
if not ignore_game and args['game'] != game:
|
||||
errors.add('InvalidGame')
|
||||
minver = min_client_version if ignore_game else ctx.minimum_client_versions[slot]
|
||||
@@ -2198,7 +2220,7 @@ async def console(ctx: Context):
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
defaults = Utils.get_options()["server_options"].as_dict()
|
||||
defaults = Utils.get_settings()["server_options"].as_dict()
|
||||
parser.add_argument('multidata', nargs="?", default=defaults["multidata"])
|
||||
parser.add_argument('--host', default=defaults["host"])
|
||||
parser.add_argument('--port', default=defaults["port"], type=int)
|
||||
|
||||
@@ -21,6 +21,10 @@ if typing.TYPE_CHECKING:
|
||||
import pathlib
|
||||
|
||||
|
||||
class OptionError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class Visibility(enum.IntFlag):
|
||||
none = 0b0000
|
||||
template = 0b0001
|
||||
|
||||
@@ -65,6 +65,7 @@ Currently, the following games are supported:
|
||||
* Castlevania 64
|
||||
* A Short Hike
|
||||
* Yoshi's Island
|
||||
* Mario & Luigi: Superstar Saga
|
||||
|
||||
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
|
||||
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled
|
||||
|
||||
@@ -282,7 +282,7 @@ class SNESState(enum.IntEnum):
|
||||
|
||||
|
||||
def launch_sni() -> None:
|
||||
sni_path = Utils.get_options()["sni_options"]["sni_path"]
|
||||
sni_path = Utils.get_settings()["sni_options"]["sni_path"]
|
||||
|
||||
if not os.path.isdir(sni_path):
|
||||
sni_path = Utils.local_path(sni_path)
|
||||
@@ -565,7 +565,7 @@ async def snes_write(ctx: SNIContext, write_list: typing.List[typing.Tuple[int,
|
||||
PutAddress_Request: SNESRequest = {"Opcode": "PutAddress", "Operands": [], 'Space': 'SNES'}
|
||||
try:
|
||||
for address, data in write_list:
|
||||
PutAddress_Request['Operands'] = [hex(address)[2:], hex(min(len(data), 256))[2:]]
|
||||
PutAddress_Request['Operands'] = [hex(address)[2:], hex(len(data))[2:]]
|
||||
if ctx.snes_socket is not None:
|
||||
await ctx.snes_socket.send(dumps(PutAddress_Request))
|
||||
await ctx.snes_socket.send(data)
|
||||
@@ -654,7 +654,7 @@ async def game_watcher(ctx: SNIContext) -> None:
|
||||
|
||||
async def run_game(romfile: str) -> None:
|
||||
auto_start = typing.cast(typing.Union[bool, str],
|
||||
Utils.get_options()["sni_options"].get("snes_rom_start", True))
|
||||
Utils.get_settings()["sni_options"].get("snes_rom_start", True))
|
||||
if auto_start is True:
|
||||
import webbrowser
|
||||
webbrowser.open(romfile)
|
||||
|
||||
4
Utils.py
4
Utils.py
@@ -201,7 +201,7 @@ def cache_path(*path: str) -> str:
|
||||
def output_path(*path: str) -> str:
|
||||
if hasattr(output_path, 'cached_path'):
|
||||
return os.path.join(output_path.cached_path, *path)
|
||||
output_path.cached_path = user_path(get_options()["general_options"]["output_path"])
|
||||
output_path.cached_path = user_path(get_settings()["general_options"]["output_path"])
|
||||
path = os.path.join(output_path.cached_path, *path)
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
return path
|
||||
@@ -619,6 +619,8 @@ def get_fuzzy_results(input_word: str, wordlist: typing.Sequence[str], limit: ty
|
||||
|
||||
def open_filename(title: str, filetypes: typing.Sequence[typing.Tuple[str, typing.Sequence[str]]], suggest: str = "") \
|
||||
-> typing.Optional[str]:
|
||||
logging.info(f"Opening file input dialog for {title}.")
|
||||
|
||||
def run(*args: str):
|
||||
return subprocess.run(args, capture_output=True, text=True).stdout.split("\n", 1)[0] or None
|
||||
|
||||
|
||||
@@ -150,6 +150,7 @@ def host_room(room: UUID):
|
||||
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
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<a href="{{ url_for("game_info", game=game_name, lang="en") }}">Game Page</a>
|
||||
{% if world.web.tutorials %}
|
||||
<span class="link-spacer">|</span>
|
||||
<a href="{{ url_for("tutorial_landing") }}#{{ game_name }}">Setup Guides</a>
|
||||
<a href="{{ url_for("tutorial_landing", _anchor = game_name | urlencode) }}">Setup Guides</a>
|
||||
{% endif %}
|
||||
{% if world.web.options_page is string %}
|
||||
<span class="link-spacer">|</span>
|
||||
|
||||
@@ -63,12 +63,13 @@ def process_multidata(compressed_multidata, files={}):
|
||||
game_data = games_package_schema.validate(game_data)
|
||||
game_data = {key: value for key, value in sorted(game_data.items())}
|
||||
game_data["checksum"] = data_package_checksum(game_data)
|
||||
game_data_package = GameDataPackage(checksum=game_data["checksum"],
|
||||
data=pickle.dumps(game_data))
|
||||
if original_checksum != game_data["checksum"]:
|
||||
raise Exception(f"Original checksum {original_checksum} != "
|
||||
f"calculated checksum {game_data['checksum']} "
|
||||
f"for game {game}.")
|
||||
|
||||
game_data_package = GameDataPackage(checksum=game_data["checksum"],
|
||||
data=pickle.dumps(game_data))
|
||||
decompressed_multidata["datapackage"][game] = {
|
||||
"version": game_data.get("version", 0),
|
||||
"checksum": game_data["checksum"],
|
||||
@@ -192,6 +193,8 @@ def uploads():
|
||||
res = upload_zip_to_db(zfile)
|
||||
except VersionException:
|
||||
flash(f"Could not load multidata. Wrong Version detected.")
|
||||
except Exception as e:
|
||||
flash(f"Could not load multidata. File may be corrupted or incompatible. ({e})")
|
||||
else:
|
||||
if res is str:
|
||||
return res
|
||||
|
||||
@@ -110,6 +110,11 @@ local IsItemable = function()
|
||||
end
|
||||
|
||||
local is_game_complete = function()
|
||||
-- If the Cannary Byte is 0xFF, then the save RAM is untrustworthy
|
||||
if memory.read_u8(canary_byte) == 0xFF then
|
||||
return game_complete
|
||||
end
|
||||
|
||||
-- If on the title screen don't read RAM, RAM can't be trusted yet
|
||||
if IsOnTitle() then return game_complete end
|
||||
|
||||
|
||||
@@ -92,6 +92,9 @@
|
||||
/worlds/lufia2ac/ @el-u
|
||||
/worlds/lufia2ac/docs/ @wordfcuk @el-u
|
||||
|
||||
# Mario & Luigi: Superstar Saga
|
||||
/worlds/mlss/ @jamesbrq
|
||||
|
||||
# Meritous
|
||||
/worlds/meritous/ @FelicitusNeko
|
||||
|
||||
|
||||
@@ -45,9 +45,6 @@
|
||||
# TODO
|
||||
#CACHE_TYPE: "simple"
|
||||
|
||||
# TODO
|
||||
#JSON_AS_ASCII: false
|
||||
|
||||
# Host Address. This is the address encoded into the patch that will be used for client auto-connect.
|
||||
#HOST_ADDRESS: archipelago.gg
|
||||
|
||||
|
||||
6
setup.py
6
setup.py
@@ -21,7 +21,7 @@ from pathlib import Path
|
||||
|
||||
# This is a bit jank. We need cx-Freeze to be able to run anything from this script, so install it
|
||||
try:
|
||||
requirement = 'cx-Freeze>=6.15.16,<7'
|
||||
requirement = 'cx-Freeze>=7.0.0'
|
||||
import pkg_resources
|
||||
try:
|
||||
pkg_resources.require(requirement)
|
||||
@@ -228,8 +228,8 @@ class BuildCommand(setuptools.command.build.build):
|
||||
|
||||
|
||||
# Override cx_Freeze's build_exe command for pre and post build steps
|
||||
class BuildExeCommand(cx_Freeze.command.build_exe.BuildEXE):
|
||||
user_options = cx_Freeze.command.build_exe.BuildEXE.user_options + [
|
||||
class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
|
||||
user_options = cx_Freeze.command.build_exe.build_exe.user_options + [
|
||||
('yes', 'y', 'Answer "yes" to all questions.'),
|
||||
('extra-data=', None, 'Additional files to add.'),
|
||||
]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from argparse import Namespace
|
||||
from typing import List, Optional, Tuple, Type, Union
|
||||
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from BaseClasses import CollectionState, Item, ItemClassification, Location, MultiWorld, Region
|
||||
from worlds.AutoWorld import World, call_all
|
||||
|
||||
gen_steps = ("generate_early", "create_regions", "create_items", "set_rules", "generate_basic", "pre_fill")
|
||||
@@ -17,19 +17,21 @@ def setup_solo_multiworld(
|
||||
:param steps: The gen steps that should be called on the generated multiworld before returning. Default calls
|
||||
steps through pre_fill
|
||||
:param seed: The seed to be used when creating this multiworld
|
||||
:return: The generated multiworld
|
||||
"""
|
||||
return setup_multiworld(world_type, steps, seed)
|
||||
|
||||
|
||||
def setup_multiworld(worlds: Union[List[Type[World]], Type[World]], steps: Tuple[str, ...] = gen_steps,
|
||||
seed: Optional[int] = None) -> MultiWorld:
|
||||
seed: Optional[int] = None) -> MultiWorld:
|
||||
"""
|
||||
Creates a multiworld with a player for each provided world type, allowing duplicates, setting default options, and
|
||||
calling the provided gen steps.
|
||||
|
||||
:param worlds: type/s of worlds to generate a multiworld for
|
||||
:param steps: gen steps that should be called before returning. Default calls through pre_fill
|
||||
:param worlds: Type/s of worlds to generate a multiworld for
|
||||
:param steps: Gen steps that should be called before returning. Default calls through pre_fill
|
||||
:param seed: The seed to be used when creating this multiworld
|
||||
:return: The generated multiworld
|
||||
"""
|
||||
if not isinstance(worlds, list):
|
||||
worlds = [worlds]
|
||||
@@ -49,3 +51,59 @@ def setup_multiworld(worlds: Union[List[Type[World]], Type[World]], steps: Tuple
|
||||
for step in steps:
|
||||
call_all(multiworld, step)
|
||||
return multiworld
|
||||
|
||||
|
||||
class TestWorld(World):
|
||||
game = f"Test Game"
|
||||
item_name_to_id = {}
|
||||
location_name_to_id = {}
|
||||
hidden = True
|
||||
|
||||
|
||||
def generate_test_multiworld(players: int = 1) -> MultiWorld:
|
||||
"""
|
||||
Generates a multiworld using a special Test Case World class, and seed of 0.
|
||||
|
||||
:param players: Number of players to generate the multiworld for
|
||||
:return: The generated test multiworld
|
||||
"""
|
||||
multiworld = setup_multiworld([TestWorld] * players, seed=0)
|
||||
multiworld.regions += [Region("Menu", player_id + 1, multiworld) for player_id in range(players)]
|
||||
|
||||
return multiworld
|
||||
|
||||
|
||||
def generate_locations(count: int, player_id: int, region: Region, address: Optional[int] = None,
|
||||
tag: str = "") -> List[Location]:
|
||||
"""
|
||||
Generates the specified amount of locations for the player and adds them to the specified region.
|
||||
|
||||
:param count: Number of locations to create
|
||||
:param player_id: ID of the player to create the locations for
|
||||
:param address: Address for the specified locations. They will all share the same address if multiple are created
|
||||
:param region: Parent region to add these locations to
|
||||
:param tag: Tag to add to the name of the generated locations
|
||||
:return: List containing the created locations
|
||||
"""
|
||||
prefix = f"player{player_id}{tag}_location"
|
||||
|
||||
locations = [Location(player_id, f"{prefix}{i}", address, region) for i in range(count)]
|
||||
region.locations += locations
|
||||
return locations
|
||||
|
||||
|
||||
def generate_items(count: int, player_id: int, advancement: bool = False, code: int = None) -> List[Item]:
|
||||
"""
|
||||
Generates the specified amount of items for the target player.
|
||||
|
||||
:param count: The amount of items to create
|
||||
:param player_id: ID of the player to create the items for
|
||||
:param advancement: Whether the created items should be advancement
|
||||
:param code: The code the items should be created with
|
||||
:return: List containing the created items
|
||||
"""
|
||||
item_type = "prog" if advancement else ""
|
||||
classification = ItemClassification.progression if advancement else ItemClassification.filler
|
||||
|
||||
items = [Item(f"player{player_id}_{item_type}item{i}", classification, code, player_id) for i in range(count)]
|
||||
return items
|
||||
|
||||
@@ -1,41 +1,15 @@
|
||||
from typing import List, Iterable
|
||||
import unittest
|
||||
|
||||
import Options
|
||||
from Options import Accessibility
|
||||
from worlds.AutoWorld import World
|
||||
from test.general import generate_items, generate_locations, generate_test_multiworld
|
||||
from Fill import FillError, balance_multiworld_progression, fill_restrictive, \
|
||||
distribute_early_items, distribute_items_restrictive
|
||||
from BaseClasses import Entrance, LocationProgressType, MultiWorld, Region, Item, Location, \
|
||||
ItemClassification, CollectionState
|
||||
ItemClassification
|
||||
from worlds.generic.Rules import CollectionRule, add_item_rule, locality_rules, set_rule
|
||||
|
||||
|
||||
def generate_multiworld(players: int = 1) -> MultiWorld:
|
||||
multiworld = MultiWorld(players)
|
||||
multiworld.set_seed(0)
|
||||
multiworld.player_name = {}
|
||||
multiworld.state = CollectionState(multiworld)
|
||||
for i in range(players):
|
||||
player_id = i+1
|
||||
world = World(multiworld, player_id)
|
||||
multiworld.game[player_id] = f"Game {player_id}"
|
||||
multiworld.worlds[player_id] = world
|
||||
multiworld.player_name[player_id] = "Test Player " + str(player_id)
|
||||
region = Region("Menu", player_id, multiworld, "Menu Region Hint")
|
||||
multiworld.regions.append(region)
|
||||
for option_key, option in Options.PerGameCommonOptions.type_hints.items():
|
||||
if hasattr(multiworld, option_key):
|
||||
getattr(multiworld, option_key).setdefault(player_id, option.from_any(getattr(option, "default")))
|
||||
else:
|
||||
setattr(multiworld, option_key, {player_id: option.from_any(getattr(option, "default"))})
|
||||
# TODO - remove this loop once all worlds use options dataclasses
|
||||
world.options = world.options_dataclass(**{option_key: getattr(multiworld, option_key)[player_id]
|
||||
for option_key in world.options_dataclass.type_hints})
|
||||
|
||||
return multiworld
|
||||
|
||||
|
||||
class PlayerDefinition(object):
|
||||
multiworld: MultiWorld
|
||||
id: int
|
||||
@@ -55,12 +29,12 @@ class PlayerDefinition(object):
|
||||
self.regions = [menu]
|
||||
|
||||
def generate_region(self, parent: Region, size: int, access_rule: CollectionRule = lambda state: True) -> Region:
|
||||
region_tag = "_region" + str(len(self.regions))
|
||||
region_name = "player" + str(self.id) + region_tag
|
||||
region = Region("player" + str(self.id) + region_tag, self.id, self.multiworld)
|
||||
self.locations += generate_locations(size, self.id, None, region, region_tag)
|
||||
region_tag = f"_region{len(self.regions)}"
|
||||
region_name = f"player{self.id}{region_tag}"
|
||||
region = Region(f"player{self.id}{region_tag}", self.id, self.multiworld)
|
||||
self.locations += generate_locations(size, self.id, region, None, region_tag)
|
||||
|
||||
entrance = Entrance(self.id, region_name + "_entrance", parent)
|
||||
entrance = Entrance(self.id, f"{region_name}_entrance", parent)
|
||||
parent.exits.append(entrance)
|
||||
entrance.connect(region)
|
||||
entrance.access_rule = access_rule
|
||||
@@ -94,7 +68,7 @@ def region_contains(region: Region, item: Item) -> bool:
|
||||
|
||||
def generate_player_data(multiworld: MultiWorld, player_id: int, location_count: int = 0, prog_item_count: int = 0, basic_item_count: int = 0) -> PlayerDefinition:
|
||||
menu = multiworld.get_region("Menu", player_id)
|
||||
locations = generate_locations(location_count, player_id, None, menu)
|
||||
locations = generate_locations(location_count, player_id, menu, None)
|
||||
prog_items = generate_items(prog_item_count, player_id, True)
|
||||
multiworld.itempool += prog_items
|
||||
basic_items = generate_items(basic_item_count, player_id, False)
|
||||
@@ -103,28 +77,6 @@ def generate_player_data(multiworld: MultiWorld, player_id: int, location_count:
|
||||
return PlayerDefinition(multiworld, player_id, menu, locations, prog_items, basic_items)
|
||||
|
||||
|
||||
def generate_locations(count: int, player_id: int, address: int = None, region: Region = None, tag: str = "") -> List[Location]:
|
||||
locations = []
|
||||
prefix = "player" + str(player_id) + tag + "_location"
|
||||
for i in range(count):
|
||||
name = prefix + str(i)
|
||||
location = Location(player_id, name, address, region)
|
||||
locations.append(location)
|
||||
region.locations.append(location)
|
||||
return locations
|
||||
|
||||
|
||||
def generate_items(count: int, player_id: int, advancement: bool = False, code: int = None) -> List[Item]:
|
||||
items = []
|
||||
item_type = "prog" if advancement else ""
|
||||
for i in range(count):
|
||||
name = "player" + str(player_id) + "_" + item_type + "item" + str(i)
|
||||
items.append(Item(name,
|
||||
ItemClassification.progression if advancement else ItemClassification.filler,
|
||||
code, player_id))
|
||||
return items
|
||||
|
||||
|
||||
def names(objs: list) -> Iterable[str]:
|
||||
return map(lambda o: o.name, objs)
|
||||
|
||||
@@ -132,7 +84,7 @@ def names(objs: list) -> Iterable[str]:
|
||||
class TestFillRestrictive(unittest.TestCase):
|
||||
def test_basic_fill(self):
|
||||
"""Tests `fill_restrictive` fills and removes the locations and items from their respective lists"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 2, 2)
|
||||
|
||||
item0 = player1.prog_items[0]
|
||||
@@ -150,7 +102,7 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
def test_ordered_fill(self):
|
||||
"""Tests `fill_restrictive` fulfills set rules"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 2, 2)
|
||||
items = player1.prog_items
|
||||
locations = player1.locations
|
||||
@@ -167,7 +119,7 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
def test_partial_fill(self):
|
||||
"""Tests that `fill_restrictive` returns unfilled locations"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 3, 2)
|
||||
|
||||
item0 = player1.prog_items[0]
|
||||
@@ -193,7 +145,7 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
def test_minimal_fill(self):
|
||||
"""Test that fill for minimal player can have unreachable items"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 2, 2)
|
||||
|
||||
items = player1.prog_items
|
||||
@@ -218,7 +170,7 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
the non-minimal player get all items.
|
||||
"""
|
||||
|
||||
multiworld = generate_multiworld(2)
|
||||
multiworld = generate_test_multiworld(2)
|
||||
player1 = generate_player_data(multiworld, 1, 3, 3)
|
||||
player2 = generate_player_data(multiworld, 2, 3, 3)
|
||||
|
||||
@@ -245,11 +197,11 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
# all of player2's locations and items should be accessible (not all of player1's)
|
||||
for item in player2.prog_items:
|
||||
self.assertTrue(multiworld.state.has(item.name, player2.id),
|
||||
f'{item} is unreachable in {item.location}')
|
||||
f"{item} is unreachable in {item.location}")
|
||||
|
||||
def test_reversed_fill(self):
|
||||
"""Test a different set of rules can be satisfied"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 2, 2)
|
||||
|
||||
item0 = player1.prog_items[0]
|
||||
@@ -268,7 +220,7 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
def test_multi_step_fill(self):
|
||||
"""Test that fill is able to satisfy multiple spheres"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 4, 4)
|
||||
|
||||
items = player1.prog_items
|
||||
@@ -293,7 +245,7 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
def test_impossible_fill(self):
|
||||
"""Test that fill raises an error when it can't place any items"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 2, 2)
|
||||
items = player1.prog_items
|
||||
locations = player1.locations
|
||||
@@ -310,7 +262,7 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
def test_circular_fill(self):
|
||||
"""Test that fill raises an error when it can't place all items"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 3, 3)
|
||||
|
||||
item0 = player1.prog_items[0]
|
||||
@@ -331,7 +283,7 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
def test_competing_fill(self):
|
||||
"""Test that fill raises an error when it can't place items in a way to satisfy the conditions"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 2, 2)
|
||||
|
||||
item0 = player1.prog_items[0]
|
||||
@@ -348,7 +300,7 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
def test_multiplayer_fill(self):
|
||||
"""Test that items can be placed across worlds"""
|
||||
multiworld = generate_multiworld(2)
|
||||
multiworld = generate_test_multiworld(2)
|
||||
player1 = generate_player_data(multiworld, 1, 2, 2)
|
||||
player2 = generate_player_data(multiworld, 2, 2, 2)
|
||||
|
||||
@@ -369,7 +321,7 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
def test_multiplayer_rules_fill(self):
|
||||
"""Test that fill across worlds satisfies the rules"""
|
||||
multiworld = generate_multiworld(2)
|
||||
multiworld = generate_test_multiworld(2)
|
||||
player1 = generate_player_data(multiworld, 1, 2, 2)
|
||||
player2 = generate_player_data(multiworld, 2, 2, 2)
|
||||
|
||||
@@ -393,7 +345,7 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
def test_restrictive_progress(self):
|
||||
"""Test that various spheres with different requirements can be filled"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, prog_item_count=25)
|
||||
items = player1.prog_items.copy()
|
||||
multiworld.completion_condition[player1.id] = lambda state: state.has_all(
|
||||
@@ -417,7 +369,7 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
def test_swap_to_earlier_location_with_item_rule(self):
|
||||
"""Test that item swap happens and works as intended"""
|
||||
# test for PR#1109
|
||||
multiworld = generate_multiworld(1)
|
||||
multiworld = generate_test_multiworld(1)
|
||||
player1 = generate_player_data(multiworld, 1, 4, 4)
|
||||
locations = player1.locations[:] # copy required
|
||||
items = player1.prog_items[:] # copy required
|
||||
@@ -442,7 +394,7 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
def test_swap_to_earlier_location_with_item_rule2(self):
|
||||
"""Test that swap works before all items are placed"""
|
||||
multiworld = generate_multiworld(1)
|
||||
multiworld = generate_test_multiworld(1)
|
||||
player1 = generate_player_data(multiworld, 1, 5, 5)
|
||||
locations = player1.locations[:] # copy required
|
||||
items = player1.prog_items[:] # copy required
|
||||
@@ -484,7 +436,7 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
def test_double_sweep(self):
|
||||
"""Test that sweep doesn't duplicate Event items when sweeping"""
|
||||
# test for PR1114
|
||||
multiworld = generate_multiworld(1)
|
||||
multiworld = generate_test_multiworld(1)
|
||||
player1 = generate_player_data(multiworld, 1, 1, 1)
|
||||
location = player1.locations[0]
|
||||
location.address = None
|
||||
@@ -498,7 +450,7 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
|
||||
def test_correct_item_instance_removed_from_pool(self):
|
||||
"""Test that a placed item gets removed from the submitted pool"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(multiworld, 1, 2, 2)
|
||||
|
||||
player1.prog_items[0].name = "Different_item_instance_but_same_item_name"
|
||||
@@ -515,7 +467,7 @@ class TestFillRestrictive(unittest.TestCase):
|
||||
class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
def test_basic_distribute(self):
|
||||
"""Test that distribute_items_restrictive is deterministic"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(
|
||||
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
locations = player1.locations
|
||||
@@ -535,7 +487,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
|
||||
def test_excluded_distribute(self):
|
||||
"""Test that distribute_items_restrictive doesn't put advancement items on excluded locations"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(
|
||||
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
locations = player1.locations
|
||||
@@ -550,7 +502,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
|
||||
def test_non_excluded_item_distribute(self):
|
||||
"""Test that useful items aren't placed on excluded locations"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(
|
||||
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
locations = player1.locations
|
||||
@@ -565,7 +517,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
|
||||
def test_too_many_excluded_distribute(self):
|
||||
"""Test that fill fails if it can't place all progression items due to too many excluded locations"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(
|
||||
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
locations = player1.locations
|
||||
@@ -578,7 +530,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
|
||||
def test_non_excluded_item_must_distribute(self):
|
||||
"""Test that fill fails if it can't place useful items due to too many excluded locations"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(
|
||||
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
locations = player1.locations
|
||||
@@ -593,7 +545,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
|
||||
def test_priority_distribute(self):
|
||||
"""Test that priority locations receive advancement items"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(
|
||||
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
locations = player1.locations
|
||||
@@ -608,7 +560,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
|
||||
def test_excess_priority_distribute(self):
|
||||
"""Test that if there's more priority locations than advancement items, they can still fill"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(
|
||||
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
locations = player1.locations
|
||||
@@ -623,7 +575,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
|
||||
def test_multiple_world_priority_distribute(self):
|
||||
"""Test that priority fill can be satisfied for multiple worlds"""
|
||||
multiworld = generate_multiworld(3)
|
||||
multiworld = generate_test_multiworld(3)
|
||||
player1 = generate_player_data(
|
||||
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
player2 = generate_player_data(
|
||||
@@ -653,7 +605,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
|
||||
def test_can_remove_locations_in_fill_hook(self):
|
||||
"""Test that distribute_items_restrictive calls the fill hook and allows for item and location removal"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(
|
||||
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
|
||||
@@ -673,12 +625,12 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
|
||||
def test_seed_robust_to_item_order(self):
|
||||
"""Test deterministic fill"""
|
||||
mw1 = generate_multiworld()
|
||||
mw1 = generate_test_multiworld()
|
||||
gen1 = generate_player_data(
|
||||
mw1, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
distribute_items_restrictive(mw1)
|
||||
|
||||
mw2 = generate_multiworld()
|
||||
mw2 = generate_test_multiworld()
|
||||
gen2 = generate_player_data(
|
||||
mw2, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
mw2.itempool.append(mw2.itempool.pop(0))
|
||||
@@ -691,12 +643,12 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
|
||||
def test_seed_robust_to_location_order(self):
|
||||
"""Test deterministic fill even if locations in a region are reordered"""
|
||||
mw1 = generate_multiworld()
|
||||
mw1 = generate_test_multiworld()
|
||||
gen1 = generate_player_data(
|
||||
mw1, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
distribute_items_restrictive(mw1)
|
||||
|
||||
mw2 = generate_multiworld()
|
||||
mw2 = generate_test_multiworld()
|
||||
gen2 = generate_player_data(
|
||||
mw2, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||
reg = mw2.get_region("Menu", gen2.id)
|
||||
@@ -710,7 +662,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
|
||||
def test_can_reserve_advancement_items_for_general_fill(self):
|
||||
"""Test that priority locations fill still satisfies item rules"""
|
||||
multiworld = generate_multiworld()
|
||||
multiworld = generate_test_multiworld()
|
||||
player1 = generate_player_data(
|
||||
multiworld, 1, location_count=5, prog_item_count=5)
|
||||
items = player1.prog_items
|
||||
@@ -727,7 +679,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
|
||||
def test_non_excluded_local_items(self):
|
||||
"""Test that local items get placed locally in a multiworld"""
|
||||
multiworld = generate_multiworld(2)
|
||||
multiworld = generate_test_multiworld(2)
|
||||
player1 = generate_player_data(
|
||||
multiworld, 1, location_count=5, basic_item_count=5)
|
||||
player2 = generate_player_data(
|
||||
@@ -748,7 +700,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||
|
||||
def test_early_items(self) -> None:
|
||||
"""Test that the early items API successfully places items early"""
|
||||
mw = generate_multiworld(2)
|
||||
mw = generate_test_multiworld(2)
|
||||
player1 = generate_player_data(mw, 1, location_count=5, basic_item_count=5)
|
||||
player2 = generate_player_data(mw, 2, location_count=5, basic_item_count=5)
|
||||
mw.early_items[1][player1.basic_items[0].name] = 1
|
||||
@@ -803,11 +755,11 @@ class TestBalanceMultiworldProgression(unittest.TestCase):
|
||||
if location.item and location.item == item:
|
||||
return True
|
||||
|
||||
self.fail("Expected " + region.name + " to contain " + item.name +
|
||||
"\n Contains" + str(list(map(lambda location: location.item, region.locations))))
|
||||
self.fail(f"Expected {region.name} to contain {item.name}.\n"
|
||||
f"Contains{list(map(lambda location: location.item, region.locations))}")
|
||||
|
||||
def setUp(self) -> None:
|
||||
multiworld = generate_multiworld(2)
|
||||
multiworld = generate_test_multiworld(2)
|
||||
self.multiworld = multiworld
|
||||
player1 = generate_player_data(
|
||||
multiworld, 1, prog_item_count=2, basic_item_count=40)
|
||||
|
||||
@@ -264,7 +264,7 @@ def fill_dungeons_restrictive(multiworld: MultiWorld):
|
||||
|
||||
if loc in all_state_base.events:
|
||||
all_state_base.events.remove(loc)
|
||||
fill_restrictive(multiworld, all_state_base, locations, in_dungeon_items, True, True, allow_excluded=True,
|
||||
fill_restrictive(multiworld, all_state_base, locations, in_dungeon_items, lock=True, allow_excluded=True,
|
||||
name="LttP Dungeon Items")
|
||||
|
||||
|
||||
|
||||
@@ -276,13 +276,14 @@ def generate_itempool(world):
|
||||
|
||||
# set up item pool
|
||||
additional_triforce_pieces = 0
|
||||
treasure_hunt_total = 0
|
||||
if multiworld.custom:
|
||||
pool, placed_items, precollected_items, clock_mode, treasure_hunt_count = (
|
||||
pool, placed_items, precollected_items, clock_mode, treasure_hunt_required = (
|
||||
make_custom_item_pool(multiworld, player))
|
||||
multiworld.rupoor_cost = min(multiworld.customitemarray[67], 9999)
|
||||
else:
|
||||
pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, additional_triforce_pieces = (
|
||||
get_pool_core(multiworld, player))
|
||||
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_required, treasure_hunt_total,
|
||||
additional_triforce_pieces) = get_pool_core(multiworld, player)
|
||||
|
||||
for item in precollected_items:
|
||||
multiworld.push_precollected(item_factory(item, world))
|
||||
@@ -337,7 +338,8 @@ def generate_itempool(world):
|
||||
if clock_mode:
|
||||
world.clock_mode = clock_mode
|
||||
|
||||
multiworld.worlds[player].treasure_hunt_count = treasure_hunt_count % 999
|
||||
multiworld.worlds[player].treasure_hunt_required = treasure_hunt_required % 999
|
||||
multiworld.worlds[player].treasure_hunt_total = treasure_hunt_total
|
||||
|
||||
dungeon_items = [item for item in get_dungeon_item_pool_player(world)
|
||||
if item.name not in multiworld.worlds[player].dungeon_local_item_names]
|
||||
@@ -590,7 +592,8 @@ def get_pool_core(world, player: int):
|
||||
placed_items = {}
|
||||
precollected_items = []
|
||||
clock_mode: str = ""
|
||||
treasure_hunt_count: int = 1
|
||||
treasure_hunt_required: int = 0
|
||||
treasure_hunt_total: int = 0
|
||||
|
||||
diff = difficulties[difficulty]
|
||||
pool.extend(diff.alwaysitems)
|
||||
@@ -679,20 +682,21 @@ def get_pool_core(world, player: int):
|
||||
if 'triforce_hunt' in goal:
|
||||
|
||||
if world.triforce_pieces_mode[player].value == TriforcePiecesMode.option_extra:
|
||||
triforce_pieces = world.triforce_pieces_available[player].value + world.triforce_pieces_extra[player].value
|
||||
treasure_hunt_total = (world.triforce_pieces_available[player].value
|
||||
+ world.triforce_pieces_extra[player].value)
|
||||
elif world.triforce_pieces_mode[player].value == TriforcePiecesMode.option_percentage:
|
||||
percentage = float(world.triforce_pieces_percentage[player].value) / 100
|
||||
triforce_pieces = int(round(world.triforce_pieces_required[player].value * percentage, 0))
|
||||
treasure_hunt_total = int(round(world.triforce_pieces_required[player].value * percentage, 0))
|
||||
else: # available
|
||||
triforce_pieces = world.triforce_pieces_available[player].value
|
||||
treasure_hunt_total = world.triforce_pieces_available[player].value
|
||||
|
||||
triforce_pieces = min(90, max(triforce_pieces, world.triforce_pieces_required[player].value))
|
||||
triforce_pieces = min(90, max(treasure_hunt_total, world.triforce_pieces_required[player].value))
|
||||
|
||||
pieces_in_core = min(extraitems, triforce_pieces)
|
||||
additional_pieces_to_place = triforce_pieces - pieces_in_core
|
||||
pool.extend(["Triforce Piece"] * pieces_in_core)
|
||||
extraitems -= pieces_in_core
|
||||
treasure_hunt_count = world.triforce_pieces_required[player].value
|
||||
treasure_hunt_required = world.triforce_pieces_required[player].value
|
||||
|
||||
for extra in diff.extras:
|
||||
if extraitems >= len(extra):
|
||||
@@ -733,7 +737,7 @@ def get_pool_core(world, player: int):
|
||||
place_item(key_location, "Small Key (Universal)")
|
||||
pool = pool[:-3]
|
||||
|
||||
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count,
|
||||
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_required, treasure_hunt_total,
|
||||
additional_pieces_to_place)
|
||||
|
||||
|
||||
@@ -749,7 +753,8 @@ def make_custom_item_pool(world, player):
|
||||
placed_items = {}
|
||||
precollected_items = []
|
||||
clock_mode: str = ""
|
||||
treasure_hunt_count: int = 1
|
||||
treasure_hunt_required: int = 0
|
||||
treasure_hunt_total: int = 0
|
||||
|
||||
def place_item(loc, item):
|
||||
assert loc not in placed_items, "cannot place item twice"
|
||||
@@ -844,7 +849,7 @@ def make_custom_item_pool(world, player):
|
||||
if "triforce" in world.goal[player]:
|
||||
pool.extend(["Triforce Piece"] * world.triforce_pieces_available[player])
|
||||
itemtotal += world.triforce_pieces_available[player]
|
||||
treasure_hunt_count = world.triforce_pieces_required[player]
|
||||
treasure_hunt_required = world.triforce_pieces_required[player]
|
||||
|
||||
if timer in ['display', 'timed', 'timed_countdown']:
|
||||
clock_mode = 'countdown' if timer == 'timed_countdown' else 'stopwatch'
|
||||
@@ -889,4 +894,4 @@ def make_custom_item_pool(world, player):
|
||||
pool.extend(['Nothing'] * (total_items_to_place - itemtotal))
|
||||
logging.warning(f"Pool was filled up with {total_items_to_place - itemtotal} Nothing's for player {player}")
|
||||
|
||||
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count)
|
||||
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_required)
|
||||
|
||||
@@ -433,7 +433,7 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory):
|
||||
if multiworld.key_drop_shuffle[player]:
|
||||
key_drop_enemies = {
|
||||
0x4DA20, 0x4DA5C, 0x4DB7F, 0x4DD73, 0x4DDC3, 0x4DE07, 0x4E201,
|
||||
0x4E20A, 0x4E326, 0x4E4F7, 0x4E686, 0x4E70C, 0x4E7C8, 0x4E7FA
|
||||
0x4E20A, 0x4E326, 0x4E4F7, 0x4E687, 0x4E70C, 0x4E7C8, 0x4E7FA
|
||||
}
|
||||
for enemy in key_drop_enemies:
|
||||
if rom.read_byte(enemy) == 0x12:
|
||||
@@ -1269,7 +1269,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
|
||||
rom.write_int32(0x18020C, 0) # starting time (in frames, sint32)
|
||||
|
||||
# set up goals for treasure hunt
|
||||
rom.write_int16(0x180163, local_world.treasure_hunt_count)
|
||||
rom.write_int16(0x180163, local_world.treasure_hunt_required)
|
||||
rom.write_bytes(0x180165, [0x0E, 0x28]) # Triforce Piece Sprite
|
||||
rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled)
|
||||
|
||||
@@ -1859,7 +1859,7 @@ def apply_oof_sfx(rom, oof: str):
|
||||
rom.write_bytes(0x12803A, oof_bytes)
|
||||
rom.write_bytes(0x12803A + len(oof_bytes), [0xEB, 0xEB])
|
||||
|
||||
#Enemizer patch: prevent Enemizer from overwriting $3188 in SPC memory with an unused sound effect ("WHAT")
|
||||
# Enemizer patch: prevent Enemizer from overwriting $3188 in SPC memory with an unused sound effect ("WHAT")
|
||||
rom.write_bytes(0x13000D, [0x00, 0x00, 0x00, 0x08])
|
||||
|
||||
|
||||
@@ -2482,16 +2482,16 @@ def write_strings(rom, world, player):
|
||||
tt['sign_ganon'] = 'Go find the Triforce pieces with your friends... Ganon is invincible!'
|
||||
else:
|
||||
tt['sign_ganon'] = 'Go find the Triforce pieces... Ganon is invincible!'
|
||||
if w.treasure_hunt_count > 1:
|
||||
if w.treasure_hunt_required > 1:
|
||||
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\n" \
|
||||
"invisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\n" \
|
||||
"hidden in a hollow tree. If you bring\n%d Triforce pieces out of %d, I can reassemble it." % \
|
||||
(w.treasure_hunt_count, world.triforce_pieces_available[player])
|
||||
(w.treasure_hunt_required, w.treasure_hunt_total)
|
||||
else:
|
||||
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\n" \
|
||||
"invisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\n" \
|
||||
"hidden in a hollow tree. If you bring\n%d Triforce piece out of %d, I can reassemble it." % \
|
||||
(w.treasure_hunt_count, world.triforce_pieces_available[player])
|
||||
(w.treasure_hunt_required, w.treasure_hunt_total)
|
||||
elif world.goal[player] in ['pedestal']:
|
||||
tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Your goal is at the pedestal.'
|
||||
tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.'
|
||||
@@ -2500,20 +2500,20 @@ def write_strings(rom, world, player):
|
||||
tt['ganon_fall_in'] = Ganon1_texts[local_random.randint(0, len(Ganon1_texts) - 1)]
|
||||
tt['ganon_fall_in_alt'] = 'You cannot defeat me until you finish your goal!'
|
||||
tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!'
|
||||
if w.treasure_hunt_count > 1:
|
||||
if w.treasure_hunt_required > 1:
|
||||
if world.goal[player] == 'ganon_triforce_hunt' and world.players > 1:
|
||||
tt['sign_ganon'] = 'You need to find %d Triforce pieces out of %d with your friends to defeat Ganon.' % \
|
||||
(w.treasure_hunt_count, world.triforce_pieces_available[player])
|
||||
(w.treasure_hunt_required, w.treasure_hunt_total)
|
||||
elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']:
|
||||
tt['sign_ganon'] = 'You need to find %d Triforce pieces out of %d to defeat Ganon.' % \
|
||||
(w.treasure_hunt_count, world.triforce_pieces_available[player])
|
||||
(w.treasure_hunt_required, w.treasure_hunt_total)
|
||||
else:
|
||||
if world.goal[player] == 'ganon_triforce_hunt' and world.players > 1:
|
||||
tt['sign_ganon'] = 'You need to find %d Triforce piece out of %d with your friends to defeat Ganon.' % \
|
||||
(w.treasure_hunt_count, world.triforce_pieces_available[player])
|
||||
(w.treasure_hunt_required, w.treasure_hunt_total)
|
||||
elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']:
|
||||
tt['sign_ganon'] = 'You need to find %d Triforce piece out of %d to defeat Ganon.' % \
|
||||
(w.treasure_hunt_count, world.triforce_pieces_available[player])
|
||||
(w.treasure_hunt_required, w.treasure_hunt_total)
|
||||
|
||||
tt['kakariko_tavern_fisherman'] = TavernMan_texts[local_random.randint(0, len(TavernMan_texts) - 1)]
|
||||
|
||||
@@ -3021,7 +3021,7 @@ def get_base_rom_bytes(file_name: str = "") -> bytes:
|
||||
|
||||
|
||||
def get_base_rom_path(file_name: str = "") -> str:
|
||||
options = Utils.get_options()
|
||||
options = Utils.get_settings()
|
||||
if not file_name:
|
||||
file_name = options["lttp_options"]["rom_file"]
|
||||
if not os.path.exists(file_name):
|
||||
|
||||
@@ -64,20 +64,24 @@ def set_rules(world):
|
||||
|
||||
if world.glitches_required[player] == 'no_glitches':
|
||||
no_glitches_rules(world, player)
|
||||
forbid_bomb_jump_requirements(world, player)
|
||||
elif world.glitches_required[player] == 'overworld_glitches':
|
||||
# Initially setting no_glitches_rules to set the baseline rules for some
|
||||
# entrances. The overworld_glitches_rules set is primarily additive.
|
||||
no_glitches_rules(world, player)
|
||||
fake_flipper_rules(world, player)
|
||||
overworld_glitches_rules(world, player)
|
||||
forbid_bomb_jump_requirements(world, player)
|
||||
elif world.glitches_required[player] in ['hybrid_major_glitches', 'no_logic']:
|
||||
no_glitches_rules(world, player)
|
||||
fake_flipper_rules(world, player)
|
||||
overworld_glitches_rules(world, player)
|
||||
underworld_glitches_rules(world, player)
|
||||
bomb_jump_requirements(world, player)
|
||||
elif world.glitches_required[player] == 'minor_glitches':
|
||||
no_glitches_rules(world, player)
|
||||
fake_flipper_rules(world, player)
|
||||
forbid_bomb_jump_requirements(world, player)
|
||||
else:
|
||||
raise NotImplementedError(f'Not implemented yet: Logic - {world.glitches_required[player]}')
|
||||
|
||||
@@ -290,6 +294,9 @@ def global_rules(multiworld: MultiWorld, player: int):
|
||||
lambda state: state.has('Hookshot', player) or state.has('Pegasus Boots', player))
|
||||
set_rule(multiworld.get_location('Hookshot Cave - Bottom Left', player), lambda state: state.has('Hookshot', player))
|
||||
|
||||
set_rule(multiworld.get_location('Hyrule Castle - Map Guard Key Drop', player),
|
||||
lambda state: can_kill_most_things(state, player, 1))
|
||||
|
||||
set_rule(multiworld.get_entrance('Sewers Door', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) or (
|
||||
multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal and multiworld.mode[
|
||||
@@ -536,6 +543,8 @@ def global_rules(multiworld: MultiWorld, player: int):
|
||||
set_rule(multiworld.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has('Pegasus Boots', player))
|
||||
set_rule(multiworld.get_entrance('Ganons Tower (Tile Room)', player), lambda state: state.has('Cane of Somaria', player))
|
||||
set_rule(multiworld.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hammer', player) and (state.has('Hookshot', player) or state.has('Pegasus Boots', player)))
|
||||
if multiworld.pot_shuffle[player]:
|
||||
set_rule(multiworld.get_location('Ganons Tower - Conveyor Cross Pot Key', player), lambda state: state.has('Hammer', player) and (state.has('Hookshot', player) or state.has('Pegasus Boots', player)))
|
||||
set_rule(multiworld.get_entrance('Ganons Tower (Map Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8) or (
|
||||
location_item_name(state, 'Ganons Tower - Map Chest', player) in [('Big Key (Ganons Tower)', player)] and state._lttp_has_key('Small Key (Ganons Tower)', player, 6)))
|
||||
|
||||
@@ -554,8 +563,8 @@ def global_rules(multiworld: MultiWorld, player: int):
|
||||
set_rule(multiworld.get_location('Ganons Tower - Firesnake Room', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or
|
||||
((item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests, [player] * len(randomizer_room_chests))) or item_name_in_location_names(state, 'Small Key (Ganons Tower)', player, [('Ganons Tower - Firesnake Room', player)])) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5)))
|
||||
for location in randomizer_room_chests:
|
||||
set_rule(multiworld.get_location(location, player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8) or (
|
||||
item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests, [player] * len(randomizer_room_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 6)))
|
||||
set_rule(multiworld.get_location(location, player), lambda state: can_use_bombs(state, player) and (state._lttp_has_key('Small Key (Ganons Tower)', player, 8) or (
|
||||
item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests, [player] * len(randomizer_room_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 6))))
|
||||
|
||||
# Once again it is possible to need more than 7 keys...
|
||||
set_rule(multiworld.get_entrance('Ganons Tower (Tile Room) Key Door', player), lambda state: state.has('Fire Rod', player) and (state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or (
|
||||
@@ -900,7 +909,6 @@ def no_glitches_rules(world, player):
|
||||
|
||||
add_rule(world.get_entrance('Ganons Tower (Double Switch Room)', player), lambda state: state.has('Hookshot', player))
|
||||
set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: False) # no glitches does not require block override
|
||||
forbid_bomb_jump_requirements(world, player)
|
||||
add_conditional_lamps(world, player)
|
||||
|
||||
def fake_flipper_rules(world, player):
|
||||
@@ -928,12 +936,20 @@ def fake_flipper_rules(world, player):
|
||||
set_rule(world.get_entrance('East Dark World River Pier', player), lambda state: state.has('Moon Pearl', player))
|
||||
|
||||
|
||||
def forbid_bomb_jump_requirements(world, player):
|
||||
def bomb_jump_requirements(multiworld, player):
|
||||
DMs_room_chests = ['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right', 'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right']
|
||||
for location in DMs_room_chests:
|
||||
add_rule(world.get_location(location, player), lambda state: state.has('Hookshot', player))
|
||||
set_rule(world.get_entrance('Paradox Cave Bomb Jump', player), lambda state: False)
|
||||
set_rule(world.get_entrance('Skull Woods First Section Bomb Jump', player), lambda state: False)
|
||||
add_rule(multiworld.get_location(location, player), lambda state: can_use_bombs(state, player), combine="or")
|
||||
set_rule(multiworld.get_entrance('Paradox Cave Bomb Jump', player), lambda state: can_use_bombs(state, player))
|
||||
set_rule(multiworld.get_entrance('Skull Woods First Section Bomb Jump', player), lambda state: can_use_bombs(state, player))
|
||||
|
||||
|
||||
def forbid_bomb_jump_requirements(multiworld, player):
|
||||
DMs_room_chests = ['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right', 'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right']
|
||||
for location in DMs_room_chests:
|
||||
add_rule(multiworld.get_location(location, player), lambda state: state.has('Hookshot', player))
|
||||
set_rule(multiworld.get_entrance('Paradox Cave Bomb Jump', player), lambda state: False)
|
||||
set_rule(multiworld.get_entrance('Skull Woods First Section Bomb Jump', player), lambda state: False)
|
||||
|
||||
|
||||
DW_Entrances = ['Bumper Cave (Bottom)',
|
||||
@@ -1012,9 +1028,6 @@ def add_conditional_lamps(world, player):
|
||||
|
||||
def open_rules(world, player):
|
||||
|
||||
set_rule(world.get_location('Hyrule Castle - Map Guard Key Drop', player),
|
||||
lambda state: can_kill_most_things(state, player, 1))
|
||||
|
||||
def basement_key_rule(state):
|
||||
if location_item_name(state, 'Sewers - Key Rat Key Drop', player) == ("Small Key (Hyrule Castle)", player):
|
||||
return state._lttp_has_key("Small Key (Hyrule Castle)", player, 2)
|
||||
@@ -1023,7 +1036,7 @@ def open_rules(world, player):
|
||||
|
||||
set_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player),
|
||||
lambda state: basement_key_rule(state) and can_kill_most_things(state, player, 2))
|
||||
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), basement_key_rule)
|
||||
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: basement_key_rule(state) and can_kill_most_things(state, player, 1))
|
||||
|
||||
set_rule(world.get_location('Sewers - Key Rat Key Drop', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 3) and can_kill_most_things(state, player, 1))
|
||||
@@ -1031,8 +1044,10 @@ def open_rules(world, player):
|
||||
set_rule(world.get_location('Hyrule Castle - Big Key Drop', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) and can_kill_most_things(state, player, 1))
|
||||
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) and
|
||||
state.has('Big Key (Hyrule Castle)', player))
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4)
|
||||
and state.has('Big Key (Hyrule Castle)', player)
|
||||
and (world.enemy_health[player] in ("easy", "default")
|
||||
or can_kill_most_things(state, player, 1)))
|
||||
|
||||
|
||||
def swordless_rules(world, player):
|
||||
@@ -1062,6 +1077,7 @@ def add_connection(parent_name, target_name, entrance_name, world, player):
|
||||
parent.exits.append(connection)
|
||||
connection.connect(target)
|
||||
|
||||
|
||||
def standard_rules(world, player):
|
||||
add_connection('Menu', 'Hyrule Castle Secret Entrance', 'Uncle S&Q', world, player)
|
||||
world.get_entrance('Uncle S&Q', player).hide_path = True
|
||||
@@ -1073,18 +1089,23 @@ def standard_rules(world, player):
|
||||
|
||||
if world.small_key_shuffle[player] != small_key_shuffle.option_universal:
|
||||
set_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1))
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1)
|
||||
and can_kill_most_things(state, player, 2))
|
||||
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1))
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1)
|
||||
and can_kill_most_things(state, player, 1))
|
||||
|
||||
set_rule(world.get_location('Hyrule Castle - Big Key Drop', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 2))
|
||||
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 2) and
|
||||
state.has('Big Key (Hyrule Castle)', player))
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 2)
|
||||
and state.has('Big Key (Hyrule Castle)', player)
|
||||
and (world.enemy_health[player] in ("easy", "default")
|
||||
or can_kill_most_things(state, player, 1)))
|
||||
|
||||
set_rule(world.get_location('Sewers - Key Rat Key Drop', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 3))
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 3)
|
||||
and can_kill_most_things(state, player, 1))
|
||||
else:
|
||||
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player),
|
||||
lambda state: state.has('Big Key (Hyrule Castle)', player))
|
||||
|
||||
@@ -30,7 +30,7 @@ def can_shoot_arrows(state: CollectionState, player: int) -> bool:
|
||||
|
||||
|
||||
def has_triforce_pieces(state: CollectionState, player: int) -> bool:
|
||||
count = state.multiworld.worlds[player].treasure_hunt_count
|
||||
count = state.multiworld.worlds[player].treasure_hunt_required
|
||||
return state.count('Triforce Piece', player) + state.count('Power Star', player) >= count
|
||||
|
||||
|
||||
|
||||
@@ -261,7 +261,8 @@ class ALTTPWorld(World):
|
||||
fix_fake_world: bool = True
|
||||
|
||||
clock_mode: str = ""
|
||||
treasure_hunt_count: int = 1
|
||||
treasure_hunt_required: int = 0
|
||||
treasure_hunt_total: int = 0
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.dungeon_local_item_names = set()
|
||||
|
||||
@@ -15,7 +15,7 @@ class TestDungeon(LTTPTestBase):
|
||||
self.remove_exits = [] # Block dungeon exits
|
||||
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
|
||||
self.multiworld.bombless_start[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = 2
|
||||
create_regions(self.multiworld, 1)
|
||||
self.multiworld.worlds[1].create_dungeons()
|
||||
create_shops(self.multiworld, 1)
|
||||
|
||||
@@ -33,22 +33,26 @@ class TestGanonsTower(TestDungeon):
|
||||
["Ganons Tower - Randomizer Room - Top Left", False, []],
|
||||
["Ganons Tower - Randomizer Room - Top Left", False, [], ['Hammer']],
|
||||
["Ganons Tower - Randomizer Room - Top Left", False, [], ['Hookshot']],
|
||||
["Ganons Tower - Randomizer Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Randomizer Room - Top Left", False, [], ['Bomb Upgrade (50)']],
|
||||
["Ganons Tower - Randomizer Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Bomb Upgrade (50)']],
|
||||
|
||||
["Ganons Tower - Randomizer Room - Top Right", False, []],
|
||||
["Ganons Tower - Randomizer Room - Top Right", False, [], ['Hammer']],
|
||||
["Ganons Tower - Randomizer Room - Top Right", False, [], ['Hookshot']],
|
||||
["Ganons Tower - Randomizer Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Randomizer Room - Top Right", False, [], ['Bomb Upgrade (50)']],
|
||||
["Ganons Tower - Randomizer Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Bomb Upgrade (50)']],
|
||||
|
||||
["Ganons Tower - Randomizer Room - Bottom Left", False, []],
|
||||
["Ganons Tower - Randomizer Room - Bottom Left", False, [], ['Hammer']],
|
||||
["Ganons Tower - Randomizer Room - Bottom Left", False, [], ['Hookshot']],
|
||||
["Ganons Tower - Randomizer Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Randomizer Room - Bottom Left", False, [], ['Bomb Upgrade (50)']],
|
||||
["Ganons Tower - Randomizer Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Bomb Upgrade (50)']],
|
||||
|
||||
["Ganons Tower - Randomizer Room - Bottom Right", False, []],
|
||||
["Ganons Tower - Randomizer Room - Bottom Right", False, [], ['Hammer']],
|
||||
["Ganons Tower - Randomizer Room - Bottom Right", False, [], ['Hookshot']],
|
||||
["Ganons Tower - Randomizer Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Randomizer Room - Bottom Right", False, [], ['Bomb Upgrade (50)']],
|
||||
["Ganons Tower - Randomizer Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Bomb Upgrade (50)']],
|
||||
|
||||
["Ganons Tower - Firesnake Room", False, []],
|
||||
["Ganons Tower - Firesnake Room", False, [], ['Hammer']],
|
||||
|
||||
@@ -16,7 +16,7 @@ class TestInverted(TestBase, LTTPTestBase):
|
||||
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
|
||||
self.multiworld.mode[1].value = 2
|
||||
self.multiworld.bombless_start[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = 2
|
||||
create_inverted_regions(self.multiworld, 1)
|
||||
self.world.create_dungeons()
|
||||
create_shops(self.multiworld, 1)
|
||||
|
||||
@@ -17,7 +17,7 @@ class TestInvertedMinor(TestBase, LTTPTestBase):
|
||||
self.multiworld.mode[1].value = 2
|
||||
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("minor_glitches")
|
||||
self.multiworld.bombless_start[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = 2
|
||||
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
|
||||
create_inverted_regions(self.multiworld, 1)
|
||||
self.world.create_dungeons()
|
||||
|
||||
@@ -17,7 +17,7 @@ class TestInvertedOWG(TestBase, LTTPTestBase):
|
||||
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("overworld_glitches")
|
||||
self.multiworld.mode[1].value = 2
|
||||
self.multiworld.bombless_start[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = 2
|
||||
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
|
||||
create_inverted_regions(self.multiworld, 1)
|
||||
self.world.create_dungeons()
|
||||
|
||||
@@ -13,7 +13,7 @@ class TestMinor(TestBase, LTTPTestBase):
|
||||
self.world_setup()
|
||||
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("minor_glitches")
|
||||
self.multiworld.bombless_start[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = 2
|
||||
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
|
||||
self.world.er_seed = 0
|
||||
self.world.create_regions()
|
||||
|
||||
@@ -14,7 +14,7 @@ class TestVanillaOWG(TestBase, LTTPTestBase):
|
||||
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
|
||||
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("overworld_glitches")
|
||||
self.multiworld.bombless_start[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = 2
|
||||
self.multiworld.worlds[1].er_seed = 0
|
||||
self.multiworld.worlds[1].create_regions()
|
||||
self.multiworld.worlds[1].create_items()
|
||||
|
||||
@@ -13,7 +13,7 @@ class TestVanilla(TestBase, LTTPTestBase):
|
||||
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("no_glitches")
|
||||
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
|
||||
self.multiworld.bombless_start[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = 2
|
||||
self.multiworld.worlds[1].er_seed = 0
|
||||
self.multiworld.worlds[1].create_regions()
|
||||
self.multiworld.worlds[1].create_items()
|
||||
|
||||
@@ -1,6 +1,38 @@
|
||||
# Celeste 64 - Changelog
|
||||
|
||||
|
||||
## v1.2
|
||||
|
||||
### Features:
|
||||
|
||||
- New optional Location Checks
|
||||
- Friendsanity
|
||||
- Signsanity
|
||||
- Carsanity
|
||||
- Move Shuffle
|
||||
- Basic movement abilities can be shuffled into the item pool
|
||||
- Ground Dash
|
||||
- Air Dash
|
||||
- Skid Jump
|
||||
- Climb
|
||||
- Logic Difficulty
|
||||
- Completely overhauled logic system
|
||||
- Standard or Hard logic difficulty can be chosen
|
||||
- Badeline Chasers
|
||||
- Opt-in options which cause Badelines to start following you as you play, which will kill on contact
|
||||
- These can be set to spawn based on either:
|
||||
- The number of locations you've checked
|
||||
- The number of Strawberry items you've received
|
||||
- How fast they follow behind you can be specified
|
||||
|
||||
### Quality of Life:
|
||||
|
||||
- The maximum number of Strawberries in the item pool can be directly set
|
||||
- The required amount of Strawberries is now set via percentage
|
||||
- All items beyond the amount placed in the item pool will be `Raspberry` items, which have no effect
|
||||
- Any unique items placed into the `start_inventory` will not be placed into the item pool
|
||||
|
||||
|
||||
## v1.1 - First Stable Release
|
||||
|
||||
### Features:
|
||||
|
||||
@@ -16,43 +16,29 @@ class Celeste64ItemData(NamedTuple):
|
||||
type: ItemClassification = ItemClassification.filler
|
||||
|
||||
|
||||
item_data_table: Dict[str, Celeste64ItemData] = {
|
||||
ItemName.strawberry: Celeste64ItemData(
|
||||
code = celeste_64_base_id + 0,
|
||||
type=ItemClassification.progression_skip_balancing,
|
||||
),
|
||||
ItemName.dash_refill: Celeste64ItemData(
|
||||
code = celeste_64_base_id + 1,
|
||||
type=ItemClassification.progression,
|
||||
),
|
||||
ItemName.double_dash_refill: Celeste64ItemData(
|
||||
code = celeste_64_base_id + 2,
|
||||
type=ItemClassification.progression,
|
||||
),
|
||||
ItemName.feather: Celeste64ItemData(
|
||||
code = celeste_64_base_id + 3,
|
||||
type=ItemClassification.progression,
|
||||
),
|
||||
ItemName.coin: Celeste64ItemData(
|
||||
code = celeste_64_base_id + 4,
|
||||
type=ItemClassification.progression,
|
||||
),
|
||||
ItemName.cassette: Celeste64ItemData(
|
||||
code = celeste_64_base_id + 5,
|
||||
type=ItemClassification.progression,
|
||||
),
|
||||
ItemName.traffic_block: Celeste64ItemData(
|
||||
code = celeste_64_base_id + 6,
|
||||
type=ItemClassification.progression,
|
||||
),
|
||||
ItemName.spring: Celeste64ItemData(
|
||||
code = celeste_64_base_id + 7,
|
||||
type=ItemClassification.progression,
|
||||
),
|
||||
ItemName.breakables: Celeste64ItemData(
|
||||
code = celeste_64_base_id + 8,
|
||||
type=ItemClassification.progression,
|
||||
)
|
||||
collectable_item_data_table: Dict[str, Celeste64ItemData] = {
|
||||
ItemName.strawberry: Celeste64ItemData(celeste_64_base_id + 0x0, ItemClassification.progression_skip_balancing),
|
||||
ItemName.raspberry: Celeste64ItemData(celeste_64_base_id + 0x9, ItemClassification.filler),
|
||||
}
|
||||
|
||||
unlockable_item_data_table: Dict[str, Celeste64ItemData] = {
|
||||
ItemName.dash_refill: Celeste64ItemData(celeste_64_base_id + 0x1, ItemClassification.progression),
|
||||
ItemName.double_dash_refill: Celeste64ItemData(celeste_64_base_id + 0x2, ItemClassification.progression),
|
||||
ItemName.feather: Celeste64ItemData(celeste_64_base_id + 0x3, ItemClassification.progression),
|
||||
ItemName.coin: Celeste64ItemData(celeste_64_base_id + 0x4, ItemClassification.progression),
|
||||
ItemName.cassette: Celeste64ItemData(celeste_64_base_id + 0x5, ItemClassification.progression),
|
||||
ItemName.traffic_block: Celeste64ItemData(celeste_64_base_id + 0x6, ItemClassification.progression),
|
||||
ItemName.spring: Celeste64ItemData(celeste_64_base_id + 0x7, ItemClassification.progression),
|
||||
ItemName.breakables: Celeste64ItemData(celeste_64_base_id + 0x8, ItemClassification.progression),
|
||||
}
|
||||
|
||||
move_item_data_table: Dict[str, Celeste64ItemData] = {
|
||||
ItemName.ground_dash: Celeste64ItemData(celeste_64_base_id + 0xA, ItemClassification.progression),
|
||||
ItemName.air_dash: Celeste64ItemData(celeste_64_base_id + 0xB, ItemClassification.progression),
|
||||
ItemName.skid_jump: Celeste64ItemData(celeste_64_base_id + 0xC, ItemClassification.progression),
|
||||
ItemName.climb: Celeste64ItemData(celeste_64_base_id + 0xD, ItemClassification.progression),
|
||||
}
|
||||
|
||||
item_data_table: Dict[str, Celeste64ItemData] = {**collectable_item_data_table, **unlockable_item_data_table, **move_item_data_table}
|
||||
|
||||
item_table = {name: data.code for name, data in item_data_table.items() if data.code is not None}
|
||||
|
||||
@@ -16,127 +16,67 @@ class Celeste64LocationData(NamedTuple):
|
||||
address: Optional[int] = None
|
||||
|
||||
|
||||
location_data_table: Dict[str, Celeste64LocationData] = {
|
||||
LocationName.strawberry_1 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 0,
|
||||
),
|
||||
LocationName.strawberry_2 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 1,
|
||||
),
|
||||
LocationName.strawberry_3 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 2,
|
||||
),
|
||||
LocationName.strawberry_4 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 3,
|
||||
),
|
||||
LocationName.strawberry_5 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 4,
|
||||
),
|
||||
LocationName.strawberry_6 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 5,
|
||||
),
|
||||
LocationName.strawberry_7 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 6,
|
||||
),
|
||||
LocationName.strawberry_8 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 7,
|
||||
),
|
||||
LocationName.strawberry_9 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 8,
|
||||
),
|
||||
LocationName.strawberry_10 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 9,
|
||||
),
|
||||
LocationName.strawberry_11 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 10,
|
||||
),
|
||||
LocationName.strawberry_12 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 11,
|
||||
),
|
||||
LocationName.strawberry_13 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 12,
|
||||
),
|
||||
LocationName.strawberry_14 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 13,
|
||||
),
|
||||
LocationName.strawberry_15 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 14,
|
||||
),
|
||||
LocationName.strawberry_16 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 15,
|
||||
),
|
||||
LocationName.strawberry_17 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 16,
|
||||
),
|
||||
LocationName.strawberry_18 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 17,
|
||||
),
|
||||
LocationName.strawberry_19 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 18,
|
||||
),
|
||||
LocationName.strawberry_20 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 19,
|
||||
),
|
||||
LocationName.strawberry_21 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 20,
|
||||
),
|
||||
LocationName.strawberry_22 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 21,
|
||||
),
|
||||
LocationName.strawberry_23 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 22,
|
||||
),
|
||||
LocationName.strawberry_24 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 23,
|
||||
),
|
||||
LocationName.strawberry_25 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 24,
|
||||
),
|
||||
LocationName.strawberry_26 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 25,
|
||||
),
|
||||
LocationName.strawberry_27 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 26,
|
||||
),
|
||||
LocationName.strawberry_28 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 27,
|
||||
),
|
||||
LocationName.strawberry_29 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 28,
|
||||
),
|
||||
LocationName.strawberry_30 : Celeste64LocationData(
|
||||
region = "Forsaken City",
|
||||
address = celeste_64_base_id + 29,
|
||||
)
|
||||
strawberry_location_data_table: Dict[str, Celeste64LocationData] = {
|
||||
LocationName.strawberry_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x00),
|
||||
LocationName.strawberry_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x01),
|
||||
LocationName.strawberry_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x02),
|
||||
LocationName.strawberry_4: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x03),
|
||||
LocationName.strawberry_5: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x04),
|
||||
LocationName.strawberry_6: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x05),
|
||||
LocationName.strawberry_7: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x06),
|
||||
LocationName.strawberry_8: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x07),
|
||||
LocationName.strawberry_9: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x08),
|
||||
LocationName.strawberry_10: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x09),
|
||||
LocationName.strawberry_11: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0A),
|
||||
LocationName.strawberry_12: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0B),
|
||||
LocationName.strawberry_13: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0C),
|
||||
LocationName.strawberry_14: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0D),
|
||||
LocationName.strawberry_15: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0E),
|
||||
LocationName.strawberry_16: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0F),
|
||||
LocationName.strawberry_17: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x10),
|
||||
LocationName.strawberry_18: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x11),
|
||||
LocationName.strawberry_19: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x12),
|
||||
LocationName.strawberry_20: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x13),
|
||||
LocationName.strawberry_21: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x14),
|
||||
LocationName.strawberry_22: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x15),
|
||||
LocationName.strawberry_23: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x16),
|
||||
LocationName.strawberry_24: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x17),
|
||||
LocationName.strawberry_25: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x18),
|
||||
LocationName.strawberry_26: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x19),
|
||||
LocationName.strawberry_27: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1A),
|
||||
LocationName.strawberry_28: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1B),
|
||||
LocationName.strawberry_29: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1C),
|
||||
LocationName.strawberry_30: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1D),
|
||||
}
|
||||
|
||||
friend_location_data_table: Dict[str, Celeste64LocationData] = {
|
||||
LocationName.granny_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x00),
|
||||
LocationName.granny_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x01),
|
||||
LocationName.granny_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x02),
|
||||
LocationName.theo_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x03),
|
||||
LocationName.theo_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x04),
|
||||
LocationName.theo_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x05),
|
||||
LocationName.badeline_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x06),
|
||||
LocationName.badeline_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x07),
|
||||
LocationName.badeline_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x08),
|
||||
}
|
||||
|
||||
sign_location_data_table: Dict[str, Celeste64LocationData] = {
|
||||
LocationName.sign_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x00),
|
||||
LocationName.sign_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x01),
|
||||
LocationName.sign_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x02),
|
||||
LocationName.sign_4: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x03),
|
||||
LocationName.sign_5: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x04),
|
||||
}
|
||||
|
||||
car_location_data_table: Dict[str, Celeste64LocationData] = {
|
||||
LocationName.car_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x300 + 0x00),
|
||||
LocationName.car_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x300 + 0x01),
|
||||
}
|
||||
|
||||
location_data_table: Dict[str, Celeste64LocationData] = {**strawberry_location_data_table,
|
||||
**friend_location_data_table,
|
||||
**sign_location_data_table,
|
||||
**car_location_data_table}
|
||||
|
||||
location_table = {name: data.address for name, data in location_data_table.items() if data.address is not None}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
strawberry = "Strawberry"
|
||||
raspberry = "Raspberry"
|
||||
|
||||
dash_refill = "Dash Refills"
|
||||
double_dash_refill = "Double Dash Refills"
|
||||
@@ -9,3 +10,8 @@ cassette = "Cassettes"
|
||||
traffic_block = "Traffic Blocks"
|
||||
spring = "Springs"
|
||||
breakables = "Breakable Blocks"
|
||||
|
||||
ground_dash = "Ground Dash"
|
||||
air_dash = "Air Dash"
|
||||
skid_jump = "Skid Jump"
|
||||
climb = "Climb"
|
||||
|
||||
@@ -29,3 +29,25 @@ strawberry_27 = "Distant Feather Cassette Strawberry"
|
||||
strawberry_28 = "Feather Arches Cassette Strawberry"
|
||||
strawberry_29 = "North-East Tower Cassette Strawberry"
|
||||
strawberry_30 = "Badeline Cassette Strawberry"
|
||||
|
||||
# Friend Locations
|
||||
granny_1 = "Granny Conversation 1"
|
||||
granny_2 = "Granny Conversation 2"
|
||||
granny_3 = "Granny Conversation 3"
|
||||
theo_1 = "Theo Conversation 1"
|
||||
theo_2 = "Theo Conversation 2"
|
||||
theo_3 = "Theo Conversation 3"
|
||||
badeline_1 = "Badeline Conversation 1"
|
||||
badeline_2 = "Badeline Conversation 2"
|
||||
badeline_3 = "Badeline Conversation 3"
|
||||
|
||||
# Sign Locations
|
||||
sign_1 = "Camera Sign"
|
||||
sign_2 = "Skid Jump Sign"
|
||||
sign_3 = "Dash Jump Sign"
|
||||
sign_4 = "Lonely Sign"
|
||||
sign_5 = "Credits Sign"
|
||||
|
||||
# Car Locations
|
||||
car_1 = "Intro Car"
|
||||
car_2 = "Secret Car"
|
||||
|
||||
@@ -1,25 +1,124 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from Options import Range, DeathLink, PerGameCommonOptions
|
||||
from Options import Choice, Range, Toggle, DeathLink, PerGameCommonOptions
|
||||
|
||||
|
||||
class StrawberriesRequired(Range):
|
||||
"""How many Strawberries you must receive to finish"""
|
||||
display_name = "Strawberries Required"
|
||||
range_start = 0
|
||||
range_end = 20
|
||||
default = 15
|
||||
|
||||
class DeathLinkAmnesty(Range):
|
||||
"""How many deaths it takes to send a DeathLink"""
|
||||
"""
|
||||
How many deaths it takes to send a DeathLink
|
||||
"""
|
||||
display_name = "Death Link Amnesty"
|
||||
range_start = 1
|
||||
range_end = 30
|
||||
default = 10
|
||||
|
||||
class TotalStrawberries(Range):
|
||||
"""
|
||||
How many Strawberries exist
|
||||
"""
|
||||
display_name = "Total Strawberries"
|
||||
range_start = 0
|
||||
range_end = 46
|
||||
default = 20
|
||||
|
||||
class StrawberriesRequiredPercentage(Range):
|
||||
"""
|
||||
Percentage of existing Strawberries you must receive to finish
|
||||
"""
|
||||
display_name = "Strawberries Required Percentage"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
default = 80
|
||||
|
||||
|
||||
class LogicDifficulty(Choice):
|
||||
"""
|
||||
Whether the logic expects you to play the intended way, or to be able to use advanced tricks and skips
|
||||
"""
|
||||
display_name = "Logic Difficulty"
|
||||
option_standard = 0
|
||||
option_hard = 1
|
||||
default = 0
|
||||
|
||||
class MoveShuffle(Toggle):
|
||||
"""
|
||||
Whether the following base movement abilities are shuffled into the item pool:
|
||||
- Ground Dash
|
||||
- Air Dash
|
||||
- Skid Jump
|
||||
- Climb
|
||||
NOTE: Having Move Shuffle and Standard Logic Difficulty will guarantee that one of the four Move items will be immediately accessible
|
||||
WARNING: Combining Move Shuffle and Hard Logic Difficulty can require very difficult tricks
|
||||
"""
|
||||
display_name = "Move Shuffle"
|
||||
|
||||
|
||||
class Friendsanity(Toggle):
|
||||
"""
|
||||
Whether chatting with your friends grants location checks
|
||||
"""
|
||||
display_name = "Friendsanity"
|
||||
|
||||
class Signsanity(Toggle):
|
||||
"""
|
||||
Whether reading signs grants location checks
|
||||
"""
|
||||
display_name = "Signsanity"
|
||||
|
||||
class Carsanity(Toggle):
|
||||
"""
|
||||
Whether riding on cars grants location checks
|
||||
"""
|
||||
display_name = "Carsanity"
|
||||
|
||||
|
||||
class BadelineChaserSource(Choice):
|
||||
"""
|
||||
What type of action causes more Badeline Chasers to start spawning
|
||||
Locations: The number of locations you've checked contributes to Badeline Chasers
|
||||
Strawberries: The number of Strawberry items you've received contributes to Badeline Chasers
|
||||
"""
|
||||
display_name = "Badeline Chaser Source"
|
||||
option_locations = 0
|
||||
option_strawberries = 1
|
||||
default = 0
|
||||
|
||||
class BadelineChaserFrequency(Range):
|
||||
"""
|
||||
How many of the `Badeline Chaser Source` actions must occur to make each Badeline Chaser start spawning
|
||||
NOTE: Choosing `0` disables Badeline Chasers entirely
|
||||
WARNING: Turning on Badeline Chasers alongside Move Shuffle could result in extremely difficult situations
|
||||
"""
|
||||
display_name = "Badeline Chaser Frequency"
|
||||
range_start = 0
|
||||
range_end = 10
|
||||
default = 0
|
||||
|
||||
class BadelineChaserSpeed(Range):
|
||||
"""
|
||||
How many seconds behind you each Badeline Chaser will be
|
||||
"""
|
||||
display_name = "Badeline Chaser Speed"
|
||||
range_start = 2
|
||||
range_end = 10
|
||||
default = 3
|
||||
|
||||
|
||||
@dataclass
|
||||
class Celeste64Options(PerGameCommonOptions):
|
||||
death_link: DeathLink
|
||||
death_link_amnesty: DeathLinkAmnesty
|
||||
strawberries_required: StrawberriesRequired
|
||||
|
||||
total_strawberries: TotalStrawberries
|
||||
strawberries_required_percentage: StrawberriesRequiredPercentage
|
||||
|
||||
logic_difficulty: LogicDifficulty
|
||||
move_shuffle: MoveShuffle
|
||||
|
||||
friendsanity: Friendsanity
|
||||
signsanity: Signsanity
|
||||
carsanity: Carsanity
|
||||
|
||||
badeline_chaser_source: BadelineChaserSource
|
||||
badeline_chaser_frequency: BadelineChaserFrequency
|
||||
badeline_chaser_speed: BadelineChaserSpeed
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from typing import Dict, List
|
||||
|
||||
from BaseClasses import CollectionState
|
||||
from worlds.generic.Rules import set_rule
|
||||
|
||||
from . import Celeste64World
|
||||
@@ -5,100 +8,336 @@ from .Names import ItemName, LocationName
|
||||
|
||||
|
||||
def set_rules(world: Celeste64World):
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_4, world.player),
|
||||
lambda state: state.has_all({ItemName.traffic_block,
|
||||
ItemName.breakables}, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_5, world.player),
|
||||
lambda state: state.has(ItemName.breakables, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_6, world.player),
|
||||
lambda state: state.has(ItemName.dash_refill, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_8, world.player),
|
||||
lambda state: state.has(ItemName.traffic_block, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_9, world.player),
|
||||
lambda state: state.has(ItemName.dash_refill, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_11, world.player),
|
||||
lambda state: state.has(ItemName.dash_refill, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_12, world.player),
|
||||
lambda state: state.has_all({ItemName.dash_refill,
|
||||
ItemName.double_dash_refill}, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_13, world.player),
|
||||
lambda state: state.has_all({ItemName.dash_refill,
|
||||
ItemName.breakables}, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_14, world.player),
|
||||
lambda state: state.has_all({ItemName.dash_refill,
|
||||
ItemName.feather}, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_15, world.player),
|
||||
lambda state: state.has_all({ItemName.dash_refill,
|
||||
ItemName.feather}, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_16, world.player),
|
||||
lambda state: state.has_all({ItemName.dash_refill,
|
||||
ItemName.feather}, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_17, world.player),
|
||||
lambda state: state.has_all({ItemName.dash_refill,
|
||||
ItemName.double_dash_refill,
|
||||
ItemName.feather,
|
||||
ItemName.traffic_block}, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_18, world.player),
|
||||
lambda state: state.has(ItemName.double_dash_refill, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_19, world.player),
|
||||
lambda state: state.has_all({ItemName.double_dash_refill,
|
||||
ItemName.spring}, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_20, world.player),
|
||||
lambda state: state.has_all({ItemName.dash_refill,
|
||||
ItemName.feather,
|
||||
ItemName.breakables}, world.player))
|
||||
if world.options.logic_difficulty == "standard":
|
||||
if world.options.move_shuffle:
|
||||
world.active_logic_mapping = location_standard_moves_logic
|
||||
else:
|
||||
world.active_logic_mapping = location_standard_logic
|
||||
else:
|
||||
if world.options.move_shuffle:
|
||||
world.active_logic_mapping = location_hard_moves_logic
|
||||
else:
|
||||
world.active_logic_mapping = location_hard_logic
|
||||
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_21, world.player),
|
||||
lambda state: state.has_all({ItemName.cassette,
|
||||
ItemName.traffic_block,
|
||||
ItemName.breakables}, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_22, world.player),
|
||||
lambda state: state.has_all({ItemName.cassette,
|
||||
ItemName.dash_refill,
|
||||
ItemName.breakables}, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_23, world.player),
|
||||
lambda state: state.has_all({ItemName.cassette,
|
||||
ItemName.dash_refill,
|
||||
ItemName.coin}, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_24, world.player),
|
||||
lambda state: state.has_all({ItemName.cassette,
|
||||
ItemName.traffic_block,
|
||||
ItemName.dash_refill}, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_25, world.player),
|
||||
lambda state: state.has_all({ItemName.cassette,
|
||||
ItemName.dash_refill,
|
||||
ItemName.double_dash_refill}, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_26, world.player),
|
||||
lambda state: state.has_all({ItemName.cassette,
|
||||
ItemName.dash_refill}, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_27, world.player),
|
||||
lambda state: state.has_all({ItemName.cassette,
|
||||
ItemName.feather,
|
||||
ItemName.coin,
|
||||
ItemName.dash_refill}, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_28, world.player),
|
||||
lambda state: state.has_all({ItemName.cassette,
|
||||
ItemName.feather,
|
||||
ItemName.coin,
|
||||
ItemName.dash_refill}, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_29, world.player),
|
||||
lambda state: state.has_all({ItemName.cassette,
|
||||
ItemName.feather,
|
||||
ItemName.coin,
|
||||
ItemName.dash_refill}, world.player))
|
||||
set_rule(world.multiworld.get_location(LocationName.strawberry_30, world.player),
|
||||
lambda state: state.has_all({ItemName.cassette,
|
||||
ItemName.feather,
|
||||
ItemName.traffic_block,
|
||||
ItemName.spring,
|
||||
ItemName.breakables,
|
||||
ItemName.dash_refill,
|
||||
ItemName.double_dash_refill}, world.player))
|
||||
for location in world.multiworld.get_locations(world.player):
|
||||
set_rule(location, lambda state, location=location: location_rule(state, world, location.name))
|
||||
|
||||
if world.options.logic_difficulty == "standard":
|
||||
if world.options.move_shuffle:
|
||||
world.goal_logic_mapping = goal_standard_moves_logic
|
||||
else:
|
||||
world.goal_logic_mapping = goal_standard_logic
|
||||
else:
|
||||
if world.options.move_shuffle:
|
||||
world.goal_logic_mapping = goal_hard_moves_logic
|
||||
else:
|
||||
world.goal_logic_mapping = goal_hard_logic
|
||||
|
||||
# Completion condition.
|
||||
world.multiworld.completion_condition[world.player] = lambda state: (state.has(ItemName.strawberry,world.player,world.options.strawberries_required.value) and
|
||||
state.has_all({ItemName.feather,
|
||||
ItemName.traffic_block,
|
||||
ItemName.breakables,
|
||||
ItemName.dash_refill,
|
||||
ItemName.double_dash_refill}, world.player))
|
||||
world.multiworld.completion_condition[world.player] = lambda state: goal_rule(state, world)
|
||||
|
||||
|
||||
goal_standard_logic: List[List[str]] = [[ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.double_dash_refill]]
|
||||
goal_hard_logic: List[List[str]] = [[]]
|
||||
goal_standard_moves_logic: List[List[str]] = [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]]
|
||||
goal_hard_moves_logic: List[List[str]] = [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
|
||||
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
|
||||
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
|
||||
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]]
|
||||
|
||||
|
||||
location_standard_logic: Dict[str, List[List[str]]] = {
|
||||
LocationName.strawberry_4: [[ItemName.traffic_block, ItemName.breakables]],
|
||||
LocationName.strawberry_6: [[ItemName.dash_refill],
|
||||
[ItemName.traffic_block]],
|
||||
LocationName.strawberry_7: [[ItemName.dash_refill],
|
||||
[ItemName.traffic_block]],
|
||||
LocationName.strawberry_8: [[ItemName.traffic_block]],
|
||||
LocationName.strawberry_9: [[ItemName.dash_refill]],
|
||||
LocationName.strawberry_11: [[ItemName.dash_refill],
|
||||
[ItemName.traffic_block]],
|
||||
LocationName.strawberry_12: [[ItemName.dash_refill, ItemName.double_dash_refill],
|
||||
[ItemName.traffic_block, ItemName.double_dash_refill]],
|
||||
LocationName.strawberry_13: [[ItemName.dash_refill, ItemName.breakables],
|
||||
[ItemName.traffic_block, ItemName.breakables]],
|
||||
LocationName.strawberry_14: [[ItemName.dash_refill, ItemName.feather],
|
||||
[ItemName.traffic_block, ItemName.feather]],
|
||||
LocationName.strawberry_15: [[ItemName.dash_refill, ItemName.feather],
|
||||
[ItemName.traffic_block, ItemName.feather]],
|
||||
LocationName.strawberry_16: [[ItemName.dash_refill, ItemName.feather],
|
||||
[ItemName.traffic_block, ItemName.feather]],
|
||||
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block]],
|
||||
LocationName.strawberry_18: [[ItemName.dash_refill, ItemName.double_dash_refill],
|
||||
[ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill]],
|
||||
LocationName.strawberry_19: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.spring],
|
||||
[ItemName.traffic_block, ItemName.double_dash_refill, ItemName.feather, ItemName.spring]],
|
||||
LocationName.strawberry_20: [[ItemName.dash_refill, ItemName.feather, ItemName.breakables],
|
||||
[ItemName.traffic_block, ItemName.feather, ItemName.breakables]],
|
||||
|
||||
LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables]],
|
||||
LocationName.strawberry_22: [[ItemName.cassette, ItemName.dash_refill, ItemName.breakables]],
|
||||
LocationName.strawberry_23: [[ItemName.cassette, ItemName.dash_refill, ItemName.coin],
|
||||
[ItemName.cassette, ItemName.traffic_block, ItemName.coin]],
|
||||
LocationName.strawberry_24: [[ItemName.cassette, ItemName.dash_refill, ItemName.traffic_block]],
|
||||
LocationName.strawberry_25: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill],
|
||||
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill]],
|
||||
LocationName.strawberry_26: [[ItemName.cassette, ItemName.dash_refill],
|
||||
[ItemName.cassette, ItemName.traffic_block]],
|
||||
LocationName.strawberry_27: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin],
|
||||
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin]],
|
||||
LocationName.strawberry_28: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin],
|
||||
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin]],
|
||||
LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin]],
|
||||
LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.spring, ItemName.breakables]],
|
||||
|
||||
LocationName.theo_1: [[ItemName.traffic_block, ItemName.breakables]],
|
||||
LocationName.theo_2: [[ItemName.traffic_block, ItemName.breakables]],
|
||||
LocationName.theo_3: [[ItemName.traffic_block, ItemName.breakables]],
|
||||
LocationName.badeline_1: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]],
|
||||
LocationName.badeline_2: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]],
|
||||
LocationName.badeline_3: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]],
|
||||
|
||||
LocationName.sign_2: [[ItemName.breakables]],
|
||||
LocationName.sign_3: [[ItemName.dash_refill],
|
||||
[ItemName.traffic_block]],
|
||||
LocationName.sign_4: [[ItemName.dash_refill, ItemName.double_dash_refill],
|
||||
[ItemName.dash_refill, ItemName.feather],
|
||||
[ItemName.traffic_block, ItemName.feather]],
|
||||
LocationName.sign_5: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]],
|
||||
|
||||
LocationName.car_2: [[ItemName.breakables]],
|
||||
}
|
||||
|
||||
location_hard_logic: Dict[str, List[List[str]]] = {
|
||||
LocationName.strawberry_13: [[ItemName.breakables]],
|
||||
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.traffic_block]],
|
||||
LocationName.strawberry_20: [[ItemName.breakables]],
|
||||
|
||||
LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables]],
|
||||
LocationName.strawberry_22: [[ItemName.cassette]],
|
||||
LocationName.strawberry_23: [[ItemName.cassette, ItemName.coin]],
|
||||
LocationName.strawberry_24: [[ItemName.cassette]],
|
||||
LocationName.strawberry_25: [[ItemName.cassette, ItemName.double_dash_refill]],
|
||||
LocationName.strawberry_26: [[ItemName.cassette]],
|
||||
LocationName.strawberry_27: [[ItemName.cassette]],
|
||||
LocationName.strawberry_28: [[ItemName.cassette, ItemName.feather]],
|
||||
LocationName.strawberry_29: [[ItemName.cassette]],
|
||||
LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables]],
|
||||
|
||||
LocationName.sign_2: [[ItemName.breakables]],
|
||||
|
||||
LocationName.car_2: [[ItemName.breakables]],
|
||||
}
|
||||
|
||||
location_standard_moves_logic: Dict[str, List[List[str]]] = {
|
||||
LocationName.strawberry_1: [[ItemName.ground_dash],
|
||||
[ItemName.air_dash],
|
||||
[ItemName.skid_jump],
|
||||
[ItemName.climb]],
|
||||
LocationName.strawberry_2: [[ItemName.ground_dash],
|
||||
[ItemName.air_dash],
|
||||
[ItemName.skid_jump],
|
||||
[ItemName.climb]],
|
||||
LocationName.strawberry_3: [[ItemName.air_dash],
|
||||
[ItemName.skid_jump]],
|
||||
LocationName.strawberry_4: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
|
||||
LocationName.strawberry_5: [[ItemName.air_dash]],
|
||||
LocationName.strawberry_6: [[ItemName.dash_refill, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.ground_dash],
|
||||
[ItemName.traffic_block, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.skid_jump],
|
||||
[ItemName.traffic_block, ItemName.climb]],
|
||||
LocationName.strawberry_7: [[ItemName.dash_refill, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.ground_dash],
|
||||
[ItemName.traffic_block, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.skid_jump],
|
||||
[ItemName.traffic_block, ItemName.climb]],
|
||||
LocationName.strawberry_8: [[ItemName.traffic_block, ItemName.ground_dash],
|
||||
[ItemName.traffic_block, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.skid_jump],
|
||||
[ItemName.traffic_block, ItemName.climb]],
|
||||
LocationName.strawberry_9: [[ItemName.dash_refill, ItemName.air_dash]],
|
||||
LocationName.strawberry_10: [[ItemName.climb]],
|
||||
LocationName.strawberry_11: [[ItemName.dash_refill, ItemName.air_dash, ItemName.climb],
|
||||
[ItemName.traffic_block, ItemName.climb]],
|
||||
LocationName.strawberry_12: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.double_dash_refill, ItemName.air_dash]],
|
||||
LocationName.strawberry_13: [[ItemName.dash_refill, ItemName.breakables, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.breakables, ItemName.ground_dash],
|
||||
[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
|
||||
LocationName.strawberry_14: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.feather, ItemName.air_dash]],
|
||||
LocationName.strawberry_15: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash, ItemName.climb],
|
||||
[ItemName.traffic_block, ItemName.feather, ItemName.climb]],
|
||||
LocationName.strawberry_16: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.feather]],
|
||||
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.ground_dash],
|
||||
[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
|
||||
[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.skid_jump],
|
||||
[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.climb]],
|
||||
LocationName.strawberry_18: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb],
|
||||
[ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]],
|
||||
LocationName.strawberry_19: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.spring, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.double_dash_refill, ItemName.feather, ItemName.spring, ItemName.air_dash]],
|
||||
LocationName.strawberry_20: [[ItemName.dash_refill, ItemName.feather, ItemName.breakables, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.feather, ItemName.breakables, ItemName.air_dash]],
|
||||
|
||||
LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
|
||||
LocationName.strawberry_22: [[ItemName.cassette, ItemName.dash_refill, ItemName.breakables, ItemName.air_dash]],
|
||||
LocationName.strawberry_23: [[ItemName.cassette, ItemName.dash_refill, ItemName.coin, ItemName.air_dash, ItemName.climb],
|
||||
[ItemName.cassette, ItemName.traffic_block, ItemName.coin, ItemName.air_dash, ItemName.climb]],
|
||||
LocationName.strawberry_24: [[ItemName.cassette, ItemName.dash_refill, ItemName.traffic_block, ItemName.air_dash]],
|
||||
LocationName.strawberry_25: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb],
|
||||
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]],
|
||||
LocationName.strawberry_26: [[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash, ItemName.climb],
|
||||
[ItemName.cassette, ItemName.traffic_block, ItemName.air_dash, ItemName.climb]],
|
||||
LocationName.strawberry_27: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin, ItemName.air_dash],
|
||||
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin, ItemName.air_dash]],
|
||||
LocationName.strawberry_28: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb],
|
||||
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb]],
|
||||
LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.skid_jump]],
|
||||
LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.spring, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
|
||||
|
||||
LocationName.granny_1: [[ItemName.ground_dash],
|
||||
[ItemName.air_dash],
|
||||
[ItemName.skid_jump],
|
||||
[ItemName.climb]],
|
||||
LocationName.granny_2: [[ItemName.ground_dash],
|
||||
[ItemName.air_dash],
|
||||
[ItemName.skid_jump],
|
||||
[ItemName.climb]],
|
||||
LocationName.granny_3: [[ItemName.ground_dash],
|
||||
[ItemName.air_dash],
|
||||
[ItemName.skid_jump],
|
||||
[ItemName.climb]],
|
||||
LocationName.theo_1: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
|
||||
LocationName.theo_2: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
|
||||
LocationName.theo_3: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
|
||||
LocationName.badeline_1: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
|
||||
LocationName.badeline_2: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
|
||||
LocationName.badeline_3: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
|
||||
|
||||
LocationName.sign_1: [[ItemName.ground_dash],
|
||||
[ItemName.air_dash],
|
||||
[ItemName.skid_jump],
|
||||
[ItemName.climb]],
|
||||
LocationName.sign_2: [[ItemName.breakables, ItemName.ground_dash],
|
||||
[ItemName.breakables, ItemName.air_dash]],
|
||||
LocationName.sign_3: [[ItemName.dash_refill, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.ground_dash],
|
||||
[ItemName.traffic_block, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.skid_jump],
|
||||
[ItemName.traffic_block, ItemName.climb]],
|
||||
LocationName.sign_4: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash],
|
||||
[ItemName.dash_refill, ItemName.feather, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.feather, ItemName.ground_dash],
|
||||
[ItemName.traffic_block, ItemName.feather, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.feather, ItemName.skid_jump],
|
||||
[ItemName.traffic_block, ItemName.feather, ItemName.climb]],
|
||||
LocationName.sign_5: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
|
||||
|
||||
LocationName.car_2: [[ItemName.breakables, ItemName.ground_dash],
|
||||
[ItemName.breakables, ItemName.air_dash]],
|
||||
}
|
||||
|
||||
location_hard_moves_logic: Dict[str, List[List[str]]] = {
|
||||
LocationName.strawberry_3: [[ItemName.air_dash],
|
||||
[ItemName.skid_jump]],
|
||||
LocationName.strawberry_5: [[ItemName.ground_dash],
|
||||
[ItemName.air_dash]],
|
||||
LocationName.strawberry_8: [[ItemName.traffic_block],
|
||||
[ItemName.ground_dash, ItemName.air_dash]],
|
||||
LocationName.strawberry_10: [[ItemName.air_dash],
|
||||
[ItemName.climb]],
|
||||
LocationName.strawberry_11: [[ItemName.ground_dash],
|
||||
[ItemName.air_dash],
|
||||
[ItemName.skid_jump]],
|
||||
LocationName.strawberry_12: [[ItemName.feather],
|
||||
[ItemName.ground_dash],
|
||||
[ItemName.air_dash]],
|
||||
LocationName.strawberry_13: [[ItemName.breakables, ItemName.ground_dash],
|
||||
[ItemName.breakables, ItemName.air_dash]],
|
||||
LocationName.strawberry_14: [[ItemName.feather, ItemName.air_dash],
|
||||
[ItemName.air_dash, ItemName.climb]],
|
||||
LocationName.strawberry_15: [[ItemName.feather],
|
||||
[ItemName.ground_dash, ItemName.air_dash]],
|
||||
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.traffic_block]],
|
||||
LocationName.strawberry_18: [[ItemName.air_dash, ItemName.climb],
|
||||
[ItemName.double_dash_refill, ItemName.air_dash]],
|
||||
LocationName.strawberry_19: [[ItemName.air_dash, ItemName.skid_jump],
|
||||
[ItemName.double_dash_refill, ItemName.spring, ItemName.air_dash],
|
||||
[ItemName.spring, ItemName.ground_dash, ItemName.air_dash]],
|
||||
LocationName.strawberry_20: [[ItemName.breakables, ItemName.air_dash]],
|
||||
|
||||
LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
|
||||
LocationName.strawberry_22: [[ItemName.cassette, ItemName.ground_dash, ItemName.air_dash],
|
||||
[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash]],
|
||||
LocationName.strawberry_23: [[ItemName.cassette, ItemName.coin, ItemName.air_dash]],
|
||||
LocationName.strawberry_24: [[ItemName.cassette, ItemName.ground_dash, ItemName.air_dash],
|
||||
[ItemName.cassette, ItemName.traffic_block, ItemName.air_dash]],
|
||||
LocationName.strawberry_25: [[ItemName.cassette, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]],
|
||||
LocationName.strawberry_26: [[ItemName.cassette, ItemName.ground_dash],
|
||||
[ItemName.cassette, ItemName.air_dash]],
|
||||
LocationName.strawberry_27: [[ItemName.cassette, ItemName.air_dash, ItemName.skid_jump],
|
||||
[ItemName.cassette, ItemName.traffic_block, ItemName.coin, ItemName.air_dash],
|
||||
[ItemName.cassette, ItemName.coin, ItemName.ground_dash],
|
||||
[ItemName.cassette, ItemName.feather, ItemName.coin, ItemName.air_dash]],
|
||||
LocationName.strawberry_28: [[ItemName.cassette, ItemName.feather, ItemName.air_dash],
|
||||
[ItemName.cassette, ItemName.feather, ItemName.climb]],
|
||||
LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash, ItemName.skid_jump],
|
||||
[ItemName.cassette, ItemName.ground_dash, ItemName.air_dash]],
|
||||
LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.ground_dash, ItemName.air_dash, ItemName.climb, ItemName.skid_jump],
|
||||
[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.feather, ItemName.air_dash, ItemName.climb, ItemName.skid_jump],
|
||||
[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.spring, ItemName.ground_dash, ItemName.air_dash, ItemName.climb],
|
||||
[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.spring, ItemName.feather, ItemName.air_dash, ItemName.climb]],
|
||||
|
||||
LocationName.badeline_1: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
|
||||
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
|
||||
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
|
||||
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]],
|
||||
LocationName.badeline_2: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
|
||||
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
|
||||
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
|
||||
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]],
|
||||
LocationName.badeline_3: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
|
||||
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
|
||||
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
|
||||
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]],
|
||||
|
||||
LocationName.sign_2: [[ItemName.breakables, ItemName.ground_dash],
|
||||
[ItemName.breakables, ItemName.air_dash]],
|
||||
LocationName.sign_5: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
|
||||
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
|
||||
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
|
||||
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]],
|
||||
|
||||
LocationName.car_2: [[ItemName.breakables, ItemName.ground_dash],
|
||||
[ItemName.breakables, ItemName.air_dash]],
|
||||
}
|
||||
|
||||
|
||||
def location_rule(state: CollectionState, world: Celeste64World, loc: str) -> bool:
|
||||
|
||||
if loc not in world.active_logic_mapping:
|
||||
return True
|
||||
|
||||
for possible_access in world.active_logic_mapping[loc]:
|
||||
if state.has_all(possible_access, world.player):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def goal_rule(state: CollectionState, world: Celeste64World) -> bool:
|
||||
if not state.has(ItemName.strawberry, world.player, world.strawberries_required):
|
||||
return False
|
||||
|
||||
for possible_access in world.goal_logic_mapping:
|
||||
if state.has_all(possible_access, world.player):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from typing import List
|
||||
from copy import deepcopy
|
||||
from typing import Dict, List
|
||||
|
||||
from BaseClasses import ItemClassification, Region, Tutorial
|
||||
from BaseClasses import ItemClassification, Location, Region, Tutorial
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from .Items import Celeste64Item, item_data_table, item_table
|
||||
from .Locations import Celeste64Location, location_data_table, location_table
|
||||
from .Names import ItemName
|
||||
from .Items import Celeste64Item, unlockable_item_data_table, move_item_data_table, item_data_table, item_table
|
||||
from .Locations import Celeste64Location, strawberry_location_data_table, friend_location_data_table,\
|
||||
sign_location_data_table, car_location_data_table, location_table
|
||||
from .Names import ItemName, LocationName
|
||||
from .Options import Celeste64Options
|
||||
|
||||
|
||||
@@ -27,6 +29,7 @@ class Celeste64World(World):
|
||||
"""Relive the magic of Celeste Mountain alongside Madeline in this small, heartfelt 3D platformer.
|
||||
Created in a week(ish) by the Celeste team to celebrate the game’s sixth anniversary 🍓✨"""
|
||||
|
||||
# Class Data
|
||||
game = "Celeste 64"
|
||||
web = Celeste64WebWorld()
|
||||
options_dataclass = Celeste64Options
|
||||
@@ -34,13 +37,18 @@ class Celeste64World(World):
|
||||
location_name_to_id = location_table
|
||||
item_name_to_id = item_table
|
||||
|
||||
# Instance Data
|
||||
strawberries_required: int
|
||||
active_logic_mapping: Dict[str, List[List[str]]]
|
||||
goal_logic_mapping: Dict[str, List[List[str]]]
|
||||
|
||||
|
||||
def create_item(self, name: str) -> Celeste64Item:
|
||||
# Only make required amount of strawberries be Progression
|
||||
if getattr(self, "options", None) and name == ItemName.strawberry:
|
||||
if getattr(self, "strawberries_required", None) and name == ItemName.strawberry:
|
||||
classification: ItemClassification = ItemClassification.filler
|
||||
self.prog_strawberries = getattr(self, "prog_strawberries", 0)
|
||||
if self.prog_strawberries < self.options.strawberries_required.value:
|
||||
if self.prog_strawberries < self.strawberries_required:
|
||||
classification = ItemClassification.progression_skip_balancing
|
||||
self.prog_strawberries += 1
|
||||
|
||||
@@ -51,9 +59,48 @@ class Celeste64World(World):
|
||||
def create_items(self) -> None:
|
||||
item_pool: List[Celeste64Item] = []
|
||||
|
||||
item_pool += [self.create_item(name) for name in item_data_table.keys()]
|
||||
location_count: int = 30
|
||||
|
||||
item_pool += [self.create_item(ItemName.strawberry) for _ in range(21)]
|
||||
if self.options.friendsanity:
|
||||
location_count += 9
|
||||
|
||||
if self.options.signsanity:
|
||||
location_count += 5
|
||||
|
||||
if self.options.carsanity:
|
||||
location_count += 2
|
||||
|
||||
item_pool += [self.create_item(name)
|
||||
for name in unlockable_item_data_table.keys()
|
||||
if name not in self.options.start_inventory]
|
||||
|
||||
if self.options.move_shuffle:
|
||||
move_items_for_itempool: List[str] = deepcopy(list(move_item_data_table.keys()))
|
||||
|
||||
if self.options.logic_difficulty == "standard":
|
||||
# If the start_inventory already includes a move, don't worry about giving it one
|
||||
if not [move for move in move_items_for_itempool if move in self.options.start_inventory]:
|
||||
chosen_start_move = self.random.choice(move_items_for_itempool)
|
||||
move_items_for_itempool.remove(chosen_start_move)
|
||||
|
||||
if self.options.carsanity:
|
||||
intro_car_loc: Location = self.multiworld.get_location(LocationName.car_1, self.player)
|
||||
intro_car_loc.place_locked_item(self.create_item(chosen_start_move))
|
||||
location_count -= 1
|
||||
else:
|
||||
self.multiworld.push_precollected(self.create_item(chosen_start_move))
|
||||
|
||||
item_pool += [self.create_item(name)
|
||||
for name in move_items_for_itempool
|
||||
if name not in self.options.start_inventory]
|
||||
|
||||
real_total_strawberries: int = min(self.options.total_strawberries.value, location_count - len(item_pool))
|
||||
self.strawberries_required = int(real_total_strawberries * (self.options.strawberries_required_percentage / 100))
|
||||
|
||||
item_pool += [self.create_item(ItemName.strawberry) for _ in range(real_total_strawberries)]
|
||||
|
||||
filler_item_count: int = location_count - len(item_pool)
|
||||
item_pool += [self.create_item(ItemName.raspberry) for _ in range(filler_item_count)]
|
||||
|
||||
self.multiworld.itempool += item_pool
|
||||
|
||||
@@ -69,14 +116,33 @@ class Celeste64World(World):
|
||||
for region_name, region_data in region_data_table.items():
|
||||
region = self.multiworld.get_region(region_name, self.player)
|
||||
region.add_locations({
|
||||
location_name: location_data.address for location_name, location_data in location_data_table.items()
|
||||
location_name: location_data.address for location_name, location_data in strawberry_location_data_table.items()
|
||||
if location_data.region == region_name
|
||||
}, Celeste64Location)
|
||||
|
||||
if self.options.friendsanity:
|
||||
region.add_locations({
|
||||
location_name: location_data.address for location_name, location_data in friend_location_data_table.items()
|
||||
if location_data.region == region_name
|
||||
}, Celeste64Location)
|
||||
|
||||
if self.options.signsanity:
|
||||
region.add_locations({
|
||||
location_name: location_data.address for location_name, location_data in sign_location_data_table.items()
|
||||
if location_data.region == region_name
|
||||
}, Celeste64Location)
|
||||
|
||||
if self.options.carsanity:
|
||||
region.add_locations({
|
||||
location_name: location_data.address for location_name, location_data in car_location_data_table.items()
|
||||
if location_data.region == region_name
|
||||
}, Celeste64Location)
|
||||
|
||||
region.add_exits(region_data_table[region_name].connecting_regions)
|
||||
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return ItemName.strawberry
|
||||
return ItemName.raspberry
|
||||
|
||||
|
||||
def set_rules(self) -> None:
|
||||
@@ -88,5 +154,12 @@ class Celeste64World(World):
|
||||
return {
|
||||
"death_link": self.options.death_link.value,
|
||||
"death_link_amnesty": self.options.death_link_amnesty.value,
|
||||
"strawberries_required": self.options.strawberries_required.value
|
||||
"strawberries_required": self.strawberries_required,
|
||||
"move_shuffle": self.options.move_shuffle.value,
|
||||
"friendsanity": self.options.friendsanity.value,
|
||||
"signsanity": self.options.signsanity.value,
|
||||
"carsanity": self.options.carsanity.value,
|
||||
"badeline_chaser_source": self.options.badeline_chaser_source.value,
|
||||
"badeline_chaser_frequency": self.options.badeline_chaser_frequency.value,
|
||||
"badeline_chaser_speed": self.options.badeline_chaser_speed.value,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
## Required Software
|
||||
- Archipelago Build of Celeste 64 from: [Celeste 64 Archipelago Releases Page](https://github.com/PoryGoneDev/Celeste64/releases/)
|
||||
|
||||
## Optional Software
|
||||
- Celeste 64 Tracker
|
||||
- PopTracker from: [PopTracker Releases Page](https://github.com/black-sliver/PopTracker/releases/)
|
||||
- Celeste 64 Archipelago PopTracker pack from: [Celeste 64 AP Tracker Releases Page](https://github.com/PoryGone/Celeste-64-AP-Tracker/releases/)
|
||||
|
||||
## Installation Procedures (Windows)
|
||||
|
||||
1. Download the above release and extract it.
|
||||
|
||||
@@ -14,7 +14,7 @@ from .stages import get_locations_from_stage, get_normal_stage_exits, vanilla_st
|
||||
from .regions import get_region_info
|
||||
from .rules import CV64Rules
|
||||
from .data import iname, rname, ename
|
||||
from ..AutoWorld import WebWorld, World
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from .aesthetics import randomize_lighting, shuffle_sub_weapons, rom_empty_breakables_flags, rom_sub_weapon_flags, \
|
||||
randomize_music, get_start_inventory_data, get_location_data, randomize_shop_prices, get_loading_zone_bytes, \
|
||||
get_countdown_numbers
|
||||
|
||||
@@ -521,7 +521,7 @@ rcon_port = args.rcon_port
|
||||
rcon_password = args.rcon_password if args.rcon_password else ''.join(
|
||||
random.choice(string.ascii_letters) for x in range(32))
|
||||
factorio_server_logger = logging.getLogger("FactorioServer")
|
||||
options = Utils.get_options()
|
||||
options = Utils.get_settings()
|
||||
executable = options["factorio_options"]["executable"]
|
||||
server_settings = args.server_settings if args.server_settings \
|
||||
else options["factorio_options"].get("server_settings", None)
|
||||
|
||||
@@ -199,8 +199,14 @@ class HKWorld(World):
|
||||
self.multiworld.regions.append(menu_region)
|
||||
# wp_exclusions = self.white_palace_exclusions()
|
||||
|
||||
# check for any goal that godhome events are relevant to
|
||||
all_event_names = event_names.copy()
|
||||
if self.multiworld.Goal[self.player] in [Goal.option_godhome, Goal.option_godhome_flower]:
|
||||
from .GodhomeData import godhome_event_names
|
||||
all_event_names.update(set(godhome_event_names))
|
||||
|
||||
# Link regions
|
||||
for event_name in event_names:
|
||||
for event_name in all_event_names:
|
||||
#if event_name in wp_exclusions:
|
||||
# continue
|
||||
loc = HKLocation(self.player, event_name, None, menu_region)
|
||||
@@ -307,12 +313,6 @@ class HKWorld(World):
|
||||
randomized = True
|
||||
_add("Elevator_Pass", "Elevator_Pass", randomized)
|
||||
|
||||
# check for any goal that godhome events are relevant to
|
||||
if self.multiworld.Goal[self.player] in [Goal.option_godhome, Goal.option_godhome_flower]:
|
||||
from .GodhomeData import godhome_event_names
|
||||
for item_name in godhome_event_names:
|
||||
_add(item_name, item_name, False)
|
||||
|
||||
for shop, locations in self.created_multi_locations.items():
|
||||
for _ in range(len(locations), getattr(self.multiworld, shop_to_option[shop])[self.player].value):
|
||||
loc = self.create_location(shop)
|
||||
|
||||
@@ -4,6 +4,7 @@ Archipelago init file for Lingo
|
||||
from logging import warning
|
||||
|
||||
from BaseClasses import Item, ItemClassification, Tutorial
|
||||
from Options import OptionError
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from .datatypes import Room, RoomEntrance
|
||||
from .items import ALL_ITEM_TABLE, ITEMS_BY_GROUP, TRAP_ITEMS, LingoItem
|
||||
@@ -52,13 +53,14 @@ class LingoWorld(World):
|
||||
player_logic: LingoPlayerLogic
|
||||
|
||||
def generate_early(self):
|
||||
if not (self.options.shuffle_doors or self.options.shuffle_colors):
|
||||
if not (self.options.shuffle_doors or self.options.shuffle_colors or self.options.shuffle_sunwarps):
|
||||
if self.multiworld.players == 1:
|
||||
warning(f"{self.multiworld.get_player_name(self.player)}'s Lingo world doesn't have any progression"
|
||||
f" items. Please turn on Door Shuffle or Color Shuffle if that doesn't seem right.")
|
||||
f" items. Please turn on Door Shuffle, Color Shuffle, or Sunwarp Shuffle if that doesn't seem"
|
||||
f" right.")
|
||||
else:
|
||||
raise Exception(f"{self.multiworld.get_player_name(self.player)}'s Lingo world doesn't have any"
|
||||
f" progression items. Please turn on Door Shuffle or Color Shuffle.")
|
||||
raise OptionError(f"{self.multiworld.get_player_name(self.player)}'s Lingo world doesn't have any"
|
||||
f" progression items. Please turn on Door Shuffle, Color Shuffle or Sunwarp Shuffle.")
|
||||
|
||||
self.player_logic = LingoPlayerLogic(self)
|
||||
|
||||
@@ -94,7 +96,7 @@ class LingoWorld(World):
|
||||
total_weight = sum(self.options.trap_weights.values())
|
||||
|
||||
if total_weight == 0:
|
||||
raise Exception("Sum of trap weights must be at least one.")
|
||||
raise OptionError("Sum of trap weights must be at least one.")
|
||||
|
||||
trap_counts = {name: int(weight * traps / total_weight)
|
||||
for name, weight in self.options.trap_weights.items()}
|
||||
|
||||
@@ -10,6 +10,7 @@ class LocationClassification(Flag):
|
||||
normal = auto()
|
||||
reduced = auto()
|
||||
insanity = auto()
|
||||
small_sphere_one = auto()
|
||||
|
||||
|
||||
class LocationData(NamedTuple):
|
||||
@@ -47,6 +48,9 @@ def load_location_data():
|
||||
if not panel.exclude_reduce:
|
||||
classification |= LocationClassification.reduced
|
||||
|
||||
if room_name == "Starting Room":
|
||||
classification |= LocationClassification.small_sphere_one
|
||||
|
||||
ALL_LOCATION_TABLE[location_name] = \
|
||||
LocationData(get_panel_location_id(room_name, panel_name), room_name,
|
||||
[RoomAndPanel(None, panel_name)], classification)
|
||||
|
||||
@@ -3,7 +3,7 @@ from dataclasses import dataclass
|
||||
from schema import And, Schema
|
||||
|
||||
from Options import Toggle, Choice, DefaultOnToggle, Range, PerGameCommonOptions, StartInventoryPool, OptionDict
|
||||
from worlds.lingo.items import TRAP_ITEMS
|
||||
from .items import TRAP_ITEMS
|
||||
|
||||
|
||||
class ShuffleDoors(Choice):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from enum import Enum
|
||||
from typing import Dict, List, NamedTuple, Optional, Set, Tuple, TYPE_CHECKING
|
||||
|
||||
from Options import OptionError
|
||||
from .datatypes import Door, DoorType, RoomAndDoor, RoomAndPanel
|
||||
from .items import ALL_ITEM_TABLE, ItemType
|
||||
from .locations import ALL_LOCATION_TABLE, LocationClassification
|
||||
@@ -149,8 +150,8 @@ class LingoPlayerLogic:
|
||||
early_color_hallways = world.options.early_color_hallways
|
||||
|
||||
if location_checks == LocationChecks.option_reduced and door_shuffle != ShuffleDoors.option_none:
|
||||
raise Exception("You cannot have reduced location checks when door shuffle is on, because there would not "
|
||||
"be enough locations for all of the door items.")
|
||||
raise OptionError("You cannot have reduced location checks when door shuffle is on, because there would not"
|
||||
" be enough locations for all of the door items.")
|
||||
|
||||
# Create door items, where needed.
|
||||
door_groups: Set[str] = set()
|
||||
@@ -219,7 +220,7 @@ class LingoPlayerLogic:
|
||||
self.event_loc_to_item[self.level_2_location] = "Victory"
|
||||
|
||||
if world.options.level_2_requirement == 1:
|
||||
raise Exception("The Level 2 requirement must be at least 2 when LEVEL 2 is the victory condition.")
|
||||
raise OptionError("The Level 2 requirement must be at least 2 when LEVEL 2 is the victory condition.")
|
||||
elif victory_condition == VictoryCondition.option_pilgrimage:
|
||||
self.victory_condition = "Pilgrim Antechamber - PILGRIM"
|
||||
self.add_location("Pilgrim Antechamber", "PILGRIM (Solved)", None,
|
||||
@@ -236,20 +237,23 @@ class LingoPlayerLogic:
|
||||
elif location_checks == LocationChecks.option_insanity:
|
||||
location_classification = LocationClassification.insanity
|
||||
|
||||
if door_shuffle != ShuffleDoors.option_none and not early_color_hallways:
|
||||
location_classification |= LocationClassification.small_sphere_one
|
||||
|
||||
for location_name, location_data in ALL_LOCATION_TABLE.items():
|
||||
if location_name != self.victory_condition:
|
||||
if location_classification not in location_data.classification:
|
||||
if not (location_classification & location_data.classification):
|
||||
continue
|
||||
|
||||
self.add_location(location_data.room, location_name, location_data.code, location_data.panels, world)
|
||||
self.real_locations.append(location_name)
|
||||
|
||||
if world.options.enable_pilgrimage and world.options.sunwarp_access == SunwarpAccess.option_disabled:
|
||||
raise Exception("Sunwarps cannot be disabled when pilgrimage is enabled.")
|
||||
raise OptionError("Sunwarps cannot be disabled when pilgrimage is enabled.")
|
||||
|
||||
if world.options.shuffle_sunwarps:
|
||||
if world.options.sunwarp_access == SunwarpAccess.option_disabled:
|
||||
raise Exception("Sunwarps cannot be shuffled if they are disabled.")
|
||||
raise OptionError("Sunwarps cannot be shuffled if they are disabled.")
|
||||
|
||||
self.sunwarp_mapping = list(range(0, 12))
|
||||
world.random.shuffle(self.sunwarp_mapping)
|
||||
@@ -275,7 +279,7 @@ class LingoPlayerLogic:
|
||||
"iterations. This is very unlikely to happen on its own, and probably indicates some "
|
||||
"kind of logic error.")
|
||||
|
||||
if door_shuffle != ShuffleDoors.option_none and location_classification != LocationClassification.insanity \
|
||||
if door_shuffle != ShuffleDoors.option_none and location_checks != LocationChecks.option_insanity \
|
||||
and not early_color_hallways and world.multiworld.players > 1:
|
||||
# Under the combination of door shuffle, normal location checks, and no early color hallways, sphere 1 is
|
||||
# only three checks. In a multiplayer situation, this can be frustrating for the player because they are
|
||||
|
||||
@@ -78,13 +78,16 @@ def get_progressive_item_id(name: str):
|
||||
def load_static_data_from_file():
|
||||
global PAINTING_ENTRANCES, PAINTING_EXITS
|
||||
|
||||
from . import datatypes
|
||||
from Utils import safe_builtins
|
||||
|
||||
class RenameUnpickler(pickle.Unpickler):
|
||||
def find_class(self, module, name):
|
||||
renamed_module = module
|
||||
if module == "datatypes":
|
||||
renamed_module = "worlds.lingo.datatypes"
|
||||
|
||||
return super(RenameUnpickler, self).find_class(renamed_module, name)
|
||||
if module in ("worlds.lingo.datatypes", "datatypes"):
|
||||
return getattr(datatypes, name)
|
||||
elif module == "builtins" and name in safe_builtins:
|
||||
return getattr(safe_builtins, name)
|
||||
raise pickle.UnpicklingError(f"global '{module}.{name}' is forbidden")
|
||||
|
||||
file = pkgutil.get_data(__name__, os.path.join("data", "generated.dat"))
|
||||
pickdata = RenameUnpickler(BytesIO(file)).load()
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from worlds.lingo.static_logic import HASHES
|
||||
from worlds.lingo.utils.pickle_static_data import hash_file
|
||||
from ..static_logic import HASHES
|
||||
from ..utils.pickle_static_data import hash_file
|
||||
|
||||
|
||||
class TestDatafile(unittest.TestCase):
|
||||
|
||||
@@ -2,8 +2,8 @@ from copy import deepcopy
|
||||
from typing import List, TYPE_CHECKING
|
||||
|
||||
from BaseClasses import CollectionState, PlandoOptions
|
||||
from worlds.generic import PlandoConnection
|
||||
from .options import ShufflePortals
|
||||
from ..generic import PlandoConnection
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import MessengerWorld
|
||||
|
||||
297
worlds/mlss/Client.py
Normal file
297
worlds/mlss/Client.py
Normal file
@@ -0,0 +1,297 @@
|
||||
from typing import TYPE_CHECKING, Optional, Set, List, Dict
|
||||
import struct
|
||||
|
||||
from NetUtils import ClientStatus
|
||||
from .Locations import roomCount, nonBlock, beanstones, roomException, shop, badge, pants, eReward
|
||||
from .Items import items_by_id
|
||||
|
||||
import asyncio
|
||||
|
||||
import worlds._bizhawk as bizhawk
|
||||
from worlds._bizhawk.client import BizHawkClient
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from worlds._bizhawk.context import BizHawkClientContext
|
||||
|
||||
ROOM_ARRAY_POINTER = 0x51FA00
|
||||
|
||||
|
||||
class MLSSClient(BizHawkClient):
|
||||
game = "Mario & Luigi Superstar Saga"
|
||||
system = "GBA"
|
||||
patch_suffix = ".apmlss"
|
||||
local_checked_locations: Set[int]
|
||||
goal_flag: int
|
||||
rom_slot_name: Optional[str]
|
||||
eUsed: List[int]
|
||||
room: int
|
||||
local_events: List[int]
|
||||
player_name: Optional[str]
|
||||
checked_flags: Dict[int, list] = {}
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.local_checked_locations = set()
|
||||
self.local_set_events = {}
|
||||
self.local_found_key_items = {}
|
||||
self.rom_slot_name = None
|
||||
self.seed_verify = False
|
||||
self.eUsed = []
|
||||
self.room = 0
|
||||
self.local_events = []
|
||||
|
||||
async def validate_rom(self, ctx: "BizHawkClientContext") -> bool:
|
||||
from CommonClient import logger
|
||||
|
||||
try:
|
||||
# Check ROM name/patch version
|
||||
rom_name_bytes = await bizhawk.read(ctx.bizhawk_ctx, [(0xA0, 14, "ROM")])
|
||||
rom_name = bytes([byte for byte in rom_name_bytes[0] if byte != 0]).decode("UTF-8")
|
||||
if not rom_name.startswith("MARIO&LUIGIU"):
|
||||
return False
|
||||
if rom_name == "MARIO&LUIGIUA8":
|
||||
logger.info(
|
||||
"ERROR: You appear to be running an unpatched version of Mario & Luigi Superstar Saga. "
|
||||
"You need to generate a patch file and use it to create a patched ROM."
|
||||
)
|
||||
return False
|
||||
if rom_name != "MARIO&LUIGIUAP":
|
||||
logger.info(
|
||||
"ERROR: The patch file used to create this ROM is not compatible with "
|
||||
"this client. Double check your client version against the version being "
|
||||
"used by the generator."
|
||||
)
|
||||
return False
|
||||
except UnicodeDecodeError:
|
||||
return False
|
||||
except bizhawk.RequestFailedError:
|
||||
return False # Should verify on the next pass
|
||||
|
||||
ctx.game = self.game
|
||||
ctx.items_handling = 0b101
|
||||
ctx.want_slot_data = True
|
||||
ctx.watcher_timeout = 0.125
|
||||
self.rom_slot_name = rom_name
|
||||
self.seed_verify = False
|
||||
name_bytes = (await bizhawk.read(ctx.bizhawk_ctx, [(0xDF0000, 16, "ROM")]))[0]
|
||||
name = bytes([byte for byte in name_bytes if byte != 0]).decode("UTF-8")
|
||||
self.player_name = name
|
||||
|
||||
for i in range(59):
|
||||
self.checked_flags[i] = []
|
||||
|
||||
return True
|
||||
|
||||
async def set_auth(self, ctx: "BizHawkClientContext") -> None:
|
||||
ctx.auth = self.player_name
|
||||
|
||||
def on_package(self, ctx, cmd, args) -> None:
|
||||
if cmd == "RoomInfo":
|
||||
ctx.seed_name = args["seed_name"]
|
||||
|
||||
async def game_watcher(self, ctx: "BizHawkClientContext") -> None:
|
||||
from CommonClient import logger
|
||||
|
||||
try:
|
||||
if ctx.seed_name is None:
|
||||
return
|
||||
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:
|
||||
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,"
|
||||
"and that you have opened the correct ROM."
|
||||
)
|
||||
raise bizhawk.ConnectorError("Loaded ROM is for Incorrect lobby.")
|
||||
self.seed_verify = True
|
||||
|
||||
read_state = await bizhawk.read(
|
||||
ctx.bizhawk_ctx,
|
||||
[
|
||||
(0x4564, 59, "EWRAM"),
|
||||
(0x2330, 2, "IWRAM"),
|
||||
(0x3FE0, 1, "IWRAM"),
|
||||
(0x304A, 1, "EWRAM"),
|
||||
(0x304B, 1, "EWRAM"),
|
||||
(0x304C, 4, "EWRAM"),
|
||||
(0x3060, 6, "EWRAM"),
|
||||
(0x4808, 2, "EWRAM"),
|
||||
(0x4407, 1, "EWRAM"),
|
||||
(0x2339, 1, "IWRAM"),
|
||||
]
|
||||
)
|
||||
flags = read_state[0]
|
||||
current_room = int.from_bytes(read_state[1], "little")
|
||||
shop_init = read_state[2][0]
|
||||
shop_scroll = read_state[3][0] & 0x1F
|
||||
is_buy = read_state[4][0] != 0
|
||||
shop_address = (struct.unpack("<I", read_state[5])[0]) & 0xFFFFFF
|
||||
logo = bytes([byte for byte in read_state[6] if byte < 0x70]).decode("UTF-8")
|
||||
received_index = (read_state[7][0] << 8) + read_state[7][1]
|
||||
cackletta = read_state[8][0] & 0x40
|
||||
shopping = read_state[9][0] & 0xF
|
||||
|
||||
if logo != "MLSSAP":
|
||||
return
|
||||
|
||||
locs_to_send = set()
|
||||
|
||||
# Checking shop purchases
|
||||
if is_buy:
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [(0x304A, [0x0, 0x0], "EWRAM")])
|
||||
if shop_address != 0x3C0618 and shop_address != 0x3C0684:
|
||||
location = shop[shop_address][shop_scroll]
|
||||
else:
|
||||
if shop_init & 0x1 != 0:
|
||||
location = badge[shop_address][shop_scroll]
|
||||
else:
|
||||
location = pants[shop_address][shop_scroll]
|
||||
if location in ctx.server_locations:
|
||||
locs_to_send.add(location)
|
||||
|
||||
# Loop for receiving items. Item is written as an ID into 0x3057.
|
||||
# ASM reads the ID in a loop and give the player the item before resetting the RAM address to 0x0.
|
||||
# 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:
|
||||
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
|
||||
if shopping & 0x3 == 0x3:
|
||||
if locs_to_send != self.local_checked_locations:
|
||||
self.local_checked_locations = locs_to_send
|
||||
|
||||
if locs_to_send is not None:
|
||||
await ctx.send_msgs([{"cmd": "LocationChecks", "locations": list(locs_to_send)}])
|
||||
return
|
||||
|
||||
# Checking flags that aren't digspots or blocks
|
||||
for item in nonBlock:
|
||||
address, mask, location = item
|
||||
if location in self.local_checked_locations:
|
||||
continue
|
||||
flag_bytes = await bizhawk.read(ctx.bizhawk_ctx, [(address, 1, "EWRAM"), (0x3060, 6, "EWRAM")])
|
||||
flag_byte = flag_bytes[0][0]
|
||||
backup_logo = bytes([byte for byte in flag_bytes[1] if byte < 0x70]).decode("UTF-8")
|
||||
if backup_logo != "MLSSAP":
|
||||
return
|
||||
if flag_byte & mask != 0:
|
||||
if location >= 0xDA0000 and location not in self.local_events:
|
||||
self.local_events += [location]
|
||||
await ctx.send_msgs(
|
||||
[
|
||||
{
|
||||
"cmd": "Set",
|
||||
"key": f"mlss_flag_{ctx.team}_{ctx.slot}",
|
||||
"default": 0,
|
||||
"want_reply": False,
|
||||
"operations": [{"operation": "or", "value": 1 << (location - 0xDA0000)}],
|
||||
}
|
||||
]
|
||||
)
|
||||
continue
|
||||
if location in roomException:
|
||||
if current_room not in roomException[location]:
|
||||
exception = True
|
||||
else:
|
||||
exception = False
|
||||
else:
|
||||
exception = True
|
||||
|
||||
if location in eReward:
|
||||
if location not in self.eUsed:
|
||||
self.eUsed += [location]
|
||||
location = eReward[len(self.eUsed) - 1]
|
||||
else:
|
||||
continue
|
||||
if (location in ctx.server_locations) and exception:
|
||||
locs_to_send.add(location)
|
||||
|
||||
# Check for set location flags.
|
||||
for byte_i, byte in enumerate(bytearray(flags)):
|
||||
for j in range(8):
|
||||
if j in self.checked_flags[byte_i]:
|
||||
continue
|
||||
and_value = 1 << j
|
||||
if byte & and_value != 0:
|
||||
flag_id = byte_i * 8 + (j + 1)
|
||||
room, item = find_key(roomCount, flag_id)
|
||||
pointer_arr = await bizhawk.read(
|
||||
ctx.bizhawk_ctx, [(ROOM_ARRAY_POINTER + ((room - 1) * 4), 4, "ROM")]
|
||||
)
|
||||
pointer = struct.unpack("<I", pointer_arr[0])[0]
|
||||
pointer = pointer & 0xFFFFFF
|
||||
offset = await bizhawk.read(ctx.bizhawk_ctx, [(pointer, 1, "ROM")])
|
||||
offset = offset[0][0]
|
||||
if offset != 0:
|
||||
offset = 2
|
||||
pointer += (item * 8) + 1 + offset
|
||||
for key, value in beanstones.items():
|
||||
if pointer == value:
|
||||
pointer = key
|
||||
break
|
||||
if pointer in ctx.server_locations:
|
||||
self.checked_flags[byte_i] += [j]
|
||||
locs_to_send.add(pointer)
|
||||
|
||||
if not ctx.finished_game and cackletta != 0 and current_room == 0x1C7:
|
||||
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
|
||||
|
||||
if self.room != current_room:
|
||||
self.room = current_room
|
||||
await ctx.send_msgs(
|
||||
[
|
||||
{
|
||||
"cmd": "Set",
|
||||
"key": f"mlss_room_{ctx.team}_{ctx.slot}",
|
||||
"default": 0,
|
||||
"want_reply": False,
|
||||
"operations": [{"operation": "replace", "value": current_room}],
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
# Send locations if there are any to send.
|
||||
if locs_to_send != self.local_checked_locations:
|
||||
self.local_checked_locations = locs_to_send
|
||||
|
||||
if locs_to_send is not None:
|
||||
await ctx.send_msgs([{"cmd": "LocationChecks", "locations": list(locs_to_send)}])
|
||||
|
||||
except bizhawk.RequestFailedError:
|
||||
# Exit handler and return to main loop to reconnect.
|
||||
pass
|
||||
except bizhawk.ConnectorError:
|
||||
pass
|
||||
|
||||
|
||||
def find_key(dictionary, target):
|
||||
leftover = target
|
||||
|
||||
for key, value in dictionary.items():
|
||||
if leftover > value:
|
||||
leftover -= value
|
||||
else:
|
||||
return key, leftover
|
||||
|
||||
|
||||
def id_to_RAM(id_: int):
|
||||
code = id_
|
||||
if 0x1C <= code <= 0x1F:
|
||||
code += 0xE
|
||||
if 0x20 <= code <= 0x26:
|
||||
code -= 0x4
|
||||
return code
|
||||
5705
worlds/mlss/Data.py
Normal file
5705
worlds/mlss/Data.py
Normal file
File diff suppressed because it is too large
Load Diff
190
worlds/mlss/Items.py
Normal file
190
worlds/mlss/Items.py
Normal file
@@ -0,0 +1,190 @@
|
||||
import typing
|
||||
|
||||
from BaseClasses import Item, ItemClassification
|
||||
|
||||
|
||||
class ItemData(typing.NamedTuple):
|
||||
code: int
|
||||
itemName: str
|
||||
classification: ItemClassification
|
||||
itemID: int
|
||||
|
||||
|
||||
class MLSSItem(Item):
|
||||
game: str = "Mario & Luigi Superstar Saga"
|
||||
|
||||
|
||||
itemList: typing.List[ItemData] = [
|
||||
ItemData(77771000, "5 Coins", ItemClassification.filler, 0x4),
|
||||
ItemData(77771001, "Mushroom", ItemClassification.filler, 0xA),
|
||||
ItemData(77771002, "Super Mushroom", ItemClassification.filler, 0xB),
|
||||
ItemData(77771003, "Ultra Mushroom", ItemClassification.filler, 0xC),
|
||||
ItemData(77771004, "Max Mushroom", ItemClassification.filler, 0xD),
|
||||
ItemData(77771005, "Nuts", ItemClassification.filler, 0xE),
|
||||
ItemData(77771006, "Super Nuts", ItemClassification.filler, 0xF),
|
||||
ItemData(77771007, "Ultra Nuts", ItemClassification.useful, 0x10),
|
||||
ItemData(77771008, "Max Nuts", ItemClassification.useful, 0x11),
|
||||
ItemData(77771009, "Syrup", ItemClassification.filler, 0x12),
|
||||
ItemData(77771010, "Super Syrup", ItemClassification.filler, 0x13),
|
||||
ItemData(77771011, "Ultra Syrup", ItemClassification.useful, 0x14),
|
||||
ItemData(77771012, "Max Syrup", ItemClassification.useful, 0x15),
|
||||
ItemData(77771013, "1-UP Mushroom", ItemClassification.useful, 0x16),
|
||||
ItemData(77771014, "1-UP Super", ItemClassification.useful, 0x17),
|
||||
ItemData(77771015, "Golden Mushroom", ItemClassification.useful, 0x18),
|
||||
ItemData(77771016, "Refreshing Herb", ItemClassification.filler, 0x19),
|
||||
ItemData(77771017, "Red Pepper", ItemClassification.useful, 0x1A),
|
||||
ItemData(77771018, "Green Pepper", ItemClassification.useful, 0x1B),
|
||||
ItemData(77771019, "Hoo Bean", ItemClassification.filler, 0x1D),
|
||||
ItemData(77771020, "Chuckle Bean", ItemClassification.filler, 0x1E),
|
||||
ItemData(77771021, "Woohoo Blend", ItemClassification.useful, 0x20),
|
||||
ItemData(77771022, "Hoohoo Blend", ItemClassification.useful, 0x21),
|
||||
ItemData(77771023, "Chuckle Blend", ItemClassification.useful, 0x22),
|
||||
ItemData(77771024, "Teehee Blend", ItemClassification.useful, 0x23),
|
||||
ItemData(77771025, "Hoolumbian", ItemClassification.useful, 0x24),
|
||||
ItemData(77771026, "Chuckoccino", ItemClassification.useful, 0x25),
|
||||
ItemData(77771027, "Teeheespresso", ItemClassification.useful, 0x26),
|
||||
ItemData(77771028, "Peasley's Rose", ItemClassification.progression, 0x31),
|
||||
ItemData(77771029, "Beanbean Brooch", ItemClassification.progression, 0x32),
|
||||
ItemData(77771030, "Red Goblet", ItemClassification.progression, 0x33),
|
||||
ItemData(77771031, "Green Goblet", ItemClassification.progression, 0x34),
|
||||
ItemData(77771032, "Red Chuckola Fruit", ItemClassification.progression, 0x35),
|
||||
ItemData(77771033, "White Chuckola Fruit", ItemClassification.progression, 0x36),
|
||||
ItemData(77771034, "Purple Chuckola Fruit", ItemClassification.progression, 0x37),
|
||||
ItemData(77771035, "Hammers", ItemClassification.progression, 0x38),
|
||||
ItemData(77771036, "Firebrand", ItemClassification.progression, 0x39),
|
||||
ItemData(77771037, "Thunderhand", ItemClassification.progression, 0x3A),
|
||||
ItemData(77771038, "Membership Card", ItemClassification.progression, 0x40),
|
||||
ItemData(77771039, "Winkle Card", ItemClassification.progression, 0x41),
|
||||
ItemData(77771040, "Peach's Extra Dress", ItemClassification.progression, 0x42),
|
||||
ItemData(77771041, "Fake Beanstar", ItemClassification.progression, 0x43),
|
||||
ItemData(77771042, "Red Pearl Bean", ItemClassification.progression, 0x45),
|
||||
ItemData(77771043, "Green Pearl Bean", ItemClassification.progression, 0x46),
|
||||
ItemData(77771044, "Bean Fruit 1", ItemClassification.progression_skip_balancing, 0x47),
|
||||
ItemData(77771045, "Bean Fruit 2", ItemClassification.progression_skip_balancing, 0x50),
|
||||
ItemData(77771046, "Bean Fruit 3", ItemClassification.progression_skip_balancing, 0x51),
|
||||
ItemData(77771047, "Bean Fruit 4", ItemClassification.progression_skip_balancing, 0x52),
|
||||
ItemData(77771048, "Bean Fruit 5", ItemClassification.progression_skip_balancing, 0x53),
|
||||
ItemData(77771049, "Bean Fruit 6", ItemClassification.progression_skip_balancing, 0x54),
|
||||
ItemData(77771050, "Bean Fruit 7", ItemClassification.progression_skip_balancing, 0x55),
|
||||
ItemData(77771051, "Blue Neon Egg", ItemClassification.progression, 0x56),
|
||||
ItemData(77771052, "Red Neon Egg", ItemClassification.progression, 0x57),
|
||||
ItemData(77771053, "Green Neon Egg", ItemClassification.progression, 0x60),
|
||||
ItemData(77771054, "Yellow Neon Egg", ItemClassification.progression, 0x61),
|
||||
ItemData(77771055, "Purple Neon Egg", ItemClassification.progression, 0x62),
|
||||
ItemData(77771056, "Orange Neon Egg", ItemClassification.progression, 0x63),
|
||||
ItemData(77771057, "Azure Neon Egg", ItemClassification.progression, 0x64),
|
||||
ItemData(77771058, "Beanstar Piece 1", ItemClassification.progression, 0x65),
|
||||
ItemData(77771059, "Beanstar Piece 2", ItemClassification.progression, 0x66),
|
||||
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(77771078, "Secret Scroll 1", ItemClassification.useful, 0x92),
|
||||
ItemData(77771079, "Secret Scroll 2", ItemClassification.useful, 0x93),
|
||||
ItemData(77771080, "Castle Badge", ItemClassification.useful, 0x9F),
|
||||
ItemData(77771081, "Pea Badge", ItemClassification.useful, 0xA0),
|
||||
ItemData(77771082, "Bean B. Badge", ItemClassification.useful, 0xA1),
|
||||
ItemData(77771083, "Counter Badge", ItemClassification.useful, 0xA2),
|
||||
ItemData(77771084, "Charity Badge", ItemClassification.useful, 0xA3),
|
||||
ItemData(77771085, "Bros. Badge", ItemClassification.useful, 0xA4),
|
||||
ItemData(77771086, "Miracle Badge", ItemClassification.useful, 0xA5),
|
||||
ItemData(77771087, "Ohoracle Badge", ItemClassification.useful, 0xA6),
|
||||
ItemData(77771088, "Mush Badge", ItemClassification.useful, 0xA7),
|
||||
ItemData(77771089, "Mari-Lui Badge", ItemClassification.useful, 0xA8),
|
||||
ItemData(77771090, "Muscle Badge", ItemClassification.useful, 0xA9),
|
||||
ItemData(77771091, "Spiny Badge AA", ItemClassification.useful, 0xAA),
|
||||
ItemData(77771092, "Mush Badge A", ItemClassification.useful, 0xAB),
|
||||
ItemData(77771093, "Grab Badge", ItemClassification.useful, 0xAC),
|
||||
ItemData(77771094, "Mush Badge AA", ItemClassification.useful, 0xAD),
|
||||
ItemData(77771095, "Power Badge", ItemClassification.useful, 0xAE),
|
||||
ItemData(77771096, "Wonder Badge", ItemClassification.useful, 0xAF),
|
||||
ItemData(77771097, "Beauty Badge", ItemClassification.useful, 0xB0),
|
||||
ItemData(77771098, "Salvage Badge", ItemClassification.useful, 0xB1),
|
||||
ItemData(77771099, "Oh-Pah Badge", ItemClassification.useful, 0xB2),
|
||||
ItemData(77771100, "Brilliant Badge", ItemClassification.useful, 0xB3),
|
||||
ItemData(77771101, "Sarge Badge", ItemClassification.useful, 0xB4),
|
||||
ItemData(77771102, "General Badge", ItemClassification.useful, 0xB5),
|
||||
ItemData(77771103, "Tank Badge", ItemClassification.useful, 0xB6),
|
||||
ItemData(77771104, "Bros. Rock", ItemClassification.useful, 0xBD),
|
||||
ItemData(77771105, "Soulful Bros.", ItemClassification.useful, 0xC0),
|
||||
ItemData(77771106, "High-End Badge", ItemClassification.useful, 0xC1),
|
||||
ItemData(77771107, "Bean Pants", ItemClassification.useful, 0xCC),
|
||||
ItemData(77771108, "Bean Trousers", ItemClassification.useful, 0xCD),
|
||||
ItemData(77771109, "Blue Jeans", ItemClassification.useful, 0xCE),
|
||||
ItemData(77771110, "Parasol Pants", ItemClassification.useful, 0xCF),
|
||||
ItemData(77771111, "Hard Pants", ItemClassification.useful, 0xD0),
|
||||
ItemData(77771112, "Heart Jeans", ItemClassification.useful, 0xD1),
|
||||
ItemData(77771113, "Plaid Trousers", ItemClassification.useful, 0xD2),
|
||||
ItemData(77771114, "#1 Trousers", ItemClassification.useful, 0xD3),
|
||||
ItemData(77771115, "Safety Slacks", ItemClassification.useful, 0xD4),
|
||||
ItemData(77771116, "Shroom Pants", ItemClassification.useful, 0xD5),
|
||||
ItemData(77771117, "Shroom Bells", ItemClassification.useful, 0xD6),
|
||||
ItemData(77771118, "Shroom Slacks", ItemClassification.useful, 0xD7),
|
||||
ItemData(77771119, "Peachy Jeans", ItemClassification.useful, 0xD8),
|
||||
ItemData(77771120, "Mushwin Pants", ItemClassification.useful, 0xD9),
|
||||
ItemData(77771121, "Mushluck Pants", ItemClassification.useful, 0xDA),
|
||||
ItemData(77771122, "Scandal Jeans", ItemClassification.useful, 0xDB),
|
||||
ItemData(77771123, "Street Jeans", ItemClassification.useful, 0xDC),
|
||||
ItemData(77771124, "Tropic Slacks", ItemClassification.useful, 0xDD),
|
||||
ItemData(77771125, "Hermetic Pants", ItemClassification.useful, 0xDE),
|
||||
ItemData(77771126, "Beanstar Pants", ItemClassification.useful, 0xDF),
|
||||
ItemData(77771127, "Peasley Slacks", ItemClassification.useful, 0xE0),
|
||||
ItemData(77771128, "Queen B. Jeans", ItemClassification.useful, 0xE1),
|
||||
ItemData(77771129, "B. Brand Jeans", ItemClassification.useful, 0xE2),
|
||||
ItemData(77771130, "Heart Slacks", ItemClassification.useful, 0xE3),
|
||||
ItemData(77771131, "Casual Slacks", ItemClassification.useful, 0xE4),
|
||||
ItemData(77771132, "Casual Coral", ItemClassification.useful, 0xEB),
|
||||
ItemData(77771133, "Harhall's Pants", ItemClassification.useful, 0xF1),
|
||||
ItemData(77771134, "Wool Trousers", ItemClassification.useful, 0xF3),
|
||||
ItemData(77771135, "Iron Pants", ItemClassification.useful, 0xF7),
|
||||
ItemData(77771136, "Greed Wallet", ItemClassification.useful, 0xF8),
|
||||
ItemData(77771137, "Bonus Ring", ItemClassification.useful, 0xF9),
|
||||
ItemData(77771138, "Excite Spring", ItemClassification.useful, 0xFA),
|
||||
ItemData(77771139, "Great Force", ItemClassification.useful, 0xFB),
|
||||
ItemData(77771140, "Power Grip", ItemClassification.useful, 0xFC),
|
||||
ItemData(77771141, "Cobalt Necktie", ItemClassification.useful, 0xFD),
|
||||
ItemData(77771142, "Game Boy Horror SP", ItemClassification.useful, 0xFE),
|
||||
ItemData(77771143, "Woo Bean", ItemClassification.skip_balancing, 0x1C),
|
||||
ItemData(77771144, "Hee Bean", ItemClassification.skip_balancing, 0x1F),
|
||||
]
|
||||
|
||||
item_frequencies: typing.Dict[str, int] = {
|
||||
"5 Coins": 40,
|
||||
"Mushroom": 55,
|
||||
"Super Mushroom": 15,
|
||||
"Ultra Mushroom": 12,
|
||||
"Nuts": 10,
|
||||
"Super Nuts": 5,
|
||||
"Ultra Nuts": 5,
|
||||
"Max Nuts": 2,
|
||||
"Syrup": 28,
|
||||
"Super Syrup": 10,
|
||||
"Ultra Syrup": 10,
|
||||
"Max Syrup": 2,
|
||||
"1-Up Mushroom": 15,
|
||||
"1-Up Super": 5,
|
||||
"Golden Mushroom": 3,
|
||||
"Refreshing Herb": 9,
|
||||
"Red Pepper": 2,
|
||||
"Green Pepper": 2,
|
||||
"Hoo Bean": 100,
|
||||
"Chuckle Bean": 200,
|
||||
"Hammers": 3,
|
||||
}
|
||||
|
||||
item_table: typing.Dict[str, ItemData] = {item.itemName: item for item in itemList}
|
||||
items_by_id: typing.Dict[int, ItemData] = {item.code: item for item in itemList}
|
||||
1185
worlds/mlss/Locations.py
Normal file
1185
worlds/mlss/Locations.py
Normal file
File diff suppressed because it is too large
Load Diff
559
worlds/mlss/Names/LocationName.py
Normal file
559
worlds/mlss/Names/LocationName.py
Normal file
@@ -0,0 +1,559 @@
|
||||
class LocationName:
|
||||
StardustFields1Block1 = "Stardust Fields Room 1 Block 1"
|
||||
StardustFields1Block2 = "Stardust Fields Room 1 Block 2"
|
||||
StardustFields2Block = "Stardust Fields Room 2 Block"
|
||||
StardustFields3Block = "Stardust Fields Room 3 Block"
|
||||
StardustFields4Block1 = "Stardust Fields Room 4 Block 1"
|
||||
StardustFields4Block2 = "Stardust Fields Room 4 Block 2"
|
||||
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"
|
||||
BeanbeanCastleTownMiniMarioBlock1 = "Beanbean Castle Town Mini Mario Block 1"
|
||||
BeanbeanCastleTownMiniMarioBlock2 = "Beanbean Castle Town Mini Mario Block 2"
|
||||
BeanbeanCastleTownMiniMarioBlock3 = "Beanbean Castle Town Mini Mario Block 3"
|
||||
BeanbeanCastleTownMiniMarioBlock4 = "Beanbean Castle Town Mini Mario Block 4"
|
||||
BeanbeanCastleTownMiniMarioBlock5 = "Beanbean Castle Town Mini Mario Block 5"
|
||||
HoohooMountainSummitDigspot = "Hoohoo Mountain Summit Digspot"
|
||||
HoohooMountainBelowSummitDigspot = "Hoohoo Mountain Below Summit Digspot"
|
||||
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"
|
||||
HoohooMountainHoohoorosRoomBlock1 = "Hoohoo Mountain Hoohooros Room Block 1"
|
||||
HoohooMountainHoohoorosRoomBlock2 = "Hoohoo Mountain Hoohooros Room Block 2"
|
||||
HoohooMountainHoohoorosRoomDigspot1 = "Hoohoo Mountain Hoohooros Room Digspot 1"
|
||||
HoohooMountainHoohoorosRoomDigspot2 = "Hoohoo Mountain Hoohooros Room Digspot 2"
|
||||
HoohooMountainBeforeHoohoorosBlock = "Hoohoo Mountain Before Hoohooros Block"
|
||||
HoohooMountainBeforeHoohoorosDigspot = "Hoohoo Mountain Before Hoohooros Digspot"
|
||||
HoohooMountainFountainRoomBlock1 = "Hoohoo Mountain Fountain Room Block 1"
|
||||
HoohooMountainFountainRoomBlock2 = "Hoohoo Mountain Fountain Room Block 2"
|
||||
HoohooMountainRoom2Digspot1 = "Hoohoo Mountain Room 2 Digspot 1"
|
||||
HoohooMountainRoom2Digspot2 = "Hoohoo Mountain Room 2 Digspot 2"
|
||||
HoohooMountainRoom1Block1 = "Hoohoo Mountain Room 1 Block 1"
|
||||
HoohooMountainRoom1Block2 = "Hoohoo Mountain Room 1 Block 2"
|
||||
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"
|
||||
HoohooVillageBridgeRoomBlock1 = "Hoohoo Village Bridge Room Block 1"
|
||||
HoohooVillageBridgeRoomBlock2 = "Hoohoo Village Bridge Room Block 2"
|
||||
HoohooVillageBridgeRoomBlock3 = "Hoohoo Village Bridge Room Block 3"
|
||||
HoohooMountainBaseBridgeRoomBlock1 = "Hoohoo Mountain Base Bridge Room Block 1"
|
||||
HoohooMountainBaseBridgeRoomBlock2 = "Hoohoo Mountain Base Bridge Room Block 2"
|
||||
HoohooMountainBaseBridgeRoomBlock3 = "Hoohoo Mountain Base Bridge Room Block 3"
|
||||
HoohooMountainBaseBridgeRoomBlock4 = "Hoohoo Mountain Base Bridge Room Block 4"
|
||||
HoohooMountainBaseBridgeRoomDigspot = "Hoohoo Mountain Base Bridge Room Digspot"
|
||||
HoohooMountainBaseBoostatueRoomBlock1 = "Hoohoo Mountain Base Boostatue Room Block 1"
|
||||
HoohooMountainBaseBoostatueRoomBlock2 = "Hoohoo Mountain Base Boostatue Room Block 2"
|
||||
HoohooMountainBaseBoostatueRoomDigspot1 = "Hoohoo Mountain Base Boostatue Room Digspot 1"
|
||||
HoohooMountainBaseBoostatueRoomDigspot2 = "Hoohoo Mountain Base Boostatue Room Digspot 2"
|
||||
HoohooMountainBaseBoostatueRoomDigspot3 = "Hoohoo Mountain Base Boostatue Room Digspot 3"
|
||||
BeanbeanOutskirtsBooStatueMole = "Beanbean Outskirts Boo Statue Mole"
|
||||
HoohooMountainBaseGrassyAreaBlock1 = "Hoohoo Mountain Base Grassy Area Block 1"
|
||||
HoohooMountainBaseGrassyAreaBlock2 = "Hoohoo Mountain Base Grassy Area Block 2"
|
||||
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"
|
||||
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"
|
||||
CaveConnectingStardustFieldsAndHoohooVillageBlock1 = "Cave Connecting Stardust Fields and Hoohoo Village Block 1"
|
||||
CaveConnectingStardustFieldsAndHoohooVillageBlock2 = "Cave Connecting Stardust Fields and Hoohoo Village Block 2"
|
||||
HoohooVillageSouthCaveBlock = "Hoohoo Village South Cave Block"
|
||||
HoohooVillageSuperHammerCaveDigspot = "Hoohoo Village Super Hammer Cave Digspot"
|
||||
HoohooVillageSuperHammerCaveBlock = "Hoohoo Village Super Hammer Cave Block"
|
||||
HoohooVillageNorthCaveRoom1Block = "Hoohoo Village North Cave Room 1 Block"
|
||||
HoohooVillageNorthCaveRoom2Block = "Hoohoo Village North Cave Room 2 Block"
|
||||
HoohooVillageNorthCaveRoom2Digspot = "Hoohoo Village North Cave Room 2 Digspot"
|
||||
HoohooMountainBaseMinecartCaveDigspot = "Hoohoo Mountain Base Minecart Cave Digspot"
|
||||
BeanbeanOutskirtsFarmRoomDigspot1 = "Beanbean Outskirts Farm Room Digspot 1"
|
||||
BeanbeanOutskirtsFarmRoomDigspot2 = "Beanbean Outskirts Farm Room Digspot 2"
|
||||
BeanbeanOutskirtsFarmRoomDigspot3 = "Beanbean Outskirts Farm Room Digspot 3"
|
||||
BeanbeanOutskirtsNWBlock = "Beanbean Outskirts NW Block"
|
||||
BeanbeanOutskirtsNWDigspot = "Beanbean Outskirts NW Digspot"
|
||||
BeanbeanOutskirtsWDigspot1 = "Beanbean Outskirts W Digspot 1"
|
||||
BeanbeanOutskirtsWDigspot2 = "Beanbean Outskirts W"
|
||||
BeanbeanOutskirtsNRoom1Digspot = "Beanbean Outskirts N Room 1 Digspot"
|
||||
BeanbeanOutskirtsNRoom2Digspot = "Beanbean Outskirts N Room 2 Digspot"
|
||||
BeanbeanOutskirtsSRoom1Digspot1 = "Beanbean Outskirts S Room 1 Digspot 1"
|
||||
BeanbeanOutskirtsSRoom1Block = "Beanbean Outskirts S Room 1 Block"
|
||||
BeanbeanOutskirtsSRoom1Digspot2 = "Beanbean Outskirts S Room 1 Digspot 2"
|
||||
BeanbeanOutskirtsSRoom2Block1 = "Beanbean Outskirts S Room 2 Block 1"
|
||||
BeanbeanOutskirtsSRoom2Digspot1 = "Beanbean Outskirts S Room 2 Digspot 1"
|
||||
BeanbeanOutskirtsSRoom2Digspot2 = "Beanbean Outskirts S Room 2 Digspot 2"
|
||||
BeanbeanOutskirtsSRoom2Block2 = "Beanbean Outskirts S Room 2 Block 2"
|
||||
BeanbeanOutskirtsSRoom2Digspot3 = "Beanbean Outskirts S Room 2 Digspot 3"
|
||||
BeanbeanOutskirtsNEDigspot1 = "Beanbean Outskirts NE Digspot 1"
|
||||
BeanbeanOutskirtsNEDigspot2 = "Beanbean Outskirts NE Digspot 2"
|
||||
BeanbeanOutskirtsEDigspot1 = "Beanbean Outskirts E Digspot 1"
|
||||
BeanbeanOutskirtsEDigspot2 = "Beanbean Outskirts E Digspot 2"
|
||||
BeanbeanOutskirtsEDigspot3 = "Beanbean Outskirts E Digspot 3"
|
||||
BeanbeanOutskirtsSEDigspot1 = "Beanbean Outskirts SE Digspot 1"
|
||||
BeanbeanOutskirtsSEDigspot2 = "Beanbean Outskirts SE Digspot 2"
|
||||
BeanbeanOutskirtsSEDigspot3 = "Beanbean Outskirts SE Digspot 3"
|
||||
BeanbeanOutskirtsNorthBeachDigspot1 = "Beanbean Outskirts North Beach Digspot 1"
|
||||
BeanbeanOutskirtsNorthBeachDigspot2 = "Beanbean Outskirts North Beach Digspot 2"
|
||||
BeanbeanOutskirtsNorthBeachDigspot3 = "Beanbean Outskirts North Beach Digspot 3"
|
||||
BeanbeanOutskirtsSouthBeachDigspot = "Beanbean Outskirts South Beach Digspot"
|
||||
BeanbeanOutskirtsSurfBeachDigspot1 = "Beanbean Outskirts Surf Beach Digspot 1"
|
||||
BeanbeanOutskirtsSurfBeachBlock = "Beanbean Outskirts Surf Beach Block"
|
||||
BeanbeanOutskirtsSurfBeachDigspot2 = "Beanbean Outskirts Surf Beach Digspot 2"
|
||||
BeanbeanOutskirtsSurfBeachDigspot3 = "Beanbean Outskirts Surf Beach Digspot 3"
|
||||
ChateauRoom1Digspot = "Chateau Room 1 Digspot"
|
||||
ChateauPoppleFightRoomBlock1 = "Chateau Popple Fight Room Block 1"
|
||||
ChateauPoppleFightRoomBlock2 = "Chateau Popple Fight Room Block 2"
|
||||
ChateauPoppleFightRoomDigspot = "Chateau Popple Fight Room Digspot"
|
||||
ChateauBarrelRoomDigspot = "Chateau Barrel Room Digspot"
|
||||
ChateauGobletRoomDigspot = "Chateau Goblet Room Digspot"
|
||||
ChucklehuckWoodsCaveRoom1Block1 = "Chucklehuck Woods Cave Room 1 Block 1"
|
||||
ChucklehuckWoodsCaveRoom1Block2 = "Chucklehuck Woods Cave Room 1 Block 2"
|
||||
ChucklehuckWoodsCaveRoom2Block = "Chucklehuck Woods Cave Room 2 Block"
|
||||
ChucklehuckWoodsCaveRoom3Block = "Chucklehuck Woods Cave Room 3 Block"
|
||||
ChucklehuckWoodsRoom2Block = "Chucklehuck Woods Room 2 Block"
|
||||
ChucklehuckWoodsRoom2Digspot = "Chucklehuck Woods Room 2 Digspot"
|
||||
ChucklehuckWoodsPipeRoomBlock1 = "Chucklehuck Woods Pipe Room Block 1"
|
||||
ChucklehuckWoodsPipeRoomBlock2 = "Chucklehuck Woods Pipe Room Block 2"
|
||||
ChucklehuckWoodsPipeRoomDigspot1 = "Chucklehuck Woods Pipe Room Digspot 1"
|
||||
ChucklehuckWoodsPipeRoomDigspot2 = "Chucklehuck Woods Pipe Room Digspot 2"
|
||||
ChucklehuckWoodsRoom4Block1 = "Chucklehuck Woods Room 4 Block 1"
|
||||
ChucklehuckWoodsRoom4Block2 = "Chucklehuck Woods Room 4 Block 2"
|
||||
ChucklehuckWoodsRoom4Block3 = "Chucklehuck Woods Room 4 Block 3"
|
||||
ChucklehuckWoodsRoom7Block1 = "Chucklehuck Woods Room 7 Block 1"
|
||||
ChucklehuckWoodsRoom7Block2 = "Chucklehuck Woods Room 7 Block 2"
|
||||
ChucklehuckWoodsRoom7Digspot1 = "Chucklehuck Woods Room 7 Digspot 1"
|
||||
ChucklehuckWoodsRoom7Digspot2 = "Chucklehuck Woods Room 7 Digspot 2"
|
||||
ChucklehuckWoodsRoom8Digspot = "Chucklehuck Woods Room 8 Digspot"
|
||||
ChucklehuckWoodsEastOfChucklerootDigspot = "Chucklehuck Woods East of Chuckleroot Digspot"
|
||||
ChucklehuckWoodsNortheastOfChucklerootDigspot1 = "Chucklehuck Woods Northeast of Chuckleroot Digspot 1"
|
||||
ChucklehuckWoodsNortheastOfChucklerootDigspot2 = "Chucklehuck Woods Northeast of Chuckleroot Digspot 2"
|
||||
ChucklehuckWoodsNortheastOfChucklerootDigspot3 = "Chucklehuck Woods Northeast of Chuckleroot Digspot 3"
|
||||
ChucklehuckWoodsNortheastOfChucklerootDigspot4 = "Chucklehuck Woods Northeast of Chuckleroot Digspot 4"
|
||||
ChucklehuckWoodsWhiteFruitRoomDigspot1 = "Chucklehuck Woods White Fruit Room Digspot 1"
|
||||
ChucklehuckWoodsWhiteFruitRoomDigspot2 = "Chucklehuck Woods White Fruit Room Digspot 2"
|
||||
ChucklehuckWoodsWhiteFruitRoomDigspot3 = "Chucklehuck Woods White Fruit Room Digspot 3"
|
||||
ChucklehuckWoodsWestOfChucklerootBlock = "Chucklehuck Woods West of Chuckleroot Block"
|
||||
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"
|
||||
WinkleAreaBeanstarRoomBlock = "Winkle Area Beanstar Room Block"
|
||||
WinkleAreaDigspot = "Winkle Area Digspot"
|
||||
WinkleAreaOutsideColosseumBlock = "Winkle Area Outside Colosseum Block"
|
||||
ChucklehuckWoodsKoopaRoomBlock1 = "Chucklehuck Woods Koopa Room Block 1"
|
||||
ChucklehuckWoodsKoopaRoomBlock2 = "Chucklehuck Woods Koopa Room Block 2"
|
||||
ChucklehuckWoodsKoopaRoomDigspot = "Chucklehuck Woods Koopa Room Digspot"
|
||||
ChucklehuckWoodsWinkleCaveBlock1 = "Chucklehuck Woods Winkle Cave Block 1"
|
||||
ChucklehuckWoodsWinkleCaveBlock2 = "Chucklehuck Woods Winkle Cave Block 2"
|
||||
OhoOasisWestDigspot = "Oho Oasis West Digspot"
|
||||
OhoOasisFirePalaceBlock = "Oho Oasis Fire Palace Block"
|
||||
SewersRoom3Block1 = "Sewers Room 3 Block 1"
|
||||
SewersRoom3Block2 = "Sewers Room 3 Block 2"
|
||||
SewersRoom3Block3 = "Sewers Room 3 Block 3"
|
||||
SewersRoom5Block1 = "Sewers Room 5 Block 1"
|
||||
SewersRoom5Block2 = "Sewers Room 5 Block 2"
|
||||
SewersPrisonRoomBlock1 = "Sewers Prison Room Block 1"
|
||||
SewersPrisonRoomBlock2 = "Sewers Prison Room Block 2"
|
||||
SewersPrisonRoomBlock3 = "Sewers Prison Room Block 3"
|
||||
SewersPrisonRoomBlock4 = "Sewers Prison Room Block 4"
|
||||
OhoOceanFirePuzzleRoomDigspot = "Oho Ocean Fire Puzzle Room Digspot"
|
||||
OhoOceanSouthRoom1Block = "Oho Ocean South Room 1 Block"
|
||||
OhoOceanSouthRoom2Digspot = "Oho Ocean South Room 2 Digspot"
|
||||
OhoOceanSpikeRoomDigspot1 = "Oho Ocean Spike Room Digspot 1"
|
||||
OhoOceanSpikeRoomDigspot2 = "Oho Ocean Spike Room Digspot 2"
|
||||
OceanNorthWhirlpoolBlock1 = "Oho Ocean North Whirlpool Block 1"
|
||||
OceanNorthWhirlpoolBlock2 = "Oho Ocean North Whirlpool Block 2"
|
||||
OceanNorthWhirlpoolBlock3 = "Oho Ocean North Whirlpool Block 3"
|
||||
OceanNorthWhirlpoolBlock4 = "Oho Ocean North Whirlpool Block 4"
|
||||
OceanNorthWhirlpoolDigspot1 = "Oho Ocean North Whirlpool Digspot 1"
|
||||
OceanNorthWhirlpoolDigspot2 = "Oho Ocean North Whirlpool Digspot 2"
|
||||
OceanSouthWhirlpoolDigspot1 = "Oho Ocean South Whirlpool Digspot 1"
|
||||
OceanSouthWhirlpoolDigspot2 = "Oho Ocean South Whirlpool Digspot 2"
|
||||
OceanSouthWhirlpoolDigspot3 = "Oho Ocean South Whirlpool Digspot 3"
|
||||
OceanSouthWhirlpoolDigspot4 = "Oho Ocean South Whirlpool Digspot 4"
|
||||
OceanSouthWhirlpoolDigspot5 = "Oho Ocean South Whirlpool Digspot 5"
|
||||
OceanSouthWhirlpoolDigspot6 = "Oho Ocean South Whirlpool Digspot 6"
|
||||
OceanSouthWhirlpoolRoom2Digspot = "Oho Ocean South Whirlpool Room 2 Digspot"
|
||||
WoohooHooniversityStarRoomBlock1 = "Woohoo Hooniversity Star Room Block 1"
|
||||
WoohooHooniversityStarRoomBlock2 = "Woohoo Hooniversity Star Room Block 2"
|
||||
WoohooHooniversityStarRoomBlock3 = "Woohoo Hooniversity Star Room Block 3"
|
||||
WoohooHooniversitySunDoorBlock1 = "Woohoo Hooniversity Sun Door Block 1"
|
||||
WoohooHooniversitySunDoorBlock2 = "Woohoo Hooniversity Sun Door Block 2"
|
||||
WoohooHooniversitySouthOfStarRoomBlock = "Woohoo Hooniversity South Of Star Room Block"
|
||||
WoohooHooniversityWestOfStarRoomDigspot1 = "Woohoo Hooniversity West Of Star Room Digspot 1"
|
||||
WoohooHooniversityWestOfStarRoomDigspot2 = "Woohoo Hooniversity West Of Star Room Digspot 2"
|
||||
WoohooHooniversityBarrelPuzzleEntranceDigspot1 = "Woohoo Hooniversity Barrel Puzzle Entrance Digspot 1"
|
||||
WoohooHooniversityBarrelPuzzleEntranceBlock1 = "Woohoo Hooniversity Barrel Puzzle Entrance Block 1"
|
||||
WoohooHooniversityBarrelPuzzleEntranceBlock2 = "Woohoo Hooniversity Barrel Puzzle Entrance Block 2"
|
||||
WoohooHooniversityBarrelPuzzleEntranceBlock3 = "Woohoo Hooniversity Barrel Puzzle Entrance Block 3"
|
||||
WoohooHooniversityBarrelPuzzleEntranceBlock4 = "Woohoo Hooniversity Barrel Puzzle Entrance Block 4"
|
||||
WoohooHooniversityBarrelPuzzleEntranceDigspot2 = "Woohoo Hooniversity Barrel Puzzle Entrance Digspot 2"
|
||||
ChucklehuckWoodsRoom1Digspot = "Chucklehuck Woods Room 1 Digspot"
|
||||
WoohooHooniversityWestOfStarRoom2Digspot = "Woohoo Hooniversity West of Star Room 2 Digspot"
|
||||
WoohooHooniversityWestOfStarRoom3Digspot = "Woohoo Hooniversity West of Star Room 3 Digspot"
|
||||
WoohooHooniversityWestOfStarRoom4Block1 = "Woohoo Hooniversity West of Star Room 4 Block 1"
|
||||
WoohooHooniversityWestOfStarRoom4Block2 = "Woohoo Hooniversity West of Star Room 4 Block 2"
|
||||
WoohooHooniversityWestOfStarRoom4Block3 = "Woohoo Hooniversity West of Star Room 4 Block 3"
|
||||
WoohooHooniversityWestOfStarRoom4Digspot1 = "Woohoo Hooniversity West of Star Room 4 Digspot 1"
|
||||
WoohooHooniversityWestOfStarRoom4Digspot2 = "Woohoo Hooniversity West of Star Room 4 Digspot 2"
|
||||
WoohooHooniversityWestOfStarRoom5Digspot = "Woohoo Hooniversity West of Star Room 5 Digspot"
|
||||
WoohooHooniversityEntranceToMiniMarioRoomDigspot1 = "Woohoo Hooniversity Entrance to Mini Mario Room Digspot 1"
|
||||
WoohooHooniversityEntranceToMiniMarioRoomDigspot2 = "Woohoo Hooniversity Entrance to Mini Mario Room Digspot 2"
|
||||
WoohooHooniversityEntranceToMiniMarioRoom2Digspot = "Woohoo Hooniversity Entrance to Mini Mario Room 2 Digspot"
|
||||
WoohooHooniversityMiniMarioPuzzleBlock = "Woohoo Hooniversity Mini Mario Puzzle Block"
|
||||
WoohooHooniversityMiniMarioPuzzleDigspot = "Woohoo Hooniversity Mini Mario Puzzle Digspot"
|
||||
WoohooHooniversityMiniMarioPuzzleSecretAreaBlock1 = "Woohoo Hooniversity Mini Mario Puzzle Secret Area Block 1"
|
||||
WoohooHooniversityMiniMarioPuzzleSecretAreaBlock2 = "Woohoo Hooniversity Mini Mario Puzzle Secret Area Block 2"
|
||||
WoohooHooniversityMiniMarioPuzzleSecretAreaBlock3 = "Woohoo Hooniversity Mini Mario Puzzle Secret Area Block 3"
|
||||
WoohooHooniversityMiniMarioPuzzleSecretAreaBlock4 = "Woohoo Hooniversity Mini Mario Puzzle Secret Area Block 4"
|
||||
WoohooHooniversityPastSunDoorBlock1 = "Woohoo Hooniversity Past Sun Door Block 1"
|
||||
WoohooHooniversityPastSunDoorBlock2 = "Woohoo Hooniversity Past Sun Door Block 2"
|
||||
WoohooHooniversityPastSunDoorBlock3 = "Woohoo Hooniversity Past Sun Door Block 3"
|
||||
WoohooHooniversityPastCacklettaRoom1Block = "Woohoo Hooniversity Past Cackletta Room 1 Block"
|
||||
WoohooHooniversityPastCacklettaRoom2Block1 = "Woohoo Hooniversity Past Cackletta Room 2 Block 1"
|
||||
WoohooHooniversityPastCacklettaRoom2Block2 = "Woohoo Hooniversity Past Cackletta Room 2 Block 2"
|
||||
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"
|
||||
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"
|
||||
GwarharLagoonPipeRoomDigspot = "Gwarhar Lagoon Pipe Room Digspot"
|
||||
GwarharLagoonMassageParlorEntranceDigspot = "Gwarhar Lagoon Massage Parlor Entrance Digspot"
|
||||
GwarharLagoonPastHermieDigspot = "Gwarhar Lagoon Past Hermie Digspot"
|
||||
GwarharLagoonEntranceToWestUnderwaterAreaDigspot = "Gwarhar Lagoon Entrance to West Underwater Area Digspot"
|
||||
GwarharLagoonFireDashPuzzleRoom1Digspot1 = "Gwarhar Lagoon Fire Dash Puzzle Room 1 Digspot 1"
|
||||
GwarharLagoonFireDashPuzzleRoom1Digspot2 = "Gwarhar Lagoon Fire Dash Puzzle Room 1 Digspot 2"
|
||||
GwarharLagoonFireDashPuzzleRoom2Digspot = "Gwarhar Lagoon Fire Dash Puzzle Room 2 Digspot"
|
||||
GwarharLagoonFireDashPuzzleRoom3Digspot1 = "Gwarhar Lagoon Fire Dash Puzzle Room 3 Digspot 1"
|
||||
GwarharLagoonFireDashPuzzleRoom3Digspot2 = "Gwarhar Lagoon Fire Dash Puzzle Room 3 Digspot 2"
|
||||
GwarharLagoonEastOfStoneBridgeBlock = "Gwarhar Lagoon East of Stone Bridge Block"
|
||||
GwarharLagoonNorthOfSpangleRoomDigspot = "Gwarhar Lagoon North of Spangle Room Digspot"
|
||||
GwarharLagoonWestOfSpangleRoomDigspot = "Gwarhar Lagoon West of Spangle Room Digspot"
|
||||
GwarharLagoonSpangleRoomBlock = "Gwarhar Lagoon Spangle Room Block"
|
||||
GwarharLagoonFirstUnderwaterAreaRoom1Block = "Gwarhar Lagoon First Underwater Area Room 1 Block"
|
||||
GwarharLagoonFirstUnderwaterAreaRoom2Block1 = "Gwarhar Lagoon First Underwater Area Room 2 Block 1"
|
||||
GwarharLagoonFirstUnderwaterAreaRoom2Block2 = "Gwarhar Lagoon First Underwater Area Room 2 Block 2"
|
||||
GwarharLagoonSecondUnderwaterAreaRoom4Digspot = "Gwarhar Lagoon Second Underwater Area Room 4 Digspot"
|
||||
GwarharLagoonSecondUnderwaterAreaRoom2Digspot1 = "Gwarhar Lagoon Second Underwater Area Room 2 Digspot 1"
|
||||
GwarharLagoonSecondUnderwaterAreaRoom2Digspot2 = "Gwarhar Lagoon Second Underwater Area Room 2 Digspot 2"
|
||||
GwarharLagoonSecondUnderwaterAreaRoom3Block1 = "Gwarhar Lagoon Second Underwater Area Room 3 Block 1"
|
||||
GwarharLagoonSecondUnderwaterAreaRoom3Block2 = "Gwarhar Lagoon Second Underwater Area Room 3 Block 2"
|
||||
GwarharLagoonSecondUnderwaterAreaRoom3Block3 = "Gwarhar Lagoon Second Underwater Area Room 3 Block 3"
|
||||
GwarharLagoonSecondUnderwaterAreaRoom1Digspot = "Gwarhar Lagoon Second Underwater Area Room 1 Digspot"
|
||||
WoohooHooniversityBasementRoom1Digspot = "Woohoo Hooniversity Basement Room 1 Digspot"
|
||||
WoohooHooniversityBasementRoom2Digspot = "Woohoo Hooniversity Basement Room 2 Digspot"
|
||||
WoohooHooniversityBasementRoom3Block = "Woohoo Hooniversity Basement Room 3 Block"
|
||||
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"
|
||||
TeeheeValleyRoom1Digspot1 = "Teehee Valley Room 1 Digspot 1"
|
||||
TeeheeValleyRoom1Digspot2 = "Teehee Valley Room 1 Digspot 2"
|
||||
TeeheeValleyRoom1Digspot3 = "Teehee Valley Room 1 Digspot 3"
|
||||
TeeheeValleyEastRoomDigspot1 = "Teehee Valley East Room Digspot 1"
|
||||
TeeheeValleyEastRoomDigspot2 = "Teehee Valley East Room Digspot 2"
|
||||
TeeheeValleyEastRoomDigspot3 = "Teehee Valley East Room Digspot 3"
|
||||
TeeheeValleySoloMarioRoomDigspot1 = "Teehee Valley Solo Mario Room Digspot 1"
|
||||
TeeheeValleySoloMarioRoomDigspot2 = "Teehee Valley Solo Mario Room Digspot 2"
|
||||
TeeheeValleySoloMarioRoomDigspot3 = "Teehee Valley Solo Mario Room Digspot 3"
|
||||
TeeheeValleySoloMarioRoomDigspot4 = "Teehee Valley Solo Mario Room Digspot 4"
|
||||
TeeheeValleyPastUltraHammersBlock1 = "Teehee Valley Past Ultra Hammer Rock Block 1"
|
||||
TeeheeValleyPastUltraHammersBlock2 = "Teehee Valley Past Ultra Hammer Rock Block 2"
|
||||
TeeheeValleyPastUltraHammersDigspot1 = "Teehee Valley Past Ultra Hammer Rock Digspot 1"
|
||||
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"
|
||||
TeeheeValleyBeforeTrunkleDigspot = "Teehee Valley Before Trunkle Digspot"
|
||||
TeeheeValleyTrunkleRoomDigspot = "Teehee Valley Trunkle Room Digspot"
|
||||
SSChuckolaStorageRoomBlock1 = "S.S. Chuckola Storage Room Block 1"
|
||||
SSChuckolaStorageRoomBlock2 = "S.S. Chuckola Storage Room Block 2"
|
||||
LittleFungitownEmbassyRoomBlock = "Little Fungitown Embassy Room Block"
|
||||
LittleFungitownEntranceRoomBlock = "Little Fungitown Entrance Room Block"
|
||||
JokesEndPipeDigspot = "Joke's End Pipe Digspot"
|
||||
JokesEndStaircaseDigspot = "Joke's End Staircase Digspot"
|
||||
JokesEndWestOfFirstBoilerRoomBlock1 = "Joke's End West Of First Boiler Room Block 1"
|
||||
JokesEndWestOfFirstBoilerRoomBlock2 = "Joke's End West Of First Boiler Room Block 2"
|
||||
JokesEndFirstBoilerRoomDigspot1 = "Joke's End First Boiler Room Digspot 1"
|
||||
JokesEndFirstBoilerRoomDigspot2 = "Joke's End First Boiler Room Digspot 2"
|
||||
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"
|
||||
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"
|
||||
JokesEndSecondFloorWestRoomBlock4 = "Joke's End Second Floor West Room Block 4"
|
||||
JokesEndSecondFloorEastRoomDigspot = "Joke's End Second Floor East Room Digspot"
|
||||
JokesEndFinalSplitUpRoomDigspot = "Joke's End Final Split Up Room Digspot"
|
||||
JokesEndSouthOfBridgeRoomBlock = "Joke's End South Of Bridge Room Block"
|
||||
JokesEndSoloLuigiRoom1Block = "Joke's End Solo Luigi Room 1 Block"
|
||||
JokesEndSoloLuigiRoom1Digspot = "Joke's End Solo Luigi Room 1 Digspot"
|
||||
JokesEndSoloMarioFinalRoomBlock1 = "Joke's End Solo Mario Final Room Block 1"
|
||||
JokesEndSoloMarioFinalRoomBlock2 = "Joke's End Solo Mario Final Room Block 2"
|
||||
JokesEndSoloMarioFinalRoomBlock3 = "Joke's End Solo Mario Final Room Block 3"
|
||||
JokesEndSoloLuigiRoom2Digspot = "Joke's End Solo Luigi Room 2 Digspot"
|
||||
JokesEndSoloMarioRoom1Digspot = "Joke's End Solo Mario Room 1 Digspot"
|
||||
JokesEndSoloMarioRoom2Block1 = "Joke's End Solo Mario Room 2 Block 1"
|
||||
JokesEndSoloMarioRoom2Block2 = "Joke's End Solo Mario Room 2 Block 2"
|
||||
JokesEndSoloMarioRoom2Block3 = "Joke's End Solo Mario Room 2 Block 3"
|
||||
JokesEndSecondBoilerRoomDigspot1 = "Joke's End Second Boiler Room Digspot 1"
|
||||
JokesEndSecondBoilerRoomDigspot2 = "Joke's End Second Boiler Room Digspot 2"
|
||||
JokesEndNorthOfSecondBoilerRoomBlock1 = "Joke's End North Of Second Boiler Room Block 1"
|
||||
JokesEndNorthOfSecondBoilerRoomBlock2 = "Joke's End North Of Second Boiler Room Block 2"
|
||||
WinkleAreaColloseumDigspot = "Winkle Area Colloseum Digspot"
|
||||
HoohooMountainFountainRoom2Block = "Hoohoo Mountain Fountain Room 2 Block"
|
||||
HoohooMountainFountainRoom2Digspot = "Hoohoo Mountain Fountain Room 2 Digspot"
|
||||
HoohooMountainPastHoohoorosConnectorRoomDigspot1 = "Hoohoo Mountain Past Hoohooros Connector Room Digspot 1"
|
||||
HoohooMountainPastHoohoorosConnectorRoomBlock = "Hoohoo Mountain Past Hoohooros Connector Room Block"
|
||||
HoohooMountainPastHoohoorosConnectorRoomDigspot2 = "Hoohoo Mountain Past Hoohooros Connector Room Digspot 2"
|
||||
JokesEndBeforeJojoraRoomBlock1 = "Joke's End Before Jojora Room Block 1"
|
||||
JokesEndBeforeJojoraRoomBlock2 = "Joke's End Before Jojora Room Block 2"
|
||||
JokesEndBeforeJojoraRoomDigspot = "Joke's End Before Jojora Room Digspot"
|
||||
JokesEndJojoraRoomDigspot = "Joke's End Jojora Room Digspot"
|
||||
BeanbeanOutskirtsBeforeHarhallDigspot1 = "Beanbean Outskirts Before Harhall Digspot 1"
|
||||
BeanbeanOutskirtsBeforeHarhallDigspot2 = "Beanbean Outskirts Before Harhall Digspot 2"
|
||||
BeanbeanOutskirtsBroochGuardsRoomDigspot1 = "Beanbean Outskirts Brooch Guards Room Digspot 1"
|
||||
BeanbeanOutskirtsBroochGuardsRoomDigspot2 = "Beanbean Outskirts Brooch Guards Room Digspot 2"
|
||||
BeanbeanOutskirtsChateauEntranceDigspot1 = "Beanbean Outskirts Chateau Entrance Digspot 1"
|
||||
BeanbeanOutskirtsChateauEntranceDigspot2 = "Beanbean Outskirts Chateau Entrance Digspot 2"
|
||||
BeanbeanOutskirtsSouthOfHooniversityGuardsDigspot1 = "Beanbean Outskirts South of Hooniversity Guards Digspot 1"
|
||||
BeanbeanOutskirtsSouthOfHooniversityGuardsDigspot2 = "Beanbean Outskirts South of Hooniversity Guards Digspot 2"
|
||||
BeanbeanOutskirtsSouthOfHooniversityGuardsDigspot3 = "Beanbean Outskirts South of Hooniversity Guards Digspot 3"
|
||||
OutsideWoohooHooniversityBlock = "Outside Woohoo Hooniversity Block"
|
||||
BeanbeanOutskirtsEntranceToHoohooMountainBaseDigspot1 = (
|
||||
"Beanbean Outskirts Entrance to Hoohoo Mountain Base Digspot 1"
|
||||
)
|
||||
BeanbeanOutskirtsEntranceToHoohooMountainBaseDigspot2 = (
|
||||
"Beanbean Outskirts Entrance to Hoohoo Mountain Base Digspot 2"
|
||||
)
|
||||
WoohooHooniversitySoloMarioBarrelAreaBlock1 = "Woohoo Hooniversity Solo Mario Barrel Area Block 1"
|
||||
WoohooHooniversitySoloMarioBarrelAreaBlock2 = "Woohoo Hooniversity Solo Mario Barrel Area Block 2"
|
||||
WoohooHooniversitySoloMarioBarrelAreaBlock3 = "Woohoo Hooniversity Solo Mario Barrel Area Block 3"
|
||||
BeanbeanOutskirtsPipe2RoomDigspot = "Beanbean Outskirts Pipe 2 Room Digspot"
|
||||
BeanbeanOutskirtsPipe4RoomDigspot = "Beanbean Outskirts Pipe 4 Room Digspot"
|
||||
BeanbeanCastleTownBeanletReward = "Beanbean Castle Town Beanlet Reward"
|
||||
HoohooVillageMoleBehindTurtle = "Hoohoo Village Mole Behind Turtle"
|
||||
HoohooMountainBaseMoleNearTeeheeValley = "Hoohoo Mountain Base Mole Near Teehee Valley"
|
||||
BeanbeanOutskirtsSoloLuigiCaveMole = "Beanbean Outskirts Solo Luigi Cave Mole"
|
||||
BeanbeanOutskirtsFarmRoomMoleReward1 = "Beanbean Outskirts Farm Room Mole Reward 1"
|
||||
BeanbeanOutskirtsFarmRoomMoleReward2 = "Beanbean Outskirts Farm Room Mole Reward 2"
|
||||
JokesEndMoleReward1 = "Joke's End Mole Reward 1"
|
||||
JokesEndMoleReward2 = "Joke's End Mole Reward 2"
|
||||
NorthOceanWhirlpoolMole = "North Ocean Whirlpool Mole"
|
||||
BeanbeanOutskirtsNESoloMarioMole1 = "Beanbean Outskirts NE Solo Mario Mole 1"
|
||||
HoohooVillageHammers = "Hoohoo Village Hammers"
|
||||
BeanbeanOutskirtsSuperHammerUpgrade = "Beanbean Outskirts Super Hammer Upgrade"
|
||||
BeanbeanOutskirtsUltraHammerUpgrade = "Beanbean Outskirts Ultra Hammer Upgrade"
|
||||
OhoOasisFirebrand = "Oho Oasis Firebrand"
|
||||
OhoOasisThunderhand = "Oho Oasis Thunderhand"
|
||||
ChucklehuckWoodsRedChuckolaFruit = "Chucklehuck Woods Red Chuckola Fruit"
|
||||
ChucklehuckWoodsWhiteChuckolaFruit = "Chucklehuck Woods White Chuckola Fruit"
|
||||
ChucklehuckWoodsPurpleChuckolaFruit = "Chucklehuck Woods Purple Chuckola Fruit"
|
||||
SSChuckolaMembershipCard = "S.S. Chuckola Membership Card"
|
||||
WinkleAreaWinkleCard = "Winkle Area Winkle Card"
|
||||
BeanbeanCastlePeachsExtraDress = "Beanbean Castle Peach's Extra Dress"
|
||||
BeanbeanCastleFakeBeastar = "Beanbean Castle Fake Beanstar"
|
||||
BeanbeanCastleTownBeanlet1 = "Beanbean Castle Town Beanlet 1"
|
||||
BeanbeanCastleTownBeanlet2 = "Beanbean Castle Town Beanlet 2"
|
||||
BeanbeanCastleTownBeanlet3 = "Beanbean Castle Town Beanlet 3"
|
||||
BeanbeanCastleTownBeanlet4 = "Beanbean Castle Town Beanlet 4"
|
||||
BeanbeanCastleTownBeanlet5 = "Beanbean Castle Town Beanlet 5"
|
||||
BeanbeanCastleTownBeanstone1 = "Beanbean Castle Town Beanstone 1"
|
||||
BeanbeanCastleTownBeanstone2 = "Beanbean Castle Town Beanstone 2"
|
||||
BeanbeanCastleTownBeanstone3 = "Beanbean Castle Town Beanstone 3"
|
||||
BeanbeanCastleTownBeanstone4 = "Beanbean Castle Town Beanstone 4"
|
||||
BeanbeanCastleTownBeanstone5 = "Beanbean Castle Town Beanstone 5"
|
||||
BeanbeanCastleTownBeanstone6 = "Beanbean Castle Town Beanstone 6"
|
||||
BeanbeanCastleTownBeanstone7 = "Beanbean Castle Town Beanstone 7"
|
||||
BeanbeanCastleTownBeanstone8 = "Beanbean Castle Town Beanstone 8"
|
||||
BeanbeanCastleTownBeanstone9 = "Beanbean Castle Town Beanstone 9"
|
||||
BeanbeanCastleTownBeanstone10 = "Beanbean Castle Town Beanstone 10"
|
||||
YoshiTheaterBlueYoshi = "Yoshi Theater Blue Yoshi"
|
||||
YoshiTheaterRedYoshi = "Yoshi Theater Red Yoshi"
|
||||
YoshiTheaterGreenYoshi = "Yoshi Theater Green Yoshi"
|
||||
YoshiTheaterYellowYoshi = "Yoshi Theater Yellow Yoshi"
|
||||
YoshiTheaterPurpleYoshi = "Yoshi Theater Purple Yoshi"
|
||||
YoshiTheaterOrangeYoshi = "Yoshi Theater Orange Yoshi"
|
||||
YoshiTheaterAzureYoshi = "Yoshi Theater Azure Yoshi"
|
||||
BeanbeanCastleBeanbeanBrooch = "Beanbean Castle Beanbean Brooch"
|
||||
BeanbeanOutskirtsSecretScroll1 = "Beanbean Outskirts Secret Scroll 1"
|
||||
BeanbeanOutskirtsSecretScroll2 = "Beanbean Outskirts Secret Scroll 2"
|
||||
BeanbeanOutskirtsBeanFruit1 = "Beanbean Outskirts Bean Fruit 1"
|
||||
BeanbeanOutskirtsBeanFruit2 = "Beanbean Outskirts Bean Fruit 2"
|
||||
BeanbeanOutskirtsBeanFruit3 = "Beanbean Outskirts Bean Fruit 3"
|
||||
BeanbeanOutskirtsBeanFruit4 = "Beanbean Outskirts Bean Fruit 4"
|
||||
BeanbeanOutskirtsBeanFruit5 = "Beanbean Outskirts Bean Fruit 5"
|
||||
BeanbeanOutskirtsBeanFruit6 = "Beanbean Outskirts Bean Fruit 6"
|
||||
BeanbeanOutskirtsBeanFruit7 = "Beanbean Outskirts Bean Fruit 7"
|
||||
HoohooMountainPeasleysRose = "Hoohoo Mountain Peasley's Rose"
|
||||
ChateauGreenGoblet = "Chateau Green Goblet"
|
||||
ChateauRedGoblet = "Chateau Red Goblet"
|
||||
GwarharLagoonRedPearlBean = "Gwarhar Lagoon Red Pearl Bean"
|
||||
GwarharLagoonGreenPearlBean = "Gwarhar Lagoon Green Pearl Bean"
|
||||
GwarharLagoonSpangle = "Gwarhar Lagoon Spangle"
|
||||
BeanstarPieceWinkleArea = "Beanstar Piece Winkle Area"
|
||||
BeanstarPieceHarhall = "Beanstar Piece Harhall"
|
||||
BeanstarPieceYoshiTheater = "Beanstar Piece Yoshi Theater"
|
||||
BeanstarPieceHermie = "Beanstar Piece Hermie"
|
||||
ShopStartingFlag1 = "Shop Starting Flag 1"
|
||||
ShopStartingFlag2 = "Shop Starting Flag 2"
|
||||
ShopStartingFlag3 = "Shop Starting Flag 3"
|
||||
ShopChuckolatorFlag = "Shop Chuckolator Flag"
|
||||
ShopMomPiranhaFlag1 = "Shop Mom Piranha Flag 1"
|
||||
ShopMomPiranhaFlag2 = "Shop Mom Piranha Flag 2"
|
||||
ShopMomPiranhaFlag3 = "Shop Mom Piranha Flag 3"
|
||||
ShopMomPiranhaFlag4 = "Shop Mom Piranha Flag 4"
|
||||
ShopPeachKidnappedFlag1 = "Shop Enter Fungitown Flag 1"
|
||||
ShopPeachKidnappedFlag2 = "Shop Enter Fungitown Flag 2"
|
||||
FungitownShopStartingFlag1 = "Fungitown Shop Starting Flag 1"
|
||||
FungitownShopStartingFlag2 = "Fungitown Shop Starting Flag 2"
|
||||
FungitownShopStartingFlag3 = "Fungitown Shop Starting Flag 3"
|
||||
FungitownShopStartingFlag4 = "Fungitown Shop Starting Flag 4"
|
||||
FungitownShopStartingFlag5 = "Fungitown Shop Starting Flag 5"
|
||||
FungitownShopStartingFlag6 = "Fungitown Shop Starting Flag 6"
|
||||
FungitownShopStartingFlag7 = "Fungitown Shop Starting Flag 7"
|
||||
FungitownShopStartingFlag8 = "Fungitown Shop Starting Flag 8"
|
||||
ShopBeanstarCompleteFlag1 = "Shop Beanstar Complete Flag 1"
|
||||
ShopBeanstarCompleteFlag2 = "Shop Beanstar Complete Flag 2"
|
||||
ShopBeanstarCompleteFlag3 = "Shop Beanstar Complete Flag 3"
|
||||
FungitownShopBeanstarCompleteFlag = "Fungitown Shop Beanstar Complete Flag"
|
||||
ShopBirdoFlag = "Shop Birdo Flag"
|
||||
FungitownShopBirdoFlag = "Fungitown Shop Birdo Flag"
|
||||
CoffeeShopBrewReward1 = "Coffee Shop Brew Reward 1"
|
||||
CoffeeShopBrewReward2 = "Coffee Shop Brew Reward 2"
|
||||
CoffeeShopBrewReward3 = "Coffee Shop Brew Reward 3"
|
||||
CoffeeShopBrewReward4 = "Coffee Shop Brew Reward 4"
|
||||
CoffeeShopBrewReward5 = "Coffee Shop Brew Reward 5"
|
||||
CoffeeShopBrewReward6 = "Coffee Shop Brew Reward 6"
|
||||
CoffeeShopBrewReward7 = "Coffee Shop Brew Reward 7"
|
||||
CoffeeShopWoohooBlend = "Coffee Shop Woohoo Blend"
|
||||
CoffeeShopHoohooBlend = "Coffee Shop Hoohoo Blend"
|
||||
CoffeeShopChuckleBlend = "Coffee Shop Chuckle Blend"
|
||||
CoffeeShopTeeheeBlend = "Coffee Shop Teehee Blend"
|
||||
CoffeeShopHoolumbian = "Coffee Shop Hoolumbian"
|
||||
CoffeeShopChuckoccino = "Coffee Shop Chuckoccino"
|
||||
CoffeeShopTeeheespresso = "Coffee Shop Teeheespresso"
|
||||
PantsShopStartingFlag1 = "Pants Shop Starting Flag 1"
|
||||
PantsShopStartingFlag2 = "Pants Shop Starting Flag 2"
|
||||
PantsShopStartingFlag3 = "Pants Shop Starting Flag 3"
|
||||
PantsShopChuckolatorFlag1 = "Pants Shop Chuckolator Flag 1"
|
||||
PantsShopChuckolatorFlag2 = "Pants Shop Chuckolator Flag 2"
|
||||
PantsShopChuckolatorFlag3 = "Pants Shop Chuckolator Flag 3"
|
||||
PantsShopMomPiranhaFlag1 = "Pants Shop Mom Piranha Flag 1"
|
||||
PantsShopMomPiranhaFlag2 = "Pants Shop Mom Piranha Flag 2"
|
||||
PantsShopMomPiranhaFlag3 = "Pants Shop Mom Piranha Flag 3"
|
||||
PantsShopPeachKidnappedFlag1 = "Pants Shop Enter Fungitown Flag 1"
|
||||
PantsShopPeachKidnappedFlag2 = "Pants Shop Enter Fungitown Flag 2"
|
||||
PantsShopPeachKidnappedFlag3 = "Pants Shop Enter Fungitown Flag 3"
|
||||
PantsShopBeanstarCompleteFlag1 = "Pants Shop Beanstar Complete Flag 1"
|
||||
PantsShopBeanstarCompleteFlag2 = "Pants Shop Beanstar Complete Flag 2"
|
||||
PantsShopBeanstarCompleteFlag3 = "Pants Shop Beanstar Complete Flag 3"
|
||||
PantsShopBirdoFlag1 = "Pants Shop Birdo Flag 1"
|
||||
PantsShopBirdoFlag2 = "Pants Shop Birdo Flag 2"
|
||||
PantsShopBirdoFlag3 = "Pants Shop Birdo Flag 3"
|
||||
FungitownPantsShopStartingFlag1 = "Fungitown Pants Shop Starting Flag 1"
|
||||
FungitownPantsShopStartingFlag2 = "Fungitown Pants Shop Starting Flag 2"
|
||||
FungitownPantsShopStartingFlag3 = "Fungitown Pants Shop Starting Flag 3"
|
||||
FungitownPantsShopBeanstarCompleteFlag1 = "Fungitown Pants Shop Beanstar Complete Flag 1"
|
||||
FungitownPantsShopBeanstarCompleteFlag2 = "Fungitown Pants Shop Beanstar Complete Flag 2"
|
||||
FungitownPantsShopBirdoFlag1 = "Fungitown Pants Shop Birdo Flag 1"
|
||||
FungitownPantsShopBirdoFlag2 = "Fungitown Pants Shop Birdo Flag 2"
|
||||
BeanbeanOutskirtsNESoloMarioMole2 = "Beanbean Outskirts NE Solo Mario Mole 2"
|
||||
GwarharLagoonSpangleReward = "Gwarhar Lagoon Spangle Reward"
|
||||
BowsersCastleEntranceBlock1 = "Bowser's Castle Entrance Block 1"
|
||||
BowsersCastleEntranceBlock2 = "Bowser's Castle Entrance Block 2"
|
||||
BowsersCastleEntranceDigspot = "Bowser's Castle Entrance Digspot"
|
||||
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"
|
||||
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"
|
||||
BowsersCastleRoyCorridorBlock2 = "Bowser's Castle Roy Corridor Block 2"
|
||||
BowsersCastleWendyLarryHallwayDigspot = "Bowser's Castle Wendy & Larry Hallway Digspot"
|
||||
BowsersCastleBeforeFawfulFightBlock1 = "Bowser's Castle Before Fawful Fight Block 1"
|
||||
BowsersCastleBeforeFawfulFightBlock2 = "Bowser's Castle Before Fawful Fight Block 2"
|
||||
BowsersCastleGreatDoorBlock1 = "Bowser's Castle Great Door Block 1"
|
||||
BowsersCastleGreatDoorBlock2 = "Bowser's Castle Great Door Block 2"
|
||||
BowsersCastleMortonRoom1Digspot = "Bowser's Castle Morton Room 1 Digspot"
|
||||
BowsersCastleLemmyRoom1Block = "Bowser's Castle Lemmy Room 1 Block"
|
||||
BowsersCastleLemmyRoom1Digspot = "Bowser's Castle Lemmy Room 1 Digspot"
|
||||
BowsersCastleLudwigRoom1Block = "Bowser's Castle Ludwig Room 1 Block"
|
||||
BowsersCastleMiniMarioSidescrollerBlock1 = "Bowser's Castle Mini Mario Sidescroller Block 1"
|
||||
BowsersCastleMiniMarioSidescrollerBlock2 = "Bowser's Castle Mini Mario Sidescroller Block 2"
|
||||
BowsersCastleMiniMarioMazeBlock1 = "Bowser's Castle Mini Mario Maze Block 1"
|
||||
BowsersCastleMiniMarioMazeBlock2 = "Bowser's Castle Mini Mario Maze Block 2"
|
||||
BowsersCastleBeforeWendyFightBlock1 = "Bowser's Castle Before Wendy Fight Block 1"
|
||||
BowsersCastleBeforeWendyFightBlock2 = "Bowser's Castle Before Wendy Fight Block 2"
|
||||
BowsersCastleLarryRoomBlock = "Bowser's Castle Larry Room Block"
|
||||
BowsersCastleLemmyRoomMole = "Bowser's Castle Lemmy Room Mole"
|
||||
SurfMinigame = "Surf Minigame"
|
||||
BeanbeanOutskirtsThunderHandMole = "Beanbean Outskirts Thunderhand Mole"
|
||||
BadgeShopMomPiranhaFlag1 = "Badge Shop Mom Piranha Flag 1"
|
||||
BadgeShopMomPiranhaFlag2 = "Badge Shop Mom Piranha Flag 2"
|
||||
BadgeShopMomPiranhaFlag3 = "Badge Shop Mom Piranha Flag 3"
|
||||
HarhallsPants = "Harhall's Pants"
|
||||
HoohooMountainBaseBooStatueCaveCoinBlock1 = "Hoohoo Mountain Base Boo Statue Cave Coin Block 1"
|
||||
HoohooMountainBaseBooStatueCaveCoinBlock2 = "Hoohoo Mountain Base Boo Statue Cave Coin Block 2"
|
||||
HoohooMountainBaseBooStatueCaveCoinBlock3 = "Hoohoo Mountain Base Boo Statue Cave Coin Block 3"
|
||||
BeanbeanOutskirtsNWCoinBlock = "Beanbean Outskirts NW Coin Block"
|
||||
BeanbeanOutskirtsSRoom1CoinBlock = "Beanbean Outskirts S Room 1 Coin Block"
|
||||
BeanbeanOutskirtsSRoom2CoinBlock = "Beanbean Outskirts S Room 2 Coin Block"
|
||||
ChateauPoppleRoomCoinBlock1 = "Chateau Popple Room Coin Block 1"
|
||||
ChateauPoppleRoomCoinBlock2 = "Chateau Popple Room Coin Block 2"
|
||||
ChucklehuckWoodsCaveRoom1CoinBlock = "Chucklehuck Woods Cave Room 1 Coin Block"
|
||||
ChucklehuckWoodsCaveRoom2CoinBlock = "Chucklehuck Woods Cave Room 2 Coin Block"
|
||||
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"
|
||||
ChucklehuckWoodsKoopaRoomCoinBlock = "Chucklehuck Woods Koopa Room Coin Block"
|
||||
ChucklehuckWoodsWinkleAreaCaveCoinBlock = "Chucklehuck Woods Winkle Area Cave Coin Block"
|
||||
SewersPrisonRoomCoinBlock = "Sewers Prison Room Coin Block"
|
||||
TeeheeValleyPastUltraHammerRocksCoinBlock = "Teehee Valley Past Ultra Hammer Rocks Coin Block"
|
||||
SSChuckolaStorageRoomCoinBlock1 = "S.S. Chuckola Storage Room Coin Block 1"
|
||||
SSChuckolaStorageRoomCoinBlock2 = "S.S. Chuckola Storage Room Coin Block 2"
|
||||
GwarharLagoonFirstUnderwaterAreaRoom2CoinBlock = "Gwarhar Lagoon First Underwater Area Room 2 Coin Block"
|
||||
JokesEndSecondFloorWestRoomCoinBlock = "Joke's End Second Floor West Room Coin Block"
|
||||
JokesEndNorthofBridgeRoomCoinBlock = "Joke's End North of Bridge Room Coin Block"
|
||||
|
||||
299
worlds/mlss/Options.py
Normal file
299
worlds/mlss/Options.py
Normal file
@@ -0,0 +1,299 @@
|
||||
from Options import Choice, Toggle, StartInventoryPool, PerGameCommonOptions, Range
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
class BowsersCastleSkip(Toggle):
|
||||
"""
|
||||
Skip straight from the entrance hall to Bowletta in Bowser's Castle.
|
||||
All Bowser's Castle locations will be removed from the location pool.
|
||||
"""
|
||||
|
||||
display_name = "Bowser's Castle Skip"
|
||||
|
||||
|
||||
class ExtraPipes(Toggle):
|
||||
"""
|
||||
Gives the player access to pipes 1, 3, 4, and 6 from the start.
|
||||
"""
|
||||
|
||||
display_name = "Start With Extra Pipes"
|
||||
|
||||
|
||||
class SkipMinecart(Toggle):
|
||||
"""
|
||||
Skip the minecart minigame that leads you through Hoohoo Mountain Base.
|
||||
This will remove the 1 location in the minecart cave from the location pool.
|
||||
"""
|
||||
|
||||
display_name = "Skip Minecart Minigame"
|
||||
|
||||
|
||||
class DisableSurf(Toggle):
|
||||
"""
|
||||
Remove the surf minigame location from the location pool.
|
||||
"""
|
||||
|
||||
display_name = "Disable Surf Minigame"
|
||||
|
||||
|
||||
class MusicOptions(Choice):
|
||||
"""
|
||||
Choose if you want to randomize or disable music.
|
||||
default: Music will be untouched.
|
||||
randomize: Music will be randomized.
|
||||
disable: All music will be disabled. No music will play throughout the entire game.
|
||||
"""
|
||||
|
||||
display_name = "Music Options"
|
||||
option_default = 0
|
||||
option_randomize = 1
|
||||
option_disable = 2
|
||||
default = 0
|
||||
|
||||
|
||||
class RandomSounds(Toggle):
|
||||
"""
|
||||
Randomizes every sound in the game, minus a select few that can softlock the game.
|
||||
"""
|
||||
|
||||
display_name = "Randomize Sounds"
|
||||
|
||||
|
||||
class MarioColor(Choice):
|
||||
"""
|
||||
This changes the color of Mario's hat, as well as some key colors that are red including UI etc.
|
||||
"""
|
||||
|
||||
display_name = "Mario's Color"
|
||||
option_red = 0
|
||||
option_green = 1
|
||||
option_blue = 2
|
||||
option_cyan = 3
|
||||
option_yellow = 4
|
||||
option_orange = 5
|
||||
option_purple = 6
|
||||
option_pink = 7
|
||||
option_black = 8
|
||||
option_white = 9
|
||||
option_silhouette = 10
|
||||
option_chaos = 11
|
||||
option_true_chaos = 12
|
||||
default = 0
|
||||
|
||||
|
||||
class LuigiColor(Choice):
|
||||
"""
|
||||
This changes the color of Luigi's hat, as well as some key colors that are green including UI etc.
|
||||
"""
|
||||
|
||||
display_name = "Luigi's Color"
|
||||
option_red = 0
|
||||
option_green = 1
|
||||
option_blue = 2
|
||||
option_cyan = 3
|
||||
option_yellow = 4
|
||||
option_orange = 5
|
||||
option_purple = 6
|
||||
option_pink = 7
|
||||
option_black = 8
|
||||
option_white = 9
|
||||
option_silhouette = 10
|
||||
option_chaos = 11
|
||||
option_true_chaos = 12
|
||||
default = 1
|
||||
|
||||
|
||||
class MarioPants(Choice):
|
||||
"""
|
||||
This changes the color of Mario's trousers.
|
||||
"""
|
||||
|
||||
display_name = "Mario's Pants Color"
|
||||
option_vanilla = 0
|
||||
option_red = 1
|
||||
option_green = 2
|
||||
option_blue = 3
|
||||
option_cyan = 4
|
||||
option_yellow = 5
|
||||
option_orange = 6
|
||||
option_purple = 7
|
||||
option_pink = 8
|
||||
option_black = 9
|
||||
option_white = 10
|
||||
option_chaos = 11
|
||||
default = 0
|
||||
|
||||
|
||||
class LuigiPants(Choice):
|
||||
"""
|
||||
This changes the color of Luigi's trousers.
|
||||
"""
|
||||
|
||||
display_name = "Luigi's Pants Color"
|
||||
option_vanilla = 0
|
||||
option_red = 1
|
||||
option_green = 2
|
||||
option_blue = 3
|
||||
option_cyan = 4
|
||||
option_yellow = 5
|
||||
option_orange = 6
|
||||
option_purple = 7
|
||||
option_pink = 8
|
||||
option_black = 9
|
||||
option_white = 10
|
||||
option_chaos = 11
|
||||
default = 0
|
||||
|
||||
|
||||
class RandomizeEnemies(Choice):
|
||||
"""
|
||||
Randomize all normal enemy encounters in the game.
|
||||
If Bowser's castle skip is enabled, then enemies from Bowser's Castle will not be included.
|
||||
Disabled: Enemies will not be randomized.
|
||||
Vanilla Groups: Vanilla enemy groups will be shuffled with each other. Custom enemy groups will not be made.
|
||||
Custom Groups: Custom enemy groups will be made and shuffled. Some enemy groups will only be semi-random,
|
||||
including groups with flying enemies or pestnuts in them.
|
||||
"""
|
||||
|
||||
display_name = "Randomize Enemies"
|
||||
option_disabled = 0
|
||||
option_vanilla_groups = 1
|
||||
option_custom_groups = 2
|
||||
default = 0
|
||||
|
||||
|
||||
class RandomizeBosses(Choice):
|
||||
"""
|
||||
Randomize all boss encounters in the game.
|
||||
If Bowser's castle skip is enabled then bosses from Bowser's Castle will not be included.
|
||||
Some bosses are not randomized due to flags, and story (such as the final boss).
|
||||
Boss Only: Bosses will only be swapped with another boss.
|
||||
Boss Normal: Bosses can be swapped with normal enemy encounters.
|
||||
"""
|
||||
|
||||
display_name = "Randomize Bosses"
|
||||
option_disabled = 0
|
||||
option_boss_only = 1
|
||||
option_boss_normal = 2
|
||||
default = 0
|
||||
|
||||
|
||||
class ScaleStats(Toggle):
|
||||
"""
|
||||
This scales enemy HP, POW, DEF, and XP to vanilla values.
|
||||
This setting is intended for use with the Enemy Randomizer and is Recommended to turn on.
|
||||
If you are not using the Enemy Randomizer the effects will be minimal.
|
||||
"""
|
||||
|
||||
display_name = "Scale Enemy Stats"
|
||||
|
||||
|
||||
class XPMultiplier(Range):
|
||||
"""
|
||||
This will multiply any XP you receive in battle by the chosen multiplier.
|
||||
"""
|
||||
|
||||
display_name = "XP Multiplier"
|
||||
range_start = 0
|
||||
range_end = 4
|
||||
default = 1
|
||||
|
||||
|
||||
class TattleHp(Toggle):
|
||||
"""
|
||||
This will display the enemies' current and max health while in battle.
|
||||
"""
|
||||
|
||||
display_name = "Tattle HP"
|
||||
|
||||
|
||||
class RandomizeBackgrounds(Toggle):
|
||||
"""
|
||||
This randomizes the background image in battles.
|
||||
"""
|
||||
|
||||
display_name = "Randomize Battle Backgrounds"
|
||||
|
||||
|
||||
class HiddenVisible(Choice):
|
||||
"""
|
||||
This makes any hidden blocks in the game into regular item blocks and vice versa.
|
||||
Disabled: Hidden blocks will remain invisible.
|
||||
Hidden Visible: Hidden blocks will turn visible to the player.
|
||||
Blocks Invisible: All item blocks will turn invisible. Hidden blocks will also remain invisible.
|
||||
"""
|
||||
|
||||
display_name = "Item Block Visibility"
|
||||
option_disabled = 0
|
||||
option_hidden_visible = 1
|
||||
option_blocks_invisible = 2
|
||||
default = 0
|
||||
|
||||
|
||||
class Coins(Toggle):
|
||||
"""
|
||||
Add all coin blocks in the game to the location pool.
|
||||
"""
|
||||
|
||||
display_name = "Coin Blocks"
|
||||
|
||||
|
||||
class HarhallsPants(Toggle):
|
||||
"""
|
||||
This will remove the Harhall's Pants check from the pool.
|
||||
"""
|
||||
|
||||
display_name = "Remove Harhall's Pants"
|
||||
|
||||
|
||||
class DifficultLogic(Toggle):
|
||||
"""
|
||||
This adjusts the logic to be more difficult in a few areas,
|
||||
allowing for the logic to account for players getting to certain areas in unintended ways.
|
||||
Enable at your own risk, this is not an option made for beginners.
|
||||
"""
|
||||
|
||||
display_name = "Difficult Logic"
|
||||
|
||||
|
||||
class ChuckleBeans(Choice):
|
||||
"""
|
||||
Choose how you want chuckle bean digspots to be randomized.
|
||||
An amount of chuckle beans will be removed from the item pool,
|
||||
equal to the amount of locations removed by the setting that you choose.
|
||||
None: No chuckle bean digspots will be added into the location pool.
|
||||
Only Visible: Only chuckle bean digspots clearly marked with an X will be added into the location pool.
|
||||
All: All chuckle bean digspots will be added into the location pool.
|
||||
"""
|
||||
|
||||
display_name = "Chuckle Beans"
|
||||
option_none = 0
|
||||
option_only_visible = 1
|
||||
option_all = 2
|
||||
default = 2
|
||||
|
||||
|
||||
@dataclass
|
||||
class MLSSOptions(PerGameCommonOptions):
|
||||
start_inventory_from_pool: StartInventoryPool
|
||||
coins: Coins
|
||||
difficult_logic: DifficultLogic
|
||||
castle_skip: BowsersCastleSkip
|
||||
extra_pipes: ExtraPipes
|
||||
skip_minecart: SkipMinecart
|
||||
disable_surf: DisableSurf
|
||||
harhalls_pants: HarhallsPants
|
||||
block_visibility: HiddenVisible
|
||||
chuckle_beans: ChuckleBeans
|
||||
music_options: MusicOptions
|
||||
randomize_sounds: RandomSounds
|
||||
randomize_enemies: RandomizeEnemies
|
||||
randomize_bosses: RandomizeBosses
|
||||
randomize_backgrounds: RandomizeBackgrounds
|
||||
scale_stats: ScaleStats
|
||||
xp_multiplier: XPMultiplier
|
||||
tattle_hp: TattleHp
|
||||
mario_color: MarioColor
|
||||
luigi_color: LuigiColor
|
||||
mario_pants: MarioPants
|
||||
luigi_pants: LuigiPants
|
||||
323
worlds/mlss/Regions.py
Normal file
323
worlds/mlss/Regions.py
Normal file
@@ -0,0 +1,323 @@
|
||||
import typing
|
||||
|
||||
from BaseClasses import Region, Entrance
|
||||
from .Locations import (
|
||||
MLSSLocation,
|
||||
mainArea,
|
||||
chucklehuck,
|
||||
castleTown,
|
||||
startingFlag,
|
||||
chuckolatorFlag,
|
||||
piranhaFlag,
|
||||
kidnappedFlag,
|
||||
beanstarFlag,
|
||||
birdoFlag,
|
||||
surfable,
|
||||
hooniversity,
|
||||
gwarharEntrance,
|
||||
gwarharMain,
|
||||
fungitown,
|
||||
fungitownBeanstar,
|
||||
fungitownBirdo,
|
||||
teeheeValley,
|
||||
winkle,
|
||||
sewers,
|
||||
airport,
|
||||
bowsers,
|
||||
bowsersMini,
|
||||
jokesEntrance,
|
||||
jokesMain,
|
||||
theater,
|
||||
booStatue,
|
||||
oasis,
|
||||
postJokes,
|
||||
baseUltraRocks,
|
||||
coins,
|
||||
)
|
||||
from . import StateLogic
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import MLSSWorld
|
||||
|
||||
|
||||
def create_regions(world: "MLSSWorld", excluded: typing.List[str]):
|
||||
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)
|
||||
|
||||
if world.options.coins:
|
||||
create_region(world, "Coins", coins, excluded)
|
||||
|
||||
if not world.options.castle_skip:
|
||||
create_region(world, "Bowser's Castle", bowsers, excluded)
|
||||
create_region(world, "Bowser's Castle Mini", bowsersMini, excluded)
|
||||
|
||||
|
||||
def connect_regions(world: "MLSSWorld"):
|
||||
names: typing.Dict[str, int] = {}
|
||||
|
||||
connect(world, names, "Menu", "Main Area")
|
||||
if world.options.coins:
|
||||
connect(world, names, "Main Area", "Coins")
|
||||
connect(world, names, "Main Area", "BaseUltraRocks", lambda state: StateLogic.ultra(state, world.player))
|
||||
connect(world, names, "Main Area", "Chucklehuck Woods", lambda state: StateLogic.brooch(state, world.player))
|
||||
connect(world, names, "Main Area", "BooStatue", lambda state: StateLogic.canCrash(state, world.player))
|
||||
connect(
|
||||
world,
|
||||
names,
|
||||
"Main Area",
|
||||
"Hooniversity",
|
||||
lambda state: StateLogic.canDig(state, world.player) and StateLogic.canMini(state, world.player),
|
||||
)
|
||||
connect(world, names, "Hooniversity", "Oasis")
|
||||
connect(
|
||||
world,
|
||||
names,
|
||||
"Main Area",
|
||||
"TeeheeValley",
|
||||
lambda state: StateLogic.super(state, world.player) or StateLogic.canDash(state, world.player),
|
||||
)
|
||||
connect(
|
||||
world,
|
||||
names,
|
||||
"TeeheeValley",
|
||||
"GwarharEntrance",
|
||||
lambda state: StateLogic.membership(state, world.player) and StateLogic.fire(state, world.player),
|
||||
)
|
||||
connect(
|
||||
world,
|
||||
names,
|
||||
"TeeheeValley",
|
||||
"Oasis",
|
||||
lambda state: StateLogic.membership(state, world.player) and StateLogic.fire(state, world.player),
|
||||
)
|
||||
connect(
|
||||
world,
|
||||
names,
|
||||
"TeeheeValley",
|
||||
"Fungitown",
|
||||
lambda state: StateLogic.thunder(state, world.player)
|
||||
and StateLogic.castleTown(state, world.player)
|
||||
and StateLogic.rose(state, world.player),
|
||||
)
|
||||
connection = connect(
|
||||
world,
|
||||
names,
|
||||
"Fungitown",
|
||||
"Fungitown Shop Beanstar Complete Flag",
|
||||
lambda state: StateLogic.pieces(state, world.player) or StateLogic.fungitown_birdo_shop(state, world.player),
|
||||
True,
|
||||
)
|
||||
world.multiworld.register_indirect_condition(world.get_region("Fungitown Shop Birdo Flag"), connection)
|
||||
connect(world, names, "Main Area", "Shop Starting Flag")
|
||||
connection = connect(
|
||||
world,
|
||||
names,
|
||||
"Shop Starting Flag",
|
||||
"Shop Chuckolator Flag",
|
||||
lambda state: (
|
||||
StateLogic.brooch(state, world.player)
|
||||
and StateLogic.fruits(state, world.player)
|
||||
and (
|
||||
StateLogic.thunder(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.hammers(state, world.player)
|
||||
)
|
||||
)
|
||||
or (
|
||||
StateLogic.piranha_shop(state, world.player)
|
||||
or StateLogic.fungitown_shop(state, world.player)
|
||||
or StateLogic.star_shop(state, world.player)
|
||||
or StateLogic.birdo_shop(state, world.player)
|
||||
),
|
||||
True,
|
||||
)
|
||||
world.multiworld.register_indirect_condition(world.get_region("Shop Mom Piranha Flag"), connection)
|
||||
world.multiworld.register_indirect_condition(world.get_region("Shop Enter Fungitown Flag"), connection)
|
||||
world.multiworld.register_indirect_condition(world.get_region("Shop Beanstar Complete Flag"), connection)
|
||||
world.multiworld.register_indirect_condition(world.get_region("Shop Birdo Flag"), connection)
|
||||
connection = connect(
|
||||
world,
|
||||
names,
|
||||
"Shop Starting Flag",
|
||||
"Shop Mom Piranha Flag",
|
||||
lambda state: StateLogic.thunder(state, world.player)
|
||||
or (
|
||||
StateLogic.fungitown_shop(state, world.player)
|
||||
or StateLogic.star_shop(state, world.player)
|
||||
or StateLogic.birdo_shop(state, world.player)
|
||||
),
|
||||
True,
|
||||
)
|
||||
world.multiworld.register_indirect_condition(world.get_region("Shop Enter Fungitown Flag"), connection)
|
||||
world.multiworld.register_indirect_condition(world.get_region("Shop Beanstar Complete Flag"), connection)
|
||||
world.multiworld.register_indirect_condition(world.get_region("Shop Birdo Flag"), connection)
|
||||
connection = connect(
|
||||
world,
|
||||
names,
|
||||
"Shop Starting Flag",
|
||||
"Shop Enter Fungitown Flag",
|
||||
lambda state: StateLogic.fungitown(state, world.player)
|
||||
or (StateLogic.star_shop(state, world.player) or StateLogic.birdo_shop(state, world.player)),
|
||||
True,
|
||||
)
|
||||
world.multiworld.register_indirect_condition(world.get_region("Shop Beanstar Complete Flag"), connection)
|
||||
world.multiworld.register_indirect_condition(world.get_region("Shop Birdo Flag"), connection)
|
||||
connection = connect(
|
||||
world,
|
||||
names,
|
||||
"Shop Starting Flag",
|
||||
"Shop Beanstar Complete Flag",
|
||||
lambda state: (
|
||||
StateLogic.castleTown(state, world.player)
|
||||
and StateLogic.pieces(state, world.player)
|
||||
and StateLogic.rose(state, world.player)
|
||||
)
|
||||
or StateLogic.birdo_shop(state, world.player),
|
||||
True,
|
||||
)
|
||||
world.multiworld.register_indirect_condition(world.get_region("Shop Birdo Flag"), connection)
|
||||
connect(world, names, "Main Area", "Sewers", lambda state: StateLogic.rose(state, world.player))
|
||||
connect(world, names, "Main Area", "Airport", lambda state: StateLogic.thunder(state, world.player))
|
||||
connect(world, names, "Main Area", "Theater", lambda state: StateLogic.canDash(state, world.player))
|
||||
connect(world, names, "Main Area", "Surfable", lambda state: StateLogic.surfable(state, world.player))
|
||||
connect(world, names, "Surfable", "GwarharEntrance")
|
||||
connect(world, names, "Surfable", "Oasis")
|
||||
connect(world, names, "Surfable", "JokesEntrance", lambda state: StateLogic.fire(state, world.player))
|
||||
connect(world, names, "JokesMain", "PostJokes", lambda state: StateLogic.postJokes(state, world.player))
|
||||
if not world.options.castle_skip:
|
||||
connect(world, names, "PostJokes", "Bowser's Castle")
|
||||
connect(
|
||||
world,
|
||||
names,
|
||||
"Bowser's Castle",
|
||||
"Bowser's Castle Mini",
|
||||
lambda state: StateLogic.canMini(state, world.player) and StateLogic.thunder(state, world.player),
|
||||
)
|
||||
connect(world, names, "Chucklehuck Woods", "Winkle", lambda state: StateLogic.canDash(state, world.player))
|
||||
connect(
|
||||
world,
|
||||
names,
|
||||
"Chucklehuck Woods",
|
||||
"Beanbean Castle Town",
|
||||
lambda state: StateLogic.fruits(state, world.player)
|
||||
and (
|
||||
StateLogic.hammers(state, world.player)
|
||||
or StateLogic.fire(state, world.player)
|
||||
or StateLogic.thunder(state, world.player)
|
||||
),
|
||||
)
|
||||
if world.options.difficult_logic:
|
||||
connect(world, names, "GwarharEntrance", "GwarharMain", lambda state: StateLogic.canDash(state, world.player))
|
||||
connect(world, names, "JokesEntrance", "JokesMain", lambda state: StateLogic.canDig(state, world.player))
|
||||
connect(
|
||||
world,
|
||||
names,
|
||||
"Shop Starting Flag",
|
||||
"Shop Birdo Flag",
|
||||
lambda state: StateLogic.postJokes(state, world.player),
|
||||
)
|
||||
connect(
|
||||
world,
|
||||
names,
|
||||
"Fungitown",
|
||||
"Fungitown Shop Birdo Flag",
|
||||
lambda state: StateLogic.postJokes(state, world.player),
|
||||
)
|
||||
else:
|
||||
connect(
|
||||
world,
|
||||
names,
|
||||
"GwarharEntrance",
|
||||
"GwarharMain",
|
||||
lambda state: StateLogic.canDash(state, world.player) and StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
connect(
|
||||
world,
|
||||
names,
|
||||
"JokesEntrance",
|
||||
"JokesMain",
|
||||
lambda state: StateLogic.canCrash(state, world.player) and StateLogic.canDig(state, world.player),
|
||||
)
|
||||
connect(
|
||||
world,
|
||||
names,
|
||||
"Shop Starting Flag",
|
||||
"Shop Birdo Flag",
|
||||
lambda state: StateLogic.canCrash(state, world.player) and StateLogic.postJokes(state, world.player),
|
||||
)
|
||||
connect(
|
||||
world,
|
||||
names,
|
||||
"Fungitown",
|
||||
"Fungitown Shop Birdo Flag",
|
||||
lambda state: StateLogic.canCrash(state, world.player) and StateLogic.postJokes(state, world.player),
|
||||
)
|
||||
|
||||
|
||||
def create_region(world: "MLSSWorld", name, locations, excluded):
|
||||
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:
|
||||
continue
|
||||
ret.locations.append(loc)
|
||||
world.multiworld.regions.append(ret)
|
||||
|
||||
|
||||
def connect(
|
||||
world: "MLSSWorld",
|
||||
used_names: typing.Dict[str, int],
|
||||
source: str,
|
||||
target: str,
|
||||
rule: typing.Optional[typing.Callable] = None,
|
||||
reach: typing.Optional[bool] = False,
|
||||
) -> Entrance | None:
|
||||
source_region = world.multiworld.get_region(source, world.player)
|
||||
target_region = world.multiworld.get_region(target, world.player)
|
||||
|
||||
if target not in used_names:
|
||||
used_names[target] = 1
|
||||
name = target
|
||||
else:
|
||||
used_names[target] += 1
|
||||
name = target + (" " * used_names[target])
|
||||
|
||||
connection = Entrance(world.player, name, source_region)
|
||||
|
||||
if rule:
|
||||
connection.access_rule = rule
|
||||
|
||||
source_region.exits.append(connection)
|
||||
connection.connect(target_region)
|
||||
if reach:
|
||||
return connection
|
||||
else:
|
||||
return None
|
||||
437
worlds/mlss/Rom.py
Normal file
437
worlds/mlss/Rom.py
Normal file
@@ -0,0 +1,437 @@
|
||||
import io
|
||||
import json
|
||||
import random
|
||||
|
||||
from . import Data
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
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
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import MLSSWorld
|
||||
|
||||
colors = [
|
||||
Data.redHat,
|
||||
Data.greenHat,
|
||||
Data.blueHat,
|
||||
Data.azureHat,
|
||||
Data.yellowHat,
|
||||
Data.orangeHat,
|
||||
Data.purpleHat,
|
||||
Data.pinkHat,
|
||||
Data.blackHat,
|
||||
Data.whiteHat,
|
||||
Data.silhouetteHat,
|
||||
Data.chaosHat,
|
||||
Data.truechaosHat
|
||||
]
|
||||
|
||||
cpants = [
|
||||
Data.vanilla,
|
||||
Data.redPants,
|
||||
Data.greenPants,
|
||||
Data.bluePants,
|
||||
Data.azurePants,
|
||||
Data.yellowPants,
|
||||
Data.orangePants,
|
||||
Data.purplePants,
|
||||
Data.pinkPants,
|
||||
Data.blackPants,
|
||||
Data.whitePants,
|
||||
Data.chaosPants
|
||||
]
|
||||
|
||||
|
||||
def get_base_rom_as_bytes() -> bytes:
|
||||
with open(get_settings().mlss_options.rom_file, "rb") as infile:
|
||||
base_rom_bytes = bytes(infile.read())
|
||||
return base_rom_bytes
|
||||
|
||||
|
||||
class MLSSPatchExtension(APPatchExtension):
|
||||
game = "Mario & Luigi Superstar Saga"
|
||||
|
||||
@staticmethod
|
||||
def randomize_music(caller: APProcedurePatch, rom: bytes):
|
||||
options = json.loads(caller.get_file("options.json").decode("UTF-8"))
|
||||
if options["music_options"] != 1:
|
||||
return rom
|
||||
stream = io.BytesIO(rom)
|
||||
random.seed(options["seed"] + options["player"])
|
||||
|
||||
songs = []
|
||||
stream.seek(0x21CB74)
|
||||
for _ in range(50):
|
||||
if stream.tell() == 0x21CBD8:
|
||||
stream.seek(4, 1)
|
||||
continue
|
||||
temp = stream.read(4)
|
||||
songs.append(temp)
|
||||
|
||||
random.shuffle(songs)
|
||||
stream.seek(0x21CB74)
|
||||
for _ in range(50):
|
||||
if stream.tell() == 0x21CBD8:
|
||||
stream.seek(4, 1)
|
||||
continue
|
||||
stream.write(songs.pop())
|
||||
|
||||
return stream.getvalue()
|
||||
|
||||
@staticmethod
|
||||
def hidden_visible(caller: APProcedurePatch, rom: bytes):
|
||||
options = json.loads(caller.get_file("options.json").decode("UTF-8"))
|
||||
if options["block_visibility"] == 0:
|
||||
return rom
|
||||
stream = io.BytesIO(rom)
|
||||
|
||||
for location in all_locations:
|
||||
stream.seek(location.id - 6)
|
||||
b = stream.read(1)
|
||||
if b[0] == 0x10 and options["block_visibility"] == 1:
|
||||
stream.seek(location.id - 6)
|
||||
stream.write(bytes([0x0]))
|
||||
if b[0] == 0x0 and options["block_visibility"] == 2:
|
||||
stream.seek(location.id - 6)
|
||||
stream.write(bytes([0x10]))
|
||||
|
||||
return stream.getvalue()
|
||||
|
||||
@staticmethod
|
||||
def randomize_sounds(caller: APProcedurePatch, rom: bytes):
|
||||
options = json.loads(caller.get_file("options.json").decode("UTF-8"))
|
||||
if options["randomize_sounds"] != 1:
|
||||
return rom
|
||||
stream = io.BytesIO(rom)
|
||||
random.seed(options["seed"] + options["player"])
|
||||
fresh_pointers = Data.sounds
|
||||
pointers = Data.sounds
|
||||
|
||||
random.shuffle(pointers)
|
||||
stream.seek(0x21CC44, 0)
|
||||
for i in range(354):
|
||||
current_position = stream.tell()
|
||||
value = int.from_bytes(stream.read(3), "little")
|
||||
if value in fresh_pointers:
|
||||
stream.seek(current_position)
|
||||
stream.write(pointers.pop().to_bytes(3, "little"))
|
||||
stream.seek(1, 1)
|
||||
|
||||
return stream.getvalue()
|
||||
|
||||
@staticmethod
|
||||
def enemy_randomize(caller: APProcedurePatch, rom: bytes):
|
||||
options = json.loads(caller.get_file("options.json").decode("UTF-8"))
|
||||
if options["randomize_bosses"] == 0 and options["randomize_enemies"] == 0:
|
||||
return rom
|
||||
|
||||
enemies = [pos for pos in Data.enemies if pos not in Data.bowsers] if options["castle_skip"] else Data.enemies
|
||||
bosses = [pos for pos in Data.bosses if pos not in Data.bowsers] if options["castle_skip"] else Data.bosses
|
||||
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:
|
||||
raw = []
|
||||
for pos in bosses:
|
||||
stream.seek(pos + 1)
|
||||
raw += [stream.read(0x1F)]
|
||||
random.shuffle(raw)
|
||||
for pos in bosses:
|
||||
stream.seek(pos + 1)
|
||||
stream.write(raw.pop())
|
||||
|
||||
if options["randomize_enemies"] == 1:
|
||||
raw = []
|
||||
for pos in enemies:
|
||||
stream.seek(pos + 1)
|
||||
raw += [stream.read(0x1F)]
|
||||
if options["randomize_bosses"] == 2:
|
||||
for pos in bosses:
|
||||
stream.seek(pos + 1)
|
||||
raw += [stream.read(0x1F)]
|
||||
random.shuffle(raw)
|
||||
for pos in enemies:
|
||||
stream.seek(pos + 1)
|
||||
stream.write(raw.pop())
|
||||
if options["randomize_bosses"] == 2:
|
||||
for pos in bosses:
|
||||
stream.seek(pos + 1)
|
||||
stream.write(raw.pop())
|
||||
return stream.getvalue()
|
||||
|
||||
enemies_raw = []
|
||||
groups = []
|
||||
|
||||
if options["randomize_enemies"] == 0:
|
||||
return stream.getvalue()
|
||||
|
||||
if options["randomize_bosses"] == 2:
|
||||
for pos in bosses:
|
||||
stream.seek(pos + 1)
|
||||
groups += [stream.read(0x1F)]
|
||||
|
||||
for pos in enemies:
|
||||
stream.seek(pos + 8)
|
||||
for _ in range(6):
|
||||
enemy = int.from_bytes(stream.read(1))
|
||||
if enemy > 0:
|
||||
stream.seek(1, 1)
|
||||
flag = int.from_bytes(stream.read(1))
|
||||
if flag == 0x7:
|
||||
break
|
||||
if flag in [0x0, 0x2, 0x4]:
|
||||
if enemy not in Data.pestnut and enemy not in Data.flying:
|
||||
enemies_raw += [enemy]
|
||||
stream.seek(1, 1)
|
||||
else:
|
||||
stream.seek(3, 1)
|
||||
|
||||
random.shuffle(enemies_raw)
|
||||
chomp = False
|
||||
for pos in enemies:
|
||||
stream.seek(pos + 8)
|
||||
|
||||
for _ in range(6):
|
||||
enemy = int.from_bytes(stream.read(1))
|
||||
if enemy > 0 and enemy not in Data.flying and enemy not in Data.pestnut:
|
||||
if enemy == 0x52:
|
||||
chomp = True
|
||||
stream.seek(1, 1)
|
||||
flag = int.from_bytes(stream.read(1))
|
||||
if flag not in [0x0, 0x2, 0x4]:
|
||||
stream.seek(1, 1)
|
||||
continue
|
||||
stream.seek(-3, 1)
|
||||
stream.write(bytes([enemies_raw.pop()]))
|
||||
stream.seek(1, 1)
|
||||
stream.write(bytes([0x6]))
|
||||
stream.seek(1, 1)
|
||||
else:
|
||||
stream.seek(3, 1)
|
||||
|
||||
stream.seek(pos + 1)
|
||||
raw = stream.read(0x1F)
|
||||
if chomp:
|
||||
raw = raw[0:3] + bytes([0x67, 0xAB, 0x28, 0x08]) + raw[7:]
|
||||
else:
|
||||
raw = raw[0:3] + bytes([0xEE, 0x2C, 0x28, 0x08]) + raw[7:]
|
||||
groups += [raw]
|
||||
chomp = False
|
||||
|
||||
random.shuffle(groups)
|
||||
arr = enemies
|
||||
if options["randomize_bosses"] == 2:
|
||||
arr += bosses
|
||||
|
||||
for pos in arr:
|
||||
stream.seek(pos + 1)
|
||||
stream.write(groups.pop())
|
||||
|
||||
return stream.getvalue()
|
||||
|
||||
|
||||
class MLSSProcedurePatch(APProcedurePatch, APTokenMixin):
|
||||
game = "Mario & Luigi Superstar Saga"
|
||||
hash = "4b1a5897d89d9e74ec7f630eefdfd435"
|
||||
patch_file_ending = ".apmlss"
|
||||
result_file_ending = ".gba"
|
||||
|
||||
procedure = [
|
||||
("apply_bsdiff4", ["base_patch.bsdiff4"]),
|
||||
("apply_tokens", ["token_data.bin"]),
|
||||
("enemy_randomize", []),
|
||||
("hidden_visible", []),
|
||||
("randomize_sounds", []),
|
||||
("randomize_music", []),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_source_data(cls) -> bytes:
|
||||
return get_base_rom_as_bytes()
|
||||
|
||||
|
||||
def write_tokens(world: "MLSSWorld", patch: MLSSProcedurePatch) -> None:
|
||||
options_dict = {
|
||||
"randomize_enemies": world.options.randomize_enemies.value,
|
||||
"randomize_bosses": world.options.randomize_bosses.value,
|
||||
"castle_skip": world.options.castle_skip.value,
|
||||
"randomize_sounds": world.options.randomize_sounds.value,
|
||||
"music_options": world.options.music_options.value,
|
||||
"block_visibility": world.options.block_visibility.value,
|
||||
"seed": world.multiworld.seed,
|
||||
"player": world.player,
|
||||
}
|
||||
patch.write_file("options.json", json.dumps(options_dict).encode("UTF-8"))
|
||||
|
||||
# Bake player name into ROM
|
||||
patch.write_token(APTokenTypes.WRITE, 0xDF0000, world.multiworld.player_name[world.player].encode("UTF-8"))
|
||||
|
||||
# Bake seed name into ROM
|
||||
patch.write_token(APTokenTypes.WRITE, 0xDF00A0, world.multiworld.seed_name.encode("UTF-8"))
|
||||
|
||||
# Bake patch into header
|
||||
patch.write_token(APTokenTypes.WRITE, 0xAD, "P".encode("UTF-8"))
|
||||
|
||||
# Intro Skip
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
0x244D08,
|
||||
bytes([0x88, 0x0, 0x19, 0x91, 0x1, 0x20, 0x58, 0x1, 0xF, 0xA0, 0x3, 0x15, 0x27, 0x8]),
|
||||
)
|
||||
|
||||
# Patch S.S Chuckola Loading Zones
|
||||
patch.write_token(APTokenTypes.WRITE, 0x25FD4E, bytes([0x48, 0x30, 0x80, 0x60, 0x50, 0x2, 0xF]))
|
||||
|
||||
patch.write_token(APTokenTypes.WRITE, 0x25FD83, bytes([0x48, 0x30, 0x80, 0x60, 0xC0, 0x2, 0xF]))
|
||||
|
||||
patch.write_token(APTokenTypes.WRITE, 0x25FDB8, bytes([0x48, 0x30, 0x05, 0x80, 0xE4, 0x0, 0xF]))
|
||||
|
||||
patch.write_token(APTokenTypes.WRITE, 0x25FDED, bytes([0x48, 0x30, 0x06, 0x80, 0xE4, 0x0, 0xF]))
|
||||
|
||||
patch.write_token(APTokenTypes.WRITE, 0x25FE22, bytes([0x48, 0x30, 0x07, 0x80, 0xE4, 0x0, 0xF]))
|
||||
|
||||
patch.write_token(APTokenTypes.WRITE, 0x25FE57, bytes([0x48, 0x30, 0x08, 0x80, 0xE4, 0x0, 0xF]))
|
||||
|
||||
if world.options.extra_pipes:
|
||||
patch.write_token(APTokenTypes.WRITE, 0xD00001, bytes([0x1]))
|
||||
|
||||
if world.options.castle_skip:
|
||||
patch.write_token(APTokenTypes.WRITE, 0x3AEAB0, bytes([0xC1, 0x67, 0x0, 0x6, 0x1C, 0x08, 0x3]))
|
||||
patch.write_token(APTokenTypes.WRITE, 0x3AEC18, bytes([0x89, 0x65, 0x0, 0xE, 0xA, 0x08, 0x1]))
|
||||
|
||||
if world.options.skip_minecart:
|
||||
patch.write_token(APTokenTypes.WRITE, 0x3AC728, bytes([0x89, 0x13, 0x0, 0x10, 0xF, 0x08, 0x1]))
|
||||
patch.write_token(APTokenTypes.WRITE, 0x3AC56C, bytes([0x49, 0x16, 0x0, 0x8, 0x8, 0x08, 0x1]))
|
||||
|
||||
if world.options.scale_stats:
|
||||
patch.write_token(APTokenTypes.WRITE, 0xD00002, bytes([0x1]))
|
||||
|
||||
if world.options.xp_multiplier:
|
||||
patch.write_token(APTokenTypes.WRITE, 0xD00003, bytes([world.options.xp_multiplier.value]))
|
||||
|
||||
if world.options.tattle_hp:
|
||||
patch.write_token(APTokenTypes.WRITE, 0xD00000, bytes([0x1]))
|
||||
|
||||
if world.options.music_options == 2:
|
||||
patch.write_token(APTokenTypes.WRITE, 0x19B118, bytes([0x0, 0x25]))
|
||||
|
||||
if world.options.randomize_backgrounds:
|
||||
all_enemies = Data.enemies + Data.bosses
|
||||
for address in all_enemies:
|
||||
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)
|
||||
):
|
||||
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)
|
||||
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)
|
||||
if "Shop" in location_name and "Coffee" not in location_name and item.player != world.player:
|
||||
desc_inject(world, patch, location, item)
|
||||
|
||||
swap_colors(world, patch, world.options.mario_pants.value, 0, True)
|
||||
swap_colors(world, patch, world.options.luigi_pants.value, 1, True)
|
||||
swap_colors(world, patch, world.options.mario_color.value, 0)
|
||||
swap_colors(world, patch, world.options.luigi_color.value, 1)
|
||||
|
||||
patch.write_file("token_data.bin", patch.get_token_binary())
|
||||
|
||||
|
||||
def swap_colors(world: "MLSSWorld", patch: MLSSProcedurePatch, color: int, bro: int,
|
||||
pants_option: Optional[bool] = False):
|
||||
if not pants_option and color == bro:
|
||||
return
|
||||
chaos = False
|
||||
if not pants_option and color == 11 or color == 12:
|
||||
chaos = True
|
||||
if pants_option and color == 11:
|
||||
chaos = True
|
||||
for c in [c for c in (cpants[color] if pants_option else colors[color])
|
||||
if (c[3] == bro if not chaos else c[1] == bro)]:
|
||||
if chaos:
|
||||
patch.write_token(APTokenTypes.WRITE, c[0],
|
||||
bytes([world.random.randint(0, 255), world.random.randint(0, 127)]))
|
||||
else:
|
||||
patch.write_token(APTokenTypes.WRITE, c[0], bytes([c[1], c[2]]))
|
||||
|
||||
|
||||
def item_inject(world: "MLSSWorld", patch: MLSSProcedurePatch, location: int, item_type: int, item: Item):
|
||||
if item.player == world.player:
|
||||
code = item_table[item.name].itemID
|
||||
else:
|
||||
code = 0x3F
|
||||
if item_type == 0:
|
||||
patch.write_token(APTokenTypes.WRITE, location, bytes([code]))
|
||||
elif item_type == 1:
|
||||
if code == 0x1D or code == 0x1E:
|
||||
code += 0xE
|
||||
if 0x20 <= code <= 0x26:
|
||||
code -= 0x4
|
||||
insert = int(code)
|
||||
insert2 = insert % 0x10
|
||||
insert2 *= 0x10
|
||||
insert //= 0x10
|
||||
insert += 0x20
|
||||
patch.write_token(APTokenTypes.WRITE, location, bytes([insert, insert2]))
|
||||
elif item_type == 2:
|
||||
if code == 0x1D or code == 0x1E:
|
||||
code += 0xE
|
||||
if 0x20 <= code <= 0x26:
|
||||
code -= 0x4
|
||||
patch.write_token(APTokenTypes.WRITE, location, bytes([code]))
|
||||
elif item_type == 3:
|
||||
if code == 0x1D or code == 0x1E:
|
||||
code += 0xE
|
||||
if code < 0x1D:
|
||||
code -= 0xA
|
||||
if 0x20 <= code <= 0x26:
|
||||
code -= 0xE
|
||||
patch.write_token(APTokenTypes.WRITE, location, bytes([code]))
|
||||
else:
|
||||
patch.write_token(APTokenTypes.WRITE, location, bytes([0x18]))
|
||||
|
||||
|
||||
def desc_inject(world: "MLSSWorld", patch: MLSSProcedurePatch, location: Location, item: Item):
|
||||
index = -1
|
||||
for key, value in shop.items():
|
||||
if location.address in value:
|
||||
if key == 0x3C05F0:
|
||||
index = value.index(location.address)
|
||||
else:
|
||||
index = value.index(location.address) + 14
|
||||
|
||||
for key, value in badge.items():
|
||||
if index != -1:
|
||||
break
|
||||
if location.address in value:
|
||||
if key == 0x3C0618:
|
||||
index = value.index(location.address) + 24
|
||||
else:
|
||||
index = value.index(location.address) + 41
|
||||
|
||||
for key, value in pants.items():
|
||||
if index != -1:
|
||||
break
|
||||
if location.address in value:
|
||||
if key == 0x3C0618:
|
||||
index = value.index(location.address) + 48
|
||||
else:
|
||||
index = value.index(location.address) + 66
|
||||
|
||||
dstring = f"{world.multiworld.player_name[item.player]}: {item.name}"
|
||||
patch.write_token(APTokenTypes.WRITE, 0xD11000 + (index * 0x40), dstring.encode("UTF8"))
|
||||
571
worlds/mlss/Rules.py
Normal file
571
worlds/mlss/Rules.py
Normal file
@@ -0,0 +1,571 @@
|
||||
import typing
|
||||
|
||||
from worlds.generic.Rules import add_rule, forbid_item
|
||||
from .Names.LocationName import LocationName
|
||||
from .Locations import all_locations, hidden
|
||||
from . import StateLogic
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import MLSSWorld
|
||||
|
||||
|
||||
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
|
||||
):
|
||||
continue
|
||||
if world.options.chuckle_beans == 0 or world.options.chuckle_beans == 1 and location.id in hidden:
|
||||
continue
|
||||
add_rule(
|
||||
world.get_location(location.name),
|
||||
lambda state: StateLogic.canDig(state, world.player),
|
||||
)
|
||||
if "Beanstone" in location.name:
|
||||
add_rule(
|
||||
world.get_location(location.name),
|
||||
lambda state: StateLogic.canDig(state, world.player),
|
||||
)
|
||||
if "Shop" in location.name and "Coffee" not in location.name and location.name not in excluded:
|
||||
forbid_item(world.get_location(location.name), "Hammers", world.player)
|
||||
if "Badge" in location.name or "Pants" in location.name:
|
||||
add_rule(
|
||||
world.get_location(location.name),
|
||||
lambda state: StateLogic.brooch(state, world.player) or StateLogic.rose(state, world.player),
|
||||
)
|
||||
if location.itemType != 0 and location.name not in excluded:
|
||||
if "Bowser" in location.name and world.options.castle_skip:
|
||||
continue
|
||||
forbid_item(world.get_location(location.name), "5 Coins", world.player)
|
||||
|
||||
if world.options.chuckle_beans == 2:
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooVillageSuperHammerCaveDigspot),
|
||||
lambda state: StateLogic.canCrash(state, world.player) or StateLogic.super(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsFarmRoomDigspot2),
|
||||
lambda state: StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsFarmRoomDigspot3),
|
||||
lambda state: StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsWhiteFruitRoomDigspot3),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndJojoraRoomDigspot),
|
||||
lambda state: StateLogic.canDash(state, world.player),
|
||||
)
|
||||
|
||||
if world.options.chuckle_beans != 0:
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainBaseBoostatueRoomDigspot2),
|
||||
lambda state: StateLogic.canCrash(state, world.player) or StateLogic.super(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsFarmRoomDigspot1),
|
||||
lambda state: StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsWhiteFruitRoomDigspot2),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.TeeheeValleyPastUltraHammersDigspot1),
|
||||
lambda state: StateLogic.ultra(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.TeeheeValleyPastUltraHammersDigspot3),
|
||||
lambda state: StateLogic.ultra(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsNorthBeachDigspot3),
|
||||
lambda state: StateLogic.canDash(state, world.player) or StateLogic.super(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsEDigspot2),
|
||||
lambda state: StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsNEDigspot1),
|
||||
lambda state: StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsSRoom1Digspot2),
|
||||
lambda state: StateLogic.ultra(state, world.player) and StateLogic.thunder(state, world.player),
|
||||
)
|
||||
|
||||
forbid_item(
|
||||
world.get_location(LocationName.SSChuckolaMembershipCard), "Nuts", world.player
|
||||
) # Bandaid Fix
|
||||
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooVillageHammerHouseBlock),
|
||||
lambda state: StateLogic.hammers(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainBaseBoostatueRoomBlock2),
|
||||
lambda state: StateLogic.canCrash(state, world.player) or StateLogic.super(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsBooStatueMole),
|
||||
lambda state: StateLogic.canMini(state, world.player) and StateLogic.canDig(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooVillageSuperHammerCaveBlock),
|
||||
lambda state: StateLogic.canCrash(state, world.player) or StateLogic.super(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsFarmRoomMoleReward1),
|
||||
lambda state: StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsFarmRoomMoleReward2),
|
||||
lambda state: StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsThunderHandMole),
|
||||
lambda state: StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsNWBlock),
|
||||
lambda state: StateLogic.super(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsBeanFruit1),
|
||||
lambda state: StateLogic.canDig(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsBeanFruit2),
|
||||
lambda state: StateLogic.canDig(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsBeanFruit3),
|
||||
lambda state: StateLogic.canDig(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsBeanFruit4),
|
||||
lambda state: StateLogic.super(state, world.player) and StateLogic.canDig(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsBeanFruit5),
|
||||
lambda state: StateLogic.super(state, world.player) and StateLogic.canDig(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsBeanFruit6),
|
||||
lambda state: StateLogic.canDig(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsBeanFruit7),
|
||||
lambda state: StateLogic.teehee(state, world.player) and StateLogic.canDig(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsSRoom1Block),
|
||||
lambda state: StateLogic.ultra(state, world.player) and StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsSRoom2Block1),
|
||||
lambda state: StateLogic.canDig(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.WoohooHooniversityMiniMarioPuzzleSecretAreaBlock1),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.WoohooHooniversityMiniMarioPuzzleSecretAreaBlock2),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.WoohooHooniversityMiniMarioPuzzleSecretAreaBlock3),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.WoohooHooniversityMiniMarioPuzzleSecretAreaBlock4),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.WoohooHooniversityMiniMarioPuzzleBlock),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsSecretScroll1),
|
||||
lambda state: StateLogic.thunder(state, world.player) and StateLogic.super(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsSecretScroll2),
|
||||
lambda state: StateLogic.thunder(state, world.player) and StateLogic.ultra(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooVillageMoleBehindTurtle),
|
||||
lambda state: StateLogic.canDash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsNESoloMarioMole1),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsNESoloMarioMole2),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsSuperHammerUpgrade),
|
||||
lambda state: StateLogic.thunder(state, world.player),
|
||||
)
|
||||
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),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsSoloLuigiCaveMole),
|
||||
lambda state: StateLogic.canDig(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsRedChuckolaFruit),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsWhiteChuckolaFruit),
|
||||
lambda state: StateLogic.canDig(state, world.player) and StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootBlock1),
|
||||
lambda state: StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootBlock2),
|
||||
lambda state: StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootBlock3),
|
||||
lambda state: StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootBlock4),
|
||||
lambda state: StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootBlock5),
|
||||
lambda state: StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootBlock6),
|
||||
lambda state: StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsRoom7Block1),
|
||||
lambda state: StateLogic.hammers(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsRoom7Block2),
|
||||
lambda state: StateLogic.hammers(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsRoom4Block1),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsRoom4Block2),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsRoom4Block3),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsPipeRoomBlock1),
|
||||
lambda state: StateLogic.hammers(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsPipeRoomBlock2),
|
||||
lambda state: StateLogic.hammers(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanCastleTownMiniMarioBlock1),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanCastleTownMiniMarioBlock2),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanCastleTownMiniMarioBlock3),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanCastleTownMiniMarioBlock4),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanCastleTownMiniMarioBlock5),
|
||||
lambda state: StateLogic.canMini(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanCastleFakeBeastar),
|
||||
lambda state: StateLogic.pieces(state, world.player) and StateLogic.rose(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanCastlePeachsExtraDress),
|
||||
lambda state: StateLogic.pieces(state, world.player) and StateLogic.rose(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.SewersRoom5Block1),
|
||||
lambda state: StateLogic.hammers(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.SewersRoom5Block2),
|
||||
lambda state: StateLogic.hammers(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.GwarharLagoonFirstUnderwaterAreaRoom1Block),
|
||||
lambda state: StateLogic.canDash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.GwarharLagoonFirstUnderwaterAreaRoom2Block1),
|
||||
lambda state: StateLogic.canDash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.GwarharLagoonFirstUnderwaterAreaRoom2Block2),
|
||||
lambda state: StateLogic.canDash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.GwarharLagoonRedPearlBean),
|
||||
lambda state: StateLogic.fire(state, world.player) and StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.GwarharLagoonGreenPearlBean),
|
||||
lambda state: StateLogic.fire(state, world.player) and StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.TeeheeValleyPastUltraHammersBlock1),
|
||||
lambda state: StateLogic.ultra(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
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),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.OhoOasisThunderhand),
|
||||
lambda state: StateLogic.canDig(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanstarPieceYoshiTheater),
|
||||
lambda state: StateLogic.neon(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.YoshiTheaterAzureYoshi),
|
||||
lambda state: StateLogic.beanFruit(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.YoshiTheaterBlueYoshi),
|
||||
lambda state: StateLogic.beanFruit(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.YoshiTheaterGreenYoshi),
|
||||
lambda state: StateLogic.beanFruit(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.YoshiTheaterOrangeYoshi),
|
||||
lambda state: StateLogic.beanFruit(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.YoshiTheaterPurpleYoshi),
|
||||
lambda state: StateLogic.beanFruit(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.YoshiTheaterRedYoshi),
|
||||
lambda state: StateLogic.beanFruit(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.YoshiTheaterYellowYoshi),
|
||||
lambda state: StateLogic.beanFruit(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.WinkleAreaBeanstarRoomBlock),
|
||||
lambda state: StateLogic.winkle(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanstarPieceWinkleArea),
|
||||
lambda state: StateLogic.winkle(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.GwarharLagoonSpangleReward),
|
||||
lambda state: StateLogic.spangle(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.PantsShopMomPiranhaFlag1),
|
||||
lambda state: StateLogic.brooch(state, world.player) or StateLogic.rose(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.PantsShopMomPiranhaFlag2),
|
||||
lambda state: StateLogic.brooch(state, world.player) or StateLogic.rose(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.PantsShopMomPiranhaFlag3),
|
||||
lambda state: StateLogic.brooch(state, world.player) or StateLogic.rose(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BadgeShopMomPiranhaFlag1),
|
||||
lambda state: StateLogic.brooch(state, world.player) or StateLogic.rose(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BadgeShopMomPiranhaFlag2),
|
||||
lambda state: StateLogic.brooch(state, world.player) or StateLogic.rose(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BadgeShopMomPiranhaFlag3),
|
||||
lambda state: StateLogic.brooch(state, world.player) or StateLogic.rose(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChateauGreenGoblet),
|
||||
lambda state: StateLogic.brooch(state, world.player) and StateLogic.canDig(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChateauRedGoblet),
|
||||
lambda state: StateLogic.brooch(state, world.player) and StateLogic.canMini(state, world.player),
|
||||
)
|
||||
|
||||
add_rule(
|
||||
world.get_location(LocationName.GwarharLagoonSpangle),
|
||||
lambda state: StateLogic.ultra(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.GwarharLagoonSpangleRoomBlock),
|
||||
lambda state: StateLogic.ultra(state, world.player),
|
||||
)
|
||||
if world.options.difficult_logic:
|
||||
add_rule(
|
||||
world.get_location(LocationName.GwarharLagoonSpangleReward),
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanstarPieceHermie),
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
if world.options.chuckle_beans != 0:
|
||||
add_rule(
|
||||
world.get_location(LocationName.GwarharLagoonPastHermieDigspot),
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
|
||||
if world.options.coins:
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainBaseBooStatueCaveCoinBlock1),
|
||||
lambda state: StateLogic.canCrash(state, world.player) or StateLogic.super(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainBaseBooStatueCaveCoinBlock2),
|
||||
lambda state: StateLogic.canCrash(state, world.player) or StateLogic.super(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.HoohooMountainBaseBooStatueCaveCoinBlock3),
|
||||
lambda state: StateLogic.canCrash(state, world.player) or StateLogic.super(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsNWCoinBlock),
|
||||
lambda state: StateLogic.super(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsSRoom1CoinBlock),
|
||||
lambda state: StateLogic.ultra(state, world.player) and StateLogic.thunder(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.BeanbeanOutskirtsSRoom2CoinBlock),
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChateauPoppleRoomCoinBlock1),
|
||||
lambda state: StateLogic.brooch(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChateauPoppleRoomCoinBlock2),
|
||||
lambda state: StateLogic.brooch(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsCaveRoom1CoinBlock),
|
||||
lambda state: StateLogic.brooch(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsCaveRoom2CoinBlock),
|
||||
lambda state: StateLogic.brooch(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsCaveRoom3CoinBlock),
|
||||
lambda state: StateLogic.brooch(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsPipe5RoomCoinBlock),
|
||||
lambda state: StateLogic.brooch(state, world.player) and StateLogic.hammers(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsRoom7CoinBlock),
|
||||
lambda state: StateLogic.brooch(state, world.player) and StateLogic.hammers(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsAfterChucklerootCoinBlock),
|
||||
lambda state: StateLogic.brooch(state, world.player) and StateLogic.fruits(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsKoopaRoomCoinBlock),
|
||||
lambda state: StateLogic.brooch(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.ChucklehuckWoodsWinkleAreaCaveCoinBlock),
|
||||
lambda state: StateLogic.brooch(state, world.player) and StateLogic.canDash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.SewersPrisonRoomCoinBlock),
|
||||
lambda state: StateLogic.rose(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.TeeheeValleyPastUltraHammerRocksCoinBlock),
|
||||
lambda state: StateLogic.ultra(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.SSChuckolaStorageRoomCoinBlock1),
|
||||
lambda state: StateLogic.super(state, world.player) or StateLogic.canDash(state, world.player),
|
||||
)
|
||||
add_rule(
|
||||
world.get_location(LocationName.SSChuckolaStorageRoomCoinBlock2),
|
||||
lambda state: StateLogic.super(state, world.player) or StateLogic.canDash(state, world.player),
|
||||
)
|
||||
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)),
|
||||
)
|
||||
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))
|
||||
),
|
||||
)
|
||||
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)),
|
||||
)
|
||||
if not world.options.difficult_logic:
|
||||
add_rule(
|
||||
world.get_location(LocationName.JokesEndNorthofBridgeRoomCoinBlock),
|
||||
lambda state: StateLogic.canCrash(state, world.player),
|
||||
)
|
||||
155
worlds/mlss/StateLogic.py
Normal file
155
worlds/mlss/StateLogic.py
Normal file
@@ -0,0 +1,155 @@
|
||||
def canDig(state, player):
|
||||
return state.has("Green Goblet", player) and state.has("Hammers", player)
|
||||
|
||||
|
||||
def canMini(state, player):
|
||||
return state.has("Red Goblet", player) and state.has("Hammers", player)
|
||||
|
||||
|
||||
def canDash(state, player):
|
||||
return state.has("Red Pearl Bean", player) and state.has("Firebrand", player)
|
||||
|
||||
|
||||
def canCrash(state, player):
|
||||
return state.has("Green Pearl Bean", player) and state.has("Thunderhand", player)
|
||||
|
||||
|
||||
def hammers(state, player):
|
||||
return state.has("Hammers", player)
|
||||
|
||||
|
||||
def super(state, player):
|
||||
return state.has("Hammers", player, 2)
|
||||
|
||||
|
||||
def ultra(state, player):
|
||||
return state.has("Hammers", player, 3)
|
||||
|
||||
|
||||
def fruits(state, player):
|
||||
return (
|
||||
state.has("Red Chuckola Fruit", player)
|
||||
and state.has("Purple Chuckola Fruit", player)
|
||||
and state.has("White Chuckola Fruit", player)
|
||||
)
|
||||
|
||||
|
||||
def pieces(state, player):
|
||||
return (
|
||||
state.has("Beanstar Piece 1", player)
|
||||
and state.has("Beanstar Piece 2", player)
|
||||
and state.has("Beanstar Piece 3", player)
|
||||
and state.has("Beanstar Piece 4", player)
|
||||
)
|
||||
|
||||
|
||||
def neon(state, player):
|
||||
return (
|
||||
state.has("Blue Neon Egg", player)
|
||||
and state.has("Red Neon Egg", player)
|
||||
and state.has("Green Neon Egg", player)
|
||||
and state.has("Yellow Neon Egg", player)
|
||||
and state.has("Purple Neon Egg", player)
|
||||
and state.has("Orange Neon Egg", player)
|
||||
and state.has("Azure Neon Egg", player)
|
||||
)
|
||||
|
||||
|
||||
def spangle(state, player):
|
||||
return state.has("Spangle", player)
|
||||
|
||||
|
||||
def rose(state, player):
|
||||
return state.has("Peasley's Rose", player)
|
||||
|
||||
|
||||
def brooch(state, player):
|
||||
return state.has("Beanbean Brooch", player)
|
||||
|
||||
|
||||
def thunder(state, player):
|
||||
return state.has("Thunderhand", player)
|
||||
|
||||
|
||||
def fire(state, player):
|
||||
return state.has("Firebrand", player)
|
||||
|
||||
|
||||
def dressBeanstar(state, player):
|
||||
return state.has("Peach's Extra Dress", player) and state.has("Fake Beanstar", player)
|
||||
|
||||
|
||||
def membership(state, player):
|
||||
return state.has("Membership Card", player)
|
||||
|
||||
|
||||
def winkle(state, player):
|
||||
return state.has("Winkle Card", player)
|
||||
|
||||
|
||||
def beanFruit(state, player):
|
||||
return (
|
||||
state.has("Bean Fruit 1", player)
|
||||
and state.has("Bean Fruit 2", player)
|
||||
and state.has("Bean Fruit 3", player)
|
||||
and state.has("Bean Fruit 4", player)
|
||||
and state.has("Bean Fruit 5", player)
|
||||
and state.has("Bean Fruit 6", player)
|
||||
and state.has("Bean Fruit 7", player)
|
||||
)
|
||||
|
||||
|
||||
def surfable(state, player):
|
||||
return ultra(state, player) and (
|
||||
(canDig(state, player) and canMini(state, player)) or (membership(state, player) and fire(state, player))
|
||||
)
|
||||
|
||||
|
||||
def postJokes(state, player):
|
||||
return (
|
||||
surfable(state, player)
|
||||
and canDig(state, player)
|
||||
and dressBeanstar(state, player)
|
||||
and pieces(state, player)
|
||||
and fruits(state, player)
|
||||
and brooch(state, player)
|
||||
and rose(state, player)
|
||||
and canDash(state, player)
|
||||
)
|
||||
|
||||
|
||||
def teehee(state, player):
|
||||
return super(state, player) or canDash(state, player)
|
||||
|
||||
|
||||
def castleTown(state, player):
|
||||
return fruits(state, player) and brooch(state, player)
|
||||
|
||||
|
||||
def fungitown(state, player):
|
||||
return (
|
||||
castleTown(state, player)
|
||||
and thunder(state, player)
|
||||
and rose(state, player)
|
||||
and (super(state, player) or canDash(state, player))
|
||||
)
|
||||
|
||||
|
||||
def piranha_shop(state, player):
|
||||
return state.can_reach("Shop Mom Piranha Flag", "Region", player)
|
||||
|
||||
|
||||
def fungitown_shop(state, player):
|
||||
return state.can_reach("Shop Enter Fungitown Flag", "Region", player)
|
||||
|
||||
|
||||
def star_shop(state, player):
|
||||
return state.can_reach("Shop Beanstar Complete Flag", "Region", player)
|
||||
|
||||
|
||||
def birdo_shop(state, player):
|
||||
return state.can_reach("Shop Birdo Flag", "Region", player)
|
||||
|
||||
|
||||
def fungitown_birdo_shop(state, player):
|
||||
return state.can_reach("Fungitown Shop Birdo Flag", "Region", player)
|
||||
183
worlds/mlss/__init__.py
Normal file
183
worlds/mlss/__init__.py
Normal file
@@ -0,0 +1,183 @@
|
||||
import os
|
||||
import pkgutil
|
||||
import typing
|
||||
import settings
|
||||
from BaseClasses import Tutorial, ItemClassification
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from typing import List, 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
|
||||
from .Names.LocationName import LocationName
|
||||
from .Client import MLSSClient
|
||||
from .Regions import create_regions, connect_regions
|
||||
from .Rom import MLSSProcedurePatch, write_tokens
|
||||
from .Rules import set_rules
|
||||
|
||||
|
||||
class MLSSWebWorld(WebWorld):
|
||||
theme = "partyTime"
|
||||
bug_report_page = "https://github.com/jamesbrq/ArchipelagoMLSS/issues"
|
||||
tutorials = [
|
||||
Tutorial(
|
||||
tutorial_name="Setup Guide",
|
||||
description="A guide to setting up Mario & Luigi: Superstar Saga for Archipelago.",
|
||||
language="English",
|
||||
file_name="setup_en.md",
|
||||
link="setup/en",
|
||||
authors=["jamesbrq"],
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class MLSSSettings(settings.Group):
|
||||
class RomFile(settings.UserFilePath):
|
||||
"""File name of the MLSS US rom"""
|
||||
|
||||
copy_to = "Mario & Luigi - Superstar Saga (U).gba"
|
||||
description = "MLSS ROM File"
|
||||
md5s = ["4b1a5897d89d9e74ec7f630eefdfd435"]
|
||||
|
||||
rom_file: RomFile = RomFile(RomFile.copy_to)
|
||||
rom_start: bool = True
|
||||
|
||||
|
||||
class MLSSWorld(World):
|
||||
"""
|
||||
Adventure with Mario and Luigi together in the Beanbean Kingdom
|
||||
to stop the evil Cackletta and retrieve the Beanstar.
|
||||
"""
|
||||
|
||||
game = "Mario & Luigi Superstar Saga"
|
||||
web = MLSSWebWorld()
|
||||
options_dataclass = MLSSOptions
|
||||
options: MLSSOptions
|
||||
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)
|
||||
|
||||
disabled_locations: List[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]
|
||||
if self.options.skip_minecart:
|
||||
self.disabled_locations += [LocationName.HoohooMountainBaseMinecartCaveDigspot]
|
||||
if self.options.disable_surf:
|
||||
self.disabled_locations += [LocationName.SurfMinigame]
|
||||
if self.options.harhalls_pants:
|
||||
self.disabled_locations += [LocationName.HarhallsPants]
|
||||
if not self.options.coins:
|
||||
self.disabled_locations += [location.name for location in all_locations if location in coins]
|
||||
|
||||
def create_regions(self) -> None:
|
||||
create_regions(self, self.disabled_locations)
|
||||
connect_regions(self)
|
||||
|
||||
item = self.create_item("Mushroom")
|
||||
self.get_location(LocationName.ShopStartingFlag1).place_locked_item(item)
|
||||
item = self.create_item("Syrup")
|
||||
self.get_location(LocationName.ShopStartingFlag2).place_locked_item(item)
|
||||
item = self.create_item("1-UP Mushroom")
|
||||
self.get_location(LocationName.ShopStartingFlag3).place_locked_item(item)
|
||||
item = self.create_item("Hoo Bean")
|
||||
self.get_location(LocationName.PantsShopStartingFlag1).place_locked_item(item)
|
||||
item = self.create_item("Chuckle Bean")
|
||||
self.get_location(LocationName.PantsShopStartingFlag2).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,
|
||||
"ChuckleBeans": self.options.chuckle_beans.value,
|
||||
"DifficultLogic": self.options.difficult_logic.value,
|
||||
"Coins": self.options.coins.value,
|
||||
}
|
||||
|
||||
def create_items(self) -> None:
|
||||
# First add in all progression and useful items
|
||||
required_items = []
|
||||
precollected = [item for item in itemList if item in self.multiworld.precollected_items]
|
||||
for item in itemList:
|
||||
if item.classification != ItemClassification.filler and item.classification != ItemClassification.skip_balancing:
|
||||
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:
|
||||
continue
|
||||
required_items += [item.itemName for _ in range(freq)]
|
||||
|
||||
for itemName in required_items:
|
||||
self.multiworld.itempool.append(self.create_item(itemName))
|
||||
|
||||
# Then, create our list of filler items
|
||||
filler_items = []
|
||||
for item in itemList:
|
||||
if item.classification != ItemClassification.filler:
|
||||
continue
|
||||
if item.itemName == "5 Coins" and not self.options.coins:
|
||||
continue
|
||||
freq = item_frequencies.get(item.itemName, 1)
|
||||
if self.options.chuckle_beans == 0:
|
||||
if item.itemName == "Chuckle Bean":
|
||||
continue
|
||||
if self.options.chuckle_beans == 1:
|
||||
if item.itemName == "Chuckle Bean":
|
||||
freq -= 59
|
||||
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)
|
||||
|
||||
self.multiworld.itempool += [
|
||||
self.create_item(filler_item_name) for filler_item_name in self.random.sample(filler_items, remaining)
|
||||
]
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
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)))
|
||||
|
||||
def generate_output(self, output_directory: str) -> None:
|
||||
patch = MLSSProcedurePatch(player=self.player, player_name=self.multiworld.player_name[self.player])
|
||||
patch.write_file("base_patch.bsdiff4", pkgutil.get_data(__name__, "data/basepatch.bsdiff"))
|
||||
write_tokens(self, patch)
|
||||
rom_path = os.path.join(
|
||||
output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}" f"{patch.patch_file_ending}"
|
||||
)
|
||||
patch.write(rom_path)
|
||||
BIN
worlds/mlss/data/basepatch.bsdiff
Normal file
BIN
worlds/mlss/data/basepatch.bsdiff
Normal file
Binary file not shown.
66
worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md
Normal file
66
worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Mario & Luigi: Superstar Saga
|
||||
|
||||
## Where is the options page?
|
||||
|
||||
The [player options page for this game](../player-options) contains all the options you need to configure and
|
||||
export a config file.
|
||||
|
||||
## What does randomization do to this game?
|
||||
|
||||
Items which the player would normally acquire throughout the game have been moved around. Logic remains, so the game is
|
||||
always able to be completed, but because of the item shuffle, the player may need to access certain areas before they
|
||||
would in the vanilla game.
|
||||
|
||||
The game has been changed to an open-world style as opposed to the linear style the vanilla game has.
|
||||
|
||||
Other Features such as Turbo through textboxes (Hold L/R+A) and Pipe Warping from any room (Hold L+R+SELECT) have been added for convenience.
|
||||
|
||||
Enemies and Bosses can be randomized, and their stats can be scaled to feel more like the vanilla game's stats.
|
||||
|
||||
Other aspects of the game can be randomized as well such as music, sounds, battle backgrounds, Mario and Luigi's Colors, and more.
|
||||
|
||||
## What is the goal of Mario & Luigi: Superstar Saga when randomized?
|
||||
|
||||
Defeat Cackletta's Soul in Bowser's Castle. This requires you to collect all 4 Beanstar Pieces, restore the Beanstar, and bring Peach's Extra Dress and the Fake Beanstar to Fawful at the end of Jokes End.
|
||||
|
||||
In total, this requires:
|
||||
- 4 Beanstar Pieces
|
||||
- Peach's Extra Dress
|
||||
- Fake Beanstar
|
||||
- Ultra Hammers
|
||||
- Fire Hand
|
||||
- Thunder Hand
|
||||
- Red Pearl Bean
|
||||
- Green Pearl Bean
|
||||
- Green Goblet
|
||||
- Peasley's Rose
|
||||
- Beanbean Brooch
|
||||
- All 3 Chuckola Fruits
|
||||
- Membership Card OR Red Goblet
|
||||
|
||||
## What items and locations can get shuffled?
|
||||
|
||||
Locations in which items can be found:
|
||||
- All Item Blocks and Coin Blocks
|
||||
- All Chuckle Bean Digspots
|
||||
- All Shop items
|
||||
- All Pants and Badge shop items
|
||||
- All Espresso brews and rewards
|
||||
- All Minigame Rewards
|
||||
- All Event based items
|
||||
|
||||
Items that can be shuffled:
|
||||
- All consumable items (Mushrooms, Nuts, Syrups etc.)
|
||||
- All Hoo Beans and Chuckle Beans
|
||||
- All Badges and Pants
|
||||
- All key items (Beanfruits, Beanbean Brooch, Hammers etc.)
|
||||
- All Extra Gears (Great Force, Gameboy Horror SP etc.)
|
||||
|
||||
## What does another world's item look like in Mario & Luigi: Superstar Saga?
|
||||
|
||||
Items will show up as a Golden Mushroom from boxes and Digspots and "AP Item" in all textboxes.
|
||||
Items in a shop from another player's world will display the player name and item name in addition to being displayed as an AP Item.
|
||||
|
||||
## When the player receives an item, what happens?
|
||||
|
||||
Items will be placed directly into the players inventory after a few seconds. Sometimes for certain events and cutscenes to be properly triggered right after you received an item, you may have to leave and re-enter the room to properly load everything required for the respective event or cutscene.
|
||||
53
worlds/mlss/docs/setup_en.md
Normal file
53
worlds/mlss/docs/setup_en.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Setup Guide for Mario & Luigi: Superstar Saga Archipelago
|
||||
|
||||
## Important
|
||||
|
||||
As we are using Bizhawk, this guide is only applicable to Windows and Linux systems.
|
||||
|
||||
## Required Software
|
||||
|
||||
- Bizhawk: [Bizhawk Releases from TASVideos](https://tasvideos.org/BizHawk/ReleaseHistory)
|
||||
- Version 2.9.1 is recommended.
|
||||
- Detailed installation instructions for Bizhawk can be found at the above link.
|
||||
- Windows users must run the prerequisite installer first, which can also be found at the above link.
|
||||
- The built-in Bizhawk client, which can be installed [here](https://github.com/ArchipelagoMW/Archipelago/releases)
|
||||
- A US copy of Mario & Luigi: Superstar Saga
|
||||
|
||||
## Optional Software
|
||||
|
||||
- [Poptracker](https://github.com/black-sliver/PopTracker/releases)
|
||||
- [MLSS Autotracker](https://github.com/seto10987/MLSS-PopTracker/releases)
|
||||
|
||||
## Configuring your YAML file
|
||||
|
||||
### What is a YAML file and why do I need one?
|
||||
|
||||
Your YAML file contains a set of configuration options which provide the generator with information about how it should
|
||||
generate your game. Each player of a multiworld will provide their own YAML file. This setup allows each player to enjoy
|
||||
an experience customized for their taste, and different players in the same multiworld can all have different options.
|
||||
|
||||
### Where do I get a YAML file?
|
||||
|
||||
You can customize your options by visiting the
|
||||
[Mario & Luigi Superstar Saga Options Page](/games/Mario%20&%20Luigi%20Superstar%20Saga/player-options)
|
||||
|
||||
## Joining a MultiWorld Game
|
||||
|
||||
### Obtain your GBA patch file
|
||||
|
||||
When you join a multiworld game, you will be asked to provide your YAML file to whoever is hosting. Once that is done,
|
||||
the host will provide you with either a link to download your data file, or with a zip file containing everyone's data
|
||||
files. Your data file should have a `.apmlss` extension.
|
||||
|
||||
Double-click on your `.apmlss` file to start your client and start the ROM patch process. Once the process is finished, the client and the emulator will be started automatically (if you associated the extension
|
||||
to the emulator as recommended).
|
||||
|
||||
### Connect to the Multiserver
|
||||
|
||||
Once both the client and the emulator are started, you must connect them. Within the emulator click on the "Tools"
|
||||
menu and select "Lua Console". Click the folder button or press Ctrl+O to open a Lua script.
|
||||
|
||||
Navigate to your Archipelago install folder and open `data/lua/connector_bizhawk_generic.lua`.
|
||||
|
||||
To connect the client to the multiserver simply put `<address>:<port>` on the textfield on top and press enter (if the
|
||||
server uses password, type in the bottom textfield `/connect <address>:<port> [password]`)
|
||||
@@ -12,7 +12,7 @@ class NoitaLocation(Location):
|
||||
class LocationData(NamedTuple):
|
||||
id: int
|
||||
flag: int = 0
|
||||
ltype: str = "shop"
|
||||
ltype: str = "Shop"
|
||||
|
||||
|
||||
class LocationFlag(IntEnum):
|
||||
@@ -25,7 +25,7 @@ class LocationFlag(IntEnum):
|
||||
|
||||
# Mapping of items in each region.
|
||||
# Only the first Hidden Chest and Pedestal are mapped here, the others are created in Regions.
|
||||
# ltype key: "chest" = Hidden Chests, "pedestal" = Pedestals, "boss" = Boss, "orb" = Orb.
|
||||
# ltype key: "Chest" = Hidden Chests, "Pedestal" = Pedestals, "Boss" = Boss, "Orb" = Orb.
|
||||
# 110000-110671
|
||||
location_region_mapping: Dict[str, Dict[str, LocationData]] = {
|
||||
"Coal Pits Holy Mountain": {
|
||||
@@ -91,117 +91,118 @@ location_region_mapping: Dict[str, Dict[str, LocationData]] = {
|
||||
"Secret Shop Item 4": LocationData(110045),
|
||||
},
|
||||
"The Sky": {
|
||||
"Kivi": LocationData(110670, LocationFlag.main_world, "boss"),
|
||||
"Kivi": LocationData(110670, LocationFlag.main_world, "Boss"),
|
||||
},
|
||||
"Floating Island": {
|
||||
"Floating Island Orb": LocationData(110658, LocationFlag.main_path, "orb"),
|
||||
"Floating Island Orb": LocationData(110658, LocationFlag.main_path, "Orb"),
|
||||
},
|
||||
"Pyramid": {
|
||||
"Kolmisilmän Koipi": LocationData(110649, LocationFlag.main_world, "boss"),
|
||||
"Pyramid Orb": LocationData(110659, LocationFlag.main_world, "orb"),
|
||||
"Sandcave Orb": LocationData(110662, LocationFlag.main_world, "orb"),
|
||||
"Kolmisilmän Koipi": LocationData(110649, LocationFlag.main_world, "Boss"),
|
||||
"Pyramid Orb": LocationData(110659, LocationFlag.main_world, "Orb"),
|
||||
"Sandcave Orb": LocationData(110662, LocationFlag.main_world, "Orb"),
|
||||
},
|
||||
"Overgrown Cavern": {
|
||||
"Overgrown Cavern Chest": LocationData(110526, LocationFlag.main_world, "chest"),
|
||||
"Overgrown Cavern Pedestal": LocationData(110546, LocationFlag.main_world, "pedestal"),
|
||||
"Overgrown Cavern Chest": LocationData(110526, LocationFlag.main_world, "Chest"),
|
||||
"Overgrown Cavern Pedestal": LocationData(110546, LocationFlag.main_world, "Pedestal"),
|
||||
},
|
||||
"Lake": {
|
||||
"Syväolento": LocationData(110651, LocationFlag.main_world, "boss"),
|
||||
"Tapion vasalli": LocationData(110669, LocationFlag.main_world, "boss"),
|
||||
"Syväolento": LocationData(110651, LocationFlag.main_world, "Boss"),
|
||||
"Tapion vasalli": LocationData(110669, LocationFlag.main_world, "Boss"),
|
||||
},
|
||||
"Frozen Vault": {
|
||||
"Frozen Vault Orb": LocationData(110660, LocationFlag.main_world, "orb"),
|
||||
"Frozen Vault Chest": LocationData(110566, LocationFlag.main_world, "chest"),
|
||||
"Frozen Vault Pedestal": LocationData(110586, LocationFlag.main_world, "pedestal"),
|
||||
"Frozen Vault Orb": LocationData(110660, LocationFlag.main_world, "Orb"),
|
||||
"Frozen Vault Chest": LocationData(110566, LocationFlag.main_world, "Chest"),
|
||||
"Frozen Vault Pedestal": LocationData(110586, LocationFlag.main_world, "Pedestal"),
|
||||
},
|
||||
"Mines": {
|
||||
"Mines Chest": LocationData(110046, LocationFlag.main_path, "chest"),
|
||||
"Mines Pedestal": LocationData(110066, LocationFlag.main_path, "pedestal"),
|
||||
"Mines Chest": LocationData(110046, LocationFlag.main_path, "Chest"),
|
||||
"Mines Pedestal": LocationData(110066, LocationFlag.main_path, "Pedestal"),
|
||||
},
|
||||
# Collapsed Mines is a very small area, combining it with the Mines. Leaving this here as a reminder
|
||||
|
||||
"Ancient Laboratory": {
|
||||
"Ylialkemisti": LocationData(110656, LocationFlag.side_path, "boss"),
|
||||
"Ylialkemisti": LocationData(110656, LocationFlag.side_path, "Boss"),
|
||||
},
|
||||
"Abyss Orb Room": {
|
||||
"Sauvojen Tuntija": LocationData(110650, LocationFlag.side_path, "boss"),
|
||||
"Abyss Orb": LocationData(110665, LocationFlag.main_path, "orb"),
|
||||
"Sauvojen Tuntija": LocationData(110650, LocationFlag.side_path, "Boss"),
|
||||
"Abyss Orb": LocationData(110665, LocationFlag.main_path, "Orb"),
|
||||
},
|
||||
"Below Lava Lake": {
|
||||
"Lava Lake Orb": LocationData(110661, LocationFlag.side_path, "orb"),
|
||||
"Lava Lake Orb": LocationData(110661, LocationFlag.side_path, "Orb"),
|
||||
},
|
||||
"Coal Pits": {
|
||||
"Coal Pits Chest": LocationData(110126, LocationFlag.main_path, "chest"),
|
||||
"Coal Pits Pedestal": LocationData(110146, LocationFlag.main_path, "pedestal"),
|
||||
"Coal Pits Chest": LocationData(110126, LocationFlag.main_path, "Chest"),
|
||||
"Coal Pits Pedestal": LocationData(110146, LocationFlag.main_path, "Pedestal"),
|
||||
},
|
||||
"Fungal Caverns": {
|
||||
"Fungal Caverns Chest": LocationData(110166, LocationFlag.side_path, "chest"),
|
||||
"Fungal Caverns Pedestal": LocationData(110186, LocationFlag.side_path, "pedestal"),
|
||||
"Fungal Caverns Chest": LocationData(110166, LocationFlag.side_path, "Chest"),
|
||||
"Fungal Caverns Pedestal": LocationData(110186, LocationFlag.side_path, "Pedestal"),
|
||||
},
|
||||
"Snowy Depths": {
|
||||
"Snowy Depths Chest": LocationData(110206, LocationFlag.main_path, "chest"),
|
||||
"Snowy Depths Pedestal": LocationData(110226, LocationFlag.main_path, "pedestal"),
|
||||
"Snowy Depths Chest": LocationData(110206, LocationFlag.main_path, "Chest"),
|
||||
"Snowy Depths Pedestal": LocationData(110226, LocationFlag.main_path, "Pedestal"),
|
||||
},
|
||||
"Magical Temple": {
|
||||
"Magical Temple Orb": LocationData(110663, LocationFlag.side_path, "orb"),
|
||||
"Magical Temple Orb": LocationData(110663, LocationFlag.side_path, "Orb"),
|
||||
},
|
||||
"Hiisi Base": {
|
||||
"Hiisi Base Chest": LocationData(110246, LocationFlag.main_path, "chest"),
|
||||
"Hiisi Base Pedestal": LocationData(110266, LocationFlag.main_path, "pedestal"),
|
||||
"Hiisi Base Chest": LocationData(110246, LocationFlag.main_path, "Chest"),
|
||||
"Hiisi Base Pedestal": LocationData(110266, LocationFlag.main_path, "Pedestal"),
|
||||
},
|
||||
"Underground Jungle": {
|
||||
"Suomuhauki": LocationData(110648, LocationFlag.main_path, "boss"),
|
||||
"Underground Jungle Chest": LocationData(110286, LocationFlag.main_path, "chest"),
|
||||
"Underground Jungle Pedestal": LocationData(110306, LocationFlag.main_path, "pedestal"),
|
||||
"Suomuhauki": LocationData(110648, LocationFlag.main_path, "Boss"),
|
||||
"Underground Jungle Chest": LocationData(110286, LocationFlag.main_path, "Chest"),
|
||||
"Underground Jungle Pedestal": LocationData(110306, LocationFlag.main_path, "Pedestal"),
|
||||
},
|
||||
"Lukki Lair": {
|
||||
"Lukki Lair Orb": LocationData(110664, LocationFlag.side_path, "orb"),
|
||||
"Lukki Lair Chest": LocationData(110326, LocationFlag.side_path, "chest"),
|
||||
"Lukki Lair Pedestal": LocationData(110346, LocationFlag.side_path, "pedestal"),
|
||||
"Lukki Lair Orb": LocationData(110664, LocationFlag.side_path, "Orb"),
|
||||
"Lukki Lair Chest": LocationData(110326, LocationFlag.side_path, "Chest"),
|
||||
"Lukki Lair Pedestal": LocationData(110346, LocationFlag.side_path, "Pedestal"),
|
||||
},
|
||||
"The Vault": {
|
||||
"The Vault Chest": LocationData(110366, LocationFlag.main_path, "chest"),
|
||||
"The Vault Pedestal": LocationData(110386, LocationFlag.main_path, "pedestal"),
|
||||
"The Vault Chest": LocationData(110366, LocationFlag.main_path, "Chest"),
|
||||
"The Vault Pedestal": LocationData(110386, LocationFlag.main_path, "Pedestal"),
|
||||
},
|
||||
"Temple of the Art": {
|
||||
"Gate Guardian": LocationData(110652, LocationFlag.main_path, "boss"),
|
||||
"Temple of the Art Chest": LocationData(110406, LocationFlag.main_path, "chest"),
|
||||
"Temple of the Art Pedestal": LocationData(110426, LocationFlag.main_path, "pedestal"),
|
||||
"Gate Guardian": LocationData(110652, LocationFlag.main_path, "Boss"),
|
||||
"Temple of the Art Chest": LocationData(110406, LocationFlag.main_path, "Chest"),
|
||||
"Temple of the Art Pedestal": LocationData(110426, LocationFlag.main_path, "Pedestal"),
|
||||
},
|
||||
"The Tower": {
|
||||
"The Tower Chest": LocationData(110606, LocationFlag.main_world, "chest"),
|
||||
"The Tower Pedestal": LocationData(110626, LocationFlag.main_world, "pedestal"),
|
||||
"The Tower Chest": LocationData(110606, LocationFlag.main_world, "Chest"),
|
||||
"The Tower Pedestal": LocationData(110626, LocationFlag.main_world, "Pedestal"),
|
||||
},
|
||||
"Wizards' Den": {
|
||||
"Mestarien Mestari": LocationData(110655, LocationFlag.main_world, "boss"),
|
||||
"Wizards' Den Orb": LocationData(110668, LocationFlag.main_world, "orb"),
|
||||
"Wizards' Den Chest": LocationData(110446, LocationFlag.main_world, "chest"),
|
||||
"Wizards' Den Pedestal": LocationData(110466, LocationFlag.main_world, "pedestal"),
|
||||
"Mestarien Mestari": LocationData(110655, LocationFlag.main_world, "Boss"),
|
||||
"Wizards' Den Orb": LocationData(110668, LocationFlag.main_world, "Orb"),
|
||||
"Wizards' Den Chest": LocationData(110446, LocationFlag.main_world, "Chest"),
|
||||
"Wizards' Den Pedestal": LocationData(110466, LocationFlag.main_world, "Pedestal"),
|
||||
},
|
||||
"Powerplant": {
|
||||
"Kolmisilmän silmä": LocationData(110657, LocationFlag.main_world, "boss"),
|
||||
"Power Plant Chest": LocationData(110486, LocationFlag.main_world, "chest"),
|
||||
"Power Plant Pedestal": LocationData(110506, LocationFlag.main_world, "pedestal"),
|
||||
"Kolmisilmän silmä": LocationData(110657, LocationFlag.main_world, "Boss"),
|
||||
"Power Plant Chest": LocationData(110486, LocationFlag.main_world, "Chest"),
|
||||
"Power Plant Pedestal": LocationData(110506, LocationFlag.main_world, "Pedestal"),
|
||||
},
|
||||
"Snow Chasm": {
|
||||
"Unohdettu": LocationData(110653, LocationFlag.main_world, "boss"),
|
||||
"Snow Chasm Orb": LocationData(110667, LocationFlag.main_world, "orb"),
|
||||
"Unohdettu": LocationData(110653, LocationFlag.main_world, "Boss"),
|
||||
"Snow Chasm Orb": LocationData(110667, LocationFlag.main_world, "Orb"),
|
||||
},
|
||||
"Meat Realm": {
|
||||
"Meat Realm Chest": LocationData(110086, LocationFlag.main_world, "chest"),
|
||||
"Meat Realm Pedestal": LocationData(110106, LocationFlag.main_world, "pedestal"),
|
||||
"Limatoukka": LocationData(110647, LocationFlag.main_world, "boss"),
|
||||
"Meat Realm Chest": LocationData(110086, LocationFlag.main_world, "Chest"),
|
||||
"Meat Realm Pedestal": LocationData(110106, LocationFlag.main_world, "Pedestal"),
|
||||
"Limatoukka": LocationData(110647, LocationFlag.main_world, "Boss"),
|
||||
},
|
||||
"West Meat Realm": {
|
||||
"Kolmisilmän sydän": LocationData(110671, LocationFlag.main_world, "boss"),
|
||||
"Kolmisilmän sydän": LocationData(110671, LocationFlag.main_world, "Boss"),
|
||||
},
|
||||
"The Laboratory": {
|
||||
"Kolmisilmä": LocationData(110646, LocationFlag.main_path, "boss"),
|
||||
"Kolmisilmä": LocationData(110646, LocationFlag.main_path, "Boss"),
|
||||
},
|
||||
"Friend Cave": {
|
||||
"Toveri": LocationData(110654, LocationFlag.main_world, "boss"),
|
||||
"Toveri": LocationData(110654, LocationFlag.main_world, "Boss"),
|
||||
},
|
||||
"The Work (Hell)": {
|
||||
"The Work (Hell) Orb": LocationData(110666, LocationFlag.main_world, "orb"),
|
||||
"The Work (Hell) Orb": LocationData(110666, LocationFlag.main_world, "Orb"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -212,18 +213,20 @@ def make_location_range(location_name: str, base_id: int, amt: int) -> Dict[str,
|
||||
return {f"{location_name} {i+1}": base_id + i for i in range(amt)}
|
||||
|
||||
|
||||
location_name_groups: Dict[str, Set[str]] = {"shop": set(), "orb": set(), "boss": set(), "chest": set(),
|
||||
"pedestal": set()}
|
||||
location_name_groups: Dict[str, Set[str]] = {"Shop": set(), "Orb": set(), "Boss": set(), "Chest": set(),
|
||||
"Pedestal": set()}
|
||||
location_name_to_id: Dict[str, int] = {}
|
||||
|
||||
|
||||
for location_group in location_region_mapping.values():
|
||||
for region_name, location_group in location_region_mapping.items():
|
||||
location_name_groups[region_name] = set()
|
||||
for locname, locinfo in location_group.items():
|
||||
# Iterating the hidden chest and pedestal locations here to avoid clutter above
|
||||
amount = 20 if locinfo.ltype in ["chest", "pedestal"] else 1
|
||||
amount = 20 if locinfo.ltype in ["Chest", "Pedestal"] else 1
|
||||
entries = make_location_range(locname, locinfo.id, amount)
|
||||
|
||||
location_name_to_id.update(entries)
|
||||
location_name_groups[locinfo.ltype].update(entries.keys())
|
||||
location_name_groups[region_name].update(entries.keys())
|
||||
|
||||
shop_locations = {name for name in location_name_to_id.keys() if "Shop Item" in name}
|
||||
|
||||
@@ -15,14 +15,14 @@ def create_locations(world: "NoitaWorld", region: Region) -> None:
|
||||
location_type = location_data.ltype
|
||||
flag = location_data.flag
|
||||
|
||||
is_orb_allowed = location_type == "orb" and flag <= world.options.orbs_as_checks
|
||||
is_boss_allowed = location_type == "boss" and flag <= world.options.bosses_as_checks
|
||||
is_orb_allowed = location_type == "Orb" and flag <= world.options.orbs_as_checks
|
||||
is_boss_allowed = location_type == "Boss" and flag <= world.options.bosses_as_checks
|
||||
amount = 0
|
||||
if flag == locations.LocationFlag.none or is_orb_allowed or is_boss_allowed:
|
||||
amount = 1
|
||||
elif location_type == "chest" and flag <= world.options.path_option:
|
||||
elif location_type == "Chest" and flag <= world.options.path_option:
|
||||
amount = world.options.hidden_chests.value
|
||||
elif location_type == "pedestal" and flag <= world.options.path_option:
|
||||
elif location_type == "Pedestal" and flag <= world.options.path_option:
|
||||
amount = world.options.pedestal_checks.value
|
||||
|
||||
region.add_locations(locations.make_location_range(location_name, location_data.id, amount),
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
# 2.1.1
|
||||
|
||||
### Features
|
||||
|
||||
- You no longer need a copy of Pokemon Emerald to generate a game, patch files generate much faster.
|
||||
|
||||
# 2.1.0
|
||||
|
||||
_Separately released, branching from 2.0.0. Included procedure patch migration, but none of the 2.0.1 fixes._
|
||||
|
||||
# 2.0.1
|
||||
|
||||
### Fixes
|
||||
|
||||
@@ -5,11 +5,12 @@ from collections import Counter
|
||||
import copy
|
||||
import logging
|
||||
import os
|
||||
import pkgutil
|
||||
from typing import Any, Set, List, Dict, Optional, Tuple, ClassVar, TextIO, Union
|
||||
|
||||
from BaseClasses import ItemClassification, MultiWorld, Tutorial, LocationProgressType
|
||||
from Fill import FillError, fill_restrictive
|
||||
from Options import Toggle
|
||||
from Options import OptionError, Toggle
|
||||
import settings
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
|
||||
@@ -25,7 +26,7 @@ from .options import (Goal, DarkCavesRequireFlash, HmRequirements, ItemPoolType,
|
||||
from .pokemon import (get_random_move, get_species_id_by_label, randomize_abilities, randomize_learnsets,
|
||||
randomize_legendary_encounters, randomize_misc_pokemon, randomize_starters,
|
||||
randomize_tm_hm_compatibility,randomize_types, randomize_wild_encounters)
|
||||
from .rom import PokemonEmeraldDeltaPatch, create_patch
|
||||
from .rom import PokemonEmeraldProcedurePatch, write_tokens
|
||||
|
||||
|
||||
class PokemonEmeraldWebWorld(WebWorld):
|
||||
@@ -60,7 +61,7 @@ class PokemonEmeraldSettings(settings.Group):
|
||||
"""File name of your English Pokemon Emerald ROM"""
|
||||
description = "Pokemon Emerald ROM File"
|
||||
copy_to = "Pokemon - Emerald Version (USA, Europe).gba"
|
||||
md5s = [PokemonEmeraldDeltaPatch.hash]
|
||||
md5s = [PokemonEmeraldProcedurePatch.hash]
|
||||
|
||||
rom_file: PokemonEmeraldRomFile = PokemonEmeraldRomFile(PokemonEmeraldRomFile.copy_to)
|
||||
|
||||
@@ -126,9 +127,6 @@ class PokemonEmeraldWorld(World):
|
||||
def stage_assert_generate(cls, multiworld: MultiWorld) -> None:
|
||||
from .sanity_check import validate_regions
|
||||
|
||||
if not os.path.exists(cls.settings.rom_file):
|
||||
raise FileNotFoundError(cls.settings.rom_file)
|
||||
|
||||
assert validate_regions()
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
@@ -183,8 +181,8 @@ class PokemonEmeraldWorld(World):
|
||||
if self.options.goal == Goal.option_legendary_hunt:
|
||||
# Prevent turning off all legendary encounters
|
||||
if len(self.options.allowed_legendary_hunt_encounters.value) == 0:
|
||||
raise ValueError(f"Pokemon Emerald: Player {self.player} ({self.multiworld.player_name[self.player]}) "
|
||||
"needs to allow at least one legendary encounter when goal is legendary hunt.")
|
||||
raise OptionError(f"Pokemon Emerald: Player {self.player} ({self.multiworld.player_name[self.player]}) "
|
||||
"needs to allow at least one legendary encounter when goal is legendary hunt.")
|
||||
|
||||
# Prevent setting the number of required legendaries higher than the number of enabled legendaries
|
||||
if self.options.legendary_hunt_count.value > len(self.options.allowed_legendary_hunt_encounters.value):
|
||||
@@ -195,8 +193,8 @@ class PokemonEmeraldWorld(World):
|
||||
|
||||
# Require random wild encounters if dexsanity is enabled
|
||||
if self.options.dexsanity and self.options.wild_pokemon == RandomizeWildPokemon.option_vanilla:
|
||||
raise ValueError(f"Pokemon Emerald: Player {self.player} ({self.multiworld.player_name[self.player]}) must "
|
||||
"not leave wild encounters vanilla if enabling dexsanity.")
|
||||
raise OptionError(f"Pokemon Emerald: Player {self.player} ({self.multiworld.player_name[self.player]}) must "
|
||||
"not leave wild encounters vanilla if enabling dexsanity.")
|
||||
|
||||
# If badges or HMs are vanilla, Norman locks you from using Surf,
|
||||
# which means you're not guaranteed to be able to reach Fortree Gym,
|
||||
@@ -591,7 +589,9 @@ class PokemonEmeraldWorld(World):
|
||||
randomize_opponent_parties(self)
|
||||
randomize_starters(self)
|
||||
|
||||
create_patch(self, output_directory)
|
||||
patch = PokemonEmeraldProcedurePatch(player=self.player, player_name=self.multiworld.player_name[self.player])
|
||||
patch.write_file("base_patch.bsdiff4", pkgutil.get_data(__name__, "data/base_patch.bsdiff4"))
|
||||
write_tokens(self, patch)
|
||||
|
||||
del self.modified_trainers
|
||||
del self.modified_tmhm_moves
|
||||
@@ -600,6 +600,10 @@ class PokemonEmeraldWorld(World):
|
||||
del self.modified_starters
|
||||
del self.modified_species
|
||||
|
||||
# Write Output
|
||||
out_file_name = self.multiworld.get_out_file_name_base(self.player)
|
||||
patch.write(os.path.join(output_directory, f"{out_file_name}{patch.patch_file_ending}"))
|
||||
|
||||
def write_spoiler(self, spoiler_handle: TextIO):
|
||||
if self.options.dexsanity:
|
||||
from collections import defaultdict
|
||||
|
||||
@@ -51,6 +51,13 @@ TRACKER_EVENT_FLAGS = [
|
||||
"FLAG_OMIT_DIVE_FROM_STEVEN_LETTER", # Steven gives Dive HM (clears seafloor cavern grunt)
|
||||
"FLAG_IS_CHAMPION",
|
||||
"FLAG_PURCHASED_HARBOR_MAIL",
|
||||
"FLAG_REGI_DOORS_OPENED",
|
||||
"FLAG_RETURNED_DEVON_GOODS",
|
||||
"FLAG_DOCK_REJECTED_DEVON_GOODS",
|
||||
"FLAG_DEFEATED_EVIL_TEAM_MT_CHIMNEY",
|
||||
"FLAG_WINGULL_SENT_ON_ERRAND",
|
||||
"FLAG_WINGULL_DELIVERED_MAIL",
|
||||
"FLAG_MET_PRETTY_PETAL_SHOP_OWNER",
|
||||
]
|
||||
EVENT_FLAG_MAP = {data.constants[flag_name]: flag_name for flag_name in TRACKER_EVENT_FLAGS}
|
||||
|
||||
@@ -84,6 +91,10 @@ KEY_LOCATION_FLAGS = [
|
||||
"NPC_GIFT_RECEIVED_OLD_ROD",
|
||||
"NPC_GIFT_RECEIVED_GOOD_ROD",
|
||||
"NPC_GIFT_RECEIVED_SUPER_ROD",
|
||||
"NPC_GIFT_RECEIVED_EON_TICKET",
|
||||
"NPC_GIFT_RECEIVED_AURORA_TICKET",
|
||||
"NPC_GIFT_RECEIVED_MYSTIC_TICKET",
|
||||
"NPC_GIFT_RECEIVED_OLD_SEA_MAP",
|
||||
]
|
||||
KEY_LOCATION_FLAG_MAP = {data.locations[location_name].flag: location_name for location_name in KEY_LOCATION_FLAGS}
|
||||
|
||||
|
||||
@@ -289,6 +289,7 @@ class TrainerData:
|
||||
party: TrainerPartyData
|
||||
address: int
|
||||
script_address: int
|
||||
battle_type: int
|
||||
|
||||
|
||||
class PokemonEmeraldData:
|
||||
@@ -1422,7 +1423,8 @@ def _init() -> None:
|
||||
trainer_json["party_address"]
|
||||
),
|
||||
trainer_json["address"],
|
||||
trainer_json["script_address"]
|
||||
trainer_json["script_address"],
|
||||
trainer_json["battle_type"]
|
||||
))
|
||||
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1269,7 +1269,7 @@
|
||||
"REGION_SLATEPORT_CITY/MAIN": {
|
||||
"parent_map": "MAP_SLATEPORT_CITY",
|
||||
"has_grass": false,
|
||||
"has_water": true,
|
||||
"has_water": false,
|
||||
"has_fishing": true,
|
||||
"locations": [
|
||||
"NPC_GIFT_RECEIVED_POWDER_JAR"
|
||||
@@ -1279,9 +1279,9 @@
|
||||
"EVENT_VISITED_SLATEPORT_CITY"
|
||||
],
|
||||
"exits": [
|
||||
"REGION_SLATEPORT_CITY/WATER",
|
||||
"REGION_ROUTE109/BEACH",
|
||||
"REGION_ROUTE110/SOUTH",
|
||||
"REGION_ROUTE134/WEST"
|
||||
"REGION_ROUTE110/SOUTH"
|
||||
],
|
||||
"warps": [
|
||||
"MAP_SLATEPORT_CITY:0/MAP_SLATEPORT_CITY_POKEMON_CENTER_1F:0",
|
||||
@@ -1296,6 +1296,19 @@
|
||||
"MAP_SLATEPORT_CITY:10/MAP_SLATEPORT_CITY_HOUSE:0"
|
||||
]
|
||||
},
|
||||
"REGION_SLATEPORT_CITY/WATER": {
|
||||
"parent_map": "MAP_SLATEPORT_CITY",
|
||||
"has_grass": false,
|
||||
"has_water": true,
|
||||
"has_fishing": true,
|
||||
"locations": [],
|
||||
"events": [],
|
||||
"exits": [
|
||||
"REGION_SLATEPORT_CITY/MAIN",
|
||||
"REGION_ROUTE134/WEST"
|
||||
],
|
||||
"warps": []
|
||||
},
|
||||
"REGION_SLATEPORT_CITY_POKEMON_CENTER_2F/MAIN": {
|
||||
"parent_map": "MAP_SLATEPORT_CITY_POKEMON_CENTER_2F",
|
||||
"has_grass": false,
|
||||
|
||||
@@ -3294,7 +3294,7 @@
|
||||
"locations": [],
|
||||
"events": [],
|
||||
"exits": [
|
||||
"REGION_SLATEPORT_CITY/MAIN"
|
||||
"REGION_SLATEPORT_CITY/WATER"
|
||||
],
|
||||
"warps": []
|
||||
},
|
||||
|
||||
@@ -21,7 +21,7 @@ clear it.
|
||||
|
||||
## Optional Software
|
||||
|
||||
- [Pokémon Emerald AP Tracker](https://github.com/AliceMousie/emerald-ap-tracker/releases/latest), for use with
|
||||
- [Pokémon Emerald AP Tracker](https://github.com/seto10987/Archipelago-Emerald-AP-Tracker/releases/latest), for use with
|
||||
[PopTracker](https://github.com/black-sliver/PopTracker/releases)
|
||||
|
||||
## Generating and Patching a Game
|
||||
@@ -64,7 +64,7 @@ perfectly safe to make progress offline; everything will re-sync when you reconn
|
||||
|
||||
Pokémon Emerald has a fully functional map tracker that supports auto-tracking.
|
||||
|
||||
1. Download [Pokémon Emerald AP Tracker](https://github.com/AliceMousie/emerald-ap-tracker/releases/latest) and
|
||||
1. Download [Pokémon Emerald AP Tracker](https://github.com/seto10987/Archipelago-Emerald-AP-Tracker/releases/latest) and
|
||||
[PopTracker](https://github.com/black-sliver/PopTracker/releases).
|
||||
2. Put the tracker pack into packs/ in your PopTracker install.
|
||||
3. Open PopTracker, and load the Pokémon Emerald pack.
|
||||
|
||||
@@ -21,7 +21,7 @@ limpiarlas, selecciona el atajo y presiona la tecla Esc.
|
||||
|
||||
## Software Opcional
|
||||
|
||||
- [Pokémon Emerald AP Tracker](https://github.com/AliceMousie/emerald-ap-tracker/releases/latest), para usar con
|
||||
- [Pokémon Emerald AP Tracker](https://github.com/seto10987/Archipelago-Emerald-AP-Tracker/releases/latest), para usar con
|
||||
[PopTracker](https://github.com/black-sliver/PopTracker/releases)
|
||||
|
||||
## Generando y Parcheando el Juego
|
||||
@@ -65,7 +65,7 @@ jugar de manera offline; se sincronizará todo cuando te vuelvas a conectar.
|
||||
|
||||
Pokémon Emerald tiene un Map Tracker completamente funcional que soporta auto-tracking.
|
||||
|
||||
1. Descarga [Pokémon Emerald AP Tracker](https://github.com/AliceMousie/emerald-ap-tracker/releases/latest) y
|
||||
1. Descarga [Pokémon Emerald AP Tracker](https://github.com/seto10987/Archipelago-Emerald-AP-Tracker/releases/latest) y
|
||||
[PopTracker](https://github.com/black-sliver/PopTracker/releases).
|
||||
2. Coloca la carpeta del Tracker en la carpeta packs/ dentro de la carpeta de instalación del PopTracker.
|
||||
3. Abre PopTracker, y carga el Pack de Pokémon Emerald Map Tracker.
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Dict, Optional, FrozenSet, Iterable
|
||||
|
||||
from BaseClasses import Location, Region
|
||||
|
||||
from .data import BASE_OFFSET, POKEDEX_OFFSET, data
|
||||
from .data import BASE_OFFSET, NATIONAL_ID_TO_SPECIES_ID, POKEDEX_OFFSET, data
|
||||
from .items import offset_item_value
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -130,8 +130,14 @@ def create_locations_with_tags(world: "PokemonEmeraldWorld", regions: Dict[str,
|
||||
location_data = data.locations[location_name]
|
||||
|
||||
location_id = offset_flag(location_data.flag)
|
||||
if location_data.flag == 0:
|
||||
location_id += POKEDEX_OFFSET + int(location_name[15:])
|
||||
if location_data.flag == 0: # Dexsanity location
|
||||
national_dex_id = int(location_name[-3:]) # Location names are formatted POKEDEX_REWARD_###
|
||||
|
||||
# Don't create this pokedex location if player can't find it in the wild
|
||||
if NATIONAL_ID_TO_SPECIES_ID[national_dex_id] in world.blacklisted_wilds:
|
||||
continue
|
||||
|
||||
location_id += POKEDEX_OFFSET + national_dex_id
|
||||
|
||||
location = PokemonEmeraldLocation(
|
||||
world.player,
|
||||
|
||||
@@ -242,9 +242,9 @@ def randomize_wild_encounters(world: "PokemonEmeraldWorld") -> None:
|
||||
RandomizeWildPokemon.option_match_type,
|
||||
RandomizeWildPokemon.option_match_base_stats_and_type,
|
||||
}
|
||||
catch_em_all = world.options.dexsanity == Toggle.option_true
|
||||
|
||||
catch_em_all_placed = set()
|
||||
already_placed = set()
|
||||
num_placeable_species = NUM_REAL_SPECIES - len(world.blacklisted_wilds)
|
||||
|
||||
priority_species = [data.constants["SPECIES_WAILORD"], data.constants["SPECIES_RELICANTH"]]
|
||||
|
||||
@@ -290,8 +290,8 @@ def randomize_wild_encounters(world: "PokemonEmeraldWorld") -> None:
|
||||
|
||||
# If dexsanity/catch 'em all mode, blacklist already placed species
|
||||
# until every species has been placed once
|
||||
if catch_em_all and len(catch_em_all_placed) < NUM_REAL_SPECIES:
|
||||
blacklists[1].append(catch_em_all_placed)
|
||||
if world.options.dexsanity and len(already_placed) < num_placeable_species:
|
||||
blacklists[1].append(already_placed)
|
||||
|
||||
# Blacklist from player options
|
||||
blacklists[2].append(world.blacklisted_wilds)
|
||||
@@ -329,8 +329,8 @@ def randomize_wild_encounters(world: "PokemonEmeraldWorld") -> None:
|
||||
new_species_id = world.random.choice(candidates).species_id
|
||||
species_old_to_new_map[species_id] = new_species_id
|
||||
|
||||
if catch_em_all and map_data.name not in POSTGAME_MAPS:
|
||||
catch_em_all_placed.add(new_species_id)
|
||||
if world.options.dexsanity and map_data.name not in POSTGAME_MAPS:
|
||||
already_placed.add(new_species_id)
|
||||
|
||||
# Actually create the new list of slots and encounter table
|
||||
new_slots: List[int] = []
|
||||
|
||||
@@ -3,12 +3,10 @@ Classes and functions related to creating a ROM patch
|
||||
"""
|
||||
import copy
|
||||
import os
|
||||
import pkgutil
|
||||
import struct
|
||||
from typing import TYPE_CHECKING, Dict, List, Tuple
|
||||
|
||||
import bsdiff4
|
||||
|
||||
from worlds.Files import APDeltaPatch
|
||||
from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes
|
||||
from settings import get_settings
|
||||
|
||||
from .data import TrainerPokemonDataTypeEnum, BASE_OFFSET, data
|
||||
@@ -96,38 +94,32 @@ CAVE_EVENT_NAME_TO_ID = {
|
||||
}
|
||||
|
||||
|
||||
def _set_bytes_le(byte_array: bytearray, address: int, size: int, value: int) -> None:
|
||||
offset = 0
|
||||
while size > 0:
|
||||
byte_array[address + offset] = value & 0xFF
|
||||
value = value >> 8
|
||||
offset += 1
|
||||
size -= 1
|
||||
|
||||
|
||||
class PokemonEmeraldDeltaPatch(APDeltaPatch):
|
||||
class PokemonEmeraldProcedurePatch(APProcedurePatch, APTokenMixin):
|
||||
game = "Pokemon Emerald"
|
||||
hash = "605b89b67018abcea91e693a4dd25be3"
|
||||
patch_file_ending = ".apemerald"
|
||||
result_file_ending = ".gba"
|
||||
|
||||
procedure = [
|
||||
("apply_bsdiff4", ["base_patch.bsdiff4"]),
|
||||
("apply_tokens", ["token_data.bin"])
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_source_data(cls) -> bytes:
|
||||
return get_base_rom_as_bytes()
|
||||
with open(get_settings().pokemon_emerald_settings.rom_file, "rb") as infile:
|
||||
base_rom_bytes = bytes(infile.read())
|
||||
|
||||
return base_rom_bytes
|
||||
|
||||
|
||||
def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
||||
base_rom = get_base_rom_as_bytes()
|
||||
base_patch = pkgutil.get_data(__name__, "data/base_patch.bsdiff4")
|
||||
patched_rom = bytearray(bsdiff4.patch(base_rom, base_patch))
|
||||
|
||||
def write_tokens(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
|
||||
# Set free fly location
|
||||
if world.options.free_fly_location:
|
||||
_set_bytes_le(
|
||||
patched_rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gArchipelagoOptions"] + 0x20,
|
||||
1,
|
||||
world.free_fly_location_id
|
||||
struct.pack("<B", world.free_fly_location_id)
|
||||
)
|
||||
|
||||
location_info: List[Tuple[int, int, str]] = []
|
||||
@@ -141,26 +133,32 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
||||
# Set local item values
|
||||
if not world.options.remote_items and location.item.player == world.player:
|
||||
if type(location.item_address) is int:
|
||||
_set_bytes_le(
|
||||
patched_rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
location.item_address,
|
||||
2,
|
||||
reverse_offset_item_value(location.item.code)
|
||||
struct.pack("<H", location.item.code - BASE_OFFSET)
|
||||
)
|
||||
elif type(location.item_address) is list:
|
||||
for address in location.item_address:
|
||||
_set_bytes_le(patched_rom, address, 2, reverse_offset_item_value(location.item.code))
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
address,
|
||||
struct.pack("<H", location.item.code - BASE_OFFSET)
|
||||
)
|
||||
else:
|
||||
if type(location.item_address) is int:
|
||||
_set_bytes_le(
|
||||
patched_rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
location.item_address,
|
||||
2,
|
||||
data.constants["ITEM_ARCHIPELAGO_PROGRESSION"]
|
||||
struct.pack("<H", data.constants["ITEM_ARCHIPELAGO_PROGRESSION"])
|
||||
)
|
||||
elif type(location.item_address) is list:
|
||||
for address in location.item_address:
|
||||
_set_bytes_le(patched_rom, address, 2, data.constants["ITEM_ARCHIPELAGO_PROGRESSION"])
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
address,
|
||||
struct.pack("<H", data.constants["ITEM_ARCHIPELAGO_PROGRESSION"])
|
||||
)
|
||||
|
||||
# Creates a list of item information to store in tables later. Those tables are used to display the item and
|
||||
# player name in a text box. In the case of not enough space, the game will default to "found an ARCHIPELAGO
|
||||
@@ -194,9 +192,21 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
||||
# message (the message for receiving an item will pop up when the client eventually gives it to them).
|
||||
# In race mode, no item location data is included, and only recieved (or own) items will show any text box.
|
||||
if item_player == world.player or world.multiworld.is_race:
|
||||
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 0, 2, flag)
|
||||
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 2, 2, 0)
|
||||
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 4, 1, 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 0,
|
||||
struct.pack("<H", flag)
|
||||
)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 2,
|
||||
struct.pack("<H", 0)
|
||||
)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 4,
|
||||
struct.pack("<B", 0)
|
||||
)
|
||||
else:
|
||||
player_name = world.multiworld.player_name[item_player]
|
||||
|
||||
@@ -207,11 +217,10 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
||||
|
||||
player_name_ids[player_name] = len(player_name_ids)
|
||||
for j, b in enumerate(encode_string(player_name, 17)):
|
||||
_set_bytes_le(
|
||||
patched_rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gArchipelagoPlayerNames"] + (player_name_ids[player_name] * 17) + j,
|
||||
1,
|
||||
b
|
||||
struct.pack("<B", b)
|
||||
)
|
||||
|
||||
if item_name not in item_name_offsets:
|
||||
@@ -224,18 +233,28 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
||||
|
||||
item_name_offsets[item_name] = next_item_name_offset
|
||||
next_item_name_offset += len(item_name) + 1
|
||||
for j, b in enumerate(encode_string(item_name) + b"\xFF"):
|
||||
_set_bytes_le(
|
||||
patched_rom,
|
||||
data.rom_addresses["gArchipelagoItemNames"] + (item_name_offsets[item_name]) + j,
|
||||
1,
|
||||
b
|
||||
)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gArchipelagoItemNames"] + (item_name_offsets[item_name]),
|
||||
encode_string(item_name) + b"\xFF"
|
||||
)
|
||||
|
||||
# There should always be enough space for one entry per location
|
||||
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 0, 2, flag)
|
||||
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 2, 2, item_name_offsets[item_name])
|
||||
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 4, 1, player_name_ids[player_name])
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 0,
|
||||
struct.pack("<H", flag)
|
||||
)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 2,
|
||||
struct.pack("<H", item_name_offsets[item_name])
|
||||
)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 4,
|
||||
struct.pack("<B", player_name_ids[player_name])
|
||||
)
|
||||
|
||||
easter_egg = get_easter_egg(world.options.easter_egg.value)
|
||||
|
||||
@@ -282,40 +301,40 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
||||
for i, slot in enumerate(pc_slots):
|
||||
address = data.rom_addresses["sNewGamePCItems"] + (i * 4)
|
||||
item = reverse_offset_item_value(world.item_name_to_id[slot[0]])
|
||||
_set_bytes_le(patched_rom, address + 0, 2, item)
|
||||
_set_bytes_le(patched_rom, address + 2, 2, slot[1])
|
||||
patch.write_token(APTokenTypes.WRITE, address + 0, struct.pack("<H", item))
|
||||
patch.write_token(APTokenTypes.WRITE, address + 2, struct.pack("<H", slot[1]))
|
||||
|
||||
# Set species data
|
||||
_set_species_info(world, patched_rom, easter_egg)
|
||||
_set_species_info(world, patch, easter_egg)
|
||||
|
||||
# Set encounter tables
|
||||
if world.options.wild_pokemon != RandomizeWildPokemon.option_vanilla:
|
||||
_set_encounter_tables(world, patched_rom)
|
||||
_set_encounter_tables(world, patch)
|
||||
|
||||
# Set opponent data
|
||||
if world.options.trainer_parties != RandomizeTrainerParties.option_vanilla or easter_egg[0] == 2:
|
||||
_set_opponents(world, patched_rom, easter_egg)
|
||||
_set_opponents(world, patch, easter_egg)
|
||||
|
||||
# Set legendary pokemon
|
||||
_set_legendary_encounters(world, patched_rom)
|
||||
_set_legendary_encounters(world, patch)
|
||||
|
||||
# Set misc pokemon
|
||||
_set_misc_pokemon(world, patched_rom)
|
||||
_set_misc_pokemon(world, patch)
|
||||
|
||||
# Set starters
|
||||
_set_starters(world, patched_rom)
|
||||
_set_starters(world, patch)
|
||||
|
||||
# Set TM moves
|
||||
_set_tm_moves(world, patched_rom, easter_egg)
|
||||
_set_tm_moves(world, patch, easter_egg)
|
||||
|
||||
# Randomize move tutor moves
|
||||
_randomize_move_tutor_moves(world, patched_rom, easter_egg)
|
||||
_randomize_move_tutor_moves(world, patch, easter_egg)
|
||||
|
||||
# Set TM/HM compatibility
|
||||
_set_tmhm_compatibility(world, patched_rom)
|
||||
_set_tmhm_compatibility(world, patch)
|
||||
|
||||
# Randomize opponent double or single
|
||||
_randomize_opponent_battle_type(world, patched_rom)
|
||||
_randomize_opponent_battle_type(world, patch)
|
||||
|
||||
# Options
|
||||
# struct ArchipelagoOptions
|
||||
@@ -360,73 +379,118 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
||||
options_address = data.rom_addresses["gArchipelagoOptions"]
|
||||
|
||||
# Set Birch pokemon
|
||||
_set_bytes_le(
|
||||
patched_rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x00,
|
||||
2,
|
||||
world.random.choice(list(data.species.keys()))
|
||||
struct.pack("<H", world.random.choice(list(data.species.keys())))
|
||||
)
|
||||
|
||||
# Set hold A to advance text
|
||||
_set_bytes_le(patched_rom, options_address + 0x02, 1, 1 if world.options.turbo_a else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x02,
|
||||
struct.pack("<B", 1 if world.options.turbo_a else 0)
|
||||
)
|
||||
|
||||
# Set receive item messages type
|
||||
_set_bytes_le(patched_rom, options_address + 0x03, 1, world.options.receive_item_messages.value)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x03,
|
||||
struct.pack("<B", world.options.receive_item_messages.value)
|
||||
)
|
||||
|
||||
# Set better shops
|
||||
_set_bytes_le(patched_rom, options_address + 0x04, 1, 1 if world.options.better_shops else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x04,
|
||||
struct.pack("<B", 1 if world.options.better_shops else 0)
|
||||
)
|
||||
|
||||
# Set reusable TMs
|
||||
_set_bytes_le(patched_rom, options_address + 0x05, 1, 1 if world.options.reusable_tms_tutors else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x05,
|
||||
struct.pack("<B", 1 if world.options.reusable_tms_tutors else 0)
|
||||
)
|
||||
|
||||
# Set guaranteed catch
|
||||
_set_bytes_le(patched_rom, options_address + 0x06, 1, 1 if world.options.guaranteed_catch else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x06,
|
||||
struct.pack("<B", 1 if world.options.guaranteed_catch else 0)
|
||||
)
|
||||
|
||||
# Set purge spinners
|
||||
_set_bytes_le(patched_rom, options_address + 0x07, 1, 1 if world.options.purge_spinners else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x07,
|
||||
struct.pack("<B", 1 if world.options.purge_spinners else 0)
|
||||
)
|
||||
|
||||
# Set blind trainers
|
||||
_set_bytes_le(patched_rom, options_address + 0x08, 1, 1 if world.options.blind_trainers else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x08,
|
||||
struct.pack("<B", 1 if world.options.blind_trainers else 0)
|
||||
)
|
||||
|
||||
# Set exp modifier
|
||||
_set_bytes_le(patched_rom, options_address + 0x09, 2, min(max(world.options.exp_modifier.value, 0), 2**16 - 1))
|
||||
_set_bytes_le(patched_rom, options_address + 0x0B, 2, 100)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x09,
|
||||
struct.pack("<H", min(max(world.options.exp_modifier.value, 0), 2**16 - 1))
|
||||
)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x0B,
|
||||
struct.pack("<H", 100)
|
||||
)
|
||||
|
||||
# Set match trainer levels
|
||||
_set_bytes_le(patched_rom, options_address + 0x0D, 1, 1 if world.options.match_trainer_levels else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x0D,
|
||||
struct.pack("<B", 1 if world.options.match_trainer_levels else 0)
|
||||
)
|
||||
|
||||
# Set match trainer levels bonus
|
||||
if world.options.match_trainer_levels == MatchTrainerLevels.option_additive:
|
||||
match_trainer_levels_bonus = max(min(world.options.match_trainer_levels_bonus.value, 100), -100)
|
||||
_set_bytes_le(patched_rom, options_address + 0x0E, 1, match_trainer_levels_bonus) # Works with negatives
|
||||
patch.write_token(APTokenTypes.WRITE, options_address + 0x0E, struct.pack("<b", match_trainer_levels_bonus))
|
||||
elif world.options.match_trainer_levels == MatchTrainerLevels.option_multiplicative:
|
||||
_set_bytes_le(patched_rom, options_address + 0x2E, 2, world.options.match_trainer_levels_bonus.value + 100)
|
||||
_set_bytes_le(patched_rom, options_address + 0x30, 2, 100)
|
||||
patch.write_token(APTokenTypes.WRITE, options_address + 0x2E, struct.pack("<H", world.options.match_trainer_levels_bonus.value + 100))
|
||||
patch.write_token(APTokenTypes.WRITE, options_address + 0x30, struct.pack("<H", 100))
|
||||
|
||||
# Set elite four requirement
|
||||
_set_bytes_le(
|
||||
patched_rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x0F,
|
||||
1,
|
||||
1 if world.options.elite_four_requirement == EliteFourRequirement.option_gyms else 0
|
||||
struct.pack("<B", 1 if world.options.elite_four_requirement == EliteFourRequirement.option_gyms else 0)
|
||||
)
|
||||
|
||||
# Set elite four count
|
||||
_set_bytes_le(patched_rom, options_address + 0x10, 1, min(max(world.options.elite_four_count.value, 0), 8))
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x10,
|
||||
struct.pack("<B", min(max(world.options.elite_four_count.value, 0), 8))
|
||||
)
|
||||
|
||||
# Set norman requirement
|
||||
_set_bytes_le(
|
||||
patched_rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x11,
|
||||
1,
|
||||
1 if world.options.norman_requirement == NormanRequirement.option_gyms else 0
|
||||
struct.pack("<B", 1 if world.options.norman_requirement == NormanRequirement.option_gyms else 0)
|
||||
)
|
||||
|
||||
# Set norman count
|
||||
_set_bytes_le(patched_rom, options_address + 0x12, 1, min(max(world.options.norman_count.value, 0), 8))
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x12,
|
||||
struct.pack("<B", min(max(world.options.norman_count.value, 0), 8))
|
||||
)
|
||||
|
||||
# Set starting badges
|
||||
_set_bytes_le(patched_rom, options_address + 0x13, 1, starting_badges)
|
||||
patch.write_token(APTokenTypes.WRITE, options_address + 0x13, struct.pack("<B", starting_badges))
|
||||
|
||||
# Set HM badge requirements
|
||||
field_move_order = [
|
||||
@@ -455,7 +519,7 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
||||
hm_badge_counts = 0
|
||||
for i, hm in enumerate(field_move_order):
|
||||
hm_badge_counts |= (world.hm_requirements[hm] if isinstance(world.hm_requirements[hm], int) else 0xF) << (i * 4)
|
||||
_set_bytes_le(patched_rom, options_address + 0x14, 4, hm_badge_counts)
|
||||
patch.write_token(APTokenTypes.WRITE, options_address + 0x14, struct.pack("<I", hm_badge_counts))
|
||||
|
||||
# Specific badges
|
||||
for i, hm in enumerate(field_move_order):
|
||||
@@ -463,21 +527,37 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
||||
bitfield = 0
|
||||
for badge in world.hm_requirements:
|
||||
bitfield |= badge_to_bit[badge]
|
||||
_set_bytes_le(patched_rom, options_address + 0x18 + i, 1, bitfield)
|
||||
patch.write_token(APTokenTypes.WRITE, options_address + 0x18, struct.pack("<B", bitfield))
|
||||
|
||||
# Set terra/marine cave locations
|
||||
terra_cave_id = CAVE_EVENT_NAME_TO_ID[world.multiworld.get_location("TERRA_CAVE_LOCATION", world.player).item.name]
|
||||
marine_cave_id = CAVE_EVENT_NAME_TO_ID[world.multiworld.get_location("MARINE_CAVE_LOCATION", world.player).item.name]
|
||||
_set_bytes_le(patched_rom, options_address + 0x21, 1, terra_cave_id | (marine_cave_id << 4))
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x21,
|
||||
struct.pack("<B", terra_cave_id | (marine_cave_id << 4))
|
||||
)
|
||||
|
||||
# Set route 115 boulders
|
||||
_set_bytes_le(patched_rom, options_address + 0x22, 1, 1 if world.options.extra_boulders else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x22,
|
||||
struct.pack("<B", 1 if world.options.extra_boulders else 0)
|
||||
)
|
||||
|
||||
# Swap route 115 layout if bumpy slope enabled
|
||||
_set_bytes_le(patched_rom, options_address + 0x23, 1, 1 if world.options.extra_bumpy_slope else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x23,
|
||||
struct.pack("<B", 1 if world.options.extra_bumpy_slope else 0)
|
||||
)
|
||||
|
||||
# Swap route 115 layout if bumpy slope enabled
|
||||
_set_bytes_le(patched_rom, options_address + 0x24, 1, 1 if world.options.modify_118 else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x24,
|
||||
struct.pack("<B", 1 if world.options.modify_118 else 0)
|
||||
)
|
||||
|
||||
# Set removed blockers
|
||||
removed_roadblocks = world.options.remove_roadblocks.value
|
||||
@@ -489,44 +569,72 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
||||
removed_roadblocks_bitfield |= (1 << 4) if "Route 119 Aqua Grunts" in removed_roadblocks else 0
|
||||
removed_roadblocks_bitfield |= (1 << 5) if "Route 112 Magma Grunts" in removed_roadblocks else 0
|
||||
removed_roadblocks_bitfield |= (1 << 6) if "Seafloor Cavern Aqua Grunt" in removed_roadblocks else 0
|
||||
_set_bytes_le(patched_rom, options_address + 0x25, 2, removed_roadblocks_bitfield)
|
||||
patch.write_token(APTokenTypes.WRITE, options_address + 0x25, struct.pack("<H", removed_roadblocks_bitfield))
|
||||
|
||||
# Mark berry trees as randomized
|
||||
_set_bytes_le(patched_rom, options_address + 0x27, 1, 1 if world.options.berry_trees else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x27,
|
||||
struct.pack("<B", 1 if world.options.berry_trees else 0)
|
||||
)
|
||||
|
||||
# Mark dexsanity as enabled
|
||||
_set_bytes_le(patched_rom, options_address + 0x28, 1, 1 if world.options.dexsanity else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x28,
|
||||
struct.pack("<B", 1 if world.options.dexsanity else 0)
|
||||
)
|
||||
|
||||
# Mark trainersanity as enabled
|
||||
_set_bytes_le(patched_rom, options_address + 0x29, 1, 1 if world.options.trainersanity else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x29,
|
||||
struct.pack("<B", 1 if world.options.trainersanity else 0)
|
||||
)
|
||||
|
||||
# Set easter egg data
|
||||
_set_bytes_le(patched_rom, options_address + 0x2B, 1, easter_egg[0])
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x2B,
|
||||
struct.pack("<B", easter_egg[0])
|
||||
)
|
||||
|
||||
# Set normalize encounter rates
|
||||
_set_bytes_le(patched_rom, options_address + 0x2C, 1, 1 if world.options.normalize_encounter_rates else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x2C,
|
||||
struct.pack("<B", 1 if world.options.normalize_encounter_rates else 0)
|
||||
)
|
||||
|
||||
# Set allow wonder trading
|
||||
_set_bytes_le(patched_rom, options_address + 0x2D, 1, 1 if world.options.enable_wonder_trading else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x2D,
|
||||
struct.pack("<B", 1 if world.options.enable_wonder_trading else 0)
|
||||
)
|
||||
|
||||
# Set allowed to skip fanfares
|
||||
_set_bytes_le(patched_rom, options_address + 0x32, 1, 1 if world.options.fanfares else 0)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
options_address + 0x32,
|
||||
struct.pack("<B", 1 if world.options.fanfares else 0)
|
||||
)
|
||||
|
||||
if easter_egg[0] == 2:
|
||||
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (easter_egg[1] * 12) + 4, 1, 50)
|
||||
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_CUT"] * 12) + 4, 1, 1)
|
||||
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_FLY"] * 12) + 4, 1, 1)
|
||||
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_SURF"] * 12) + 4, 1, 1)
|
||||
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_STRENGTH"] * 12) + 4, 1, 1)
|
||||
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_FLASH"] * 12) + 4, 1, 1)
|
||||
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_ROCK_SMASH"] * 12) + 4, 1, 1)
|
||||
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_WATERFALL"] * 12) + 4, 1, 1)
|
||||
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_DIVE"] * 12) + 4, 1, 1)
|
||||
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_DIG"] * 12) + 4, 1, 1)
|
||||
offset = data.rom_addresses["gBattleMoves"] + 4
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (easter_egg[1] * 12), struct.pack("<B", 50))
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_CUT"] * 12), struct.pack("<B", 1))
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_FLY"] * 12), struct.pack("<B", 1))
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_SURF"] * 12), struct.pack("<B", 1))
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_STRENGTH"] * 12), struct.pack("<B", 1))
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_FLASH"] * 12), struct.pack("<B", 1))
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_ROCK_SMASH"] * 12), struct.pack("<B", 1))
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_WATERFALL"] * 12), struct.pack("<B", 1))
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_DIVE"] * 12), struct.pack("<B", 1))
|
||||
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_DIG"] * 12), struct.pack("<B", 1))
|
||||
|
||||
# Set slot auth
|
||||
for i, byte in enumerate(world.auth):
|
||||
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoInfo"] + i, 1, byte)
|
||||
patch.write_token(APTokenTypes.WRITE, data.rom_addresses["gArchipelagoInfo"], world.auth)
|
||||
|
||||
# Randomize music
|
||||
if world.options.music:
|
||||
@@ -534,11 +642,10 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
||||
randomized_looping_music = copy.copy(_LOOPING_MUSIC)
|
||||
world.random.shuffle(randomized_looping_music)
|
||||
for original_music, randomized_music in zip(_LOOPING_MUSIC, randomized_looping_music):
|
||||
_set_bytes_le(
|
||||
patched_rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gRandomizedSoundTable"] + (data.constants[original_music] * 2),
|
||||
2,
|
||||
data.constants[randomized_music]
|
||||
struct.pack("<H", data.constants[randomized_music])
|
||||
)
|
||||
|
||||
# Randomize fanfares
|
||||
@@ -547,40 +654,21 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
||||
randomized_fanfares = [fanfare_name for fanfare_name in _FANFARES]
|
||||
world.random.shuffle(randomized_fanfares)
|
||||
for i, fanfare_pair in enumerate(zip(_FANFARES.keys(), randomized_fanfares)):
|
||||
_set_bytes_le(
|
||||
patched_rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gRandomizedSoundTable"] + (data.constants[fanfare_pair[0]] * 2),
|
||||
2,
|
||||
data.constants[fanfare_pair[1]]
|
||||
struct.pack("<H", data.constants[fanfare_pair[1]])
|
||||
)
|
||||
_set_bytes_le(
|
||||
patched_rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["sFanfares"] + (i * 4) + 2,
|
||||
2,
|
||||
_FANFARES[fanfare_pair[1]]
|
||||
struct.pack("<H", _FANFARES[fanfare_pair[1]])
|
||||
)
|
||||
|
||||
# Write Output
|
||||
out_file_name = world.multiworld.get_out_file_name_base(world.player)
|
||||
output_path = os.path.join(output_directory, f"{out_file_name}.gba")
|
||||
with open(output_path, "wb") as out_file:
|
||||
out_file.write(patched_rom)
|
||||
patch = PokemonEmeraldDeltaPatch(os.path.splitext(output_path)[0] + ".apemerald", player=world.player,
|
||||
player_name=world.multiworld.get_player_name(world.player),
|
||||
patched_path=output_path)
|
||||
|
||||
patch.write()
|
||||
os.unlink(output_path)
|
||||
patch.write_file("token_data.bin", patch.get_token_binary())
|
||||
|
||||
|
||||
def get_base_rom_as_bytes() -> bytes:
|
||||
with open(get_settings().pokemon_emerald_settings.rom_file, "rb") as infile:
|
||||
base_rom_bytes = bytes(infile.read())
|
||||
|
||||
return base_rom_bytes
|
||||
|
||||
|
||||
def _set_encounter_tables(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||
def _set_encounter_tables(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
|
||||
"""
|
||||
Encounter tables are lists of
|
||||
struct {
|
||||
@@ -595,30 +683,31 @@ def _set_encounter_tables(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||
if table is not None:
|
||||
for i, species_id in enumerate(table.slots):
|
||||
address = table.address + 2 + (4 * i)
|
||||
_set_bytes_le(rom, address, 2, species_id)
|
||||
patch.write_token(APTokenTypes.WRITE, address, struct.pack("<H", species_id))
|
||||
|
||||
|
||||
def _set_species_info(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tuple[int, int]) -> None:
|
||||
def _set_species_info(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch, easter_egg: Tuple[int, int]) -> None:
|
||||
for species in world.modified_species.values():
|
||||
_set_bytes_le(rom, species.address + 6, 1, species.types[0])
|
||||
_set_bytes_le(rom, species.address + 7, 1, species.types[1])
|
||||
_set_bytes_le(rom, species.address + 8, 1, species.catch_rate)
|
||||
_set_bytes_le(rom, species.address + 22, 1, species.abilities[0])
|
||||
_set_bytes_le(rom, species.address + 23, 1, species.abilities[1])
|
||||
patch.write_token(APTokenTypes.WRITE, species.address + 6, struct.pack("<B", species.types[0]))
|
||||
patch.write_token(APTokenTypes.WRITE, species.address + 7, struct.pack("<B", species.types[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, species.address + 8, struct.pack("<B", species.catch_rate))
|
||||
|
||||
if easter_egg[0] == 3:
|
||||
_set_bytes_le(rom, species.address + 22, 1, easter_egg[1])
|
||||
_set_bytes_le(rom, species.address + 23, 1, easter_egg[1])
|
||||
patch.write_token(APTokenTypes.WRITE, species.address + 22, struct.pack("<B", easter_egg[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, species.address + 23, struct.pack("<B", easter_egg[1]))
|
||||
else:
|
||||
patch.write_token(APTokenTypes.WRITE, species.address + 22, struct.pack("<B", species.abilities[0]))
|
||||
patch.write_token(APTokenTypes.WRITE, species.address + 23, struct.pack("<B", species.abilities[1]))
|
||||
|
||||
for i, learnset_move in enumerate(species.learnset):
|
||||
level_move = learnset_move.level << 9 | learnset_move.move_id
|
||||
if easter_egg[0] == 2:
|
||||
level_move = learnset_move.level << 9 | easter_egg[1]
|
||||
|
||||
_set_bytes_le(rom, species.learnset_address + (i * 2), 2, level_move)
|
||||
patch.write_token(APTokenTypes.WRITE, species.learnset_address + (i * 2), struct.pack("<H", level_move))
|
||||
|
||||
|
||||
def _set_opponents(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tuple[int, int]) -> None:
|
||||
def _set_opponents(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch, easter_egg: Tuple[int, int]) -> None:
|
||||
for trainer in world.modified_trainers:
|
||||
party_address = trainer.party.address
|
||||
|
||||
@@ -632,53 +721,50 @@ def _set_opponents(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tup
|
||||
pokemon_address = party_address + (i * pokemon_data_size)
|
||||
|
||||
# Replace species
|
||||
_set_bytes_le(rom, pokemon_address + 0x04, 2, pokemon.species_id)
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x04, struct.pack("<H", pokemon.species_id))
|
||||
|
||||
# Replace custom moves if applicable
|
||||
if trainer.party.pokemon_data_type == TrainerPokemonDataTypeEnum.NO_ITEM_CUSTOM_MOVES:
|
||||
if easter_egg[0] == 2:
|
||||
_set_bytes_le(rom, pokemon_address + 0x06, 2, easter_egg[1])
|
||||
_set_bytes_le(rom, pokemon_address + 0x08, 2, easter_egg[1])
|
||||
_set_bytes_le(rom, pokemon_address + 0x0A, 2, easter_egg[1])
|
||||
_set_bytes_le(rom, pokemon_address + 0x0C, 2, easter_egg[1])
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x06, struct.pack("<H", easter_egg[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x08, struct.pack("<H", easter_egg[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0A, struct.pack("<H", easter_egg[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0C, struct.pack("<H", easter_egg[1]))
|
||||
else:
|
||||
_set_bytes_le(rom, pokemon_address + 0x06, 2, pokemon.moves[0])
|
||||
_set_bytes_le(rom, pokemon_address + 0x08, 2, pokemon.moves[1])
|
||||
_set_bytes_le(rom, pokemon_address + 0x0A, 2, pokemon.moves[2])
|
||||
_set_bytes_le(rom, pokemon_address + 0x0C, 2, pokemon.moves[3])
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x06, struct.pack("<H", pokemon.moves[0]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x08, struct.pack("<H", pokemon.moves[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0A, struct.pack("<H", pokemon.moves[2]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0C, struct.pack("<H", pokemon.moves[3]))
|
||||
elif trainer.party.pokemon_data_type == TrainerPokemonDataTypeEnum.ITEM_CUSTOM_MOVES:
|
||||
if easter_egg[0] == 2:
|
||||
_set_bytes_le(rom, pokemon_address + 0x08, 2, easter_egg[1])
|
||||
_set_bytes_le(rom, pokemon_address + 0x0A, 2, easter_egg[1])
|
||||
_set_bytes_le(rom, pokemon_address + 0x0C, 2, easter_egg[1])
|
||||
_set_bytes_le(rom, pokemon_address + 0x0E, 2, easter_egg[1])
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x08, struct.pack("<H", easter_egg[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0A, struct.pack("<H", easter_egg[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0C, struct.pack("<H", easter_egg[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0E, struct.pack("<H", easter_egg[1]))
|
||||
else:
|
||||
_set_bytes_le(rom, pokemon_address + 0x08, 2, pokemon.moves[0])
|
||||
_set_bytes_le(rom, pokemon_address + 0x0A, 2, pokemon.moves[1])
|
||||
_set_bytes_le(rom, pokemon_address + 0x0C, 2, pokemon.moves[2])
|
||||
_set_bytes_le(rom, pokemon_address + 0x0E, 2, pokemon.moves[3])
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x08, struct.pack("<H", pokemon.moves[0]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0A, struct.pack("<H", pokemon.moves[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0C, struct.pack("<H", pokemon.moves[2]))
|
||||
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0E, struct.pack("<H", pokemon.moves[3]))
|
||||
|
||||
|
||||
def _set_legendary_encounters(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||
def _set_legendary_encounters(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
|
||||
for encounter in world.modified_legendary_encounters:
|
||||
_set_bytes_le(rom, encounter.address, 2, encounter.species_id)
|
||||
patch.write_token(APTokenTypes.WRITE, encounter.address, struct.pack("<H", encounter.species_id))
|
||||
|
||||
|
||||
def _set_misc_pokemon(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||
def _set_misc_pokemon(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
|
||||
for encounter in world.modified_misc_pokemon:
|
||||
_set_bytes_le(rom, encounter.address, 2, encounter.species_id)
|
||||
patch.write_token(APTokenTypes.WRITE, encounter.address, struct.pack("<H", encounter.species_id))
|
||||
|
||||
|
||||
def _set_starters(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||
address = data.rom_addresses["sStarterMon"]
|
||||
(starter_1, starter_2, starter_3) = world.modified_starters
|
||||
|
||||
_set_bytes_le(rom, address + 0, 2, starter_1)
|
||||
_set_bytes_le(rom, address + 2, 2, starter_2)
|
||||
_set_bytes_le(rom, address + 4, 2, starter_3)
|
||||
def _set_starters(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
|
||||
patch.write_token(APTokenTypes.WRITE, data.rom_addresses["sStarterMon"] + 0, struct.pack("<H", world.modified_starters[0]))
|
||||
patch.write_token(APTokenTypes.WRITE, data.rom_addresses["sStarterMon"] + 2, struct.pack("<H", world.modified_starters[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, data.rom_addresses["sStarterMon"] + 4, struct.pack("<H", world.modified_starters[2]))
|
||||
|
||||
|
||||
def _set_tm_moves(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tuple[int, int]) -> None:
|
||||
def _set_tm_moves(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch, easter_egg: Tuple[int, int]) -> None:
|
||||
tmhm_list_address = data.rom_addresses["sTMHMMoves"]
|
||||
|
||||
for i, move in enumerate(world.modified_tmhm_moves):
|
||||
@@ -686,19 +772,24 @@ def _set_tm_moves(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tupl
|
||||
if i >= 50:
|
||||
break
|
||||
|
||||
_set_bytes_le(rom, tmhm_list_address + (i * 2), 2, move)
|
||||
if easter_egg[0] == 2:
|
||||
_set_bytes_le(rom, tmhm_list_address + (i * 2), 2, easter_egg[1])
|
||||
patch.write_token(APTokenTypes.WRITE, tmhm_list_address + (i * 2), struct.pack("<H", easter_egg[1]))
|
||||
else:
|
||||
patch.write_token(APTokenTypes.WRITE, tmhm_list_address + (i * 2), struct.pack("<H", move))
|
||||
|
||||
|
||||
def _set_tmhm_compatibility(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||
def _set_tmhm_compatibility(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
|
||||
learnsets_address = data.rom_addresses["gTMHMLearnsets"]
|
||||
|
||||
for species in world.modified_species.values():
|
||||
_set_bytes_le(rom, learnsets_address + (species.species_id * 8), 8, species.tm_hm_compatibility)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
learnsets_address + (species.species_id * 8),
|
||||
struct.pack("<Q", species.tm_hm_compatibility)
|
||||
)
|
||||
|
||||
|
||||
def _randomize_opponent_battle_type(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||
def _randomize_opponent_battle_type(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
|
||||
probability = world.options.double_battle_chance.value / 100
|
||||
|
||||
battle_type_map = {
|
||||
@@ -710,26 +801,29 @@ def _randomize_opponent_battle_type(world: "PokemonEmeraldWorld", rom: bytearray
|
||||
|
||||
for trainer_data in data.trainers:
|
||||
if trainer_data.script_address != 0 and len(trainer_data.party.pokemon) > 1:
|
||||
original_battle_type = rom[trainer_data.script_address + 1]
|
||||
original_battle_type = trainer_data.battle_type
|
||||
if original_battle_type in battle_type_map: # Don't touch anything other than regular single battles
|
||||
if world.random.random() < probability:
|
||||
# Set the trainer to be a double battle
|
||||
_set_bytes_le(rom, trainer_data.address + 0x18, 1, 1)
|
||||
patch.write_token(APTokenTypes.WRITE, trainer_data.address + 0x18, struct.pack("<B", 1))
|
||||
|
||||
# Swap the battle type in the script for the purpose of loading the right text
|
||||
# and setting data to the right places
|
||||
_set_bytes_le(
|
||||
rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
trainer_data.script_address + 1,
|
||||
1,
|
||||
battle_type_map[original_battle_type]
|
||||
struct.pack("<B", battle_type_map[original_battle_type])
|
||||
)
|
||||
|
||||
|
||||
def _randomize_move_tutor_moves(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tuple[int, int]) -> None:
|
||||
def _randomize_move_tutor_moves(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch, easter_egg: Tuple[int, int]) -> None:
|
||||
if easter_egg[0] == 2:
|
||||
for i in range(30):
|
||||
_set_bytes_le(rom, data.rom_addresses["gTutorMoves"] + (i * 2), 2, easter_egg[1])
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gTutorMoves"] + (i * 2),
|
||||
struct.pack("<H", easter_egg[1])
|
||||
)
|
||||
else:
|
||||
if world.options.tm_tutor_moves:
|
||||
new_tutor_moves = []
|
||||
@@ -737,17 +831,27 @@ def _randomize_move_tutor_moves(world: "PokemonEmeraldWorld", rom: bytearray, ea
|
||||
new_move = get_random_move(world.random, set(new_tutor_moves) | world.blacklisted_moves | HM_MOVES)
|
||||
new_tutor_moves.append(new_move)
|
||||
|
||||
_set_bytes_le(rom, data.rom_addresses["gTutorMoves"] + (i * 2), 2, new_move)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gTutorMoves"] + (i * 2),
|
||||
struct.pack("<H", new_move)
|
||||
)
|
||||
|
||||
# Always set Fortree move tutor to Dig
|
||||
_set_bytes_le(rom, data.rom_addresses["gTutorMoves"] + (24 * 2), 2, data.constants["MOVE_DIG"])
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["gTutorMoves"] + (24 * 2),
|
||||
struct.pack("<H", data.constants["MOVE_DIG"])
|
||||
)
|
||||
|
||||
# Modify compatibility
|
||||
if world.options.tm_tutor_compatibility.value != -1:
|
||||
for species in data.species.values():
|
||||
_set_bytes_le(
|
||||
rom,
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
data.rom_addresses["sTutorLearnsets"] + (species.species_id * 4),
|
||||
4,
|
||||
bool_array_to_int([world.random.randrange(0, 100) < world.options.tm_tutor_compatibility.value for _ in range(32)])
|
||||
struct.pack("<I", bool_array_to_int([
|
||||
world.random.randrange(0, 100) < world.options.tm_tutor_compatibility.value
|
||||
for _ in range(32)
|
||||
]))
|
||||
)
|
||||
|
||||
@@ -464,7 +464,7 @@ def set_rules(world: "PokemonEmeraldWorld") -> None:
|
||||
|
||||
# Slateport City
|
||||
set_rule(
|
||||
get_entrance("REGION_SLATEPORT_CITY/MAIN -> REGION_ROUTE134/WEST"),
|
||||
get_entrance("REGION_SLATEPORT_CITY/MAIN -> REGION_SLATEPORT_CITY/WATER"),
|
||||
hm_rules["HM03 Surf"]
|
||||
)
|
||||
set_rule(
|
||||
@@ -1531,6 +1531,10 @@ def set_rules(world: "PokemonEmeraldWorld") -> None:
|
||||
if world.options.dexsanity:
|
||||
for i in range(NUM_REAL_SPECIES):
|
||||
species = data.species[NATIONAL_ID_TO_SPECIES_ID[i + 1]]
|
||||
|
||||
if species.species_id in world.blacklisted_wilds:
|
||||
continue
|
||||
|
||||
set_rule(
|
||||
get_location(f"Pokedex - {species.label}"),
|
||||
lambda state, species_name=species.name: state.has(f"CATCH_{species_name}", world.player)
|
||||
@@ -1538,7 +1542,8 @@ def set_rules(world: "PokemonEmeraldWorld") -> None:
|
||||
|
||||
# Legendary hunt prevents Latios from being a wild spawn so the roamer
|
||||
# can be tracked, and also guarantees that the roamer is a Latios.
|
||||
if world.options.goal == Goal.option_legendary_hunt:
|
||||
if world.options.goal == Goal.option_legendary_hunt and \
|
||||
data.constants["SPECIES_LATIOS"] not in world.blacklisted_wilds:
|
||||
set_rule(
|
||||
get_location(f"Pokedex - Latios"),
|
||||
lambda state: state.has("EVENT_ENCOUNTER_LATIOS", world.player)
|
||||
|
||||
@@ -59,6 +59,10 @@ class TestSurf(PokemonEmeraldTestBase):
|
||||
self.assertFalse(self.can_reach_entrance("REGION_ROUTE119/UPPER -> REGION_FORTREE_CITY/MAIN"))
|
||||
self.assertFalse(self.can_reach_entrance("MAP_FORTREE_CITY:3/MAP_FORTREE_CITY_MART:0"))
|
||||
|
||||
# Slateport Access
|
||||
self.collect_by_name(["HM06 Rock Smash", "Dynamo Badge", "Mach Bike"])
|
||||
self.assertFalse(self.can_reach_region("MAP_SLATEPORT_CITY_WATER_ENCOUNTERS"))
|
||||
|
||||
def test_accessible_with_surf_only(self) -> None:
|
||||
self.collect_by_name(["HM03 Surf", "Balance Badge"])
|
||||
self.assertTrue(self.can_reach_location(location_name_to_label("ITEM_PETALBURG_CITY_ETHER")))
|
||||
@@ -70,6 +74,7 @@ class TestSurf(PokemonEmeraldTestBase):
|
||||
self.assertTrue(self.can_reach_entrance("REGION_ROUTE119/UPPER -> REGION_FORTREE_CITY/MAIN"))
|
||||
self.assertTrue(self.can_reach_entrance("MAP_FORTREE_CITY:3/MAP_FORTREE_CITY_MART:0"))
|
||||
self.assertTrue(self.can_reach_location(location_name_to_label("BADGE_4")))
|
||||
self.assertTrue(self.can_reach_region("MAP_SLATEPORT_CITY_WATER_ENCOUNTERS"))
|
||||
|
||||
|
||||
class TestFreeFly(PokemonEmeraldTestBase):
|
||||
|
||||
@@ -650,7 +650,7 @@ campaign_final_mission_locations: Dict[SC2Campaign, SC2CampaignGoal] = {
|
||||
SC2Campaign.PROLOGUE: SC2CampaignGoal(SC2Mission.EVIL_AWOKEN, "Evil Awoken: Victory"),
|
||||
SC2Campaign.LOTV: SC2CampaignGoal(SC2Mission.SALVATION, "Salvation: Victory"),
|
||||
SC2Campaign.EPILOGUE: None,
|
||||
SC2Campaign.NCO: None,
|
||||
SC2Campaign.NCO: SC2CampaignGoal(SC2Mission.END_GAME, "End Game: Victory"),
|
||||
}
|
||||
|
||||
campaign_alt_final_mission_locations: Dict[SC2Campaign, Dict[SC2Mission, str]] = {
|
||||
@@ -683,7 +683,6 @@ campaign_alt_final_mission_locations: Dict[SC2Campaign, Dict[SC2Mission, str]] =
|
||||
SC2Mission.THE_ESSENCE_OF_ETERNITY: "The Essence of Eternity: Victory",
|
||||
},
|
||||
SC2Campaign.NCO: {
|
||||
SC2Mission.END_GAME: "End Game: Victory",
|
||||
SC2Mission.FLASHPOINT: "Flashpoint: Victory",
|
||||
SC2Mission.DARK_SKIES: "Dark Skies: Victory",
|
||||
SC2Mission.NIGHT_TERRORS: "Night Terrors: Victory",
|
||||
@@ -709,10 +708,10 @@ def get_goal_location(mission: SC2Mission) -> Union[str, None]:
|
||||
return primary_campaign_goal.location
|
||||
|
||||
campaign_alt_goals = campaign_alt_final_mission_locations[campaign]
|
||||
if campaign_alt_goals is not None:
|
||||
if campaign_alt_goals is not None and mission in campaign_alt_goals:
|
||||
return campaign_alt_goals.get(mission)
|
||||
|
||||
return None
|
||||
return mission.mission_name + ": Victory"
|
||||
|
||||
|
||||
def get_campaign_potential_goal_missions(campaign: SC2Campaign) -> List[SC2Mission]:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Callable, Dict, List, Set, Union, Tuple
|
||||
from typing import Callable, Dict, List, Set, Union, Tuple, Optional
|
||||
from BaseClasses import Item, Location
|
||||
from .Items import get_full_item_list, spider_mine_sources, second_pass_placeable_items, progressive_if_nco, \
|
||||
progressive_if_ext, spear_of_adun_calldowns, spear_of_adun_castable_passives, nova_equipment
|
||||
@@ -69,21 +69,39 @@ def filter_missions(world: World) -> Dict[MissionPools, List[SC2Mission]]:
|
||||
return mission_pools
|
||||
|
||||
# Finding the goal map
|
||||
goal_priorities = {campaign: get_campaign_goal_priority(campaign, excluded_missions) for campaign in enabled_campaigns}
|
||||
goal_level = max(goal_priorities.values())
|
||||
candidate_campaigns: List[SC2Campaign] = [campaign for campaign, goal_priority in goal_priorities.items() if goal_priority == goal_level]
|
||||
candidate_campaigns.sort(key=lambda it: it.id)
|
||||
goal_campaign = world.random.choice(candidate_campaigns)
|
||||
primary_goal = campaign_final_mission_locations[goal_campaign]
|
||||
if primary_goal is None or primary_goal.mission in excluded_missions:
|
||||
# No primary goal or its mission is excluded
|
||||
candidate_missions = list(campaign_alt_final_mission_locations[goal_campaign].keys())
|
||||
candidate_missions = [mission for mission in candidate_missions if mission not in excluded_missions]
|
||||
if len(candidate_missions) == 0:
|
||||
raise Exception("There are no valid goal missions. Please exclude fewer missions.")
|
||||
goal_mission = world.random.choice(candidate_missions)
|
||||
goal_mission: Optional[SC2Mission] = None
|
||||
if mission_order_type in campaign_depending_orders:
|
||||
# Prefer long campaigns over shorter ones and harder missions over easier ones
|
||||
goal_priorities = {campaign: get_campaign_goal_priority(campaign, excluded_missions) for campaign in enabled_campaigns}
|
||||
goal_level = max(goal_priorities.values())
|
||||
candidate_campaigns: List[SC2Campaign] = [campaign for campaign, goal_priority in goal_priorities.items() if goal_priority == goal_level]
|
||||
candidate_campaigns.sort(key=lambda it: it.id)
|
||||
|
||||
goal_campaign = world.random.choice(candidate_campaigns)
|
||||
primary_goal = campaign_final_mission_locations[goal_campaign]
|
||||
if primary_goal is None or primary_goal.mission in excluded_missions:
|
||||
# No primary goal or its mission is excluded
|
||||
candidate_missions = list(campaign_alt_final_mission_locations[goal_campaign].keys())
|
||||
candidate_missions = [mission for mission in candidate_missions if mission not in excluded_missions]
|
||||
if len(candidate_missions) == 0:
|
||||
raise Exception("There are no valid goal missions. Please exclude fewer missions.")
|
||||
goal_mission = world.random.choice(candidate_missions)
|
||||
else:
|
||||
goal_mission = primary_goal.mission
|
||||
else:
|
||||
goal_mission = primary_goal.mission
|
||||
# Find one of the missions with the hardest difficulty
|
||||
available_missions: List[SC2Mission] = \
|
||||
[mission for mission in SC2Mission
|
||||
if (mission not in excluded_missions and mission.campaign in enabled_campaigns)]
|
||||
available_missions.sort(key=lambda it: it.id)
|
||||
# Loop over pools, from hardest to easiest
|
||||
for mission_pool in range(MissionPools.VERY_HARD, MissionPools.STARTER - 1, -1):
|
||||
pool_missions: List[SC2Mission] = [mission for mission in available_missions if mission.pool == mission_pool]
|
||||
if pool_missions:
|
||||
goal_mission = world.random.choice(pool_missions)
|
||||
break
|
||||
if goal_mission is None:
|
||||
raise Exception("There are no valid goal missions. Please exclude fewer missions.")
|
||||
|
||||
# Excluding missions
|
||||
for difficulty, mission_pool in mission_pools.items():
|
||||
|
||||
@@ -6,23 +6,23 @@ The player options page for this game contains all the options you need to confi
|
||||
options page link: [SM64EX Player Options Page](../player-options).
|
||||
|
||||
## What does randomization do to this game?
|
||||
All 120 Stars, the 3 Cap Switches, the Basement and Secound Floor Key are now Location Checks and may contain Items for different games as well
|
||||
as different Items from within SM64.
|
||||
All 120 Stars, the 3 Cap Switches, the Basement and Second Floor Key are now location checks and may contain items for different games as well
|
||||
as different items from within SM64.
|
||||
|
||||
|
||||
## What is the goal of SM64EX when randomized?
|
||||
As in most Mario Games, save the Princess!
|
||||
As in most Mario games, save the Princess!
|
||||
|
||||
## Which items can be in another player's world?
|
||||
Any of the 120 Stars, and the two Castle Keys. Additionally, Cap Switches are also considered "Items" and the "!"-Boxes will only be active
|
||||
when someone collects the corresponding Cap Switch Item.
|
||||
when someone collects the corresponding Cap Switch item.
|
||||
|
||||
## What does another world's item look like in SM64EX?
|
||||
The Items are visually unchanged, though after collecting a Message will pop up to inform you what you collected,
|
||||
The items are visually unchanged, though after collecting a message will pop up to inform you what you collected,
|
||||
and who will receive it.
|
||||
|
||||
## When the player receives an item, what happens?
|
||||
When you receive an Item, a Message will pop up to inform you where you received the Item from,
|
||||
When you receive an item, a message will pop up to inform you where you received the item from,
|
||||
and which one it is.
|
||||
|
||||
NOTE: The Secret Star count in the Menu is broken.
|
||||
NOTE: The Secret Star count in the menu is broken.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Required Software
|
||||
|
||||
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases).
|
||||
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) (optional, but recommended).
|
||||
- [SNI](https://github.com/alttpo/sni/releases). This is automatically included with your Archipelago installation above.
|
||||
- SNI is not compatible with (Q)Usb2Snes.
|
||||
- Hardware or software capable of loading and playing SNES ROM files, including:
|
||||
@@ -14,6 +14,7 @@
|
||||
- An SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), or other compatible hardware. **note:
|
||||
modded SNES minis are currently not supported by SNI. Some users have claimed success with QUsb2Snes for this system,
|
||||
but it is not supported.**
|
||||
- A modern web browser to run the client.
|
||||
- Your legally obtained Secret of Evermore US ROM file, probably named `Secret of Evermore (USA).sfc`
|
||||
|
||||
## Create a Config (.yaml) File
|
||||
@@ -60,12 +61,12 @@ page: [Evermizer apbpatch Page](https://evermizer.com/apbpatch)
|
||||
|
||||
### Connect to SNI
|
||||
|
||||
#### With an emulator
|
||||
|
||||
Start SNI either from the Archipelago install folder or the stand-alone version. If this is its first time launching,
|
||||
you may be prompted to allow it to communicate through the Windows Firewall.
|
||||
|
||||
#### snes9x-nwa
|
||||
#### With an emulator
|
||||
|
||||
##### snes9x-nwa
|
||||
|
||||
1. Click on the Network Menu and check **Enable Emu Network Control**
|
||||
2. Load your ROM file if it hasn't already been loaded.
|
||||
|
||||
@@ -24,7 +24,7 @@ item_table: Dict[str, ItemData] = {
|
||||
"Red Candle": ItemData(107, progression),
|
||||
"Book of Magic": ItemData(108, progression),
|
||||
"Magical Key": ItemData(109, useful),
|
||||
"Red Ring": ItemData(110, useful),
|
||||
"Red Ring": ItemData(110, progression),
|
||||
"Silver Arrow": ItemData(111, progression),
|
||||
"Sword": ItemData(112, progression),
|
||||
"White Sword": ItemData(113, progression),
|
||||
@@ -37,7 +37,7 @@ item_table: Dict[str, ItemData] = {
|
||||
"Food": ItemData(120, progression),
|
||||
"Water of Life (Blue)": ItemData(121, useful),
|
||||
"Water of Life (Red)": ItemData(122, useful),
|
||||
"Blue Ring": ItemData(123, useful),
|
||||
"Blue Ring": ItemData(123, progression),
|
||||
"Triforce Fragment": ItemData(124, progression),
|
||||
"Power Bracelet": ItemData(125, useful),
|
||||
"Small Key": ItemData(126, filler),
|
||||
|
||||
@@ -28,6 +28,7 @@ def set_rules(tloz_world: "TLoZWorld"):
|
||||
or location.name not in dangerous_weapon_locations:
|
||||
add_rule(world.get_location(location.name, player),
|
||||
lambda state: state.has_group("weapons", player))
|
||||
# This part of the loop sets up an expected amount of defense needed for each dungeon
|
||||
if i > 0: # Don't need an extra heart for Level 1
|
||||
add_rule(world.get_location(location.name, player),
|
||||
lambda state, hearts=i: state.has("Heart Container", player, hearts) or
|
||||
@@ -49,7 +50,7 @@ def set_rules(tloz_world: "TLoZWorld"):
|
||||
for location in level.locations:
|
||||
add_rule(world.get_location(location.name, player),
|
||||
lambda state: state.has_group("candles", player)
|
||||
or (state.has("Magical Rod", player) and state.has("Book", player)))
|
||||
or (state.has("Magical Rod", player) and state.has("Book of Magic", player)))
|
||||
|
||||
# Everything from 5 on up has gaps
|
||||
for level in tloz_world.levels[5:]:
|
||||
@@ -84,6 +85,11 @@ def set_rules(tloz_world: "TLoZWorld"):
|
||||
add_rule(world.get_location(location, player),
|
||||
lambda state: state.has_group("swords", player) or state.has("Magical Rod", player))
|
||||
|
||||
# Candle access for Level 8
|
||||
for location in tloz_world.levels[8].locations:
|
||||
add_rule(world.get_location(location.name, player),
|
||||
lambda state: state.has_group("candles", player))
|
||||
|
||||
add_rule(world.get_location("Level 8 Item (Magical Key)", player),
|
||||
lambda state: state.has("Bow", player) and state.has_group("arrows", player))
|
||||
if options.ExpandedPool:
|
||||
|
||||
@@ -260,11 +260,11 @@ class TLoZWorld(World):
|
||||
rom_data[location_id] = item_id
|
||||
|
||||
# We shuffle the tiers of rupee caves. Caves that shared a value before still will.
|
||||
secret_caves = self.multiworld.per_slot_randoms[self.player].sample(sorted(secret_money_ids), 3)
|
||||
secret_caves = self.random.sample(sorted(secret_money_ids), 3)
|
||||
secret_cave_money_amounts = [20, 50, 100]
|
||||
for i, amount in enumerate(secret_cave_money_amounts):
|
||||
# Giving approximately double the money to keep grinding down
|
||||
amount = amount * self.multiworld.per_slot_randoms[self.player].triangular(1.5, 2.5)
|
||||
amount = amount * self.random.triangular(1.5, 2.5)
|
||||
secret_cave_money_amounts[i] = int(amount)
|
||||
for i, cave in enumerate(secret_caves):
|
||||
rom_data[secret_money_ids[cave]] = secret_cave_money_amounts[i]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Dict, List, Any
|
||||
from typing import Dict, List, Any, Tuple, TypedDict
|
||||
from logging import warning
|
||||
from BaseClasses import Region, Location, Item, Tutorial, ItemClassification
|
||||
from BaseClasses import Region, Location, Item, Tutorial, ItemClassification, MultiWorld
|
||||
from .items import item_name_to_id, item_table, item_name_groups, fool_tiers, filler_items, slot_data_item_names
|
||||
from .locations import location_table, location_name_groups, location_name_to_id, hexagon_locations
|
||||
from .rules import set_location_rules, set_region_rules, randomize_ability_unlocks, gold_hexagon
|
||||
@@ -8,8 +8,9 @@ from .er_rules import set_er_location_rules
|
||||
from .regions import tunic_regions
|
||||
from .er_scripts import create_er_regions
|
||||
from .er_data import portal_mapping
|
||||
from .options import TunicOptions
|
||||
from .options import TunicOptions, EntranceRando
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from worlds.generic import PlandoConnection
|
||||
from decimal import Decimal, ROUND_HALF_UP
|
||||
|
||||
|
||||
@@ -36,6 +37,13 @@ class TunicLocation(Location):
|
||||
game: str = "TUNIC"
|
||||
|
||||
|
||||
class SeedGroup(TypedDict):
|
||||
logic_rules: int # logic rules value
|
||||
laurels_at_10_fairies: bool # laurels location value
|
||||
fixed_shop: bool # fixed shop value
|
||||
plando: List[PlandoConnection] # consolidated list of plando connections for the seed group
|
||||
|
||||
|
||||
class TunicWorld(World):
|
||||
"""
|
||||
Explore a land filled with lost legends, ancient powers, and ferocious monsters in TUNIC, an isometric action game
|
||||
@@ -57,8 +65,21 @@ class TunicWorld(World):
|
||||
slot_data_items: List[TunicItem]
|
||||
tunic_portal_pairs: Dict[str, str]
|
||||
er_portal_hints: Dict[int, str]
|
||||
seed_groups: Dict[str, SeedGroup] = {}
|
||||
|
||||
def generate_early(self) -> None:
|
||||
if self.multiworld.plando_connections[self.player]:
|
||||
for index, cxn in enumerate(self.multiworld.plando_connections[self.player]):
|
||||
# making shops second to simplify other things later
|
||||
if cxn.entrance.startswith("Shop"):
|
||||
replacement = PlandoConnection(cxn.exit, "Shop Portal", "both")
|
||||
self.multiworld.plando_connections[self.player].remove(cxn)
|
||||
self.multiworld.plando_connections[self.player].insert(index, replacement)
|
||||
elif cxn.exit.startswith("Shop"):
|
||||
replacement = PlandoConnection(cxn.entrance, "Shop Portal", "both")
|
||||
self.multiworld.plando_connections[self.player].remove(cxn)
|
||||
self.multiworld.plando_connections[self.player].insert(index, replacement)
|
||||
|
||||
# Universal tracker stuff, shouldn't do anything in standard gen
|
||||
if hasattr(self.multiworld, "re_gen_passthrough"):
|
||||
if "TUNIC" in self.multiworld.re_gen_passthrough:
|
||||
@@ -74,6 +95,58 @@ class TunicWorld(World):
|
||||
self.options.entrance_rando.value = passthrough["entrance_rando"]
|
||||
self.options.shuffle_ladders.value = passthrough["shuffle_ladders"]
|
||||
|
||||
@classmethod
|
||||
def stage_generate_early(cls, multiworld: MultiWorld) -> None:
|
||||
tunic_worlds: Tuple[TunicWorld] = multiworld.get_game_worlds("TUNIC")
|
||||
for tunic in tunic_worlds:
|
||||
# if it's one of the options, then it isn't a custom seed group
|
||||
if tunic.options.entrance_rando.value in EntranceRando.options:
|
||||
continue
|
||||
group = tunic.options.entrance_rando.value
|
||||
# if this is the first world in the group, set the rules equal to its rules
|
||||
if group not in cls.seed_groups:
|
||||
cls.seed_groups[group] = SeedGroup(logic_rules=tunic.options.logic_rules.value,
|
||||
laurels_at_10_fairies=tunic.options.laurels_location == 3,
|
||||
fixed_shop=bool(tunic.options.fixed_shop),
|
||||
plando=multiworld.plando_connections[tunic.player])
|
||||
continue
|
||||
|
||||
# lower value is more restrictive
|
||||
if tunic.options.logic_rules.value < cls.seed_groups[group]["logic_rules"]:
|
||||
cls.seed_groups[group]["logic_rules"] = tunic.options.logic_rules.value
|
||||
# laurels at 10 fairies changes logic for secret gathering place placement
|
||||
if tunic.options.laurels_location == 3:
|
||||
cls.seed_groups[group]["laurels_at_10_fairies"] = True
|
||||
# fewer shops, one at windmill
|
||||
if tunic.options.fixed_shop:
|
||||
cls.seed_groups[group]["fixed_shop"] = True
|
||||
|
||||
if multiworld.plando_connections[tunic.player]:
|
||||
# loop through the connections in the player's yaml
|
||||
for cxn in multiworld.plando_connections[tunic.player]:
|
||||
new_cxn = True
|
||||
for group_cxn in cls.seed_groups[group]["plando"]:
|
||||
# if neither entrance nor exit match anything in the group, add to group
|
||||
if ((cxn.entrance == group_cxn.entrance and cxn.exit == group_cxn.exit)
|
||||
or (cxn.exit == group_cxn.entrance and cxn.entrance == group_cxn.exit)):
|
||||
new_cxn = False
|
||||
break
|
||||
|
||||
# check if this pair is the same as a pair in the group already
|
||||
is_mismatched = (
|
||||
cxn.entrance == group_cxn.entrance and cxn.exit != group_cxn.exit
|
||||
or cxn.entrance == group_cxn.exit and cxn.exit != group_cxn.entrance
|
||||
or cxn.exit == group_cxn.entrance and cxn.entrance != group_cxn.exit
|
||||
or cxn.exit == group_cxn.exit and cxn.entrance != group_cxn.entrance
|
||||
)
|
||||
if is_mismatched:
|
||||
raise Exception(f"TUNIC: Conflict between seed group {group}'s plando "
|
||||
f"connection {group_cxn.entrance} <-> {group_cxn.exit} and "
|
||||
f"{tunic.multiworld.get_player_name(tunic.player)}'s plando "
|
||||
f"connection {cxn.entrance} <-> {cxn.exit}")
|
||||
if new_cxn:
|
||||
cls.seed_groups[group]["plando"].append(cxn)
|
||||
|
||||
def create_item(self, name: str) -> TunicItem:
|
||||
item_data = item_table[name]
|
||||
return TunicItem(name, item_data.classification, self.item_name_to_id[name], self.player)
|
||||
@@ -140,7 +213,7 @@ class TunicWorld(World):
|
||||
if self.options.shuffle_ladders:
|
||||
ladder_count = 0
|
||||
for item_name, item_data in item_table.items():
|
||||
if item_data.item_group == "ladders":
|
||||
if item_data.item_group == "Ladders":
|
||||
items_to_create[item_name] = 1
|
||||
ladder_count += 1
|
||||
remove_filler(ladder_count)
|
||||
@@ -259,7 +332,7 @@ class TunicWorld(World):
|
||||
name, connection = connection
|
||||
# for LS entrances, we just want to give the portal name
|
||||
if "(LS)" in name:
|
||||
name, _ = name.split(" (LS) ")
|
||||
name = name.split(" (LS) ", 1)[0]
|
||||
# was getting some cases like Library Grave -> Library Grave -> other place
|
||||
if name in portal_names and name != previous_name:
|
||||
previous_name = name
|
||||
|
||||
@@ -64,11 +64,8 @@ For the Entrance Randomizer:
|
||||
- The portal in the trophy room of the Old House is active from the start.
|
||||
- The elevator in Cathedral is immediately usable without activating the fuse. Activating the fuse does nothing.
|
||||
|
||||
## What item groups are there?
|
||||
Bombs, consumables (non-bomb ones), weapons, melee weapons (stick and sword), keys, hexagons, offerings, hero relics, cards, golden treasures, money, pages, and abilities (the three ability pages). There are also a few groups being used for singular items: laurels, orb, dagger, magic rod, holy cross, prayer, icebolt, and progressive sword.
|
||||
|
||||
## What location groups are there?
|
||||
Holy cross (for all holy cross checks), fairies (for the two fairy checks), well (for the coin well checks), shop, bosses (for the bosses with checks associated with them), hero relic (for the 6 hero grave checks), and ladders (for the ladder items when you have shuffle ladders enabled).
|
||||
## Does this game have item and location groups?
|
||||
Yes! To find what they are, open up the Archipelago Text Client while connected to a TUNIC session and type in `/item_groups` or `/location_groups`.
|
||||
|
||||
## Is Connection Plando supported?
|
||||
Yes. The host needs to enable it in their `host.yaml`, and the player's yaml needs to contain a plando_connections block.
|
||||
|
||||
@@ -1452,7 +1452,7 @@ def set_er_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int])
|
||||
|
||||
# Beneath the Vault
|
||||
set_rule(multiworld.get_location("Beneath the Fortress - Bridge", player),
|
||||
lambda state: state.has_group("melee weapons", player, 1) or state.has_any({laurels, fire_wand}, player))
|
||||
lambda state: state.has_group("Melee Weapons", player, 1) or state.has_any({laurels, fire_wand}, player))
|
||||
set_rule(multiworld.get_location("Beneath the Fortress - Obscured Behind Waterfall", player),
|
||||
lambda state: has_lantern(state, player, options))
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from .locations import location_table
|
||||
from .er_data import Portal, tunic_er_regions, portal_mapping, \
|
||||
dependent_regions_restricted, dependent_regions_nmg, dependent_regions_ur
|
||||
from .er_rules import set_er_region_rules
|
||||
from .options import EntranceRando
|
||||
from worlds.generic import PlandoConnection
|
||||
from random import Random
|
||||
|
||||
@@ -128,12 +129,21 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
|
||||
portal_pairs: Dict[Portal, Portal] = {}
|
||||
dead_ends: List[Portal] = []
|
||||
two_plus: List[Portal] = []
|
||||
logic_rules = world.options.logic_rules.value
|
||||
player_name = world.multiworld.get_player_name(world.player)
|
||||
logic_rules = world.options.logic_rules.value
|
||||
fixed_shop = world.options.fixed_shop
|
||||
laurels_location = world.options.laurels_location
|
||||
|
||||
# if it's not one of the EntranceRando options, it's a custom seed
|
||||
if world.options.entrance_rando.value not in EntranceRando.options:
|
||||
seed_group = world.seed_groups[world.options.entrance_rando.value]
|
||||
logic_rules = seed_group["logic_rules"]
|
||||
fixed_shop = seed_group["fixed_shop"]
|
||||
laurels_location = "10_fairies" if seed_group["laurels_at_10_fairies"] is True else False
|
||||
|
||||
shop_scenes: Set[str] = set()
|
||||
shop_count = 6
|
||||
if world.options.fixed_shop.value:
|
||||
if fixed_shop:
|
||||
shop_count = 1
|
||||
shop_scenes.add("Overworld Redux")
|
||||
|
||||
@@ -163,7 +173,10 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
|
||||
start_region = "Overworld"
|
||||
connected_regions.update(add_dependent_regions(start_region, logic_rules))
|
||||
|
||||
plando_connections = world.multiworld.plando_connections[world.player]
|
||||
if world.options.entrance_rando.value in EntranceRando.options:
|
||||
plando_connections = world.multiworld.plando_connections[world.player]
|
||||
else:
|
||||
plando_connections = world.seed_groups[world.options.entrance_rando.value]["plando"]
|
||||
|
||||
# universal tracker support stuff, don't need to care about region dependency
|
||||
if hasattr(world.multiworld, "re_gen_passthrough"):
|
||||
@@ -198,10 +211,6 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
|
||||
p_entrance = connection.entrance
|
||||
p_exit = connection.exit
|
||||
|
||||
if p_entrance.startswith("Shop"):
|
||||
p_entrance = p_exit
|
||||
p_exit = "Shop Portal"
|
||||
|
||||
portal1 = None
|
||||
portal2 = None
|
||||
|
||||
@@ -213,7 +222,18 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
|
||||
portal2 = portal
|
||||
|
||||
# search dead_ends individually since we can't really remove items from two_plus during the loop
|
||||
if not portal1:
|
||||
if portal1:
|
||||
two_plus.remove(portal1)
|
||||
else:
|
||||
# if not both, they're both dead ends
|
||||
if not portal2:
|
||||
if world.options.entrance_rando.value not in EntranceRando.options:
|
||||
raise Exception(f"Tunic ER seed group {world.options.entrance_rando.value} paired a dead "
|
||||
"end to a dead end in their plando connections.")
|
||||
else:
|
||||
raise Exception(f"{player_name} paired a dead end to a dead end in their "
|
||||
"plando connections.")
|
||||
|
||||
for portal in dead_ends:
|
||||
if p_entrance == portal.name:
|
||||
portal1 = portal
|
||||
@@ -222,16 +242,18 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
|
||||
raise Exception(f"Could not find entrance named {p_entrance} for "
|
||||
f"plando connections in {player_name}'s YAML.")
|
||||
dead_ends.remove(portal1)
|
||||
else:
|
||||
two_plus.remove(portal1)
|
||||
|
||||
if not portal2:
|
||||
if portal2:
|
||||
two_plus.remove(portal2)
|
||||
else:
|
||||
# check if portal2 is a dead end
|
||||
for portal in dead_ends:
|
||||
if p_exit == portal.name:
|
||||
portal2 = portal
|
||||
break
|
||||
if p_exit in ["Shop Portal", "Shop"]:
|
||||
portal2 = Portal(name="Shop Portal", region=f"Shop",
|
||||
# if it's not a dead end, it might be a shop
|
||||
if p_exit == "Shop Portal":
|
||||
portal2 = Portal(name="Shop Portal", region="Shop",
|
||||
destination="Previous Region", tag="_")
|
||||
shop_count -= 1
|
||||
if shop_count < 0:
|
||||
@@ -240,13 +262,12 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
|
||||
if p.name == p_entrance:
|
||||
shop_scenes.add(p.scene())
|
||||
break
|
||||
# and if it's neither shop nor dead end, it just isn't correct
|
||||
else:
|
||||
if not portal2:
|
||||
raise Exception(f"Could not find entrance named {p_exit} for "
|
||||
f"plando connections in {player_name}'s YAML.")
|
||||
dead_ends.remove(portal2)
|
||||
else:
|
||||
two_plus.remove(portal2)
|
||||
|
||||
portal_pairs[portal1] = portal2
|
||||
|
||||
@@ -270,7 +291,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
|
||||
|
||||
# need to plando fairy cave, or it could end up laurels locked
|
||||
# fix this later to be random after adding some item logic to dependent regions
|
||||
if world.options.laurels_location == "10_fairies" and not hasattr(world.multiworld, "re_gen_passthrough"):
|
||||
if laurels_location == "10_fairies" and not hasattr(world.multiworld, "re_gen_passthrough"):
|
||||
portal1 = None
|
||||
portal2 = None
|
||||
for portal in two_plus:
|
||||
@@ -291,7 +312,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
|
||||
two_plus.remove(portal1)
|
||||
dead_ends.remove(portal2)
|
||||
|
||||
if world.options.fixed_shop and not hasattr(world.multiworld, "re_gen_passthrough"):
|
||||
if fixed_shop and not hasattr(world.multiworld, "re_gen_passthrough"):
|
||||
portal1 = None
|
||||
for portal in two_plus:
|
||||
if portal.scene_destination() == "Overworld Redux, Windmill_":
|
||||
@@ -307,7 +328,8 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
|
||||
two_plus.remove(portal1)
|
||||
|
||||
random_object: Random = world.random
|
||||
if world.options.entrance_rando.value != 1:
|
||||
# use the seed given in the options to shuffle the portals
|
||||
if isinstance(world.options.entrance_rando.value, str):
|
||||
random_object = Random(world.options.entrance_rando.value)
|
||||
# we want to start by making sure every region is accessible
|
||||
random_object.shuffle(two_plus)
|
||||
|
||||
@@ -13,158 +13,158 @@ class TunicItemData(NamedTuple):
|
||||
item_base_id = 509342400
|
||||
|
||||
item_table: Dict[str, TunicItemData] = {
|
||||
"Firecracker x2": TunicItemData(ItemClassification.filler, 3, 0, "bombs"),
|
||||
"Firecracker x3": TunicItemData(ItemClassification.filler, 3, 1, "bombs"),
|
||||
"Firecracker x4": TunicItemData(ItemClassification.filler, 3, 2, "bombs"),
|
||||
"Firecracker x5": TunicItemData(ItemClassification.filler, 1, 3, "bombs"),
|
||||
"Firecracker x6": TunicItemData(ItemClassification.filler, 2, 4, "bombs"),
|
||||
"Fire Bomb x2": TunicItemData(ItemClassification.filler, 2, 5, "bombs"),
|
||||
"Fire Bomb x3": TunicItemData(ItemClassification.filler, 1, 6, "bombs"),
|
||||
"Ice Bomb x2": TunicItemData(ItemClassification.filler, 2, 7, "bombs"),
|
||||
"Ice Bomb x3": TunicItemData(ItemClassification.filler, 2, 8, "bombs"),
|
||||
"Ice Bomb x5": TunicItemData(ItemClassification.filler, 1, 9, "bombs"),
|
||||
"Lure": TunicItemData(ItemClassification.filler, 4, 10, "consumables"),
|
||||
"Lure x2": TunicItemData(ItemClassification.filler, 1, 11, "consumables"),
|
||||
"Pepper x2": TunicItemData(ItemClassification.filler, 4, 12, "consumables"),
|
||||
"Ivy x3": TunicItemData(ItemClassification.filler, 2, 13, "consumables"),
|
||||
"Effigy": TunicItemData(ItemClassification.useful, 12, 14, "money"),
|
||||
"HP Berry": TunicItemData(ItemClassification.filler, 2, 15, "consumables"),
|
||||
"HP Berry x2": TunicItemData(ItemClassification.filler, 4, 16, "consumables"),
|
||||
"HP Berry x3": TunicItemData(ItemClassification.filler, 2, 17, "consumables"),
|
||||
"MP Berry": TunicItemData(ItemClassification.filler, 4, 18, "consumables"),
|
||||
"MP Berry x2": TunicItemData(ItemClassification.filler, 2, 19, "consumables"),
|
||||
"MP Berry x3": TunicItemData(ItemClassification.filler, 7, 20, "consumables"),
|
||||
"Firecracker x2": TunicItemData(ItemClassification.filler, 3, 0, "Bombs"),
|
||||
"Firecracker x3": TunicItemData(ItemClassification.filler, 3, 1, "Bombs"),
|
||||
"Firecracker x4": TunicItemData(ItemClassification.filler, 3, 2, "Bombs"),
|
||||
"Firecracker x5": TunicItemData(ItemClassification.filler, 1, 3, "Bombs"),
|
||||
"Firecracker x6": TunicItemData(ItemClassification.filler, 2, 4, "Bombs"),
|
||||
"Fire Bomb x2": TunicItemData(ItemClassification.filler, 2, 5, "Bombs"),
|
||||
"Fire Bomb x3": TunicItemData(ItemClassification.filler, 1, 6, "Bombs"),
|
||||
"Ice Bomb x2": TunicItemData(ItemClassification.filler, 2, 7, "Bombs"),
|
||||
"Ice Bomb x3": TunicItemData(ItemClassification.filler, 2, 8, "Bombs"),
|
||||
"Ice Bomb x5": TunicItemData(ItemClassification.filler, 1, 9, "Bombs"),
|
||||
"Lure": TunicItemData(ItemClassification.filler, 4, 10, "Consumables"),
|
||||
"Lure x2": TunicItemData(ItemClassification.filler, 1, 11, "Consumables"),
|
||||
"Pepper x2": TunicItemData(ItemClassification.filler, 4, 12, "Consumables"),
|
||||
"Ivy x3": TunicItemData(ItemClassification.filler, 2, 13, "Consumables"),
|
||||
"Effigy": TunicItemData(ItemClassification.useful, 12, 14, "Money"),
|
||||
"HP Berry": TunicItemData(ItemClassification.filler, 2, 15, "Consumables"),
|
||||
"HP Berry x2": TunicItemData(ItemClassification.filler, 4, 16, "Consumables"),
|
||||
"HP Berry x3": TunicItemData(ItemClassification.filler, 2, 17, "Consumables"),
|
||||
"MP Berry": TunicItemData(ItemClassification.filler, 4, 18, "Consumables"),
|
||||
"MP Berry x2": TunicItemData(ItemClassification.filler, 2, 19, "Consumables"),
|
||||
"MP Berry x3": TunicItemData(ItemClassification.filler, 7, 20, "Consumables"),
|
||||
"Fairy": TunicItemData(ItemClassification.progression, 20, 21),
|
||||
"Stick": TunicItemData(ItemClassification.progression, 1, 22, "weapons"),
|
||||
"Sword": TunicItemData(ItemClassification.progression, 3, 23, "weapons"),
|
||||
"Sword Upgrade": TunicItemData(ItemClassification.progression, 4, 24, "weapons"),
|
||||
"Magic Wand": TunicItemData(ItemClassification.progression, 1, 25, "weapons"),
|
||||
"Stick": TunicItemData(ItemClassification.progression, 1, 22, "Weapons"),
|
||||
"Sword": TunicItemData(ItemClassification.progression, 3, 23, "Weapons"),
|
||||
"Sword Upgrade": TunicItemData(ItemClassification.progression, 4, 24, "Weapons"),
|
||||
"Magic Wand": TunicItemData(ItemClassification.progression, 1, 25, "Weapons"),
|
||||
"Magic Dagger": TunicItemData(ItemClassification.progression, 1, 26),
|
||||
"Magic Orb": TunicItemData(ItemClassification.progression, 1, 27),
|
||||
"Hero's Laurels": TunicItemData(ItemClassification.progression, 1, 28),
|
||||
"Lantern": TunicItemData(ItemClassification.progression, 1, 29),
|
||||
"Gun": TunicItemData(ItemClassification.useful, 1, 30, "weapons"),
|
||||
"Gun": TunicItemData(ItemClassification.useful, 1, 30, "Weapons"),
|
||||
"Shield": TunicItemData(ItemClassification.useful, 1, 31),
|
||||
"Dath Stone": TunicItemData(ItemClassification.useful, 1, 32),
|
||||
"Hourglass": TunicItemData(ItemClassification.useful, 1, 33),
|
||||
"Old House Key": TunicItemData(ItemClassification.progression, 1, 34, "keys"),
|
||||
"Key": TunicItemData(ItemClassification.progression, 2, 35, "keys"),
|
||||
"Fortress Vault Key": TunicItemData(ItemClassification.progression, 1, 36, "keys"),
|
||||
"Flask Shard": TunicItemData(ItemClassification.useful, 12, 37, "potions"),
|
||||
"Potion Flask": TunicItemData(ItemClassification.useful, 5, 38, "potions"),
|
||||
"Old House Key": TunicItemData(ItemClassification.progression, 1, 34, "Keys"),
|
||||
"Key": TunicItemData(ItemClassification.progression, 2, 35, "Keys"),
|
||||
"Fortress Vault Key": TunicItemData(ItemClassification.progression, 1, 36, "Keys"),
|
||||
"Flask Shard": TunicItemData(ItemClassification.useful, 12, 37),
|
||||
"Potion Flask": TunicItemData(ItemClassification.useful, 5, 38, "Flask"),
|
||||
"Golden Coin": TunicItemData(ItemClassification.progression, 17, 39),
|
||||
"Card Slot": TunicItemData(ItemClassification.useful, 4, 40),
|
||||
"Red Questagon": TunicItemData(ItemClassification.progression_skip_balancing, 1, 41, "hexagons"),
|
||||
"Green Questagon": TunicItemData(ItemClassification.progression_skip_balancing, 1, 42, "hexagons"),
|
||||
"Blue Questagon": TunicItemData(ItemClassification.progression_skip_balancing, 1, 43, "hexagons"),
|
||||
"Gold Questagon": TunicItemData(ItemClassification.progression_skip_balancing, 0, 44, "hexagons"),
|
||||
"ATT Offering": TunicItemData(ItemClassification.useful, 4, 45, "offerings"),
|
||||
"DEF Offering": TunicItemData(ItemClassification.useful, 4, 46, "offerings"),
|
||||
"Potion Offering": TunicItemData(ItemClassification.useful, 3, 47, "offerings"),
|
||||
"HP Offering": TunicItemData(ItemClassification.useful, 6, 48, "offerings"),
|
||||
"MP Offering": TunicItemData(ItemClassification.useful, 3, 49, "offerings"),
|
||||
"SP Offering": TunicItemData(ItemClassification.useful, 2, 50, "offerings"),
|
||||
"Hero Relic - ATT": TunicItemData(ItemClassification.useful, 1, 51, "hero relics"),
|
||||
"Hero Relic - DEF": TunicItemData(ItemClassification.useful, 1, 52, "hero relics"),
|
||||
"Hero Relic - HP": TunicItemData(ItemClassification.useful, 1, 53, "hero relics"),
|
||||
"Hero Relic - MP": TunicItemData(ItemClassification.useful, 1, 54, "hero relics"),
|
||||
"Hero Relic - POTION": TunicItemData(ItemClassification.useful, 1, 55, "hero relics"),
|
||||
"Hero Relic - SP": TunicItemData(ItemClassification.useful, 1, 56, "hero relics"),
|
||||
"Orange Peril Ring": TunicItemData(ItemClassification.useful, 1, 57, "cards"),
|
||||
"Tincture": TunicItemData(ItemClassification.useful, 1, 58, "cards"),
|
||||
"Scavenger Mask": TunicItemData(ItemClassification.progression, 1, 59, "cards"),
|
||||
"Cyan Peril Ring": TunicItemData(ItemClassification.useful, 1, 60, "cards"),
|
||||
"Bracer": TunicItemData(ItemClassification.useful, 1, 61, "cards"),
|
||||
"Dagger Strap": TunicItemData(ItemClassification.useful, 1, 62, "cards"),
|
||||
"Inverted Ash": TunicItemData(ItemClassification.useful, 1, 63, "cards"),
|
||||
"Lucky Cup": TunicItemData(ItemClassification.useful, 1, 64, "cards"),
|
||||
"Magic Echo": TunicItemData(ItemClassification.useful, 1, 65, "cards"),
|
||||
"Anklet": TunicItemData(ItemClassification.useful, 1, 66, "cards"),
|
||||
"Muffling Bell": TunicItemData(ItemClassification.useful, 1, 67, "cards"),
|
||||
"Glass Cannon": TunicItemData(ItemClassification.useful, 1, 68, "cards"),
|
||||
"Perfume": TunicItemData(ItemClassification.useful, 1, 69, "cards"),
|
||||
"Louder Echo": TunicItemData(ItemClassification.useful, 1, 70, "cards"),
|
||||
"Aura's Gem": TunicItemData(ItemClassification.useful, 1, 71, "cards"),
|
||||
"Bone Card": TunicItemData(ItemClassification.useful, 1, 72, "cards"),
|
||||
"Mr Mayor": TunicItemData(ItemClassification.useful, 1, 73, "golden treasures"),
|
||||
"Secret Legend": TunicItemData(ItemClassification.useful, 1, 74, "golden treasures"),
|
||||
"Sacred Geometry": TunicItemData(ItemClassification.useful, 1, 75, "golden treasures"),
|
||||
"Vintage": TunicItemData(ItemClassification.useful, 1, 76, "golden treasures"),
|
||||
"Just Some Pals": TunicItemData(ItemClassification.useful, 1, 77, "golden treasures"),
|
||||
"Regal Weasel": TunicItemData(ItemClassification.useful, 1, 78, "golden treasures"),
|
||||
"Spring Falls": TunicItemData(ItemClassification.useful, 1, 79, "golden treasures"),
|
||||
"Power Up": TunicItemData(ItemClassification.useful, 1, 80, "golden treasures"),
|
||||
"Back To Work": TunicItemData(ItemClassification.useful, 1, 81, "golden treasures"),
|
||||
"Phonomath": TunicItemData(ItemClassification.useful, 1, 82, "golden treasures"),
|
||||
"Dusty": TunicItemData(ItemClassification.useful, 1, 83, "golden treasures"),
|
||||
"Forever Friend": TunicItemData(ItemClassification.useful, 1, 84, "golden treasures"),
|
||||
"Fool Trap": TunicItemData(ItemClassification.trap, 0, 85, "fool"),
|
||||
"Money x1": TunicItemData(ItemClassification.filler, 3, 86, "money"),
|
||||
"Money x10": TunicItemData(ItemClassification.filler, 1, 87, "money"),
|
||||
"Money x15": TunicItemData(ItemClassification.filler, 10, 88, "money"),
|
||||
"Money x16": TunicItemData(ItemClassification.filler, 1, 89, "money"),
|
||||
"Money x20": TunicItemData(ItemClassification.filler, 17, 90, "money"),
|
||||
"Money x25": TunicItemData(ItemClassification.filler, 14, 91, "money"),
|
||||
"Money x30": TunicItemData(ItemClassification.filler, 4, 92, "money"),
|
||||
"Money x32": TunicItemData(ItemClassification.filler, 4, 93, "money"),
|
||||
"Money x40": TunicItemData(ItemClassification.filler, 3, 94, "money"),
|
||||
"Money x48": TunicItemData(ItemClassification.filler, 1, 95, "money"),
|
||||
"Money x50": TunicItemData(ItemClassification.filler, 7, 96, "money"),
|
||||
"Money x64": TunicItemData(ItemClassification.filler, 1, 97, "money"),
|
||||
"Money x100": TunicItemData(ItemClassification.filler, 5, 98, "money"),
|
||||
"Money x128": TunicItemData(ItemClassification.useful, 3, 99, "money"),
|
||||
"Money x200": TunicItemData(ItemClassification.useful, 1, 100, "money"),
|
||||
"Money x255": TunicItemData(ItemClassification.useful, 1, 101, "money"),
|
||||
"Pages 0-1": TunicItemData(ItemClassification.useful, 1, 102, "pages"),
|
||||
"Pages 2-3": TunicItemData(ItemClassification.useful, 1, 103, "pages"),
|
||||
"Pages 4-5": TunicItemData(ItemClassification.useful, 1, 104, "pages"),
|
||||
"Pages 6-7": TunicItemData(ItemClassification.useful, 1, 105, "pages"),
|
||||
"Pages 8-9": TunicItemData(ItemClassification.useful, 1, 106, "pages"),
|
||||
"Pages 10-11": TunicItemData(ItemClassification.useful, 1, 107, "pages"),
|
||||
"Pages 12-13": TunicItemData(ItemClassification.useful, 1, 108, "pages"),
|
||||
"Pages 14-15": TunicItemData(ItemClassification.useful, 1, 109, "pages"),
|
||||
"Pages 16-17": TunicItemData(ItemClassification.useful, 1, 110, "pages"),
|
||||
"Pages 18-19": TunicItemData(ItemClassification.useful, 1, 111, "pages"),
|
||||
"Pages 20-21": TunicItemData(ItemClassification.useful, 1, 112, "pages"),
|
||||
"Pages 22-23": TunicItemData(ItemClassification.useful, 1, 113, "pages"),
|
||||
"Pages 24-25 (Prayer)": TunicItemData(ItemClassification.progression, 1, 114, "pages"),
|
||||
"Pages 26-27": TunicItemData(ItemClassification.useful, 1, 115, "pages"),
|
||||
"Pages 28-29": TunicItemData(ItemClassification.useful, 1, 116, "pages"),
|
||||
"Pages 30-31": TunicItemData(ItemClassification.useful, 1, 117, "pages"),
|
||||
"Pages 32-33": TunicItemData(ItemClassification.useful, 1, 118, "pages"),
|
||||
"Pages 34-35": TunicItemData(ItemClassification.useful, 1, 119, "pages"),
|
||||
"Pages 36-37": TunicItemData(ItemClassification.useful, 1, 120, "pages"),
|
||||
"Pages 38-39": TunicItemData(ItemClassification.useful, 1, 121, "pages"),
|
||||
"Pages 40-41": TunicItemData(ItemClassification.useful, 1, 122, "pages"),
|
||||
"Pages 42-43 (Holy Cross)": TunicItemData(ItemClassification.progression, 1, 123, "pages"),
|
||||
"Pages 44-45": TunicItemData(ItemClassification.useful, 1, 124, "pages"),
|
||||
"Pages 46-47": TunicItemData(ItemClassification.useful, 1, 125, "pages"),
|
||||
"Pages 48-49": TunicItemData(ItemClassification.useful, 1, 126, "pages"),
|
||||
"Pages 50-51": TunicItemData(ItemClassification.useful, 1, 127, "pages"),
|
||||
"Pages 52-53 (Icebolt)": TunicItemData(ItemClassification.progression, 1, 128, "pages"),
|
||||
"Pages 54-55": TunicItemData(ItemClassification.useful, 1, 129, "pages"),
|
||||
"Red Questagon": TunicItemData(ItemClassification.progression_skip_balancing, 1, 41, "Hexagons"),
|
||||
"Green Questagon": TunicItemData(ItemClassification.progression_skip_balancing, 1, 42, "Hexagons"),
|
||||
"Blue Questagon": TunicItemData(ItemClassification.progression_skip_balancing, 1, 43, "Hexagons"),
|
||||
"Gold Questagon": TunicItemData(ItemClassification.progression_skip_balancing, 0, 44, "Hexagons"),
|
||||
"ATT Offering": TunicItemData(ItemClassification.useful, 4, 45, "Offerings"),
|
||||
"DEF Offering": TunicItemData(ItemClassification.useful, 4, 46, "Offerings"),
|
||||
"Potion Offering": TunicItemData(ItemClassification.useful, 3, 47, "Offerings"),
|
||||
"HP Offering": TunicItemData(ItemClassification.useful, 6, 48, "Offerings"),
|
||||
"MP Offering": TunicItemData(ItemClassification.useful, 3, 49, "Offerings"),
|
||||
"SP Offering": TunicItemData(ItemClassification.useful, 2, 50, "Offerings"),
|
||||
"Hero Relic - ATT": TunicItemData(ItemClassification.useful, 1, 51, "Hero Relics"),
|
||||
"Hero Relic - DEF": TunicItemData(ItemClassification.useful, 1, 52, "Hero Relics"),
|
||||
"Hero Relic - HP": TunicItemData(ItemClassification.useful, 1, 53, "Hero Relics"),
|
||||
"Hero Relic - MP": TunicItemData(ItemClassification.useful, 1, 54, "Hero Relics"),
|
||||
"Hero Relic - POTION": TunicItemData(ItemClassification.useful, 1, 55, "Hero Relics"),
|
||||
"Hero Relic - SP": TunicItemData(ItemClassification.useful, 1, 56, "Hero Relics"),
|
||||
"Orange Peril Ring": TunicItemData(ItemClassification.useful, 1, 57, "Cards"),
|
||||
"Tincture": TunicItemData(ItemClassification.useful, 1, 58, "Cards"),
|
||||
"Scavenger Mask": TunicItemData(ItemClassification.progression, 1, 59, "Cards"),
|
||||
"Cyan Peril Ring": TunicItemData(ItemClassification.useful, 1, 60, "Cards"),
|
||||
"Bracer": TunicItemData(ItemClassification.useful, 1, 61, "Cards"),
|
||||
"Dagger Strap": TunicItemData(ItemClassification.useful, 1, 62, "Cards"),
|
||||
"Inverted Ash": TunicItemData(ItemClassification.useful, 1, 63, "Cards"),
|
||||
"Lucky Cup": TunicItemData(ItemClassification.useful, 1, 64, "Cards"),
|
||||
"Magic Echo": TunicItemData(ItemClassification.useful, 1, 65, "Cards"),
|
||||
"Anklet": TunicItemData(ItemClassification.useful, 1, 66, "Cards"),
|
||||
"Muffling Bell": TunicItemData(ItemClassification.useful, 1, 67, "Cards"),
|
||||
"Glass Cannon": TunicItemData(ItemClassification.useful, 1, 68, "Cards"),
|
||||
"Perfume": TunicItemData(ItemClassification.useful, 1, 69, "Cards"),
|
||||
"Louder Echo": TunicItemData(ItemClassification.useful, 1, 70, "Cards"),
|
||||
"Aura's Gem": TunicItemData(ItemClassification.useful, 1, 71, "Cards"),
|
||||
"Bone Card": TunicItemData(ItemClassification.useful, 1, 72, "Cards"),
|
||||
"Mr Mayor": TunicItemData(ItemClassification.useful, 1, 73, "Golden Treasures"),
|
||||
"Secret Legend": TunicItemData(ItemClassification.useful, 1, 74, "Golden Treasures"),
|
||||
"Sacred Geometry": TunicItemData(ItemClassification.useful, 1, 75, "Golden Treasures"),
|
||||
"Vintage": TunicItemData(ItemClassification.useful, 1, 76, "Golden Treasures"),
|
||||
"Just Some Pals": TunicItemData(ItemClassification.useful, 1, 77, "Golden Treasures"),
|
||||
"Regal Weasel": TunicItemData(ItemClassification.useful, 1, 78, "Golden Treasures"),
|
||||
"Spring Falls": TunicItemData(ItemClassification.useful, 1, 79, "Golden Treasures"),
|
||||
"Power Up": TunicItemData(ItemClassification.useful, 1, 80, "Golden Treasures"),
|
||||
"Back To Work": TunicItemData(ItemClassification.useful, 1, 81, "Golden Treasures"),
|
||||
"Phonomath": TunicItemData(ItemClassification.useful, 1, 82, "Golden Treasures"),
|
||||
"Dusty": TunicItemData(ItemClassification.useful, 1, 83, "Golden Treasures"),
|
||||
"Forever Friend": TunicItemData(ItemClassification.useful, 1, 84, "Golden Treasures"),
|
||||
"Fool Trap": TunicItemData(ItemClassification.trap, 0, 85),
|
||||
"Money x1": TunicItemData(ItemClassification.filler, 3, 86, "Money"),
|
||||
"Money x10": TunicItemData(ItemClassification.filler, 1, 87, "Money"),
|
||||
"Money x15": TunicItemData(ItemClassification.filler, 10, 88, "Money"),
|
||||
"Money x16": TunicItemData(ItemClassification.filler, 1, 89, "Money"),
|
||||
"Money x20": TunicItemData(ItemClassification.filler, 17, 90, "Money"),
|
||||
"Money x25": TunicItemData(ItemClassification.filler, 14, 91, "Money"),
|
||||
"Money x30": TunicItemData(ItemClassification.filler, 4, 92, "Money"),
|
||||
"Money x32": TunicItemData(ItemClassification.filler, 4, 93, "Money"),
|
||||
"Money x40": TunicItemData(ItemClassification.filler, 3, 94, "Money"),
|
||||
"Money x48": TunicItemData(ItemClassification.filler, 1, 95, "Money"),
|
||||
"Money x50": TunicItemData(ItemClassification.filler, 7, 96, "Money"),
|
||||
"Money x64": TunicItemData(ItemClassification.filler, 1, 97, "Money"),
|
||||
"Money x100": TunicItemData(ItemClassification.filler, 5, 98, "Money"),
|
||||
"Money x128": TunicItemData(ItemClassification.useful, 3, 99, "Money"),
|
||||
"Money x200": TunicItemData(ItemClassification.useful, 1, 100, "Money"),
|
||||
"Money x255": TunicItemData(ItemClassification.useful, 1, 101, "Money"),
|
||||
"Pages 0-1": TunicItemData(ItemClassification.useful, 1, 102, "Pages"),
|
||||
"Pages 2-3": TunicItemData(ItemClassification.useful, 1, 103, "Pages"),
|
||||
"Pages 4-5": TunicItemData(ItemClassification.useful, 1, 104, "Pages"),
|
||||
"Pages 6-7": TunicItemData(ItemClassification.useful, 1, 105, "Pages"),
|
||||
"Pages 8-9": TunicItemData(ItemClassification.useful, 1, 106, "Pages"),
|
||||
"Pages 10-11": TunicItemData(ItemClassification.useful, 1, 107, "Pages"),
|
||||
"Pages 12-13": TunicItemData(ItemClassification.useful, 1, 108, "Pages"),
|
||||
"Pages 14-15": TunicItemData(ItemClassification.useful, 1, 109, "Pages"),
|
||||
"Pages 16-17": TunicItemData(ItemClassification.useful, 1, 110, "Pages"),
|
||||
"Pages 18-19": TunicItemData(ItemClassification.useful, 1, 111, "Pages"),
|
||||
"Pages 20-21": TunicItemData(ItemClassification.useful, 1, 112, "Pages"),
|
||||
"Pages 22-23": TunicItemData(ItemClassification.useful, 1, 113, "Pages"),
|
||||
"Pages 24-25 (Prayer)": TunicItemData(ItemClassification.progression, 1, 114, "Pages"),
|
||||
"Pages 26-27": TunicItemData(ItemClassification.useful, 1, 115, "Pages"),
|
||||
"Pages 28-29": TunicItemData(ItemClassification.useful, 1, 116, "Pages"),
|
||||
"Pages 30-31": TunicItemData(ItemClassification.useful, 1, 117, "Pages"),
|
||||
"Pages 32-33": TunicItemData(ItemClassification.useful, 1, 118, "Pages"),
|
||||
"Pages 34-35": TunicItemData(ItemClassification.useful, 1, 119, "Pages"),
|
||||
"Pages 36-37": TunicItemData(ItemClassification.useful, 1, 120, "Pages"),
|
||||
"Pages 38-39": TunicItemData(ItemClassification.useful, 1, 121, "Pages"),
|
||||
"Pages 40-41": TunicItemData(ItemClassification.useful, 1, 122, "Pages"),
|
||||
"Pages 42-43 (Holy Cross)": TunicItemData(ItemClassification.progression, 1, 123, "Pages"),
|
||||
"Pages 44-45": TunicItemData(ItemClassification.useful, 1, 124, "Pages"),
|
||||
"Pages 46-47": TunicItemData(ItemClassification.useful, 1, 125, "Pages"),
|
||||
"Pages 48-49": TunicItemData(ItemClassification.useful, 1, 126, "Pages"),
|
||||
"Pages 50-51": TunicItemData(ItemClassification.useful, 1, 127, "Pages"),
|
||||
"Pages 52-53 (Icebolt)": TunicItemData(ItemClassification.progression, 1, 128, "Pages"),
|
||||
"Pages 54-55": TunicItemData(ItemClassification.useful, 1, 129, "Pages"),
|
||||
|
||||
"Ladders near Weathervane": TunicItemData(ItemClassification.progression, 0, 130, "ladders"),
|
||||
"Ladders near Overworld Checkpoint": TunicItemData(ItemClassification.progression, 0, 131, "ladders"),
|
||||
"Ladders near Patrol Cave": TunicItemData(ItemClassification.progression, 0, 132, "ladders"),
|
||||
"Ladder near Temple Rafters": TunicItemData(ItemClassification.progression, 0, 133, "ladders"),
|
||||
"Ladders near Dark Tomb": TunicItemData(ItemClassification.progression, 0, 134, "ladders"),
|
||||
"Ladder to Quarry": TunicItemData(ItemClassification.progression, 0, 135, "ladders"),
|
||||
"Ladders to West Bell": TunicItemData(ItemClassification.progression, 0, 136, "ladders"),
|
||||
"Ladders in Overworld Town": TunicItemData(ItemClassification.progression, 0, 137, "ladders"),
|
||||
"Ladder to Ruined Atoll": TunicItemData(ItemClassification.progression, 0, 138, "ladders"),
|
||||
"Ladder to Swamp": TunicItemData(ItemClassification.progression, 0, 139, "ladders"),
|
||||
"Ladders in Well": TunicItemData(ItemClassification.progression, 0, 140, "ladders"),
|
||||
"Ladder in Dark Tomb": TunicItemData(ItemClassification.progression, 0, 141, "ladders"),
|
||||
"Ladder to East Forest": TunicItemData(ItemClassification.progression, 0, 142, "ladders"),
|
||||
"Ladders to Lower Forest": TunicItemData(ItemClassification.progression, 0, 143, "ladders"),
|
||||
"Ladder to Beneath the Vault": TunicItemData(ItemClassification.progression, 0, 144, "ladders"),
|
||||
"Ladders in Hourglass Cave": TunicItemData(ItemClassification.progression, 0, 145, "ladders"),
|
||||
"Ladders in South Atoll": TunicItemData(ItemClassification.progression, 0, 146, "ladders"),
|
||||
"Ladders to Frog's Domain": TunicItemData(ItemClassification.progression, 0, 147, "ladders"),
|
||||
"Ladders in Library": TunicItemData(ItemClassification.progression, 0, 148, "ladders"),
|
||||
"Ladders in Lower Quarry": TunicItemData(ItemClassification.progression, 0, 149, "ladders"),
|
||||
"Ladders in Swamp": TunicItemData(ItemClassification.progression, 0, 150, "ladders"),
|
||||
"Ladders near Weathervane": TunicItemData(ItemClassification.progression, 0, 130, "Ladders"),
|
||||
"Ladders near Overworld Checkpoint": TunicItemData(ItemClassification.progression, 0, 131, "Ladders"),
|
||||
"Ladders near Patrol Cave": TunicItemData(ItemClassification.progression, 0, 132, "Ladders"),
|
||||
"Ladder near Temple Rafters": TunicItemData(ItemClassification.progression, 0, 133, "Ladders"),
|
||||
"Ladders near Dark Tomb": TunicItemData(ItemClassification.progression, 0, 134, "Ladders"),
|
||||
"Ladder to Quarry": TunicItemData(ItemClassification.progression, 0, 135, "Ladders"),
|
||||
"Ladders to West Bell": TunicItemData(ItemClassification.progression, 0, 136, "Ladders"),
|
||||
"Ladders in Overworld Town": TunicItemData(ItemClassification.progression, 0, 137, "Ladders"),
|
||||
"Ladder to Ruined Atoll": TunicItemData(ItemClassification.progression, 0, 138, "Ladders"),
|
||||
"Ladder to Swamp": TunicItemData(ItemClassification.progression, 0, 139, "Ladders"),
|
||||
"Ladders in Well": TunicItemData(ItemClassification.progression, 0, 140, "Ladders"),
|
||||
"Ladder in Dark Tomb": TunicItemData(ItemClassification.progression, 0, 141, "Ladders"),
|
||||
"Ladder to East Forest": TunicItemData(ItemClassification.progression, 0, 142, "Ladders"),
|
||||
"Ladders to Lower Forest": TunicItemData(ItemClassification.progression, 0, 143, "Ladders"),
|
||||
"Ladder to Beneath the Vault": TunicItemData(ItemClassification.progression, 0, 144, "Ladders"),
|
||||
"Ladders in Hourglass Cave": TunicItemData(ItemClassification.progression, 0, 145, "Ladders"),
|
||||
"Ladders in South Atoll": TunicItemData(ItemClassification.progression, 0, 146, "Ladders"),
|
||||
"Ladders to Frog's Domain": TunicItemData(ItemClassification.progression, 0, 147, "Ladders"),
|
||||
"Ladders in Library": TunicItemData(ItemClassification.progression, 0, 148, "Ladders"),
|
||||
"Ladders in Lower Quarry": TunicItemData(ItemClassification.progression, 0, 149, "Ladders"),
|
||||
"Ladders in Swamp": TunicItemData(ItemClassification.progression, 0, 150, "Ladders"),
|
||||
}
|
||||
|
||||
fool_tiers: List[List[str]] = [
|
||||
@@ -220,20 +220,23 @@ item_name_groups: Dict[str, Set[str]] = {
|
||||
|
||||
# extra groups for the purpose of aliasing items
|
||||
extra_groups: Dict[str, Set[str]] = {
|
||||
"laurels": {"Hero's Laurels"},
|
||||
"orb": {"Magic Orb"},
|
||||
"dagger": {"Magic Dagger"},
|
||||
"magic rod": {"Magic Wand"},
|
||||
"holy cross": {"Pages 42-43 (Holy Cross)"},
|
||||
"prayer": {"Pages 24-25 (Prayer)"},
|
||||
"icebolt": {"Pages 52-53 (Icebolt)"},
|
||||
"ice rod": {"Pages 52-53 (Icebolt)"},
|
||||
"melee weapons": {"Stick", "Sword", "Sword Upgrade"},
|
||||
"progressive sword": {"Sword Upgrade"},
|
||||
"abilities": {"Pages 24-25 (Prayer)", "Pages 42-43 (Holy Cross)", "Pages 52-53 (Icebolt)"},
|
||||
"questagons": {"Red Questagon", "Green Questagon", "Blue Questagon", "Gold Questagon"},
|
||||
"ladder to atoll": {"Ladder to Ruined Atoll"}, # fuzzy matching made it hint Ladders in Well, now it won't
|
||||
"ladders to bell": {"Ladders to West Bell"},
|
||||
"Laurels": {"Hero's Laurels"},
|
||||
"Orb": {"Magic Orb"},
|
||||
"Dagger": {"Magic Dagger"},
|
||||
"Wand": {"Magic Wand"},
|
||||
"Magic Rod": {"Magic Wand"},
|
||||
"Fire Rod": {"Magic Wand"},
|
||||
"Holy Cross": {"Pages 42-43 (Holy Cross)"},
|
||||
"Prayer": {"Pages 24-25 (Prayer)"},
|
||||
"Icebolt": {"Pages 52-53 (Icebolt)"},
|
||||
"Ice Rod": {"Pages 52-53 (Icebolt)"},
|
||||
"Melee Weapons": {"Stick", "Sword", "Sword Upgrade"},
|
||||
"Progressive Sword": {"Sword Upgrade"},
|
||||
"Abilities": {"Pages 24-25 (Prayer)", "Pages 42-43 (Holy Cross)", "Pages 52-53 (Icebolt)"},
|
||||
"Questagons": {"Red Questagon", "Green Questagon", "Blue Questagon", "Gold Questagon"},
|
||||
"Ladder to Atoll": {"Ladder to Ruined Atoll"}, # fuzzy matching made it hint Ladders in Well, now it won't
|
||||
"Ladders to Bell": {"Ladders to West Bell"},
|
||||
"Ladders to Well": {"Ladders in Well"}, # fuzzy matching decided ladders in well was ladders to west bell
|
||||
}
|
||||
|
||||
item_name_groups.update(extra_groups)
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
from typing import Dict, NamedTuple, Set, Optional, List
|
||||
from typing import Dict, NamedTuple, Set, Optional
|
||||
|
||||
|
||||
class TunicLocationData(NamedTuple):
|
||||
region: str
|
||||
er_region: str # entrance rando region
|
||||
location_group: Optional[str] = None
|
||||
location_groups: Optional[List[str]] = None
|
||||
|
||||
|
||||
location_base_id = 509342400
|
||||
@@ -46,8 +45,8 @@ location_table: Dict[str, TunicLocationData] = {
|
||||
"Guardhouse 2 - Bottom Floor Secret": TunicLocationData("East Forest", "Guard House 2 Lower"),
|
||||
"Guardhouse 1 - Upper Floor Obscured": TunicLocationData("East Forest", "Guard House 1 East"),
|
||||
"Guardhouse 1 - Upper Floor": TunicLocationData("East Forest", "Guard House 1 East"),
|
||||
"East Forest - Dancing Fox Spirit Holy Cross": TunicLocationData("East Forest", "East Forest Dance Fox Spot", location_group="holy cross"),
|
||||
"East Forest - Golden Obelisk Holy Cross": TunicLocationData("East Forest", "Lower Forest", location_group="holy cross"),
|
||||
"East Forest - Dancing Fox Spirit Holy Cross": TunicLocationData("East Forest", "East Forest Dance Fox Spot", location_group="Holy Cross"),
|
||||
"East Forest - Golden Obelisk Holy Cross": TunicLocationData("East Forest", "Lower Forest", location_group="Holy Cross"),
|
||||
"East Forest - Ice Rod Grapple Chest": TunicLocationData("East Forest", "East Forest"),
|
||||
"East Forest - Above Save Point": TunicLocationData("East Forest", "East Forest"),
|
||||
"East Forest - Above Save Point Obscured": TunicLocationData("East Forest", "East Forest"),
|
||||
@@ -65,18 +64,18 @@ location_table: Dict[str, TunicLocationData] = {
|
||||
"Forest Belltower - Obscured Near Bell Top Floor": TunicLocationData("East Forest", "Forest Belltower Upper"),
|
||||
"Forest Belltower - Obscured Beneath Bell Bottom Floor": TunicLocationData("East Forest", "Forest Belltower Main"),
|
||||
"Forest Belltower - Page Pickup": TunicLocationData("East Forest", "Forest Belltower Main"),
|
||||
"Forest Grave Path - Holy Cross Code by Grave": TunicLocationData("East Forest", "Forest Grave Path by Grave", location_group="holy cross"),
|
||||
"Forest Grave Path - Holy Cross Code by Grave": TunicLocationData("East Forest", "Forest Grave Path by Grave", location_group="Holy Cross"),
|
||||
"Forest Grave Path - Above Gate": TunicLocationData("East Forest", "Forest Grave Path Main"),
|
||||
"Forest Grave Path - Obscured Chest": TunicLocationData("East Forest", "Forest Grave Path Main"),
|
||||
"Forest Grave Path - Upper Walkway": TunicLocationData("East Forest", "Forest Grave Path Upper"),
|
||||
"Forest Grave Path - Sword Pickup": TunicLocationData("East Forest", "Forest Grave Path by Grave"),
|
||||
"Hero's Grave - Tooth Relic": TunicLocationData("East Forest", "Hero Relic - East Forest", location_group="hero relic"),
|
||||
"Hero's Grave - Tooth Relic": TunicLocationData("East Forest", "Hero Relic - East Forest"),
|
||||
"Fortress Courtyard - From East Belltower": TunicLocationData("East Forest", "Fortress Exterior from East Forest"),
|
||||
"Fortress Leaf Piles - Secret Chest": TunicLocationData("Eastern Vault Fortress", "Fortress Leaf Piles"),
|
||||
"Fortress Arena - Hexagon Red": TunicLocationData("Eastern Vault Fortress", "Fortress Arena"),
|
||||
"Fortress Arena - Siege Engine/Vault Key Pickup": TunicLocationData("Eastern Vault Fortress", "Fortress Arena", location_group="bosses"),
|
||||
"Fortress Arena - Siege Engine/Vault Key Pickup": TunicLocationData("Eastern Vault Fortress", "Fortress Arena", location_group="Bosses"),
|
||||
"Fortress East Shortcut - Chest Near Slimes": TunicLocationData("Eastern Vault Fortress", "Fortress East Shortcut Lower"),
|
||||
"Eastern Vault Fortress - [West Wing] Candles Holy Cross": TunicLocationData("Eastern Vault Fortress", "Eastern Vault Fortress", location_group="holy cross"),
|
||||
"Eastern Vault Fortress - [West Wing] Candles Holy Cross": TunicLocationData("Eastern Vault Fortress", "Eastern Vault Fortress", location_group="Holy Cross"),
|
||||
"Eastern Vault Fortress - [West Wing] Dark Room Chest 1": TunicLocationData("Eastern Vault Fortress", "Eastern Vault Fortress"),
|
||||
"Eastern Vault Fortress - [West Wing] Dark Room Chest 2": TunicLocationData("Eastern Vault Fortress", "Eastern Vault Fortress"),
|
||||
"Eastern Vault Fortress - [East Wing] Bombable Wall": TunicLocationData("Eastern Vault Fortress", "Eastern Vault Fortress"),
|
||||
@@ -84,7 +83,7 @@ location_table: Dict[str, TunicLocationData] = {
|
||||
"Fortress Grave Path - Upper Walkway": TunicLocationData("Eastern Vault Fortress", "Fortress Grave Path Upper"),
|
||||
"Fortress Grave Path - Chest Right of Grave": TunicLocationData("Eastern Vault Fortress", "Fortress Grave Path"),
|
||||
"Fortress Grave Path - Obscured Chest Left of Grave": TunicLocationData("Eastern Vault Fortress", "Fortress Grave Path"),
|
||||
"Hero's Grave - Flowers Relic": TunicLocationData("Eastern Vault Fortress", "Hero Relic - Fortress", location_group="hero relic"),
|
||||
"Hero's Grave - Flowers Relic": TunicLocationData("Eastern Vault Fortress", "Hero Relic - Fortress"),
|
||||
"Beneath the Fortress - Bridge": TunicLocationData("Beneath the Vault", "Beneath the Vault Back"),
|
||||
"Beneath the Fortress - Cell Chest 1": TunicLocationData("Beneath the Vault", "Beneath the Vault Back"),
|
||||
"Beneath the Fortress - Obscured Behind Waterfall": TunicLocationData("Beneath the Vault", "Beneath the Vault Front"),
|
||||
@@ -101,8 +100,8 @@ location_table: Dict[str, TunicLocationData] = {
|
||||
"Frog's Domain - Side Room Chest": TunicLocationData("Frog's Domain", "Frog's Domain"),
|
||||
"Frog's Domain - Side Room Grapple Secret": TunicLocationData("Frog's Domain", "Frog's Domain"),
|
||||
"Frog's Domain - Magic Orb Pickup": TunicLocationData("Frog's Domain", "Frog's Domain"),
|
||||
"Librarian - Hexagon Green": TunicLocationData("Library", "Library Arena", location_group="bosses"),
|
||||
"Library Hall - Holy Cross Chest": TunicLocationData("Library", "Library Hall", location_group="holy cross"),
|
||||
"Librarian - Hexagon Green": TunicLocationData("Library", "Library Arena", location_group="Bosses"),
|
||||
"Library Hall - Holy Cross Chest": TunicLocationData("Library", "Library Hall", location_group="Holy Cross"),
|
||||
"Library Lab - Chest By Shrine 2": TunicLocationData("Library", "Library Lab"),
|
||||
"Library Lab - Chest By Shrine 1": TunicLocationData("Library", "Library Lab"),
|
||||
"Library Lab - Chest By Shrine 3": TunicLocationData("Library", "Library Lab"),
|
||||
@@ -110,7 +109,7 @@ location_table: Dict[str, TunicLocationData] = {
|
||||
"Library Lab - Page 3": TunicLocationData("Library", "Library Lab"),
|
||||
"Library Lab - Page 1": TunicLocationData("Library", "Library Lab"),
|
||||
"Library Lab - Page 2": TunicLocationData("Library", "Library Lab"),
|
||||
"Hero's Grave - Mushroom Relic": TunicLocationData("Library", "Hero Relic - Library", location_group="hero relic"),
|
||||
"Hero's Grave - Mushroom Relic": TunicLocationData("Library", "Hero Relic - Library"),
|
||||
"Lower Mountain - Page Before Door": TunicLocationData("Overworld", "Lower Mountain"),
|
||||
"Changing Room - Normal Chest": TunicLocationData("Overworld", "Changing Room"),
|
||||
"Fortress Courtyard - Chest Near Cave": TunicLocationData("Overworld", "Fortress Exterior near cave"),
|
||||
@@ -165,49 +164,49 @@ location_table: Dict[str, TunicLocationData] = {
|
||||
"Ruined Shop - Chest 2": TunicLocationData("Overworld", "Ruined Shop"),
|
||||
"Ruined Shop - Chest 3": TunicLocationData("Overworld", "Ruined Shop"),
|
||||
"Ruined Passage - Page Pickup": TunicLocationData("Overworld", "Ruined Passage"),
|
||||
"Shop - Potion 1": TunicLocationData("Overworld", "Shop", location_group="shop"),
|
||||
"Shop - Potion 2": TunicLocationData("Overworld", "Shop", location_group="shop"),
|
||||
"Shop - Coin 1": TunicLocationData("Overworld", "Shop", location_group="shop"),
|
||||
"Shop - Coin 2": TunicLocationData("Overworld", "Shop", location_group="shop"),
|
||||
"Shop - Potion 1": TunicLocationData("Overworld", "Shop"),
|
||||
"Shop - Potion 2": TunicLocationData("Overworld", "Shop"),
|
||||
"Shop - Coin 1": TunicLocationData("Overworld", "Shop"),
|
||||
"Shop - Coin 2": TunicLocationData("Overworld", "Shop"),
|
||||
"Special Shop - Secret Page Pickup": TunicLocationData("Overworld", "Special Shop"),
|
||||
"Stick House - Stick Chest": TunicLocationData("Overworld", "Stick House"),
|
||||
"Sealed Temple - Page Pickup": TunicLocationData("Overworld", "Sealed Temple"),
|
||||
"Hourglass Cave - Hourglass Chest": TunicLocationData("Overworld", "Hourglass Cave"),
|
||||
"Far Shore - Secret Chest": TunicLocationData("Overworld", "Far Shore"),
|
||||
"Far Shore - Page Pickup": TunicLocationData("Overworld", "Far Shore to Spawn Region"),
|
||||
"Coins in the Well - 10 Coins": TunicLocationData("Overworld", "Overworld", location_group="well"),
|
||||
"Coins in the Well - 15 Coins": TunicLocationData("Overworld", "Overworld", location_group="well"),
|
||||
"Coins in the Well - 3 Coins": TunicLocationData("Overworld", "Overworld", location_group="well"),
|
||||
"Coins in the Well - 6 Coins": TunicLocationData("Overworld", "Overworld", location_group="well"),
|
||||
"Secret Gathering Place - 20 Fairy Reward": TunicLocationData("Overworld", "Secret Gathering Place", location_group="fairies"),
|
||||
"Secret Gathering Place - 10 Fairy Reward": TunicLocationData("Overworld", "Secret Gathering Place", location_group="fairies"),
|
||||
"Overworld - [West] Moss Wall Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", location_group="holy cross"),
|
||||
"Overworld - [Southwest] Flowers Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Beach", location_group="holy cross"),
|
||||
"Overworld - [Southwest] Fountain Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", location_group="holy cross"),
|
||||
"Overworld - [Northeast] Flowers Holy Cross": TunicLocationData("Overworld Holy Cross", "East Overworld", location_group="holy cross"),
|
||||
"Overworld - [East] Weathervane Holy Cross": TunicLocationData("Overworld Holy Cross", "East Overworld", location_group="holy cross"),
|
||||
"Overworld - [West] Windmill Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", location_group="holy cross"),
|
||||
"Overworld - [Southwest] Haiku Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Beach", location_group="holy cross"),
|
||||
"Overworld - [West] Windchimes Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", location_group="holy cross"),
|
||||
"Overworld - [South] Starting Platform Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", location_group="holy cross"),
|
||||
"Overworld - [Northwest] Golden Obelisk Page": TunicLocationData("Overworld Holy Cross", "Upper Overworld", location_group="holy cross"),
|
||||
"Old House - Holy Cross Door Page": TunicLocationData("Overworld Holy Cross", "Old House Back", location_group="holy cross"),
|
||||
"Cube Cave - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Cube Cave", location_group="holy cross"),
|
||||
"Southeast Cross Door - Chest 3": TunicLocationData("Overworld Holy Cross", "Southeast Cross Room", location_group="holy cross"),
|
||||
"Southeast Cross Door - Chest 2": TunicLocationData("Overworld Holy Cross", "Southeast Cross Room", location_group="holy cross"),
|
||||
"Southeast Cross Door - Chest 1": TunicLocationData("Overworld Holy Cross", "Southeast Cross Room", location_group="holy cross"),
|
||||
"Maze Cave - Maze Room Holy Cross": TunicLocationData("Overworld Holy Cross", "Maze Cave", location_group="holy cross"),
|
||||
"Caustic Light Cave - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Caustic Light Cave", location_group="holy cross"),
|
||||
"Old House - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Old House Front", location_group="holy cross"),
|
||||
"Patrol Cave - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Patrol Cave", location_group="holy cross"),
|
||||
"Ruined Passage - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Ruined Passage", location_group="holy cross"),
|
||||
"Hourglass Cave - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Hourglass Cave Tower", location_group="holy cross"),
|
||||
"Sealed Temple - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Sealed Temple", location_group="holy cross"),
|
||||
"Fountain Cross Door - Page Pickup": TunicLocationData("Overworld Holy Cross", "Fountain Cross Room", location_group="holy cross"),
|
||||
"Secret Gathering Place - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Secret Gathering Place", location_group="holy cross"),
|
||||
"Top of the Mountain - Page At The Peak": TunicLocationData("Overworld Holy Cross", "Top of the Mountain", location_group="holy cross"),
|
||||
"Coins in the Well - 10 Coins": TunicLocationData("Overworld", "Overworld", location_group="Well"),
|
||||
"Coins in the Well - 15 Coins": TunicLocationData("Overworld", "Overworld", location_group="Well"),
|
||||
"Coins in the Well - 3 Coins": TunicLocationData("Overworld", "Overworld", location_group="Well"),
|
||||
"Coins in the Well - 6 Coins": TunicLocationData("Overworld", "Overworld", location_group="Well"),
|
||||
"Secret Gathering Place - 20 Fairy Reward": TunicLocationData("Overworld", "Secret Gathering Place", location_group="Fairies"),
|
||||
"Secret Gathering Place - 10 Fairy Reward": TunicLocationData("Overworld", "Secret Gathering Place", location_group="Fairies"),
|
||||
"Overworld - [West] Moss Wall Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", location_group="Holy Cross"),
|
||||
"Overworld - [Southwest] Flowers Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Beach", location_group="Holy Cross"),
|
||||
"Overworld - [Southwest] Fountain Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", location_group="Holy Cross"),
|
||||
"Overworld - [Northeast] Flowers Holy Cross": TunicLocationData("Overworld Holy Cross", "East Overworld", location_group="Holy Cross"),
|
||||
"Overworld - [East] Weathervane Holy Cross": TunicLocationData("Overworld Holy Cross", "East Overworld", location_group="Holy Cross"),
|
||||
"Overworld - [West] Windmill Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", location_group="Holy Cross"),
|
||||
"Overworld - [Southwest] Haiku Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Beach", location_group="Holy Cross"),
|
||||
"Overworld - [West] Windchimes Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", location_group="Holy Cross"),
|
||||
"Overworld - [South] Starting Platform Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", location_group="Holy Cross"),
|
||||
"Overworld - [Northwest] Golden Obelisk Page": TunicLocationData("Overworld Holy Cross", "Upper Overworld", location_group="Holy Cross"),
|
||||
"Old House - Holy Cross Door Page": TunicLocationData("Overworld Holy Cross", "Old House Back", location_group="Holy Cross"),
|
||||
"Cube Cave - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Cube Cave", location_group="Holy Cross"),
|
||||
"Southeast Cross Door - Chest 3": TunicLocationData("Overworld Holy Cross", "Southeast Cross Room", location_group="Holy Cross"),
|
||||
"Southeast Cross Door - Chest 2": TunicLocationData("Overworld Holy Cross", "Southeast Cross Room", location_group="Holy Cross"),
|
||||
"Southeast Cross Door - Chest 1": TunicLocationData("Overworld Holy Cross", "Southeast Cross Room", location_group="Holy Cross"),
|
||||
"Maze Cave - Maze Room Holy Cross": TunicLocationData("Overworld Holy Cross", "Maze Cave", location_group="Holy Cross"),
|
||||
"Caustic Light Cave - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Caustic Light Cave", location_group="Holy Cross"),
|
||||
"Old House - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Old House Front", location_group="Holy Cross"),
|
||||
"Patrol Cave - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Patrol Cave", location_group="Holy Cross"),
|
||||
"Ruined Passage - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Ruined Passage", location_group="Holy Cross"),
|
||||
"Hourglass Cave - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Hourglass Cave Tower", location_group="Holy Cross"),
|
||||
"Sealed Temple - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Sealed Temple", location_group="Holy Cross"),
|
||||
"Fountain Cross Door - Page Pickup": TunicLocationData("Overworld Holy Cross", "Fountain Cross Room", location_group="Holy Cross"),
|
||||
"Secret Gathering Place - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Secret Gathering Place", location_group="Holy Cross"),
|
||||
"Top of the Mountain - Page At The Peak": TunicLocationData("Overworld Holy Cross", "Top of the Mountain", location_group="Holy Cross"),
|
||||
"Monastery - Monastery Chest": TunicLocationData("Quarry", "Monastery Back"),
|
||||
"Quarry - [Back Entrance] Bushes Holy Cross": TunicLocationData("Quarry Back", "Quarry Back", location_group="holy cross"),
|
||||
"Quarry - [Back Entrance] Bushes Holy Cross": TunicLocationData("Quarry Back", "Quarry Back", location_group="Holy Cross"),
|
||||
"Quarry - [Back Entrance] Chest": TunicLocationData("Quarry Back", "Quarry Back"),
|
||||
"Quarry - [Central] Near Shortcut Ladder": TunicLocationData("Quarry", "Quarry"),
|
||||
"Quarry - [East] Near Telescope": TunicLocationData("Quarry", "Quarry"),
|
||||
@@ -225,7 +224,7 @@ location_table: Dict[str, TunicLocationData] = {
|
||||
"Quarry - [Central] Above Ladder Dash Chest": TunicLocationData("Quarry", "Quarry Monastery Entry"),
|
||||
"Quarry - [West] Upper Area Bombable Wall": TunicLocationData("Quarry Back", "Quarry Back"),
|
||||
"Quarry - [East] Bombable Wall": TunicLocationData("Quarry", "Quarry"),
|
||||
"Hero's Grave - Ash Relic": TunicLocationData("Quarry", "Hero Relic - Quarry", location_group="hero relics"),
|
||||
"Hero's Grave - Ash Relic": TunicLocationData("Quarry", "Hero Relic - Quarry"),
|
||||
"Quarry - [West] Shooting Range Secret Path": TunicLocationData("Lower Quarry", "Lower Quarry"),
|
||||
"Quarry - [West] Near Shooting Range": TunicLocationData("Lower Quarry", "Lower Quarry"),
|
||||
"Quarry - [West] Below Shooting Range": TunicLocationData("Lower Quarry", "Lower Quarry"),
|
||||
@@ -246,7 +245,7 @@ location_table: Dict[str, TunicLocationData] = {
|
||||
"Rooted Ziggurat Lower - Guarded By Double Turrets": TunicLocationData("Rooted Ziggurat", "Rooted Ziggurat Lower Front"),
|
||||
"Rooted Ziggurat Lower - After 2nd Double Turret Chest": TunicLocationData("Rooted Ziggurat", "Rooted Ziggurat Lower Front"),
|
||||
"Rooted Ziggurat Lower - Guarded By Double Turrets 2": TunicLocationData("Rooted Ziggurat", "Rooted Ziggurat Lower Front"),
|
||||
"Rooted Ziggurat Lower - Hexagon Blue": TunicLocationData("Rooted Ziggurat", "Rooted Ziggurat Lower Back", location_group="bosses"),
|
||||
"Rooted Ziggurat Lower - Hexagon Blue": TunicLocationData("Rooted Ziggurat", "Rooted Ziggurat Lower Back", location_group="Bosses"),
|
||||
"Ruined Atoll - [West] Near Kevin Block": TunicLocationData("Ruined Atoll", "Ruined Atoll"),
|
||||
"Ruined Atoll - [South] Upper Floor On Power Line": TunicLocationData("Ruined Atoll", "Ruined Atoll Ladder Tops"),
|
||||
"Ruined Atoll - [South] Chest Near Big Crabs": TunicLocationData("Ruined Atoll", "Ruined Atoll"),
|
||||
@@ -288,14 +287,14 @@ location_table: Dict[str, TunicLocationData] = {
|
||||
"Swamp - [South Graveyard] Upper Walkway Dash Chest": TunicLocationData("Swamp", "Swamp Mid"),
|
||||
"Swamp - [South Graveyard] Above Big Skeleton": TunicLocationData("Swamp", "Swamp Front"),
|
||||
"Swamp - [Central] Beneath Memorial": TunicLocationData("Swamp", "Swamp Mid"),
|
||||
"Hero's Grave - Feathers Relic": TunicLocationData("Swamp", "Hero Relic - Swamp", location_group="hero relic"),
|
||||
"Hero's Grave - Feathers Relic": TunicLocationData("Swamp", "Hero Relic - Swamp"),
|
||||
"West Furnace - Chest": TunicLocationData("West Garden", "Furnace Walking Path"),
|
||||
"Overworld - [West] Near West Garden Entrance": TunicLocationData("West Garden", "Overworld to West Garden from Furnace"),
|
||||
"West Garden - [Central Highlands] Holy Cross (Blue Lines)": TunicLocationData("West Garden", "West Garden", location_group="holy cross"),
|
||||
"West Garden - [West Lowlands] Tree Holy Cross Chest": TunicLocationData("West Garden", "West Garden", location_group="holy cross"),
|
||||
"West Garden - [Central Highlands] Holy Cross (Blue Lines)": TunicLocationData("West Garden", "West Garden", location_group="Holy Cross"),
|
||||
"West Garden - [West Lowlands] Tree Holy Cross Chest": TunicLocationData("West Garden", "West Garden", location_group="Holy Cross"),
|
||||
"West Garden - [Southeast Lowlands] Outside Cave": TunicLocationData("West Garden", "West Garden"),
|
||||
"West Garden - [Central Lowlands] Chest Beneath Faeries": TunicLocationData("West Garden", "West Garden"),
|
||||
"West Garden - [North] Behind Holy Cross Door": TunicLocationData("West Garden", "West Garden", location_group="holy cross"),
|
||||
"West Garden - [North] Behind Holy Cross Door": TunicLocationData("West Garden", "West Garden", location_group="Holy Cross"),
|
||||
"West Garden - [Central Highlands] Top of Ladder Before Boss": TunicLocationData("West Garden", "West Garden"),
|
||||
"West Garden - [Central Lowlands] Passage Beneath Bridge": TunicLocationData("West Garden", "West Garden"),
|
||||
"West Garden - [North] Across From Page Pickup": TunicLocationData("West Garden", "West Garden"),
|
||||
@@ -307,12 +306,12 @@ location_table: Dict[str, TunicLocationData] = {
|
||||
"West Garden - [West Highlands] Upper Left Walkway": TunicLocationData("West Garden", "West Garden"),
|
||||
"West Garden - [Central Lowlands] Chest Beneath Save Point": TunicLocationData("West Garden", "West Garden"),
|
||||
"West Garden - [Central Highlands] Behind Guard Captain": TunicLocationData("West Garden", "West Garden"),
|
||||
"West Garden - [Central Highlands] After Garden Knight": TunicLocationData("Overworld", "West Garden after Boss", location_group="bosses"),
|
||||
"West Garden - [Central Highlands] After Garden Knight": TunicLocationData("Overworld", "West Garden after Boss", location_group="Bosses"),
|
||||
"West Garden - [South Highlands] Secret Chest Beneath Fuse": TunicLocationData("West Garden", "West Garden"),
|
||||
"West Garden - [East Lowlands] Page Behind Ice Dagger House": TunicLocationData("West Garden", "West Garden Portal Item"),
|
||||
"West Garden - [North] Page Pickup": TunicLocationData("West Garden", "West Garden"),
|
||||
"West Garden House - [Southeast Lowlands] Ice Dagger Pickup": TunicLocationData("West Garden", "Magic Dagger House"),
|
||||
"Hero's Grave - Effigy Relic": TunicLocationData("West Garden", "Hero Relic - West Garden", location_group="hero relic"),
|
||||
"Hero's Grave - Effigy Relic": TunicLocationData("West Garden", "Hero Relic - West Garden"),
|
||||
}
|
||||
|
||||
hexagon_locations: Dict[str, str] = {
|
||||
@@ -325,7 +324,7 @@ location_name_to_id: Dict[str, int] = {name: location_base_id + index for index,
|
||||
|
||||
location_name_groups: Dict[str, Set[str]] = {}
|
||||
for loc_name, loc_data in location_table.items():
|
||||
loc_group_name = loc_name.split(" - ", 1)[0]
|
||||
location_name_groups.setdefault(loc_group_name, set()).add(loc_name)
|
||||
if loc_data.location_group:
|
||||
if loc_data.location_group not in location_name_groups.keys():
|
||||
location_name_groups[loc_data.location_group] = set()
|
||||
location_name_groups[loc_data.location_group].add(loc_name)
|
||||
location_name_groups.setdefault(loc_data.location_group, set()).add(loc_name)
|
||||
|
||||
@@ -103,8 +103,10 @@ class ExtraHexagonPercentage(Range):
|
||||
class EntranceRando(TextChoice):
|
||||
"""
|
||||
Randomize the connections between scenes.
|
||||
If you set this to a value besides true or false, that value will be used as a custom seed.
|
||||
A small, very lost fox on a big adventure.
|
||||
|
||||
If you set this option's value to a string, it will be used as a custom seed.
|
||||
Every player who uses the same custom seed will have the same entrances, choosing the most restrictive settings among these players for the purpose of pairing entrances.
|
||||
"""
|
||||
internal_name = "entrance_rando"
|
||||
display_name = "Entrance Rando"
|
||||
|
||||
@@ -329,7 +329,7 @@ def set_normal_rules(world: "YoshisIslandWorld") -> None:
|
||||
|
||||
set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Red Coins", player), lambda state: state.has("Super Star", player))
|
||||
set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Flowers", player), lambda state: state.has("Super Star", player))
|
||||
set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Stars", player), lambda state: state.has("Super Star", player))
|
||||
set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Stars", player), lambda state: logic.has_midring(state) or state.has("Tulip", player))
|
||||
set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Level Clear", player), lambda state: state.has("Super Star", player))
|
||||
|
||||
set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "! Switch", "Egg Launcher"}, player))
|
||||
|
||||
Reference in New Issue
Block a user