mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-28 04:33:26 -07:00
Merge remote-tracking branch 'upstream/main' into instruction_patch_kdl3
This commit is contained in:
@@ -718,10 +718,6 @@ class CollectionState():
|
||||
def count(self, item: str, player: int) -> int:
|
||||
return self.prog_items[player][item]
|
||||
|
||||
def item_count(self, item: str, player: int) -> int:
|
||||
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
|
||||
|
||||
@@ -259,7 +259,7 @@ def main(args: Optional[Union[argparse.Namespace, dict]] = None):
|
||||
elif not args:
|
||||
args = {}
|
||||
|
||||
if "Patch|Game|Component" in args:
|
||||
if args.get("Patch|Game|Component", None) is not None:
|
||||
file, component = identify(args["Patch|Game|Component"])
|
||||
if file:
|
||||
args['file'] = file
|
||||
|
||||
@@ -175,11 +175,13 @@ class Context:
|
||||
all_item_and_group_names: typing.Dict[str, typing.Set[str]]
|
||||
all_location_and_group_names: typing.Dict[str, typing.Set[str]]
|
||||
non_hintable_names: typing.Dict[str, typing.Set[str]]
|
||||
logger: logging.Logger
|
||||
|
||||
def __init__(self, host: str, port: int, server_password: str, password: str, location_check_points: int,
|
||||
hint_cost: int, item_cheat: bool, release_mode: str = "disabled", collect_mode="disabled",
|
||||
remaining_mode: str = "disabled", auto_shutdown: typing.SupportsFloat = 0, compatibility: int = 2,
|
||||
log_network: bool = False):
|
||||
log_network: bool = False, logger: logging.Logger = logging.getLogger()):
|
||||
self.logger = logger
|
||||
super(Context, self).__init__()
|
||||
self.slot_info = {}
|
||||
self.log_network = log_network
|
||||
@@ -287,12 +289,12 @@ class Context:
|
||||
try:
|
||||
await endpoint.socket.send(msg)
|
||||
except websockets.ConnectionClosed:
|
||||
logging.exception(f"Exception during send_msgs, could not send {msg}")
|
||||
self.logger.exception(f"Exception during send_msgs, could not send {msg}")
|
||||
await self.disconnect(endpoint)
|
||||
return False
|
||||
else:
|
||||
if self.log_network:
|
||||
logging.info(f"Outgoing message: {msg}")
|
||||
self.logger.info(f"Outgoing message: {msg}")
|
||||
return True
|
||||
|
||||
async def send_encoded_msgs(self, endpoint: Endpoint, msg: str) -> bool:
|
||||
@@ -301,12 +303,12 @@ class Context:
|
||||
try:
|
||||
await endpoint.socket.send(msg)
|
||||
except websockets.ConnectionClosed:
|
||||
logging.exception("Exception during send_encoded_msgs")
|
||||
self.logger.exception("Exception during send_encoded_msgs")
|
||||
await self.disconnect(endpoint)
|
||||
return False
|
||||
else:
|
||||
if self.log_network:
|
||||
logging.info(f"Outgoing message: {msg}")
|
||||
self.logger.info(f"Outgoing message: {msg}")
|
||||
return True
|
||||
|
||||
async def broadcast_send_encoded_msgs(self, endpoints: typing.Iterable[Endpoint], msg: str) -> bool:
|
||||
@@ -317,11 +319,11 @@ class Context:
|
||||
try:
|
||||
websockets.broadcast(sockets, msg)
|
||||
except RuntimeError:
|
||||
logging.exception("Exception during broadcast_send_encoded_msgs")
|
||||
self.logger.exception("Exception during broadcast_send_encoded_msgs")
|
||||
return False
|
||||
else:
|
||||
if self.log_network:
|
||||
logging.info(f"Outgoing broadcast: {msg}")
|
||||
self.logger.info(f"Outgoing broadcast: {msg}")
|
||||
return True
|
||||
|
||||
def broadcast_all(self, msgs: typing.List[dict]):
|
||||
@@ -330,7 +332,7 @@ class Context:
|
||||
async_start(self.broadcast_send_encoded_msgs(endpoints, msgs))
|
||||
|
||||
def broadcast_text_all(self, text: str, additional_arguments: dict = {}):
|
||||
logging.info("Notice (all): %s" % text)
|
||||
self.logger.info("Notice (all): %s" % text)
|
||||
self.broadcast_all([{**{"cmd": "PrintJSON", "data": [{ "text": text }]}, **additional_arguments}])
|
||||
|
||||
def broadcast_team(self, team: int, msgs: typing.List[dict]):
|
||||
@@ -352,7 +354,7 @@ class Context:
|
||||
def notify_client(self, client: Client, text: str, additional_arguments: dict = {}):
|
||||
if not client.auth:
|
||||
return
|
||||
logging.info("Notice (Player %s in team %d): %s" % (client.name, client.team + 1, text))
|
||||
self.logger.info("Notice (Player %s in team %d): %s" % (client.name, client.team + 1, text))
|
||||
async_start(self.send_msgs(client, [{"cmd": "PrintJSON", "data": [{ "text": text }], **additional_arguments}]))
|
||||
|
||||
def notify_client_multiple(self, client: Client, texts: typing.List[str], additional_arguments: dict = {}):
|
||||
@@ -451,7 +453,7 @@ class Context:
|
||||
for game_name, data in decoded_obj.get("datapackage", {}).items():
|
||||
if game_name in game_data_packages:
|
||||
data = game_data_packages[game_name]
|
||||
logging.info(f"Loading embedded data package for game {game_name}")
|
||||
self.logger.info(f"Loading embedded data package for game {game_name}")
|
||||
self.gamespackage[game_name] = data
|
||||
self.item_name_groups[game_name] = data["item_name_groups"]
|
||||
if "location_name_groups" in data:
|
||||
@@ -483,7 +485,7 @@ class Context:
|
||||
with open(self.save_filename, "wb") as f:
|
||||
f.write(zlib.compress(encoded_save))
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
self.logger.exception(e)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
@@ -501,9 +503,9 @@ class Context:
|
||||
save_data = restricted_loads(zlib.decompress(f.read()))
|
||||
self.set_save(save_data)
|
||||
except FileNotFoundError:
|
||||
logging.error('No save data found, starting a new game')
|
||||
self.logger.error('No save data found, starting a new game')
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
self.logger.exception(e)
|
||||
self._start_async_saving()
|
||||
|
||||
def _start_async_saving(self):
|
||||
@@ -520,11 +522,11 @@ class Context:
|
||||
next_wakeup = (second - get_datetime_second()) % self.auto_save_interval
|
||||
time.sleep(max(1.0, next_wakeup))
|
||||
if self.save_dirty:
|
||||
logging.debug("Saving via thread.")
|
||||
self.logger.debug("Saving via thread.")
|
||||
self._save()
|
||||
except OperationalError as e:
|
||||
logging.exception(e)
|
||||
logging.info(f"Saving failed. Retry in {self.auto_save_interval} seconds.")
|
||||
self.logger.exception(e)
|
||||
self.logger.info(f"Saving failed. Retry in {self.auto_save_interval} seconds.")
|
||||
else:
|
||||
self.save_dirty = False
|
||||
self.auto_saver_thread = threading.Thread(target=save_regularly, daemon=True)
|
||||
@@ -598,7 +600,7 @@ class Context:
|
||||
if "stored_data" in savedata:
|
||||
self.stored_data = savedata["stored_data"]
|
||||
# count items and slots from lists for items_handling = remote
|
||||
logging.info(
|
||||
self.logger.info(
|
||||
f'Loaded save file with {sum([len(v) for k, v in self.received_items.items() if k[2]])} received items '
|
||||
f'for {sum(k[2] for k in self.received_items)} players')
|
||||
|
||||
@@ -640,13 +642,13 @@ class Context:
|
||||
try:
|
||||
raise Exception(f"Could not set server option {key}, skipping.") from e
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
logging.debug(f"Setting server option {key} to {value} from supplied multidata")
|
||||
self.logger.exception(e)
|
||||
self.logger.debug(f"Setting server option {key} to {value} from supplied multidata")
|
||||
setattr(self, key, value)
|
||||
elif key == "disable_item_cheat":
|
||||
self.item_cheat = not bool(value)
|
||||
else:
|
||||
logging.debug(f"Unrecognized server option {key}")
|
||||
self.logger.debug(f"Unrecognized server option {key}")
|
||||
|
||||
def get_aliased_name(self, team: int, slot: int):
|
||||
if (team, slot) in self.name_aliases:
|
||||
@@ -680,7 +682,7 @@ class Context:
|
||||
self.hints[team, player].add(hint)
|
||||
new_hint_events.add(player)
|
||||
|
||||
logging.info("Notice (Team #%d): %s" % (team + 1, format_hint(self, team, hint)))
|
||||
self.logger.info("Notice (Team #%d): %s" % (team + 1, format_hint(self, team, hint)))
|
||||
for slot in new_hint_events:
|
||||
self.on_new_hint(team, slot)
|
||||
for slot, hint_data in concerns.items():
|
||||
@@ -739,21 +741,21 @@ async def server(websocket, path: str = "/", ctx: Context = None):
|
||||
|
||||
try:
|
||||
if ctx.log_network:
|
||||
logging.info("Incoming connection")
|
||||
ctx.logger.info("Incoming connection")
|
||||
await on_client_connected(ctx, client)
|
||||
if ctx.log_network:
|
||||
logging.info("Sent Room Info")
|
||||
ctx.logger.info("Sent Room Info")
|
||||
async for data in websocket:
|
||||
if ctx.log_network:
|
||||
logging.info(f"Incoming message: {data}")
|
||||
ctx.logger.info(f"Incoming message: {data}")
|
||||
for msg in decode(data):
|
||||
await process_client_cmd(ctx, client, msg)
|
||||
except Exception as e:
|
||||
if not isinstance(e, websockets.WebSocketException):
|
||||
logging.exception(e)
|
||||
ctx.logger.exception(e)
|
||||
finally:
|
||||
if ctx.log_network:
|
||||
logging.info("Disconnected")
|
||||
ctx.logger.info("Disconnected")
|
||||
await ctx.disconnect(client)
|
||||
|
||||
|
||||
@@ -985,7 +987,7 @@ def register_location_checks(ctx: Context, team: int, slot: int, locations: typi
|
||||
new_item = NetworkItem(item_id, location, slot, flags)
|
||||
send_items_to(ctx, team, target_player, new_item)
|
||||
|
||||
logging.info('(Team #%d) %s sent %s to %s (%s)' % (
|
||||
ctx.logger.info('(Team #%d) %s sent %s to %s (%s)' % (
|
||||
team + 1, ctx.player_names[(team, slot)], ctx.item_names[item_id],
|
||||
ctx.player_names[(team, target_player)], ctx.location_names[location]))
|
||||
info_text = json_format_send_event(new_item, target_player)
|
||||
@@ -1625,7 +1627,7 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
|
||||
try:
|
||||
cmd: str = args["cmd"]
|
||||
except:
|
||||
logging.exception(f"Could not get command from {args}")
|
||||
ctx.logger.exception(f"Could not get command from {args}")
|
||||
await ctx.send_msgs(client, [{'cmd': 'InvalidPacket', "type": "cmd", "original_cmd": None,
|
||||
"text": f"Could not get command from {args} at `cmd`"}])
|
||||
raise
|
||||
@@ -1668,7 +1670,7 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
|
||||
if ctx.compatibility == 0 and args['version'] != version_tuple:
|
||||
errors.add('IncompatibleVersion')
|
||||
if errors:
|
||||
logging.info(f"A client connection was refused due to: {errors}, the sent connect information was {args}.")
|
||||
ctx.logger.info(f"A client connection was refused due to: {errors}, the sent connect information was {args}.")
|
||||
await ctx.send_msgs(client, [{"cmd": "ConnectionRefused", "errors": list(errors)}])
|
||||
else:
|
||||
team, slot = ctx.connect_names[args['name']]
|
||||
@@ -2286,7 +2288,7 @@ async def auto_shutdown(ctx, to_cancel=None):
|
||||
if to_cancel:
|
||||
for task in to_cancel:
|
||||
task.cancel()
|
||||
logging.info("Shutting down due to inactivity.")
|
||||
ctx.logger.info("Shutting down due to inactivity.")
|
||||
|
||||
while not ctx.exit_event.is_set():
|
||||
if not ctx.client_activity_timers.values():
|
||||
|
||||
37
Options.py
37
Options.py
@@ -140,12 +140,6 @@ class Option(typing.Generic[T], metaclass=AssembleOptions):
|
||||
def current_key(self) -> str:
|
||||
return self.name_lookup[self.value]
|
||||
|
||||
def get_current_option_name(self) -> str:
|
||||
"""Deprecated. use current_option_name instead. TODO remove around 0.4"""
|
||||
logging.warning(DeprecationWarning(f"get_current_option_name for {self.__class__.__name__} is deprecated."
|
||||
f" use current_option_name instead. Worlds should use {self}.current_key"))
|
||||
return self.current_option_name
|
||||
|
||||
@property
|
||||
def current_option_name(self) -> str:
|
||||
"""For display purposes. Worlds should be using current_key."""
|
||||
@@ -750,37 +744,6 @@ class NamedRange(Range):
|
||||
return super().from_text(text)
|
||||
|
||||
|
||||
class SpecialRange(NamedRange):
|
||||
special_range_cutoff = 0
|
||||
|
||||
# TODO: remove class SpecialRange, earliest 3 releases after 0.4.3
|
||||
def __new__(cls, value: int) -> SpecialRange:
|
||||
from Utils import deprecate
|
||||
deprecate(f"Option type {cls.__name__} is a subclass of SpecialRange, which is deprecated and pending removal. "
|
||||
"Consider switching to NamedRange, which supports all use-cases of SpecialRange, and more. In "
|
||||
"NamedRange, range_start specifies the lower end of the regular range, while special values can be "
|
||||
"placed anywhere (below, inside, or above the regular range).")
|
||||
return super().__new__(cls)
|
||||
|
||||
@classmethod
|
||||
def weighted_range(cls, text) -> Range:
|
||||
if text == "random-low":
|
||||
return cls(cls.triangular(cls.special_range_cutoff, cls.range_end, cls.special_range_cutoff))
|
||||
elif text == "random-high":
|
||||
return cls(cls.triangular(cls.special_range_cutoff, cls.range_end, cls.range_end))
|
||||
elif text == "random-middle":
|
||||
return cls(cls.triangular(cls.special_range_cutoff, cls.range_end))
|
||||
elif text.startswith("random-range-"):
|
||||
return cls.custom_range(text)
|
||||
elif text == "random":
|
||||
return cls(random.randint(cls.special_range_cutoff, cls.range_end))
|
||||
else:
|
||||
raise Exception(f"random text \"{text}\" did not resolve to a recognized pattern. "
|
||||
f"Acceptable values are: random, random-high, random-middle, random-low, "
|
||||
f"random-range-low-<min>-<max>, random-range-middle-<min>-<max>, "
|
||||
f"random-range-high-<min>-<max>, or random-range-<min>-<max>.")
|
||||
|
||||
|
||||
class FreezeValidKeys(AssembleOptions):
|
||||
def __new__(mcs, name, bases, attrs):
|
||||
if "valid_keys" in attrs:
|
||||
|
||||
@@ -66,6 +66,7 @@ Currently, the following games are supported:
|
||||
* A Short Hike
|
||||
* Yoshi's Island
|
||||
* Mario & Luigi: Superstar Saga
|
||||
* Bomb Rush Cyberfunk
|
||||
|
||||
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
|
||||
|
||||
@@ -23,6 +23,7 @@ app.jinja_env.filters['all'] = all
|
||||
|
||||
app.config["SELFHOST"] = True # application process is in charge of running the websites
|
||||
app.config["GENERATORS"] = 8 # maximum concurrent world gens
|
||||
app.config["HOSTERS"] = 8 # maximum concurrent room hosters
|
||||
app.config["SELFLAUNCH"] = True # application process is in charge of launching Rooms.
|
||||
app.config["SELFLAUNCHCERT"] = None # can point to a SSL Certificate to encrypt Room websocket connections
|
||||
app.config["SELFLAUNCHKEY"] = None # can point to a SSL Certificate Key to encrypt Room websocket connections
|
||||
|
||||
@@ -3,7 +3,6 @@ from __future__ import annotations
|
||||
import json
|
||||
import logging
|
||||
import multiprocessing
|
||||
import threading
|
||||
import time
|
||||
import typing
|
||||
from uuid import UUID
|
||||
@@ -15,16 +14,6 @@ from Utils import restricted_loads
|
||||
from .locker import Locker, AlreadyRunningException
|
||||
|
||||
|
||||
def launch_room(room: Room, config: dict):
|
||||
# requires db_session!
|
||||
if room.last_activity >= datetime.utcnow() - timedelta(seconds=room.timeout):
|
||||
multiworld = multiworlds.get(room.id, None)
|
||||
if not multiworld:
|
||||
multiworld = MultiworldInstance(room, config)
|
||||
|
||||
multiworld.start()
|
||||
|
||||
|
||||
def handle_generation_success(seed_id):
|
||||
logging.info(f"Generation finished for seed {seed_id}")
|
||||
|
||||
@@ -59,21 +48,30 @@ def init_db(pony_config: dict):
|
||||
db.generate_mapping()
|
||||
|
||||
|
||||
def cleanup():
|
||||
"""delete unowned user-content"""
|
||||
with db_session:
|
||||
# >>> bool(uuid.UUID(int=0))
|
||||
# True
|
||||
rooms = Room.select(lambda room: room.owner == UUID(int=0)).delete(bulk=True)
|
||||
seeds = Seed.select(lambda seed: seed.owner == UUID(int=0) and not seed.rooms).delete(bulk=True)
|
||||
slots = Slot.select(lambda slot: not slot.seed).delete(bulk=True)
|
||||
# Command gets deleted by ponyorm Cascade Delete, as Room is Required
|
||||
if rooms or seeds or slots:
|
||||
logging.info(f"{rooms} Rooms, {seeds} Seeds and {slots} Slots have been deleted.")
|
||||
|
||||
|
||||
def autohost(config: dict):
|
||||
def keep_running():
|
||||
try:
|
||||
with Locker("autohost"):
|
||||
# delete unowned user-content
|
||||
with db_session:
|
||||
# >>> bool(uuid.UUID(int=0))
|
||||
# True
|
||||
rooms = Room.select(lambda room: room.owner == UUID(int=0)).delete(bulk=True)
|
||||
seeds = Seed.select(lambda seed: seed.owner == UUID(int=0) and not seed.rooms).delete(bulk=True)
|
||||
slots = Slot.select(lambda slot: not slot.seed).delete(bulk=True)
|
||||
# Command gets deleted by ponyorm Cascade Delete, as Room is Required
|
||||
if rooms or seeds or slots:
|
||||
logging.info(f"{rooms} Rooms, {seeds} Seeds and {slots} Slots have been deleted.")
|
||||
run_guardian()
|
||||
cleanup()
|
||||
hosters = []
|
||||
for x in range(config["HOSTERS"]):
|
||||
hoster = MultiworldInstance(config, x)
|
||||
hosters.append(hoster)
|
||||
hoster.start()
|
||||
|
||||
while 1:
|
||||
time.sleep(0.1)
|
||||
with db_session:
|
||||
@@ -81,7 +79,9 @@ def autohost(config: dict):
|
||||
room for room in Room if
|
||||
room.last_activity >= datetime.utcnow() - timedelta(days=3))
|
||||
for room in rooms:
|
||||
launch_room(room, config)
|
||||
# we have to filter twice, as the per-room timeout can't currently be PonyORM transpiled.
|
||||
if room.last_activity >= datetime.utcnow() - timedelta(seconds=room.timeout):
|
||||
hosters[room.id.int % len(hosters)].start_room(room.id)
|
||||
|
||||
except AlreadyRunningException:
|
||||
logging.info("Autohost reports as already running, not starting another.")
|
||||
@@ -132,29 +132,38 @@ multiworlds: typing.Dict[type(Room.id), MultiworldInstance] = {}
|
||||
|
||||
|
||||
class MultiworldInstance():
|
||||
def __init__(self, room: Room, config: dict):
|
||||
self.room_id = room.id
|
||||
def __init__(self, config: dict, id: int):
|
||||
self.room_ids = set()
|
||||
self.process: typing.Optional[multiprocessing.Process] = None
|
||||
with guardian_lock:
|
||||
multiworlds[self.room_id] = self
|
||||
self.ponyconfig = config["PONY"]
|
||||
self.cert = config["SELFLAUNCHCERT"]
|
||||
self.key = config["SELFLAUNCHKEY"]
|
||||
self.host = config["HOST_ADDRESS"]
|
||||
self.rooms_to_start = multiprocessing.Queue()
|
||||
self.rooms_shutting_down = multiprocessing.Queue()
|
||||
self.name = f"MultiHoster{id}"
|
||||
|
||||
def start(self):
|
||||
if self.process and self.process.is_alive():
|
||||
return False
|
||||
|
||||
logging.info(f"Spinning up {self.room_id}")
|
||||
process = multiprocessing.Process(group=None, target=run_server_process,
|
||||
args=(self.room_id, self.ponyconfig, get_static_server_data(),
|
||||
self.cert, self.key, self.host),
|
||||
name="MultiHost")
|
||||
args=(self.name, self.ponyconfig, get_static_server_data(),
|
||||
self.cert, self.key, self.host,
|
||||
self.rooms_to_start, self.rooms_shutting_down),
|
||||
name=self.name)
|
||||
process.start()
|
||||
# bind after start to prevent thread sync issues with guardian.
|
||||
self.process = process
|
||||
|
||||
def start_room(self, room_id):
|
||||
while not self.rooms_shutting_down.empty():
|
||||
self.room_ids.remove(self.rooms_shutting_down.get(block=True, timeout=None))
|
||||
if room_id in self.room_ids:
|
||||
pass # should already be hosted currently.
|
||||
else:
|
||||
self.room_ids.add(room_id)
|
||||
self.rooms_to_start.put(room_id)
|
||||
|
||||
def stop(self):
|
||||
if self.process:
|
||||
self.process.terminate()
|
||||
@@ -168,40 +177,6 @@ class MultiworldInstance():
|
||||
self.process = None
|
||||
|
||||
|
||||
guardian = None
|
||||
guardian_lock = threading.Lock()
|
||||
|
||||
|
||||
def run_guardian():
|
||||
global guardian
|
||||
global multiworlds
|
||||
with guardian_lock:
|
||||
if not guardian:
|
||||
try:
|
||||
import resource
|
||||
except ModuleNotFoundError:
|
||||
pass # unix only module
|
||||
else:
|
||||
# Each Server is another file handle, so request as many as we can from the system
|
||||
file_limit = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
|
||||
# set soft limit to hard limit
|
||||
resource.setrlimit(resource.RLIMIT_NOFILE, (file_limit, file_limit))
|
||||
|
||||
def guard():
|
||||
while 1:
|
||||
time.sleep(1)
|
||||
done = []
|
||||
with guardian_lock:
|
||||
for key, instance in multiworlds.items():
|
||||
if instance.done():
|
||||
instance.collect()
|
||||
done.append(key)
|
||||
for key in done:
|
||||
del (multiworlds[key])
|
||||
|
||||
guardian = threading.Thread(name="Guardian", target=guard)
|
||||
|
||||
|
||||
from .models import Room, Generation, STATE_QUEUED, STATE_STARTED, STATE_ERROR, db, Seed, Slot
|
||||
from .customserver import run_server_process, get_static_server_data
|
||||
from .generate import gen_game
|
||||
|
||||
@@ -5,6 +5,7 @@ import collections
|
||||
import datetime
|
||||
import functools
|
||||
import logging
|
||||
import multiprocessing
|
||||
import pickle
|
||||
import random
|
||||
import socket
|
||||
@@ -53,17 +54,19 @@ del MultiServer
|
||||
|
||||
class DBCommandProcessor(ServerCommandProcessor):
|
||||
def output(self, text: str):
|
||||
logging.info(text)
|
||||
self.ctx.logger.info(text)
|
||||
|
||||
|
||||
class WebHostContext(Context):
|
||||
room_id: int
|
||||
|
||||
def __init__(self, static_server_data: dict):
|
||||
def __init__(self, static_server_data: dict, logger: logging.Logger):
|
||||
# static server data is used during _load_game_data to load required data,
|
||||
# without needing to import worlds system, which takes quite a bit of memory
|
||||
self.static_server_data = static_server_data
|
||||
super(WebHostContext, self).__init__("", 0, "", "", 1, 40, True, "enabled", "enabled", "enabled", 0, 2)
|
||||
super(WebHostContext, self).__init__("", 0, "", "", 1,
|
||||
40, True, "enabled", "enabled",
|
||||
"enabled", 0, 2, logger=logger)
|
||||
del self.static_server_data
|
||||
self.main_loop = asyncio.get_running_loop()
|
||||
self.video = {}
|
||||
@@ -159,63 +162,95 @@ def get_static_server_data() -> dict:
|
||||
return data
|
||||
|
||||
|
||||
def run_server_process(room_id, ponyconfig: dict, static_server_data: dict,
|
||||
def set_up_logging(room_id) -> logging.Logger:
|
||||
import os
|
||||
# logger setup
|
||||
logger = logging.getLogger(f"RoomLogger {room_id}")
|
||||
|
||||
# this *should* be empty, but just in case.
|
||||
for handler in logger.handlers[:]:
|
||||
logger.removeHandler(handler)
|
||||
handler.close()
|
||||
|
||||
file_handler = logging.FileHandler(
|
||||
os.path.join(Utils.user_path("logs"), f"{room_id}.txt"),
|
||||
"a",
|
||||
encoding="utf-8-sig")
|
||||
file_handler.setFormatter(logging.Formatter("[%(asctime)s]: %(message)s"))
|
||||
logger.setLevel(logging.INFO)
|
||||
logger.addHandler(file_handler)
|
||||
return logger
|
||||
|
||||
|
||||
def run_server_process(name: str, ponyconfig: dict, static_server_data: dict,
|
||||
cert_file: typing.Optional[str], cert_key_file: typing.Optional[str],
|
||||
host: str):
|
||||
host: str, rooms_to_run: multiprocessing.Queue, rooms_shutting_down: multiprocessing.Queue):
|
||||
Utils.init_logging(name)
|
||||
try:
|
||||
import resource
|
||||
except ModuleNotFoundError:
|
||||
pass # unix only module
|
||||
else:
|
||||
# Each Server is another file handle, so request as many as we can from the system
|
||||
file_limit = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
|
||||
# set soft limit to hard limit
|
||||
resource.setrlimit(resource.RLIMIT_NOFILE, (file_limit, file_limit))
|
||||
del resource, file_limit
|
||||
|
||||
# establish DB connection for multidata and multisave
|
||||
db.bind(**ponyconfig)
|
||||
db.generate_mapping(check_tables=False)
|
||||
|
||||
async def main():
|
||||
if "worlds" in sys.modules:
|
||||
raise Exception("Worlds system should not be loaded in the custom server.")
|
||||
if "worlds" in sys.modules:
|
||||
raise Exception("Worlds system should not be loaded in the custom server.")
|
||||
|
||||
import gc
|
||||
Utils.init_logging(str(room_id), write_mode="a")
|
||||
ctx = WebHostContext(static_server_data)
|
||||
ctx.load(room_id)
|
||||
ctx.init_save()
|
||||
ssl_context = load_server_cert(cert_file, cert_key_file) if cert_file else None
|
||||
gc.collect() # free intermediate objects used during setup
|
||||
import gc
|
||||
ssl_context = load_server_cert(cert_file, cert_key_file) if cert_file else None
|
||||
del cert_file, cert_key_file, ponyconfig
|
||||
gc.collect() # free intermediate objects used during setup
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
async def start_room(room_id):
|
||||
try:
|
||||
ctx.server = websockets.serve(functools.partial(server, ctx=ctx), ctx.host, ctx.port, ssl=ssl_context)
|
||||
logger = set_up_logging(room_id)
|
||||
ctx = WebHostContext(static_server_data, logger)
|
||||
ctx.load(room_id)
|
||||
ctx.init_save()
|
||||
try:
|
||||
ctx.server = websockets.serve(functools.partial(server, ctx=ctx), ctx.host, ctx.port, ssl=ssl_context)
|
||||
|
||||
await ctx.server
|
||||
except OSError: # likely port in use
|
||||
ctx.server = websockets.serve(functools.partial(server, ctx=ctx), ctx.host, 0, ssl=ssl_context)
|
||||
await ctx.server
|
||||
except OSError: # likely port in use
|
||||
ctx.server = websockets.serve(functools.partial(server, ctx=ctx), ctx.host, 0, ssl=ssl_context)
|
||||
|
||||
await ctx.server
|
||||
port = 0
|
||||
for wssocket in ctx.server.ws_server.sockets:
|
||||
socketname = wssocket.getsockname()
|
||||
if wssocket.family == socket.AF_INET6:
|
||||
# Prefer IPv4, as most users seem to not have working ipv6 support
|
||||
if not port:
|
||||
await ctx.server
|
||||
port = 0
|
||||
for wssocket in ctx.server.ws_server.sockets:
|
||||
socketname = wssocket.getsockname()
|
||||
if wssocket.family == socket.AF_INET6:
|
||||
# Prefer IPv4, as most users seem to not have working ipv6 support
|
||||
if not port:
|
||||
port = socketname[1]
|
||||
elif wssocket.family == socket.AF_INET:
|
||||
port = socketname[1]
|
||||
elif wssocket.family == socket.AF_INET:
|
||||
port = socketname[1]
|
||||
if port:
|
||||
logging.info(f'Hosting game at {host}:{port}')
|
||||
if port:
|
||||
ctx.logger.info(f'Hosting game at {host}:{port}')
|
||||
with db_session:
|
||||
room = Room.get(id=ctx.room_id)
|
||||
room.last_port = port
|
||||
else:
|
||||
ctx.logger.exception("Could not determine port. Likely hosting failure.")
|
||||
with db_session:
|
||||
room = Room.get(id=ctx.room_id)
|
||||
room.last_port = port
|
||||
else:
|
||||
logging.exception("Could not determine port. Likely hosting failure.")
|
||||
with db_session:
|
||||
ctx.auto_shutdown = Room.get(id=room_id).timeout
|
||||
ctx.shutdown_task = asyncio.create_task(auto_shutdown(ctx, []))
|
||||
await ctx.shutdown_task
|
||||
ctx.auto_shutdown = Room.get(id=room_id).timeout
|
||||
ctx.shutdown_task = asyncio.create_task(auto_shutdown(ctx, []))
|
||||
await ctx.shutdown_task
|
||||
|
||||
# ensure auto launch is on the same page in regard to room activity.
|
||||
with db_session:
|
||||
room: Room = Room.get(id=ctx.room_id)
|
||||
room.last_activity = datetime.datetime.utcnow() - datetime.timedelta(seconds=room.timeout + 60)
|
||||
# ensure auto launch is on the same page in regard to room activity.
|
||||
with db_session:
|
||||
room: Room = Room.get(id=ctx.room_id)
|
||||
room.last_activity = datetime.datetime.utcnow() - datetime.timedelta(seconds=room.timeout + 60)
|
||||
|
||||
logging.info("Shutting down")
|
||||
|
||||
with Locker(room_id):
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
with db_session:
|
||||
room = Room.get(id=room_id)
|
||||
@@ -228,3 +263,17 @@ def run_server_process(room_id, ponyconfig: dict, static_server_data: dict,
|
||||
# ensure the Room does not spin up again on its own, minute of safety buffer
|
||||
room.last_activity = datetime.datetime.utcnow() - datetime.timedelta(minutes=1, seconds=room.timeout)
|
||||
raise
|
||||
finally:
|
||||
rooms_shutting_down.put(room_id)
|
||||
|
||||
class Starter(threading.Thread):
|
||||
def run(self):
|
||||
while 1:
|
||||
next_room = rooms_to_run.get(block=True, timeout=None)
|
||||
asyncio.run_coroutine_threadsafe(start_room(next_room), loop)
|
||||
logging.info(f"Starting room {next_room} on {name}.")
|
||||
|
||||
starter = Starter()
|
||||
starter.daemon = True
|
||||
starter.start()
|
||||
loop.run_forever()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
# A Link to the Past
|
||||
/worlds/alttp/ @Berserker66
|
||||
|
||||
# Aquaria
|
||||
/worlds/aquaria/ @tioui
|
||||
|
||||
# ArchipIDLE
|
||||
/worlds/archipidle/ @LegendaryLinux
|
||||
|
||||
@@ -25,6 +28,9 @@
|
||||
# Blasphemous
|
||||
/worlds/blasphemous/ @TRPG0
|
||||
|
||||
# Bomb Rush Cyberfunk
|
||||
/worlds/bomb_rush_cyberfunk/ @TRPG0
|
||||
|
||||
# Bumper Stickers
|
||||
/worlds/bumpstik/ @FelicitusNeko
|
||||
|
||||
|
||||
@@ -155,10 +155,12 @@ Gives the player starting hints for where the items defined here are.
|
||||
Gives the player starting hints for the items on locations defined here.
|
||||
|
||||
### ExcludeLocations
|
||||
Marks locations given here as `LocationProgressType.Excluded` so that progression items can't be placed on them.
|
||||
Marks locations given here as `LocationProgressType.Excluded` so that neither progression nor useful items can be
|
||||
placed on them.
|
||||
|
||||
### PriorityLocations
|
||||
Marks locations given here as `LocationProgressType.Priority` forcing progression items on them.
|
||||
Marks locations given here as `LocationProgressType.Priority` forcing progression items on them if any are available in
|
||||
the pool.
|
||||
|
||||
### ItemLinks
|
||||
Allows users to share their item pool with other players. Currently item links are per game. A link of one game between
|
||||
|
||||
@@ -17,13 +17,14 @@ Then run any of the starting point scripts, like Generate.py, and the included M
|
||||
required modules and after pressing enter proceed to install everything automatically.
|
||||
After this, you should be able to run the programs.
|
||||
|
||||
* `Launcher.py` gives access to many components, including clients registered in `worlds/LauncherComponents.py`.
|
||||
* The Launcher button "Generate Template Options" will generate default yamls for all worlds.
|
||||
* With yaml(s) in the `Players` folder, `Generate.py` will generate the multiworld archive.
|
||||
* `MultiServer.py`, with the filename of the generated archive as a command line parameter, will host the multiworld locally.
|
||||
* `--log_network` is a command line parameter useful for debugging.
|
||||
* `WebHost.py` will host the website on your computer.
|
||||
* You can copy `docs/webhost configuration sample.yaml` to `config.yaml`
|
||||
to change WebHost options (like the web hosting port number).
|
||||
* As a side effect, `WebHost.py` creates the template yamls for all the games in `WebHostLib/static/generated`.
|
||||
|
||||
|
||||
## Windows
|
||||
|
||||
@@ -25,6 +25,8 @@ class TestBase(unittest.TestCase):
|
||||
{"medallions", "stones", "rewards", "logic_bottles"},
|
||||
"Starcraft 2":
|
||||
{"Missions", "WoL Missions"},
|
||||
"Yu-Gi-Oh! 2006":
|
||||
{"Campaign Boss Beaten"}
|
||||
}
|
||||
for game_name, world_type in AutoWorldRegister.world_types.items():
|
||||
with self.subTest(game_name, game_name=game_name):
|
||||
|
||||
@@ -206,8 +206,8 @@ class World(metaclass=AutoWorldRegister):
|
||||
|
||||
game: ClassVar[str]
|
||||
"""name the game"""
|
||||
topology_present: ClassVar[bool] = False
|
||||
"""indicate if world type has any meaningful layout/pathing"""
|
||||
topology_present: bool = False
|
||||
"""indicate if this world has any meaningful layout/pathing"""
|
||||
|
||||
all_item_and_group_names: ClassVar[FrozenSet[str]] = frozenset()
|
||||
"""gets automatically populated with all item and item group names"""
|
||||
|
||||
210
worlds/aquaria/Items.py
Normal file
210
worlds/aquaria/Items.py
Normal file
@@ -0,0 +1,210 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Fri, 15 Mar 2024 18:41:40 +0000
|
||||
Description: Manage items in the Aquaria game multiworld randomizer
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from enum import Enum
|
||||
from BaseClasses import Item, ItemClassification
|
||||
|
||||
class ItemType(Enum):
|
||||
"""
|
||||
Used to indicate to the multi-world if an item is usefull or not
|
||||
"""
|
||||
NORMAL = 0
|
||||
PROGRESSION = 1
|
||||
JUNK = 2
|
||||
|
||||
class ItemGroup(Enum):
|
||||
"""
|
||||
Used to group items
|
||||
"""
|
||||
COLLECTIBLE = 0
|
||||
INGREDIENT = 1
|
||||
RECIPE = 2
|
||||
HEALTH = 3
|
||||
UTILITY = 4
|
||||
SONG = 5
|
||||
TURTLE = 6
|
||||
|
||||
class AquariaItem(Item):
|
||||
"""
|
||||
A single item in the Aquaria game.
|
||||
"""
|
||||
game: str = "Aquaria"
|
||||
"""The name of the game"""
|
||||
|
||||
def __init__(self, name: str, classification: ItemClassification,
|
||||
code: Optional[int], player: int):
|
||||
"""
|
||||
Initialisation of the Item
|
||||
:param name: The name of the item
|
||||
:param classification: If the item is usefull or not
|
||||
:param code: The ID of the item (if None, it is an event)
|
||||
:param player: The ID of the player in the multiworld
|
||||
"""
|
||||
super().__init__(name, classification, code, player)
|
||||
|
||||
class ItemData:
|
||||
"""
|
||||
Data of an item.
|
||||
"""
|
||||
id:int
|
||||
count:int
|
||||
type:ItemType
|
||||
group:ItemGroup
|
||||
|
||||
def __init__(self, id:int, count:int, type:ItemType, group:ItemGroup):
|
||||
"""
|
||||
Initialisation of the item data
|
||||
@param id: The item ID
|
||||
@param count: the number of items in the pool
|
||||
@param type: the importance type of the item
|
||||
@param group: the usage of the item in the game
|
||||
"""
|
||||
self.id = id
|
||||
self.count = count
|
||||
self.type = type
|
||||
self.group = group
|
||||
|
||||
"""Information data for every (not event) item."""
|
||||
item_table = {
|
||||
# name: ID, Nb, Item Type, Item Group
|
||||
"Anemone": ItemData(698000, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_anemone
|
||||
"Arnassi statue": ItemData(698001, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_arnassi_statue
|
||||
"Big seed": ItemData(698002, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_big_seed
|
||||
"Glowing seed": ItemData(698003, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_bio_seed
|
||||
"Black pearl": ItemData(698004, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_blackpearl
|
||||
"Baby blaster": ItemData(698005, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_blaster
|
||||
"Crab armor": ItemData(698006, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_crab_costume
|
||||
"Baby dumbo": ItemData(698007, 1, ItemType.PROGRESSION, ItemGroup.UTILITY), # collectible_dumbo
|
||||
"Tooth": ItemData(698008, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_energy_boss
|
||||
"Energy statue": ItemData(698009, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_energy_statue
|
||||
"Krotite armor": ItemData(698010, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_energy_temple
|
||||
"Golden starfish": ItemData(698011, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_gold_star
|
||||
"Golden gear": ItemData(698012, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_golden_gear
|
||||
"Jelly beacon": ItemData(698013, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_jelly_beacon
|
||||
"Jelly costume": ItemData(698014, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_jelly_costume
|
||||
"Jelly plant": ItemData(698015, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_jelly_plant
|
||||
"Mithalas doll": ItemData(698016, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithala_doll
|
||||
"Mithalan dress": ItemData(698017, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithalan_costume
|
||||
"Mithalas banner": ItemData(698018, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithalas_banner
|
||||
"Mithalas pot": ItemData(698019, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithalas_pot
|
||||
"Mutant costume": ItemData(698020, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mutant_costume
|
||||
"Baby nautilus": ItemData(698021, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_nautilus
|
||||
"Baby piranha": ItemData(698022, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_piranha
|
||||
"Arnassi Armor": ItemData(698023, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_seahorse_costume
|
||||
"Seed bag": ItemData(698024, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_seed_bag
|
||||
"King's Skull": ItemData(698025, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_skull
|
||||
"Song plant spore": ItemData(698026, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_spore_seed
|
||||
"Stone head": ItemData(698027, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_stone_head
|
||||
"Sun key": ItemData(698028, 1, ItemType.NORMAL, ItemGroup.COLLECTIBLE), # collectible_sun_key
|
||||
"Girl costume": ItemData(698029, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_teen_costume
|
||||
"Odd container": ItemData(698030, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_treasure_chest
|
||||
"Trident": ItemData(698031, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_trident_head
|
||||
"Turtle egg": ItemData(698032, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_turtle_egg
|
||||
"Jelly egg": ItemData(698033, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_upsidedown_seed
|
||||
"Urchin costume": ItemData(698034, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_urchin_costume
|
||||
"Baby walker": ItemData(698035, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_walker
|
||||
"Vedha's Cure-All-All": ItemData(698036, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_Vedha'sCure-All
|
||||
"Zuuna's perogi": ItemData(698037, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_Zuuna'sperogi
|
||||
"Arcane poultice": ItemData(698038, 7, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_arcanepoultice
|
||||
"Berry ice cream": ItemData(698039, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_berryicecream
|
||||
"Buttery sea loaf": ItemData(698040, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_butterysealoaf
|
||||
"Cold borscht": ItemData(698041, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_coldborscht
|
||||
"Cold soup": ItemData(698042, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_coldsoup
|
||||
"Crab cake": ItemData(698043, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_crabcake
|
||||
"Divine soup": ItemData(698044, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_divinesoup
|
||||
"Dumbo ice cream": ItemData(698045, 3, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_dumboicecream
|
||||
"Fish oil": ItemData(698046, 2, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_fishoil
|
||||
"Glowing egg": ItemData(698047, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_glowingegg
|
||||
"Hand roll": ItemData(698048, 5, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_handroll
|
||||
"Healing poultice": ItemData(698049, 4, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_healingpoultice
|
||||
"Hearty soup": ItemData(698050, 5, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_heartysoup
|
||||
"Hot borscht": ItemData(698051, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_hotborscht
|
||||
"Hot soup": ItemData(698052, 3, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_hotsoup
|
||||
"Ice cream": ItemData(698053, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_icecream
|
||||
"Leadership roll": ItemData(698054, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leadershiproll
|
||||
"Leaf poultice": ItemData(698055, 5, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_leafpoultice
|
||||
"Leeching poultice": ItemData(698056, 4, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leechingpoultice
|
||||
"Legendary cake": ItemData(698057, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_legendarycake
|
||||
"Loaf of life": ItemData(698058, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_loafoflife
|
||||
"Long life soup": ItemData(698059, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_longlifesoup
|
||||
"Magic soup": ItemData(698060, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_magicsoup
|
||||
"Mushroom x 2": ItemData(698061, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_mushroom
|
||||
"Perogi": ItemData(698062, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_perogi
|
||||
"Plant leaf": ItemData(698063, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_plantleaf
|
||||
"Plump perogi": ItemData(698064, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_plumpperogi
|
||||
"Poison loaf": ItemData(698065, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_poisonloaf
|
||||
"Poison soup": ItemData(698066, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_poisonsoup
|
||||
"Rainbow mushroom": ItemData(698067, 4, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_rainbowmushroom
|
||||
"Rainbow soup": ItemData(698068, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_rainbowsoup
|
||||
"Red berry": ItemData(698069, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_redberry
|
||||
"Red bulb x 2": ItemData(698070, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_redbulb
|
||||
"Rotten cake": ItemData(698071, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_rottencake
|
||||
"Rotten loaf x 8": ItemData(698072, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_rottenloaf
|
||||
"Rotten meat": ItemData(698073, 5, ItemType.JUNK, ItemGroup.INGREDIENT), # ingredient_rottenmeat
|
||||
"Royal soup": ItemData(698074, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_royalsoup
|
||||
"Sea cake": ItemData(698075, 4, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_seacake
|
||||
"Sea loaf": ItemData(698076, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_sealoaf
|
||||
"Shark fin soup": ItemData(698077, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_sharkfinsoup
|
||||
"Sight poultice": ItemData(698078, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_sightpoultice
|
||||
"Small bone x 2": ItemData(698079, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallbone
|
||||
"Small egg": ItemData(698080, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallegg
|
||||
"Small tentacle x 2": ItemData(698081, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smalltentacle
|
||||
"Special bulb": ItemData(698082, 5, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_specialbulb
|
||||
"Special cake": ItemData(698083, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_specialcake
|
||||
"Spicy meat x 2": ItemData(698084, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_spicymeat
|
||||
"Spicy roll": ItemData(698085, 11, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_spicyroll
|
||||
"Spicy soup": ItemData(698086, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_spicysoup
|
||||
"Spider roll": ItemData(698087, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_spiderroll
|
||||
"Swamp cake": ItemData(698088, 3, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_swampcake
|
||||
"Tasty cake": ItemData(698089, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_tastycake
|
||||
"Tasty roll": ItemData(698090, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_tastyroll
|
||||
"Tough cake": ItemData(698091, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_toughcake
|
||||
"Turtle soup": ItemData(698092, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_turtlesoup
|
||||
"Vedha sea crisp": ItemData(698093, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_vedhaseacrisp
|
||||
"Veggie cake": ItemData(698094, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_veggiecake
|
||||
"Veggie ice cream": ItemData(698095, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_veggieicecream
|
||||
"Veggie soup": ItemData(698096, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_veggiesoup
|
||||
"Volcano roll": ItemData(698097, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_volcanoroll
|
||||
"Health upgrade": ItemData(698098, 5, ItemType.NORMAL, ItemGroup.HEALTH), # upgrade_health_?
|
||||
"Wok": ItemData(698099, 1, ItemType.NORMAL, ItemGroup.UTILITY), # upgrade_wok
|
||||
"Eel oil x 2": ItemData(698100, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_eeloil
|
||||
"Fish meat x 2": ItemData(698101, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_fishmeat
|
||||
"Fish oil x 3": ItemData(698102, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_fishoil
|
||||
"Glowing egg x 2": ItemData(698103, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_glowingegg
|
||||
"Healing poultice x 2": ItemData(698104, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_healingpoultice
|
||||
"Hot soup x 2": ItemData(698105, 1, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_hotsoup
|
||||
"Leadership roll x 2": ItemData(698106, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leadershiproll
|
||||
"Leaf poultice x 3": ItemData(698107, 2, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_leafpoultice
|
||||
"Plant leaf x 2": ItemData(698108, 2, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_plantleaf
|
||||
"Plant leaf x 3": ItemData(698109, 4, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_plantleaf
|
||||
"Rotten meat x 2": ItemData(698110, 1, ItemType.JUNK, ItemGroup.INGREDIENT), # ingredient_rottenmeat
|
||||
"Rotten meat x 8": ItemData(698111, 1, ItemType.JUNK, ItemGroup.INGREDIENT), # ingredient_rottenmeat
|
||||
"Sea loaf x 2": ItemData(698112, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_sealoaf
|
||||
"Small bone x 3": ItemData(698113, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallbone
|
||||
"Small egg x 2": ItemData(698114, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallegg
|
||||
"Li and Li song": ItemData(698115, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_li
|
||||
"Shield song": ItemData(698116, 1, ItemType.NORMAL, ItemGroup.SONG), # song_shield
|
||||
"Beast form": ItemData(698117, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_beast
|
||||
"Sun form": ItemData(698118, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_sun
|
||||
"Nature form": ItemData(698119, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_nature
|
||||
"Energy form": ItemData(698120, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_energy
|
||||
"Bind song": ItemData(698121, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_bind
|
||||
"Fish form": ItemData(698122, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_fish
|
||||
"Spirit form": ItemData(698123, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_spirit
|
||||
"Dual form": ItemData(698124, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_dual
|
||||
"Transturtle Veil top left": ItemData(698125, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_veil01
|
||||
"Transturtle Veil top right": ItemData(698126, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_veil02
|
||||
"Transturtle Open Water top right": ItemData(698127, 1, ItemType.PROGRESSION,
|
||||
ItemGroup.TURTLE), # transport_openwater03
|
||||
"Transturtle Forest bottom left": ItemData(698128, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_forest04
|
||||
"Transturtle Home water": ItemData(698129, 1, ItemType.NORMAL, ItemGroup.TURTLE), # transport_mainarea
|
||||
"Transturtle Abyss right": ItemData(698130, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_abyss03
|
||||
"Transturtle Final Boss": ItemData(698131, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_finalboss
|
||||
"Transturtle Simon says": ItemData(698132, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_forest05
|
||||
"Transturtle Arnassi ruins": ItemData(698133, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_seahorse
|
||||
}
|
||||
|
||||
574
worlds/aquaria/Locations.py
Normal file
574
worlds/aquaria/Locations.py
Normal file
@@ -0,0 +1,574 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Fri, 15 Mar 2024 18:41:40 +0000
|
||||
Description: Manage locations in the Aquaria game multiworld randomizer
|
||||
"""
|
||||
|
||||
from BaseClasses import Location
|
||||
|
||||
|
||||
class AquariaLocation(Location):
|
||||
"""
|
||||
A location in the game.
|
||||
"""
|
||||
game: str = "Aquaria"
|
||||
"""The name of the game"""
|
||||
|
||||
def __init__(self, player: int, name="", code=None, parent=None) -> None:
|
||||
"""
|
||||
Initialisation of the object
|
||||
:param player: the ID of the player
|
||||
:param name: the name of the location
|
||||
:param code: the ID (or address) of the location (Event if None)
|
||||
:param parent: the Region that this location belongs to
|
||||
"""
|
||||
super(AquariaLocation, self).__init__(player, name, code, parent)
|
||||
self.event = code is None
|
||||
|
||||
|
||||
class AquariaLocations:
|
||||
|
||||
locations_verse_cave_r = {
|
||||
"Verse cave, bulb in the skeleton room": 698107,
|
||||
"Verse cave, bulb in the path left of the skeleton room": 698108,
|
||||
"Verse cave right area, Big Seed": 698175,
|
||||
}
|
||||
|
||||
locations_verse_cave_l = {
|
||||
"Verse cave, the Naija hint about here shield ability": 698200,
|
||||
"Verse cave left area, bulb in the center part": 698021,
|
||||
"Verse cave left area, bulb in the right part": 698022,
|
||||
"Verse cave left area, bulb under the rock at the end of the path": 698023,
|
||||
}
|
||||
|
||||
locations_home_water = {
|
||||
"Home water, bulb below the grouper fish": 698058,
|
||||
"Home water, bulb in the path bellow Nautilus Prime": 698059,
|
||||
"Home water, bulb in the little room above the grouper fish": 698060,
|
||||
"Home water, bulb in the end of the left path from the verse cave": 698061,
|
||||
"Home water, bulb in the top left path": 698062,
|
||||
"Home water, bulb in the bottom left room": 698063,
|
||||
"Home water, bulb close to the Naija's home": 698064,
|
||||
"Home water, bulb under the rock in the left path from the verse cave": 698065,
|
||||
}
|
||||
|
||||
locations_home_water_nautilus = {
|
||||
"Home water, Nautilus Egg": 698194,
|
||||
}
|
||||
|
||||
locations_home_water_transturtle = {
|
||||
"Home water, Transturtle": 698213,
|
||||
}
|
||||
|
||||
locations_naija_home = {
|
||||
"Naija's home, bulb after the energy door": 698119,
|
||||
"Naija's home, bulb under the rock at the right of the main path": 698120,
|
||||
}
|
||||
|
||||
locations_song_cave = {
|
||||
"Song cave, Erulian spirit": 698206,
|
||||
"Song cave, bulb in the top left part": 698071,
|
||||
"Song cave, bulb in the big anemone room": 698072,
|
||||
"Song cave, bulb in the path to the singing statues": 698073,
|
||||
"Song cave, bulb under the rock in the path to the singing statues": 698074,
|
||||
"Song cave, bulb under the rock close to the song door": 698075,
|
||||
"Song cave, Verse egg": 698160,
|
||||
"Song cave, Jelly beacon": 698178,
|
||||
"Song cave, Anemone seed": 698162,
|
||||
}
|
||||
|
||||
locations_energy_temple_1 = {
|
||||
"Energy temple first area, beating the energy statue": 698205,
|
||||
"Energy temple first area, bulb in the bottom room blocked by a rock": 698027,
|
||||
}
|
||||
|
||||
locations_energy_temple_idol = {
|
||||
"Energy temple first area, Energy Idol": 698170,
|
||||
}
|
||||
|
||||
locations_energy_temple_2 = {
|
||||
"Energy temple second area, bulb under the rock": 698028,
|
||||
}
|
||||
|
||||
locations_energy_temple_altar = {
|
||||
"Energy temple bottom entrance, Krotite armor": 698163,
|
||||
}
|
||||
|
||||
locations_energy_temple_3 = {
|
||||
"Energy temple third area, bulb in the bottom path": 698029,
|
||||
}
|
||||
|
||||
locations_energy_temple_boss = {
|
||||
"Energy temple boss area, Fallen god tooth": 698169,
|
||||
}
|
||||
|
||||
locations_energy_temple_blaster_room = {
|
||||
"Energy temple blaster room, Blaster egg": 698195,
|
||||
}
|
||||
|
||||
locations_openwater_tl = {
|
||||
"Open water top left area, bulb under the rock in the right path": 698001,
|
||||
"Open water top left area, bulb under the rock in the left path": 698002,
|
||||
"Open water top left area, bulb to the right of the save cristal": 698003,
|
||||
}
|
||||
|
||||
locations_openwater_tr = {
|
||||
"Open water top right area, bulb in the small path before Mithalas": 698004,
|
||||
"Open water top right area, bulb in the path from the left entrance": 698005,
|
||||
"Open water top right area, bulb in the clearing close to the bottom exit": 698006,
|
||||
"Open water top right area, bulb in the big clearing close to the save cristal": 698007,
|
||||
"Open water top right area, bulb in the big clearing to the top exit": 698008,
|
||||
"Open water top right area, first urn in the Mithalas exit": 698148,
|
||||
"Open water top right area, second urn in the Mithalas exit": 698149,
|
||||
"Open water top right area, third urn in the Mithalas exit": 698150,
|
||||
}
|
||||
locations_openwater_tr_turtle = {
|
||||
"Open water top right area, bulb in the turtle room": 698009,
|
||||
"Open water top right area, Transturtle": 698211,
|
||||
}
|
||||
|
||||
locations_openwater_bl = {
|
||||
"Open water bottom left area, bulb behind the chomper fish": 698011,
|
||||
"Open water bottom left area, bulb inside the downest fish pass": 698010,
|
||||
}
|
||||
|
||||
locations_skeleton_path = {
|
||||
"Open water skeleton path, bulb close to the right exit": 698012,
|
||||
"Open water skeleton path, bulb behind the chomper fish": 698013,
|
||||
}
|
||||
|
||||
locations_skeleton_path_sc = {
|
||||
"Open water skeleton path, King skull": 698177,
|
||||
}
|
||||
|
||||
locations_arnassi = {
|
||||
"Arnassi Ruins, bulb in the right part": 698014,
|
||||
"Arnassi Ruins, bulb in the left part": 698015,
|
||||
"Arnassi Ruins, bulb in the center part": 698016,
|
||||
"Arnassi ruins, Song plant spore on the top of the ruins": 698179,
|
||||
"Arnassi ruins, Arnassi Armor": 698191,
|
||||
}
|
||||
|
||||
locations_arnassi_path = {
|
||||
"Arnassi Ruins, Arnassi statue": 698164,
|
||||
"Arnassi Ruins, Transturtle": 698217,
|
||||
}
|
||||
|
||||
locations_arnassi_crab_boss = {
|
||||
"Arnassi ruins, Crab armor": 698187,
|
||||
}
|
||||
|
||||
locations_simon = {
|
||||
"Kelp forest, beating Simon says": 698156,
|
||||
"Simon says area, Transturtle": 698216,
|
||||
}
|
||||
|
||||
locations_mithalas_city = {
|
||||
"Mithalas city, first bulb in the left city part": 698030,
|
||||
"Mithalas city, second bulb in the left city part": 698035,
|
||||
"Mithalas city, bulb in the right part": 698031,
|
||||
"Mithalas city, bulb at the top of the city": 698033,
|
||||
"Mithalas city, first bulb in a broken home": 698034,
|
||||
"Mithalas city, second bulb in a broken home": 698041,
|
||||
"Mithalas city, bulb in the bottom left part": 698037,
|
||||
"Mithalas city, first bulb in one of the homes": 698038,
|
||||
"Mithalas city, second bulb in one of the homes": 698039,
|
||||
"Mithalas city, first urn in one of the homes": 698123,
|
||||
"Mithalas city, second urn in one of the homes": 698124,
|
||||
"Mithalas city, first urn in the city reserve": 698125,
|
||||
"Mithalas city, second urn in the city reserve": 698126,
|
||||
"Mithalas city, third urn in the city reserve": 698127,
|
||||
}
|
||||
|
||||
locations_mithalas_city_top_path = {
|
||||
"Mithalas city, first bulb at the end of the top path": 698032,
|
||||
"Mithalas city, second bulb at the end of the top path": 698040,
|
||||
"Mithalas city, bulb in the top path": 698036,
|
||||
"Mithalas city, Mithalas pot": 698174,
|
||||
"Mithalas city, urn in the cathedral flower tube entrance": 698128,
|
||||
}
|
||||
|
||||
locations_mithalas_city_fishpass = {
|
||||
"Mithalas city, Doll": 698173,
|
||||
"Mithalas city, urn inside a home fish pass": 698129,
|
||||
}
|
||||
|
||||
locations_cathedral_l = {
|
||||
"Mithalas city castle, bulb in the flesh hole": 698042,
|
||||
"Mithalas city castle, Blue banner": 698165,
|
||||
"Mithalas city castle, urn in the bedroom": 698130,
|
||||
"Mithalas city castle, first urn of the single lamp path": 698131,
|
||||
"Mithalas city castle, second urn of the single lamp path": 698132,
|
||||
"Mithalas city castle, urn in the bottom room": 698133,
|
||||
"Mithalas city castle, first urn on the entrance path": 698134,
|
||||
"Mithalas city castle, second urn on the entrance path": 698135,
|
||||
}
|
||||
|
||||
locations_cathedral_l_tube = {
|
||||
"Mithalas castle, beating the priests": 698208,
|
||||
}
|
||||
|
||||
locations_cathedral_l_sc = {
|
||||
"Mithalas city castle, Trident head": 698183,
|
||||
}
|
||||
|
||||
locations_cathedral_r = {
|
||||
"Mithalas cathedral, first urn in the top right room": 698136,
|
||||
"Mithalas cathedral, second urn in the top right room": 698137,
|
||||
"Mithalas cathedral, third urn in the top right room": 698138,
|
||||
"Mithalas cathedral, urn in the flesh room with fleas": 698139,
|
||||
"Mithalas cathedral, first urn in the bottom right path": 698140,
|
||||
"Mithalas cathedral, second urn in the bottom right path": 698141,
|
||||
"Mithalas cathedral, urn behind the flesh vein": 698142,
|
||||
"Mithalas cathedral, urn in the top left eyes boss room": 698143,
|
||||
"Mithalas cathedral, first urn in the path behind the flesh vein": 698144,
|
||||
"Mithalas cathedral, second urn in the path behind the flesh vein": 698145,
|
||||
"Mithalas cathedral, third urn in the path behind the flesh vein": 698146,
|
||||
"Mithalas cathedral, one of the urns in the top right room": 698147,
|
||||
"Mithalas cathedral, Mithalan Dress": 698189,
|
||||
"Mithalas cathedral right area, urn bellow the left entrance": 698198,
|
||||
}
|
||||
|
||||
locations_cathedral_underground = {
|
||||
"Cathedral underground, bulb in the center part": 698113,
|
||||
"Cathedral underground, first bulb in the top left part": 698114,
|
||||
"Cathedral underground, second bulb in the top left part": 698115,
|
||||
"Cathedral underground, third bulb in the top left part": 698116,
|
||||
"Cathedral underground, bulb close to the save cristal": 698117,
|
||||
"Cathedral underground, bulb in the bottom right path": 698118,
|
||||
}
|
||||
|
||||
locations_cathedral_boss = {
|
||||
"Cathedral boss area, beating Mithalan God": 698202,
|
||||
}
|
||||
|
||||
locations_forest_tl = {
|
||||
"Kelp Forest top left area, bulb in the bottom left clearing": 698044,
|
||||
"Kelp Forest top left area, bulb in the path down from the top left clearing": 698045,
|
||||
"Kelp Forest top left area, bulb in the top left clearing": 698046,
|
||||
"Kelp Forest top left, Jelly Egg": 698185,
|
||||
}
|
||||
|
||||
locations_forest_tl_fp = {
|
||||
"Kelp Forest top left area, bulb close to the Verse egg": 698047,
|
||||
"Kelp forest top left area, Verse egg": 698158,
|
||||
}
|
||||
|
||||
locations_forest_tr = {
|
||||
"Kelp Forest top right area, bulb under the rock in the right path": 698048,
|
||||
"Kelp Forest top right area, bulb at the left of the center clearing": 698049,
|
||||
"Kelp Forest top right area, bulb in the left path's big room": 698051,
|
||||
"Kelp Forest top right area, bulb in the left path's small room": 698052,
|
||||
"Kelp Forest top right area, bulb at the top of the center clearing": 698053,
|
||||
"Kelp forest top right area, Black pearl": 698167,
|
||||
}
|
||||
|
||||
locations_forest_tr_fp = {
|
||||
"Kelp Forest top right area, bulb in the top fish pass": 698050,
|
||||
}
|
||||
|
||||
locations_forest_bl = {
|
||||
"Kelp Forest bottom left area, bulb close to the spirit crystals": 698054,
|
||||
"Kelp forest bottom left area, Walker baby": 698186,
|
||||
"Kelp Forest bottom left area, Transturtle": 698212,
|
||||
}
|
||||
|
||||
locations_forest_br = {
|
||||
"Kelp forest bottom right area, Odd Container": 698168,
|
||||
}
|
||||
|
||||
locations_forest_boss = {
|
||||
"Kelp forest boss area, beating Drunian God": 698204,
|
||||
}
|
||||
|
||||
locations_forest_boss_entrance = {
|
||||
"Kelp Forest boss room, bulb at the bottom of the area": 698055,
|
||||
}
|
||||
|
||||
locations_forest_fish_cave = {
|
||||
"Kelp Forest bottom left area, Fish cave puzzle": 698207,
|
||||
}
|
||||
|
||||
locations_forest_sprite_cave = {
|
||||
"Kelp Forest sprite cave, bulb inside the fish pass": 698056,
|
||||
}
|
||||
|
||||
locations_forest_sprite_cave_tube = {
|
||||
"Kelp Forest sprite cave, bulb in the second room": 698057,
|
||||
"Kelp Forest Sprite Cave, Seed bag": 698176,
|
||||
}
|
||||
|
||||
locations_mermog_cave = {
|
||||
"Mermog cave, bulb in the left part of the cave": 698121,
|
||||
}
|
||||
|
||||
locations_mermog_boss = {
|
||||
"Mermog cave, Piranha Egg": 698197,
|
||||
}
|
||||
|
||||
locations_veil_tl = {
|
||||
"The veil top left area, In the Li cave": 698199,
|
||||
"The veil top left area, bulb under the rock in the top right path": 698078,
|
||||
"The veil top left area, bulb hidden behind the blocking rock": 698076,
|
||||
"The veil top left area, Transturtle": 698209,
|
||||
}
|
||||
|
||||
locations_veil_tl_fp = {
|
||||
"The veil top left area, bulb inside the fish pass": 698077,
|
||||
}
|
||||
|
||||
locations_turtle_cave = {
|
||||
"Turtle cave, Turtle Egg": 698184,
|
||||
}
|
||||
|
||||
locations_turtle_cave_bubble = {
|
||||
"Turtle cave, bulb in bubble cliff": 698000,
|
||||
"Turtle cave, Urchin costume": 698193,
|
||||
}
|
||||
|
||||
locations_veil_tr_r = {
|
||||
"The veil top right area, bulb in the middle of the wall jump cliff": 698079,
|
||||
"The veil top right area, golden starfish at the bottom right of the bottom path": 698180,
|
||||
}
|
||||
|
||||
locations_veil_tr_l = {
|
||||
"The veil top right area, bulb in the top of the water fall": 698080,
|
||||
"The veil top right area, Transturtle": 698210,
|
||||
}
|
||||
|
||||
locations_veil_bl = {
|
||||
"The veil bottom area, bulb in the left path": 698082,
|
||||
}
|
||||
|
||||
locations_veil_b_sc = {
|
||||
"The veil bottom area, bulb in the spirit path": 698081,
|
||||
}
|
||||
|
||||
locations_veil_bl_fp = {
|
||||
"The veil bottom area, Verse egg": 698157,
|
||||
}
|
||||
|
||||
locations_veil_br = {
|
||||
"The veil bottom area, Stone Head": 698181,
|
||||
}
|
||||
|
||||
locations_octo_cave_t = {
|
||||
"Octopus cave, Dumbo Egg": 698196,
|
||||
}
|
||||
|
||||
locations_octo_cave_b = {
|
||||
"Octopus cave, bulb in the path below the octopus cave path": 698122,
|
||||
}
|
||||
|
||||
locations_sun_temple_l = {
|
||||
"Sun temple, bulb in the top left part": 698094,
|
||||
"Sun temple, bulb in the top right part": 698095,
|
||||
"Sun temple, bulb at the top of the high dark room": 698096,
|
||||
"Sun temple, Golden Gear": 698171,
|
||||
}
|
||||
|
||||
locations_sun_temple_r = {
|
||||
"Sun temple, first bulb of the temple": 698091,
|
||||
"Sun temple, bulb on the left part": 698092,
|
||||
"Sun temple, bulb in the hidden room of the right part": 698093,
|
||||
"Sun temple, Sun key": 698182,
|
||||
}
|
||||
|
||||
locations_sun_temple_boss_path = {
|
||||
"Sun Worm path, first path bulb": 698017,
|
||||
"Sun Worm path, second path bulb": 698018,
|
||||
"Sun Worm path, first cliff bulb": 698019,
|
||||
"Sun Worm path, second cliff bulb": 698020,
|
||||
}
|
||||
|
||||
locations_sun_temple_boss = {
|
||||
"Sun temple boss area, beating Sun God": 698203,
|
||||
}
|
||||
|
||||
locations_abyss_l = {
|
||||
"Abyss left area, bulb in hidden path room": 698024,
|
||||
"Abyss left area, bulb in the right part": 698025,
|
||||
"Abyss left area, Glowing seed": 698166,
|
||||
"Abyss left area, Glowing Plant": 698172,
|
||||
}
|
||||
|
||||
locations_abyss_lb = {
|
||||
"Abyss left area, bulb in the bottom fish pass": 698026,
|
||||
}
|
||||
|
||||
locations_abyss_r = {
|
||||
"Abyss right area, bulb behind the rock in the whale room": 698109,
|
||||
"Abyss right area, bulb in the middle path": 698110,
|
||||
"Abyss right area, bulb behind the rock in the middle path": 698111,
|
||||
"Abyss right area, bulb in the left green room": 698112,
|
||||
"Abyss right area, Transturtle": 698214,
|
||||
}
|
||||
|
||||
locations_ice_cave = {
|
||||
"Ice cave, bulb in the room to the right": 698083,
|
||||
"Ice cave, First bulbs in the top exit room": 698084,
|
||||
"Ice cave, Second bulbs in the top exit room": 698085,
|
||||
"Ice cave, third bulbs in the top exit room": 698086,
|
||||
"Ice cave, bulb in the left room": 698087,
|
||||
}
|
||||
|
||||
locations_bubble_cave = {
|
||||
"Bubble cave, bulb in the left cave wall": 698089,
|
||||
"Bubble cave, bulb in the right cave wall (behind the ice cristal)": 698090,
|
||||
}
|
||||
|
||||
locations_bubble_cave_boss = {
|
||||
"Bubble cave, Verse egg": 698161,
|
||||
}
|
||||
|
||||
locations_king_jellyfish_cave = {
|
||||
"King Jellyfish cave, bulb in the right path from King Jelly": 698088,
|
||||
"King Jellyfish cave, Jellyfish Costume": 698188,
|
||||
}
|
||||
|
||||
locations_whale = {
|
||||
"The whale, Verse egg": 698159,
|
||||
}
|
||||
|
||||
locations_sunken_city_r = {
|
||||
"Sunken city right area, crate close to the save cristal": 698154,
|
||||
"Sunken city right area, crate in the left bottom room": 698155,
|
||||
}
|
||||
|
||||
locations_sunken_city_l = {
|
||||
"Sunken city left area, crate in the little pipe room": 698151,
|
||||
"Sunken city left area, crate close to the save cristal": 698152,
|
||||
"Sunken city left area, crate before the bedroom": 698153,
|
||||
}
|
||||
|
||||
locations_sunken_city_l_bedroom = {
|
||||
"Sunken city left area, Girl Costume": 698192,
|
||||
}
|
||||
|
||||
locations_sunken_city_boss = {
|
||||
"Sunken city, bulb on the top of the boss area (boiler room)": 698043,
|
||||
}
|
||||
|
||||
locations_body_c = {
|
||||
"The body center area, breaking li cage": 698201,
|
||||
"The body main area, bulb on the main path blocking tube": 698097,
|
||||
}
|
||||
|
||||
locations_body_l = {
|
||||
"The body left area, first bulb in the top face room": 698066,
|
||||
"The body left area, second bulb in the top face room": 698069,
|
||||
"The body left area, bulb bellow the water stream": 698067,
|
||||
"The body left area, bulb in the top path to the top face room": 698068,
|
||||
"The body left area, bulb in the bottom face room": 698070,
|
||||
}
|
||||
|
||||
locations_body_rt = {
|
||||
"The body right area, bulb in the top face room": 698100,
|
||||
}
|
||||
|
||||
locations_body_rb = {
|
||||
"The body right area, bulb in the top path to the bottom face room": 698098,
|
||||
"The body right area, bulb in the bottom face room": 698099,
|
||||
}
|
||||
|
||||
locations_body_b = {
|
||||
"The body bottom area, bulb in the Jelly Zap room": 698101,
|
||||
"The body bottom area, bulb in the nautilus room": 698102,
|
||||
"The body bottom area, Mutant Costume": 698190,
|
||||
}
|
||||
|
||||
locations_final_boss_tube = {
|
||||
"Final boss area, first bulb in the turtle room": 698103,
|
||||
"Final boss area, second bulbs in the turtle room": 698104,
|
||||
"Final boss area, third bulbs in the turtle room": 698105,
|
||||
"Final boss area, Transturtle": 698215,
|
||||
}
|
||||
|
||||
locations_final_boss = {
|
||||
"Final boss area, bulb in the boss third form room": 698106,
|
||||
}
|
||||
|
||||
|
||||
location_table = {
|
||||
**AquariaLocations.locations_openwater_tl,
|
||||
**AquariaLocations.locations_openwater_tr,
|
||||
**AquariaLocations.locations_openwater_tr_turtle,
|
||||
**AquariaLocations.locations_openwater_bl,
|
||||
**AquariaLocations.locations_skeleton_path,
|
||||
**AquariaLocations.locations_skeleton_path_sc,
|
||||
**AquariaLocations.locations_arnassi,
|
||||
**AquariaLocations.locations_arnassi_path,
|
||||
**AquariaLocations.locations_arnassi_crab_boss,
|
||||
**AquariaLocations.locations_sun_temple_l,
|
||||
**AquariaLocations.locations_sun_temple_r,
|
||||
**AquariaLocations.locations_sun_temple_boss_path,
|
||||
**AquariaLocations.locations_sun_temple_boss,
|
||||
**AquariaLocations.locations_verse_cave_r,
|
||||
**AquariaLocations.locations_verse_cave_l,
|
||||
**AquariaLocations.locations_abyss_l,
|
||||
**AquariaLocations.locations_abyss_lb,
|
||||
**AquariaLocations.locations_abyss_r,
|
||||
**AquariaLocations.locations_energy_temple_1,
|
||||
**AquariaLocations.locations_energy_temple_2,
|
||||
**AquariaLocations.locations_energy_temple_3,
|
||||
**AquariaLocations.locations_energy_temple_boss,
|
||||
**AquariaLocations.locations_energy_temple_blaster_room,
|
||||
**AquariaLocations.locations_energy_temple_altar,
|
||||
**AquariaLocations.locations_energy_temple_idol,
|
||||
**AquariaLocations.locations_mithalas_city,
|
||||
**AquariaLocations.locations_mithalas_city_top_path,
|
||||
**AquariaLocations.locations_mithalas_city_fishpass,
|
||||
**AquariaLocations.locations_cathedral_l,
|
||||
**AquariaLocations.locations_cathedral_l_tube,
|
||||
**AquariaLocations.locations_cathedral_l_sc,
|
||||
**AquariaLocations.locations_cathedral_r,
|
||||
**AquariaLocations.locations_cathedral_underground,
|
||||
**AquariaLocations.locations_cathedral_boss,
|
||||
**AquariaLocations.locations_forest_tl,
|
||||
**AquariaLocations.locations_forest_tl_fp,
|
||||
**AquariaLocations.locations_forest_tr,
|
||||
**AquariaLocations.locations_forest_tr_fp,
|
||||
**AquariaLocations.locations_forest_bl,
|
||||
**AquariaLocations.locations_forest_br,
|
||||
**AquariaLocations.locations_forest_boss,
|
||||
**AquariaLocations.locations_forest_boss_entrance,
|
||||
**AquariaLocations.locations_forest_sprite_cave,
|
||||
**AquariaLocations.locations_forest_sprite_cave_tube,
|
||||
**AquariaLocations.locations_forest_fish_cave,
|
||||
**AquariaLocations.locations_home_water,
|
||||
**AquariaLocations.locations_home_water_transturtle,
|
||||
**AquariaLocations.locations_home_water_nautilus,
|
||||
**AquariaLocations.locations_body_l,
|
||||
**AquariaLocations.locations_body_rt,
|
||||
**AquariaLocations.locations_body_rb,
|
||||
**AquariaLocations.locations_body_c,
|
||||
**AquariaLocations.locations_body_b,
|
||||
**AquariaLocations.locations_final_boss_tube,
|
||||
**AquariaLocations.locations_final_boss,
|
||||
**AquariaLocations.locations_song_cave,
|
||||
**AquariaLocations.locations_veil_tl,
|
||||
**AquariaLocations.locations_veil_tl_fp,
|
||||
**AquariaLocations.locations_turtle_cave,
|
||||
**AquariaLocations.locations_turtle_cave_bubble,
|
||||
**AquariaLocations.locations_veil_tr_r,
|
||||
**AquariaLocations.locations_veil_tr_l,
|
||||
**AquariaLocations.locations_veil_bl,
|
||||
**AquariaLocations.locations_veil_b_sc,
|
||||
**AquariaLocations.locations_veil_bl_fp,
|
||||
**AquariaLocations.locations_veil_br,
|
||||
**AquariaLocations.locations_ice_cave,
|
||||
**AquariaLocations.locations_king_jellyfish_cave,
|
||||
**AquariaLocations.locations_bubble_cave,
|
||||
**AquariaLocations.locations_bubble_cave_boss,
|
||||
**AquariaLocations.locations_naija_home,
|
||||
**AquariaLocations.locations_mermog_cave,
|
||||
**AquariaLocations.locations_mermog_boss,
|
||||
**AquariaLocations.locations_octo_cave_t,
|
||||
**AquariaLocations.locations_octo_cave_b,
|
||||
**AquariaLocations.locations_sunken_city_l,
|
||||
**AquariaLocations.locations_sunken_city_r,
|
||||
**AquariaLocations.locations_sunken_city_boss,
|
||||
**AquariaLocations.locations_sunken_city_l_bedroom,
|
||||
**AquariaLocations.locations_simon,
|
||||
**AquariaLocations.locations_whale,
|
||||
}
|
||||
145
worlds/aquaria/Options.py
Normal file
145
worlds/aquaria/Options.py
Normal file
@@ -0,0 +1,145 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Fri, 15 Mar 2024 18:41:40 +0000
|
||||
Description: Manage options in the Aquaria game multiworld randomizer
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from Options import Toggle, Choice, Range, DeathLink, PerGameCommonOptions, DefaultOnToggle, StartInventoryPool
|
||||
|
||||
|
||||
class IngredientRandomizer(Choice):
|
||||
"""
|
||||
Randomize Ingredients. Select if the simple ingredients (that does not have
|
||||
a recipe) should be randomized. If 'common_ingredients' is selected, the
|
||||
randomization will exclude the "Red Bulb", "Special Bulb" and "Rukh Egg".
|
||||
"""
|
||||
display_name = "Randomize Ingredients"
|
||||
option_off = 0
|
||||
option_common_ingredients = 1
|
||||
option_all_ingredients = 2
|
||||
default = 0
|
||||
|
||||
|
||||
class DishRandomizer(Toggle):
|
||||
"""Randomize the drop of Dishes (Ingredients with recipe)."""
|
||||
display_name = "Dish Randomizer"
|
||||
|
||||
|
||||
class TurtleRandomizer(Choice):
|
||||
"""Randomize the transportation turtle."""
|
||||
display_name = "Turtle Randomizer"
|
||||
option_no_turtle_randomization = 0
|
||||
option_randomize_all_turtle = 1
|
||||
option_randomize_turtle_other_than_the_final_one = 2
|
||||
default = 2
|
||||
|
||||
|
||||
class EarlyEnergyForm(DefaultOnToggle):
|
||||
"""
|
||||
Force the Energy Form to be in a location before leaving the areas around the Home Water.
|
||||
"""
|
||||
display_name = "Early Energy Form"
|
||||
|
||||
|
||||
class AquarianTranslation(Toggle):
|
||||
"""Translate to English the Aquarian scripture in the game."""
|
||||
display_name = "Translate Aquarian"
|
||||
|
||||
|
||||
class BigBossesToBeat(Range):
|
||||
"""
|
||||
A number of big bosses to beat before having access to the creator (the final boss). The big bosses are
|
||||
"Fallen God", "Mithalan God", "Drunian God", "Sun God" and "The Golem".
|
||||
"""
|
||||
display_name = "Big bosses to beat"
|
||||
range_start = 0
|
||||
range_end = 5
|
||||
default = 0
|
||||
|
||||
|
||||
class MiniBossesToBeat(Range):
|
||||
"""
|
||||
A number of Minibosses to beat before having access to the creator (the final boss). Mini bosses are
|
||||
"Nautilus Prime", "Blaster Peg Prime", "Mergog", "Mithalan priests", "Octopus Prime", "Crabbius Maximus",
|
||||
"Mantis Shrimp Prime" and "King Jellyfish God Prime". Note that the Energy statue and Simon says are not
|
||||
mini bosses.
|
||||
"""
|
||||
display_name = "Mini bosses to beat"
|
||||
range_start = 0
|
||||
range_end = 8
|
||||
default = 0
|
||||
|
||||
|
||||
class Objective(Choice):
|
||||
"""
|
||||
The game objective can be only to kill the creator or to kill the creator
|
||||
and having obtained the three every secret memories
|
||||
"""
|
||||
display_name = "Objective"
|
||||
option_kill_the_creator = 0
|
||||
option_obtain_secrets_and_kill_the_creator = 1
|
||||
default = 0
|
||||
|
||||
class SkipFirstVision(Toggle):
|
||||
"""
|
||||
The first vision in the game; where Naija transform to Energy Form and get fload by enemy; is quite cool but
|
||||
can be quite long when you already know what is going on. This option can be used to skip this vision.
|
||||
"""
|
||||
display_name = "Skip first Naija's vision"
|
||||
|
||||
class NoProgressionHardOrHiddenLocation(Toggle):
|
||||
"""
|
||||
Make sure that there is no progression items at hard to get or hard to find locations.
|
||||
Those locations that will be very High location (that need beast form, soup and skill to get), every
|
||||
location in the bubble cave, locations that need you to cross a false wall without any indication, Arnassi
|
||||
race, bosses and mini-bosses. Usefull for those that want a casual run.
|
||||
"""
|
||||
display_name = "No progression in hard or hidden locations"
|
||||
|
||||
class LightNeededToGetToDarkPlaces(DefaultOnToggle):
|
||||
"""
|
||||
Make sure that the sun form or the dumbo pet can be aquired before getting to dark places. Be aware that navigating
|
||||
in dark place without light is extremely difficult.
|
||||
"""
|
||||
display_name = "Light needed to get to dark places"
|
||||
|
||||
class BindSongNeededToGetUnderRockBulb(Toggle):
|
||||
"""
|
||||
Make sure that the bind song can be aquired before having to obtain sing bulb under rocks.
|
||||
"""
|
||||
display_name = "Bind song needed to get sing bulbs under rocks"
|
||||
|
||||
|
||||
class UnconfineHomeWater(Choice):
|
||||
"""
|
||||
Open the way out of Home water area so that Naija can go to open water and beyond without the bind song.
|
||||
"""
|
||||
display_name = "Unconfine Home Water Area"
|
||||
option_off = 0
|
||||
option_via_energy_door = 1
|
||||
option_via_transturtle = 2
|
||||
option_via_both = 3
|
||||
default = 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class AquariaOptions(PerGameCommonOptions):
|
||||
"""
|
||||
Every option in the Aquaria randomizer
|
||||
"""
|
||||
start_inventory_from_pool: StartInventoryPool
|
||||
objective: Objective
|
||||
mini_bosses_to_beat: MiniBossesToBeat
|
||||
big_bosses_to_beat: BigBossesToBeat
|
||||
turtle_randomizer: TurtleRandomizer
|
||||
early_energy_form: EarlyEnergyForm
|
||||
light_needed_to_get_to_dark_places: LightNeededToGetToDarkPlaces
|
||||
bind_song_needed_to_get_under_rock_bulb: BindSongNeededToGetUnderRockBulb
|
||||
unconfine_home_water: UnconfineHomeWater
|
||||
no_progression_hard_or_hidden_locations: NoProgressionHardOrHiddenLocation
|
||||
ingredient_randomizer: IngredientRandomizer
|
||||
dish_randomizer: DishRandomizer
|
||||
aquarian_translation: AquarianTranslation
|
||||
skip_first_vision: SkipFirstVision
|
||||
death_link: DeathLink
|
||||
1401
worlds/aquaria/Regions.py
Executable file
1401
worlds/aquaria/Regions.py
Executable file
File diff suppressed because it is too large
Load Diff
218
worlds/aquaria/__init__.py
Normal file
218
worlds/aquaria/__init__.py
Normal file
@@ -0,0 +1,218 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Fri, 15 Mar 2024 18:41:40 +0000
|
||||
Description: Main module for Aquaria game multiworld randomizer
|
||||
"""
|
||||
|
||||
from typing import List, Dict, ClassVar, Any
|
||||
from ..AutoWorld import World, WebWorld
|
||||
from BaseClasses import Tutorial, MultiWorld, ItemClassification
|
||||
from .Items import item_table, AquariaItem, ItemType, ItemGroup
|
||||
from .Locations import location_table
|
||||
from .Options import AquariaOptions
|
||||
from .Regions import AquariaRegions
|
||||
|
||||
|
||||
class AquariaWeb(WebWorld):
|
||||
"""
|
||||
Class used to generate the Aquaria Game Web pages (setup, tutorial, etc.)
|
||||
"""
|
||||
theme = "ocean"
|
||||
|
||||
bug_report_page = "https://github.com/tioui/Aquaria_Randomizer/issues"
|
||||
|
||||
setup = Tutorial(
|
||||
"Multiworld Setup Guide",
|
||||
"A guide to setting up Aquaria for MultiWorld.",
|
||||
"English",
|
||||
"setup_en.md",
|
||||
"setup/en",
|
||||
["Tioui"]
|
||||
)
|
||||
|
||||
setup_fr = Tutorial(
|
||||
"Guide de configuration Multimonde",
|
||||
"Un guide pour configurer Aquaria MultiWorld",
|
||||
"Français",
|
||||
"setup_fr.md",
|
||||
"setup/fr",
|
||||
["Tioui"]
|
||||
)
|
||||
|
||||
tutorials = [setup, setup_fr]
|
||||
|
||||
|
||||
class AquariaWorld(World):
|
||||
"""
|
||||
Aquaria is a side-scrolling action-adventure game. It follows Naija, an
|
||||
aquatic humanoid woman, as she explores the underwater world of Aquaria.
|
||||
Along her journey, she learns about the history of the world she inhabits
|
||||
as well as her own past. The gameplay focuses on a combination of swimming,
|
||||
singing, and combat, through which Naija can interact with the world. Her
|
||||
songs can move items, affect plants and animals, and change her physical
|
||||
appearance into other forms that have different abilities, like firing
|
||||
projectiles at hostile creatures, or passing through barriers inaccessible
|
||||
to her in her natural form.
|
||||
From: https://en.wikipedia.org/wiki/Aquaria_(video_game)
|
||||
"""
|
||||
|
||||
game: str = "Aquaria"
|
||||
"The name of the game"
|
||||
|
||||
topology_present = True
|
||||
"show path to required location checks in spoiler"
|
||||
|
||||
web: WebWorld = AquariaWeb()
|
||||
"The web page generation informations"
|
||||
|
||||
item_name_to_id: ClassVar[Dict[str, int]] =\
|
||||
{name: data.id for name, data in item_table.items()}
|
||||
"The name and associated ID of each item of the world"
|
||||
|
||||
item_name_groups = {
|
||||
"Damage": {"Energy form", "Nature form", "Beast form",
|
||||
"Li and Li song", "Baby nautilus", "Baby piranha",
|
||||
"Baby blaster"},
|
||||
"Light": {"Sun form", "Baby dumbo"}
|
||||
}
|
||||
"""Grouping item make it easier to find them"""
|
||||
|
||||
location_name_to_id = location_table
|
||||
"The name and associated ID of each location of the world"
|
||||
|
||||
base_id = 698000
|
||||
"The starting ID of the items and locations of the world"
|
||||
|
||||
ingredients_substitution: List[int]
|
||||
"Used to randomize ingredient drop"
|
||||
|
||||
options_dataclass = AquariaOptions
|
||||
"Used to manage world options"
|
||||
|
||||
options: AquariaOptions
|
||||
"Every options of the world"
|
||||
|
||||
regions: AquariaRegions
|
||||
"Used to manage Regions"
|
||||
|
||||
exclude: List[str]
|
||||
|
||||
def __init__(self, multiworld: MultiWorld, player: int):
|
||||
"""Initialisation of the Aquaria World"""
|
||||
super(AquariaWorld, self).__init__(multiworld, player)
|
||||
self.regions = AquariaRegions(multiworld, player)
|
||||
self.ingredients_substitution = []
|
||||
self.exclude = []
|
||||
|
||||
def create_regions(self) -> None:
|
||||
"""
|
||||
Create every Region in `regions`
|
||||
"""
|
||||
self.regions.add_regions_to_world()
|
||||
self.regions.connect_regions()
|
||||
self.regions.add_event_locations()
|
||||
|
||||
def create_item(self, name: str) -> AquariaItem:
|
||||
"""
|
||||
Create an AquariaItem using `name' as item name.
|
||||
"""
|
||||
result: AquariaItem
|
||||
try:
|
||||
data = item_table[name]
|
||||
classification: ItemClassification = ItemClassification.useful
|
||||
if data.type == ItemType.JUNK:
|
||||
classification = ItemClassification.filler
|
||||
elif data.type == ItemType.PROGRESSION:
|
||||
classification = ItemClassification.progression
|
||||
result = AquariaItem(name, classification, data.id, self.player)
|
||||
except BaseException:
|
||||
raise Exception('The item ' + name + ' is not valid.')
|
||||
|
||||
return result
|
||||
|
||||
def __pre_fill_item(self, item_name: str, location_name: str, precollected) -> None:
|
||||
"""Pre-assign an item to a location"""
|
||||
if item_name not in precollected:
|
||||
self.exclude.append(item_name)
|
||||
data = item_table[item_name]
|
||||
item = AquariaItem(item_name, ItemClassification.useful, data.id, self.player)
|
||||
self.multiworld.get_location(location_name, self.player).place_locked_item(item)
|
||||
|
||||
def get_filler_item_name(self):
|
||||
"""Getting a random ingredient item as filler"""
|
||||
ingredients = []
|
||||
for name, data in item_table.items():
|
||||
if data.group == ItemGroup.INGREDIENT:
|
||||
ingredients.append(name)
|
||||
filler_item_name = self.random.choice(ingredients)
|
||||
return filler_item_name
|
||||
|
||||
def create_items(self) -> None:
|
||||
"""Create every item in the world"""
|
||||
precollected = [item.name for item in self.multiworld.precollected_items[self.player]]
|
||||
if self.options.turtle_randomizer.value > 0:
|
||||
if self.options.turtle_randomizer.value == 2:
|
||||
self.__pre_fill_item("Transturtle Final Boss", "Final boss area, Transturtle", precollected)
|
||||
else:
|
||||
self.__pre_fill_item("Transturtle Veil top left", "The veil top left area, Transturtle", precollected)
|
||||
self.__pre_fill_item("Transturtle Veil top right", "The veil top right area, Transturtle", precollected)
|
||||
self.__pre_fill_item("Transturtle Open Water top right", "Open water top right area, Transturtle",
|
||||
precollected)
|
||||
self.__pre_fill_item("Transturtle Forest bottom left", "Kelp Forest bottom left area, Transturtle",
|
||||
precollected)
|
||||
self.__pre_fill_item("Transturtle Home water", "Home water, Transturtle", precollected)
|
||||
self.__pre_fill_item("Transturtle Abyss right", "Abyss right area, Transturtle", precollected)
|
||||
self.__pre_fill_item("Transturtle Final Boss", "Final boss area, Transturtle", precollected)
|
||||
# The last two are inverted because in the original game, they are special turtle that communicate directly
|
||||
self.__pre_fill_item("Transturtle Simon says", "Arnassi Ruins, Transturtle", precollected)
|
||||
self.__pre_fill_item("Transturtle Arnassi ruins", "Simon says area, Transturtle", precollected)
|
||||
for name, data in item_table.items():
|
||||
if name in precollected:
|
||||
precollected.remove(name)
|
||||
self.multiworld.itempool.append(self.create_item(self.get_filler_item_name()))
|
||||
else:
|
||||
if name not in self.exclude:
|
||||
for i in range(data.count):
|
||||
item = self.create_item(name)
|
||||
self.multiworld.itempool.append(item)
|
||||
|
||||
def set_rules(self) -> None:
|
||||
"""
|
||||
Launched when the Multiworld generator is ready to generate rules
|
||||
"""
|
||||
|
||||
self.regions.adjusting_rules(self.options)
|
||||
self.multiworld.completion_condition[self.player] = lambda \
|
||||
state: state.has("Victory", self.player)
|
||||
|
||||
def generate_basic(self) -> None:
|
||||
"""
|
||||
Player-specific randomization that does not affect logic.
|
||||
Used to fill then `ingredients_substitution` list
|
||||
"""
|
||||
simple_ingredients_substitution = [i for i in range(27)]
|
||||
if self.options.ingredient_randomizer.value > 0:
|
||||
if self.options.ingredient_randomizer.value == 1:
|
||||
simple_ingredients_substitution.pop(-1)
|
||||
simple_ingredients_substitution.pop(-1)
|
||||
simple_ingredients_substitution.pop(-1)
|
||||
self.random.shuffle(simple_ingredients_substitution)
|
||||
if self.options.ingredient_randomizer.value == 1:
|
||||
simple_ingredients_substitution.extend([24, 25, 26])
|
||||
dishes_substitution = [i for i in range(27, 76)]
|
||||
if self.options.dish_randomizer:
|
||||
self.random.shuffle(dishes_substitution)
|
||||
self.ingredients_substitution.clear()
|
||||
self.ingredients_substitution.extend(simple_ingredients_substitution)
|
||||
self.ingredients_substitution.extend(dishes_substitution)
|
||||
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
return {"ingredientReplacement": self.ingredients_substitution,
|
||||
"aquarianTranslate": bool(self.options.aquarian_translation.value),
|
||||
"secret_needed": self.options.objective.value > 0,
|
||||
"minibosses_to_kill": self.options.mini_bosses_to_beat.value,
|
||||
"bigbosses_to_kill": self.options.big_bosses_to_beat.value,
|
||||
"skip_first_vision": bool(self.options.skip_first_vision.value),
|
||||
"unconfine_home_water_energy_door": self.options.unconfine_home_water.value in [1, 3],
|
||||
"unconfine_home_water_transturtle": self.options.unconfine_home_water.value in [2, 3],
|
||||
}
|
||||
64
worlds/aquaria/docs/en_Aquaria.md
Normal file
64
worlds/aquaria/docs/en_Aquaria.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Aquaria
|
||||
|
||||
## Game page in other languages:
|
||||
* [Français](/games/Aquaria/info/fr)
|
||||
|
||||
## Where is the options page?
|
||||
|
||||
The player options page for this game contains all the options you need to configure and export a config file. Player
|
||||
options page link: [Aquaria Player Options Page](../player-options).
|
||||
|
||||
## What does randomization do to this game?
|
||||
The locations in the randomizer are:
|
||||
|
||||
- All sing bulbs;
|
||||
- All Mithalas Urns;
|
||||
- All Sunken City crates;
|
||||
- Collectible treasure locations (including pet eggs and costumes);
|
||||
- Beating Simon says;
|
||||
- Li cave;
|
||||
- Every Transportation Turtle (also called transturtle);
|
||||
- Locations where you get songs,
|
||||
* Erulian spirit cristal,
|
||||
* Energy status mini-boss,
|
||||
* Beating Mithalan God boss,
|
||||
* Fish cave puzzle,
|
||||
* Beating Drunian God boss,
|
||||
* Beating Sun God boss,
|
||||
* Breaking Li cage in the body
|
||||
|
||||
Note that, unlike the vanilla game, when opening sing bulbs, Mithalas urns and Sunken City crates,
|
||||
nothing will come out of them. The moment those bulbs, urns and crates are opened, the location is considered received.
|
||||
|
||||
The items in the randomizer are:
|
||||
- Dishes (used to learn recipes*);
|
||||
- Some ingredients;
|
||||
- The Wok (third plate used to cook 3 ingredients recipes everywhere);
|
||||
- All collectible treasure (including pet eggs and costumes);
|
||||
- Li and Li song;
|
||||
- All songs (other than Li's song since it is learned when Li is obtained);
|
||||
- Transportation to transturtles.
|
||||
|
||||
Also, there is the option to randomize every ingredient drops (from fishes, monsters
|
||||
or plants).
|
||||
|
||||
*Note that, unlike in the vanilla game, the recipes for dishes (other than the Sea Loaf)
|
||||
cannot be cooked (and learn) before being obtained as randomized items. Also, enemies and plants
|
||||
that drop dishes that have not been learned before will drop ingredients of this dish instead.
|
||||
|
||||
## What is the goal of the game?
|
||||
The goal of the Aquaria game is to beat the creator. You can also add other goals like getting
|
||||
secret memories, beating a number of mini-bosses and beating a number of bosses.
|
||||
|
||||
## Which items can be in another player's world?
|
||||
Any items specified above can be in another player's world.
|
||||
|
||||
## What does another world's item look like in Aquaria?
|
||||
No visuals are shown when finding locations other than collectible treasure.
|
||||
For those treasures, the visual of the treasure is visually unchanged.
|
||||
After collecting a location check, a message will be shown to inform the player
|
||||
what has been 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, and which one it is.
|
||||
65
worlds/aquaria/docs/fr_Aquaria.md
Normal file
65
worlds/aquaria/docs/fr_Aquaria.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Aquaria
|
||||
|
||||
## Où se trouve la page des options ?
|
||||
|
||||
La [page des options du joueur pour ce jeu](../player-options) contient tous
|
||||
les options dont vous avez besoin pour configurer et exporter le fichier.
|
||||
|
||||
## Quel est l'effet de la randomisation sur ce jeu ?
|
||||
|
||||
Les localisations du "Ransomizer" sont:
|
||||
|
||||
- tous les bulbes musicaux;
|
||||
- toutes les urnes de Mithalas;
|
||||
- toutes les caisses de la cité engloutie;
|
||||
- les localisations des trésors de collections (incluant les oeufs d'animaux de compagnie et les costumes);
|
||||
- Battre Simom dit;
|
||||
- La caverne de Li;
|
||||
- Les tortues de transportation (transturtle);
|
||||
- Localisation ou on obtient normalement les musiques,
|
||||
* cristal de l'esprit Erulien,
|
||||
* le mini-boss de la statue de l'énergie,
|
||||
* battre le dieu de Mithalas,
|
||||
* résoudre l'énigme de la caverne des poissons,
|
||||
* battre le dieu Drunien,
|
||||
* battre le dieu du soleil,
|
||||
* détruire la cage de Li dans le corps,
|
||||
|
||||
À noter que, contrairement au jeu original, lors de l'ouverture d'un bulbe musical, d'une urne de Mithalas ou
|
||||
d'une caisse de la cité engloutie, aucun objet n'en sortira. La localisation représentée par l'objet ouvert est reçue
|
||||
dès l'ouverture.
|
||||
|
||||
Les objets pouvant être obtenus sont:
|
||||
- les recettes (permettant d'apprendre les recettes*);
|
||||
- certains ingrédients;
|
||||
- le Wok (la troisième assiette permettant de cuisiner avec trois ingrédients n'importe où);
|
||||
- Tous les trésors de collection (incluant les oeufs d'animal de compagnie et les costumes);
|
||||
- Li et la musique de Li;
|
||||
- Toutes les musiques (autre que la musique de Li puisque cette dernière est apprise en obtenant Li);
|
||||
- Les localisations de transportation.
|
||||
|
||||
Il y a également l'option pour mélanger les ingrédients obtenus en éliminant des monstres, des poissons ou des plantes.
|
||||
|
||||
*À noter que, contrairement au jeu original, il est impossible de cuisiner une recette qui n'a pas préalablement
|
||||
été apprise en obtenant un repas en tant qu'objet. À noter également que les ennemies et plantes qui
|
||||
donnent un repas dont la recette n'a pas préalablement été apprise vont donner les ingrédients de cette
|
||||
recette.
|
||||
|
||||
## Quel est le but de Aquaria ?
|
||||
|
||||
Dans Aquaria, le but est de battre le monstre final (le créateur). Il est également possible d'ajouter
|
||||
des buts comme obtenir les trois souvenirs secrets, ou devoir battre une quantité de boss ou de mini-boss.
|
||||
|
||||
## Quels objets peuvent se trouver dans le monde d'un autre joueur ?
|
||||
|
||||
Tous les objets indiqués plus haut peuvent être obtenus à partir du monde d'un autre joueur.
|
||||
|
||||
## À quoi ressemble un objet d'un autre monde dans ce jeu
|
||||
|
||||
Autre que pour les trésors de collection (dont le visuel demeure inchangé),
|
||||
les autres localisations n'ont aucun visuel. Lorsqu'une localisation randomisée est obtenue,
|
||||
un message est affiché à l'écran pour indiquer quel objet a été trouvé et pour quel joueur.
|
||||
|
||||
## Que se passe-t-il lorsque le joueur reçoit un objet ?
|
||||
|
||||
Chaque fois qu'un objet est reçu, un message apparaît à l'écran pour en informer le joueur.
|
||||
114
worlds/aquaria/docs/setup_en.md
Normal file
114
worlds/aquaria/docs/setup_en.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# Aquaria Randomizer Setup Guide
|
||||
|
||||
## Required Software
|
||||
|
||||
- The original Aquaria Game (buyable from a lot of online game seller);
|
||||
- The [Aquaria randomizer](https://github.com/tioui/Aquaria_Randomizer/releases)
|
||||
- Optional, for sending [commands](/tutorial/Archipelago/commands/en) like `!hint`: the TextClient from [the most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases)
|
||||
|
||||
## Installation and execution Procedures
|
||||
|
||||
### Windows
|
||||
|
||||
First, you should copy the original Aquaria folder game. The randomizer will possibly modify the game so that
|
||||
the original game will stop working. Copying the folder will guarantee that the original game keeps on working.
|
||||
Also, in Windows, the save files are stored in the Aquaria folder. So copying the Aquaria folder for every Multiworld
|
||||
game you play will make sure that every game has their own save game.
|
||||
|
||||
Unzip the Aquaria randomizer release and copy all unzipped files in the Aquaria game folder. The unzipped files
|
||||
are those:
|
||||
- aquaria_randomizer.exe
|
||||
- OpenAL32.dll
|
||||
- override (directory)
|
||||
- SDL2.dll
|
||||
- usersettings.xml
|
||||
- wrap_oal.dll
|
||||
- cacert.pem
|
||||
|
||||
If there is a conflict between file in the original game folder and the unzipped files, you should override
|
||||
the original files with the one of the unzipped randomizer.
|
||||
|
||||
Finally, to launch the randomizer, you must use the command line interface (you can open the command line interface
|
||||
by writing `cmd` in the address bar of the Windows file explorer). Here is the command line to use to start the
|
||||
randomizer:
|
||||
|
||||
```bash
|
||||
aquaria_randomizer.exe --name YourName --server theServer:thePort
|
||||
```
|
||||
|
||||
or, if the room has a password:
|
||||
|
||||
```bash
|
||||
aquaria_randomizer.exe --name YourName --server theServer:thePort --password thePassword
|
||||
```
|
||||
|
||||
### Linux when using the AppImage
|
||||
|
||||
If you use the AppImage, just copy it in the Aquaria game folder. You then have to make it executable. You
|
||||
can do that from command line by using
|
||||
|
||||
```bash
|
||||
chmod +x Aquaria_Randomizer-*.AppImage
|
||||
```
|
||||
|
||||
or by using the Graphical Explorer of your system.
|
||||
|
||||
To launch the randomizer, just launch in command line:
|
||||
|
||||
```bash
|
||||
./Aquaria_Randomizer-*.AppImage --name YourName --server theServer:thePort
|
||||
```
|
||||
|
||||
or, if the room has a password:
|
||||
|
||||
```bash
|
||||
./Aquaria_Randomizer-*.AppImage --name YourName --server theServer:thePort --password thePassword
|
||||
```
|
||||
|
||||
Note that you should not have multiple Aquaria_Randomizer AppImage file in the same folder. If this situation occurred,
|
||||
the preceding commands will launch the game multiple times.
|
||||
|
||||
### Linux when using the tar file
|
||||
|
||||
First, you should copy the original Aquaria folder game. The randomizer will possibly modify the game so that
|
||||
the original game will stop working. Copying the folder will guarantee that the original game keeps on working.
|
||||
|
||||
Untar the Aquaria randomizer release and copy all extracted files in the Aquaria game folder. The extracted
|
||||
files are those:
|
||||
- aquaria_randomizer
|
||||
- override (directory)
|
||||
- usersettings.xml
|
||||
- cacert.pem
|
||||
|
||||
If there is a conflict between file in the original game folder and the extracted files, you should override
|
||||
the original files with the one of the extracted randomizer files.
|
||||
|
||||
Then, you should use your system package manager to install liblua5, libogg, libvorbis, libopenal and libsdl2.
|
||||
On Debian base system (like Ubuntu), you can use the following command:
|
||||
|
||||
```bash
|
||||
sudo apt install liblua5.1-0-dev libogg-dev libvorbis-dev libopenal-dev libsdl2-dev
|
||||
```
|
||||
|
||||
Also, if there is some `.so` files in the Aquaria original game folder (`libgcc_s.so.1`, `libopenal.so.1`,
|
||||
`libSDL-1.2.so.0` and `libstdc++.so.6`), you should remove them from the Aquaria Randomizer game folder. Those are
|
||||
old libraries that will not work on the recent build of the randomizer.
|
||||
|
||||
To launch the randomizer, just launch in command line:
|
||||
|
||||
```bash
|
||||
./aquaria_randomizer --name YourName --server theServer:thePort
|
||||
```
|
||||
|
||||
or, if the room has a password:
|
||||
|
||||
```bash
|
||||
./aquaria_randomizer --name YourName --server theServer:thePort --password thePassword
|
||||
```
|
||||
|
||||
Note: If you have a permission denied error when using the command line, you can use this command line to be
|
||||
sure that your executable has executable permission:
|
||||
|
||||
```bash
|
||||
chmod +x aquaria_randomizer
|
||||
```
|
||||
118
worlds/aquaria/docs/setup_fr.md
Normal file
118
worlds/aquaria/docs/setup_fr.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# Guide de configuration MultiWorld d'Aquaria
|
||||
|
||||
## Logiciels nécessaires
|
||||
|
||||
- Le jeu Aquaria original (trouvable sur la majorité des sites de ventes de jeux vidéo en ligne)
|
||||
- Le client Randomizer d'Aquaria [Aquaria randomizer](https://github.com/tioui/Aquaria_Randomizer/releases)
|
||||
- De manière optionnel, pour pouvoir envoyer des [commandes](/tutorial/Archipelago/commands/en) comme `!hint`: utilisez le client texte de [la version la plus récente d'Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases)
|
||||
|
||||
## Procédures d'installation et d'exécution
|
||||
|
||||
### Windows
|
||||
|
||||
En premier lieu, vous devriez effectuer une nouvelle copie du jeu d'Aquaria original à chaque fois que vous effectuez une
|
||||
nouvelle partie. La première raison de cette copie est que le randomizer modifie des fichiers qui rendront possiblement
|
||||
le jeu original non fonctionnel. La seconde raison d'effectuer cette copie est que les sauvegardes sont créées
|
||||
directement dans le répertoire du jeu. Donc, la copie permet d'éviter de perdre vos sauvegardes du jeu d'origine ou
|
||||
encore de charger une sauvegarde d'une ancienne partie de multiworld (ce qui pourrait avoir comme conséquence de briser
|
||||
la logique du multiworld).
|
||||
|
||||
Désarchiver le randomizer d'Aquaria et copier tous les fichiers de l'archive dans le répertoire du jeu d'Aquaria. Le
|
||||
fichier d'archive devrait contenir les fichiers suivants:
|
||||
- aquaria_randomizer.exe
|
||||
- OpenAL32.dll
|
||||
- override (directory)
|
||||
- SDL2.dll
|
||||
- usersettings.xml
|
||||
- wrap_oal.dll
|
||||
- cacert.pem
|
||||
|
||||
S'il y a des conflits entre les fichiers de l'archive zip et les fichiers du jeu original, vous devez utiliser
|
||||
les fichiers contenus dans l'archive zip.
|
||||
|
||||
Finalement, pour lancer le randomizer, vous devez utiliser la ligne de commande (vous pouvez ouvrir une interface de
|
||||
ligne de commande, entrez l'adresse `cmd` dans la barre d'adresse de l'explorateur de fichier de Windows). Voici
|
||||
la ligne de commande à utiliser pour lancer le randomizer:
|
||||
|
||||
```bash
|
||||
aquaria_randomizer.exe --name VotreNom --server leServeur:LePort
|
||||
```
|
||||
|
||||
ou, si vous devez entrer un mot de passe:
|
||||
|
||||
```bash
|
||||
aquaria_randomizer.exe --name VotreNom --server leServeur:LePort --password leMotDePasse
|
||||
```
|
||||
|
||||
### Linux avec le fichier AppImage
|
||||
|
||||
Si vous utilisez le fichier AppImage, copiez le fichier dans le répertoire du jeu d'Aquaria. Ensuite, assurez-vous de
|
||||
le mettre exécutable. Vous pouvez mettre le fichier exécutable avec la commande suivante:
|
||||
|
||||
```bash
|
||||
chmod +x Aquaria_Randomizer-*.AppImage
|
||||
```
|
||||
|
||||
ou bien en utilisant l'explorateur graphique de votre système.
|
||||
|
||||
Pour lancer le randomizer, utiliser la commande suivante:
|
||||
|
||||
```bash
|
||||
./Aquaria_Randomizer-*.AppImage --name VotreNom --server LeServeur:LePort
|
||||
```
|
||||
|
||||
Si vous devez entrer un mot de passe:
|
||||
|
||||
```bash
|
||||
./Aquaria_Randomizer-*.AppImage --name VotreNom --server LeServeur:LePort --password LeMotDePasse
|
||||
```
|
||||
|
||||
À noter que vous ne devez pas avoir plusieurs fichiers AppImage différents dans le même répertoire. Si cette situation
|
||||
survient, le jeu sera lancé plusieurs fois.
|
||||
|
||||
### Linux avec le fichier tar
|
||||
|
||||
En premier lieu, assurez-vous de faire une copie du répertoire du jeu d'origine d'Aquaria. Les fichiers contenus
|
||||
dans le randomizer auront comme impact de rendre le jeu d'origine non fonctionnel. Donc, effectuer la copie du jeu
|
||||
avant de déposer le randomizer à l'intérieur permet de vous assurer de garder une version du jeu d'origine fonctionnel.
|
||||
|
||||
Désarchiver le fichier tar et copier tous les fichiers qu'il contient dans le répertoire du jeu d'origine d'Aquaria. Les
|
||||
fichiers extraient du fichier tar devraient être les suivants:
|
||||
- aquaria_randomizer
|
||||
- override (directory)
|
||||
- usersettings.xml
|
||||
- cacert.pem
|
||||
|
||||
S'il y a des conflits entre les fichiers de l'archive tar et les fichiers du jeu original, vous devez utiliser
|
||||
les fichiers contenus dans l'archive tar.
|
||||
|
||||
Ensuite, vous devez installer manuellement les librairies dont dépend le jeu: liblua5, libogg, libvorbis, libopenal and
|
||||
libsdl2. Vous pouvez utiliser le système de "package" de votre système pour les installer. Voici un exemple avec
|
||||
Debian (et Ubuntu):
|
||||
|
||||
```bash
|
||||
sudo apt install liblua5.1-0-dev libogg-dev libvorbis-dev libopenal-dev libsdl2-dev
|
||||
```
|
||||
|
||||
Notez également que s'il y a des fichiers ".so" dans le répertoire d'Aquaria (`libgcc_s.so.1`, `libopenal.so.1`,
|
||||
`libSDL-1.2.so.0` and `libstdc++.so.6`), vous devriez les retirer. Il s'agit de vieille version des librairies qui
|
||||
ne sont plus fonctionnelles dans les systèmes modernes et qui pourrait empêcher le randomizer de fonctionner.
|
||||
|
||||
Pour lancer le randomizer, utiliser la commande suivante:
|
||||
|
||||
```bash
|
||||
./aquaria_randomizer --name VotreNom --server LeServeur:LePort
|
||||
```
|
||||
|
||||
Si vous devez entrer un mot de passe:
|
||||
|
||||
```bash
|
||||
./aquaria_randomizer --name VotreNom --server LeServeur:LePort --password LeMotDePasse
|
||||
```
|
||||
|
||||
Note: Si vous avez une erreur de permission lors de l'exécution du randomizer, vous pouvez utiliser cette commande
|
||||
pour vous assurer que votre fichier est exécutable:
|
||||
|
||||
```bash
|
||||
chmod +x aquaria_randomizer
|
||||
```
|
||||
218
worlds/aquaria/test/__init__.py
Normal file
218
worlds/aquaria/test/__init__.py
Normal file
@@ -0,0 +1,218 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Base class for the Aquaria randomizer unit tests
|
||||
"""
|
||||
|
||||
|
||||
from test.bases import WorldTestBase
|
||||
|
||||
# Every location accessible after the home water.
|
||||
after_home_water_locations = [
|
||||
"Sun Crystal",
|
||||
"Home water, Transturtle",
|
||||
"Open water top left area, bulb under the rock in the right path",
|
||||
"Open water top left area, bulb under the rock in the left path",
|
||||
"Open water top left area, bulb to the right of the save cristal",
|
||||
"Open water top right area, bulb in the small path before Mithalas",
|
||||
"Open water top right area, bulb in the path from the left entrance",
|
||||
"Open water top right area, bulb in the clearing close to the bottom exit",
|
||||
"Open water top right area, bulb in the big clearing close to the save cristal",
|
||||
"Open water top right area, bulb in the big clearing to the top exit",
|
||||
"Open water top right area, first urn in the Mithalas exit",
|
||||
"Open water top right area, second urn in the Mithalas exit",
|
||||
"Open water top right area, third urn in the Mithalas exit",
|
||||
"Open water top right area, bulb in the turtle room",
|
||||
"Open water top right area, Transturtle",
|
||||
"Open water bottom left area, bulb behind the chomper fish",
|
||||
"Open water bottom left area, bulb inside the downest fish pass",
|
||||
"Open water skeleton path, bulb close to the right exit",
|
||||
"Open water skeleton path, bulb behind the chomper fish",
|
||||
"Open water skeleton path, King skull",
|
||||
"Arnassi Ruins, bulb in the right part",
|
||||
"Arnassi Ruins, bulb in the left part",
|
||||
"Arnassi Ruins, bulb in the center part",
|
||||
"Arnassi ruins, Song plant spore on the top of the ruins",
|
||||
"Arnassi ruins, Arnassi Armor",
|
||||
"Arnassi Ruins, Arnassi statue",
|
||||
"Arnassi Ruins, Transturtle",
|
||||
"Arnassi ruins, Crab armor",
|
||||
"Simon says area, Transturtle",
|
||||
"Mithalas city, first bulb in the left city part",
|
||||
"Mithalas city, second bulb in the left city part",
|
||||
"Mithalas city, bulb in the right part",
|
||||
"Mithalas city, bulb at the top of the city",
|
||||
"Mithalas city, first bulb in a broken home",
|
||||
"Mithalas city, second bulb in a broken home",
|
||||
"Mithalas city, bulb in the bottom left part",
|
||||
"Mithalas city, first bulb in one of the homes",
|
||||
"Mithalas city, second bulb in one of the homes",
|
||||
"Mithalas city, first urn in one of the homes",
|
||||
"Mithalas city, second urn in one of the homes",
|
||||
"Mithalas city, first urn in the city reserve",
|
||||
"Mithalas city, second urn in the city reserve",
|
||||
"Mithalas city, third urn in the city reserve",
|
||||
"Mithalas city, first bulb at the end of the top path",
|
||||
"Mithalas city, second bulb at the end of the top path",
|
||||
"Mithalas city, bulb in the top path",
|
||||
"Mithalas city, Mithalas pot",
|
||||
"Mithalas city, urn in the cathedral flower tube entrance",
|
||||
"Mithalas city, Doll",
|
||||
"Mithalas city, urn inside a home fish pass",
|
||||
"Mithalas city castle, bulb in the flesh hole",
|
||||
"Mithalas city castle, Blue banner",
|
||||
"Mithalas city castle, urn in the bedroom",
|
||||
"Mithalas city castle, first urn of the single lamp path",
|
||||
"Mithalas city castle, second urn of the single lamp path",
|
||||
"Mithalas city castle, urn in the bottom room",
|
||||
"Mithalas city castle, first urn on the entrance path",
|
||||
"Mithalas city castle, second urn on the entrance path",
|
||||
"Mithalas castle, beating the priests",
|
||||
"Mithalas city castle, Trident head",
|
||||
"Mithalas cathedral, first urn in the top right room",
|
||||
"Mithalas cathedral, second urn in the top right room",
|
||||
"Mithalas cathedral, third urn in the top right room",
|
||||
"Mithalas cathedral, urn in the flesh room with fleas",
|
||||
"Mithalas cathedral, first urn in the bottom right path",
|
||||
"Mithalas cathedral, second urn in the bottom right path",
|
||||
"Mithalas cathedral, urn behind the flesh vein",
|
||||
"Mithalas cathedral, urn in the top left eyes boss room",
|
||||
"Mithalas cathedral, first urn in the path behind the flesh vein",
|
||||
"Mithalas cathedral, second urn in the path behind the flesh vein",
|
||||
"Mithalas cathedral, third urn in the path behind the flesh vein",
|
||||
"Mithalas cathedral, one of the urns in the top right room",
|
||||
"Mithalas cathedral, Mithalan Dress",
|
||||
"Mithalas cathedral right area, urn bellow the left entrance",
|
||||
"Cathedral underground, bulb in the center part",
|
||||
"Cathedral underground, first bulb in the top left part",
|
||||
"Cathedral underground, second bulb in the top left part",
|
||||
"Cathedral underground, third bulb in the top left part",
|
||||
"Cathedral underground, bulb close to the save cristal",
|
||||
"Cathedral underground, bulb in the bottom right path",
|
||||
"Cathedral boss area, beating Mithalan God",
|
||||
"Kelp Forest top left area, bulb in the bottom left clearing",
|
||||
"Kelp Forest top left area, bulb in the path down from the top left clearing",
|
||||
"Kelp Forest top left area, bulb in the top left clearing",
|
||||
"Kelp Forest top left, Jelly Egg",
|
||||
"Kelp Forest top left area, bulb close to the Verse egg",
|
||||
"Kelp forest top left area, Verse egg",
|
||||
"Kelp Forest top right area, bulb under the rock in the right path",
|
||||
"Kelp Forest top right area, bulb at the left of the center clearing",
|
||||
"Kelp Forest top right area, bulb in the left path's big room",
|
||||
"Kelp Forest top right area, bulb in the left path's small room",
|
||||
"Kelp Forest top right area, bulb at the top of the center clearing",
|
||||
"Kelp forest top right area, Black pearl",
|
||||
"Kelp Forest top right area, bulb in the top fish pass",
|
||||
"Kelp Forest bottom left area, bulb close to the spirit crystals",
|
||||
"Kelp forest bottom left area, Walker baby",
|
||||
"Kelp Forest bottom left area, Transturtle",
|
||||
"Kelp forest bottom right area, Odd Container",
|
||||
"Kelp forest boss area, beating Drunian God",
|
||||
"Kelp Forest boss room, bulb at the bottom of the area",
|
||||
"Kelp Forest bottom left area, Fish cave puzzle",
|
||||
"Kelp Forest sprite cave, bulb inside the fish pass",
|
||||
"Kelp Forest sprite cave, bulb in the second room",
|
||||
"Kelp Forest Sprite Cave, Seed bag",
|
||||
"Mermog cave, bulb in the left part of the cave",
|
||||
"Mermog cave, Piranha Egg",
|
||||
"The veil top left area, In the Li cave",
|
||||
"The veil top left area, bulb under the rock in the top right path",
|
||||
"The veil top left area, bulb hidden behind the blocking rock",
|
||||
"The veil top left area, Transturtle",
|
||||
"The veil top left area, bulb inside the fish pass",
|
||||
"Turtle cave, Turtle Egg",
|
||||
"Turtle cave, bulb in bubble cliff",
|
||||
"Turtle cave, Urchin costume",
|
||||
"The veil top right area, bulb in the middle of the wall jump cliff",
|
||||
"The veil top right area, golden starfish at the bottom right of the bottom path",
|
||||
"The veil top right area, bulb in the top of the water fall",
|
||||
"The veil top right area, Transturtle",
|
||||
"The veil bottom area, bulb in the left path",
|
||||
"The veil bottom area, bulb in the spirit path",
|
||||
"The veil bottom area, Verse egg",
|
||||
"The veil bottom area, Stone Head",
|
||||
"Octopus cave, Dumbo Egg",
|
||||
"Octopus cave, bulb in the path below the octopus cave path",
|
||||
"Bubble cave, bulb in the left cave wall",
|
||||
"Bubble cave, bulb in the right cave wall (behind the ice cristal)",
|
||||
"Bubble cave, Verse egg",
|
||||
"Sun temple, bulb in the top left part",
|
||||
"Sun temple, bulb in the top right part",
|
||||
"Sun temple, bulb at the top of the high dark room",
|
||||
"Sun temple, Golden Gear",
|
||||
"Sun temple, first bulb of the temple",
|
||||
"Sun temple, bulb on the left part",
|
||||
"Sun temple, bulb in the hidden room of the right part",
|
||||
"Sun temple, Sun key",
|
||||
"Sun Worm path, first path bulb",
|
||||
"Sun Worm path, second path bulb",
|
||||
"Sun Worm path, first cliff bulb",
|
||||
"Sun Worm path, second cliff bulb",
|
||||
"Sun temple boss area, beating Sun God",
|
||||
"Abyss left area, bulb in hidden path room",
|
||||
"Abyss left area, bulb in the right part",
|
||||
"Abyss left area, Glowing seed",
|
||||
"Abyss left area, Glowing Plant",
|
||||
"Abyss left area, bulb in the bottom fish pass",
|
||||
"Abyss right area, bulb behind the rock in the whale room",
|
||||
"Abyss right area, bulb in the middle path",
|
||||
"Abyss right area, bulb behind the rock in the middle path",
|
||||
"Abyss right area, bulb in the left green room",
|
||||
"Abyss right area, Transturtle",
|
||||
"Ice cave, bulb in the room to the right",
|
||||
"Ice cave, First bulbs in the top exit room",
|
||||
"Ice cave, Second bulbs in the top exit room",
|
||||
"Ice cave, third bulbs in the top exit room",
|
||||
"Ice cave, bulb in the left room",
|
||||
"King Jellyfish cave, bulb in the right path from King Jelly",
|
||||
"King Jellyfish cave, Jellyfish Costume",
|
||||
"The whale, Verse egg",
|
||||
"Sunken city right area, crate close to the save cristal",
|
||||
"Sunken city right area, crate in the left bottom room",
|
||||
"Sunken city left area, crate in the little pipe room",
|
||||
"Sunken city left area, crate close to the save cristal",
|
||||
"Sunken city left area, crate before the bedroom",
|
||||
"Sunken city left area, Girl Costume",
|
||||
"Sunken city, bulb on the top of the boss area (boiler room)",
|
||||
"The body center area, breaking li cage",
|
||||
"The body main area, bulb on the main path blocking tube",
|
||||
"The body left area, first bulb in the top face room",
|
||||
"The body left area, second bulb in the top face room",
|
||||
"The body left area, bulb bellow the water stream",
|
||||
"The body left area, bulb in the top path to the top face room",
|
||||
"The body left area, bulb in the bottom face room",
|
||||
"The body right area, bulb in the top face room",
|
||||
"The body right area, bulb in the top path to the bottom face room",
|
||||
"The body right area, bulb in the bottom face room",
|
||||
"The body bottom area, bulb in the Jelly Zap room",
|
||||
"The body bottom area, bulb in the nautilus room",
|
||||
"The body bottom area, Mutant Costume",
|
||||
"Final boss area, first bulb in the turtle room",
|
||||
"Final boss area, second bulbs in the turtle room",
|
||||
"Final boss area, third bulbs in the turtle room",
|
||||
"Final boss area, Transturtle",
|
||||
"Final boss area, bulb in the boss third form room",
|
||||
"Kelp forest, beating Simon says",
|
||||
"Beating Fallen God",
|
||||
"Beating Mithalan God",
|
||||
"Beating Drunian God",
|
||||
"Beating Sun God",
|
||||
"Beating the Golem",
|
||||
"Beating Nautilus Prime",
|
||||
"Beating Blaster Peg Prime",
|
||||
"Beating Mergog",
|
||||
"Beating Mithalan priests",
|
||||
"Beating Octopus Prime",
|
||||
"Beating Crabbius Maximus",
|
||||
"Beating Mantis Shrimp Prime",
|
||||
"Beating King Jellyfish God Prime",
|
||||
"First secret",
|
||||
"Second secret",
|
||||
"Third secret",
|
||||
"Sunken City cleared",
|
||||
"Objective complete",
|
||||
]
|
||||
|
||||
class AquariaTestBase(WorldTestBase):
|
||||
"""Base class for Aquaria unit tests"""
|
||||
game = "Aquaria"
|
||||
48
worlds/aquaria/test/test_beast_form_access.py
Normal file
48
worlds/aquaria/test/test_beast_form_access.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without the beast form
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
|
||||
|
||||
class BeastFormAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without the beast form"""
|
||||
|
||||
def test_beast_form_location(self) -> None:
|
||||
"""Test locations that require beast form"""
|
||||
locations = [
|
||||
"Mithalas castle, beating the priests",
|
||||
"Arnassi ruins, Crab armor",
|
||||
"Arnassi ruins, Song plant spore on the top of the ruins",
|
||||
"Mithalas city, first bulb at the end of the top path",
|
||||
"Mithalas city, second bulb at the end of the top path",
|
||||
"Mithalas city, bulb in the top path",
|
||||
"Mithalas city, Mithalas pot",
|
||||
"Mithalas city, urn in the cathedral flower tube entrance",
|
||||
"Mermog cave, Piranha Egg",
|
||||
"Mithalas cathedral, Mithalan Dress",
|
||||
"Turtle cave, bulb in bubble cliff",
|
||||
"Turtle cave, Urchin costume",
|
||||
"Sun Worm path, first cliff bulb",
|
||||
"Sun Worm path, second cliff bulb",
|
||||
"The veil top right area, bulb in the top of the water fall",
|
||||
"Bubble cave, bulb in the left cave wall",
|
||||
"Bubble cave, bulb in the right cave wall (behind the ice cristal)",
|
||||
"Bubble cave, Verse egg",
|
||||
"Sunken city, bulb on the top of the boss area (boiler room)",
|
||||
"Octopus cave, Dumbo Egg",
|
||||
"Beating the Golem",
|
||||
"Beating Mergog",
|
||||
"Beating Crabbius Maximus",
|
||||
"Beating Octopus Prime",
|
||||
"Beating Mantis Shrimp Prime",
|
||||
"King Jellyfish cave, Jellyfish Costume",
|
||||
"King Jellyfish cave, bulb in the right path from King Jelly",
|
||||
"Beating King Jellyfish God Prime",
|
||||
"Beating Mithalan priests",
|
||||
"Sunken City cleared"
|
||||
]
|
||||
items = [["Beast form"]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
36
worlds/aquaria/test/test_bind_song_access.py
Normal file
36
worlds/aquaria/test/test_bind_song_access.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without the bind song (without the location
|
||||
under rock needing bind song option)
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase, after_home_water_locations
|
||||
|
||||
|
||||
class BindSongAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without the bind song"""
|
||||
options = {
|
||||
"bind_song_needed_to_get_under_rock_bulb": False,
|
||||
}
|
||||
|
||||
def test_bind_song_location(self) -> None:
|
||||
"""Test locations that require Bind song"""
|
||||
locations = [
|
||||
"Verse cave right area, Big Seed",
|
||||
"Home water, bulb in the path bellow Nautilus Prime",
|
||||
"Home water, bulb in the bottom left room",
|
||||
"Home water, Nautilus Egg",
|
||||
"Song cave, Verse egg",
|
||||
"Energy temple first area, beating the energy statue",
|
||||
"Energy temple first area, bulb in the bottom room blocked by a rock",
|
||||
"Energy temple first area, Energy Idol",
|
||||
"Energy temple second area, bulb under the rock",
|
||||
"Energy temple bottom entrance, Krotite armor",
|
||||
"Energy temple third area, bulb in the bottom path",
|
||||
"Energy temple boss area, Fallen god tooth",
|
||||
"Energy temple blaster room, Blaster egg",
|
||||
*after_home_water_locations
|
||||
]
|
||||
items = [["Bind song"]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
42
worlds/aquaria/test/test_bind_song_option_access.py
Normal file
42
worlds/aquaria/test/test_bind_song_option_access.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without the bind song (with the location
|
||||
under rock needing bind song option)
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
from worlds.aquaria.test.test_bind_song_access import after_home_water_locations
|
||||
|
||||
|
||||
class BindSongOptionAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without the bind song"""
|
||||
options = {
|
||||
"bind_song_needed_to_get_under_rock_bulb": True,
|
||||
}
|
||||
|
||||
def test_bind_song_location(self) -> None:
|
||||
"""Test locations that require Bind song with the bind song needed option activated"""
|
||||
locations = [
|
||||
"Verse cave right area, Big Seed",
|
||||
"Verse cave left area, bulb under the rock at the end of the path",
|
||||
"Home water, bulb under the rock in the left path from the verse cave",
|
||||
"Song cave, bulb under the rock close to the song door",
|
||||
"Song cave, bulb under the rock in the path to the singing statues",
|
||||
"Naija's home, bulb under the rock at the right of the main path",
|
||||
"Home water, bulb in the path bellow Nautilus Prime",
|
||||
"Home water, bulb in the bottom left room",
|
||||
"Home water, Nautilus Egg",
|
||||
"Song cave, Verse egg",
|
||||
"Energy temple first area, beating the energy statue",
|
||||
"Energy temple first area, bulb in the bottom room blocked by a rock",
|
||||
"Energy temple first area, Energy Idol",
|
||||
"Energy temple second area, bulb under the rock",
|
||||
"Energy temple bottom entrance, Krotite armor",
|
||||
"Energy temple third area, bulb in the bottom path",
|
||||
"Energy temple boss area, Fallen god tooth",
|
||||
"Energy temple blaster room, Blaster egg",
|
||||
*after_home_water_locations
|
||||
]
|
||||
items = [["Bind song"]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
20
worlds/aquaria/test/test_confined_home_water.py
Normal file
20
worlds/aquaria/test/test_confined_home_water.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Fri, 03 May 2024 14:07:35 +0000
|
||||
Description: Unit test used to test accessibility of region with the home water confine via option
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
|
||||
|
||||
class ConfinedHomeWaterAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of region with the unconfine home water option disabled"""
|
||||
options = {
|
||||
"unconfine_home_water": 0,
|
||||
"early_energy_form": False
|
||||
}
|
||||
|
||||
def test_confine_home_water_location(self) -> None:
|
||||
"""Test region accessible with confined home water"""
|
||||
self.assertFalse(self.can_reach_region("Open water top left area"), "Can reach Open water top left area")
|
||||
self.assertFalse(self.can_reach_region("Home Water, turtle room"), "Can reach Home Water, turtle room")
|
||||
26
worlds/aquaria/test/test_dual_song_access.py
Normal file
26
worlds/aquaria/test/test_dual_song_access.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without the dual song
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
|
||||
|
||||
class LiAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without the dual song"""
|
||||
options = {
|
||||
"turtle_randomizer": 1,
|
||||
}
|
||||
|
||||
def test_li_song_location(self) -> None:
|
||||
"""Test locations that require the dual song"""
|
||||
locations = [
|
||||
"The body bottom area, bulb in the Jelly Zap room",
|
||||
"The body bottom area, bulb in the nautilus room",
|
||||
"The body bottom area, Mutant Costume",
|
||||
"Final boss area, bulb in the boss third form room",
|
||||
"Objective complete"
|
||||
]
|
||||
items = [["Dual form"]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
73
worlds/aquaria/test/test_energy_form_access.py
Normal file
73
worlds/aquaria/test/test_energy_form_access.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without the bind song (without the early
|
||||
energy form option)
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
|
||||
|
||||
class EnergyFormAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without the energy form"""
|
||||
options = {
|
||||
"early_energy_form": False,
|
||||
}
|
||||
|
||||
def test_energy_form_location(self) -> None:
|
||||
"""Test locations that require Energy form"""
|
||||
locations = [
|
||||
"Home water, Nautilus Egg",
|
||||
"Naija's home, bulb after the energy door",
|
||||
"Energy temple first area, bulb in the bottom room blocked by a rock",
|
||||
"Energy temple second area, bulb under the rock",
|
||||
"Energy temple bottom entrance, Krotite armor",
|
||||
"Energy temple third area, bulb in the bottom path",
|
||||
"Energy temple boss area, Fallen god tooth",
|
||||
"Energy temple blaster room, Blaster egg",
|
||||
"Mithalas castle, beating the priests",
|
||||
"Mithalas cathedral, first urn in the top right room",
|
||||
"Mithalas cathedral, second urn in the top right room",
|
||||
"Mithalas cathedral, third urn in the top right room",
|
||||
"Mithalas cathedral, urn in the flesh room with fleas",
|
||||
"Mithalas cathedral, first urn in the bottom right path",
|
||||
"Mithalas cathedral, second urn in the bottom right path",
|
||||
"Mithalas cathedral, urn behind the flesh vein",
|
||||
"Mithalas cathedral, urn in the top left eyes boss room",
|
||||
"Mithalas cathedral, first urn in the path behind the flesh vein",
|
||||
"Mithalas cathedral, second urn in the path behind the flesh vein",
|
||||
"Mithalas cathedral, third urn in the path behind the flesh vein",
|
||||
"Mithalas cathedral, one of the urns in the top right room",
|
||||
"Mithalas cathedral, Mithalan Dress",
|
||||
"Mithalas cathedral right area, urn bellow the left entrance",
|
||||
"Cathedral boss area, beating Mithalan God",
|
||||
"Kelp Forest top left area, bulb close to the Verse egg",
|
||||
"Kelp forest top left area, Verse egg",
|
||||
"Kelp forest boss area, beating Drunian God",
|
||||
"Mermog cave, Piranha Egg",
|
||||
"Octopus cave, Dumbo Egg",
|
||||
"Sun temple boss area, beating Sun God",
|
||||
"Arnassi ruins, Crab armor",
|
||||
"King Jellyfish cave, bulb in the right path from King Jelly",
|
||||
"King Jellyfish cave, Jellyfish Costume",
|
||||
"Sunken city, bulb on the top of the boss area (boiler room)",
|
||||
"Final boss area, bulb in the boss third form room",
|
||||
"Beating Fallen God",
|
||||
"Beating Mithalan God",
|
||||
"Beating Drunian God",
|
||||
"Beating Sun God",
|
||||
"Beating the Golem",
|
||||
"Beating Nautilus Prime",
|
||||
"Beating Blaster Peg Prime",
|
||||
"Beating Mergog",
|
||||
"Beating Mithalan priests",
|
||||
"Beating Octopus Prime",
|
||||
"Beating Crabbius Maximus",
|
||||
"Beating King Jellyfish God Prime",
|
||||
"First secret",
|
||||
"Sunken City cleared",
|
||||
"Objective complete",
|
||||
|
||||
]
|
||||
items = [["Energy form"]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
31
worlds/aquaria/test/test_energy_form_access_option.py
Normal file
31
worlds/aquaria/test/test_energy_form_access_option.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without the bind song (with the early
|
||||
energy form option)
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase, after_home_water_locations
|
||||
|
||||
|
||||
class EnergyFormAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without the energy form"""
|
||||
options = {
|
||||
"early_energy_form": True,
|
||||
}
|
||||
|
||||
def test_energy_form_location(self) -> None:
|
||||
"""Test locations that require Energy form with early energy song enable"""
|
||||
locations = [
|
||||
"Home water, Nautilus Egg",
|
||||
"Naija's home, bulb after the energy door",
|
||||
"Energy temple first area, bulb in the bottom room blocked by a rock",
|
||||
"Energy temple second area, bulb under the rock",
|
||||
"Energy temple bottom entrance, Krotite armor",
|
||||
"Energy temple third area, bulb in the bottom path",
|
||||
"Energy temple boss area, Fallen god tooth",
|
||||
"Energy temple blaster room, Blaster egg",
|
||||
*after_home_water_locations
|
||||
]
|
||||
items = [["Energy form"]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
37
worlds/aquaria/test/test_fish_form_access.py
Normal file
37
worlds/aquaria/test/test_fish_form_access.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without the fish form
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
|
||||
|
||||
class FishFormAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without the fish form"""
|
||||
options = {
|
||||
"turtle_randomizer": 1,
|
||||
}
|
||||
|
||||
def test_fish_form_location(self) -> None:
|
||||
"""Test locations that require fish form"""
|
||||
locations = [
|
||||
"The veil top left area, bulb inside the fish pass",
|
||||
"Mithalas city, Doll",
|
||||
"Mithalas city, urn inside a home fish pass",
|
||||
"Kelp Forest top right area, bulb in the top fish pass",
|
||||
"The veil bottom area, Verse egg",
|
||||
"Open water bottom left area, bulb inside the downest fish pass",
|
||||
"Kelp Forest top left area, bulb close to the Verse egg",
|
||||
"Kelp forest top left area, Verse egg",
|
||||
"Mermog cave, bulb in the left part of the cave",
|
||||
"Mermog cave, Piranha Egg",
|
||||
"Beating Mergog",
|
||||
"Octopus cave, Dumbo Egg",
|
||||
"Octopus cave, bulb in the path below the octopus cave path",
|
||||
"Beating Octopus Prime",
|
||||
"Abyss left area, bulb in the bottom fish pass",
|
||||
"Arnassi ruins, Arnassi Armor"
|
||||
]
|
||||
items = [["Fish form"]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
45
worlds/aquaria/test/test_li_song_access.py
Normal file
45
worlds/aquaria/test/test_li_song_access.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without Li
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
|
||||
|
||||
class LiAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without Li"""
|
||||
options = {
|
||||
"turtle_randomizer": 1,
|
||||
}
|
||||
|
||||
def test_li_song_location(self) -> None:
|
||||
"""Test locations that require Li"""
|
||||
locations = [
|
||||
"Sunken city right area, crate close to the save cristal",
|
||||
"Sunken city right area, crate in the left bottom room",
|
||||
"Sunken city left area, crate in the little pipe room",
|
||||
"Sunken city left area, crate close to the save cristal",
|
||||
"Sunken city left area, crate before the bedroom",
|
||||
"Sunken city left area, Girl Costume",
|
||||
"Sunken city, bulb on the top of the boss area (boiler room)",
|
||||
"The body center area, breaking li cage",
|
||||
"The body main area, bulb on the main path blocking tube",
|
||||
"The body left area, first bulb in the top face room",
|
||||
"The body left area, second bulb in the top face room",
|
||||
"The body left area, bulb bellow the water stream",
|
||||
"The body left area, bulb in the top path to the top face room",
|
||||
"The body left area, bulb in the bottom face room",
|
||||
"The body right area, bulb in the top face room",
|
||||
"The body right area, bulb in the top path to the bottom face room",
|
||||
"The body right area, bulb in the bottom face room",
|
||||
"The body bottom area, bulb in the Jelly Zap room",
|
||||
"The body bottom area, bulb in the nautilus room",
|
||||
"The body bottom area, Mutant Costume",
|
||||
"Final boss area, bulb in the boss third form room",
|
||||
"Beating the Golem",
|
||||
"Sunken City cleared",
|
||||
"Objective complete"
|
||||
]
|
||||
items = [["Li and Li song", "Body tongue cleared"]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
71
worlds/aquaria/test/test_light_access.py
Normal file
71
worlds/aquaria/test/test_light_access.py
Normal file
@@ -0,0 +1,71 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without a light (Dumbo pet or sun form)
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
|
||||
|
||||
class LightAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without light"""
|
||||
options = {
|
||||
"turtle_randomizer": 1,
|
||||
"light_needed_to_get_to_dark_places": True,
|
||||
}
|
||||
|
||||
def test_light_location(self) -> None:
|
||||
"""Test locations that require light"""
|
||||
locations = [
|
||||
# Since the `assertAccessDependency` sweep for events even if I tell it not to, those location cannot be
|
||||
# tested.
|
||||
# "Third secret",
|
||||
# "Sun temple, bulb in the top left part",
|
||||
# "Sun temple, bulb in the top right part",
|
||||
# "Sun temple, bulb at the top of the high dark room",
|
||||
# "Sun temple, Golden Gear",
|
||||
# "Sun Worm path, first path bulb",
|
||||
# "Sun Worm path, second path bulb",
|
||||
# "Sun Worm path, first cliff bulb",
|
||||
"Octopus cave, Dumbo Egg",
|
||||
"Kelp forest bottom right area, Odd Container",
|
||||
"Kelp forest top right area, Black pearl",
|
||||
"Abyss left area, bulb in hidden path room",
|
||||
"Abyss left area, bulb in the right part",
|
||||
"Abyss left area, Glowing seed",
|
||||
"Abyss left area, Glowing Plant",
|
||||
"Abyss left area, bulb in the bottom fish pass",
|
||||
"Abyss right area, bulb behind the rock in the whale room",
|
||||
"Abyss right area, bulb in the middle path",
|
||||
"Abyss right area, bulb behind the rock in the middle path",
|
||||
"Abyss right area, bulb in the left green room",
|
||||
"Abyss right area, Transturtle",
|
||||
"Ice cave, bulb in the room to the right",
|
||||
"Ice cave, First bulbs in the top exit room",
|
||||
"Ice cave, Second bulbs in the top exit room",
|
||||
"Ice cave, third bulbs in the top exit room",
|
||||
"Ice cave, bulb in the left room",
|
||||
"Bubble cave, bulb in the left cave wall",
|
||||
"Bubble cave, bulb in the right cave wall (behind the ice cristal)",
|
||||
"Bubble cave, Verse egg",
|
||||
"Beating Mantis Shrimp Prime",
|
||||
"King Jellyfish cave, bulb in the right path from King Jelly",
|
||||
"King Jellyfish cave, Jellyfish Costume",
|
||||
"Beating King Jellyfish God Prime",
|
||||
"The whale, Verse egg",
|
||||
"First secret",
|
||||
"Sunken city right area, crate close to the save cristal",
|
||||
"Sunken city right area, crate in the left bottom room",
|
||||
"Sunken city left area, crate in the little pipe room",
|
||||
"Sunken city left area, crate close to the save cristal",
|
||||
"Sunken city left area, crate before the bedroom",
|
||||
"Sunken city left area, Girl Costume",
|
||||
"Sunken city, bulb on the top of the boss area (boiler room)",
|
||||
"Sunken City cleared",
|
||||
"Beating the Golem",
|
||||
"Beating Octopus Prime",
|
||||
"Final boss area, bulb in the boss third form room",
|
||||
"Objective complete",
|
||||
]
|
||||
items = [["Sun form", "Baby dumbo", "Has sun crystal"]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
57
worlds/aquaria/test/test_nature_form_access.py
Normal file
57
worlds/aquaria/test/test_nature_form_access.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without the nature form
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
|
||||
|
||||
class NatureFormAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without the nature form"""
|
||||
options = {
|
||||
"turtle_randomizer": 1,
|
||||
}
|
||||
|
||||
def test_nature_form_location(self) -> None:
|
||||
"""Test locations that require nature form"""
|
||||
locations = [
|
||||
"Song cave, Anemone seed",
|
||||
"Energy temple blaster room, Blaster egg",
|
||||
"Beating Blaster Peg Prime",
|
||||
"Kelp forest top left area, Verse egg",
|
||||
"Kelp Forest top left area, bulb close to the Verse egg",
|
||||
"Mithalas castle, beating the priests",
|
||||
"Kelp Forest sprite cave, bulb in the second room",
|
||||
"Kelp Forest Sprite Cave, Seed bag",
|
||||
"Beating Mithalan priests",
|
||||
"Abyss left area, bulb in the bottom fish pass",
|
||||
"Bubble cave, Verse egg",
|
||||
"Beating Mantis Shrimp Prime",
|
||||
"Sunken city right area, crate close to the save cristal",
|
||||
"Sunken city right area, crate in the left bottom room",
|
||||
"Sunken city left area, crate in the little pipe room",
|
||||
"Sunken city left area, crate close to the save cristal",
|
||||
"Sunken city left area, crate before the bedroom",
|
||||
"Sunken city left area, Girl Costume",
|
||||
"Sunken city, bulb on the top of the boss area (boiler room)",
|
||||
"Beating the Golem",
|
||||
"Sunken City cleared",
|
||||
"The body center area, breaking li cage",
|
||||
"The body main area, bulb on the main path blocking tube",
|
||||
"The body left area, first bulb in the top face room",
|
||||
"The body left area, second bulb in the top face room",
|
||||
"The body left area, bulb bellow the water stream",
|
||||
"The body left area, bulb in the top path to the top face room",
|
||||
"The body left area, bulb in the bottom face room",
|
||||
"The body right area, bulb in the top face room",
|
||||
"The body right area, bulb in the top path to the bottom face room",
|
||||
"The body right area, bulb in the bottom face room",
|
||||
"The body bottom area, bulb in the Jelly Zap room",
|
||||
"The body bottom area, bulb in the nautilus room",
|
||||
"The body bottom area, Mutant Costume",
|
||||
"Final boss area, bulb in the boss third form room",
|
||||
"Objective complete"
|
||||
]
|
||||
items = [["Nature form"]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Fri, 03 May 2024 14:07:35 +0000
|
||||
Description: Unit test used to test that no progression items can be put in hard or hidden locations when option enabled
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
from BaseClasses import ItemClassification
|
||||
|
||||
|
||||
class UNoProgressionHardHiddenTest(AquariaTestBase):
|
||||
"""Unit test used to test that no progression items can be put in hard or hidden locations when option enabled"""
|
||||
options = {
|
||||
"no_progression_hard_or_hidden_locations": True
|
||||
}
|
||||
|
||||
unfillable_locations = [
|
||||
"Energy temple boss area, Fallen god tooth",
|
||||
"Cathedral boss area, beating Mithalan God",
|
||||
"Kelp forest boss area, beating Drunian God",
|
||||
"Sun temple boss area, beating Sun God",
|
||||
"Sunken city, bulb on the top of the boss area (boiler room)",
|
||||
"Home water, Nautilus Egg",
|
||||
"Energy temple blaster room, Blaster egg",
|
||||
"Mithalas castle, beating the priests",
|
||||
"Mermog cave, Piranha Egg",
|
||||
"Octopus cave, Dumbo Egg",
|
||||
"King Jellyfish cave, bulb in the right path from King Jelly",
|
||||
"King Jellyfish cave, Jellyfish Costume",
|
||||
"Final boss area, bulb in the boss third form room",
|
||||
"Sun Worm path, first cliff bulb",
|
||||
"Sun Worm path, second cliff bulb",
|
||||
"The veil top right area, bulb in the top of the water fall",
|
||||
"Bubble cave, bulb in the left cave wall",
|
||||
"Bubble cave, bulb in the right cave wall (behind the ice cristal)",
|
||||
"Bubble cave, Verse egg",
|
||||
"Kelp Forest bottom left area, bulb close to the spirit crystals",
|
||||
"Kelp forest bottom left area, Walker baby",
|
||||
"Sun temple, Sun key",
|
||||
"The body bottom area, Mutant Costume",
|
||||
"Sun temple, bulb in the hidden room of the right part",
|
||||
"Arnassi ruins, Arnassi Armor",
|
||||
]
|
||||
|
||||
def test_unconfine_home_water_both_location_fillable(self) -> None:
|
||||
"""
|
||||
Unit test used to test that no progression items can be put in hard or hidden locations when option enabled
|
||||
"""
|
||||
for location in self.unfillable_locations:
|
||||
for item_name in self.world.item_names:
|
||||
item = self.get_item_by_name(item_name)
|
||||
if item.classification == ItemClassification.progression:
|
||||
self.assertFalse(
|
||||
self.world.get_location(location).can_fill(self.multiworld.state, item, False),
|
||||
"The location \"" + location + "\" can be filled with \"" + item_name + "\"")
|
||||
else:
|
||||
self.assertTrue(
|
||||
self.world.get_location(location).can_fill(self.multiworld.state, item, False),
|
||||
"The location \"" + location + "\" cannot be filled with \"" + item_name + "\"")
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Fri, 03 May 2024 14:07:35 +0000
|
||||
Description: Unit test used to test that progression items can be put in hard or hidden locations when option disabled
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
from BaseClasses import ItemClassification
|
||||
|
||||
|
||||
class UNoProgressionHardHiddenTest(AquariaTestBase):
|
||||
"""Unit test used to test that no progression items can be put in hard or hidden locations when option disabled"""
|
||||
options = {
|
||||
"no_progression_hard_or_hidden_locations": False
|
||||
}
|
||||
|
||||
unfillable_locations = [
|
||||
"Energy temple boss area, Fallen god tooth",
|
||||
"Cathedral boss area, beating Mithalan God",
|
||||
"Kelp forest boss area, beating Drunian God",
|
||||
"Sun temple boss area, beating Sun God",
|
||||
"Sunken city, bulb on the top of the boss area (boiler room)",
|
||||
"Home water, Nautilus Egg",
|
||||
"Energy temple blaster room, Blaster egg",
|
||||
"Mithalas castle, beating the priests",
|
||||
"Mermog cave, Piranha Egg",
|
||||
"Octopus cave, Dumbo Egg",
|
||||
"King Jellyfish cave, bulb in the right path from King Jelly",
|
||||
"King Jellyfish cave, Jellyfish Costume",
|
||||
"Final boss area, bulb in the boss third form room",
|
||||
"Sun Worm path, first cliff bulb",
|
||||
"Sun Worm path, second cliff bulb",
|
||||
"The veil top right area, bulb in the top of the water fall",
|
||||
"Bubble cave, bulb in the left cave wall",
|
||||
"Bubble cave, bulb in the right cave wall (behind the ice cristal)",
|
||||
"Bubble cave, Verse egg",
|
||||
"Kelp Forest bottom left area, bulb close to the spirit crystals",
|
||||
"Kelp forest bottom left area, Walker baby",
|
||||
"Sun temple, Sun key",
|
||||
"The body bottom area, Mutant Costume",
|
||||
"Sun temple, bulb in the hidden room of the right part",
|
||||
"Arnassi ruins, Arnassi Armor",
|
||||
]
|
||||
|
||||
def test_unconfine_home_water_both_location_fillable(self) -> None:
|
||||
"""Unit test used to test that progression items can be put in hard or hidden locations when option disabled"""
|
||||
for location in self.unfillable_locations:
|
||||
for item_name in self.world.item_names:
|
||||
item = self.get_item_by_name(item_name)
|
||||
self.assertTrue(
|
||||
self.world.get_location(location).can_fill(self.multiworld.state, item, False),
|
||||
"The location \"" + location + "\" cannot be filled with \"" + item_name + "\"")
|
||||
|
||||
36
worlds/aquaria/test/test_spirit_form_access.py
Normal file
36
worlds/aquaria/test/test_spirit_form_access.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without the spirit form
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
|
||||
|
||||
class SpiritFormAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without the spirit form"""
|
||||
|
||||
def test_spirit_form_location(self) -> None:
|
||||
"""Test locations that require spirit form"""
|
||||
locations = [
|
||||
"The veil bottom area, bulb in the spirit path",
|
||||
"Mithalas city castle, Trident head",
|
||||
"Open water skeleton path, King skull",
|
||||
"Kelp forest bottom left area, Walker baby",
|
||||
"Abyss right area, bulb behind the rock in the whale room",
|
||||
"The whale, Verse egg",
|
||||
"Ice cave, bulb in the room to the right",
|
||||
"Ice cave, First bulbs in the top exit room",
|
||||
"Ice cave, Second bulbs in the top exit room",
|
||||
"Ice cave, third bulbs in the top exit room",
|
||||
"Ice cave, bulb in the left room",
|
||||
"Bubble cave, bulb in the left cave wall",
|
||||
"Bubble cave, bulb in the right cave wall (behind the ice cristal)",
|
||||
"Bubble cave, Verse egg",
|
||||
"Sunken city left area, Girl Costume",
|
||||
"Beating Mantis Shrimp Prime",
|
||||
"First secret",
|
||||
"Arnassi ruins, Arnassi Armor",
|
||||
]
|
||||
items = [["Spirit form"]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
25
worlds/aquaria/test/test_sun_form_access.py
Normal file
25
worlds/aquaria/test/test_sun_form_access.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without the sun form
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
|
||||
|
||||
class SunFormAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without the sun form"""
|
||||
|
||||
def test_sun_form_location(self) -> None:
|
||||
"""Test locations that require sun form"""
|
||||
locations = [
|
||||
"First secret",
|
||||
"The whale, Verse egg",
|
||||
"Abyss right area, bulb behind the rock in the whale room",
|
||||
"Octopus cave, Dumbo Egg",
|
||||
"Beating Octopus Prime",
|
||||
"Final boss area, bulb in the boss third form room",
|
||||
"Objective complete"
|
||||
]
|
||||
items = [["Sun form"]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
21
worlds/aquaria/test/test_unconfine_home_water_via_both.py
Normal file
21
worlds/aquaria/test/test_unconfine_home_water_via_both.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Fri, 03 May 2024 14:07:35 +0000
|
||||
Description: Unit test used to test accessibility of region with the unconfined home water option via transportation
|
||||
turtle and energy door
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
|
||||
|
||||
class UnconfineHomeWaterBothAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of region with the unconfine home water option enabled"""
|
||||
options = {
|
||||
"unconfine_home_water": 3,
|
||||
"early_energy_form": False
|
||||
}
|
||||
|
||||
def test_unconfine_home_water_both_location(self) -> None:
|
||||
"""Test locations accessible with unconfined home water via energy door and transportation turtle"""
|
||||
self.assertTrue(self.can_reach_region("Open water top left area"), "Cannot reach Open water top left area")
|
||||
self.assertTrue(self.can_reach_region("Home Water, turtle room"), "Cannot reach Home Water, turtle room")
|
||||
@@ -0,0 +1,20 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Fri, 03 May 2024 14:07:35 +0000
|
||||
Description: Unit test used to test accessibility of region with the unconfined home water option via the energy door
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
|
||||
|
||||
class UnconfineHomeWaterEnergyDoorAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of region with the unconfine home water option enabled"""
|
||||
options = {
|
||||
"unconfine_home_water": 1,
|
||||
"early_energy_form": False
|
||||
}
|
||||
|
||||
def test_unconfine_home_water_energy_door_location(self) -> None:
|
||||
"""Test locations accessible with unconfined home water via energy door"""
|
||||
self.assertTrue(self.can_reach_region("Open water top left area"), "Cannot reach Open water top left area")
|
||||
self.assertFalse(self.can_reach_region("Home Water, turtle room"), "Can reach Home Water, turtle room")
|
||||
@@ -0,0 +1,20 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Fri, 03 May 2024 14:07:35 +0000
|
||||
Description: Unit test used to test accessibility of region with the unconfined home water option via transturtle
|
||||
"""
|
||||
|
||||
from worlds.aquaria.test import AquariaTestBase
|
||||
|
||||
|
||||
class UnconfineHomeWaterTransturtleAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of region with the unconfine home water option enabled"""
|
||||
options = {
|
||||
"unconfine_home_water": 2,
|
||||
"early_energy_form": False
|
||||
}
|
||||
|
||||
def test_unconfine_home_water_transturtle_location(self) -> None:
|
||||
"""Test locations accessible with unconfined home water via transportation turtle"""
|
||||
self.assertTrue(self.can_reach_region("Home Water, turtle room"), "Cannot reach Home Water, turtle room")
|
||||
self.assertFalse(self.can_reach_region("Open water top left area"), "Can reach Open water top left area")
|
||||
553
worlds/bomb_rush_cyberfunk/Items.py
Normal file
553
worlds/bomb_rush_cyberfunk/Items.py
Normal file
@@ -0,0 +1,553 @@
|
||||
from typing import TypedDict, List, Dict, Set
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class BRCType(Enum):
|
||||
Music = 0
|
||||
GraffitiM = 1
|
||||
GraffitiL = 2
|
||||
GraffitiXL = 3
|
||||
Skateboard = 4
|
||||
InlineSkates = 5
|
||||
BMX = 6
|
||||
Character = 7
|
||||
Outfit = 8
|
||||
REP = 9
|
||||
Camera = 10
|
||||
|
||||
|
||||
class ItemDict(TypedDict, total=False):
|
||||
name: str
|
||||
count: int
|
||||
type: BRCType
|
||||
|
||||
|
||||
base_id = 2308000
|
||||
|
||||
|
||||
item_table: List[ItemDict] = [
|
||||
# Music
|
||||
{'name': "Music (GET ENUF)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Chuckin Up)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Spectres)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (You Can Say Hi)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (JACK DA FUNK)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Feel The Funk (Computer Love))",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Big City Life)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (I Wanna Kno)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Plume)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Two Days Off)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Scraped On The Way Out)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Last Hoorah)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (State of Mind)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (AGUA)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Condensed milk)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Light Switch)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Hair Dun Nails Dun)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Precious Thing)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Next To Me)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Refuse)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Iridium)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Funk Express)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (In The Pocket)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Bounce Upon A Time)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (hwbouths)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Morning Glow)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Chromebies)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (watchyaback!)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Anime Break)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (DA PEOPLE)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Trinitron)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Operator)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Sunshine Popping Mixtape)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (House Cats Mixtape)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Breaking Machine Mixtape)",
|
||||
'type': BRCType.Music},
|
||||
{'name': "Music (Beastmode Hip Hop Mixtape)",
|
||||
'type': BRCType.Music},
|
||||
|
||||
# Graffiti
|
||||
{'name': "Graffiti (M - OVERWHELMME)",
|
||||
'type': BRCType.GraffitiM},
|
||||
{'name': "Graffiti (M - QUICK BING)",
|
||||
'type': BRCType.GraffitiM},
|
||||
{'name': "Graffiti (M - BLOCKY)",
|
||||
'type': BRCType.GraffitiM},
|
||||
#{'name': "Graffiti (M - Flow)",
|
||||
# 'type': BRCType.GraffitiM},
|
||||
{'name': "Graffiti (M - Pora)",
|
||||
'type': BRCType.GraffitiM},
|
||||
{'name': "Graffiti (M - Teddy 4)",
|
||||
'type': BRCType.GraffitiM},
|
||||
{'name': "Graffiti (M - BOMB BEATS)",
|
||||
'type': BRCType.GraffitiM},
|
||||
{'name': "Graffiti (M - SPRAYTANICPANIC!)",
|
||||
'type': BRCType.GraffitiM},
|
||||
{'name': "Graffiti (M - SHOGUN)",
|
||||
'type': BRCType.GraffitiM},
|
||||
#{'name': "Graffiti (M - EVIL DARUMA)",
|
||||
# 'type': BRCType.GraffitiM},
|
||||
{'name': "Graffiti (M - TeleBinge)",
|
||||
'type': BRCType.GraffitiM},
|
||||
#{'name': "Graffiti (M - All Screws Loose)",
|
||||
# 'type': BRCType.GraffitiM},
|
||||
{'name': "Graffiti (M - 0m33)",
|
||||
'type': BRCType.GraffitiM},
|
||||
{'name': "Graffiti (M - Vom'B)",
|
||||
'type': BRCType.GraffitiM},
|
||||
{'name': "Graffiti (M - Street classic)",
|
||||
'type': BRCType.GraffitiM},
|
||||
{'name': "Graffiti (M - Thick Candy)",
|
||||
'type': BRCType.GraffitiM},
|
||||
{'name': "Graffiti (M - colorBOMB)",
|
||||
'type': BRCType.GraffitiM},
|
||||
{'name': "Graffiti (M - Zona Leste)",
|
||||
'type': BRCType.GraffitiM},
|
||||
{'name': "Graffiti (M - Stacked Symbols)",
|
||||
'type': BRCType.GraffitiM},
|
||||
#{'name': "Graffiti (M - Constellation Circle)",
|
||||
# 'type': BRCType.GraffitiM},
|
||||
{'name': "Graffiti (M - B-boy Love)",
|
||||
'type': BRCType.GraffitiM},
|
||||
{'name': "Graffiti (M - Devil 68)",
|
||||
'type': BRCType.GraffitiM},
|
||||
{'name': "Graffiti (M - pico pow)",
|
||||
'type': BRCType.GraffitiM},
|
||||
#{'name': "Graffiti (M - 8 MINUTES OF LEAN MEAN)",
|
||||
# 'type': BRCType.GraffitiM},
|
||||
{'name': "Graffiti (L - WHOLE SIXER)",
|
||||
'type': BRCType.GraffitiL},
|
||||
{'name': "Graffiti (L - INFINITY)",
|
||||
'type': BRCType.GraffitiL},
|
||||
#{'name': "Graffiti (L - Dynamo)",
|
||||
# 'type': BRCType.GraffitiL},
|
||||
{'name': "Graffiti (L - VoodooBoy)",
|
||||
'type': BRCType.GraffitiL},
|
||||
{'name': "Graffiti (L - Fang It Up!)",
|
||||
'type': BRCType.GraffitiL},
|
||||
{'name': "Graffiti (L - FREAKS)",
|
||||
'type': BRCType.GraffitiL},
|
||||
{'name': "Graffiti (L - Graffo Le Fou)",
|
||||
'type': BRCType.GraffitiL},
|
||||
{'name': "Graffiti (L - Lauder)",
|
||||
'type': BRCType.GraffitiL},
|
||||
{'name': "Graffiti (L - SpawningSeason)",
|
||||
'type': BRCType.GraffitiL},
|
||||
{'name': "Graffiti (L - Moai Marathon)",
|
||||
'type': BRCType.GraffitiL},
|
||||
{'name': "Graffiti (L - Tius)",
|
||||
'type': BRCType.GraffitiL},
|
||||
#{'name': "Graffiti (L - KANI-BOZU)",
|
||||
# 'type': BRCType.GraffitiL},
|
||||
{'name': "Graffiti (L - NOISY NINJA)",
|
||||
'type': BRCType.GraffitiL},
|
||||
#{'name': "Graffiti (L - Dinner On The Court)",
|
||||
# 'type': BRCType.GraffitiL},
|
||||
{'name': "Graffiti (L - Campaign Trail)",
|
||||
'type': BRCType.GraffitiL},
|
||||
{'name': "Graffiti (L - skate or di3)",
|
||||
'type': BRCType.GraffitiL},
|
||||
{'name': "Graffiti (L - Jd Vila Formosa)",
|
||||
'type': BRCType.GraffitiL},
|
||||
{'name': "Graffiti (L - Messenger Mural)",
|
||||
'type': BRCType.GraffitiL},
|
||||
#{'name': "Graffiti (L - Solstice Script)",
|
||||
# 'type': BRCType.GraffitiL},
|
||||
{'name': "Graffiti (L - RECORD.HEAD)",
|
||||
'type': BRCType.GraffitiL},
|
||||
{'name': "Graffiti (L - Boom)",
|
||||
'type': BRCType.GraffitiL},
|
||||
{'name': "Graffiti (L - wild rush)",
|
||||
'type': BRCType.GraffitiL},
|
||||
{'name': "Graffiti (L - buttercup)",
|
||||
'type': BRCType.GraffitiL},
|
||||
#{'name': "Graffiti (L - DIGITAL BLOCKBUSTER)",
|
||||
# 'type': BRCType.GraffitiL},
|
||||
{'name': "Graffiti (XL - Gold Rush)",
|
||||
'type': BRCType.GraffitiXL},
|
||||
{'name': "Graffiti (XL - WILD STRUXXA)",
|
||||
'type': BRCType.GraffitiXL},
|
||||
{'name': "Graffiti (XL - VIBRATIONS)",
|
||||
'type': BRCType.GraffitiXL},
|
||||
#{'name': "Graffiti (XL - Bevel)",
|
||||
# 'type': BRCType.GraffitiXL},
|
||||
{'name': "Graffiti (XL - SECOND SIGHT)",
|
||||
'type': BRCType.GraffitiXL},
|
||||
{'name': "Graffiti (XL - Bomb Croc)",
|
||||
'type': BRCType.GraffitiXL},
|
||||
{'name': "Graffiti (XL - FATE)",
|
||||
'type': BRCType.GraffitiXL},
|
||||
{'name': "Graffiti (XL - Web Spitter)",
|
||||
'type': BRCType.GraffitiXL},
|
||||
{'name': "Graffiti (XL - MOTORCYCLE GANG)",
|
||||
'type': BRCType.GraffitiXL},
|
||||
#{'name': "Graffiti (XL - CYBER TENGU)",
|
||||
# 'type': BRCType.GraffitiXL},
|
||||
#{'name': "Graffiti (XL - Don't Screw Around)",
|
||||
# 'type': BRCType.GraffitiXL},
|
||||
{'name': "Graffiti (XL - Deep Dive)",
|
||||
'type': BRCType.GraffitiXL},
|
||||
{'name': "Graffiti (XL - MegaHood)",
|
||||
'type': BRCType.GraffitiXL},
|
||||
{'name': "Graffiti (XL - Gamex UPA ABL)",
|
||||
'type': BRCType.GraffitiXL},
|
||||
{'name': "Graffiti (XL - BiGSHiNYBoMB)",
|
||||
'type': BRCType.GraffitiXL},
|
||||
{'name': "Graffiti (XL - Bomb Burner)",
|
||||
'type': BRCType.GraffitiXL},
|
||||
#{'name': "Graffiti (XL - Astrological Augury)",
|
||||
# 'type': BRCType.GraffitiXL},
|
||||
{'name': "Graffiti (XL - Pirate's Life 4 Me)",
|
||||
'type': BRCType.GraffitiXL},
|
||||
{'name': "Graffiti (XL - Bombing by FireMan)",
|
||||
'type': BRCType.GraffitiXL},
|
||||
{'name': "Graffiti (XL - end 2 end)",
|
||||
'type': BRCType.GraffitiXL},
|
||||
{'name': "Graffiti (XL - Raver Funk)",
|
||||
'type': BRCType.GraffitiXL},
|
||||
{'name': "Graffiti (XL - headphones on Helmet on)",
|
||||
'type': BRCType.GraffitiXL},
|
||||
#{'name': "Graffiti (XL - HIGH TECH WS)",
|
||||
# 'type': BRCType.GraffitiXL},
|
||||
|
||||
# Skateboards
|
||||
{'name': "Skateboard (Devon)",
|
||||
'type': BRCType.Skateboard},
|
||||
{'name': "Skateboard (Terrence)",
|
||||
'type': BRCType.Skateboard},
|
||||
{'name': "Skateboard (Maceo)",
|
||||
'type': BRCType.Skateboard},
|
||||
{'name': "Skateboard (Lazer Accuracy)",
|
||||
'type': BRCType.Skateboard},
|
||||
{'name': "Skateboard (Death Boogie)",
|
||||
'type': BRCType.Skateboard},
|
||||
{'name': "Skateboard (Sylk)",
|
||||
'type': BRCType.Skateboard},
|
||||
{'name': "Skateboard (Taiga)",
|
||||
'type': BRCType.Skateboard},
|
||||
{'name': "Skateboard (Just Swell)",
|
||||
'type': BRCType.Skateboard},
|
||||
{'name': "Skateboard (Mantra)",
|
||||
'type': BRCType.Skateboard},
|
||||
|
||||
# Inline Skates
|
||||
{'name': "Inline Skates (Glaciers)",
|
||||
'type': BRCType.InlineSkates},
|
||||
{'name': "Inline Skates (Sweet Royale)",
|
||||
'type': BRCType.InlineSkates},
|
||||
{'name': "Inline Skates (Strawberry Missiles)",
|
||||
'type': BRCType.InlineSkates},
|
||||
{'name': "Inline Skates (Ice Cold Killers)",
|
||||
'type': BRCType.InlineSkates},
|
||||
{'name': "Inline Skates (Red Industry)",
|
||||
'type': BRCType.InlineSkates},
|
||||
{'name': "Inline Skates (Mech Adversary)",
|
||||
'type': BRCType.InlineSkates},
|
||||
{'name': "Inline Skates (Orange Blasters)",
|
||||
'type': BRCType.InlineSkates},
|
||||
{'name': "Inline Skates (ck)",
|
||||
'type': BRCType.InlineSkates},
|
||||
{'name': "Inline Skates (Sharpshooters)",
|
||||
'type': BRCType.InlineSkates},
|
||||
|
||||
# BMX
|
||||
{'name': "BMX (Mr. Taupe)",
|
||||
'type': BRCType.BMX},
|
||||
{'name': "BMX (Gum)",
|
||||
'type': BRCType.BMX},
|
||||
{'name': "BMX (Steel Wheeler)",
|
||||
'type': BRCType.BMX},
|
||||
{'name': "BMX (oyo)",
|
||||
'type': BRCType.BMX},
|
||||
{'name': "BMX (Rigid No.6)",
|
||||
'type': BRCType.BMX},
|
||||
{'name': "BMX (Ceremony)",
|
||||
'type': BRCType.BMX},
|
||||
{'name': "BMX (XXX)",
|
||||
'type': BRCType.BMX},
|
||||
{'name': "BMX (Terrazza)",
|
||||
'type': BRCType.BMX},
|
||||
{'name': "BMX (Dedication)",
|
||||
'type': BRCType.BMX},
|
||||
|
||||
# Outfits
|
||||
{'name': "Outfit (Red - Autumn)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Red - Winter)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Tryce - Autumn)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Tryce - Winter)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Bel - Autumn)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Bel - Winter)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Vinyl - Autumn)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Vinyl - Winter)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Solace - Autumn)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Solace - Winter)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Felix - Autumn)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Felix - Winter)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Rave - Autumn)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Rave - Winter)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Mesh - Autumn)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Mesh - Winter)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Shine - Autumn)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Shine - Winter)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Rise - Autumn)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Rise - Winter)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Coil - Autumn)",
|
||||
'type': BRCType.Outfit},
|
||||
{'name': "Outfit (Coil - Winter)",
|
||||
'type': BRCType.Outfit},
|
||||
|
||||
# Characters
|
||||
{'name': "Tryce",
|
||||
'type': BRCType.Character},
|
||||
{'name': "Bel",
|
||||
'type': BRCType.Character},
|
||||
{'name': "Vinyl",
|
||||
'type': BRCType.Character},
|
||||
{'name': "Solace",
|
||||
'type': BRCType.Character},
|
||||
{'name': "Rave",
|
||||
'type': BRCType.Character},
|
||||
{'name': "Mesh",
|
||||
'type': BRCType.Character},
|
||||
{'name': "Shine",
|
||||
'type': BRCType.Character},
|
||||
{'name': "Rise",
|
||||
'type': BRCType.Character},
|
||||
{'name': "Coil",
|
||||
'type': BRCType.Character},
|
||||
{'name': "Frank",
|
||||
'type': BRCType.Character},
|
||||
{'name': "Rietveld",
|
||||
'type': BRCType.Character},
|
||||
{'name': "DJ Cyber",
|
||||
'type': BRCType.Character},
|
||||
{'name': "Eclipse",
|
||||
'type': BRCType.Character},
|
||||
{'name': "DOT.EXE",
|
||||
'type': BRCType.Character},
|
||||
{'name': "Devil Theory",
|
||||
'type': BRCType.Character},
|
||||
{'name': "Flesh Prince",
|
||||
'type': BRCType.Character},
|
||||
{'name': "Futurism",
|
||||
'type': BRCType.Character},
|
||||
{'name': "Oldhead",
|
||||
'type': BRCType.Character},
|
||||
|
||||
# REP
|
||||
{'name': "8 REP",
|
||||
'type': BRCType.REP},
|
||||
{'name': "16 REP",
|
||||
'type': BRCType.REP},
|
||||
{'name': "24 REP",
|
||||
'type': BRCType.REP},
|
||||
{'name': "32 REP",
|
||||
'type': BRCType.REP},
|
||||
{'name': "48 REP",
|
||||
'type': BRCType.REP},
|
||||
|
||||
# App
|
||||
{'name': "Camera App",
|
||||
'type': BRCType.Camera}
|
||||
]
|
||||
|
||||
|
||||
group_table: Dict[str, Set[str]] = {
|
||||
"graffitim": {"Graffiti (M - OVERWHELMME)",
|
||||
"Graffiti (M - QUICK BING)",
|
||||
"Graffiti (M - BLOCKY)",
|
||||
"Graffiti (M - Pora)",
|
||||
"Graffiti (M - Teddy 4)",
|
||||
"Graffiti (M - BOMB BEATS)",
|
||||
"Graffiti (M - SPRAYTANICPANIC!)",
|
||||
"Graffiti (M - SHOGUN)",
|
||||
"Graffiti (M - TeleBinge)",
|
||||
"Graffiti (M - 0m33)",
|
||||
"Graffiti (M - Vom'B)",
|
||||
"Graffiti (M - Street classic)",
|
||||
"Graffiti (M - Thick Candy)",
|
||||
"Graffiti (M - colorBOMB)",
|
||||
"Graffiti (M - Zona Leste)",
|
||||
"Graffiti (M - Stacked Symbols)",
|
||||
"Graffiti (M - B-boy Love)",
|
||||
"Graffiti (M - Devil 68)",
|
||||
"Graffiti (M - pico pow)"},
|
||||
"graffitil": {"Graffiti (L - WHOLE SIXER)",
|
||||
"Graffiti (L - INFINITY)",
|
||||
"Graffiti (L - VoodooBoy)",
|
||||
"Graffiti (L - Fang It Up!)",
|
||||
"Graffiti (L - FREAKS)",
|
||||
"Graffiti (L - Graffo Le Fou)",
|
||||
"Graffiti (L - Lauder)",
|
||||
"Graffiti (L - SpawningSeason)",
|
||||
"Graffiti (L - Moai Marathon)",
|
||||
"Graffiti (L - Tius)",
|
||||
"Graffiti (L - NOISY NINJA)",
|
||||
"Graffiti (L - Campaign Trail)",
|
||||
"Graffiti (L - skate or di3)",
|
||||
"Graffiti (L - Jd Vila Formosa)",
|
||||
"Graffiti (L - Messenger Mural)",
|
||||
"Graffiti (L - RECORD.HEAD)",
|
||||
"Graffiti (L - Boom)",
|
||||
"Graffiti (L - wild rush)",
|
||||
"Graffiti (L - buttercup)"},
|
||||
"graffitixl": {"Graffiti (XL - Gold Rush)",
|
||||
"Graffiti (XL - WILD STRUXXA)",
|
||||
"Graffiti (XL - VIBRATIONS)",
|
||||
"Graffiti (XL - SECOND SIGHT)",
|
||||
"Graffiti (XL - Bomb Croc)",
|
||||
"Graffiti (XL - FATE)",
|
||||
"Graffiti (XL - Web Spitter)",
|
||||
"Graffiti (XL - MOTORCYCLE GANG)",
|
||||
"Graffiti (XL - Deep Dive)",
|
||||
"Graffiti (XL - MegaHood)",
|
||||
"Graffiti (XL - Gamex UPA ABL)",
|
||||
"Graffiti (XL - BiGSHiNYBoMB)",
|
||||
"Graffiti (XL - Bomb Burner)",
|
||||
"Graffiti (XL - Pirate's Life 4 Me)",
|
||||
"Graffiti (XL - Bombing by FireMan)",
|
||||
"Graffiti (XL - end 2 end)",
|
||||
"Graffiti (XL - Raver Funk)",
|
||||
"Graffiti (XL - headphones on Helmet on)"},
|
||||
"skateboard": {"Skateboard (Devon)",
|
||||
"Skateboard (Terrence)",
|
||||
"Skateboard (Maceo)",
|
||||
"Skateboard (Lazer Accuracy)",
|
||||
"Skateboard (Death Boogie)",
|
||||
"Skateboard (Sylk)",
|
||||
"Skateboard (Taiga)",
|
||||
"Skateboard (Just Swell)",
|
||||
"Skateboard (Mantra)"},
|
||||
"inline skates": {"Inline Skates (Glaciers)",
|
||||
"Inline Skates (Sweet Royale)",
|
||||
"Inline Skates (Strawberry Missiles)",
|
||||
"Inline Skates (Ice Cold Killers)",
|
||||
"Inline Skates (Red Industry)",
|
||||
"Inline Skates (Mech Adversary)",
|
||||
"Inline Skates (Orange Blasters)",
|
||||
"Inline Skates (ck)",
|
||||
"Inline Skates (Sharpshooters)"},
|
||||
"skates": {"Inline Skates (Glaciers)",
|
||||
"Inline Skates (Sweet Royale)",
|
||||
"Inline Skates (Strawberry Missiles)",
|
||||
"Inline Skates (Ice Cold Killers)",
|
||||
"Inline Skates (Red Industry)",
|
||||
"Inline Skates (Mech Adversary)",
|
||||
"Inline Skates (Orange Blasters)",
|
||||
"Inline Skates (ck)",
|
||||
"Inline Skates (Sharpshooters)"},
|
||||
"inline": {"Inline Skates (Glaciers)",
|
||||
"Inline Skates (Sweet Royale)",
|
||||
"Inline Skates (Strawberry Missiles)",
|
||||
"Inline Skates (Ice Cold Killers)",
|
||||
"Inline Skates (Red Industry)",
|
||||
"Inline Skates (Mech Adversary)",
|
||||
"Inline Skates (Orange Blasters)",
|
||||
"Inline Skates (ck)",
|
||||
"Inline Skates (Sharpshooters)"},
|
||||
"bmx": {"BMX (Mr. Taupe)",
|
||||
"BMX (Gum)",
|
||||
"BMX (Steel Wheeler)",
|
||||
"BMX (oyo)",
|
||||
"BMX (Rigid No.6)",
|
||||
"BMX (Ceremony)",
|
||||
"BMX (XXX)",
|
||||
"BMX (Terrazza)",
|
||||
"BMX (Dedication)"},
|
||||
"bike": {"BMX (Mr. Taupe)",
|
||||
"BMX (Gum)",
|
||||
"BMX (Steel Wheeler)",
|
||||
"BMX (oyo)",
|
||||
"BMX (Rigid No.6)",
|
||||
"BMX (Ceremony)",
|
||||
"BMX (XXX)",
|
||||
"BMX (Terrazza)",
|
||||
"BMX (Dedication)"},
|
||||
"bicycle": {"BMX (Mr. Taupe)",
|
||||
"BMX (Gum)",
|
||||
"BMX (Steel Wheeler)",
|
||||
"BMX (oyo)",
|
||||
"BMX (Rigid No.6)",
|
||||
"BMX (Ceremony)",
|
||||
"BMX (XXX)",
|
||||
"BMX (Terrazza)",
|
||||
"BMX (Dedication)"},
|
||||
"characters": {"Tryce",
|
||||
"Bel",
|
||||
"Vinyl",
|
||||
"Solace",
|
||||
"Rave",
|
||||
"Mesh",
|
||||
"Shine",
|
||||
"Rise",
|
||||
"Coil",
|
||||
"Frank",
|
||||
"Rietveld",
|
||||
"DJ Cyber",
|
||||
"Eclipse",
|
||||
"DOT.EXE",
|
||||
"Devil Theory",
|
||||
"Flesh Prince",
|
||||
"Futurism",
|
||||
"Oldhead"},
|
||||
"girl": {"Bel",
|
||||
"Vinyl",
|
||||
"Rave",
|
||||
"Shine",
|
||||
"Rise",
|
||||
"Futurism"}
|
||||
}
|
||||
785
worlds/bomb_rush_cyberfunk/Locations.py
Normal file
785
worlds/bomb_rush_cyberfunk/Locations.py
Normal file
@@ -0,0 +1,785 @@
|
||||
from typing import TypedDict, List
|
||||
from .Regions import Stages
|
||||
|
||||
|
||||
class LocationDict(TypedDict):
|
||||
name: str
|
||||
stage: Stages
|
||||
game_id: str
|
||||
|
||||
|
||||
class EventDict(TypedDict):
|
||||
name: str
|
||||
stage: str
|
||||
item: str
|
||||
|
||||
|
||||
location_table: List[LocationDict] = [
|
||||
{'name': "Hideout: Half pipe CD",
|
||||
'stage': Stages.H,
|
||||
'game_id': "MusicTrack_CondensedMilk"},
|
||||
{'name': "Hideout: Garage tower CD",
|
||||
'stage': Stages.H,
|
||||
'game_id': "MusicTrack_MorningGlow"},
|
||||
{'name': "Hideout: Rooftop CD",
|
||||
'stage': Stages.H,
|
||||
'game_id': "MusicTrack_LightSwitch"},
|
||||
{'name': "Hideout: Under staircase graffiti",
|
||||
'stage': Stages.H,
|
||||
'game_id': "UnlockGraffiti_grafTex_M1"},
|
||||
{'name': "Hideout: Secret area graffiti",
|
||||
'stage': Stages.H,
|
||||
'game_id': "UnlockGraffiti_grafTex_L1"},
|
||||
{'name': "Hideout: Rear studio graffiti",
|
||||
'stage': Stages.H,
|
||||
'game_id': "UnlockGraffiti_grafTex_XL1"},
|
||||
{'name': "Hideout: Corner ledge graffiti",
|
||||
'stage': Stages.H,
|
||||
'game_id': "UnlockGraffiti_grafTex_M2"},
|
||||
{'name': "Hideout: Upper platform skateboard",
|
||||
'stage': Stages.H,
|
||||
'game_id': "SkateboardDeck3"},
|
||||
{'name': "Hideout: BMX garage skateboard",
|
||||
'stage': Stages.H,
|
||||
'game_id': "SkateboardDeck2"},
|
||||
{'name': "Hideout: Unlock phone app",
|
||||
'stage': Stages.H,
|
||||
'game_id': "camera"},
|
||||
{'name': "Hideout: Vinyl joins the crew",
|
||||
'stage': Stages.H,
|
||||
'game_id': "girl1"},
|
||||
{'name': "Hideout: Solace joins the crew",
|
||||
'stage': Stages.H,
|
||||
'game_id': "dummy"},
|
||||
|
||||
{'name': "Versum Hill: Main street Robo Post graffiti",
|
||||
'stage': Stages.VH1,
|
||||
'game_id': "UnlockGraffiti_grafTex_L4"},
|
||||
{'name': "Versum Hill: Behind glass graffiti",
|
||||
'stage': Stages.VH1,
|
||||
'game_id': "UnlockGraffiti_grafTex_L3"},
|
||||
{'name': "Versum Hill: Office room graffiti",
|
||||
'stage': Stages.VH1,
|
||||
'game_id': "UnlockGraffiti_grafTex_M4"},
|
||||
{'name': "Versum Hill: Under bridge graffiti",
|
||||
'stage': Stages.VH2,
|
||||
'game_id': "UnlockGraffiti_grafTex_XL4"},
|
||||
{'name': "Versum Hill: Train rail ledge skateboard",
|
||||
'stage': Stages.VH2,
|
||||
'game_id': "SkateboardDeck6"},
|
||||
{'name': "Versum Hill: Train station CD",
|
||||
'stage': Stages.VH2,
|
||||
'game_id': "MusicTrack_PreciousThing"},
|
||||
{'name': "Versum Hill: Billboard platform outfit",
|
||||
'stage': Stages.VH2,
|
||||
'game_id': "MetalheadOutfit3"},
|
||||
{'name': "Versum Hill: Hilltop Robo Post CD",
|
||||
'stage': Stages.VH2,
|
||||
'game_id': "MusicTrack_BounceUponATime"},
|
||||
{'name': "Versum Hill: Hill secret skateboard",
|
||||
'stage': Stages.VH2,
|
||||
'game_id': "SkateboardDeck7"},
|
||||
{'name': "Versum Hill: Rooftop CD",
|
||||
'stage': Stages.VH2,
|
||||
'game_id': "MusicTrack_NextToMe"},
|
||||
{'name': "Versum Hill: Wallrunning challenge reward",
|
||||
'stage': Stages.VH2,
|
||||
'game_id': "UnlockGraffiti_grafTex_M3"},
|
||||
{'name': "Versum Hill: Manual challenge reward",
|
||||
'stage': Stages.VH2,
|
||||
'game_id': "UnlockGraffiti_grafTex_L2"},
|
||||
{'name': "Versum Hill: Corner challenge reward",
|
||||
'stage': Stages.VH2,
|
||||
'game_id': "UnlockGraffiti_grafTex_M13"},
|
||||
{'name': "Versum Hill: Side street alley outfit",
|
||||
'stage': Stages.VH3,
|
||||
'game_id': "MetalheadOutfit4"},
|
||||
{'name': "Versum Hill: Side street secret skateboard",
|
||||
'stage': Stages.VH3,
|
||||
'game_id': "SkateboardDeck9"},
|
||||
{'name': "Versum Hill: Basketball court alley skateboard",
|
||||
'stage': Stages.VH4,
|
||||
'game_id': "SkateboardDeck5"},
|
||||
{'name': "Versum Hill: Basketball court Robo Post CD",
|
||||
'stage': Stages.VH4,
|
||||
'game_id': "MusicTrack_Operator"},
|
||||
{'name': "Versum Hill: Underground mall billboard graffiti",
|
||||
'stage': Stages.VHO,
|
||||
'game_id': "UnlockGraffiti_grafTex_XL3"},
|
||||
{'name': "Versum Hill: Underground mall vending machine skateboard",
|
||||
'stage': Stages.VHO,
|
||||
'game_id': "SkateboardDeck8"},
|
||||
{'name': "Versum Hill: BMX gate outfit",
|
||||
'stage': Stages.VH1,
|
||||
'game_id': "AngelOutfit3"},
|
||||
{'name': "Versum Hill: Glass floor skates",
|
||||
'stage': Stages.VH2,
|
||||
'game_id': "InlineSkates4"},
|
||||
{'name': "Versum Hill: Basketball court shortcut CD",
|
||||
'stage': Stages.VH4,
|
||||
'game_id': "MusicTrack_GetEnuf"},
|
||||
{'name': "Versum Hill: Rave joins the crew",
|
||||
'stage': Stages.VHO,
|
||||
'game_id': "angel"},
|
||||
{'name': "Versum Hill: Frank joins the crew",
|
||||
'stage': Stages.VH2,
|
||||
'game_id': "frank"},
|
||||
{'name': "Versum Hill: Rietveld joins the crew",
|
||||
'stage': Stages.VH4,
|
||||
'game_id': "jetpackBossPlayer"},
|
||||
{'name': "Versum Hill: Big Polo",
|
||||
'stage': Stages.VH1,
|
||||
'game_id': "PoloBuilding/Mascot_Polo_sit_big"},
|
||||
{'name': "Versum Hill: Trash Polo",
|
||||
'stage': Stages.VH1,
|
||||
'game_id': "TrashCluster (1)/Mascot_Polo_street"},
|
||||
{'name': "Versum Hill: Fruit stand Polo",
|
||||
'stage': Stages.VHO,
|
||||
'game_id': "SecretRoom/Mascot_Polo_street"},
|
||||
|
||||
{'name': "Millennium Square: Center ramp graffiti",
|
||||
'stage': Stages.MS,
|
||||
'game_id': "UnlockGraffiti_grafTex_L6"},
|
||||
{'name': "Millennium Square: Rooftop staircase graffiti",
|
||||
'stage': Stages.MS,
|
||||
'game_id': "UnlockGraffiti_grafTex_M8"},
|
||||
{'name': "Millennium Square: Toilet graffiti",
|
||||
'stage': Stages.MS,
|
||||
'game_id': "UnlockGraffiti_grafTex_XL6"},
|
||||
{'name': "Millennium Square: Trash graffiti",
|
||||
'stage': Stages.MS,
|
||||
'game_id': "UnlockGraffiti_grafTex_M5"},
|
||||
{'name': "Millennium Square: Center tower graffiti",
|
||||
'stage': Stages.MS,
|
||||
'game_id': "UnlockGraffiti_grafTex_M6"},
|
||||
{'name': "Millennium Square: Rooftop billboard graffiti",
|
||||
'stage': Stages.MS,
|
||||
'game_id': "UnlockGraffiti_grafTex_XL7"},
|
||||
{'name': "Millennium Square: Center Robo Post CD",
|
||||
'stage': Stages.MS,
|
||||
'game_id': "MusicTrack_FeelTheFunk"},
|
||||
{'name': "Millennium Square: Parking garage Robo Post CD",
|
||||
'stage': Stages.MS,
|
||||
'game_id': "MusicTrack_Plume"},
|
||||
{'name': "Millennium Square: Mall ledge outfit",
|
||||
'stage': Stages.MS,
|
||||
'game_id': "BlockGuyOutfit3"},
|
||||
{'name': "Millennium Square: Alley rooftop outfit",
|
||||
'stage': Stages.MS,
|
||||
'game_id': "BlockGuyOutfit4"},
|
||||
{'name': "Millennium Square: Alley staircase skateboard",
|
||||
'stage': Stages.MS,
|
||||
'game_id': "SkateboardDeck4"},
|
||||
{'name': "Millennium Square: Secret painting skates",
|
||||
'stage': Stages.MS,
|
||||
'game_id': "InlineSkates2"},
|
||||
{'name': "Millennium Square: Vending machine skates",
|
||||
'stage': Stages.MS,
|
||||
'game_id': "InlineSkates3"},
|
||||
{'name': "Millennium Square: Walkway roof skates",
|
||||
'stage': Stages.MS,
|
||||
'game_id': "InlineSkates5"},
|
||||
{'name': "Millennium Square: Alley ledge skates",
|
||||
'stage': Stages.MS,
|
||||
'game_id': "InlineSkates6"},
|
||||
{'name': "Millennium Square: DJ Cyber joins the crew",
|
||||
'stage': Stages.MS,
|
||||
'game_id': "dj"},
|
||||
{'name': "Millennium Square: Half pipe Polo",
|
||||
'stage': Stages.MS,
|
||||
'game_id': "propsSecretArea/Mascot_Polo_street"},
|
||||
|
||||
{'name': "Brink Terminal: Upside grind challenge reward",
|
||||
'stage': Stages.BT1,
|
||||
'game_id': "UnlockGraffiti_grafTex_M10"},
|
||||
{'name': "Brink Terminal: Manual challenge reward",
|
||||
'stage': Stages.BT1,
|
||||
'game_id': "UnlockGraffiti_grafTex_L8"},
|
||||
{'name': "Brink Terminal: Score challenge reward",
|
||||
'stage': Stages.BT1,
|
||||
'game_id': "UnlockGraffiti_grafTex_M12"},
|
||||
{'name': "Brink Terminal: Under square ledge graffiti",
|
||||
'stage': Stages.BT1,
|
||||
'game_id': "UnlockGraffiti_grafTex_L9"},
|
||||
{'name': "Brink Terminal: Bus graffiti",
|
||||
'stage': Stages.BT1,
|
||||
'game_id': "UnlockGraffiti_grafTex_XL9"},
|
||||
{'name': "Brink Terminal: Under square Robo Post graffiti",
|
||||
'stage': Stages.BT1,
|
||||
'game_id': "UnlockGraffiti_grafTex_M9"},
|
||||
{'name': "Brink Terminal: BMX gate graffiti",
|
||||
'stage': Stages.BT1,
|
||||
'game_id': "UnlockGraffiti_grafTex_L7"},
|
||||
{'name': "Brink Terminal: Square tower CD",
|
||||
'stage': Stages.BT1,
|
||||
'game_id': "MusicTrack_Chapter1Mixtape"},
|
||||
{'name': "Brink Terminal: Trash CD",
|
||||
'stage': Stages.BT1,
|
||||
'game_id': "MusicTrack_HairDunNailsDun"},
|
||||
{'name': "Brink Terminal: Shop roof outfit",
|
||||
'stage': Stages.BT1,
|
||||
'game_id': "AngelOutfit4"},
|
||||
{'name': "Brink Terminal: Underground glass skates",
|
||||
'stage': Stages.BTO1,
|
||||
'game_id': "InlineSkates8"},
|
||||
{'name': "Brink Terminal: Glass roof skates",
|
||||
'stage': Stages.BT1,
|
||||
'game_id': "InlineSkates10"},
|
||||
{'name': "Brink Terminal: Mesh's skateboard",
|
||||
'stage': Stages.BTO2,
|
||||
'game_id': "SkateboardDeck10"}, # double check this one
|
||||
{'name': "Brink Terminal: Underground ramp skates",
|
||||
'stage': Stages.BTO1,
|
||||
'game_id': "InlineSkates7"},
|
||||
{'name': "Brink Terminal: Rooftop halfpipe graffiti",
|
||||
'stage': Stages.BT3,
|
||||
'game_id': "UnlockGraffiti_grafTex_M11"},
|
||||
{'name': "Brink Terminal: Wire grind CD",
|
||||
'stage': Stages.BT2,
|
||||
'game_id': "MusicTrack_Watchyaback"},
|
||||
{'name': "Brink Terminal: Rooftop glass CD",
|
||||
'stage': Stages.BT3,
|
||||
'game_id': "MusicTrack_Refuse"},
|
||||
{'name': "Brink Terminal: Tower core outfit",
|
||||
'stage': Stages.BT3,
|
||||
'game_id': "SpacegirlOutfit4"},
|
||||
{'name': "Brink Terminal: High rooftop outfit",
|
||||
'stage': Stages.BT3,
|
||||
'game_id': "WideKidOutfit3"},
|
||||
{'name': "Brink Terminal: Ocean platform CD",
|
||||
'stage': Stages.BTO2,
|
||||
'game_id': "MusicTrack_ScrapedOnTheWayOut"},
|
||||
{'name': "Brink Terminal: End of dock CD",
|
||||
'stage': Stages.BTO2,
|
||||
'game_id': "MusicTrack_Hwbouths"},
|
||||
{'name': "Brink Terminal: Dock Robo Post outfit",
|
||||
'stage': Stages.BTO2,
|
||||
'game_id': "WideKidOutfit4"},
|
||||
{'name': "Brink Terminal: Control room skates",
|
||||
'stage': Stages.BTO2,
|
||||
'game_id': "InlineSkates9"},
|
||||
{'name': "Brink Terminal: Mesh joins the crew",
|
||||
'stage': Stages.BTO2,
|
||||
'game_id': "wideKid"},
|
||||
{'name': "Brink Terminal: Eclipse joins the crew",
|
||||
'stage': Stages.BT1,
|
||||
'game_id': "medusa"},
|
||||
{'name': "Brink Terminal: Behind glass Polo",
|
||||
'stage': Stages.BT1,
|
||||
'game_id': "KingFood (Bear)/Mascot_Polo_street"},
|
||||
|
||||
{'name': "Millennium Mall: Warehouse pallet graffiti",
|
||||
'stage': Stages.MM1,
|
||||
'game_id': "UnlockGraffiti_grafTex_L5"},
|
||||
{'name': "Millennium Mall: Wall alcove graffiti",
|
||||
'stage': Stages.MM1,
|
||||
'game_id': "UnlockGraffiti_grafTex_XL10"},
|
||||
{'name': "Millennium Mall: Maintenance shaft CD",
|
||||
'stage': Stages.MM1,
|
||||
'game_id': "MusicTrack_MissingBreak"},
|
||||
{'name': "Millennium Mall: Glass cylinder CD",
|
||||
'stage': Stages.MM1,
|
||||
'game_id': "MusicTrack_DAPEOPLE"},
|
||||
{'name': "Millennium Mall: Lower Robo Post outfit",
|
||||
'stage': Stages.MM1,
|
||||
'game_id': "SpacegirlOutfit3"},
|
||||
{'name': "Millennium Mall: Atrium vending machine graffiti",
|
||||
'stage': Stages.MM2,
|
||||
'game_id': "UnlockGraffiti_grafTex_M15"},
|
||||
{'name': "Millennium Mall: Trick challenge reward",
|
||||
'stage': Stages.MM2,
|
||||
'game_id': "UnlockGraffiti_grafTex_XL8"},
|
||||
{'name': "Millennium Mall: Slide challenge reward",
|
||||
'stage': Stages.MM2,
|
||||
'game_id': "UnlockGraffiti_grafTex_L10"},
|
||||
{'name': "Millennium Mall: Fish challenge reward",
|
||||
'stage': Stages.MM2,
|
||||
'game_id': "UnlockGraffiti_grafTex_L12"},
|
||||
{'name': "Millennium Mall: Score challenge reward",
|
||||
'stage': Stages.MM2,
|
||||
'game_id': "UnlockGraffiti_grafTex_XL11"},
|
||||
{'name': "Millennium Mall: Atrium top floor Robo Post CD",
|
||||
'stage': Stages.MM2,
|
||||
'game_id': "MusicTrack_TwoDaysOff"},
|
||||
{'name': "Millennium Mall: Atrium top floor floating CD",
|
||||
'stage': Stages.MM2,
|
||||
'game_id': "MusicTrack_Spectres"},
|
||||
{'name': "Millennium Mall: Atrium top floor BMX",
|
||||
'stage': Stages.MM2,
|
||||
'game_id': "BMXBike2"},
|
||||
{'name': "Millennium Mall: Theater entrance BMX",
|
||||
'stage': Stages.MM2,
|
||||
'game_id': "BMXBike3"},
|
||||
{'name': "Millennium Mall: Atrium BMX gate BMX",
|
||||
'stage': Stages.MM2,
|
||||
'game_id': "BMXBike5"},
|
||||
{'name': "Millennium Mall: Upside down rail outfit",
|
||||
'stage': Stages.MM2,
|
||||
'game_id': "BunGirlOutfit3"},
|
||||
{'name': "Millennium Mall: Theater stage corner graffiti",
|
||||
'stage': Stages.MM3,
|
||||
'game_id': "UnlockGraffiti_grafTex_L15"},
|
||||
{'name': "Millennium Mall: Theater hanging billboards graffiti",
|
||||
'stage': Stages.MM3,
|
||||
'game_id': "UnlockGraffiti_grafTex_XL15"},
|
||||
{'name': "Millennium Mall: Theater garage graffiti",
|
||||
'stage': Stages.MM3,
|
||||
'game_id': "UnlockGraffiti_grafTex_M16"},
|
||||
{'name': "Millennium Mall: Theater maintenance CD",
|
||||
'stage': Stages.MM3,
|
||||
'game_id': "MusicTrack_WannaKno"},
|
||||
{'name': "Millennium Mall: Race track Robo Post CD",
|
||||
'stage': Stages.MMO2,
|
||||
'game_id': "MusicTrack_StateOfMind"},
|
||||
{'name': "Millennium Mall: Hanging lights CD",
|
||||
'stage': Stages.MMO1,
|
||||
'game_id': "MusicTrack_Chapter2Mixtape"},
|
||||
{'name': "Millennium Mall: Shine joins the crew",
|
||||
'stage': Stages.MM3,
|
||||
'game_id': "bunGirl"},
|
||||
{'name': "Millennium Mall: DOT.EXE joins the crew",
|
||||
'stage': Stages.MM2,
|
||||
'game_id': "eightBall"},
|
||||
|
||||
{'name': "Pyramid Island: Lower rooftop graffiti",
|
||||
'stage': Stages.PI1,
|
||||
'game_id': "UnlockGraffiti_grafTex_L18"},
|
||||
{'name': "Pyramid Island: Polo graffiti",
|
||||
'stage': Stages.PI1,
|
||||
'game_id': "UnlockGraffiti_grafTex_L16"},
|
||||
{'name': "Pyramid Island: Above entrance graffiti",
|
||||
'stage': Stages.PI1,
|
||||
'game_id': "UnlockGraffiti_grafTex_XL16"},
|
||||
{'name': "Pyramid Island: BMX gate BMX",
|
||||
'stage': Stages.PI1,
|
||||
'game_id': "BMXBike6"},
|
||||
{'name': "Pyramid Island: Quarter pipe rooftop graffiti",
|
||||
'stage': Stages.PI2,
|
||||
'game_id': "UnlockGraffiti_grafTex_M17"},
|
||||
{'name': "Pyramid Island: Supply port Robo Post CD",
|
||||
'stage': Stages.PI2,
|
||||
'game_id': "MusicTrack_Trinitron"},
|
||||
{'name': "Pyramid Island: Above gate ledge CD",
|
||||
'stage': Stages.PI2,
|
||||
'game_id': "MusicTrack_Agua"},
|
||||
{'name': "Pyramid Island: Smoke hole BMX",
|
||||
'stage': Stages.PI2,
|
||||
'game_id': "BMXBike8"},
|
||||
{'name': "Pyramid Island: Above gate rail outfit",
|
||||
'stage': Stages.PI2,
|
||||
'game_id': "VinylOutfit3"},
|
||||
{'name': "Pyramid Island: Rail loop outfit",
|
||||
'stage': Stages.PI2,
|
||||
'game_id': "BunGirlOutfit4"},
|
||||
{'name': "Pyramid Island: Score challenge reward",
|
||||
'stage': Stages.PI2,
|
||||
'game_id': "UnlockGraffiti_grafTex_XL2"},
|
||||
{'name': "Pyramid Island: Score challenge 2 reward",
|
||||
'stage': Stages.PI2,
|
||||
'game_id': "UnlockGraffiti_grafTex_L13"},
|
||||
{'name': "Pyramid Island: Quarter pipe challenge reward",
|
||||
'stage': Stages.PI2,
|
||||
'game_id': "UnlockGraffiti_grafTex_XL12"},
|
||||
{'name': "Pyramid Island: Wind turbines CD",
|
||||
'stage': Stages.PI3,
|
||||
'game_id': "MusicTrack_YouCanSayHi"},
|
||||
{'name': "Pyramid Island: Shortcut glass CD",
|
||||
'stage': Stages.PI3,
|
||||
'game_id': "MusicTrack_Chromebies"},
|
||||
{'name': "Pyramid Island: Turret jump CD",
|
||||
'stage': Stages.PI3,
|
||||
'game_id': "MusicTrack_ChuckinUp"},
|
||||
{'name': "Pyramid Island: Helipad BMX",
|
||||
'stage': Stages.PI3,
|
||||
'game_id': "BMXBike7"},
|
||||
{'name': "Pyramid Island: Pipe outfit",
|
||||
'stage': Stages.PI3,
|
||||
'game_id': "PufferGirlOutfit3"},
|
||||
{'name': "Pyramid Island: Trash outfit",
|
||||
'stage': Stages.PI3,
|
||||
'game_id': "PufferGirlOutfit4"},
|
||||
{'name': "Pyramid Island: Pyramid top CD",
|
||||
'stage': Stages.PI4,
|
||||
'game_id': "MusicTrack_BigCityLife"},
|
||||
{'name': "Pyramid Island: Pyramid top Robo Post CD",
|
||||
'stage': Stages.PI4,
|
||||
'game_id': "MusicTrack_Chapter3Mixtape"},
|
||||
{'name': "Pyramid Island: Maze outfit",
|
||||
'stage': Stages.PIO,
|
||||
'game_id': "VinylOutfit4"},
|
||||
{'name': "Pyramid Island: Rise joins the crew",
|
||||
'stage': Stages.PI4,
|
||||
'game_id': "pufferGirl"},
|
||||
{'name': "Pyramid Island: Devil Theory joins the crew",
|
||||
'stage': Stages.PI3,
|
||||
'game_id': "boarder"},
|
||||
{'name': "Pyramid Island: Polo pile 1",
|
||||
'stage': Stages.PI1,
|
||||
'game_id': "Secret01Trash/Mascot_Polo_sit_big_wave"},
|
||||
{'name': "Pyramid Island: Polo pile 2",
|
||||
'stage': Stages.PI1,
|
||||
'game_id': "Secret01Trash/Mascot_Polo_sit_big_wave (1)"},
|
||||
{'name': "Pyramid Island: Polo pile 3",
|
||||
'stage': Stages.PI1,
|
||||
'game_id': "Secret01Trash/Mascot_Polo_sit_big_wave (2)"},
|
||||
{'name': "Pyramid Island: Polo pile 4",
|
||||
'stage': Stages.PI1,
|
||||
'game_id': "Secret01Trash/Mascot_Polo_sit_big_wave (3)"},
|
||||
{'name': "Pyramid Island: Maze glass Polo",
|
||||
'stage': Stages.PIO,
|
||||
'game_id': "Start/Mascot_Polo_sit_big (1)"},
|
||||
{'name': "Pyramid Island: Maze classroom Polo",
|
||||
'stage': Stages.PIO,
|
||||
'game_id': "PeteRoom/Mascot_Polo_sit_big_wave (1)"},
|
||||
{'name': "Pyramid Island: Maze vent Polo",
|
||||
'stage': Stages.PIO,
|
||||
'game_id': "CheckerRoom/Mascot_Polo_street"},
|
||||
{'name': "Pyramid Island: Big maze Polo",
|
||||
'stage': Stages.PIO,
|
||||
'game_id': "YellowPoloRoom/Mascot_Polo_sit_big"},
|
||||
{'name': "Pyramid Island: Maze desk Polo",
|
||||
'stage': Stages.PIO,
|
||||
'game_id': "PoloRoom/Mascot_Polo_sit_big"},
|
||||
{'name': "Pyramid Island: Maze forklift Polo",
|
||||
'stage': Stages.PIO,
|
||||
'game_id': "ForkliftRoom/Mascot_Polo_sit_big_wave"},
|
||||
|
||||
{'name': "Mataan: Robo Post graffiti",
|
||||
'stage': Stages.MA1,
|
||||
'game_id': "UnlockGraffiti_grafTex_XL17"},
|
||||
{'name': "Mataan: Secret ledge BMX",
|
||||
'stage': Stages.MA1,
|
||||
'game_id': "BMXBike9"},
|
||||
{'name': "Mataan: Highway rooftop BMX",
|
||||
'stage': Stages.MA1,
|
||||
'game_id': "BMXBike10"},
|
||||
{'name': "Mataan: Trash CD",
|
||||
'stage': Stages.MA2,
|
||||
'game_id': "MusicTrack_JackDaFunk"},
|
||||
{'name': "Mataan: Half pipe CD",
|
||||
'stage': Stages.MA2,
|
||||
'game_id': "MusicTrack_FunkExpress"},
|
||||
{'name': "Mataan: Across bull horns graffiti",
|
||||
'stage': Stages.MA2,
|
||||
'game_id': "UnlockGraffiti_grafTex_L17"},
|
||||
{'name': "Mataan: Small rooftop graffiti",
|
||||
'stage': Stages.MA2,
|
||||
'game_id': "UnlockGraffiti_grafTex_M18"},
|
||||
{'name': "Mataan: Trash graffiti",
|
||||
'stage': Stages.MA2,
|
||||
'game_id': "UnlockGraffiti_grafTex_XL5"},
|
||||
{'name': "Mataan: Deep city Robo Post CD",
|
||||
'stage': Stages.MA3,
|
||||
'game_id': "MusicTrack_LastHoorah"},
|
||||
{'name': "Mataan: Deep city tower CD",
|
||||
'stage': Stages.MA3,
|
||||
'game_id': "MusicTrack_Chapter4Mixtape"},
|
||||
{'name': "Mataan: Race challenge reward",
|
||||
'stage': Stages.MA3,
|
||||
'game_id': "UnlockGraffiti_grafTex_M14"},
|
||||
{'name': "Mataan: Wallrunning challenge reward",
|
||||
'stage': Stages.MA3,
|
||||
'game_id': "UnlockGraffiti_grafTex_L14"},
|
||||
{'name': "Mataan: Score challenge reward",
|
||||
'stage': Stages.MA3,
|
||||
'game_id': "UnlockGraffiti_grafTex_XL13"},
|
||||
{'name': "Mataan: Deep city vent jump BMX",
|
||||
'stage': Stages.MA3,
|
||||
'game_id': "BMXBike4"},
|
||||
{'name': "Mataan: Deep city side wires outfit",
|
||||
'stage': Stages.MA3,
|
||||
'game_id': "DummyOutfit3"},
|
||||
{'name': "Mataan: Deep city center island outfit",
|
||||
'stage': Stages.MA3,
|
||||
'game_id': "DummyOutfit4"},
|
||||
{'name': "Mataan: Red light rail graffiti",
|
||||
'stage': Stages.MAO,
|
||||
'game_id': "UnlockGraffiti_grafTex_XL18"},
|
||||
{'name': "Mataan: Red light side alley outfit",
|
||||
'stage': Stages.MAO,
|
||||
'game_id': "RingDudeOutfit3"},
|
||||
{'name': "Mataan: Statue hand outfit",
|
||||
'stage': Stages.MA4,
|
||||
'game_id': "RingDudeOutfit4"},
|
||||
{'name': "Mataan: Crane CD",
|
||||
'stage': Stages.MA5,
|
||||
'game_id': "MusicTrack_InThePocket"},
|
||||
{'name': "Mataan: Elephant tower glass outfit",
|
||||
'stage': Stages.MA5,
|
||||
'game_id': "LegendFaceOutfit3"},
|
||||
{'name': "Mataan: Helipad outfit",
|
||||
'stage': Stages.MA5,
|
||||
'game_id': "LegendFaceOutfit4"},
|
||||
{'name': "Mataan: Vending machine CD",
|
||||
'stage': Stages.MA5,
|
||||
'game_id': "MusicTrack_Iridium"},
|
||||
{'name': "Mataan: Coil joins the crew",
|
||||
'stage': Stages.MA5,
|
||||
'game_id': "ringdude"},
|
||||
{'name': "Mataan: Flesh Prince joins the crew",
|
||||
'stage': Stages.MA5,
|
||||
'game_id': "prince"},
|
||||
{'name': "Mataan: Futurism joins the crew",
|
||||
'stage': Stages.MA5,
|
||||
'game_id': "futureGirl"},
|
||||
{'name': "Mataan: Trash Polo",
|
||||
'stage': Stages.MA2,
|
||||
'game_id': "PropsMallArea/Mascot_Polo_street"},
|
||||
{'name': "Mataan: Shopping Polo",
|
||||
'stage': Stages.MA5,
|
||||
'game_id': "propsMarket/Mascot_Polo_street"},
|
||||
|
||||
{'name': "Tagged 5 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf5"},
|
||||
{'name': "Tagged 10 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf10"},
|
||||
{'name': "Tagged 15 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf15"},
|
||||
{'name': "Tagged 20 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf20"},
|
||||
{'name': "Tagged 25 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf25"},
|
||||
{'name': "Tagged 30 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf30"},
|
||||
{'name': "Tagged 35 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf35"},
|
||||
{'name': "Tagged 40 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf40"},
|
||||
{'name': "Tagged 45 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf45"},
|
||||
{'name': "Tagged 50 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf50"},
|
||||
{'name': "Tagged 55 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf55"},
|
||||
{'name': "Tagged 60 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf60"},
|
||||
{'name': "Tagged 65 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf65"},
|
||||
{'name': "Tagged 70 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf70"},
|
||||
{'name': "Tagged 75 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf75"},
|
||||
{'name': "Tagged 80 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf80"},
|
||||
{'name': "Tagged 85 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf85"},
|
||||
{'name': "Tagged 90 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf90"},
|
||||
{'name': "Tagged 95 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf95"},
|
||||
{'name': "Tagged 100 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf100"},
|
||||
{'name': "Tagged 105 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf105"},
|
||||
{'name': "Tagged 110 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf110"},
|
||||
{'name': "Tagged 115 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf115"},
|
||||
{'name': "Tagged 120 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf120"},
|
||||
{'name': "Tagged 125 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf125"},
|
||||
{'name': "Tagged 130 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf130"},
|
||||
{'name': "Tagged 135 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf135"},
|
||||
{'name': "Tagged 140 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf140"},
|
||||
{'name': "Tagged 145 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf145"},
|
||||
{'name': "Tagged 150 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf150"},
|
||||
{'name': "Tagged 155 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf155"},
|
||||
{'name': "Tagged 160 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf160"},
|
||||
{'name': "Tagged 165 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf165"},
|
||||
{'name': "Tagged 170 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf170"},
|
||||
{'name': "Tagged 175 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf175"},
|
||||
{'name': "Tagged 180 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf180"},
|
||||
{'name': "Tagged 185 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf185"},
|
||||
{'name': "Tagged 190 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf190"},
|
||||
{'name': "Tagged 195 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf195"},
|
||||
{'name': "Tagged 200 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf200"},
|
||||
{'name': "Tagged 205 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf205"},
|
||||
{'name': "Tagged 210 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf210"},
|
||||
{'name': "Tagged 215 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf215"},
|
||||
{'name': "Tagged 220 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf220"},
|
||||
{'name': "Tagged 225 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf225"},
|
||||
{'name': "Tagged 230 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf230"},
|
||||
{'name': "Tagged 235 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf235"},
|
||||
{'name': "Tagged 240 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf240"},
|
||||
{'name': "Tagged 245 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf245"},
|
||||
{'name': "Tagged 250 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf250"},
|
||||
{'name': "Tagged 255 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf255"},
|
||||
{'name': "Tagged 260 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf260"},
|
||||
{'name': "Tagged 265 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf265"},
|
||||
{'name': "Tagged 270 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf270"},
|
||||
{'name': "Tagged 275 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf275"},
|
||||
{'name': "Tagged 280 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf280"},
|
||||
{'name': "Tagged 285 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf285"},
|
||||
{'name': "Tagged 290 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf290"},
|
||||
{'name': "Tagged 295 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf295"},
|
||||
{'name': "Tagged 300 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf300"},
|
||||
{'name': "Tagged 305 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf305"},
|
||||
{'name': "Tagged 310 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf310"},
|
||||
{'name': "Tagged 315 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf315"},
|
||||
{'name': "Tagged 320 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf320"},
|
||||
{'name': "Tagged 325 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf325"},
|
||||
{'name': "Tagged 330 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf330"},
|
||||
{'name': "Tagged 335 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf335"},
|
||||
{'name': "Tagged 340 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf340"},
|
||||
{'name': "Tagged 345 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf345"},
|
||||
{'name': "Tagged 350 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf350"},
|
||||
{'name': "Tagged 355 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf355"},
|
||||
{'name': "Tagged 360 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf360"},
|
||||
{'name': "Tagged 365 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf365"},
|
||||
{'name': "Tagged 370 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf370"},
|
||||
{'name': "Tagged 375 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf375"},
|
||||
{'name': "Tagged 380 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf380"},
|
||||
{'name': "Tagged 385 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf385"},
|
||||
{'name': "Tagged 389 Graffiti Spots",
|
||||
'stage': Stages.Misc,
|
||||
'game_id': "graf379"},
|
||||
]
|
||||
|
||||
|
||||
event_table: List[EventDict] = [
|
||||
{'name': "Versum Hill: Complete Chapter 1",
|
||||
'stage': Stages.VH4,
|
||||
'item': "Chapter Completed"},
|
||||
{'name': "Brink Terminal: Complete Chapter 2",
|
||||
'stage': Stages.BT3,
|
||||
'item': "Chapter Completed"},
|
||||
{'name': "Millennium Mall: Complete Chapter 3",
|
||||
'stage': Stages.MM3,
|
||||
'item': "Chapter Completed"},
|
||||
{'name': "Pyramid Island: Complete Chapter 4",
|
||||
'stage': Stages.PI3,
|
||||
'item': "Chapter Completed"},
|
||||
{'name': "Defeat Faux",
|
||||
'stage': Stages.MA5,
|
||||
'item': "Victory"},
|
||||
]
|
||||
162
worlds/bomb_rush_cyberfunk/Options.py
Normal file
162
worlds/bomb_rush_cyberfunk/Options.py
Normal file
@@ -0,0 +1,162 @@
|
||||
from dataclasses import dataclass
|
||||
from Options import Choice, Toggle, DefaultOnToggle, Range, DeathLink, PerGameCommonOptions
|
||||
import typing
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from random import Random
|
||||
else:
|
||||
Random = typing.Any
|
||||
|
||||
|
||||
class Logic(Choice):
|
||||
"""Choose the logic used by the randomizer."""
|
||||
display_name = "Logic"
|
||||
option_glitchless = 0
|
||||
option_glitched = 1
|
||||
default = 0
|
||||
|
||||
|
||||
class SkipIntro(DefaultOnToggle):
|
||||
"""Skips escaping the police station.
|
||||
Graffiti spots tagged during the intro will not unlock items."""
|
||||
display_name = "Skip Intro"
|
||||
|
||||
|
||||
class SkipDreams(Toggle):
|
||||
"""Skips the dream sequences at the end of each chapter.
|
||||
This can be changed later in the options menu inside the Archipelago phone app."""
|
||||
display_name = "Skip Dreams"
|
||||
|
||||
|
||||
class SkipHands(Toggle):
|
||||
"""Skips spraying the lion statue hands after the dream in Chapter 5."""
|
||||
display_name = "Skip Statue Hands"
|
||||
|
||||
|
||||
class TotalRep(Range):
|
||||
"""Change the total amount of REP in your world.
|
||||
At least 960 REP is needed to finish the game.
|
||||
Will be rounded to the nearest number divisible by 8."""
|
||||
display_name = "Total REP"
|
||||
range_start = 1000
|
||||
range_end = 2000
|
||||
default = 1400
|
||||
|
||||
def round_to_nearest_step(self):
|
||||
rem: int = self.value % 8
|
||||
if rem >= 5:
|
||||
self.value = self.value - rem + 8
|
||||
else:
|
||||
self.value = self.value - rem
|
||||
|
||||
def get_rep_item_counts(self, random_source: Random, location_count: int) -> typing.List[int]:
|
||||
def increment_item(item: int) -> int:
|
||||
if item >= 32:
|
||||
item = 48
|
||||
else:
|
||||
item += 8
|
||||
return item
|
||||
|
||||
items = [8]*location_count
|
||||
while sum(items) < self.value:
|
||||
index = random_source.randint(0, location_count-1)
|
||||
while items[index] >= 48:
|
||||
index = random_source.randint(0, location_count-1)
|
||||
items[index] = increment_item(items[index])
|
||||
|
||||
while sum(items) > self.value:
|
||||
index = random_source.randint(0, location_count-1)
|
||||
while not (items[index] == 16 or items[index] == 24 or items[index] == 32):
|
||||
index = random_source.randint(0, location_count-1)
|
||||
items[index] -= 8
|
||||
|
||||
return [items.count(8), items.count(16), items.count(24), items.count(32), items.count(48)]
|
||||
|
||||
|
||||
class EndingREP(Toggle):
|
||||
"""Changes the final boss to require 1000 REP instead of 960 REP to start."""
|
||||
display_name = "Extra REP Required"
|
||||
|
||||
|
||||
class StartStyle(Choice):
|
||||
"""Choose which movestyle to start with."""
|
||||
display_name = "Starting Movestyle"
|
||||
option_skateboard = 2
|
||||
option_inline_skates = 3
|
||||
option_bmx = 1
|
||||
default = 2
|
||||
|
||||
|
||||
class LimitedGraffiti(Toggle):
|
||||
"""Each graffiti design can only be used a limited number of times before being removed from your inventory.
|
||||
In some cases, such as completing a dream, using graffiti to defeat enemies, or spraying over your own graffiti,
|
||||
uses will not be counted.
|
||||
If enabled, doing graffiti is disabled during crew battles, to prevent softlocking."""
|
||||
display_name = "Limited Graffiti"
|
||||
|
||||
|
||||
class SGraffiti(Choice):
|
||||
"""Choose if small graffiti should be separate, meaning that you will need to switch characters every time you run
|
||||
out, or combined, meaning that unlocking new characters will add 5 uses that any character can use.
|
||||
Has no effect if Limited Graffiti is disabled."""
|
||||
display_name = "Small Graffiti Uses"
|
||||
option_separate = 0
|
||||
option_combined = 1
|
||||
default = 0
|
||||
|
||||
|
||||
class JunkPhotos(Toggle):
|
||||
"""Skip taking pictures of Polo for items."""
|
||||
display_name = "Skip Polo Photos"
|
||||
|
||||
|
||||
class DontSavePhotos(Toggle):
|
||||
"""Photos taken with the Camera app will not be saved.
|
||||
This can be changed later in the options menu inside the Archipelago phone app."""
|
||||
display_name = "Don't Save Photos"
|
||||
|
||||
|
||||
class ScoreDifficulty(Choice):
|
||||
"""Alters the score required to win score challenges and crew battles.
|
||||
This can be changed later in the options menu inside the Archipelago phone app."""
|
||||
display_name = "Score Difficulty"
|
||||
option_normal = 0
|
||||
option_medium = 1
|
||||
option_hard = 2
|
||||
option_very_hard = 3
|
||||
option_extreme = 4
|
||||
default = 0
|
||||
|
||||
|
||||
class DamageMultiplier(Range):
|
||||
"""Multiplies all damage received.
|
||||
At 3x, most damage will OHKO the player, including falling into pits.
|
||||
At 6x, all damage will OHKO the player.
|
||||
This can be changed later in the options menu inside the Archipelago phone app."""
|
||||
display_name = "Damage Multiplier"
|
||||
range_start = 1
|
||||
range_end = 6
|
||||
default = 1
|
||||
|
||||
|
||||
class BRCDeathLink(DeathLink):
|
||||
"""When you die, everyone dies. The reverse is also true.
|
||||
This can be changed later in the options menu inside the Archipelago phone app."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class BombRushCyberfunkOptions(PerGameCommonOptions):
|
||||
logic: Logic
|
||||
skip_intro: SkipIntro
|
||||
skip_dreams: SkipDreams
|
||||
skip_statue_hands: SkipHands
|
||||
total_rep: TotalRep
|
||||
extra_rep_required: EndingREP
|
||||
starting_movestyle: StartStyle
|
||||
limited_graffiti: LimitedGraffiti
|
||||
small_graffiti_uses: SGraffiti
|
||||
skip_polo_photos: JunkPhotos
|
||||
dont_save_photos: DontSavePhotos
|
||||
score_difficulty: ScoreDifficulty
|
||||
damage_multiplier: DamageMultiplier
|
||||
death_link: BRCDeathLink
|
||||
103
worlds/bomb_rush_cyberfunk/Regions.py
Normal file
103
worlds/bomb_rush_cyberfunk/Regions.py
Normal file
@@ -0,0 +1,103 @@
|
||||
from typing import Dict
|
||||
|
||||
|
||||
class Stages:
|
||||
Misc = "Misc"
|
||||
H = "Hideout"
|
||||
VH1 = "Versum Hill"
|
||||
VH2 = "Versum Hill - After Roadblock"
|
||||
VHO = "Versum Hill - Underground Mall"
|
||||
VH3 = "Versum Hill - Side Street"
|
||||
VH4 = "Versum Hill - Basketball Court"
|
||||
MS = "Millennium Square"
|
||||
BT1 = "Brink Terminal"
|
||||
BTO1 = "Brink Terminal - Underground"
|
||||
BTO2 = "Brink Terminal - Dock"
|
||||
BT2 = "Brink Terminal - Planet Plaza"
|
||||
BT3 = "Brink Terminal - Tower"
|
||||
MM1 = "Millennium Mall"
|
||||
MMO1 = "Millennium Mall - Hanging Lights"
|
||||
MM2 = "Millennium Mall - Atrium"
|
||||
MMO2 = "Millennium Mall - Race Track"
|
||||
MM3 = "Millennium Mall - Theater"
|
||||
PI1 = "Pyramid Island - Base"
|
||||
PI2 = "Pyramid Island - After Gate"
|
||||
PIO = "Pyramid Island - Maze"
|
||||
PI3 = "Pyramid Island - Upper Areas"
|
||||
PI4 = "Pyramid Island - Top"
|
||||
MA1 = "Mataan - Streets"
|
||||
MA2 = "Mataan - After Smoke Wall"
|
||||
MA3 = "Mataan - Deep City"
|
||||
MAO = "Mataan - Red Light District"
|
||||
MA4 = "Mataan - Lion Statue"
|
||||
MA5 = "Mataan - Skyscrapers"
|
||||
|
||||
|
||||
region_exits: Dict[str, str] = {
|
||||
Stages.Misc: [Stages.H],
|
||||
Stages.H: [Stages.Misc,
|
||||
Stages.VH1,
|
||||
Stages.MS,
|
||||
Stages.MA1],
|
||||
Stages.VH1: [Stages.H,
|
||||
Stages.VH2],
|
||||
Stages.VH2: [Stages.H,
|
||||
Stages.VH1,
|
||||
Stages.MS,
|
||||
Stages.VHO,
|
||||
Stages.VH3,
|
||||
Stages.VH4],
|
||||
Stages.VHO: [Stages.VH2],
|
||||
Stages.VH3: [Stages.VH2],
|
||||
Stages.VH4: [Stages.VH2,
|
||||
Stages.VH1],
|
||||
Stages.MS: [Stages.VH2,
|
||||
Stages.BT1,
|
||||
Stages.MM1,
|
||||
Stages.PI1,
|
||||
Stages.MA1],
|
||||
Stages.BT1: [Stages.MS,
|
||||
Stages.BTO1,
|
||||
Stages.BTO2,
|
||||
Stages.BT2],
|
||||
Stages.BTO1: [Stages.BT1],
|
||||
Stages.BTO2: [Stages.BT1],
|
||||
Stages.BT2: [Stages.BT1,
|
||||
Stages.BT3],
|
||||
Stages.BT3: [Stages.BT1,
|
||||
Stages.BT2],
|
||||
Stages.MM1: [Stages.MS,
|
||||
Stages.MMO1,
|
||||
Stages.MM2],
|
||||
Stages.MMO1: [Stages.MM1],
|
||||
Stages.MM2: [Stages.MM1,
|
||||
Stages.MMO2,
|
||||
Stages.MM3],
|
||||
Stages.MMO2: [Stages.MM2],
|
||||
Stages.MM3: [Stages.MM2,
|
||||
Stages.MM1],
|
||||
Stages.PI1: [Stages.MS,
|
||||
Stages.PI2],
|
||||
Stages.PI2: [Stages.PI1,
|
||||
Stages.PIO,
|
||||
Stages.PI3],
|
||||
Stages.PIO: [Stages.PI2],
|
||||
Stages.PI3: [Stages.PI1,
|
||||
Stages.PI2,
|
||||
Stages.PI4],
|
||||
Stages.PI4: [Stages.PI1,
|
||||
Stages.PI2,
|
||||
Stages.PI3],
|
||||
Stages.MA1: [Stages.H,
|
||||
Stages.MS,
|
||||
Stages.MA2],
|
||||
Stages.MA2: [Stages.MA1,
|
||||
Stages.MA3],
|
||||
Stages.MA3: [Stages.MA2,
|
||||
Stages.MAO,
|
||||
Stages.MA4],
|
||||
Stages.MAO: [Stages.MA3],
|
||||
Stages.MA4: [Stages.MA3,
|
||||
Stages.MA5],
|
||||
Stages.MA5: [Stages.MA1]
|
||||
}
|
||||
1039
worlds/bomb_rush_cyberfunk/Rules.py
Normal file
1039
worlds/bomb_rush_cyberfunk/Rules.py
Normal file
File diff suppressed because it is too large
Load Diff
203
worlds/bomb_rush_cyberfunk/__init__.py
Normal file
203
worlds/bomb_rush_cyberfunk/__init__.py
Normal file
@@ -0,0 +1,203 @@
|
||||
from typing import Any, Dict
|
||||
from BaseClasses import MultiWorld, Region, Location, Item, Tutorial, ItemClassification, CollectionState
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from .Items import base_id, item_table, group_table, BRCType
|
||||
from .Locations import location_table, event_table
|
||||
from .Regions import region_exits
|
||||
from .Rules import rules
|
||||
from .Options import BombRushCyberfunkOptions, StartStyle
|
||||
|
||||
|
||||
class BombRushCyberfunkWeb(WebWorld):
|
||||
theme = "ocean"
|
||||
tutorials = [Tutorial(
|
||||
"Multiworld Setup Guide",
|
||||
"A guide to setting up Bomb Rush Cyberfunk randomizer and connecting to an Archipelago Multiworld",
|
||||
"English",
|
||||
"setup_en.md",
|
||||
"setup/en",
|
||||
["TRPG"]
|
||||
)]
|
||||
|
||||
|
||||
class BombRushCyberfunkWorld(World):
|
||||
"""Bomb Rush Cyberfunk is 1 second per second of advanced funkstyle. Battle rival crews and dispatch militarized
|
||||
police to conquer the five boroughs of New Amsterdam. Become All City."""
|
||||
|
||||
game = "Bomb Rush Cyberfunk"
|
||||
web = BombRushCyberfunkWeb()
|
||||
|
||||
item_name_to_id = {item["name"]: (base_id + index) for index, item in enumerate(item_table)}
|
||||
item_name_to_type = {item["name"]: item["type"] for item in item_table}
|
||||
location_name_to_id = {loc["name"]: (base_id + index) for index, loc in enumerate(location_table)}
|
||||
|
||||
item_name_groups = group_table
|
||||
options_dataclass = BombRushCyberfunkOptions
|
||||
options: BombRushCyberfunkOptions
|
||||
|
||||
def __init__(self, multiworld: MultiWorld, player: int):
|
||||
super(BombRushCyberfunkWorld, self).__init__(multiworld, player)
|
||||
self.item_classification: Dict[BRCType, ItemClassification] = {
|
||||
BRCType.Music: ItemClassification.filler,
|
||||
BRCType.GraffitiM: ItemClassification.progression,
|
||||
BRCType.GraffitiL: ItemClassification.progression,
|
||||
BRCType.GraffitiXL: ItemClassification.progression,
|
||||
BRCType.Outfit: ItemClassification.filler,
|
||||
BRCType.Character: ItemClassification.progression,
|
||||
BRCType.REP: ItemClassification.progression_skip_balancing,
|
||||
BRCType.Camera: ItemClassification.progression
|
||||
}
|
||||
|
||||
def collect(self, state: "CollectionState", item: "Item") -> bool:
|
||||
change = super().collect(state, item)
|
||||
if change and "REP" in item.name:
|
||||
rep: int = int(item.name[0:len(item.name)-4])
|
||||
state.prog_items[item.player]["rep"] += rep
|
||||
return change
|
||||
|
||||
def remove(self, state: "CollectionState", item: "Item") -> bool:
|
||||
change = super().remove(state, item)
|
||||
if change and "REP" in item.name:
|
||||
rep: int = int(item.name[0:len(item.name)-4])
|
||||
state.prog_items[item.player]["rep"] -= rep
|
||||
return change
|
||||
|
||||
def set_rules(self):
|
||||
rules(self)
|
||||
|
||||
def get_item_classification(self, item_type: BRCType) -> ItemClassification:
|
||||
classification = ItemClassification.filler
|
||||
if item_type in self.item_classification.keys():
|
||||
classification = self.item_classification[item_type]
|
||||
|
||||
return classification
|
||||
|
||||
def create_item(self, name: str) -> "BombRushCyberfunkItem":
|
||||
item_id: int = self.item_name_to_id[name]
|
||||
item_type: BRCType = self.item_name_to_type[name]
|
||||
classification = self.get_item_classification(item_type)
|
||||
|
||||
return BombRushCyberfunkItem(name, classification, item_id, self.player)
|
||||
|
||||
def create_event(self, event: str) -> "BombRushCyberfunkItem":
|
||||
return BombRushCyberfunkItem(event, ItemClassification.progression_skip_balancing, None, self.player)
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
item = self.random.choice(item_table)
|
||||
|
||||
while self.get_item_classification(item["type"]) == ItemClassification.progression:
|
||||
item = self.random.choice(item_table)
|
||||
|
||||
return item["name"]
|
||||
|
||||
def generate_early(self):
|
||||
if self.options.starting_movestyle == StartStyle.option_skateboard:
|
||||
self.item_classification[BRCType.Skateboard] = ItemClassification.filler
|
||||
else:
|
||||
self.item_classification[BRCType.Skateboard] = ItemClassification.progression
|
||||
|
||||
if self.options.starting_movestyle == StartStyle.option_inline_skates:
|
||||
self.item_classification[BRCType.InlineSkates] = ItemClassification.filler
|
||||
else:
|
||||
self.item_classification[BRCType.InlineSkates] = ItemClassification.progression
|
||||
|
||||
if self.options.starting_movestyle == StartStyle.option_bmx:
|
||||
self.item_classification[BRCType.BMX] = ItemClassification.filler
|
||||
else:
|
||||
self.item_classification[BRCType.BMX] = ItemClassification.progression
|
||||
|
||||
def create_items(self):
|
||||
rep_locations: int = 87
|
||||
if self.options.skip_polo_photos:
|
||||
rep_locations -= 18
|
||||
|
||||
self.options.total_rep.round_to_nearest_step()
|
||||
rep_counts = self.options.total_rep.get_rep_item_counts(self.random, rep_locations)
|
||||
#print(sum([8*rep_counts[0], 16*rep_counts[1], 24*rep_counts[2], 32*rep_counts[3], 48*rep_counts[4]]), \
|
||||
# rep_counts)
|
||||
|
||||
pool = []
|
||||
|
||||
for item in item_table:
|
||||
if "REP" in item["name"]:
|
||||
count: int = 0
|
||||
|
||||
if item["name"] == "8 REP":
|
||||
count = rep_counts[0]
|
||||
elif item["name"] == "16 REP":
|
||||
count = rep_counts[1]
|
||||
elif item["name"] == "24 REP":
|
||||
count = rep_counts[2]
|
||||
elif item["name"] == "32 REP":
|
||||
count = rep_counts[3]
|
||||
elif item["name"] == "48 REP":
|
||||
count = rep_counts[4]
|
||||
|
||||
if count > 0:
|
||||
for _ in range(count):
|
||||
pool.append(self.create_item(item["name"]))
|
||||
else:
|
||||
pool.append(self.create_item(item["name"]))
|
||||
|
||||
self.multiworld.itempool += pool
|
||||
|
||||
def create_regions(self):
|
||||
multiworld = self.multiworld
|
||||
player = self.player
|
||||
|
||||
menu = Region("Menu", player, multiworld)
|
||||
multiworld.regions.append(menu)
|
||||
|
||||
for n in region_exits:
|
||||
multiworld.regions += [Region(n, player, multiworld)]
|
||||
|
||||
menu.add_exits({"Hideout": "New Game"})
|
||||
|
||||
for n in region_exits:
|
||||
self.get_region(n).add_exits(region_exits[n])
|
||||
|
||||
for index, loc in enumerate(location_table):
|
||||
if self.options.skip_polo_photos and "Polo" in loc["name"]:
|
||||
continue
|
||||
stage: Region = self.get_region(loc["stage"])
|
||||
stage.add_locations({loc["name"]: base_id + index})
|
||||
|
||||
for e in event_table:
|
||||
stage: Region = self.get_region(e["stage"])
|
||||
event = BombRushCyberfunkLocation(player, e["name"], None, stage)
|
||||
event.show_in_spoiler = False
|
||||
event.place_locked_item(self.create_event(e["item"]))
|
||||
stage.locations += [event]
|
||||
|
||||
multiworld.completion_condition[player] = lambda state: state.has("Victory", player)
|
||||
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
options = self.options
|
||||
|
||||
slot_data: Dict[str, Any] = {
|
||||
"locations": {loc["game_id"]: (base_id + index) for index, loc in enumerate(location_table)},
|
||||
"logic": options.logic.value,
|
||||
"skip_intro": bool(options.skip_intro.value),
|
||||
"skip_dreams": bool(options.skip_dreams.value),
|
||||
"skip_statue_hands": bool(options.skip_statue_hands.value),
|
||||
"total_rep": options.total_rep.value,
|
||||
"extra_rep_required": bool(options.extra_rep_required.value),
|
||||
"starting_movestyle": options.starting_movestyle.value,
|
||||
"limited_graffiti": bool(options.limited_graffiti.value),
|
||||
"small_graffiti_uses": options.small_graffiti_uses.value,
|
||||
"skip_polo_photos": bool(options.skip_polo_photos.value),
|
||||
"dont_save_photos": bool(options.dont_save_photos.value),
|
||||
"score_difficulty": int(options.score_difficulty.value),
|
||||
"damage_multiplier": options.damage_multiplier.value,
|
||||
"death_link": bool(options.death_link.value)
|
||||
}
|
||||
|
||||
return slot_data
|
||||
|
||||
|
||||
class BombRushCyberfunkItem(Item):
|
||||
game: str = "Bomb Rush Cyberfunk"
|
||||
|
||||
|
||||
class BombRushCyberfunkLocation(Location):
|
||||
game: str = "Bomb Rush Cyberfunk"
|
||||
29
worlds/bomb_rush_cyberfunk/docs/en_Bomb Rush Cyberfunk.md
Normal file
29
worlds/bomb_rush_cyberfunk/docs/en_Bomb Rush Cyberfunk.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Bomb Rush Cyberfunk
|
||||
|
||||
## 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 in this game?
|
||||
|
||||
The goal of Bomb Rush Cyberfunk randomizer is to defeat all rival crews in each borough of New Amsterdam. REP is no
|
||||
longer earned from doing graffiti, and is instead earned by finding it randomly in the multiworld.
|
||||
|
||||
Items can be found by picking up any type of collectible, unlocking characters, taking pictures of Polo, and for every
|
||||
5 graffiti spots tagged. The types of items that can be found are Music, Graffiti (M), Graffiti (L), Graffiti (XL),
|
||||
Skateboards, Inline Skates, BMX, Outfits, Characters, REP, and the Camera.
|
||||
|
||||
Several changes have been made to the game for a better experience as a randomizer:
|
||||
|
||||
- The prelude in the police station can be skipped.
|
||||
- The map for each stage is always unlocked.
|
||||
- The taxi is always unlocked, but you will still need to visit each stage's taxi stop before you can use them.
|
||||
- No M, L, or XL graffiti is unlocked at the beginning.
|
||||
- Optionally, graffiti can be depleted after a certain number of uses.
|
||||
- All characters except Red are locked.
|
||||
- One single REP count is used throughout the game, instead of having separate totals for each stage. REP requirements
|
||||
are the same as the original game, but added together in order. At least 960 REP is needed to finish the game.
|
||||
|
||||
The mod also adds two new apps to the phone, an "Encounter" app which lets you retry certain events early, and the
|
||||
"Archipelago" app which lets you view chat messages and change some options while playing.
|
||||
41
worlds/bomb_rush_cyberfunk/docs/setup_en.md
Normal file
41
worlds/bomb_rush_cyberfunk/docs/setup_en.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Bomb Rush Cyberfunk Multiworld Setup Guide
|
||||
|
||||
## Quick Links
|
||||
|
||||
- Bomb Rush Cyberfunk: [Steam](https://store.steampowered.com/app/1353230/Bomb_Rush_Cyberfunk/)
|
||||
- Archipelago Mod: [Thunderstore](https://thunderstore.io/c/bomb-rush-cyberfunk/p/TRPG/BRC_Archipelago/),
|
||||
[GitHub](https://github.com/TRPG0/BRC-Archipelago/releases)
|
||||
|
||||
## Setup
|
||||
|
||||
To install the Archipelago mod, you can use a mod manager like
|
||||
[r2modman](https://thunderstore.io/c/bomb-rush-cyberfunk/p/ebkr/r2modman/), or install manually by following these steps:
|
||||
|
||||
1. Download and install [BepInEx 5.4.22 x64](https://github.com/BepInEx/BepInEx/releases/tag/v5.4.22) in your Bomb Rush
|
||||
Cyberfunk root folder. *Do not use any pre-release versions of BepInEx 6.*
|
||||
|
||||
2. Start Bomb Rush Cyberfunk once so that BepInEx can create its required configuration files.
|
||||
|
||||
3. Download the zip archive from the [releases](https://github.com/TRPG0/BRC-Archipelago/releases) page, and extract its
|
||||
contents into `BepInEx\plugins`.
|
||||
|
||||
After installing Archipelago, there are some additional mods that can also be installed for a better experience:
|
||||
|
||||
- [MoreMap](https://thunderstore.io/c/bomb-rush-cyberfunk/p/TRPG/MoreMap/) by TRPG
|
||||
- Adds pins to the map for every type of collectible.
|
||||
- [FasterLoadTimes](https://thunderstore.io/c/bomb-rush-cyberfunk/p/cspotcode/FasterLoadTimes/) by cspotcode
|
||||
- Load stages faster by skipping assets that are already loaded.
|
||||
- [CutsceneSkip](https://thunderstore.io/c/bomb-rush-cyberfunk/p/Jay/CutsceneSkip/) by Jay
|
||||
- Makes every cutscene skippable.
|
||||
- [GimmeMyBoost](https://thunderstore.io/c/bomb-rush-cyberfunk/p/Yuri/GimmeMyBoost/) by Yuri
|
||||
- Retains boost when loading into a new stage.
|
||||
- [DisableAnnoyingCutscenes](https://thunderstore.io/c/bomb-rush-cyberfunk/p/viliger/DisableAnnoyingCutscenes/) by viliger
|
||||
- Disables the police cutscenes when increasing your heat level.
|
||||
- [FastTravel](https://thunderstore.io/c/bomb-rush-cyberfunk/p/tari/FastTravel/) by tari
|
||||
- Adds an app to the phone to call for a taxi from anywhere.
|
||||
|
||||
## Connecting
|
||||
|
||||
To connect to an Archipelago server, click one of the Archipelago buttons next to the save files. If the save file is
|
||||
blank or already has randomizer save data, it will open a menu where you can enter the server address and port, your
|
||||
name, and a password if necessary. Then click the check mark to connect to the server.
|
||||
5
worlds/bomb_rush_cyberfunk/test/__init__.py
Normal file
5
worlds/bomb_rush_cyberfunk/test/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from test.bases import WorldTestBase
|
||||
|
||||
|
||||
class BombRushCyberfunkTestBase(WorldTestBase):
|
||||
game = "Bomb Rush Cyberfunk"
|
||||
284
worlds/bomb_rush_cyberfunk/test/test_graffiti_spots.py
Normal file
284
worlds/bomb_rush_cyberfunk/test/test_graffiti_spots.py
Normal file
@@ -0,0 +1,284 @@
|
||||
from . import BombRushCyberfunkTestBase
|
||||
from ..Rules import build_access_cache, spots_s_glitchless, spots_s_glitched, spots_m_glitchless, spots_m_glitched, \
|
||||
spots_l_glitchless, spots_l_glitched, spots_xl_glitched, spots_xl_glitchless
|
||||
|
||||
|
||||
class TestSpotsGlitchless(BombRushCyberfunkTestBase):
|
||||
@property
|
||||
def run_default_tests(self) -> bool:
|
||||
return False
|
||||
|
||||
def test_spots_glitchless(self) -> None:
|
||||
player = self.player
|
||||
|
||||
self.collect_by_name([
|
||||
"Graffiti (M - OVERWHELMME)",
|
||||
"Graffiti (L - WHOLE SIXER)",
|
||||
"Graffiti (XL - Gold Rush)"
|
||||
])
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 1 - hideout
|
||||
self.assertEqual(10, spots_s_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(4, spots_m_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(7, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(3, spots_xl_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.collect_by_name("Inline Skates (Glaciers)")
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
self.assertEqual(8, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["rep"] = 20
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 1 - VH1-2
|
||||
self.assertEqual(22, spots_s_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(20, spots_m_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(23, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(9, spots_xl_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["rep"] = 65
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 1 - VH3
|
||||
self.assertEqual(23, spots_m_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(24, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["rep"] = 90
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 1 - VH4
|
||||
self.assertEqual(10, spots_xl_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["Chapter Completed"] = 1
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 2 - MS + MA1
|
||||
self.assertEqual(34, spots_s_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(39, spots_m_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(38, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(19, spots_xl_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["rep"] = 120
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 2 - VHO
|
||||
self.assertEqual(35, spots_s_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(43, spots_m_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(40, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.collect_by_name("Bel")
|
||||
self.multiworld.state.prog_items[player]["rep"] = 180
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 2 - BT1
|
||||
self.assertEqual(44, spots_s_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(56, spots_m_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(50, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(22, spots_xl_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["rep"] = 220
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 2 - BT2
|
||||
self.assertEqual(47, spots_s_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(60, spots_m_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(52, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(23, spots_xl_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["rep"] = 250
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 2 - BTO1
|
||||
self.assertEqual(53, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(24, spots_xl_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["rep"] = 280
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 2 - BT3 / chapter 3 - MS
|
||||
self.assertEqual(58, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(28, spots_xl_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["rep"] = 320
|
||||
self.multiworld.state.prog_items[player]["Chapter Completed"] = 2
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 2 - BTO2 / chapter 3 - MS
|
||||
self.assertEqual(54, spots_s_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(67, spots_m_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(62, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(30, spots_xl_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["rep"] = 380
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 3 - MM1-2
|
||||
self.assertEqual(61, spots_s_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(78, spots_m_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(73, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(37, spots_xl_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["rep"] = 491
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 3 - MM3
|
||||
self.assertEqual(64, spots_s_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(82, spots_m_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(77, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(42, spots_xl_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["Chapter Completed"] = 3
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 4 - MS / BT / MMO1 / PI1
|
||||
self.assertEqual(66, spots_s_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(85, spots_m_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(85, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(46, spots_xl_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["rep"] = 620
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 4 - PI2
|
||||
self.assertEqual(71, spots_s_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(88, spots_m_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(89, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["rep"] = 660
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 4 - PI3
|
||||
self.assertEqual(79, spots_s_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(96, spots_m_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(94, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(51, spots_xl_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["rep"] = 730
|
||||
self.multiworld.state.prog_items[player]["Chapter Completed"] = 4
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 5 - PI4
|
||||
self.assertEqual(98, spots_m_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(96, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["rep"] = 780
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 5 - PIO
|
||||
self.assertEqual(81, spots_s_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(103, spots_m_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(98, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(54, spots_xl_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["rep"] = 850
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 5 - MA2
|
||||
self.assertEqual(84, spots_s_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(99, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(56, spots_xl_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["rep"] = 864
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 5 - MA3
|
||||
self.assertEqual(89, spots_s_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(111, spots_m_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(102, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(58, spots_xl_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["rep"] = 935
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 5 - MAO
|
||||
self.assertEqual(92, spots_s_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(112, spots_m_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(104, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(60, spots_xl_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["rep"] = 960
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, False)
|
||||
|
||||
# chapter 5 - MA4-5
|
||||
self.assertEqual(94, spots_s_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(123, spots_m_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(111, spots_l_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(62, spots_xl_glitchless(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
class TestSpotsGlitched(BombRushCyberfunkTestBase):
|
||||
options = {
|
||||
"logic": "glitched"
|
||||
}
|
||||
|
||||
@property
|
||||
def run_default_tests(self) -> bool:
|
||||
return False
|
||||
|
||||
def test_spots_glitched(self) -> None:
|
||||
player = self.player
|
||||
|
||||
self.collect_by_name([
|
||||
"Graffiti (M - OVERWHELMME)",
|
||||
"Graffiti (L - WHOLE SIXER)",
|
||||
"Graffiti (XL - Gold Rush)"
|
||||
])
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, True)
|
||||
|
||||
self.assertEqual(75, spots_s_glitched(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(99, spots_m_glitched(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(88, spots_l_glitched(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(51, spots_xl_glitched(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.collect_by_name("Bel")
|
||||
self.multiworld.state.prog_items[player]["Chapter Completed"] = 1
|
||||
self.multiworld.state.prog_items[player]["rep"] = 180
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, True)
|
||||
|
||||
# brink terminal
|
||||
self.assertEqual(88, spots_s_glitched(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(120, spots_m_glitched(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(106, spots_l_glitched(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(58, spots_xl_glitched(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["Chapter Completed"] = 2
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, True)
|
||||
|
||||
# chapter 3
|
||||
self.assertEqual(94, spots_s_glitched(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(123, spots_m_glitched(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(110, spots_l_glitched(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(61, spots_xl_glitched(self.multiworld.state, player, False, access_cache))
|
||||
|
||||
|
||||
self.multiworld.state.prog_items[player]["Chapter Completed"] = 3
|
||||
access_cache = build_access_cache(self.multiworld.state, player, 2, False, True)
|
||||
|
||||
# chapter 4
|
||||
self.assertEqual(111, spots_l_glitched(self.multiworld.state, player, False, access_cache))
|
||||
self.assertEqual(62, spots_xl_glitched(self.multiworld.state, player, False, access_cache))
|
||||
29
worlds/bomb_rush_cyberfunk/test/test_options.py
Normal file
29
worlds/bomb_rush_cyberfunk/test/test_options.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from . import BombRushCyberfunkTestBase
|
||||
|
||||
|
||||
class TestRegularGraffitiGlitchless(BombRushCyberfunkTestBase):
|
||||
options = {
|
||||
"logic": "glitchless",
|
||||
"limited_graffiti": False
|
||||
}
|
||||
|
||||
|
||||
class TestLimitedGraffitiGlitchless(BombRushCyberfunkTestBase):
|
||||
options = {
|
||||
"logic": "glitchless",
|
||||
"limited_graffiti": True
|
||||
}
|
||||
|
||||
|
||||
class TestRegularGraffitiGlitched(BombRushCyberfunkTestBase):
|
||||
options = {
|
||||
"logic": "glitched",
|
||||
"limited_graffiti": False
|
||||
}
|
||||
|
||||
|
||||
class TestLimitedGraffitiGlitched(BombRushCyberfunkTestBase):
|
||||
options = {
|
||||
"logic": "glitched",
|
||||
"limited_graffiti": True
|
||||
}
|
||||
45
worlds/bomb_rush_cyberfunk/test/test_rep_items.py
Normal file
45
worlds/bomb_rush_cyberfunk/test/test_rep_items.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from . import BombRushCyberfunkTestBase
|
||||
from typing import List
|
||||
|
||||
|
||||
rep_item_names: List[str] = [
|
||||
"8 REP",
|
||||
"16 REP",
|
||||
"24 REP",
|
||||
"32 REP",
|
||||
"48 REP"
|
||||
]
|
||||
|
||||
|
||||
class TestCollectAndRemoveREP(BombRushCyberfunkTestBase):
|
||||
@property
|
||||
def run_default_tests(self) -> bool:
|
||||
return False
|
||||
|
||||
def test_default_rep_total(self) -> None:
|
||||
self.collect_by_name(rep_item_names)
|
||||
self.assertEqual(1400, self.multiworld.state.prog_items[self.player]["rep"])
|
||||
|
||||
new_total = 1400
|
||||
|
||||
if self.count("8 REP") > 0:
|
||||
new_total -= 8
|
||||
self.remove(self.get_item_by_name("8 REP"))
|
||||
|
||||
if self.count("16 REP") > 0:
|
||||
new_total -= 16
|
||||
self.remove(self.get_item_by_name("16 REP"))
|
||||
|
||||
if self.count("24 REP") > 0:
|
||||
new_total -= 24
|
||||
self.remove(self.get_item_by_name("24 REP"))
|
||||
|
||||
if self.count("32 REP") > 0:
|
||||
new_total -= 32
|
||||
self.remove(self.get_item_by_name("32 REP"))
|
||||
|
||||
if self.count("48 REP") > 0:
|
||||
new_total -= 48
|
||||
self.remove(self.get_item_by_name("48 REP"))
|
||||
|
||||
self.assertEqual(new_total, self.multiworld.state.prog_items[self.player]["rep"])
|
||||
@@ -31,7 +31,7 @@ def cv64_string_to_bytearray(cv64text: str, a_advance: bool = False, append_end:
|
||||
if char in cv64_char_dict:
|
||||
text_bytes.extend([0x00, cv64_char_dict[char][0]])
|
||||
else:
|
||||
text_bytes.extend([0x00, 0x41])
|
||||
text_bytes.extend([0x00, 0x21])
|
||||
|
||||
if a_advance:
|
||||
text_bytes.extend([0xA3, 0x00])
|
||||
@@ -45,7 +45,10 @@ def cv64_text_truncate(cv64text: str, textbox_len_limit: int) -> str:
|
||||
line_len = 0
|
||||
|
||||
for i in range(len(cv64text)):
|
||||
line_len += cv64_char_dict[cv64text[i]][1]
|
||||
if cv64text[i] in cv64_char_dict:
|
||||
line_len += cv64_char_dict[cv64text[i]][1]
|
||||
else:
|
||||
line_len += 5
|
||||
|
||||
if line_len > textbox_len_limit:
|
||||
return cv64text[0x00:i]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Dict
|
||||
from dataclasses import dataclass
|
||||
|
||||
from Options import OptionDict
|
||||
from Options import OptionDict, PerGameCommonOptions
|
||||
|
||||
|
||||
class Locations(OptionDict):
|
||||
@@ -18,8 +18,8 @@ class Rules(OptionDict):
|
||||
display_name = "rules"
|
||||
|
||||
|
||||
ff1_options: Dict[str, OptionDict] = {
|
||||
"locations": Locations,
|
||||
"items": Items,
|
||||
"rules": Rules
|
||||
}
|
||||
@dataclass
|
||||
class FF1Options(PerGameCommonOptions):
|
||||
locations: Locations
|
||||
items: Items
|
||||
rules: Rules
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import Dict
|
||||
from BaseClasses import Item, Location, MultiWorld, Tutorial, ItemClassification
|
||||
from .Items import ItemData, FF1Items, FF1_STARTER_ITEMS, FF1_PROGRESSION_LIST, FF1_BRIDGE
|
||||
from .Locations import EventId, FF1Locations, generate_rule, CHAOS_TERMINATED_EVENT
|
||||
from .Options import ff1_options
|
||||
from .Options import FF1Options
|
||||
from ..AutoWorld import World, WebWorld
|
||||
|
||||
|
||||
@@ -34,7 +34,8 @@ class FF1World(World):
|
||||
Part puzzle and part speed-run, it breathes new life into one of the most influential games ever made.
|
||||
"""
|
||||
|
||||
option_definitions = ff1_options
|
||||
options: FF1Options
|
||||
options_dataclass = FF1Options
|
||||
settings: typing.ClassVar[FF1Settings]
|
||||
settings_key = "ffr_options"
|
||||
game = "Final Fantasy"
|
||||
@@ -58,20 +59,20 @@ class FF1World(World):
|
||||
def stage_assert_generate(cls, multiworld: MultiWorld) -> None:
|
||||
# Fail generation if there are no items in the pool
|
||||
for player in multiworld.get_game_players(cls.game):
|
||||
options = get_options(multiworld, 'items', player)
|
||||
assert options,\
|
||||
items = multiworld.worlds[player].options.items.value
|
||||
assert items, \
|
||||
f"FFR settings submitted with no key items ({multiworld.get_player_name(player)}). Please ensure you " \
|
||||
f"generated the settings using finalfantasyrandomizer.com AND enabled the AP flag"
|
||||
|
||||
def create_regions(self):
|
||||
locations = get_options(self.multiworld, 'locations', self.player)
|
||||
rules = get_options(self.multiworld, 'rules', self.player)
|
||||
locations = self.options.locations.value
|
||||
rules = self.options.rules.value
|
||||
menu_region = self.ff1_locations.create_menu_region(self.player, locations, rules, self.multiworld)
|
||||
terminated_event = Location(self.player, CHAOS_TERMINATED_EVENT, EventId, menu_region)
|
||||
terminated_item = Item(CHAOS_TERMINATED_EVENT, ItemClassification.progression, EventId, self.player)
|
||||
terminated_event.place_locked_item(terminated_item)
|
||||
|
||||
items = get_options(self.multiworld, 'items', self.player)
|
||||
items = self.options.items.value
|
||||
goal_rule = generate_rule([[name for name in items.keys() if name in FF1_PROGRESSION_LIST and name != "Shard"]],
|
||||
self.player)
|
||||
terminated_event.access_rule = goal_rule
|
||||
@@ -93,7 +94,7 @@ class FF1World(World):
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has(CHAOS_TERMINATED_EVENT, self.player)
|
||||
|
||||
def create_items(self):
|
||||
items = get_options(self.multiworld, 'items', self.player)
|
||||
items = self.options.items.value
|
||||
if FF1_BRIDGE in items.keys():
|
||||
self._place_locked_item_in_sphere0(FF1_BRIDGE)
|
||||
if items:
|
||||
@@ -109,7 +110,7 @@ class FF1World(World):
|
||||
|
||||
def _place_locked_item_in_sphere0(self, progression_item: str):
|
||||
if progression_item:
|
||||
rules = get_options(self.multiworld, 'rules', self.player)
|
||||
rules = self.options.rules.value
|
||||
sphere_0_locations = [name for name, rules in rules.items()
|
||||
if rules and len(rules[0]) == 0 and name not in self.locked_locations]
|
||||
if sphere_0_locations:
|
||||
@@ -126,7 +127,3 @@ class FF1World(World):
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.multiworld.random.choice(["Heal", "Pure", "Soft", "Tent", "Cabin", "House"])
|
||||
|
||||
|
||||
def get_options(world: MultiWorld, name: str, player: int):
|
||||
return getattr(world, name, None)[player].value
|
||||
|
||||
@@ -132,9 +132,10 @@ guide: [Archipelago Plando Guide](/tutorial/Archipelago/plando/en)
|
||||
the location without using any hint points.
|
||||
* `start_location_hints` is the same as `start_hints` but for locations, allowing you to hint for the item contained
|
||||
there without using any hint points.
|
||||
* `exclude_locations` lets you define any locations that you don't want to do and during generation will force a "junk"
|
||||
item which isn't necessary for progression to go in these locations.
|
||||
* `priority_locations` is the inverse of `exclude_locations`, forcing a progression item in the defined locations.
|
||||
* `exclude_locations` lets you define any locations that you don't want to do and forces a filler or trap item which
|
||||
isn't necessary for progression into these locations.
|
||||
* `priority_locations` lets you define any locations that you want to do and forces a progression item into these
|
||||
locations.
|
||||
* `item_links` allows players to link their items into a group with the same item link name and game. The items declared
|
||||
in `item_pool` get combined and when an item is found for the group, all players in the group receive it. Item links
|
||||
can also have local and non local items, forcing the items to either be placed within the worlds of the group or in
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
from Options import Choice, Toggle, DefaultOnToggle, DeathLink, PerGameCommonOptions
|
||||
from Options import Choice, Removed, Toggle, DefaultOnToggle, DeathLink, PerGameCommonOptions
|
||||
|
||||
class PartyShuffle(Toggle):
|
||||
"""Shuffles party members into the pool.
|
||||
@@ -18,10 +18,22 @@ class MedallionShuffle(Toggle):
|
||||
"""Shuffles red medallions into the pool."""
|
||||
display_name = "Shuffle Red Medallions"
|
||||
|
||||
class RandomStart(Toggle):
|
||||
"""Start the randomizer in 1 of 4 positions.
|
||||
(Waynehouse, Viewax's Edifice, TV Island, Shield Facility)"""
|
||||
display_name = "Randomize Start Location"
|
||||
class StartLocation(Choice):
|
||||
"""Select the starting location from 1 of 4 positions."""
|
||||
display_name = "Start Location"
|
||||
option_waynehouse = 0
|
||||
option_viewaxs_edifice = 1
|
||||
option_tv_island = 2
|
||||
option_shield_facility = 3
|
||||
default = 0
|
||||
|
||||
@classmethod
|
||||
def get_option_name(cls, value: int) -> str:
|
||||
if value == 1:
|
||||
return "Viewax's Edifice"
|
||||
if value == 2:
|
||||
return "TV Island"
|
||||
return super().get_option_name(value)
|
||||
|
||||
class ExtraLogic(DefaultOnToggle):
|
||||
"""Include some extra items in logic (CHARGE UP, 1x PAPER CUP) to prevent the game from becoming too difficult."""
|
||||
@@ -37,6 +49,9 @@ class Hylics2Options(PerGameCommonOptions):
|
||||
party_shuffle: PartyShuffle
|
||||
gesture_shuffle: GestureShuffle
|
||||
medallion_shuffle: MedallionShuffle
|
||||
random_start: RandomStart
|
||||
start_location: StartLocation
|
||||
extra_items_in_logic: ExtraLogic
|
||||
death_link: Hylics2DeathLink
|
||||
death_link: Hylics2DeathLink
|
||||
|
||||
# Removed options
|
||||
random_start: Removed
|
||||
|
||||
@@ -132,8 +132,7 @@ def set_rules(hylics2world):
|
||||
extra = hylics2world.options.extra_items_in_logic
|
||||
party = hylics2world.options.party_shuffle
|
||||
medallion = hylics2world.options.medallion_shuffle
|
||||
random_start = hylics2world.options.random_start
|
||||
start_location = hylics2world.start_location
|
||||
start_location = hylics2world.options.start_location
|
||||
|
||||
# Afterlife
|
||||
add_rule(world.get_location("Afterlife: TV", player),
|
||||
@@ -499,7 +498,7 @@ def set_rules(hylics2world):
|
||||
add_rule(i, lambda state: enter_hylemxylem(state, player))
|
||||
|
||||
# random start logic (default)
|
||||
if not random_start or random_start and start_location == "Waynehouse":
|
||||
if start_location == "waynehouse":
|
||||
# entrances
|
||||
for i in world.get_region("Viewax", player).entrances:
|
||||
add_rule(i, lambda state: (
|
||||
@@ -514,7 +513,7 @@ def set_rules(hylics2world):
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
|
||||
# random start logic (Viewax's Edifice)
|
||||
elif random_start and start_location == "Viewax's Edifice":
|
||||
elif start_location == "viewaxs_edifice":
|
||||
for i in world.get_region("Waynehouse", player).entrances:
|
||||
add_rule(i, lambda state: (
|
||||
air_dash(state, player)
|
||||
@@ -544,8 +543,8 @@ def set_rules(hylics2world):
|
||||
for i in world.get_region("Sage Labyrinth", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
|
||||
# random start logic (TV Island)
|
||||
elif random_start and start_location == "TV Island":
|
||||
# start logic (TV Island)
|
||||
elif start_location == "tv_island":
|
||||
for i in world.get_region("Waynehouse", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("New Muldul", player).entrances:
|
||||
@@ -563,8 +562,8 @@ def set_rules(hylics2world):
|
||||
for i in world.get_region("Sage Labyrinth", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
|
||||
# random start logic (Shield Facility)
|
||||
elif random_start and start_location == "Shield Facility":
|
||||
# start logic (Shield Facility)
|
||||
elif start_location == "shield_facility":
|
||||
for i in world.get_region("Waynehouse", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("New Muldul", player).entrances:
|
||||
@@ -578,4 +577,4 @@ def set_rules(hylics2world):
|
||||
for i in world.get_region("TV Island", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("Sage Labyrinth", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
|
||||
@@ -39,8 +39,6 @@ class Hylics2World(World):
|
||||
|
||||
data_version = 3
|
||||
|
||||
start_location = "Waynehouse"
|
||||
|
||||
|
||||
def set_rules(self):
|
||||
Rules.set_rules(self)
|
||||
@@ -56,19 +54,6 @@ class Hylics2World(World):
|
||||
return Hylics2Item(event, ItemClassification.progression_skip_balancing, None, self.player)
|
||||
|
||||
|
||||
# set random starting location if option is enabled
|
||||
def generate_early(self):
|
||||
if self.options.random_start:
|
||||
i = self.random.randint(0, 3)
|
||||
if i == 0:
|
||||
self.start_location = "Waynehouse"
|
||||
elif i == 1:
|
||||
self.start_location = "Viewax's Edifice"
|
||||
elif i == 2:
|
||||
self.start_location = "TV Island"
|
||||
elif i == 3:
|
||||
self.start_location = "Shield Facility"
|
||||
|
||||
def create_items(self):
|
||||
# create item pool
|
||||
pool = []
|
||||
@@ -149,8 +134,8 @@ class Hylics2World(World):
|
||||
slot_data: Dict[str, Any] = {
|
||||
"party_shuffle": self.options.party_shuffle.value,
|
||||
"medallion_shuffle": self.options.medallion_shuffle.value,
|
||||
"random_start" : self.options.random_start.value,
|
||||
"start_location" : self.start_location,
|
||||
"random_start": int(self.options.start_location != "waynehouse"),
|
||||
"start_location" : self.options.start_location.current_option_name,
|
||||
"death_link": self.options.death_link.value
|
||||
}
|
||||
return slot_data
|
||||
@@ -189,14 +174,14 @@ class Hylics2World(World):
|
||||
# create entrance and connect it to parent and destination regions
|
||||
ent = Entrance(self.player, f"{reg.name} {k}", reg)
|
||||
reg.exits.append(ent)
|
||||
if k == "New Game" and self.options.random_start:
|
||||
if self.start_location == "Waynehouse":
|
||||
if k == "New Game":
|
||||
if self.options.start_location == "waynehouse":
|
||||
ent.connect(region_table[2])
|
||||
elif self.start_location == "Viewax's Edifice":
|
||||
elif self.options.start_location == "viewaxs_edifice":
|
||||
ent.connect(region_table[6])
|
||||
elif self.start_location == "TV Island":
|
||||
elif self.options.start_location == "tv_island":
|
||||
ent.connect(region_table[9])
|
||||
elif self.start_location == "Shield Facility":
|
||||
elif self.options.start_location == "shield_facility":
|
||||
ent.connect(region_table[11])
|
||||
else:
|
||||
for name, num in Exits.exit_lookup_table.items():
|
||||
|
||||
@@ -155,7 +155,8 @@ class MessengerWorld(World):
|
||||
self.starting_portals.append("Searing Crags Portal")
|
||||
portals_to_strip = [portal for portal in ["Riviere Turquoise Portal", "Sunken Shrine Portal"]
|
||||
if portal in self.starting_portals]
|
||||
self.starting_portals.remove(self.random.choice(portals_to_strip))
|
||||
if portals_to_strip:
|
||||
self.starting_portals.remove(self.random.choice(portals_to_strip))
|
||||
|
||||
self.filler = FILLER.copy()
|
||||
if self.options.traps:
|
||||
|
||||
@@ -4,6 +4,10 @@ from ..portals import PORTALS
|
||||
|
||||
|
||||
class PortalTestBase(MessengerTestBase):
|
||||
options = {
|
||||
"available_portals": 3,
|
||||
}
|
||||
|
||||
def test_portal_reqs(self) -> None:
|
||||
"""tests the paths to open a portal if only that portal is closed with vanilla connections."""
|
||||
# portal and requirements to reach it if it's the only closed portal
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import typing
|
||||
from Options import Choice, Option, Range
|
||||
from dataclasses import dataclass
|
||||
from Options import Choice, Option, Range, PerGameCommonOptions
|
||||
|
||||
|
||||
class IncomeBoost(Range):
|
||||
@@ -30,9 +31,8 @@ class CommanderChoice(Choice):
|
||||
option_unlockable_factions = 1
|
||||
option_random_starting_faction = 2
|
||||
|
||||
|
||||
wargroove_options: typing.Dict[str, type(Option)] = {
|
||||
"income_boost": IncomeBoost,
|
||||
"commander_defense_boost": CommanderDefenseBoost,
|
||||
"commander_choice": CommanderChoice
|
||||
}
|
||||
@dataclass
|
||||
class WargrooveOptions(PerGameCommonOptions):
|
||||
income_boost: IncomeBoost
|
||||
commander_defense_boost: CommanderDefenseBoost
|
||||
commander_choice: CommanderChoice
|
||||
|
||||
@@ -7,8 +7,8 @@ from .Items import item_table, faction_table
|
||||
from .Locations import location_table
|
||||
from .Regions import create_regions
|
||||
from .Rules import set_rules
|
||||
from ..AutoWorld import World, WebWorld
|
||||
from .Options import wargroove_options
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from .Options import WargrooveOptions
|
||||
|
||||
|
||||
class WargrooveSettings(settings.Group):
|
||||
@@ -38,11 +38,11 @@ class WargrooveWorld(World):
|
||||
Command an army, in this retro style turn based strategy game!
|
||||
"""
|
||||
|
||||
option_definitions = wargroove_options
|
||||
options: WargrooveOptions
|
||||
options_dataclass = WargrooveOptions
|
||||
settings: typing.ClassVar[WargrooveSettings]
|
||||
game = "Wargroove"
|
||||
topology_present = True
|
||||
data_version = 1
|
||||
web = WargrooveWeb()
|
||||
|
||||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||||
@@ -50,16 +50,17 @@ class WargrooveWorld(World):
|
||||
|
||||
def _get_slot_data(self):
|
||||
return {
|
||||
'seed': "".join(self.multiworld.per_slot_randoms[self.player].choice(string.ascii_letters) for i in range(16)),
|
||||
'income_boost': self.multiworld.income_boost[self.player],
|
||||
'commander_defense_boost': self.multiworld.commander_defense_boost[self.player],
|
||||
'can_choose_commander': self.multiworld.commander_choice[self.player] != 0,
|
||||
'seed': "".join(self.random.choice(string.ascii_letters) for i in range(16)),
|
||||
'income_boost': self.options.income_boost.value,
|
||||
'commander_defense_boost': self.options.commander_defense_boost.value,
|
||||
'can_choose_commander': self.options.commander_choice.value != 0,
|
||||
'commander_choice': self.options.commander_choice.value,
|
||||
'starting_groove_multiplier': 20 # Backwards compatibility in case this ever becomes an option
|
||||
}
|
||||
|
||||
def generate_early(self):
|
||||
# Selecting a random starting faction
|
||||
if self.multiworld.commander_choice[self.player] == 2:
|
||||
if self.options.commander_choice.value == 2:
|
||||
factions = [faction for faction in faction_table.keys() if faction != "Starter"]
|
||||
starting_faction = WargrooveItem(self.multiworld.random.choice(factions) + ' Commanders', self.player)
|
||||
self.multiworld.push_precollected(starting_faction)
|
||||
@@ -68,7 +69,7 @@ class WargrooveWorld(World):
|
||||
# Fill out our pool with our items from the item table
|
||||
pool = []
|
||||
precollected_item_names = {item.name for item in self.multiworld.precollected_items[self.player]}
|
||||
ignore_faction_items = self.multiworld.commander_choice[self.player] == 0
|
||||
ignore_faction_items = self.options.commander_choice.value == 0
|
||||
for name, data in item_table.items():
|
||||
if data.code is not None and name not in precollected_item_names and not data.classification == ItemClassification.filler:
|
||||
if name.endswith(' Commanders') and ignore_faction_items:
|
||||
@@ -105,9 +106,6 @@ class WargrooveWorld(World):
|
||||
|
||||
def fill_slot_data(self) -> dict:
|
||||
slot_data = self._get_slot_data()
|
||||
for option_name in wargroove_options:
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
slot_data[option_name] = int(option.value)
|
||||
return slot_data
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
|
||||
@@ -7,7 +7,7 @@ from typing import Any, Dict, List, Optional, cast
|
||||
|
||||
from BaseClasses import CollectionState, Entrance, Location, Region, Tutorial
|
||||
|
||||
from Options import PerGameCommonOptions, Toggle
|
||||
from Options import OptionError, PerGameCommonOptions, Toggle
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
|
||||
from .data import static_items as static_witness_items
|
||||
@@ -124,9 +124,9 @@ class WitnessWorld(World):
|
||||
warning(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have any progression"
|
||||
f" items. Please turn on Symbol Shuffle, Door Shuffle or Laser Shuffle if that doesn't seem right.")
|
||||
elif not interacts_sufficiently_with_multiworld and self.multiworld.players > 1:
|
||||
raise Exception(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have enough"
|
||||
f" progression items that can be placed in other players' worlds. Please turn on Symbol"
|
||||
f" Shuffle, Door Shuffle, or Obelisk Keys.")
|
||||
raise OptionError(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have enough"
|
||||
f" progression items that can be placed in other players' worlds. Please turn on Symbol"
|
||||
f" Shuffle, Door Shuffle, or Obelisk Keys.")
|
||||
|
||||
def generate_early(self) -> None:
|
||||
disabled_locations = self.options.exclude_locations.value
|
||||
|
||||
@@ -482,7 +482,7 @@ Outside Monastery (Monastery) - Main Island - True - Inside Monastery - 0x0C128
|
||||
158207 - 0x03713 (Laser Shortcut Panel) - True - True
|
||||
Door - 0x0364E (Laser Shortcut) - 0x03713
|
||||
158208 - 0x00B10 (Entry Left) - True - True
|
||||
158209 - 0x00C92 (Entry Right) - True - True
|
||||
158209 - 0x00C92 (Entry Right) - 0x00B10 - True
|
||||
Door - 0x0C128 (Entry Inner) - 0x00B10
|
||||
Door - 0x0C153 (Entry Outer) - 0x00C92
|
||||
158210 - 0x00290 (Outside 1) - 0x09D9B - True
|
||||
@@ -1033,7 +1033,7 @@ Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near -
|
||||
158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Stars & Colored Squares & Stars + Same Colored Symbol
|
||||
158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars & Colored Squares & Stars + Same Colored Symbol
|
||||
158429 - 0x09FD7 (Near Row 4) - 0x09FD6 - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers
|
||||
158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Colored Squares & Symmetry & Colored Dots
|
||||
158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Symmetry & Colored Dots
|
||||
Door - 0x09FFB (Staircase Near) - 0x09FD8
|
||||
|
||||
Mountain Floor 2 At Door (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EDD:
|
||||
|
||||
@@ -482,7 +482,7 @@ Outside Monastery (Monastery) - Main Island - True - Inside Monastery - 0x0C128
|
||||
158207 - 0x03713 (Laser Shortcut Panel) - True - True
|
||||
Door - 0x0364E (Laser Shortcut) - 0x03713
|
||||
158208 - 0x00B10 (Entry Left) - True - True
|
||||
158209 - 0x00C92 (Entry Right) - True - True
|
||||
158209 - 0x00C92 (Entry Right) - 0x00B10 - True
|
||||
Door - 0x0C128 (Entry Inner) - 0x00B10
|
||||
Door - 0x0C153 (Entry Outer) - 0x00C92
|
||||
158210 - 0x00290 (Outside 1) - 0x09D9B - True
|
||||
|
||||
@@ -482,7 +482,7 @@ Outside Monastery (Monastery) - Main Island - True - Inside Monastery - 0x0C128
|
||||
158207 - 0x03713 (Laser Shortcut Panel) - True - True
|
||||
Door - 0x0364E (Laser Shortcut) - 0x03713
|
||||
158208 - 0x00B10 (Entry Left) - True - True
|
||||
158209 - 0x00C92 (Entry Right) - True - True
|
||||
158209 - 0x00C92 (Entry Right) - 0x00B10 - True
|
||||
Door - 0x0C128 (Entry Inner) - 0x00B10
|
||||
Door - 0x0C153 (Entry Outer) - 0x00C92
|
||||
158210 - 0x00290 (Outside 1) - 0x09D9B - True
|
||||
|
||||
454
worlds/yugioh06/__init__.py
Normal file
454
worlds/yugioh06/__init__.py
Normal file
@@ -0,0 +1,454 @@
|
||||
import os
|
||||
import pkgutil
|
||||
from typing import Any, ClassVar, Dict, List
|
||||
|
||||
import settings
|
||||
from BaseClasses import Entrance, Item, ItemClassification, Location, MultiWorld, Region, Tutorial
|
||||
|
||||
import Utils
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
|
||||
from .boosterpacks import booster_contents as booster_contents
|
||||
from .boosterpacks import get_booster_locations
|
||||
from .items import (
|
||||
Banlist_Items,
|
||||
booster_packs,
|
||||
draft_boosters,
|
||||
draft_opponents,
|
||||
excluded_items,
|
||||
item_to_index,
|
||||
tier_1_opponents,
|
||||
useful,
|
||||
)
|
||||
from .items import (
|
||||
challenges as challenges,
|
||||
)
|
||||
from .locations import (
|
||||
Bonuses,
|
||||
Campaign_Opponents,
|
||||
Limited_Duels,
|
||||
Required_Cards,
|
||||
Theme_Duels,
|
||||
collection_events,
|
||||
get_beat_challenge_events,
|
||||
special,
|
||||
)
|
||||
from .logic import core_booster, yugioh06_difficulty
|
||||
from .opponents import OpponentData, get_opponent_condition, get_opponent_locations, get_opponents
|
||||
from .opponents import challenge_opponents as challenge_opponents
|
||||
from .options import Yugioh06Options
|
||||
from .rom import MD5America, MD5Europe, YGO06ProcedurePatch, write_tokens
|
||||
from .rom import get_base_rom_path as get_base_rom_path
|
||||
from .rom_values import banlist_ids as banlist_ids
|
||||
from .rom_values import function_addresses as function_addresses
|
||||
from .rom_values import structure_deck_selection as structure_deck_selection
|
||||
from .rules import set_rules
|
||||
from .structure_deck import get_deck_content_locations
|
||||
from .client_bh import YuGiOh2006Client
|
||||
|
||||
|
||||
class Yugioh06Web(WebWorld):
|
||||
theme = "stone"
|
||||
setup = Tutorial(
|
||||
"Multiworld Setup Tutorial",
|
||||
"A guide to setting up Yu-Gi-Oh! - Ultimate Masters Edition - World Championship Tournament 2006 "
|
||||
"for Archipelago on your computer.",
|
||||
"English",
|
||||
"docs/setup_en.md",
|
||||
"setup/en",
|
||||
["Rensen"],
|
||||
)
|
||||
tutorials = [setup]
|
||||
|
||||
|
||||
class Yugioh2006Setting(settings.Group):
|
||||
class Yugioh2006RomFile(settings.UserFilePath):
|
||||
"""File name of your Yu-Gi-Oh 2006 ROM"""
|
||||
|
||||
description = "Yu-Gi-Oh 2006 ROM File"
|
||||
copy_to = "YuGiOh06.gba"
|
||||
md5s = [MD5Europe, MD5America]
|
||||
|
||||
rom_file: Yugioh2006RomFile = Yugioh2006RomFile(Yugioh2006RomFile.copy_to)
|
||||
|
||||
|
||||
class Yugioh06World(World):
|
||||
"""
|
||||
Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006 is the definitive Yu-Gi-Oh
|
||||
simulator on the GBA. Featuring over 2000 cards and over 90 Challenges.
|
||||
"""
|
||||
|
||||
game = "Yu-Gi-Oh! 2006"
|
||||
web = Yugioh06Web()
|
||||
options: Yugioh06Options
|
||||
options_dataclass = Yugioh06Options
|
||||
settings_key = "yugioh06_settings"
|
||||
settings: ClassVar[Yugioh2006Setting]
|
||||
|
||||
item_name_to_id = {}
|
||||
start_id = 5730000
|
||||
for k, v in item_to_index.items():
|
||||
item_name_to_id[k] = v + start_id
|
||||
|
||||
location_name_to_id = {}
|
||||
for k, v in Bonuses.items():
|
||||
location_name_to_id[k] = v + start_id
|
||||
|
||||
for k, v in Limited_Duels.items():
|
||||
location_name_to_id[k] = v + start_id
|
||||
|
||||
for k, v in Theme_Duels.items():
|
||||
location_name_to_id[k] = v + start_id
|
||||
|
||||
for k, v in Campaign_Opponents.items():
|
||||
location_name_to_id[k] = v + start_id
|
||||
|
||||
for k, v in special.items():
|
||||
location_name_to_id[k] = v + start_id
|
||||
|
||||
for k, v in Required_Cards.items():
|
||||
location_name_to_id[k] = v + start_id
|
||||
|
||||
item_name_groups = {
|
||||
"Core Booster": core_booster,
|
||||
"Campaign Boss Beaten": ["Tier 1 Beaten", "Tier 2 Beaten", "Tier 3 Beaten", "Tier 4 Beaten", "Tier 5 Beaten"],
|
||||
}
|
||||
|
||||
removed_challenges: List[str]
|
||||
starting_booster: str
|
||||
starting_opponent: str
|
||||
campaign_opponents: List[OpponentData]
|
||||
is_draft_mode: bool
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
super().__init__(world, player)
|
||||
|
||||
def generate_early(self):
|
||||
self.starting_opponent = ""
|
||||
self.starting_booster = ""
|
||||
self.removed_challenges = []
|
||||
# Universal tracker stuff, shouldn't do anything in standard gen
|
||||
if hasattr(self.multiworld, "re_gen_passthrough"):
|
||||
if "Yu-Gi-Oh! 2006" in self.multiworld.re_gen_passthrough:
|
||||
# bypassing random yaml settings
|
||||
slot_data = self.multiworld.re_gen_passthrough["Yu-Gi-Oh! 2006"]
|
||||
self.options.structure_deck.value = slot_data["structure_deck"]
|
||||
self.options.banlist.value = slot_data["banlist"]
|
||||
self.options.final_campaign_boss_unlock_condition.value = slot_data[
|
||||
"final_campaign_boss_unlock_condition"
|
||||
]
|
||||
self.options.fourth_tier_5_campaign_boss_unlock_condition.value = slot_data[
|
||||
"fourth_tier_5_campaign_boss_unlock_condition"
|
||||
]
|
||||
self.options.third_tier_5_campaign_boss_unlock_condition.value = slot_data[
|
||||
"third_tier_5_campaign_boss_unlock_condition"
|
||||
]
|
||||
self.options.final_campaign_boss_challenges.value = slot_data["final_campaign_boss_challenges"]
|
||||
self.options.fourth_tier_5_campaign_boss_challenges.value = slot_data[
|
||||
"fourth_tier_5_campaign_boss_challenges"
|
||||
]
|
||||
self.options.third_tier_5_campaign_boss_challenges.value = slot_data[
|
||||
"third_tier_5_campaign_boss_challenges"
|
||||
]
|
||||
self.options.final_campaign_boss_campaign_opponents.value = slot_data[
|
||||
"final_campaign_boss_campaign_opponents"
|
||||
]
|
||||
self.options.fourth_tier_5_campaign_boss_campaign_opponents.value = slot_data[
|
||||
"fourth_tier_5_campaign_boss_campaign_opponents"
|
||||
]
|
||||
self.options.third_tier_5_campaign_boss_campaign_opponents.value = slot_data[
|
||||
"third_tier_5_campaign_boss_campaign_opponents"
|
||||
]
|
||||
self.options.number_of_challenges.value = slot_data["number_of_challenges"]
|
||||
self.removed_challenges = slot_data["removed challenges"]
|
||||
self.starting_booster = slot_data["starting_booster"]
|
||||
self.starting_opponent = slot_data["starting_opponent"]
|
||||
|
||||
if self.options.structure_deck.current_key == "none":
|
||||
self.is_draft_mode = True
|
||||
boosters = draft_boosters
|
||||
if self.options.campaign_opponents_shuffle.value:
|
||||
opponents = tier_1_opponents
|
||||
else:
|
||||
opponents = draft_opponents
|
||||
else:
|
||||
self.is_draft_mode = False
|
||||
boosters = booster_packs
|
||||
opponents = tier_1_opponents
|
||||
|
||||
if self.options.structure_deck.current_key == "random_deck":
|
||||
self.options.structure_deck.value = self.random.randint(0, 5)
|
||||
for item in self.options.start_inventory:
|
||||
if item in opponents:
|
||||
self.starting_opponent = item
|
||||
if item in boosters:
|
||||
self.starting_booster = item
|
||||
if not self.starting_opponent:
|
||||
self.starting_opponent = self.random.choice(opponents)
|
||||
self.multiworld.push_precollected(self.create_item(self.starting_opponent))
|
||||
if not self.starting_booster:
|
||||
self.starting_booster = self.random.choice(boosters)
|
||||
self.multiworld.push_precollected(self.create_item(self.starting_booster))
|
||||
banlist = self.options.banlist.value
|
||||
self.multiworld.push_precollected(self.create_item(Banlist_Items[banlist]))
|
||||
|
||||
if not self.removed_challenges:
|
||||
challenge = list((Limited_Duels | Theme_Duels).keys())
|
||||
noc = len(challenge) - max(
|
||||
self.options.third_tier_5_campaign_boss_challenges.value
|
||||
if self.options.third_tier_5_campaign_boss_unlock_condition == "challenges"
|
||||
else 0,
|
||||
self.options.fourth_tier_5_campaign_boss_challenges.value
|
||||
if self.options.fourth_tier_5_campaign_boss_unlock_condition == "challenges"
|
||||
else 0,
|
||||
self.options.final_campaign_boss_challenges.value
|
||||
if self.options.final_campaign_boss_unlock_condition == "challenges"
|
||||
else 0,
|
||||
self.options.number_of_challenges.value,
|
||||
)
|
||||
|
||||
self.random.shuffle(challenge)
|
||||
excluded = self.options.exclude_locations.value.intersection(challenge)
|
||||
prio = self.options.priority_locations.value.intersection(challenge)
|
||||
normal = [e for e in challenge if e not in excluded and e not in prio]
|
||||
total = list(excluded) + normal + list(prio)
|
||||
self.removed_challenges = total[:noc]
|
||||
|
||||
self.campaign_opponents = get_opponents(
|
||||
self.multiworld, self.player, self.options.campaign_opponents_shuffle.value
|
||||
)
|
||||
|
||||
def create_region(self, name: str, locations=None, exits=None):
|
||||
region = Region(name, self.player, self.multiworld)
|
||||
if locations:
|
||||
for location_name, lid in locations.items():
|
||||
if lid is not None and isinstance(lid, int):
|
||||
lid = self.location_name_to_id[location_name]
|
||||
else:
|
||||
lid = None
|
||||
location = Yugioh2006Location(self.player, location_name, lid, region)
|
||||
region.locations.append(location)
|
||||
|
||||
if exits:
|
||||
for _exit in exits:
|
||||
region.exits.append(Entrance(self.player, _exit, region))
|
||||
return region
|
||||
|
||||
def create_regions(self):
|
||||
structure_deck = self.options.structure_deck.current_key
|
||||
self.multiworld.regions += [
|
||||
self.create_region("Menu", None, ["to Deck Edit", "to Campaign", "to Challenges", "to Card Shop"]),
|
||||
self.create_region("Campaign", Bonuses | Campaign_Opponents),
|
||||
self.create_region("Challenges"),
|
||||
self.create_region("Card Shop", Required_Cards | collection_events),
|
||||
self.create_region("Structure Deck", get_deck_content_locations(structure_deck)),
|
||||
]
|
||||
|
||||
self.get_entrance("to Campaign").connect(self.get_region("Campaign"))
|
||||
self.get_entrance("to Challenges").connect(self.get_region("Challenges"))
|
||||
self.get_entrance("to Card Shop").connect(self.get_region("Card Shop"))
|
||||
self.get_entrance("to Deck Edit").connect(self.get_region("Structure Deck"))
|
||||
|
||||
campaign = self.get_region("Campaign")
|
||||
# Campaign Opponents
|
||||
for opponent in self.campaign_opponents:
|
||||
unlock_item = "Campaign Tier " + str(opponent.tier) + " Column " + str(opponent.column)
|
||||
region = self.create_region(opponent.name, get_opponent_locations(opponent))
|
||||
entrance = Entrance(self.player, unlock_item, campaign)
|
||||
if opponent.tier == 5 and opponent.column > 2:
|
||||
unlock_amount = 0
|
||||
is_challenge = True
|
||||
if opponent.column == 3:
|
||||
if self.options.third_tier_5_campaign_boss_unlock_condition.value == 1:
|
||||
unlock_item = "Challenge Beaten"
|
||||
unlock_amount = self.options.third_tier_5_campaign_boss_challenges.value
|
||||
is_challenge = True
|
||||
else:
|
||||
unlock_item = "Campaign Boss Beaten"
|
||||
unlock_amount = self.options.third_tier_5_campaign_boss_campaign_opponents.value
|
||||
is_challenge = False
|
||||
if opponent.column == 4:
|
||||
if self.options.fourth_tier_5_campaign_boss_unlock_condition.value == 1:
|
||||
unlock_item = "Challenge Beaten"
|
||||
unlock_amount = self.options.fourth_tier_5_campaign_boss_challenges.value
|
||||
is_challenge = True
|
||||
else:
|
||||
unlock_item = "Campaign Boss Beaten"
|
||||
unlock_amount = self.options.fourth_tier_5_campaign_boss_campaign_opponents.value
|
||||
is_challenge = False
|
||||
if opponent.column == 5:
|
||||
if self.options.final_campaign_boss_unlock_condition.value == 1:
|
||||
unlock_item = "Challenge Beaten"
|
||||
unlock_amount = self.options.final_campaign_boss_challenges.value
|
||||
is_challenge = True
|
||||
else:
|
||||
unlock_item = "Campaign Boss Beaten"
|
||||
unlock_amount = self.options.final_campaign_boss_campaign_opponents.value
|
||||
is_challenge = False
|
||||
entrance.access_rule = get_opponent_condition(
|
||||
opponent, unlock_item, unlock_amount, self.player, is_challenge
|
||||
)
|
||||
else:
|
||||
entrance.access_rule = lambda state, unlock=unlock_item, opp=opponent: state.has(
|
||||
unlock, self.player
|
||||
) and yugioh06_difficulty(state, self.player, opp.difficulty)
|
||||
campaign.exits.append(entrance)
|
||||
entrance.connect(region)
|
||||
self.multiworld.regions.append(region)
|
||||
|
||||
card_shop = self.get_region("Card Shop")
|
||||
# Booster Contents
|
||||
for booster in booster_packs:
|
||||
region = self.create_region(booster, get_booster_locations(booster))
|
||||
entrance = Entrance(self.player, booster, card_shop)
|
||||
entrance.access_rule = lambda state, unlock=booster: state.has(unlock, self.player)
|
||||
card_shop.exits.append(entrance)
|
||||
entrance.connect(region)
|
||||
self.multiworld.regions.append(region)
|
||||
|
||||
challenge_region = self.get_region("Challenges")
|
||||
# Challenges
|
||||
for challenge, lid in (Limited_Duels | Theme_Duels).items():
|
||||
if challenge in self.removed_challenges:
|
||||
continue
|
||||
region = self.create_region(challenge, {challenge: lid, challenge + " Complete": None})
|
||||
entrance = Entrance(self.player, challenge, challenge_region)
|
||||
entrance.access_rule = lambda state, unlock=challenge: state.has(unlock + " Unlock", self.player)
|
||||
challenge_region.exits.append(entrance)
|
||||
entrance.connect(region)
|
||||
self.multiworld.regions.append(region)
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
classification: ItemClassification = ItemClassification.progression
|
||||
if name == "5000DP":
|
||||
classification = ItemClassification.filler
|
||||
if name in useful:
|
||||
classification = ItemClassification.useful
|
||||
return Item(name, classification, self.item_name_to_id[name], self.player)
|
||||
|
||||
def create_filler(self) -> Item:
|
||||
return self.create_item("5000DP")
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return "5000DP"
|
||||
|
||||
def create_items(self):
|
||||
start_inventory = self.options.start_inventory.value.copy()
|
||||
item_pool = []
|
||||
items = item_to_index.copy()
|
||||
starting_list = Banlist_Items[self.options.banlist.value]
|
||||
if not self.options.add_empty_banlist.value and starting_list != "No Banlist":
|
||||
items.pop("No Banlist")
|
||||
for rc in self.removed_challenges:
|
||||
items.pop(rc + " Unlock")
|
||||
items.pop(self.starting_opponent)
|
||||
items.pop(self.starting_booster)
|
||||
items.pop(starting_list)
|
||||
for name in items:
|
||||
if name in excluded_items or name in start_inventory:
|
||||
continue
|
||||
item = self.create_item(name)
|
||||
item_pool.append(item)
|
||||
|
||||
needed_item_pool_size = sum(loc not in self.removed_challenges for loc in self.location_name_to_id)
|
||||
needed_filler_amount = needed_item_pool_size - len(item_pool)
|
||||
item_pool += [self.create_item("5000DP") for _ in range(needed_filler_amount)]
|
||||
|
||||
self.multiworld.itempool += item_pool
|
||||
|
||||
for challenge in get_beat_challenge_events(self):
|
||||
item = Yugioh2006Item("Challenge Beaten", ItemClassification.progression, None, self.player)
|
||||
location = self.multiworld.get_location(challenge, self.player)
|
||||
location.place_locked_item(item)
|
||||
|
||||
for opponent in self.campaign_opponents:
|
||||
for location_name, event in get_opponent_locations(opponent).items():
|
||||
if event is not None and not isinstance(event, int):
|
||||
item = Yugioh2006Item(event, ItemClassification.progression, None, self.player)
|
||||
location = self.multiworld.get_location(location_name, self.player)
|
||||
location.place_locked_item(item)
|
||||
|
||||
for booster in booster_packs:
|
||||
for location_name, content in get_booster_locations(booster).items():
|
||||
item = Yugioh2006Item(content, ItemClassification.progression, None, self.player)
|
||||
location = self.multiworld.get_location(location_name, self.player)
|
||||
location.place_locked_item(item)
|
||||
|
||||
structure_deck = self.options.structure_deck.current_key
|
||||
for location_name, content in get_deck_content_locations(structure_deck).items():
|
||||
item = Yugioh2006Item(content, ItemClassification.progression, None, self.player)
|
||||
location = self.multiworld.get_location(location_name, self.player)
|
||||
location.place_locked_item(item)
|
||||
|
||||
for event in collection_events:
|
||||
item = Yugioh2006Item(event, ItemClassification.progression, None, self.player)
|
||||
location = self.multiworld.get_location(event, self.player)
|
||||
location.place_locked_item(item)
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self)
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
outfilepname = f"_P{self.player}"
|
||||
outfilepname += f"_{self.multiworld.get_file_safe_player_name(self.player).replace(' ', '_')}"
|
||||
self.rom_name_text = f'YGO06{Utils.__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed:11}\0'
|
||||
self.romName = bytearray(self.rom_name_text, "utf8")[:0x20]
|
||||
self.romName.extend([0] * (0x20 - len(self.romName)))
|
||||
self.rom_name = self.romName
|
||||
self.playerName = bytearray(self.multiworld.player_name[self.player], "utf8")[:0x20]
|
||||
self.playerName.extend([0] * (0x20 - len(self.playerName)))
|
||||
patch = YGO06ProcedurePatch(player=self.player, player_name=self.multiworld.player_name[self.player])
|
||||
patch.write_file("base_patch.bsdiff4", pkgutil.get_data(__name__, "patch.bsdiff4"))
|
||||
if self.is_draft_mode:
|
||||
patch.procedure.insert(1, ("apply_bsdiff4", ["draft_patch.bsdiff4"]))
|
||||
patch.write_file("draft_patch.bsdiff4", pkgutil.get_data(__name__, "patches/draft.bsdiff4"))
|
||||
if self.options.ocg_arts:
|
||||
patch.procedure.insert(1, ("apply_bsdiff4", ["ocg_patch.bsdiff4"]))
|
||||
patch.write_file("ocg_patch.bsdiff4", pkgutil.get_data(__name__, "patches/ocg.bsdiff4"))
|
||||
write_tokens(self, patch)
|
||||
|
||||
# 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 fill_slot_data(self) -> Dict[str, Any]:
|
||||
slot_data: Dict[str, Any] = {
|
||||
"structure_deck": self.options.structure_deck.value,
|
||||
"banlist": self.options.banlist.value,
|
||||
"final_campaign_boss_unlock_condition": self.options.final_campaign_boss_unlock_condition.value,
|
||||
"fourth_tier_5_campaign_boss_unlock_condition":
|
||||
self.options.fourth_tier_5_campaign_boss_unlock_condition.value,
|
||||
"third_tier_5_campaign_boss_unlock_condition":
|
||||
self.options.third_tier_5_campaign_boss_unlock_condition.value,
|
||||
"final_campaign_boss_challenges": self.options.final_campaign_boss_challenges.value,
|
||||
"fourth_tier_5_campaign_boss_challenges":
|
||||
self.options.fourth_tier_5_campaign_boss_challenges.value,
|
||||
"third_tier_5_campaign_boss_challenges":
|
||||
self.options.third_tier_5_campaign_boss_campaign_opponents.value,
|
||||
"final_campaign_boss_campaign_opponents":
|
||||
self.options.final_campaign_boss_campaign_opponents.value,
|
||||
"fourth_tier_5_campaign_boss_campaign_opponents":
|
||||
self.options.fourth_tier_5_campaign_boss_unlock_condition.value,
|
||||
"third_tier_5_campaign_boss_campaign_opponents":
|
||||
self.options.third_tier_5_campaign_boss_campaign_opponents.value,
|
||||
"number_of_challenges": self.options.number_of_challenges.value,
|
||||
}
|
||||
|
||||
slot_data["removed challenges"] = self.removed_challenges
|
||||
slot_data["starting_booster"] = self.starting_booster
|
||||
slot_data["starting_opponent"] = self.starting_opponent
|
||||
return slot_data
|
||||
|
||||
# for the universal tracker, doesn't get called in standard gen
|
||||
@staticmethod
|
||||
def interpret_slot_data(slot_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
# returning slot_data so it regens, giving it back in multiworld.re_gen_passthrough
|
||||
return slot_data
|
||||
|
||||
|
||||
class Yugioh2006Item(Item):
|
||||
game: str = "Yu-Gi-Oh! 2006"
|
||||
|
||||
|
||||
class Yugioh2006Location(Location):
|
||||
game: str = "Yu-Gi-Oh! 2006"
|
||||
923
worlds/yugioh06/boosterpacks.py
Normal file
923
worlds/yugioh06/boosterpacks.py
Normal file
@@ -0,0 +1,923 @@
|
||||
from typing import Dict, Set
|
||||
|
||||
booster_contents: Dict[str, Set[str]] = {
|
||||
"LEGEND OF B.E.W.D.": {
|
||||
"Exodia",
|
||||
"Dark Magician",
|
||||
"Polymerization",
|
||||
"Skull Servant"
|
||||
},
|
||||
"METAL RAIDERS": {
|
||||
"Petit Moth",
|
||||
"Cocoon of Evolution",
|
||||
"Time Wizard",
|
||||
"Gate Guardian",
|
||||
"Kazejin",
|
||||
"Suijin",
|
||||
"Sanga of the Thunder",
|
||||
"Sangan",
|
||||
"Castle of Dark Illusions",
|
||||
"Soul Release",
|
||||
"Magician of Faith",
|
||||
"Dark Elf",
|
||||
"Summoned Skull",
|
||||
"Sangan",
|
||||
"7 Colored Fish",
|
||||
"Tribute to the Doomed",
|
||||
"Horn of Heaven",
|
||||
"Magic Jammer",
|
||||
"Seven Tools of the Bandit",
|
||||
"Solemn Judgment",
|
||||
"Dream Clown",
|
||||
"Heavy Storm"
|
||||
},
|
||||
"PHARAOH'S SERVANT": {
|
||||
"Beast of Talwar",
|
||||
"Jinzo",
|
||||
"Gearfried the Iron Knight",
|
||||
"Harpie's Brother",
|
||||
"Gravity Bind",
|
||||
"Solemn Wishes",
|
||||
"Kiseitai",
|
||||
"Morphing Jar #2",
|
||||
"The Shallow Grave",
|
||||
"Nobleman of Crossout",
|
||||
"Magic Drain"
|
||||
},
|
||||
"PHARAONIC GUARDIAN": {
|
||||
"Don Zaloog",
|
||||
"Reasoning",
|
||||
"Dark Snake Syndrome",
|
||||
"Helpoemer",
|
||||
"Newdoria",
|
||||
"Spirit Reaper",
|
||||
"Yomi Ship",
|
||||
"Pyramid Turtle",
|
||||
"Master Kyonshee",
|
||||
"Book of Life",
|
||||
"Call of the Mummy",
|
||||
"Gravekeeper's Spy",
|
||||
"Gravekeeper's Guard",
|
||||
"A Cat of Ill Omen",
|
||||
"Jowls of Dark Demise",
|
||||
"Non Aggression Area",
|
||||
"Terraforming",
|
||||
"Des Lacooda",
|
||||
"Swarm of Locusts",
|
||||
"Swarm of Scarabs",
|
||||
"Wandering Mummy",
|
||||
"Royal Keeper",
|
||||
"Book of Moon",
|
||||
"Book of Taiyou",
|
||||
"Dust Tornado",
|
||||
"Raigeki Break"
|
||||
},
|
||||
"SPELL RULER": {
|
||||
"Ritual",
|
||||
"Messenger of Peace",
|
||||
"Megamorph",
|
||||
"Shining Angel",
|
||||
"Mystic Tomato",
|
||||
"Giant Rat",
|
||||
"Mother Grizzly",
|
||||
"UFO Turtle",
|
||||
"Flying Kamakiri 1",
|
||||
"Giant Germ",
|
||||
"Nimble Momonga",
|
||||
"Cyber Jar",
|
||||
"Spear Cretin",
|
||||
"Toon Mermaid",
|
||||
"Toon Summoned Skull",
|
||||
"Toon World",
|
||||
"Rush Recklessly",
|
||||
"The Reliable Guardian",
|
||||
"Senju of the Thousand Hands",
|
||||
"Sonic Bird",
|
||||
"Mystical Space Typhoon"
|
||||
},
|
||||
"LABYRINTH OF NIGHTMARE": {
|
||||
"Destiny Board",
|
||||
"Spirit Message 'I'",
|
||||
"Spirit Message 'N'",
|
||||
"Spirit Message 'A'",
|
||||
"Spirit Message 'L'",
|
||||
"Fusion Gate",
|
||||
"Jowgen the Spiritualist",
|
||||
"Fairy Box",
|
||||
"Aqua Spirit",
|
||||
"Rock Spirit",
|
||||
"Spirit of Flames",
|
||||
"Garuda the Wind Spirit",
|
||||
"Hysteric Fairy",
|
||||
"Kycoo the Ghost Destroyer",
|
||||
"Gemini Elf",
|
||||
"Amphibian Beast",
|
||||
"Revival Jam",
|
||||
"Dancing Fairy",
|
||||
"Cure Mermaid",
|
||||
"The Last Warrior from Another Planet",
|
||||
"United We Stand",
|
||||
"Earthbound Spirit",
|
||||
"The Masked Beast"
|
||||
},
|
||||
"LEGACY OF DARKNESS": {
|
||||
"Last Turn",
|
||||
"Yata-Garasu",
|
||||
"Opticlops",
|
||||
"Dark Ruler Ha Des",
|
||||
"Exiled Force",
|
||||
"Injection Fairy Lily",
|
||||
"Spear Dragon",
|
||||
"Luster Dragon #2",
|
||||
"Twin-Headed Behemoth",
|
||||
"Airknight Parshath",
|
||||
"Freed the Matchless General",
|
||||
"Marauding Captain",
|
||||
"Reinforcement of the Army",
|
||||
"Cave Dragon",
|
||||
"Troop Dragon",
|
||||
"Stamping Destruction",
|
||||
"Creature Swap",
|
||||
"Asura Priest",
|
||||
"Fushi No Tori",
|
||||
"Maharaghi",
|
||||
"Susa Soldier",
|
||||
"Emergency Provisions",
|
||||
},
|
||||
"MAGICIAN'S FORCE": {
|
||||
"Huge Revolution",
|
||||
"Oppressed People",
|
||||
"United Resistance",
|
||||
"People Running About",
|
||||
"X-Head Cannon",
|
||||
"Y-Dragon Head",
|
||||
"Z-Metal Tank",
|
||||
"XY-Dragon Cannon",
|
||||
"XZ-Tank Cannon",
|
||||
"YZ-Tank Dragon",
|
||||
"XYZ-Dragon Cannon",
|
||||
"Cliff the Trap Remover",
|
||||
"Wave-Motion Cannon",
|
||||
"Ritual",
|
||||
"Magical Merchant",
|
||||
"Poison of the Old Man",
|
||||
"Chaos Command Magician",
|
||||
"Skilled Dark Magician",
|
||||
"Dark Blade",
|
||||
"Great Angus",
|
||||
"Luster Dragon",
|
||||
"Breaker the magical Warrior",
|
||||
"Old Vindictive Magician",
|
||||
"Apprentice Magician",
|
||||
"Burning Beast",
|
||||
"Freezing Beast",
|
||||
"Pitch-Dark Dragon",
|
||||
"Giant Orc",
|
||||
"Second Goblin",
|
||||
"Decayed Commander",
|
||||
"Zombie Tiger",
|
||||
"Vampire Orchis",
|
||||
"Des Dendle",
|
||||
"Frontline Base",
|
||||
"Formation Union",
|
||||
"Pitch-Black Power Stone",
|
||||
"Magical Marionette",
|
||||
"Royal Magical Library",
|
||||
"Spell Shield Type-8",
|
||||
"Tribute Doll",
|
||||
},
|
||||
"DARK CRISIS": {
|
||||
"Final Countdown",
|
||||
"Ojama Green",
|
||||
"Dark Scorpion Combination",
|
||||
"Dark Scorpion - Chick the Yellow",
|
||||
"Dark Scorpion - Meanae the Thorn",
|
||||
"Dark Scorpion - Gorg the Strong",
|
||||
"Ritual",
|
||||
"Tsukuyomi",
|
||||
"Ojama Trio",
|
||||
"Kaiser Glider",
|
||||
"D.D. Warrior Lady",
|
||||
"Archfiend Soldier",
|
||||
"Skull Archfiend of Lightning",
|
||||
"Blindly Loyal Goblin",
|
||||
"Gagagigo",
|
||||
"Nin-Ken Dog",
|
||||
"Zolga",
|
||||
"Kelbek",
|
||||
"Mudora",
|
||||
"Cestus of Dagla",
|
||||
"Vampire Lord",
|
||||
"Metallizing Parasite - Lunatite",
|
||||
"D. D. Trainer",
|
||||
"Spell Reproduction",
|
||||
"Contract with the Abyss",
|
||||
"Dark Master - Zorc"
|
||||
},
|
||||
"INVASION OF CHAOS": {
|
||||
"Ojama Delta Hurricane",
|
||||
"Ojama Yellow",
|
||||
"Ojama Black",
|
||||
"Heart of the Underdog",
|
||||
"Chaos Emperor Dragon - Envoy of the End",
|
||||
"Self-Destruct Button",
|
||||
"Manticore of Darkness",
|
||||
"Dimension Fusion",
|
||||
"Gigantes",
|
||||
"Inferno",
|
||||
"Silpheed",
|
||||
"Mad Dog of Darkness",
|
||||
"Ryu Kokki",
|
||||
"Berserk Gorilla",
|
||||
"Neo Bug",
|
||||
"Dark Driceratops",
|
||||
"Hyper Hammerhead",
|
||||
"Sea Serpent Warrior of Darkness",
|
||||
"Giga Gagagigo",
|
||||
"Terrorking Salmon",
|
||||
"Blazing Inpachi",
|
||||
"Stealth Bird",
|
||||
"Reload",
|
||||
"Cursed Seal of the Forbidden Spell",
|
||||
"Stray Lambs",
|
||||
"Manju of the Ten Thousand Hands"
|
||||
},
|
||||
"ANCIENT SANCTUARY": {
|
||||
"Monster Gate",
|
||||
"Wall of Revealing Light",
|
||||
"Mystik Wok",
|
||||
"The Agent of Judgment - Saturn",
|
||||
"Zaborg the Thunder Monarch",
|
||||
"Regenerating Mummy",
|
||||
"The End of Anubis",
|
||||
"Solar Flare Dragon",
|
||||
"Level Limit - Area B",
|
||||
"King of the Swamp",
|
||||
"Enemy Controller",
|
||||
"Enchanting Fitting Room"
|
||||
},
|
||||
"SOUL OF THE DUELIST": {
|
||||
"Ninja Grandmaster Sasuke",
|
||||
"Mystic Swordsman LV2",
|
||||
"Mystic Swordsman LV4",
|
||||
"Enraged Muka Muka",
|
||||
"Mobius the Frost Monarch",
|
||||
"Horus the Black Flame Dragon LV6",
|
||||
"Ultimate Baseball Kid",
|
||||
"Armed Dragon LV3",
|
||||
"Armed Dragon LV5",
|
||||
"Masked Dragon",
|
||||
"Element Dragon",
|
||||
"Horus the Black Flame Dragon LV4",
|
||||
"Level Up!",
|
||||
"Howling Insect",
|
||||
"Mobius the Frost Monarch"
|
||||
},
|
||||
"RISE OF DESTINY": {
|
||||
"Homunculus the Alchemic Being",
|
||||
"Thestalos the Firestorm Monarch",
|
||||
"Roc from the Valley of Haze",
|
||||
"Harpie Lady 1",
|
||||
"Silent Swordsman Lv3",
|
||||
"Mystic Swordsman LV6",
|
||||
"Ultimate Insect Lv3",
|
||||
"Divine Wrath",
|
||||
"Serial Spell"
|
||||
},
|
||||
"FLAMING ETERNITY": {
|
||||
"Insect Knight",
|
||||
"Chiron the Mage",
|
||||
"Granmarg the Rock Monarch",
|
||||
"Silent Swordsman Lv5",
|
||||
"The Dark - Hex-Sealed Fusion",
|
||||
"The Earth - Hex-Sealed Fusion",
|
||||
"The Light - Hex-Sealed Fusion",
|
||||
"Ultimate Insect Lv5",
|
||||
"Blast Magician",
|
||||
"Golem Sentry",
|
||||
"Rescue Cat",
|
||||
"Blade Rabbit"
|
||||
},
|
||||
"THE LOST MILLENIUM": {
|
||||
"Ritual",
|
||||
"Megarock Dragon",
|
||||
"D.D. Survivor",
|
||||
"Hieracosphinx",
|
||||
"Elemental Hero Flame Wingman",
|
||||
"Elemental Hero Avian",
|
||||
"Elemental Hero Burstinatrix",
|
||||
"Elemental Hero Clayman",
|
||||
"Elemental Hero Sparkman",
|
||||
"Elemental Hero Thunder Giant",
|
||||
"Aussa the Earth Charmer",
|
||||
"Brain Control"
|
||||
},
|
||||
"CYBERNETIC REVOLUTION": {
|
||||
"Power Bond",
|
||||
"Cyber Dragon",
|
||||
"Cyber Twin Dragon",
|
||||
"Cybernetic Magician",
|
||||
"Indomitable Fighter Lei Lei",
|
||||
"Protective Soul Ailin",
|
||||
"Miracle Fusion",
|
||||
"Elemental Hero Bubbleman",
|
||||
"Jerry Beans Man"
|
||||
},
|
||||
"ELEMENTAL ENERGY": {
|
||||
"V-Tiger Jet",
|
||||
"W-Wing Catapult",
|
||||
"VW-Tiger Catapult",
|
||||
"VWXYZ-Dragon Catapult Cannon",
|
||||
"Zure, Knight of Dark World",
|
||||
"Brron, Mad King of Dark World",
|
||||
"Familiar-Possessed - Aussa",
|
||||
"Familiar-Possessed - Eria",
|
||||
"Familiar-Possessed - Hiita",
|
||||
"Familiar-Possessed - Wynn",
|
||||
"Oxygeddon",
|
||||
"Roll Out!",
|
||||
"Dark World Lightning",
|
||||
"Elemental Hero Rampart Blaster",
|
||||
"Elemental Hero Shining Flare Wingman",
|
||||
"Elemental Hero Wildedge",
|
||||
"Elemental Hero Wildheart",
|
||||
"Elemental Hero Bladedge",
|
||||
"Pot of Avarice",
|
||||
"B.E.S. Tetran"
|
||||
},
|
||||
"SHADOW OF INFINITY": {
|
||||
"Hamon, Lord of Striking Thunder",
|
||||
"Raviel, Lord of Phantasms",
|
||||
"Uria, Lord of Searing Flames",
|
||||
"Ritual",
|
||||
"Treeborn Frog",
|
||||
"Saber Beetle",
|
||||
"Tenkabito Shien",
|
||||
"Princess Pikeru",
|
||||
"Gokipon",
|
||||
"Demise, King of Armageddon",
|
||||
"Anteatereatingant"
|
||||
},
|
||||
"GAME GIFT COLLECTION": {
|
||||
"Ritual",
|
||||
"Valkyrion the Magna Warrior",
|
||||
"Alpha the Magnet Warrior",
|
||||
"Beta the Magnet Warrior",
|
||||
"Gamma the Magnet Warrior",
|
||||
"Magical Blast",
|
||||
"Dunames Dark Witch",
|
||||
"Vorse Raider",
|
||||
"Exarion Universe",
|
||||
"Abyss Soldier",
|
||||
"Slate Warrior",
|
||||
"Cyber-Tech Alligator",
|
||||
"D.D. Assailant",
|
||||
"Goblin Zombie",
|
||||
"Elemental Hero Madballman",
|
||||
"Mind Control",
|
||||
"Toon Dark Magician Girl",
|
||||
"Great Spirit",
|
||||
"Graceful Dice",
|
||||
"Negate Attack",
|
||||
"Foolish Burial",
|
||||
"Card Destruction",
|
||||
"Dark Magic Ritual",
|
||||
"Calamity of the Wicked"
|
||||
},
|
||||
"Special Gift Collection": {
|
||||
"Gate Guardian",
|
||||
"Scapegoat",
|
||||
"Gil Garth",
|
||||
"La Jinn the Mystical Genie of the Lamp",
|
||||
"Summoned Skull",
|
||||
"Inferno Hammer",
|
||||
"Gemini Elf",
|
||||
"Cyber Harpie Lady",
|
||||
"Dandylion",
|
||||
"Blade Knight",
|
||||
"Curse of Vampire",
|
||||
"Elemental Hero Flame Wingman",
|
||||
"Magician of Black Chaos"
|
||||
},
|
||||
"Fairy Collection": {
|
||||
"Silpheed",
|
||||
"Dunames Dark Witch",
|
||||
"Hysteric Fairy",
|
||||
"The Agent of Judgment - Saturn",
|
||||
"Shining Angel",
|
||||
"Airknight Parshath",
|
||||
"Dancing Fairy",
|
||||
"Zolga",
|
||||
"Kelbek",
|
||||
"Mudora",
|
||||
"Protective Soul Ailin",
|
||||
"Marshmallon",
|
||||
"Goddess with the Third Eye",
|
||||
"Asura Priest",
|
||||
"Manju of the Ten Thousand Hands",
|
||||
"Senju of the Thousand Hands"
|
||||
},
|
||||
"Dragon Collection": {
|
||||
"Victory D.",
|
||||
"Chaos Emperor Dragon - Envoy of the End",
|
||||
"Kaiser Glider",
|
||||
"Horus the Black Flame Dragon LV6",
|
||||
"Luster Dragon",
|
||||
"Luster Dragon #2"
|
||||
"Spear Dragon",
|
||||
"Armed Dragon LV3",
|
||||
"Armed Dragon LV5",
|
||||
"Twin-Headed Behemoth",
|
||||
"Cave Dragon",
|
||||
"Masked Dragon",
|
||||
"Element Dragon",
|
||||
"Troop Dragon",
|
||||
"Horus the Black Flame Dragon LV4",
|
||||
"Pitch-Dark Dragon"
|
||||
},
|
||||
"Warrior Collection A": {
|
||||
"Gate Guardian",
|
||||
"Gearfried the Iron Knight",
|
||||
"Dimensional Warrior",
|
||||
"Command Knight",
|
||||
"The Last Warrior from Another Planet",
|
||||
"Dream Clown"
|
||||
},
|
||||
"Warrior Collection B": {
|
||||
"Don Zaloog",
|
||||
"Dark Scorpion - Chick the Yellow",
|
||||
"Dark Scorpion - Meanae the Thorn",
|
||||
"Dark Scorpion - Gorg the Strong",
|
||||
"Cliff the Trap Remover",
|
||||
"Ninja Grandmaster Sasuke",
|
||||
"D.D. Warrior Lady",
|
||||
"Mystic Swordsman LV2",
|
||||
"Mystic Swordsman LV4",
|
||||
"Mystic Swordsman LV6",
|
||||
"Dark Blade",
|
||||
"Blindly Loyal Goblin",
|
||||
"Exiled Force",
|
||||
"Ultimate Baseball Kid",
|
||||
"Freed the Matchless General",
|
||||
"Holy Knight Ishzark",
|
||||
"Silent Swordsman Lv3",
|
||||
"Silent Swordsman Lv5",
|
||||
"Warrior Lady of the Wasteland",
|
||||
"D.D. Assailant",
|
||||
"Blade Knight",
|
||||
"Marauding Captain",
|
||||
"Toon Goblin Attack Force"
|
||||
},
|
||||
"Fiend Collection A": {
|
||||
"Sangan",
|
||||
"Castle of Dark Illusions",
|
||||
"Barox",
|
||||
"La Jinn the Mystical Genie of the Lamp",
|
||||
"Summoned Skull",
|
||||
"Beast of Talwar",
|
||||
"Sangan",
|
||||
"Giant Germ",
|
||||
"Spear Cretin",
|
||||
"Versago the Destroyer",
|
||||
"Toon Summoned Skull"
|
||||
},
|
||||
"Fiend Collection B": {
|
||||
"Raviel, Lord of Phantasms",
|
||||
"Yata-Garasu",
|
||||
"Helpoemer",
|
||||
"Archfiend Soldier",
|
||||
"Skull Descovery Knight",
|
||||
"Gil Garth",
|
||||
"Opticlops",
|
||||
"Zure, Knight of Dark World",
|
||||
"Brron, Mad King of Dark World",
|
||||
"D.D. Survivor",
|
||||
"Skull Archfiend of Lightning",
|
||||
"The End of Anubis",
|
||||
"Dark Ruler Ha Des",
|
||||
"Inferno Hammer",
|
||||
"Legendary Fiend",
|
||||
"Newdoria",
|
||||
"Slate Warrior",
|
||||
"Giant Orc",
|
||||
"Second Goblin",
|
||||
"Kiseitai",
|
||||
"Jowls of Dark Demise",
|
||||
"D. D. Trainer",
|
||||
"Earthbound Spirit"
|
||||
},
|
||||
"Machine Collection A": {
|
||||
"Cyber-Stein",
|
||||
"Mechanicalchaser",
|
||||
"Jinzo",
|
||||
"UFO Turtle",
|
||||
"Cyber-Tech Alligator"
|
||||
},
|
||||
"Machine Collection B": {
|
||||
"X-Head Cannon",
|
||||
"Y-Dragon Head",
|
||||
"Z-Metal Tank",
|
||||
"XY-Dragon Cannon",
|
||||
"XZ-Tank Cannon",
|
||||
"YZ-Tank Dragon",
|
||||
"XYZ-Dragon Cannon",
|
||||
"V-Tiger Jet",
|
||||
"W-Wing Catapult",
|
||||
"VW-Tiger Catapult",
|
||||
"VWXYZ-Dragon Catapult Cannon",
|
||||
"Cyber Dragon",
|
||||
"Cyber Twin Dragon",
|
||||
"Green Gadget",
|
||||
"Red Gadget",
|
||||
"Yellow Gadget",
|
||||
"B.E.S. Tetran"
|
||||
},
|
||||
"Spellcaster Collection A": {
|
||||
"Exodia",
|
||||
"Dark Sage",
|
||||
"Dark Magician",
|
||||
"Time Wizard",
|
||||
"Kazejin",
|
||||
"Magician of Faith",
|
||||
"Dark Elf",
|
||||
"Gemini Elf",
|
||||
"Injection Fairy Lily",
|
||||
"Cosmo Queen",
|
||||
"Magician of Black Chaos"
|
||||
},
|
||||
"Spellcaster Collection B": {
|
||||
"Jowgen the Spiritualist",
|
||||
"Tsukuyomi",
|
||||
"Manticore of Darkness",
|
||||
"Chaos Command Magician",
|
||||
"Cybernetic Magician",
|
||||
"Skilled Dark Magician",
|
||||
"Kycoo the Ghost Destroyer",
|
||||
"Toon Gemini Elf",
|
||||
"Toon Masked Sorcerer",
|
||||
"Toon Dark Magician Girl",
|
||||
"Familiar-Possessed - Aussa",
|
||||
"Familiar-Possessed - Eria",
|
||||
"Familiar-Possessed - Hiita",
|
||||
"Familiar-Possessed - Wynn",
|
||||
"Breaker the magical Warrior",
|
||||
"The Tricky",
|
||||
"Gravekeeper's Spy",
|
||||
"Gravekeeper's Guard",
|
||||
"Summon Priest",
|
||||
"Old Vindictive Magician",
|
||||
"Apprentice Magician",
|
||||
"Princess Pikeru",
|
||||
"Blast Magician",
|
||||
"Magical Marionette",
|
||||
"Mythical Beast Cerberus",
|
||||
"Royal Magical Library",
|
||||
"Aussa the Earth Charmer",
|
||||
|
||||
},
|
||||
"Zombie Collection": {
|
||||
"Skull Servant",
|
||||
"Regenerating Mummy",
|
||||
"Ryu Kokki",
|
||||
"Spirit Reaper",
|
||||
"Pyramid Turtle",
|
||||
"Master Kyonshee",
|
||||
"Curse of Vampire",
|
||||
"Vampire Lord",
|
||||
"Goblin Zombie",
|
||||
"Decayed Commander",
|
||||
"Zombie Tiger",
|
||||
"Des Lacooda",
|
||||
"Wandering Mummy",
|
||||
"Royal Keeper"
|
||||
},
|
||||
"Special Monsters A": {
|
||||
"X-Head Cannon",
|
||||
"Y-Dragon Head",
|
||||
"Z-Metal Tank",
|
||||
"V-Tiger Jet",
|
||||
"W-Wing Catapult",
|
||||
"Yata-Garasu",
|
||||
"Tsukuyomi",
|
||||
"Dark Blade",
|
||||
"Toon Gemini Elf",
|
||||
"Toon Goblin Attack Force",
|
||||
"Toon Masked Sorcerer",
|
||||
"Toon Mermaid",
|
||||
"Toon Dark Magician Girl",
|
||||
"Toon Summoned Skull",
|
||||
"Toon World",
|
||||
"Burning Beast",
|
||||
"Freezing Beast",
|
||||
"Metallizing Parasite - Lunatite",
|
||||
"Pitch-Dark Dragon",
|
||||
"Giant Orc",
|
||||
"Second Goblin",
|
||||
"Decayed Commander",
|
||||
"Zombie Tiger",
|
||||
"Vampire Orchis",
|
||||
"Des Dendle",
|
||||
"Indomitable Fighter Lei Lei",
|
||||
"Protective Soul Ailin",
|
||||
"Frontline Base",
|
||||
"Formation Union",
|
||||
"Roll Out!",
|
||||
"Asura Priest",
|
||||
"Fushi No Tori",
|
||||
"Maharaghi",
|
||||
"Susa Soldier"
|
||||
},
|
||||
"Special Monsters B": {
|
||||
"Polymerization",
|
||||
"Mystic Swordsman LV2",
|
||||
"Mystic Swordsman LV4",
|
||||
"Mystic Swordsman LV6",
|
||||
"Horus the Black Flame Dragon LV6",
|
||||
"Horus the Black Flame Dragon LV4",
|
||||
"Armed Dragon LV3"
|
||||
"Armed Dragon LV5",
|
||||
"Silent Swordsman Lv3",
|
||||
"Silent Swordsman Lv5",
|
||||
"Elemental Hero Flame Wingman",
|
||||
"Elemental Hero Avian",
|
||||
"Elemental Hero Burstinatrix",
|
||||
"Miracle Fusion",
|
||||
"Elemental Hero Madballman",
|
||||
"Elemental Hero Bubbleman",
|
||||
"Elemental Hero Clayman",
|
||||
"Elemental Hero Rampart Blaster",
|
||||
"Elemental Hero Shining Flare Wingman",
|
||||
"Elemental Hero Sparkman",
|
||||
"Elemental Hero Steam Healer",
|
||||
"Elemental Hero Thunder Giant",
|
||||
"Elemental Hero Wildedge",
|
||||
"Elemental Hero Wildheart",
|
||||
"Elemental Hero Bladedge",
|
||||
"Level Up!",
|
||||
"Ultimate Insect Lv3",
|
||||
"Ultimate Insect Lv5"
|
||||
},
|
||||
"Reverse Collection": {
|
||||
"Magical Merchant",
|
||||
"Castle of Dark Illusions",
|
||||
"Magician of Faith",
|
||||
"Penguin Soldier",
|
||||
"Blade Knight",
|
||||
"Gravekeeper's Spy",
|
||||
"Gravekeeper's Guard",
|
||||
"Old Vindictive Magician",
|
||||
"A Cat of Ill Omen",
|
||||
"Jowls of Dark Demise",
|
||||
"Cyber Jar",
|
||||
"Morphing Jar",
|
||||
"Morphing Jar #2",
|
||||
"Needle Worm",
|
||||
"Spear Cretin",
|
||||
"Nobleman of Crossout",
|
||||
"Aussa the Earth Charmer"
|
||||
},
|
||||
"LP Recovery Collection": {
|
||||
"Mystik Wok",
|
||||
"Poison of the Old Man",
|
||||
"Hysteric Fairy",
|
||||
"Dancing Fairy",
|
||||
"Zolga",
|
||||
"Cestus of Dagla",
|
||||
"Nimble Momonga",
|
||||
"Solemn Wishes",
|
||||
"Cure Mermaid",
|
||||
"Princess Pikeru",
|
||||
"Kiseitai",
|
||||
"Elemental Hero Steam Healer",
|
||||
"Fushi No Tori",
|
||||
"Emergency Provisions"
|
||||
},
|
||||
"Special Summon Collection A": {
|
||||
"Perfectly Ultimate Great Moth",
|
||||
"Dark Sage",
|
||||
"Polymerization",
|
||||
"Ritual",
|
||||
"Cyber-Stein",
|
||||
"Scapegoat",
|
||||
"Aqua Spirit",
|
||||
"Rock Spirit",
|
||||
"Spirit of Flames",
|
||||
"Garuda the Wind Spirit",
|
||||
"Shining Angel",
|
||||
"Mystic Tomato",
|
||||
"Giant Rat",
|
||||
"Mother Grizzly",
|
||||
"UFO Turtle",
|
||||
"Flying Kamakiri 1",
|
||||
"Giant Germ",
|
||||
"Revival Jam",
|
||||
"Pyramid Turtle",
|
||||
"Troop Dragon",
|
||||
"Gravekeeper's Spy",
|
||||
"Pitch-Dark Dragon",
|
||||
"Decayed Commander",
|
||||
"Zombie Tiger",
|
||||
"Vampire Orchis",
|
||||
"Des Dendle",
|
||||
"Nimble Momonga",
|
||||
"The Last Warrior from Another Planet",
|
||||
"Embodiment of Apophis",
|
||||
"Cyber Jar",
|
||||
"Morphing Jar #2",
|
||||
"Spear Cretin",
|
||||
"Dark Magic Curtain"
|
||||
},
|
||||
"Special Summon Collection B": {
|
||||
"Monster Gate",
|
||||
"Chaos Emperor Dragon - Envoy of the End",
|
||||
"Ojama Trio",
|
||||
"Dimension Fusion",
|
||||
"Return from the Different Dimension",
|
||||
"Gigantes",
|
||||
"Inferno",
|
||||
"Silpheed",
|
||||
"Mystic Swordsman LV2",
|
||||
"Mystic Swordsman LV4",
|
||||
"Skilled Dark Magician",
|
||||
"Horus the Black Flame Dragon LV6",
|
||||
"Armed Dragon LV3",
|
||||
"Armed Dragon LV5",
|
||||
"Marauding Captain",
|
||||
"Masked Dragon",
|
||||
"The Tricky",
|
||||
"Magical Dimension",
|
||||
"Frontline Base",
|
||||
"Formation Union",
|
||||
"Princess Pikeru",
|
||||
"Skull Zoma",
|
||||
"Metal Reflect Slime"
|
||||
"Level Up!",
|
||||
"Howling Insect",
|
||||
"Tribute Doll",
|
||||
"Enchanting Fitting Room",
|
||||
"Stray Lambs"
|
||||
},
|
||||
"Special Summon Collection C": {
|
||||
"Hamon, Lord of Striking Thunder",
|
||||
"Raviel, Lord of Phantasms",
|
||||
"Uria, Lord of Searing Flames",
|
||||
"Treeborn Frog",
|
||||
"Cyber Dragon",
|
||||
"Familiar-Possessed - Aussa",
|
||||
"Familiar-Possessed - Eria",
|
||||
"Familiar-Possessed - Hiita",
|
||||
"Familiar-Possessed - Wynn",
|
||||
"Silent Swordsman Lv3",
|
||||
"Silent Swordsman Lv5",
|
||||
"Warrior Lady of the Wasteland",
|
||||
"Dandylion",
|
||||
"Curse of Vampire",
|
||||
"Summon Priest",
|
||||
"Miracle Fusion",
|
||||
"Elemental Hero Bubbleman",
|
||||
"The Dark - Hex-Sealed Fusion",
|
||||
"The Earth - Hex-Sealed Fusion",
|
||||
"The Light - Hex-Sealed Fusion",
|
||||
"Ultimate Insect Lv3",
|
||||
"Ultimate Insect Lv5",
|
||||
"Rescue Cat",
|
||||
"Anteatereatingant"
|
||||
},
|
||||
"Equipment Collection": {
|
||||
"Megamorph",
|
||||
"Cestus of Dagla",
|
||||
"United We Stand"
|
||||
},
|
||||
"Continuous Spell/Trap A": {
|
||||
"Destiny Board",
|
||||
"Spirit Message 'I'",
|
||||
"Spirit Message 'N'",
|
||||
"Spirit Message 'A'",
|
||||
"Spirit Message 'L'",
|
||||
"Messenger of Peace",
|
||||
"Fairy Box",
|
||||
"Ultimate Offering",
|
||||
"Gravity Bind",
|
||||
"Solemn Wishes",
|
||||
"Embodiment of Apophis",
|
||||
"Toon World"
|
||||
},
|
||||
"Continuous Spell/Trap B": {
|
||||
"Hamon, Lord of Striking Thunder",
|
||||
"Uria, Lord of Searing Flames",
|
||||
"Wave-Motion Cannon",
|
||||
"Heart of the Underdog",
|
||||
"Wall of Revealing Light",
|
||||
"Dark Snake Syndrome",
|
||||
"Call of the Mummy",
|
||||
"Frontline Base",
|
||||
"Level Limit - Area B",
|
||||
"Skull Zoma",
|
||||
"Pitch-Black Power Stone",
|
||||
"Metal Reflect Slime"
|
||||
},
|
||||
"Quick/Counter Collection": {
|
||||
"Mystik Wok",
|
||||
"Poison of the Old Man",
|
||||
"Scapegoat",
|
||||
"Magical Dimension",
|
||||
"Enemy Controller",
|
||||
"Collapse",
|
||||
"Emergency Provisions",
|
||||
"Graceful Dice",
|
||||
"Offerings to the Doomed",
|
||||
"Reload",
|
||||
"Rush Recklessly",
|
||||
"The Reliable Guardian",
|
||||
"Cursed Seal of the Forbidden Spell",
|
||||
"Divine Wrath",
|
||||
"Horn of Heaven",
|
||||
"Magic Drain",
|
||||
"Magic Jammer",
|
||||
"Negate Attack",
|
||||
"Seven Tools of the Bandit",
|
||||
"Solemn Judgment",
|
||||
"Spell Shield Type-8",
|
||||
"Book of Moon",
|
||||
"Serial Spell",
|
||||
"Mystical Space Typhoon"
|
||||
},
|
||||
"Direct Damage Collection": {
|
||||
"Hamon, Lord of Striking Thunder",
|
||||
"Chaos Emperor Dragon - Envoy of the End",
|
||||
"Dark Snake Syndrome",
|
||||
"Inferno",
|
||||
"Exarion Universe",
|
||||
"Kycoo the Ghost Destroyer",
|
||||
"Giant Germ",
|
||||
"Familiar-Possessed - Aussa",
|
||||
"Familiar-Possessed - Eria",
|
||||
"Familiar-Possessed - Hiita",
|
||||
"Familiar-Possessed - Wynn",
|
||||
"Dark Driceratops",
|
||||
"Saber Beetle",
|
||||
"Thestalos the Firestorm Monarch",
|
||||
"Solar Flare Dragon",
|
||||
"Ultimate Baseball Kid",
|
||||
"Spear Dragon",
|
||||
"Oxygeddon",
|
||||
"Airknight Parshath",
|
||||
"Vampire Lord",
|
||||
"Stamping Destruction",
|
||||
"Decayed Commander",
|
||||
"Jowls of Dark Demise",
|
||||
"Stealth Bird",
|
||||
"Elemental Hero Bladedge",
|
||||
},
|
||||
"Direct Attack Collection": {
|
||||
"Victory D.",
|
||||
"Dark Scorpion Combination",
|
||||
"Spirit Reaper",
|
||||
"Elemental Hero Rampart Blaster",
|
||||
"Toon Gemini Elf",
|
||||
"Toon Goblin Attack Force",
|
||||
"Toon Masked Sorcerer",
|
||||
"Toon Mermaid",
|
||||
"Toon Summoned Skull",
|
||||
"Toon Dark Magician Girl"
|
||||
},
|
||||
"Monster Destroy Collection": {
|
||||
"Hamon, Lord of Striking Thunder",
|
||||
"Inferno",
|
||||
"Ninja Grandmaster Sasuke",
|
||||
"Zaborg the Thunder Monarch",
|
||||
"Mystic Swordsman LV2",
|
||||
"Mystic Swordsman LV4",
|
||||
"Mystic Swordsman LV6",
|
||||
"Skull Descovery Knight",
|
||||
"Inferno Hammer",
|
||||
"Ryu Kokki",
|
||||
"Newdoria",
|
||||
"Exiled Force",
|
||||
"Yomi Ship",
|
||||
"Armed Dragon LV5",
|
||||
"Element Dragon",
|
||||
"Old Vindictive Magician",
|
||||
"Magical Dimension",
|
||||
"Des Dendle",
|
||||
"Nobleman of Crossout",
|
||||
"Shield Crash",
|
||||
"Tribute to the Doomed",
|
||||
"Elemental Hero Flame Wingman",
|
||||
"Elemental Hero Shining Flare Wingman",
|
||||
"Elemental Hero Steam Healer",
|
||||
"Blast Magician",
|
||||
"Magical Marionette",
|
||||
"Swarm of Scarabs",
|
||||
"Offerings to the Doomed",
|
||||
"Divine Wrath",
|
||||
"Dream Clown"
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_booster_locations(booster: str) -> Dict[str, str]:
|
||||
return {
|
||||
f"{booster} {i}": content
|
||||
for i, content in enumerate(booster_contents[booster])
|
||||
}
|
||||
139
worlds/yugioh06/client_bh.py
Normal file
139
worlds/yugioh06/client_bh.py
Normal file
@@ -0,0 +1,139 @@
|
||||
import math
|
||||
from typing import TYPE_CHECKING, List, Optional, Set
|
||||
|
||||
from NetUtils import ClientStatus, NetworkItem
|
||||
|
||||
import worlds._bizhawk as bizhawk
|
||||
from worlds._bizhawk.client import BizHawkClient
|
||||
from worlds.yugioh06 import item_to_index
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from worlds._bizhawk.context import BizHawkClientContext
|
||||
|
||||
|
||||
class YuGiOh2006Client(BizHawkClient):
|
||||
game = "Yu-Gi-Oh! 2006"
|
||||
system = "GBA"
|
||||
patch_suffix = ".apygo06"
|
||||
local_checked_locations: Set[int]
|
||||
goal_flag: int
|
||||
rom_slot_name: Optional[str]
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.local_checked_locations = set()
|
||||
self.rom_slot_name = None
|
||||
|
||||
async def validate_rom(self, ctx: "BizHawkClientContext") -> bool:
|
||||
from CommonClient import logger
|
||||
|
||||
try:
|
||||
# Check if ROM is some version of Yu-Gi-Oh! 2006
|
||||
game_name = ((await bizhawk.read(ctx.bizhawk_ctx, [(0xA0, 11, "ROM")]))[0]).decode("ascii")
|
||||
if game_name != "YUGIOHWCT06":
|
||||
return False
|
||||
|
||||
# Check if we can read the slot name. Doing this here instead of set_auth as a protection against
|
||||
# validating a ROM where there's no slot name to read.
|
||||
try:
|
||||
slot_name_bytes = (await bizhawk.read(ctx.bizhawk_ctx, [(0x30, 32, "ROM")]))[0]
|
||||
self.rom_slot_name = bytes([byte for byte in slot_name_bytes if byte != 0]).decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
logger.info("Could not read slot name from ROM. Are you sure this ROM matches this client version?")
|
||||
return False
|
||||
except UnicodeDecodeError:
|
||||
return False
|
||||
except bizhawk.RequestFailedError:
|
||||
return False # Should verify on the next pass
|
||||
|
||||
ctx.game = self.game
|
||||
ctx.items_handling = 0b001
|
||||
ctx.want_slot_data = False
|
||||
return True
|
||||
|
||||
async def set_auth(self, ctx: "BizHawkClientContext") -> None:
|
||||
ctx.auth = self.rom_slot_name
|
||||
|
||||
async def game_watcher(self, ctx: "BizHawkClientContext") -> None:
|
||||
try:
|
||||
read_state = await bizhawk.read(
|
||||
ctx.bizhawk_ctx,
|
||||
[
|
||||
(0x0, 8, "EWRAM"),
|
||||
(0x52E8, 32, "EWRAM"),
|
||||
(0x5308, 32, "EWRAM"),
|
||||
(0x5325, 1, "EWRAM"),
|
||||
(0x6C38, 4, "EWRAM"),
|
||||
],
|
||||
)
|
||||
game_state = read_state[0].decode("utf-8")
|
||||
locations = read_state[1]
|
||||
items = read_state[2]
|
||||
amount_items = int.from_bytes(read_state[3], "little")
|
||||
money = int.from_bytes(read_state[4], "little")
|
||||
|
||||
# make sure save was created
|
||||
if game_state != "YWCT2006":
|
||||
return
|
||||
local_items = bytearray(items)
|
||||
await bizhawk.guarded_write(
|
||||
ctx.bizhawk_ctx,
|
||||
[(0x5308, parse_items(bytearray(items), ctx.items_received), "EWRAM")],
|
||||
[(0x5308, local_items, "EWRAM")],
|
||||
)
|
||||
money_received = 0
|
||||
for item in ctx.items_received:
|
||||
if item.item == item_to_index["5000DP"] + 5730000:
|
||||
money_received += 1
|
||||
if money_received > amount_items:
|
||||
await bizhawk.guarded_write(
|
||||
ctx.bizhawk_ctx,
|
||||
[
|
||||
(0x6C38, (money + (money_received - amount_items) * 5000).to_bytes(4, "little"), "EWRAM"),
|
||||
(0x5325, money_received.to_bytes(2, "little"), "EWRAM"),
|
||||
],
|
||||
[
|
||||
(0x6C38, money.to_bytes(4, "little"), "EWRAM"),
|
||||
(0x5325, amount_items.to_bytes(2, "little"), "EWRAM"),
|
||||
],
|
||||
)
|
||||
|
||||
locs_to_send = set()
|
||||
|
||||
# Check for set location flags.
|
||||
for byte_i, byte in enumerate(bytearray(locations)):
|
||||
for i in range(8):
|
||||
and_value = 1 << i
|
||||
if byte & and_value != 0:
|
||||
flag_id = byte_i * 8 + i
|
||||
|
||||
location_id = flag_id + 5730001
|
||||
if location_id in ctx.server_locations:
|
||||
locs_to_send.add(location_id)
|
||||
|
||||
# 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)}])
|
||||
|
||||
# Send game clear if we're in either any ending cutscene or the credits state.
|
||||
if not ctx.finished_game and locations[18] & (1 << 5) != 0:
|
||||
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
|
||||
|
||||
except bizhawk.RequestFailedError:
|
||||
# Exit handler and return to main loop to reconnect.
|
||||
pass
|
||||
|
||||
|
||||
# Parses bit-map for local items and adds the received items to that bit-map
|
||||
def parse_items(local_items: bytearray, items: List[NetworkItem]) -> bytearray:
|
||||
array = local_items
|
||||
for item in items:
|
||||
index = item.item - 5730001
|
||||
if index != 254:
|
||||
byte = math.floor(index / 8)
|
||||
bit = index % 8
|
||||
array[byte] = array[byte] | (1 << bit)
|
||||
return array
|
||||
53
worlds/yugioh06/docs/en_Yu-Gi-Oh! 2006.md
Normal file
53
worlds/yugioh06/docs/en_Yu-Gi-Oh! 2006.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006
|
||||
|
||||
## 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?
|
||||
|
||||
Unlocking Booster Packs, Campaign, Limited and Theme Duel Opponents has been changed.
|
||||
You only need to beat each Campaign Opponent once.
|
||||
Logic expects you to have access to the Booster Packs necessary to get the locations at a reasonable pace and consistency.
|
||||
Logic remains, so the game is always able to be completed, but because of the shuffle, the player may need to defeat certain opponents before they
|
||||
would in the vanilla game.
|
||||
|
||||
You can change how much money you receive and how much booster packs cost.
|
||||
|
||||
## What is the goal of Yu-Gi-Oh! 2006 when randomized?
|
||||
|
||||
Defeat a certain amount of Limited/Theme Duels to Unlock the final Campaign Opponent and beat it.
|
||||
|
||||
## What items and locations get shuffled?
|
||||
|
||||
Locations in which items can be found:
|
||||
- Getting a Duel Bonus for the first time
|
||||
- Beating a certain amount campaign opponents of the same level.
|
||||
- Beating a Limited/Theme Duel
|
||||
- Obtaining certain cards (same that unlock a theme duel in vanilla)
|
||||
|
||||
Items that are shuffled:
|
||||
- Unlocking Booster Packs (the "ALL" Booster Packs are excluded)
|
||||
- Unlocking Campaign Opponents
|
||||
- Unlocking Limited/Theme Duels
|
||||
- Banlists
|
||||
|
||||
## What items are _not_ randomized?
|
||||
Certain Key Items are kept in their original locations:
|
||||
- Duel Puzzles
|
||||
- Survival Mode
|
||||
- Booster Pack Contents
|
||||
|
||||
## Which items can be in another player's world?
|
||||
|
||||
Any shuffled item can be in other players' worlds.
|
||||
|
||||
|
||||
## What does another world's item look like in Yu-Gi-Oh! 2006?
|
||||
|
||||
You can only tell when and what you got via the client.
|
||||
|
||||
## When the player receives an item, what happens?
|
||||
|
||||
The Opponent/Pack becomes available to you.
|
||||
72
worlds/yugioh06/docs/setup_en.md
Normal file
72
worlds/yugioh06/docs/setup_en.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Setup Guide for Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006 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.7.0 and later are supported.
|
||||
- Detailed installation instructions for Bizhawk can be found at the above link.
|
||||
- Windows users must run the prereq installer first, which can also be found at the above link.
|
||||
- The built-in Archipelago client, which can be installed [here](https://github.com/ArchipelagoMW/Archipelago/releases)
|
||||
- A US or European Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006 Rom
|
||||
|
||||
## Configuring Bizhawk
|
||||
|
||||
Once Bizhawk has been installed, open Bizhawk and change the following settings:
|
||||
|
||||
- Go to Config > Customize. Switch to the Advanced tab, then switch the Lua Core from "NLua+KopiLua" to
|
||||
"Lua+LuaInterface". This is required for the Lua script to function correctly.
|
||||
**NOTE: Even if "Lua+LuaInterface" is already selected, toggle between the two options and reselect it. Fresh installs**
|
||||
**of newer versions of Bizhawk have a tendency to show "Lua+LuaInterface" as the default selected option but still load**
|
||||
**"NLua+KopiLua" until this step is done.**
|
||||
- Under Config > Customize > Advanced, make sure the box for AutoSaveRAM is checked, and click the 5s button.
|
||||
This reduces the possibility of losing save data in emulator crashes.
|
||||
- Under Config > Customize, check the "Run in background" and "Accept background input" boxes. This will allow you to
|
||||
continue playing in the background, even if another window is selected, such as the Client.
|
||||
- Under Config > Hotkeys, many hotkeys are listed, with many bound to common keys on the keyboard. You will likely want
|
||||
to disable most of these, which you can do quickly using `Esc`.
|
||||
|
||||
It is strongly recommended to associate GBA rom extensions (\*.gba) to the Bizhawk we've just installed.
|
||||
To do so, we simply have to search any GBA rom we happened to own, right click and select "Open with...", unfold
|
||||
the list that appears and select the bottom option "Look for another application", then browse to the Bizhawk folder
|
||||
and select EmuHawk.exe.
|
||||
|
||||
## Configuring your YAML file
|
||||
|
||||
### What is a YAML file and why do I need one?
|
||||
|
||||
Your YAML file contains a set of configuration options which provide the generator with information about how it should
|
||||
generate your game. Each player of a multiworld will provide their own YAML file. This setup allows each player to enjoy
|
||||
an experience customized for their taste, and different players in the same multiworld can all have different options.
|
||||
|
||||
### Where do I get a YAML file?
|
||||
|
||||
You can customize your options by visiting the
|
||||
[Yu-Gi-Oh! 2006 Player Options Page](/games/Yu-Gi-Oh!%202006/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 `.apygo06` extension.
|
||||
|
||||
Double-click on your `.apygo06` file to start your client and start the ROM patch process. Once the process is finished
|
||||
(this can take a while), the client and the emulator will be started automatically (if you associated the extension
|
||||
to the emulator as recommended).
|
||||
|
||||
### Connect to the Multiserver
|
||||
|
||||
Once both the client and the emulator are started, you must connect them. Within the emulator click on the "Tools"
|
||||
menu and select "Lua Console". Click the folder button or press Ctrl+O to open a Lua script.
|
||||
|
||||
Navigate to your Archipelago install folder and open `data/lua/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]`)
|
||||
|
||||
Don't forget to start manipulating RNG early by shouting "Heart of the Cards!" during generation.
|
||||
72
worlds/yugioh06/fusions.py
Normal file
72
worlds/yugioh06/fusions.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from typing import List, NamedTuple
|
||||
|
||||
|
||||
class FusionData(NamedTuple):
|
||||
name: str
|
||||
materials: List[str]
|
||||
replaceable: bool
|
||||
additional_spells: List[str]
|
||||
|
||||
|
||||
fusions = {
|
||||
"Elemental Hero Flame Wingman": FusionData(
|
||||
"Elemental Hero Flame Wingman",
|
||||
["Elemental Hero Avian", "Elemental Hero Burstinatrix"],
|
||||
True,
|
||||
["Miracle Fusion"]),
|
||||
"Elemental Hero Madballman": FusionData(
|
||||
"Elemental Hero Madballman",
|
||||
["Elemental Hero Bubbleman", "Elemental Hero Clayman"],
|
||||
True,
|
||||
["Miracle Fusion"]),
|
||||
"Elemental Hero Rampart Blaster": FusionData(
|
||||
"Elemental Hero Rampart Blaster",
|
||||
["Elemental Hero Burstinatrix", "Elemental Hero Clayman"],
|
||||
True,
|
||||
["Miracle Fusion"]),
|
||||
"Elemental Hero Shining Flare Wingman": FusionData(
|
||||
"Elemental Hero Shining Flare Wingman",
|
||||
["Elemental Hero Flame Wingman", "Elemental Hero Sparkman"],
|
||||
True,
|
||||
["Miracle Fusion"]),
|
||||
"Elemental Hero Steam Healer": FusionData(
|
||||
"Elemental Hero Steam Healer",
|
||||
["Elemental Hero Burstinatrix", "Elemental Hero Bubbleman"],
|
||||
True,
|
||||
["Miracle Fusion"]),
|
||||
"Elemental Hero Wildedge": FusionData(
|
||||
"Elemental Hero Wildedge",
|
||||
["Elemental Hero Wildheart", "Elemental Hero Bladedge"],
|
||||
True,
|
||||
["Miracle Fusion"])
|
||||
}
|
||||
|
||||
fusion_subs = ["The Dark - Hex-Sealed Fusion",
|
||||
"The Earth - Hex-Sealed Fusion",
|
||||
"The Light - Hex-Sealed Fusion",
|
||||
"Goddess with the Third Eye",
|
||||
"King of the Swamp",
|
||||
"Versago the Destroyer",
|
||||
# Only in All-packs
|
||||
"Beastking of the Swamps",
|
||||
"Mystical Sheep #1"]
|
||||
|
||||
|
||||
def has_all_materials(state, monster, player):
|
||||
data = fusions.get(monster)
|
||||
if not state.has(monster, player):
|
||||
return False
|
||||
if data is None:
|
||||
return True
|
||||
else:
|
||||
materials = data.replaceable and state.has_any(fusion_subs, player)
|
||||
for material in data.materials:
|
||||
materials += has_all_materials(state, material, player)
|
||||
return materials >= len(data.materials)
|
||||
|
||||
|
||||
def count_has_materials(state, monsters, player):
|
||||
amount = 0
|
||||
for monster in monsters:
|
||||
amount += has_all_materials(state, monster, player)
|
||||
return amount
|
||||
369
worlds/yugioh06/items.py
Normal file
369
worlds/yugioh06/items.py
Normal file
@@ -0,0 +1,369 @@
|
||||
from typing import Dict, List
|
||||
|
||||
item_to_index: Dict[str, int] = {
|
||||
"LEGEND OF B.E.W.D.": 1,
|
||||
"METAL RAIDERS": 2,
|
||||
"PHARAOH'S SERVANT": 3,
|
||||
"PHARAONIC GUARDIAN": 4,
|
||||
"SPELL RULER": 5,
|
||||
"LABYRINTH OF NIGHTMARE": 6,
|
||||
"LEGACY OF DARKNESS": 7,
|
||||
"MAGICIAN'S FORCE": 8,
|
||||
"DARK CRISIS": 9,
|
||||
"INVASION OF CHAOS": 10,
|
||||
"ANCIENT SANCTUARY": 11,
|
||||
"SOUL OF THE DUELIST": 12,
|
||||
"RISE OF DESTINY": 13,
|
||||
"FLAMING ETERNITY": 14,
|
||||
"THE LOST MILLENIUM": 15,
|
||||
"CYBERNETIC REVOLUTION": 16,
|
||||
"ELEMENTAL ENERGY": 17,
|
||||
"SHADOW OF INFINITY": 18,
|
||||
"GAME GIFT COLLECTION": 19,
|
||||
"Special Gift Collection": 20,
|
||||
"Fairy Collection": 21,
|
||||
"Dragon Collection": 22,
|
||||
"Warrior Collection A": 23,
|
||||
"Warrior Collection B": 24,
|
||||
"Fiend Collection A": 25,
|
||||
"Fiend Collection B": 26,
|
||||
"Machine Collection A": 27,
|
||||
"Machine Collection B": 28,
|
||||
"Spellcaster Collection A": 29,
|
||||
"Spellcaster Collection B": 30,
|
||||
"Zombie Collection": 31,
|
||||
"Special Monsters A": 32,
|
||||
"Special Monsters B": 33,
|
||||
"Reverse Collection": 34,
|
||||
"LP Recovery Collection": 35,
|
||||
"Special Summon Collection A": 36,
|
||||
"Special Summon Collection B": 37,
|
||||
"Special Summon Collection C": 38,
|
||||
"Equipment Collection": 39,
|
||||
"Continuous Spell/Trap A": 40,
|
||||
"Continuous Spell/Trap B": 41,
|
||||
"Quick/Counter Collection": 42,
|
||||
"Direct Damage Collection": 43,
|
||||
"Direct Attack Collection": 44,
|
||||
"Monster Destroy Collection": 45,
|
||||
"All Normal Monsters": 46,
|
||||
"All Effect Monsters": 47,
|
||||
"All Fusion Monsters": 48,
|
||||
"All Traps": 49,
|
||||
"All Spells": 50,
|
||||
"All at Random": 51,
|
||||
"LD01 All except Level 4 forbidden Unlock": 52,
|
||||
"LD02 Medium/high Level forbidden Unlock": 53,
|
||||
"LD03 ATK 1500 or more forbidden Unlock": 54,
|
||||
"LD04 Flip Effects forbidden Unlock": 55,
|
||||
"LD05 Tributes forbidden Unlock": 56,
|
||||
"LD06 Traps forbidden Unlock": 57,
|
||||
"LD07 Large Deck A Unlock": 58,
|
||||
"LD08 Large Deck B Unlock": 59,
|
||||
"LD09 Sets Forbidden Unlock": 60,
|
||||
"LD10 All except LV monsters forbidden Unlock": 61,
|
||||
"LD11 All except Fairies forbidden Unlock": 62,
|
||||
"LD12 All except Wind forbidden Unlock": 63,
|
||||
"LD13 All except monsters forbidden Unlock": 64,
|
||||
"LD14 Level 3 or below forbidden Unlock": 65,
|
||||
"LD15 DEF 1500 or less forbidden Unlock": 66,
|
||||
"LD16 Effect Monsters forbidden Unlock": 67,
|
||||
"LD17 Spells forbidden Unlock": 68,
|
||||
"LD18 Attacks forbidden Unlock": 69,
|
||||
"LD19 All except E-Hero's forbidden Unlock": 70,
|
||||
"LD20 All except Warriors forbidden Unlock": 71,
|
||||
"LD21 All except Dark forbidden Unlock": 72,
|
||||
"LD22 All limited cards forbidden Unlock": 73,
|
||||
"LD23 Refer to Mar 05 Banlist Unlock": 74,
|
||||
"LD24 Refer to Sept 04 Banlist Unlock": 75,
|
||||
"LD25 Low Life Points Unlock": 76,
|
||||
"LD26 All except Toons forbidden Unlock": 77,
|
||||
"LD27 All except Spirits forbidden Unlock": 78,
|
||||
"LD28 All except Dragons forbidden Unlock": 79,
|
||||
"LD29 All except Spellcasters forbidden Unlock": 80,
|
||||
"LD30 All except Light forbidden Unlock": 81,
|
||||
"LD31 All except Fire forbidden Unlock": 82,
|
||||
"LD32 Decks with multiples forbidden Unlock": 83,
|
||||
"LD33 Special Summons forbidden Unlock": 84,
|
||||
"LD34 Normal Summons forbidden Unlock": 85,
|
||||
"LD35 All except Zombies forbidden Unlock": 86,
|
||||
"LD36 All except Earth forbidden Unlock": 87,
|
||||
"LD37 All except Water forbidden Unlock": 88,
|
||||
"LD38 Refer to Mar 04 Banlist Unlock": 89,
|
||||
"LD39 Monsters forbidden Unlock": 90,
|
||||
"LD40 Refer to Sept 05 Banlist Unlock": 91,
|
||||
"LD41 Refer to Sept 03 Banlist Unlock": 92,
|
||||
"TD01 Battle Damage Unlock": 93,
|
||||
"TD02 Deflected Damage Unlock": 94,
|
||||
"TD03 Normal Summon Unlock": 95,
|
||||
"TD04 Ritual Summon Unlock": 96,
|
||||
"TD05 Special Summon A Unlock": 97,
|
||||
"TD06 20x Spell Unlock": 98,
|
||||
"TD07 10x Trap Unlock": 99,
|
||||
"TD08 Draw Unlock": 100,
|
||||
"TD09 Hand Destruction Unlock": 101,
|
||||
"TD10 During Opponent's Turn Unlock": 102,
|
||||
"TD11 Recover Unlock": 103,
|
||||
"TD12 Remove Monsters by Effect Unlock": 104,
|
||||
"TD13 Flip Summon Unlock": 105,
|
||||
"TD14 Special Summon B Unlock": 106,
|
||||
"TD15 Token Unlock": 107,
|
||||
"TD16 Union Unlock": 108,
|
||||
"TD17 10x Quick Spell Unlock": 109,
|
||||
"TD18 The Forbidden Unlock": 110,
|
||||
"TD19 20 Turns Unlock": 111,
|
||||
"TD20 Deck Destruction Unlock": 112,
|
||||
"TD21 Victory D. Unlock": 113,
|
||||
"TD22 The Preventers Fight Back Unlock": 114,
|
||||
"TD23 Huge Revolution Unlock": 115,
|
||||
"TD24 Victory in 5 Turns Unlock": 116,
|
||||
"TD25 Moth Grows Up Unlock": 117,
|
||||
"TD26 Magnetic Power Unlock": 118,
|
||||
"TD27 Dark Sage Unlock": 119,
|
||||
"TD28 Direct Damage Unlock": 120,
|
||||
"TD29 Destroy Monsters in Battle Unlock": 121,
|
||||
"TD30 Tribute Summon Unlock": 122,
|
||||
"TD31 Special Summon C Unlock": 123,
|
||||
"TD32 Toon Unlock": 124,
|
||||
"TD33 10x Counter Unlock": 125,
|
||||
"TD34 Destiny Board Unlock": 126,
|
||||
"TD35 Huge Damage in a Turn Unlock": 127,
|
||||
"TD36 V-Z In the House Unlock": 128,
|
||||
"TD37 Uria, Lord of Searing Flames Unlock": 129,
|
||||
"TD38 Hamon, Lord of Striking Thunder Unlock": 130,
|
||||
"TD39 Raviel, Lord of Phantasms Unlock": 131,
|
||||
"TD40 Make a Chain Unlock": 132,
|
||||
"TD41 The Gatekeeper Stands Tall Unlock": 133,
|
||||
"TD42 Serious Damage Unlock": 134,
|
||||
"TD43 Return Monsters with Effects Unlock": 135,
|
||||
"TD44 Fusion Summon Unlock": 136,
|
||||
"TD45 Big Damage at once Unlock": 137,
|
||||
"TD46 XYZ In the House Unlock": 138,
|
||||
"TD47 Spell Counter Unlock": 139,
|
||||
"TD48 Destroy Monsters with Effects Unlock": 140,
|
||||
"TD49 Plunder Unlock": 141,
|
||||
"TD50 Dark Scorpion Combination Unlock": 142,
|
||||
"Campaign Tier 1 Column 1": 143,
|
||||
"Campaign Tier 1 Column 2": 144,
|
||||
"Campaign Tier 1 Column 3": 145,
|
||||
"Campaign Tier 1 Column 4": 146,
|
||||
"Campaign Tier 1 Column 5": 147,
|
||||
"Campaign Tier 2 Column 1": 148,
|
||||
"Campaign Tier 2 Column 2": 149,
|
||||
"Campaign Tier 2 Column 3": 150,
|
||||
"Campaign Tier 2 Column 4": 151,
|
||||
"Campaign Tier 2 Column 5": 152,
|
||||
"Campaign Tier 3 Column 1": 153,
|
||||
"Campaign Tier 3 Column 2": 154,
|
||||
"Campaign Tier 3 Column 3": 155,
|
||||
"Campaign Tier 3 Column 4": 156,
|
||||
"Campaign Tier 3 Column 5": 157,
|
||||
"Campaign Tier 4 Column 1": 158,
|
||||
"Campaign Tier 4 Column 2": 159,
|
||||
"Campaign Tier 4 Column 3": 160,
|
||||
"Campaign Tier 4 Column 4": 161,
|
||||
"Campaign Tier 4 Column 5": 162,
|
||||
"Campaign Tier 5 Column 1": 163,
|
||||
"Campaign Tier 5 Column 2": 164,
|
||||
"No Banlist": 167,
|
||||
"Banlist September 2003": 168,
|
||||
"Banlist March 2004": 169,
|
||||
"Banlist September 2004": 170,
|
||||
"Banlist March 2005": 171,
|
||||
"Banlist September 2005": 172,
|
||||
"5000DP": 254,
|
||||
"Remote": 255,
|
||||
}
|
||||
|
||||
tier_1_opponents: List[str] = [
|
||||
"Campaign Tier 1 Column 1",
|
||||
"Campaign Tier 1 Column 2",
|
||||
"Campaign Tier 1 Column 3",
|
||||
"Campaign Tier 1 Column 4",
|
||||
"Campaign Tier 1 Column 5",
|
||||
]
|
||||
|
||||
Banlist_Items: List[str] = [
|
||||
"No Banlist",
|
||||
"Banlist September 2003",
|
||||
"Banlist March 2004",
|
||||
"Banlist September 2004",
|
||||
"Banlist March 2005",
|
||||
"Banlist September 2005",
|
||||
]
|
||||
|
||||
draft_boosters: List[str] = [
|
||||
"METAL RAIDERS",
|
||||
"PHARAOH'S SERVANT",
|
||||
"PHARAONIC GUARDIAN",
|
||||
"LABYRINTH OF NIGHTMARE",
|
||||
"LEGACY OF DARKNESS",
|
||||
"MAGICIAN'S FORCE",
|
||||
"DARK CRISIS",
|
||||
"INVASION OF CHAOS",
|
||||
"RISE OF DESTINY",
|
||||
"ELEMENTAL ENERGY",
|
||||
"SHADOW OF INFINITY",
|
||||
]
|
||||
|
||||
draft_opponents: List[str] = ["Campaign Tier 1 Column 1", "Campaign Tier 1 Column 5"]
|
||||
|
||||
booster_packs: List[str] = [
|
||||
"LEGEND OF B.E.W.D.",
|
||||
"METAL RAIDERS",
|
||||
"PHARAOH'S SERVANT",
|
||||
"PHARAONIC GUARDIAN",
|
||||
"SPELL RULER",
|
||||
"LABYRINTH OF NIGHTMARE",
|
||||
"LEGACY OF DARKNESS",
|
||||
"MAGICIAN'S FORCE",
|
||||
"DARK CRISIS",
|
||||
"INVASION OF CHAOS",
|
||||
"ANCIENT SANCTUARY",
|
||||
"SOUL OF THE DUELIST",
|
||||
"RISE OF DESTINY",
|
||||
"FLAMING ETERNITY",
|
||||
"THE LOST MILLENIUM",
|
||||
"CYBERNETIC REVOLUTION",
|
||||
"ELEMENTAL ENERGY",
|
||||
"SHADOW OF INFINITY",
|
||||
"GAME GIFT COLLECTION",
|
||||
"Special Gift Collection",
|
||||
"Fairy Collection",
|
||||
"Dragon Collection",
|
||||
"Warrior Collection A",
|
||||
"Warrior Collection B",
|
||||
"Fiend Collection A",
|
||||
"Fiend Collection B",
|
||||
"Machine Collection A",
|
||||
"Machine Collection B",
|
||||
"Spellcaster Collection A",
|
||||
"Spellcaster Collection B",
|
||||
"Zombie Collection",
|
||||
"Special Monsters A",
|
||||
"Special Monsters B",
|
||||
"Reverse Collection",
|
||||
"LP Recovery Collection",
|
||||
"Special Summon Collection A",
|
||||
"Special Summon Collection B",
|
||||
"Special Summon Collection C",
|
||||
"Equipment Collection",
|
||||
"Continuous Spell/Trap A",
|
||||
"Continuous Spell/Trap B",
|
||||
"Quick/Counter Collection",
|
||||
"Direct Damage Collection",
|
||||
"Direct Attack Collection",
|
||||
"Monster Destroy Collection",
|
||||
]
|
||||
|
||||
challenges: List[str] = [
|
||||
"LD01 All except Level 4 forbidden Unlock",
|
||||
"LD02 Medium/high Level forbidden Unlock",
|
||||
"LD03 ATK 1500 or more forbidden Unlock",
|
||||
"LD04 Flip Effects forbidden Unlock",
|
||||
"LD05 Tributes forbidden Unlock",
|
||||
"LD06 Traps forbidden Unlock",
|
||||
"LD07 Large Deck A Unlock",
|
||||
"LD08 Large Deck B Unlock",
|
||||
"LD09 Sets Forbidden Unlock",
|
||||
"LD10 All except LV monsters forbidden Unlock",
|
||||
"LD11 All except Fairies forbidden Unlock",
|
||||
"LD12 All except Wind forbidden Unlock",
|
||||
"LD13 All except monsters forbidden Unlock",
|
||||
"LD14 Level 3 or below forbidden Unlock",
|
||||
"LD15 DEF 1500 or less forbidden Unlock",
|
||||
"LD16 Effect Monsters forbidden Unlock",
|
||||
"LD17 Spells forbidden Unlock",
|
||||
"LD18 Attacks forbidden Unlock",
|
||||
"LD19 All except E-Hero's forbidden Unlock",
|
||||
"LD20 All except Warriors forbidden Unlock",
|
||||
"LD21 All except Dark forbidden Unlock",
|
||||
"LD22 All limited cards forbidden Unlock",
|
||||
"LD23 Refer to Mar 05 Banlist Unlock",
|
||||
"LD24 Refer to Sept 04 Banlist Unlock",
|
||||
"LD25 Low Life Points Unlock",
|
||||
"LD26 All except Toons forbidden Unlock",
|
||||
"LD27 All except Spirits forbidden Unlock",
|
||||
"LD28 All except Dragons forbidden Unlock",
|
||||
"LD29 All except Spellcasters forbidden Unlock",
|
||||
"LD30 All except Light forbidden Unlock",
|
||||
"LD31 All except Fire forbidden Unlock",
|
||||
"LD32 Decks with multiples forbidden Unlock",
|
||||
"LD33 Special Summons forbidden Unlock",
|
||||
"LD34 Normal Summons forbidden Unlock",
|
||||
"LD35 All except Zombies forbidden Unlock",
|
||||
"LD36 All except Earth forbidden Unlock",
|
||||
"LD37 All except Water forbidden Unlock",
|
||||
"LD38 Refer to Mar 04 Banlist Unlock",
|
||||
"LD39 Monsters forbidden Unlock",
|
||||
"LD40 Refer to Sept 05 Banlist Unlock",
|
||||
"LD41 Refer to Sept 03 Banlist Unlock",
|
||||
"TD01 Battle Damage Unlock",
|
||||
"TD02 Deflected Damage Unlock",
|
||||
"TD03 Normal Summon Unlock",
|
||||
"TD04 Ritual Summon Unlock",
|
||||
"TD05 Special Summon A Unlock",
|
||||
"TD06 20x Spell Unlock",
|
||||
"TD07 10x Trap Unlock",
|
||||
"TD08 Draw Unlock",
|
||||
"TD09 Hand Destruction Unlock",
|
||||
"TD10 During Opponent's Turn Unlock",
|
||||
"TD11 Recover Unlock",
|
||||
"TD12 Remove Monsters by Effect Unlock",
|
||||
"TD13 Flip Summon Unlock",
|
||||
"TD14 Special Summon B Unlock",
|
||||
"TD15 Token Unlock",
|
||||
"TD16 Union Unlock",
|
||||
"TD17 10x Quick Spell Unlock",
|
||||
"TD18 The Forbidden Unlock",
|
||||
"TD19 20 Turns Unlock",
|
||||
"TD20 Deck Destruction Unlock",
|
||||
"TD21 Victory D. Unlock",
|
||||
"TD22 The Preventers Fight Back Unlock",
|
||||
"TD23 Huge Revolution Unlock",
|
||||
"TD24 Victory in 5 Turns Unlock",
|
||||
"TD25 Moth Grows Up Unlock",
|
||||
"TD26 Magnetic Power Unlock",
|
||||
"TD27 Dark Sage Unlock",
|
||||
"TD28 Direct Damage Unlock",
|
||||
"TD29 Destroy Monsters in Battle Unlock",
|
||||
"TD30 Tribute Summon Unlock",
|
||||
"TD31 Special Summon C Unlock",
|
||||
"TD32 Toon Unlock",
|
||||
"TD33 10x Counter Unlock",
|
||||
"TD34 Destiny Board Unlock",
|
||||
"TD35 Huge Damage in a Turn Unlock",
|
||||
"TD36 V-Z In the House Unlock",
|
||||
"TD37 Uria, Lord of Searing Flames Unlock",
|
||||
"TD38 Hamon, Lord of Striking Thunder Unlock",
|
||||
"TD39 Raviel, Lord of Phantasms Unlock",
|
||||
"TD40 Make a Chain Unlock",
|
||||
"TD41 The Gatekeeper Stands Tall Unlock",
|
||||
"TD42 Serious Damage Unlock",
|
||||
"TD43 Return Monsters with Effects Unlock",
|
||||
"TD44 Fusion Summon Unlock",
|
||||
"TD45 Big Damage at once Unlock",
|
||||
"TD46 XYZ In the House Unlock",
|
||||
"TD47 Spell Counter Unlock",
|
||||
"TD48 Destroy Monsters with Effects Unlock",
|
||||
"TD49 Plunder Unlock",
|
||||
"TD50 Dark Scorpion Combination Unlock",
|
||||
]
|
||||
|
||||
excluded_items: List[str] = [
|
||||
"All Normal Monsters",
|
||||
"All Effect Monsters",
|
||||
"All Fusion Monsters",
|
||||
"All Traps",
|
||||
"All Spells",
|
||||
"All at Random",
|
||||
"5000DP",
|
||||
"Remote",
|
||||
]
|
||||
|
||||
useful: List[str] = [
|
||||
"Banlist March 2004",
|
||||
"Banlist September 2004",
|
||||
"Banlist March 2005",
|
||||
"Banlist September 2005",
|
||||
]
|
||||
213
worlds/yugioh06/locations.py
Normal file
213
worlds/yugioh06/locations.py
Normal file
@@ -0,0 +1,213 @@
|
||||
Bonuses = {
|
||||
"Duelist Bonus Level 1": 1,
|
||||
"Duelist Bonus Level 2": 2,
|
||||
"Duelist Bonus Level 3": 3,
|
||||
"Duelist Bonus Level 4": 4,
|
||||
"Duelist Bonus Level 5": 5,
|
||||
"Battle Damage": 6,
|
||||
"Battle Damage Only Bonus": 7,
|
||||
"Max ATK Bonus": 8,
|
||||
"Max Damage Bonus": 9,
|
||||
"Destroyed in Battle Bonus": 10,
|
||||
"Spell Card Bonus": 11,
|
||||
"Trap Card Bonus": 12,
|
||||
"Tribute Summon Bonus": 13,
|
||||
"Fusion Summon Bonus": 14,
|
||||
"Ritual Summon Bonus": 15,
|
||||
"No Special Summon Bonus": 16,
|
||||
"No Spell Cards Bonus": 17,
|
||||
"No Trap Cards Bonus": 18,
|
||||
"No Damage Bonus": 19,
|
||||
"Over 20000 LP Bonus": 20,
|
||||
"Low LP Bonus": 21,
|
||||
"Extremely Low LP Bonus": 22,
|
||||
"Low Deck Bonus": 23,
|
||||
"Extremely Low Deck Bonus": 24,
|
||||
"Effect Damage Only Bonus": 25,
|
||||
"No More Cards Bonus": 26,
|
||||
"Opponent's Turn Finish Bonus": 27,
|
||||
"Exactly 0 LP Bonus": 28,
|
||||
"Reversal Finish Bonus": 29,
|
||||
"Quick Finish Bonus": 30,
|
||||
"Exodia Finish Bonus": 31,
|
||||
"Last Turn Finish Bonus": 32,
|
||||
"Final Countdown Finish Bonus": 33,
|
||||
"Destiny Board Finish Bonus": 34,
|
||||
"Yata-Garasu Finish Bonus": 35,
|
||||
"Skull Servant Finish Bonus": 36,
|
||||
"Konami Bonus": 37,
|
||||
}
|
||||
|
||||
Limited_Duels = {
|
||||
"LD01 All except Level 4 forbidden": 38,
|
||||
"LD02 Medium/high Level forbidden": 39,
|
||||
"LD03 ATK 1500 or more forbidden": 40,
|
||||
"LD04 Flip Effects forbidden": 41,
|
||||
"LD05 Tributes forbidden": 42,
|
||||
"LD06 Traps forbidden": 43,
|
||||
"LD07 Large Deck A": 44,
|
||||
"LD08 Large Deck B": 45,
|
||||
"LD09 Sets Forbidden": 46,
|
||||
"LD10 All except LV monsters forbidden": 47,
|
||||
"LD11 All except Fairies forbidden": 48,
|
||||
"LD12 All except Wind forbidden": 49,
|
||||
"LD13 All except monsters forbidden": 50,
|
||||
"LD14 Level 3 or below forbidden": 51,
|
||||
"LD15 DEF 1500 or less forbidden": 52,
|
||||
"LD16 Effect Monsters forbidden": 53,
|
||||
"LD17 Spells forbidden": 54,
|
||||
"LD18 Attacks forbidden": 55,
|
||||
"LD19 All except E-Hero's forbidden": 56,
|
||||
"LD20 All except Warriors forbidden": 57,
|
||||
"LD21 All except Dark forbidden": 58,
|
||||
"LD22 All limited cards forbidden": 59,
|
||||
"LD23 Refer to Mar 05 Banlist": 60,
|
||||
"LD24 Refer to Sept 04 Banlist": 61,
|
||||
"LD25 Low Life Points": 62,
|
||||
"LD26 All except Toons forbidden": 63,
|
||||
"LD27 All except Spirits forbidden": 64,
|
||||
"LD28 All except Dragons forbidden": 65,
|
||||
"LD29 All except Spellcasters forbidden": 66,
|
||||
"LD30 All except Light forbidden": 67,
|
||||
"LD31 All except Fire forbidden": 68,
|
||||
"LD32 Decks with multiples forbidden": 69,
|
||||
"LD33 Special Summons forbidden": 70,
|
||||
"LD34 Normal Summons forbidden": 71,
|
||||
"LD35 All except Zombies forbidden": 72,
|
||||
"LD36 All except Earth forbidden": 73,
|
||||
"LD37 All except Water forbidden": 74,
|
||||
"LD38 Refer to Mar 04 Banlist": 75,
|
||||
"LD39 Monsters forbidden": 76,
|
||||
"LD40 Refer to Sept 05 Banlist": 77,
|
||||
"LD41 Refer to Sept 03 Banlist": 78,
|
||||
}
|
||||
|
||||
Theme_Duels = {
|
||||
"TD01 Battle Damage": 79,
|
||||
"TD02 Deflected Damage": 80,
|
||||
"TD03 Normal Summon": 81,
|
||||
"TD04 Ritual Summon": 82,
|
||||
"TD05 Special Summon A": 83,
|
||||
"TD06 20x Spell": 84,
|
||||
"TD07 10x Trap": 85,
|
||||
"TD08 Draw": 86,
|
||||
"TD09 Hand Destruction": 87,
|
||||
"TD10 During Opponent's Turn": 88,
|
||||
"TD11 Recover": 89,
|
||||
"TD12 Remove Monsters by Effect": 90,
|
||||
"TD13 Flip Summon": 91,
|
||||
"TD14 Special Summon B": 92,
|
||||
"TD15 Token": 93,
|
||||
"TD16 Union": 94,
|
||||
"TD17 10x Quick Spell": 95,
|
||||
"TD18 The Forbidden": 96,
|
||||
"TD19 20 Turns": 97,
|
||||
"TD20 Deck Destruction": 98,
|
||||
"TD21 Victory D.": 99,
|
||||
"TD22 The Preventers Fight Back": 100,
|
||||
"TD23 Huge Revolution": 101,
|
||||
"TD24 Victory in 5 Turns": 102,
|
||||
"TD25 Moth Grows Up": 103,
|
||||
"TD26 Magnetic Power": 104,
|
||||
"TD27 Dark Sage": 105,
|
||||
"TD28 Direct Damage": 106,
|
||||
"TD29 Destroy Monsters in Battle": 107,
|
||||
"TD30 Tribute Summon": 108,
|
||||
"TD31 Special Summon C": 109,
|
||||
"TD32 Toon": 110,
|
||||
"TD33 10x Counter": 111,
|
||||
"TD34 Destiny Board": 112,
|
||||
"TD35 Huge Damage in a Turn": 113,
|
||||
"TD36 V-Z In the House": 114,
|
||||
"TD37 Uria, Lord of Searing Flames": 115,
|
||||
"TD38 Hamon, Lord of Striking Thunder": 116,
|
||||
"TD39 Raviel, Lord of Phantasms": 117,
|
||||
"TD40 Make a Chain": 118,
|
||||
"TD41 The Gatekeeper Stands Tall": 119,
|
||||
"TD42 Serious Damage": 120,
|
||||
"TD43 Return Monsters with Effects": 121,
|
||||
"TD44 Fusion Summon": 122,
|
||||
"TD45 Big Damage at once": 123,
|
||||
"TD46 XYZ In the House": 124,
|
||||
"TD47 Spell Counter": 125,
|
||||
"TD48 Destroy Monsters with Effects": 126,
|
||||
"TD49 Plunder": 127,
|
||||
"TD50 Dark Scorpion Combination": 128,
|
||||
}
|
||||
|
||||
Campaign_Opponents = {
|
||||
"Campaign Tier 1: 1 Win": 129,
|
||||
"Campaign Tier 1: 3 Wins A": 130,
|
||||
"Campaign Tier 1: 3 Wins B": 131,
|
||||
"Campaign Tier 1: 5 Wins A": 132,
|
||||
"Campaign Tier 1: 5 Wins B": 133,
|
||||
"Campaign Tier 2: 1 Win": 134,
|
||||
"Campaign Tier 2: 3 Wins A": 135,
|
||||
"Campaign Tier 2: 3 Wins B": 136,
|
||||
"Campaign Tier 2: 5 Wins A": 137,
|
||||
"Campaign Tier 2: 5 Wins B": 138,
|
||||
"Campaign Tier 3: 1 Win": 139,
|
||||
"Campaign Tier 3: 3 Wins A": 140,
|
||||
"Campaign Tier 3: 3 Wins B": 141,
|
||||
"Campaign Tier 3: 5 Wins A": 142,
|
||||
"Campaign Tier 3: 5 Wins B": 143,
|
||||
"Campaign Tier 4: 5 Wins A": 144,
|
||||
"Campaign Tier 4: 5 Wins B": 145,
|
||||
}
|
||||
|
||||
special = {
|
||||
"Campaign Tier 5: Column 1 Win": 146,
|
||||
"Campaign Tier 5: Column 2 Win": 147,
|
||||
"Campaign Tier 5: Column 3 Win": 148,
|
||||
"Campaign Tier 5: Column 4 Win": 149,
|
||||
# "Campaign Final Boss Win": 150,
|
||||
}
|
||||
|
||||
Required_Cards = {
|
||||
"Obtain all pieces of Exodia": 154,
|
||||
"Obtain Final Countdown": 155,
|
||||
"Obtain Victory Dragon": 156,
|
||||
"Obtain Ojama Delta Hurricane and its required cards": 157,
|
||||
"Obtain Huge Revolution and its required cards": 158,
|
||||
"Obtain Perfectly Ultimate Great Moth and its required cards": 159,
|
||||
"Obtain Valkyrion the Magna Warrior and its pieces": 160,
|
||||
"Obtain Dark Sage and its required cards": 161,
|
||||
"Obtain Destiny Board and its letters": 162,
|
||||
"Obtain all XYZ-Dragon Cannon fusions and their materials": 163,
|
||||
"Obtain VWXYZ-Dragon Catapult Cannon and the fusion materials": 164,
|
||||
"Obtain Hamon, Lord of Striking Thunder": 165,
|
||||
"Obtain Raviel, Lord of Phantasms": 166,
|
||||
"Obtain Uria, Lord of Searing Flames": 167,
|
||||
"Obtain Gate Guardian and its pieces": 168,
|
||||
"Obtain Dark Scorpion Combination and its required cards": 169,
|
||||
}
|
||||
|
||||
collection_events = {
|
||||
"Ojama Delta Hurricane and required cards": None,
|
||||
"Huge Revolution and its required cards": None,
|
||||
"Perfectly Ultimate Great Moth and its required cards": None,
|
||||
"Valkyrion the Magna Warrior and its pieces": None,
|
||||
"Dark Sage and its required cards": None,
|
||||
"Destiny Board and its letters": None,
|
||||
"XYZ-Dragon Cannon fusions and their materials": None,
|
||||
"VWXYZ-Dragon Catapult Cannon and the fusion materials": None,
|
||||
"Gate Guardian and its pieces": None,
|
||||
"Dark Scorpion Combination and its required cards": None,
|
||||
"Can Exodia Win": None,
|
||||
"Can Yata Lock": None,
|
||||
"Can Stall with Monsters": None,
|
||||
"Can Stall with ST": None,
|
||||
"Can Last Turn Win": None,
|
||||
"Has Back-row removal": None,
|
||||
}
|
||||
|
||||
|
||||
def get_beat_challenge_events(self):
|
||||
beat_events = {}
|
||||
for limited in Limited_Duels.keys():
|
||||
if limited not in self.removed_challenges:
|
||||
beat_events[limited + " Complete"] = None
|
||||
for theme in Theme_Duels.keys():
|
||||
if theme not in self.removed_challenges:
|
||||
beat_events[theme + " Complete"] = None
|
||||
return beat_events
|
||||
28
worlds/yugioh06/logic.py
Normal file
28
worlds/yugioh06/logic.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from typing import List
|
||||
|
||||
from BaseClasses import CollectionState
|
||||
|
||||
core_booster: List[str] = [
|
||||
"LEGEND OF B.E.W.D.",
|
||||
"METAL RAIDERS",
|
||||
"PHARAOH'S SERVANT",
|
||||
"PHARAONIC GUARDIAN",
|
||||
"SPELL RULER",
|
||||
"LABYRINTH OF NIGHTMARE",
|
||||
"LEGACY OF DARKNESS",
|
||||
"MAGICIAN'S FORCE",
|
||||
"DARK CRISIS",
|
||||
"INVASION OF CHAOS",
|
||||
"ANCIENT SANCTUARY",
|
||||
"SOUL OF THE DUELIST",
|
||||
"RISE OF DESTINY",
|
||||
"FLAMING ETERNITY",
|
||||
"THE LOST MILLENIUM",
|
||||
"CYBERNETIC REVOLUTION",
|
||||
"ELEMENTAL ENERGY",
|
||||
"SHADOW OF INFINITY",
|
||||
]
|
||||
|
||||
|
||||
def yugioh06_difficulty(state: CollectionState, player: int, amount: int):
|
||||
return state.has_from_list(core_booster, player, amount)
|
||||
264
worlds/yugioh06/opponents.py
Normal file
264
worlds/yugioh06/opponents.py
Normal file
@@ -0,0 +1,264 @@
|
||||
from typing import Dict, List, NamedTuple, Optional, Union
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from worlds.generic.Rules import CollectionRule
|
||||
|
||||
from worlds.yugioh06 import item_to_index, tier_1_opponents, yugioh06_difficulty
|
||||
from worlds.yugioh06.locations import special
|
||||
|
||||
|
||||
class OpponentData(NamedTuple):
|
||||
id: int
|
||||
name: str
|
||||
campaign_info: List[str]
|
||||
tier: int
|
||||
column: int
|
||||
card_id: int = 0
|
||||
deck_name_id: int = 0
|
||||
deck_file: str = ""
|
||||
difficulty: int = 1
|
||||
additional_info: List[str] = []
|
||||
|
||||
def tier(self, tier):
|
||||
self.tier = tier
|
||||
|
||||
def column(self, column):
|
||||
self.column = column
|
||||
|
||||
|
||||
challenge_opponents = [
|
||||
# Theme
|
||||
OpponentData(27, "Exarion Universe", [], 1, 1, 5452, 13001, "deck/theme_001.ydc\x00\x00\x00\x00", 0),
|
||||
OpponentData(28, "Stone Statue of the Aztecs", [], 4, 1, 4754, 13002, "deck/theme_002.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(29, "Raging Flame Sprite", [], 1, 1, 6189, 13003, "deck/theme_003.ydc\x00\x00\x00\x00", 0),
|
||||
OpponentData(30, "Princess Pikeru", [], 1, 1, 6605, 13004, "deck/theme_004.ydc\x00\x00\x00\x00", 0),
|
||||
OpponentData(31, "Princess Curran", ["Quick-Finish"], 1, 1, 6606, 13005, "deck/theme_005.ydc\x00\x00\x00\x00", 0,
|
||||
["Has Back-row removal"]),
|
||||
OpponentData(32, "Gearfried the Iron Knight", ["Quick-Finish"], 2, 1, 5059, 13006,
|
||||
"deck/theme_006.ydc\x00\x00\x00\x00", 1),
|
||||
OpponentData(33, "Zaborg the Thunder Monarch", [], 3, 1, 5965, 13007, "deck/theme_007.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(34, "Kycoo the Ghost Destroyer", ["Quick-Finish"], 3, 1, 5248, 13008,
|
||||
"deck/theme_008.ydc\x00\x00\x00\x00"),
|
||||
OpponentData(35, "Penguin Soldier", ["Quick-Finish"], 1, 1, 4608, 13009, "deck/theme_009.ydc\x00\x00\x00\x00", 0),
|
||||
OpponentData(36, "Green Gadget", [], 5, 1, 6151, 13010, "deck/theme_010.ydc\x00\x00\x00\x00", 5),
|
||||
OpponentData(37, "Guardian Sphinx", ["Quick-Finish"], 3, 1, 5422, 13011, "deck/theme_011.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(38, "Cyber-Tech Alligator", [], 2, 1, 4790, 13012, "deck/theme_012.ydc\x00\x00\x00\x00", 1),
|
||||
OpponentData(39, "UFOroid Fighter", [], 3, 1, 6395, 13013, "deck/theme_013.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(40, "Relinquished", [], 3, 1, 4737, 13014, "deck/theme_014.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(41, "Manticore of Darkness", [], 2, 1, 5881, 13015, "deck/theme_015.ydc\x00\x00\x00\x00", 1),
|
||||
OpponentData(42, "Vampire Lord", [], 3, 1, 5410, 13016, "deck/theme_016.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(43, "Gigantes", ["Quick-Finish"], 3, 1, 5831, 13017, "deck/theme_017.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(44, "Insect Queen", ["Quick-Finish"], 2, 1, 4768, 13018, "deck/theme_018.ydc\x00\x00\x00\x00", 1),
|
||||
OpponentData(45, "Second Goblin", ["Quick-Finish"], 1, 1, 5587, 13019, "deck/theme_019.ydc\x00\x00\x00\x00", 0),
|
||||
OpponentData(46, "Toon Summoned Skull", [], 4, 1, 4735, 13020, "deck/theme_020.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(47, "Iron Blacksmith Kotetsu", [], 2, 1, 5769, 13021, "deck/theme_021.ydc\x00\x00\x00\x00", 1),
|
||||
OpponentData(48, "Magician of Faith", [], 1, 1, 4434, 13022, "deck/theme_022.ydc\x00\x00\x00\x00", 0),
|
||||
OpponentData(49, "Mask of Darkness", [], 1, 1, 4108, 13023, "deck/theme_023.ydc\x00\x00\x00\x00", 0),
|
||||
OpponentData(50, "Dark Ruler Vandalgyon", [], 3, 1, 6410, 13024, "deck/theme_024.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(51, "Aussa the Earth Charmer", ["Quick-Finish"], 2, 1, 6335, 13025,
|
||||
"deck/theme_025.ydc\x00\x00\x00\x00", 1),
|
||||
OpponentData(52, "Exodia Necross", ["Quick-Finish"], 2, 1, 5701, 13026, "deck/theme_026.ydc\x00\x00\x00\x00", 1),
|
||||
OpponentData(53, "Dark Necrofear", [], 3, 1, 5222, 13027, "deck/theme_027.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(54, "Demise, King of Armageddon", [], 4, 1, 6613, 13028, "deck/theme_028.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(55, "Yamata Dragon", [], 3, 1, 5377, 13029, "deck/theme_029.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(56, "Blue-Eyes Ultimate Dragon", [], 3, 1, 4386, 13030, "deck/theme_030.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(57, "Wave-Motion Cannon", [], 4, 1, 5614, 13031, "deck/theme_031.ydc\x00\x00\x00\x00", 3,
|
||||
["Has Back-row removal"]),
|
||||
# Unused opponent
|
||||
# OpponentData(58, "Yata-Garasu", [], 1, 1, 5375, 13032, "deck/theme_031.ydc\x00\x00\x00\x00"),
|
||||
# Unused opponent
|
||||
# OpponentData(59, "Makyura the Destructor", [], 1, 1, 5285, 13033, "deck/theme_031.ydc\x00\x00\x00\x00"),
|
||||
OpponentData(60, "Morphing Jar", [], 5, 1, 4597, 13034, "deck/theme_034.ydc\x00\x00\x00\x00", 4),
|
||||
OpponentData(61, "Spirit Reaper", [], 2, 1, 5526, 13035, "deck/theme_035.ydc\x00\x00\x00\x00", 1),
|
||||
OpponentData(62, "Victory D.", [], 3, 1, 5868, 13036, "deck/theme_036.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(63, "VWXYZ-Dragon Catapult Cannon", ["Quick-Finish"], 3, 1, 6484, 13037,
|
||||
"deck/theme_037.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(64, "XYZ-Dragon Cannon", [], 2, 1, 5556, 13038, "deck/theme_038.ydc\x00\x00\x00\x00", 1),
|
||||
OpponentData(65, "Uria, Lord of Searing Flames", [], 4, 1, 6563, 13039, "deck/theme_039.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(66, "Hamon, Lord of Striking Thunder", [], 4, 1, 6564, 13040, "deck/theme_040.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(67, "Raviel, Lord of Phantasms TD", [], 4, 1, 6565, 13041, "deck/theme_041.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(68, "Ojama Trio", [], 1, 1, 5738, 13042, "deck/theme_042.ydc\x00\x00\x00\x00", 0),
|
||||
OpponentData(69, "People Running About", ["Quick-Finish"], 1, 1, 5578, 13043, "deck/theme_043.ydc\x00\x00\x00\x00",
|
||||
0),
|
||||
OpponentData(70, "Cyber-Stein", [], 5, 1, 4426, 13044, "deck/theme_044.ydc\x00\x00\x00\x00", 4),
|
||||
OpponentData(71, "Winged Kuriboh LV10", [], 4, 1, 6406, 13045, "deck/theme_045.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(72, "Blue-Eyes Shining Dragon", [], 3, 1, 6082, 13046, "deck/theme_046.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(73, "Perfectly Ultimate Great Moth", ["Quick-Finish"], 3, 1, 4073, 13047,
|
||||
"deck/theme_047.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(74, "Gate Guardian", [], 4, 1, 4380, 13048, "deck/theme_048.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(75, "Valkyrion the Magna Warrior", [], 3, 1, 5002, 13049, "deck/theme_049.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(76, "Dark Sage", [], 4, 1, 5230, 13050, "deck/theme_050.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(77, "Don Zaloog", [], 4, 1, 5426, 13051, "deck/theme_051.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(78, "Blast Magician", ["Quick-Finish"], 2, 1, 6250, 13052, "deck/theme_052.ydc\x00\x00\x00\x00", 1),
|
||||
# Limited
|
||||
OpponentData(79, "Zombyra the Dark", [], 5, 1, 5245, 23000, "deck/limit_000.ydc\x00\x00\x00\x00", 5),
|
||||
OpponentData(80, "Goblin Attack Force", [], 4, 1, 5145, 23001, "deck/limit_001.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(81, "Giant Kozaky", [], 4, 1, 6420, 23002, "deck/limit_002.ydc\x00\x00\x00\x00", 4),
|
||||
OpponentData(82, "Big Shield Gardna", ["Quick-Finish"], 2, 1, 4764, 23003, "deck/limit_003.ydc\x00\x00\x00\x00", 1),
|
||||
OpponentData(83, "Panther Warrior", [], 3, 1, 4751, 23004, "deck/limit_004.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(84, "Silent Magician LV4", ["Quick-Finish"], 2, 1, 6167, 23005, "deck/limit_005.ydc\x00\x00\x00\x00",
|
||||
1),
|
||||
OpponentData(85, "Summoned Skull", [], 4, 1, 4028, 23006, "deck/limit_006.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(86, "Ancient Gear Golem", [], 5, 1, 6315, 23007, "deck/limit_007.ydc\x00\x00\x00\x00", 5),
|
||||
OpponentData(87, "Chaos Sorcerer", [], 5, 1, 5833, 23008, "deck/limit_008.ydc\x00\x00\x00\x00", 5),
|
||||
OpponentData(88, "Breaker the Magical Warrior", [], 5, 1, 5655, 23009, "deck/limit_009.ydc\x00\x00\x00\x00", 4),
|
||||
OpponentData(89, "Dark Magician of Chaos", [], 4, 1, 5880, 23010, "deck/limit_010.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(90, "Stealth Bird", ["Quick-Finish"], 2, 1, 5882, 23011, "deck/limit_011.ydc\x00\x00\x00\x00", 1),
|
||||
OpponentData(91, "Rapid-Fire Magician", ["Quick-Finish"], 2, 1, 6500, 23012, "deck/limit_012.ydc\x00\x00\x00\x00",
|
||||
1),
|
||||
OpponentData(92, "Morphing Jar #2", [], 5, 1, 4969, 23013, "deck/limit_013.ydc\x00\x00\x00\x00", 4),
|
||||
OpponentData(93, "Cyber Jar", [], 5, 1, 4913, 23014, "deck/limit_014.ydc\x00\x00\x00\x00", 4),
|
||||
# Unused/Broken
|
||||
# OpponentData(94, "Exodia the Forbidden One", [], 1, 1, 4027, 23015, "deck/limit_015.ydc\x00\x00\x00\x00"),
|
||||
OpponentData(94, "Dark Paladin", [], 4, 1, 5628, 23016, "deck/limit_016.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(95, "F.G.D.", [], 5, 1, 5502, 23017, "deck/limit_017.ydc\x00\x00\x00\x00", 4),
|
||||
OpponentData(96, "Blue-Eyes Toon Dragon", ["Quick-Finish"], 2, 1, 4773, 23018, "deck/limit_018.ydc\x00\x00\x00\x00",
|
||||
1),
|
||||
OpponentData(97, "Tsukuyomi", [], 3, 1, 5780, 23019, "deck/limit_019.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(98, "Silent Swordsman LV3", ["Quick-Finish"], 2, 1, 6162, 23020, "deck/limit_020.ydc\x00\x00\x00\x00",
|
||||
2),
|
||||
OpponentData(99, "Elemental Hero Flame Wingman", ["Quick-Finish"], 2, 1, 6344, 23021,
|
||||
"deck/limit_021.ydc\x00\x00\x00\x00", 0),
|
||||
OpponentData(100, "Armed Dragon LV7", ["Quick-Finish"], 2, 1, 6107, 23022, "deck/limit_022.ydc\x00\x00\x00\x00", 0),
|
||||
OpponentData(101, "Alkana Knight Joker", ["Quick-Finish"], 1, 1, 6454, 23023, "deck/limit_023.ydc\x00\x00\x00\x00",
|
||||
0),
|
||||
OpponentData(102, "Sorcerer of Dark Magic", [], 4, 1, 6086, 23024, "deck/limit_024.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(103, "Shinato, King of a Higher Plane", [], 4, 1, 5697, 23025, "deck/limit_025.ydc\x00\x00\x00\x00",
|
||||
3),
|
||||
OpponentData(104, "Ryu Kokki", [], 5, 1, 5902, 23026, "deck/limit_026.ydc\x00\x00\x00\x00", 4),
|
||||
OpponentData(105, "Cyber Dragon", [], 5, 1, 6390, 23027, "deck/limit_027.ydc\x00\x00\x00\x00", 4),
|
||||
OpponentData(106, "Dark Dreadroute", ["Quick-Finish"], 3, 1, 6405, 23028, "deck/limit_028.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(107, "Ultimate Insect LV7", ["Quick-Finish"], 3, 1, 6319, 23029, "deck/limit_029.ydc\x00\x00\x00\x00",
|
||||
2),
|
||||
OpponentData(108, "Thestalos the Firestorm Monarch", ["Quick-Finish"], 3, 1, 6190, 23030,
|
||||
"deck/limit_030.ydc\x00\x00\x00\x00"),
|
||||
OpponentData(109, "Master of Oz", ["Quick-Finish"], 3, 1, 6127, 23031, "deck/limit_031.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(110, "Orca Mega-Fortress of Darkness", ["Quick-Finish"], 3, 1, 5896, 23032,
|
||||
"deck/limit_032.ydc\x00\x00\x00\x00", 2),
|
||||
OpponentData(111, "Airknight Parshath", ["Quick-Finish"], 4, 1, 5023, 23033, "deck/limit_033.ydc\x00\x00\x00\x00",
|
||||
3),
|
||||
OpponentData(112, "Dark Scorpion Burglars", ["Quick-Finish"], 4, 1, 5425, 23034,
|
||||
"deck/limit_034.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(113, "Gilford the Lightning", [], 4, 1, 5451, 23035, "deck/limit_035.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(114, "Embodiment of Apophis", [], 2, 1, 5234, 23036, "deck/limit_036.ydc\x00\x00\x00\x00", 1),
|
||||
OpponentData(115, "Great Maju Garzett", [], 5, 1, 5768, 23037, "deck/limit_037.ydc\x00\x00\x00\x00", 4),
|
||||
OpponentData(116, "Black Luster Soldier - Envoy of the Beginning", [], 5, 1, 5835, 23038,
|
||||
"deck/limit_038.ydc\x00\x00\x00\x00", 4),
|
||||
OpponentData(117, "Red-Eyes B. Dragon", [], 4, 1, 4088, 23039, "deck/limit_039.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(118, "Blue-Eyes White Dragon", [], 4, 1, 4007, 23040, "deck/limit_040.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(119, "Dark Magician", [], 4, 1, 4041, 23041, "deck/limit_041.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(0, "Starter", ["Quick-Finish"], 1, 1, 4064, 1510, "deck/SD0_STARTER.ydc\x00\x00", 0),
|
||||
OpponentData(10, "DRAGON'S ROAR", ["Quick-Finish"], 2, 1, 6292, 1511, "deck/SD1_DRAGON.ydc\x00\x00\x00", 1),
|
||||
OpponentData(11, "ZOMBIE MADNESS", ["Quick-Finish"], 2, 1, 6293, 1512, "deck/SD2_UNDEAD.ydc\x00\x00\x00", 1),
|
||||
OpponentData(12, "BLAZING DESTRUCTION", ["Quick-Finish"], 2, 1, 6368, 1513, "deck/SD3_FIRE.ydc\x00\x00\x00\x00\x00",
|
||||
1,
|
||||
["Has Back-row removal"]),
|
||||
OpponentData(13, "FURY FROM THE DEEP", [], 2, 1, 6376, 1514,
|
||||
"deck/SD4_UMI.ydc\x00\x00\x00\x00\x00\x00", 1, ["Has Back-row removal"]),
|
||||
OpponentData(15, "WARRIORS TRIUMPH", ["Quick-Finish"], 2, 1, 6456, 1515, "deck/SD5_SOLDIER.ydc\x00\x00", 1),
|
||||
OpponentData(16, "SPELLCASTERS JUDGEMENT", ["Quick-Finish"], 2, 1, 6530, 1516, "deck/SD6_MAGICIAN.ydc\x00", 1),
|
||||
OpponentData(17, "INVICIBLE FORTRESS", [], 2, 1, 6640, 1517, "deck/SD7_GANSEKI.ydc\x00\x00", 1),
|
||||
OpponentData(7, "Goblin King 2", ["Quick-Finish"], 3, 3, 5973, 8007, "deck/LV2_kingG2.ydc\x00\x00\x00", 2),
|
||||
]
|
||||
|
||||
|
||||
def get_opponents(multiworld: Optional[MultiWorld], player: Optional[int], randomize: bool = False) -> List[
|
||||
OpponentData]:
|
||||
opponents_table: List[OpponentData] = [
|
||||
# Tier 1
|
||||
OpponentData(0, "Kuriboh", [], 1, 1, 4064, 8000, "deck/LV1_kuriboh.ydc\x00\x00"),
|
||||
OpponentData(1, "Scapegoat", [], 1, 2, 4818, 8001, "deck/LV1_sukego.ydc\x00\x00\x00", 0,
|
||||
["Has Back-row removal"]),
|
||||
OpponentData(2, "Skull Servant", [], 1, 3, 4030, 8002, "deck/LV1_waito.ydc\x00\x00\x00\x00", 0,
|
||||
["Has Back-row removal"]),
|
||||
OpponentData(3, "Watapon", [], 1, 4, 6092, 8003, "deck/LV1_watapon.ydc\x00\x00", 0, ["Has Back-row removal"]),
|
||||
OpponentData(4, "White Magician Pikeru", [], 1, 5, 5975, 8004, "deck/LV1_pikeru.ydc\x00\x00\x00"),
|
||||
# Tier 2
|
||||
OpponentData(5, "Battery Man C", ["Quick-Finish"], 2, 1, 6428, 8005, "deck/LV2_denti.ydc\x00\x00\x00\x00", 1),
|
||||
OpponentData(6, "Ojama Yellow", [], 2, 2, 5811, 8006, "deck/LV2_ojama.ydc\x00\x00\x00\x00", 1,
|
||||
["Has Back-row removal"]),
|
||||
OpponentData(7, "Goblin King", ["Quick-Finish"], 2, 3, 5973, 8007, "deck/LV2_kingG.ydc\x00\x00\x00\x00", 1),
|
||||
OpponentData(8, "Des Frog", ["Quick-Finish"], 2, 4, 6424, 8008, "deck/LV2_kaeru.ydc\x00\x00\x00\x00", 1),
|
||||
OpponentData(9, "Water Dragon", ["Quick-Finish"], 2, 5, 6481, 8009, "deck/LV2_waterD.ydc\x00\x00\x00", 1),
|
||||
# Tier 3
|
||||
OpponentData(10, "Red-Eyes Darkness Dragon", ["Quick-Finish"], 3, 1, 6292, 8010, "deck/LV3_RedEyes.ydc\x00\x00",
|
||||
2),
|
||||
OpponentData(11, "Vampire Genesis", ["Quick-Finish"], 3, 2, 6293, 8011, "deck/LV3_vamp.ydc\x00\x00\x00\x00\x00",
|
||||
2),
|
||||
OpponentData(12, "Infernal Flame Emperor", [], 3, 3, 6368, 8012, "deck/LV3_flame.ydc\x00\x00\x00\x00", 2,
|
||||
["Has Back-row removal"]),
|
||||
OpponentData(13, "Ocean Dragon Lord - Neo-Daedalus", [], 3, 4, 6376, 8013, "deck/LV3_daidaros.ydc\x00", 2,
|
||||
["Has Back-row removal"]),
|
||||
OpponentData(14, "Helios Duo Megiste", ["Quick-Finish"], 3, 5, 6647, 8014, "deck/LV3_heriosu.ydc\x00\x00", 2),
|
||||
# Tier 4
|
||||
OpponentData(15, "Gilford the Legend", ["Quick-Finish"], 4, 1, 6456, 8015, "deck/LV4_gilfo.ydc\x00\x00\x00\x00",
|
||||
3),
|
||||
OpponentData(16, "Dark Eradicator Warlock", ["Quick-Finish"], 4, 2, 6530, 8016, "deck/LV4_kuromadou.ydc", 3),
|
||||
OpponentData(17, "Guardian Exode", [], 4, 3, 6640, 8017, "deck/LV4_exodo.ydc\x00\x00\x00\x00", 3),
|
||||
OpponentData(18, "Goldd, Wu-Lord of Dark World", ["Quick-Finish"], 4, 4, 6505, 8018, "deck/LV4_ankokukai.ydc",
|
||||
3),
|
||||
OpponentData(19, "Elemental Hero Erikshieler", ["Quick-Finish"], 4, 5, 6639, 8019,
|
||||
"deck/LV4_Ehero.ydc\x00\x00\x00\x00", 3),
|
||||
# Tier 5
|
||||
OpponentData(20, "Raviel, Lord of Phantasms", [], 5, 1, 6565, 8020, "deck/LV5_ravieru.ydc\x00\x00", 4),
|
||||
OpponentData(21, "Horus the Black Flame Dragon LV8", [], 5, 2, 6100, 8021, "deck/LV5_horus.ydc\x00\x00\x00\x00",
|
||||
4),
|
||||
OpponentData(22, "Stronghold", [], 5, 3, 6153, 8022, "deck/LV5_gadget.ydc\x00\x00\x00", 5),
|
||||
OpponentData(23, "Sacred Phoenix of Nephthys", [], 5, 4, 6236, 8023, "deck/LV5_nephthys.ydc\x00", 6),
|
||||
OpponentData(24, "Cyber End Dragon", ["Goal"], 5, 5, 6397, 8024, "deck/LV5_cyber.ydc\x00\x00\x00\x00", 7),
|
||||
]
|
||||
world = multiworld.worlds[player]
|
||||
if not randomize:
|
||||
return opponents_table
|
||||
opponents = opponents_table + challenge_opponents
|
||||
start = world.random.choice([o for o in opponents if o.tier == 1 and len(o.additional_info) == 0])
|
||||
opponents.remove(start)
|
||||
goal = world.random.choice([o for o in opponents if "Goal" in o.campaign_info])
|
||||
opponents.remove(goal)
|
||||
world.random.shuffle(opponents)
|
||||
chosen_ones = opponents[:23]
|
||||
for item in (multiworld.precollected_items[player]):
|
||||
if item.name in tier_1_opponents:
|
||||
# convert item index to opponent index
|
||||
chosen_ones.insert(item_to_index[item.name] - item_to_index["Campaign Tier 1 Column 1"], start)
|
||||
break
|
||||
chosen_ones.append(goal)
|
||||
tier = 1
|
||||
column = 1
|
||||
recreation = []
|
||||
for opp in chosen_ones:
|
||||
recreation.append(OpponentData(opp.id, opp.name, opp.campaign_info, tier, column, opp.card_id,
|
||||
opp.deck_name_id, opp.deck_file, opp.difficulty))
|
||||
column += 1
|
||||
if column > 5:
|
||||
column = 1
|
||||
tier += 1
|
||||
|
||||
return recreation
|
||||
|
||||
|
||||
def get_opponent_locations(opponent: OpponentData) -> Dict[str, Optional[Union[str, int]]]:
|
||||
location = {opponent.name + " Beaten": "Tier " + str(opponent.tier) + " Beaten"}
|
||||
if opponent.tier > 4 and opponent.column != 5:
|
||||
name = "Campaign Tier 5: Column " + str(opponent.column) + " Win"
|
||||
# return a int instead so a item can be placed at this location later
|
||||
location[name] = special[name]
|
||||
for info in opponent.campaign_info:
|
||||
location[opponent.name + "-> " + info] = info
|
||||
return location
|
||||
|
||||
|
||||
def get_opponent_condition(opponent: OpponentData, unlock_item: str, unlock_amount: int, player: int,
|
||||
is_challenge: bool) -> CollectionRule:
|
||||
if is_challenge:
|
||||
return lambda state: (
|
||||
state.has(unlock_item, player, unlock_amount)
|
||||
and yugioh06_difficulty(state, player, opponent.difficulty)
|
||||
and state.has_all(opponent.additional_info, player)
|
||||
)
|
||||
else:
|
||||
return lambda state: (
|
||||
state.has_group(unlock_item, player, unlock_amount)
|
||||
and yugioh06_difficulty(state, player, opponent.difficulty)
|
||||
and state.has_all(opponent.additional_info, player)
|
||||
)
|
||||
195
worlds/yugioh06/options.py
Normal file
195
worlds/yugioh06/options.py
Normal file
@@ -0,0 +1,195 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from Options import Choice, DefaultOnToggle, PerGameCommonOptions, Range, Toggle
|
||||
|
||||
|
||||
class StructureDeck(Choice):
|
||||
"""Which Structure Deck you start with"""
|
||||
|
||||
display_name = "Structure Deck"
|
||||
option_dragons_roar = 0
|
||||
option_zombie_madness = 1
|
||||
option_blazing_destruction = 2
|
||||
option_fury_from_the_deep = 3
|
||||
option_warriors_triumph = 4
|
||||
option_spellcasters_judgement = 5
|
||||
option_none = 6
|
||||
option_random_deck = 7
|
||||
default = 7
|
||||
|
||||
|
||||
class Banlist(Choice):
|
||||
"""Which Banlist you start with"""
|
||||
|
||||
display_name = "Banlist"
|
||||
option_no_banlist = 0
|
||||
option_september_2003 = 1
|
||||
option_march_2004 = 2
|
||||
option_september_2004 = 3
|
||||
option_march_2005 = 4
|
||||
option_september_2005 = 5
|
||||
default = option_september_2005
|
||||
|
||||
|
||||
class FinalCampaignBossUnlockCondition(Choice):
|
||||
"""How to unlock the final campaign boss and goal for the world"""
|
||||
|
||||
display_name = "Final Campaign Boss unlock Condition"
|
||||
option_campaign_opponents = 0
|
||||
option_challenges = 1
|
||||
|
||||
|
||||
class FourthTier5UnlockCondition(Choice):
|
||||
"""How to unlock the fourth campaign boss"""
|
||||
|
||||
display_name = "Fourth Tier 5 Campaign Boss unlock Condition"
|
||||
option_campaign_opponents = 0
|
||||
option_challenges = 1
|
||||
|
||||
|
||||
class ThirdTier5UnlockCondition(Choice):
|
||||
"""How to unlock the third campaign boss"""
|
||||
|
||||
display_name = "Third Tier 5 Campaign Boss unlock Condition"
|
||||
option_campaign_opponents = 0
|
||||
option_challenges = 1
|
||||
|
||||
|
||||
class FinalCampaignBossChallenges(Range):
|
||||
"""Number of Limited/Theme Duels completed for the Final Campaign Boss to appear"""
|
||||
|
||||
display_name = "Final Campaign Boss challenges unlock amount"
|
||||
range_start = 0
|
||||
range_end = 91
|
||||
default = 10
|
||||
|
||||
|
||||
class FourthTier5CampaignBossChallenges(Range):
|
||||
"""Number of Limited/Theme Duels completed for the Fourth Level 5 Campaign Opponent to appear"""
|
||||
|
||||
display_name = "Fourth Tier 5 Campaign Boss unlock amount"
|
||||
range_start = 0
|
||||
range_end = 91
|
||||
default = 5
|
||||
|
||||
|
||||
class ThirdTier5CampaignBossChallenges(Range):
|
||||
"""Number of Limited/Theme Duels completed for the Third Level 5 Campaign Opponent to appear"""
|
||||
|
||||
display_name = "Third Tier 5 Campaign Boss unlock amount"
|
||||
range_start = 0
|
||||
range_end = 91
|
||||
default = 2
|
||||
|
||||
|
||||
class FinalCampaignBossCampaignOpponents(Range):
|
||||
"""Number of Campaign Opponents Duels defeated for the Final Campaign Boss to appear"""
|
||||
|
||||
display_name = "Final Campaign Boss campaign opponent unlock amount"
|
||||
range_start = 0
|
||||
range_end = 24
|
||||
default = 12
|
||||
|
||||
|
||||
class FourthTier5CampaignBossCampaignOpponents(Range):
|
||||
"""Number of Campaign Opponents Duels defeated for the Fourth Level 5 Campaign Opponent to appear"""
|
||||
|
||||
display_name = "Fourth Tier 5 Campaign Boss campaign opponent unlock amount"
|
||||
range_start = 0
|
||||
range_end = 23
|
||||
default = 7
|
||||
|
||||
|
||||
class ThirdTier5CampaignBossCampaignOpponents(Range):
|
||||
"""Number of Campaign Opponents Duels defeated for the Third Level 5 Campaign Opponent to appear"""
|
||||
|
||||
display_name = "Third Tier 5 Campaign Boss campaign opponent unlock amount"
|
||||
range_start = 0
|
||||
range_end = 22
|
||||
default = 3
|
||||
|
||||
|
||||
class NumberOfChallenges(Range):
|
||||
"""Number of random Limited/Theme Duels that are included. The rest will be inaccessible."""
|
||||
|
||||
display_name = "Number of Challenges"
|
||||
range_start = 0
|
||||
range_end = 91
|
||||
default = 10
|
||||
|
||||
|
||||
class StartingMoney(Range):
|
||||
"""The amount of money you start with"""
|
||||
|
||||
display_name = "Starting Money"
|
||||
range_start = 0
|
||||
range_end = 100000
|
||||
default = 3000
|
||||
|
||||
|
||||
class MoneyRewardMultiplier(Range):
|
||||
"""By which amount the campaign reward money is multiplied"""
|
||||
|
||||
display_name = "Money Reward Multiplier"
|
||||
range_start = 1
|
||||
range_end = 255
|
||||
default = 20
|
||||
|
||||
|
||||
class NormalizeBoostersPacks(DefaultOnToggle):
|
||||
"""If enabled every booster pack costs the same otherwise vanilla cost is used"""
|
||||
|
||||
display_name = "Normalize Booster Packs"
|
||||
|
||||
|
||||
class BoosterPackPrices(Range):
|
||||
"""
|
||||
Only Works if normalize booster packs is enabled.
|
||||
Sets the amount that what every booster pack costs.
|
||||
"""
|
||||
|
||||
display_name = "Booster Pack Prices"
|
||||
range_start = 1
|
||||
range_end = 3000
|
||||
default = 100
|
||||
|
||||
|
||||
class AddEmptyBanList(Toggle):
|
||||
"""Adds a Ban List where everything is at 3 to the item pool"""
|
||||
|
||||
display_name = "Add Empty Ban List"
|
||||
|
||||
|
||||
class CampaignOpponentsShuffle(Toggle):
|
||||
"""Replaces the campaign with random opponents from the entire game"""
|
||||
|
||||
display_name = "Campaign Opponents Shuffle"
|
||||
|
||||
|
||||
class OCGArts(Toggle):
|
||||
"""Always use the OCG artworks for cards"""
|
||||
|
||||
display_name = "OCG Arts"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Yugioh06Options(PerGameCommonOptions):
|
||||
structure_deck: StructureDeck
|
||||
banlist: Banlist
|
||||
final_campaign_boss_unlock_condition: FinalCampaignBossUnlockCondition
|
||||
fourth_tier_5_campaign_boss_unlock_condition: FourthTier5UnlockCondition
|
||||
third_tier_5_campaign_boss_unlock_condition: ThirdTier5UnlockCondition
|
||||
final_campaign_boss_challenges: FinalCampaignBossChallenges
|
||||
fourth_tier_5_campaign_boss_challenges: FourthTier5CampaignBossChallenges
|
||||
third_tier_5_campaign_boss_challenges: ThirdTier5CampaignBossChallenges
|
||||
final_campaign_boss_campaign_opponents: FinalCampaignBossCampaignOpponents
|
||||
fourth_tier_5_campaign_boss_campaign_opponents: FourthTier5CampaignBossCampaignOpponents
|
||||
third_tier_5_campaign_boss_campaign_opponents: ThirdTier5CampaignBossCampaignOpponents
|
||||
number_of_challenges: NumberOfChallenges
|
||||
starting_money: StartingMoney
|
||||
money_reward_multiplier: MoneyRewardMultiplier
|
||||
normalize_boosters_packs: NormalizeBoostersPacks
|
||||
booster_pack_prices: BoosterPackPrices
|
||||
add_empty_banlist: AddEmptyBanList
|
||||
campaign_opponents_shuffle: CampaignOpponentsShuffle
|
||||
ocg_arts: OCGArts
|
||||
BIN
worlds/yugioh06/patch.bsdiff4
Normal file
BIN
worlds/yugioh06/patch.bsdiff4
Normal file
Binary file not shown.
BIN
worlds/yugioh06/patches/draft.bsdiff4
Normal file
BIN
worlds/yugioh06/patches/draft.bsdiff4
Normal file
Binary file not shown.
BIN
worlds/yugioh06/patches/ocg.bsdiff4
Normal file
BIN
worlds/yugioh06/patches/ocg.bsdiff4
Normal file
Binary file not shown.
163
worlds/yugioh06/rom.py
Normal file
163
worlds/yugioh06/rom.py
Normal file
@@ -0,0 +1,163 @@
|
||||
import hashlib
|
||||
import math
|
||||
import os
|
||||
import struct
|
||||
|
||||
from settings import get_settings
|
||||
|
||||
import Utils
|
||||
from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes
|
||||
|
||||
from worlds.AutoWorld import World
|
||||
from .items import item_to_index
|
||||
from .rom_values import banlist_ids, function_addresses, structure_deck_selection
|
||||
|
||||
MD5Europe = "020411d3b08f5639eb8cb878283f84bf"
|
||||
MD5America = "b8a7c976b28172995fe9e465d654297a"
|
||||
|
||||
|
||||
class YGO06ProcedurePatch(APProcedurePatch, APTokenMixin):
|
||||
game = "Yu-Gi-Oh! 2006"
|
||||
hash = MD5America
|
||||
patch_file_ending = ".apygo06"
|
||||
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_bytes()
|
||||
|
||||
|
||||
def write_tokens(world: World, patch: YGO06ProcedurePatch):
|
||||
structure_deck = structure_deck_selection.get(world.options.structure_deck.value)
|
||||
# set structure deck
|
||||
patch.write_token(APTokenTypes.WRITE, 0x000FD0AA, struct.pack("<B", structure_deck))
|
||||
# set banlist
|
||||
banlist = world.options.banlist
|
||||
patch.write_token(APTokenTypes.WRITE, 0xF4496, struct.pack("<B", banlist_ids.get(banlist.value)))
|
||||
# set items to locations map
|
||||
randomizer_data_start = 0x0000F310
|
||||
for location in world.multiworld.get_locations(world.player):
|
||||
item = location.item.name
|
||||
if location.item.player != world.player:
|
||||
item = "Remote"
|
||||
item_id = item_to_index.get(item)
|
||||
if item_id is None:
|
||||
continue
|
||||
location_id = world.location_name_to_id[location.name] - 5730000
|
||||
patch.write_token(APTokenTypes.WRITE, randomizer_data_start + location_id, struct.pack("<B", item_id))
|
||||
# set starting inventory
|
||||
inventory_map = [0 for i in range(32)]
|
||||
starting_inventory = list(map(lambda i: i.name, world.multiworld.precollected_items[world.player]))
|
||||
starting_inventory += world.options.start_inventory.value
|
||||
for start_inventory in starting_inventory:
|
||||
item_id = world.item_name_to_id[start_inventory] - 5730001
|
||||
index = math.floor(item_id / 8)
|
||||
bit = item_id % 8
|
||||
inventory_map[index] = inventory_map[index] | (1 << bit)
|
||||
for i in range(32):
|
||||
patch.write_token(APTokenTypes.WRITE, 0xE9DC + i, struct.pack("<B", inventory_map[i]))
|
||||
# set unlock conditions for the last 3 campaign opponents
|
||||
third_tier_5 = (
|
||||
world.options.third_tier_5_campaign_boss_challenges.value
|
||||
if world.options.third_tier_5_campaign_boss_unlock_condition.value == 1
|
||||
else world.options.third_tier_5_campaign_boss_campaign_opponents.value
|
||||
)
|
||||
patch.write_token(APTokenTypes.WRITE, 0xEEFA, struct.pack("<B", third_tier_5))
|
||||
|
||||
fourth_tier_5 = (
|
||||
world.options.fourth_tier_5_campaign_boss_challenges.value
|
||||
if world.options.fourth_tier_5_campaign_boss_unlock_condition.value == 1
|
||||
else world.options.fourth_tier_5_campaign_boss_campaign_opponents.value
|
||||
)
|
||||
patch.write_token(APTokenTypes.WRITE, 0xEF10, struct.pack("<B", fourth_tier_5))
|
||||
final = (
|
||||
world.options.final_campaign_boss_challenges.value
|
||||
if world.options.final_campaign_boss_unlock_condition.value == 1
|
||||
else world.options.final_campaign_boss_campaign_opponents.value
|
||||
)
|
||||
patch.write_token(APTokenTypes.WRITE, 0xEF22, struct.pack("<B", final))
|
||||
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
0xEEF8,
|
||||
struct.pack(
|
||||
"<B",
|
||||
int((function_addresses.get(world.options.third_tier_5_campaign_boss_unlock_condition.value) - 0xEEFA) / 2),
|
||||
),
|
||||
)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
0xEF0E,
|
||||
struct.pack(
|
||||
"<B",
|
||||
int(
|
||||
(function_addresses.get(world.options.fourth_tier_5_campaign_boss_unlock_condition.value) - 0xEF10) / 2
|
||||
),
|
||||
),
|
||||
)
|
||||
patch.write_token(
|
||||
APTokenTypes.WRITE,
|
||||
0xEF20,
|
||||
struct.pack(
|
||||
"<B", int((function_addresses.get(world.options.final_campaign_boss_unlock_condition.value) - 0xEF22) / 2)
|
||||
),
|
||||
)
|
||||
# set starting money
|
||||
patch.write_token(APTokenTypes.WRITE, 0xF4734, struct.pack("<I", world.options.starting_money))
|
||||
patch.write_token(APTokenTypes.WRITE, 0xE70C, struct.pack("<B", world.options.money_reward_multiplier.value))
|
||||
patch.write_token(APTokenTypes.WRITE, 0xE6E4, struct.pack("<B", world.options.money_reward_multiplier.value))
|
||||
# normalize booster packs if option is set
|
||||
if world.options.normalize_boosters_packs.value:
|
||||
booster_pack_price = world.options.booster_pack_prices.value.to_bytes(2, "little")
|
||||
for booster in range(51):
|
||||
space = booster * 16
|
||||
patch.write_token(APTokenTypes.WRITE, 0x1E5E2E8 + space, struct.pack("<B", booster_pack_price[0]))
|
||||
patch.write_token(APTokenTypes.WRITE, 0x1E5E2E9 + space, struct.pack("<B", booster_pack_price[1]))
|
||||
patch.write_token(APTokenTypes.WRITE, 0x1E5E2EA + space, struct.pack("<B", 5))
|
||||
# set shuffled campaign opponents if option is set
|
||||
if world.options.campaign_opponents_shuffle.value:
|
||||
i = 0
|
||||
for opp in world.campaign_opponents:
|
||||
space = i * 32
|
||||
patch.write_token(APTokenTypes.WRITE, 0x000F3BA + i, struct.pack("<B", opp.id))
|
||||
patch.write_token(APTokenTypes.WRITE, 0x1E58D0E + space, struct.pack("<H", opp.card_id))
|
||||
patch.write_token(APTokenTypes.WRITE, 0x1E58D10 + space, struct.pack("<H", opp.deck_name_id))
|
||||
for j, b in enumerate(opp.deck_file.encode("ascii")):
|
||||
patch.write_token(APTokenTypes.WRITE, 0x1E58D12 + space + j, struct.pack("<B", b))
|
||||
i = i + 1
|
||||
|
||||
for j, b in enumerate(world.romName):
|
||||
patch.write_token(APTokenTypes.WRITE, 0x10 + j, struct.pack("<B", b))
|
||||
for j, b in enumerate(world.playerName):
|
||||
patch.write_token(APTokenTypes.WRITE, 0x30 + j, struct.pack("<B", b))
|
||||
|
||||
patch.write_file("token_data.bin", patch.get_token_binary())
|
||||
|
||||
|
||||
def get_base_rom_bytes(file_name: str = "") -> bytes:
|
||||
base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None)
|
||||
if not base_rom_bytes:
|
||||
file_name = get_base_rom_path(file_name)
|
||||
base_rom_bytes = bytes(Utils.read_snes_rom(open(file_name, "rb")))
|
||||
|
||||
basemd5 = hashlib.md5()
|
||||
basemd5.update(base_rom_bytes)
|
||||
md5hash = basemd5.hexdigest()
|
||||
if MD5Europe != md5hash and MD5America != md5hash:
|
||||
raise Exception(
|
||||
"Supplied Base Rom does not match known MD5 for"
|
||||
"Yu-Gi-Oh! World Championship 2006 America or Europe "
|
||||
"Get the correct game and version, then dump it"
|
||||
)
|
||||
get_base_rom_bytes.base_rom_bytes = base_rom_bytes
|
||||
return base_rom_bytes
|
||||
|
||||
|
||||
def get_base_rom_path(file_name: str = "") -> str:
|
||||
if not file_name:
|
||||
file_name = get_settings().yugioh06_settings.rom_file
|
||||
if not os.path.exists(file_name):
|
||||
file_name = Utils.user_path(file_name)
|
||||
return file_name
|
||||
38
worlds/yugioh06/rom_values.py
Normal file
38
worlds/yugioh06/rom_values.py
Normal file
@@ -0,0 +1,38 @@
|
||||
structure_deck_selection = {
|
||||
# DRAGON'S ROAR
|
||||
0: 0x1,
|
||||
# ZOMBIE MADNESS
|
||||
1: 0x5,
|
||||
# BLAZING DESTRUCTION
|
||||
2: 0x9,
|
||||
# FURY FROM THE DEEP
|
||||
3: 0xD,
|
||||
# Warrior'S TRIUMPH
|
||||
4: 0x11,
|
||||
# SPELLCASTER'S JUDGEMENT
|
||||
5: 0x15,
|
||||
# Draft Mode
|
||||
6: 0x1,
|
||||
}
|
||||
|
||||
banlist_ids = {
|
||||
# NoList
|
||||
0: 0x0,
|
||||
# September 2003
|
||||
1: 0x5,
|
||||
# March 2004
|
||||
2: 0x6,
|
||||
# September 2004
|
||||
3: 0x7,
|
||||
# March 2005
|
||||
4: 0x8,
|
||||
# September 2005
|
||||
5: 0x9,
|
||||
}
|
||||
|
||||
function_addresses = {
|
||||
# Count Campaign Opponents
|
||||
0: 0xF0C8,
|
||||
# Count Challenges
|
||||
1: 0xEF3A,
|
||||
}
|
||||
12
worlds/yugioh06/ruff.toml
Normal file
12
worlds/yugioh06/ruff.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
line-length = 120
|
||||
|
||||
[lint]
|
||||
preview = true
|
||||
select = ["E", "F", "W", "I", "N", "Q", "UP", "RUF", "ISC", "T20"]
|
||||
ignore = ["RUF012", "RUF100"]
|
||||
|
||||
[per-file-ignores]
|
||||
# The way options definitions work right now, world devs are forced to break line length requirements.
|
||||
"options.py" = ["E501"]
|
||||
# Yu Gi Oh specific: The structure of the Opponents.py file makes the line length violations acceptable.
|
||||
"Opponents.py" = ["E501"]
|
||||
868
worlds/yugioh06/rules.py
Normal file
868
worlds/yugioh06/rules.py
Normal file
@@ -0,0 +1,868 @@
|
||||
from worlds.generic.Rules import add_rule
|
||||
|
||||
from . import yugioh06_difficulty
|
||||
from .fusions import count_has_materials
|
||||
|
||||
|
||||
def set_rules(world):
|
||||
player = world.player
|
||||
multiworld = world.multiworld
|
||||
|
||||
location_rules = {
|
||||
# Campaign
|
||||
"Campaign Tier 1: 1 Win": lambda state: state.has("Tier 1 Beaten", player),
|
||||
"Campaign Tier 1: 3 Wins A": lambda state: state.has("Tier 1 Beaten", player, 3),
|
||||
"Campaign Tier 1: 3 Wins B": lambda state: state.has("Tier 1 Beaten", player, 3),
|
||||
"Campaign Tier 1: 5 Wins A": lambda state: state.has("Tier 1 Beaten", player, 5),
|
||||
"Campaign Tier 1: 5 Wins B": lambda state: state.has("Tier 1 Beaten", player, 5),
|
||||
"Campaign Tier 2: 1 Win": lambda state: state.has("Tier 2 Beaten", player),
|
||||
"Campaign Tier 2: 3 Wins A": lambda state: state.has("Tier 2 Beaten", player, 3),
|
||||
"Campaign Tier 2: 3 Wins B": lambda state: state.has("Tier 2 Beaten", player, 3),
|
||||
"Campaign Tier 2: 5 Wins A": lambda state: state.has("Tier 2 Beaten", player, 5),
|
||||
"Campaign Tier 2: 5 Wins B": lambda state: state.has("Tier 2 Beaten", player, 5),
|
||||
"Campaign Tier 3: 1 Win": lambda state: state.has("Tier 3 Beaten", player),
|
||||
"Campaign Tier 3: 3 Wins A": lambda state: state.has("Tier 3 Beaten", player, 3),
|
||||
"Campaign Tier 3: 3 Wins B": lambda state: state.has("Tier 3 Beaten", player, 3),
|
||||
"Campaign Tier 3: 5 Wins A": lambda state: state.has("Tier 3 Beaten", player, 5),
|
||||
"Campaign Tier 3: 5 Wins B": lambda state: state.has("Tier 3 Beaten", player, 5),
|
||||
"Campaign Tier 4: 5 Wins A": lambda state: state.has("Tier 4 Beaten", player, 5),
|
||||
"Campaign Tier 4: 5 Wins B": lambda state: state.has("Tier 4 Beaten", player, 5),
|
||||
|
||||
# Bonuses
|
||||
"Duelist Bonus Level 1": lambda state: state.has("Tier 1 Beaten", player),
|
||||
"Duelist Bonus Level 2": lambda state: state.has("Tier 2 Beaten", player),
|
||||
"Duelist Bonus Level 3": lambda state: state.has("Tier 3 Beaten", player),
|
||||
"Duelist Bonus Level 4": lambda state: state.has("Tier 4 Beaten", player),
|
||||
"Duelist Bonus Level 5": lambda state: state.has("Tier 5 Beaten", player),
|
||||
"Max ATK Bonus": lambda state: yugioh06_difficulty(state, player, 2),
|
||||
"No Spell Cards Bonus": lambda state: yugioh06_difficulty(state, player, 2),
|
||||
"No Trap Cards Bonus": lambda state: yugioh06_difficulty(state, player, 2),
|
||||
"No Damage Bonus": lambda state: state.has_group("Campaign Boss Beaten", player, 3),
|
||||
"Low Deck Bonus": lambda state: state.has_any(["Reasoning", "Monster Gate", "Magical Merchant"], player) and
|
||||
yugioh06_difficulty(state, player, 3),
|
||||
"Extremely Low Deck Bonus":
|
||||
lambda state: state.has_any(["Reasoning", "Monster Gate", "Magical Merchant"], player) and
|
||||
yugioh06_difficulty(state, player, 2),
|
||||
"Opponent's Turn Finish Bonus": lambda state: yugioh06_difficulty(state, player, 2),
|
||||
"Exactly 0 LP Bonus": lambda state: yugioh06_difficulty(state, player, 2),
|
||||
"Reversal Finish Bonus": lambda state: yugioh06_difficulty(state, player, 2),
|
||||
"Quick Finish Bonus": lambda state: state.has("Quick-Finish", player) or yugioh06_difficulty(state, player, 6),
|
||||
"Exodia Finish Bonus": lambda state: state.has("Can Exodia Win", player),
|
||||
"Last Turn Finish Bonus": lambda state: state.has("Can Last Turn Win", player),
|
||||
"Yata-Garasu Finish Bonus": lambda state: state.has("Can Yata Lock", player),
|
||||
"Skull Servant Finish Bonus": lambda state: state.has("Skull Servant", player) and
|
||||
yugioh06_difficulty(state, player, 3),
|
||||
"Konami Bonus": lambda state: state.has_all(["Messenger of Peace", "Castle of Dark Illusions", "Mystik Wok"],
|
||||
player) or (state.has_all(["Mystik Wok", "Barox", "Cyber-Stein",
|
||||
"Poison of the Old Man"],
|
||||
player) and yugioh06_difficulty(state,
|
||||
player, 8)),
|
||||
"Max Damage Bonus": lambda state: state.has_any(["Wave-Motion Cannon", "Megamorph", "United We Stand",
|
||||
"Mage Power"], player),
|
||||
"Fusion Summon Bonus": lambda state: state.has_any(["Polymerization", "Fusion Gate", "Power Bond"], player),
|
||||
"Ritual Summon Bonus": lambda state: state.has("Ritual", player),
|
||||
"Over 20000 LP Bonus": lambda state: can_gain_lp_every_turn(state, player)
|
||||
and state.has("Can Stall with ST", player),
|
||||
"Low LP Bonus": lambda state: state.has("Wall of Revealing Light", player) and yugioh06_difficulty(state, player,
|
||||
2),
|
||||
"Extremely Low LP Bonus": lambda state: state.has_all(["Wall of Revealing Light", "Messenger of Peace"], player)
|
||||
and yugioh06_difficulty(state, player, 4),
|
||||
"Effect Damage Only Bonus": lambda state: state.has_all(["Solar Flare Dragon", "UFO Turtle"], player)
|
||||
or state.has("Wave-Motion Cannon", player)
|
||||
or state.can_reach("Final Countdown Finish Bonus", "Location", player)
|
||||
or state.can_reach("Destiny Board Finish Bonus", "Location", player)
|
||||
or state.has("Can Exodia Win", player)
|
||||
or state.has("Can Last Turn Win", player),
|
||||
"No More Cards Bonus": lambda state: state.has_any(["Cyber Jar", "Morphing Jar",
|
||||
"Morphing Jar #2", "Needle Worm"], player)
|
||||
and state.has_any(["The Shallow Grave", "Spear Cretin"],
|
||||
player) and yugioh06_difficulty(state, player, 5),
|
||||
"Final Countdown Finish Bonus": lambda state: state.has("Final Countdown", player)
|
||||
and state.has("Can Stall with ST", player),
|
||||
"Destiny Board Finish Bonus": lambda state: state.has("Can Stall with Monsters", player) and
|
||||
state.has("Destiny Board and its letters", player) and
|
||||
state.has("A Cat of Ill Omen", player),
|
||||
|
||||
# Cards
|
||||
"Obtain all pieces of Exodia": lambda state: state.has("Exodia", player),
|
||||
"Obtain Final Countdown": lambda state: state.has("Final Countdown", player),
|
||||
"Obtain Victory Dragon": lambda state: state.has("Victory D.", player),
|
||||
"Obtain Ojama Delta Hurricane and its required cards":
|
||||
lambda state: state.has("Ojama Delta Hurricane and required cards", player),
|
||||
"Obtain Huge Revolution and its required cards":
|
||||
lambda state: state.has("Huge Revolution and its required cards", player),
|
||||
"Obtain Perfectly Ultimate Great Moth and its required cards":
|
||||
lambda state: state.has("Perfectly Ultimate Great Moth and its required cards", player),
|
||||
"Obtain Valkyrion the Magna Warrior and its pieces":
|
||||
lambda state: state.has("Valkyrion the Magna Warrior and its pieces", player),
|
||||
"Obtain Dark Sage and its required cards": lambda state: state.has("Dark Sage and its required cards", player),
|
||||
"Obtain Destiny Board and its letters": lambda state: state.has("Destiny Board and its letters", player),
|
||||
"Obtain all XYZ-Dragon Cannon fusions and their materials":
|
||||
lambda state: state.has("XYZ-Dragon Cannon fusions and their materials", player),
|
||||
"Obtain VWXYZ-Dragon Catapult Cannon and the fusion materials":
|
||||
lambda state: state.has("VWXYZ-Dragon Catapult Cannon and the fusion materials", player),
|
||||
"Obtain Hamon, Lord of Striking Thunder":
|
||||
lambda state: state.has("Hamon, Lord of Striking Thunder", player),
|
||||
"Obtain Raviel, Lord of Phantasms":
|
||||
lambda state: state.has("Raviel, Lord of Phantasms", player),
|
||||
"Obtain Uria, Lord of Searing Flames":
|
||||
lambda state: state.has("Uria, Lord of Searing Flames", player),
|
||||
"Obtain Gate Guardian and its pieces":
|
||||
lambda state: state.has("Gate Guardian and its pieces", player),
|
||||
"Obtain Dark Scorpion Combination and its required cards":
|
||||
lambda state: state.has("Dark Scorpion Combination and its required cards", player),
|
||||
# Collection Events
|
||||
"Ojama Delta Hurricane and required cards":
|
||||
lambda state: state.has_all(["Ojama Delta Hurricane", "Ojama Green", "Ojama Yellow", "Ojama Black"],
|
||||
player),
|
||||
"Huge Revolution and its required cards":
|
||||
lambda state: state.has_all(["Huge Revolution", "Oppressed People", "United Resistance",
|
||||
"People Running About"], player),
|
||||
"Perfectly Ultimate Great Moth and its required cards":
|
||||
lambda state: state.has_all(["Perfectly Ultimate Great Moth", "Petit Moth", "Cocoon of Evolution"], player),
|
||||
"Valkyrion the Magna Warrior and its pieces":
|
||||
lambda state: state.has_all(["Valkyrion the Magna Warrior", "Alpha the Magnet Warrior",
|
||||
"Beta the Magnet Warrior", "Gamma the Magnet Warrior"], player),
|
||||
"Dark Sage and its required cards":
|
||||
lambda state: state.has_all(["Dark Sage", "Dark Magician", "Time Wizard"], player),
|
||||
"Destiny Board and its letters":
|
||||
lambda state: state.has_all(["Destiny Board", "Spirit Message 'I'", "Spirit Message 'N'",
|
||||
"Spirit Message 'A'", "Spirit Message 'L'"], player),
|
||||
"XYZ-Dragon Cannon fusions and their materials":
|
||||
lambda state: state.has_all(["X-Head Cannon", "Y-Dragon Head", "Z-Metal Tank",
|
||||
"XY-Dragon Cannon", "XZ-Tank Cannon", "YZ-Tank Dragon", "XYZ-Dragon Cannon"],
|
||||
player),
|
||||
"VWXYZ-Dragon Catapult Cannon and the fusion materials":
|
||||
lambda state: state.has_all(["X-Head Cannon", "Y-Dragon Head", "Z-Metal Tank", "XYZ-Dragon Cannon",
|
||||
"V-Tiger Jet", "W-Wing Catapult", "VW-Tiger Catapult",
|
||||
"VWXYZ-Dragon Catapult Cannon"],
|
||||
player),
|
||||
"Gate Guardian and its pieces":
|
||||
lambda state: state.has_all(["Gate Guardian", "Kazejin", "Suijin", "Sanga of the Thunder"], player),
|
||||
"Dark Scorpion Combination and its required cards":
|
||||
lambda state: state.has_all(["Dark Scorpion Combination", "Don Zaloog", "Dark Scorpion - Chick the Yellow",
|
||||
"Dark Scorpion - Meanae the Thorn", "Dark Scorpion - Gorg the Strong",
|
||||
"Cliff the Trap Remover"], player),
|
||||
"Can Exodia Win":
|
||||
lambda state: state.has_all(["Exodia", "Heart of the Underdog"], player),
|
||||
"Can Last Turn Win":
|
||||
lambda state: state.has_all(["Last Turn", "Wall of Revealing Light"], player) and
|
||||
(state.has_any(["Jowgen the Spiritualist", "Jowls of Dark Demise", "Non Aggression Area"],
|
||||
player)
|
||||
or state.has_all(["Cyber-Stein", "The Last Warrior from Another Planet"], player)),
|
||||
"Can Yata Lock":
|
||||
lambda state: state.has_all(["Yata-Garasu", "Chaos Emperor Dragon - Envoy of the End", "Sangan"], player)
|
||||
and state.has_any(["No Banlist", "Banlist September 2003"], player),
|
||||
"Can Stall with Monsters":
|
||||
lambda state: state.count_from_list_exclusive(
|
||||
["Spirit Reaper", "Giant Germ", "Marshmallon", "Nimble Momonga"], player) >= 2,
|
||||
"Can Stall with ST":
|
||||
lambda state: state.count_from_list_exclusive(["Level Limit - Area B", "Gravity Bind", "Messenger of Peace"],
|
||||
player) >= 2,
|
||||
"Has Back-row removal":
|
||||
lambda state: back_row_removal(state, player)
|
||||
|
||||
}
|
||||
access_rules = {
|
||||
# Limited
|
||||
"LD01 All except Level 4 forbidden":
|
||||
lambda state: yugioh06_difficulty(state, player, 2),
|
||||
"LD02 Medium/high Level forbidden":
|
||||
lambda state: yugioh06_difficulty(state, player, 1),
|
||||
"LD03 ATK 1500 or more forbidden":
|
||||
lambda state: yugioh06_difficulty(state, player, 4),
|
||||
"LD04 Flip Effects forbidden":
|
||||
lambda state: yugioh06_difficulty(state, player, 1),
|
||||
"LD05 Tributes forbidden":
|
||||
lambda state: yugioh06_difficulty(state, player, 1),
|
||||
"LD06 Traps forbidden":
|
||||
lambda state: yugioh06_difficulty(state, player, 1),
|
||||
"LD07 Large Deck A":
|
||||
lambda state: yugioh06_difficulty(state, player, 4),
|
||||
"LD08 Large Deck B":
|
||||
lambda state: yugioh06_difficulty(state, player, 4),
|
||||
"LD09 Sets Forbidden":
|
||||
lambda state: yugioh06_difficulty(state, player, 1),
|
||||
"LD10 All except LV monsters forbidden":
|
||||
lambda state: only_level(state, player) and yugioh06_difficulty(state, player, 2),
|
||||
"LD11 All except Fairies forbidden":
|
||||
lambda state: only_fairy(state, player) and yugioh06_difficulty(state, player, 2),
|
||||
"LD12 All except Wind forbidden":
|
||||
lambda state: only_wind(state, player) and yugioh06_difficulty(state, player, 2),
|
||||
"LD13 All except monsters forbidden":
|
||||
lambda state: yugioh06_difficulty(state, player, 3),
|
||||
"LD14 Level 3 or below forbidden":
|
||||
lambda state: yugioh06_difficulty(state, player, 1),
|
||||
"LD15 DEF 1500 or less forbidden":
|
||||
lambda state: yugioh06_difficulty(state, player, 3),
|
||||
"LD16 Effect Monsters forbidden":
|
||||
lambda state: only_normal(state, player) and yugioh06_difficulty(state, player, 4),
|
||||
"LD17 Spells forbidden":
|
||||
lambda state: yugioh06_difficulty(state, player, 3),
|
||||
"LD18 Attacks forbidden":
|
||||
lambda state: state.has_all(["Wave-Motion Cannon", "Stealth Bird"], player)
|
||||
and state.count_from_list_exclusive(["Dark World Lightning", "Nobleman of Crossout",
|
||||
"Shield Crash", "Tribute to the Doomed"], player) >= 2
|
||||
and yugioh06_difficulty(state, player, 3),
|
||||
"LD19 All except E-Hero's forbidden":
|
||||
lambda state: state.has_any(["Polymerization", "Fusion Gate"], player) and
|
||||
count_has_materials(state, ["Elemental Hero Flame Wingman",
|
||||
"Elemental Hero Madballman",
|
||||
"Elemental Hero Rampart Blaster",
|
||||
"Elemental Hero Steam Healer",
|
||||
"Elemental Hero Shining Flare Wingman",
|
||||
"Elemental Hero Wildedge"], player) >= 3 and
|
||||
yugioh06_difficulty(state, player, 3),
|
||||
"LD20 All except Warriors forbidden":
|
||||
lambda state: only_warrior(state, player) and yugioh06_difficulty(state, player, 2),
|
||||
"LD21 All except Dark forbidden":
|
||||
lambda state: only_dark(state, player) and yugioh06_difficulty(state, player, 2),
|
||||
"LD22 All limited cards forbidden":
|
||||
lambda state: yugioh06_difficulty(state, player, 3),
|
||||
"LD23 Refer to Mar 05 Banlist":
|
||||
lambda state: yugioh06_difficulty(state, player, 5),
|
||||
"LD24 Refer to Sept 04 Banlist":
|
||||
lambda state: yugioh06_difficulty(state, player, 5),
|
||||
"LD25 Low Life Points":
|
||||
lambda state: yugioh06_difficulty(state, player, 5),
|
||||
"LD26 All except Toons forbidden":
|
||||
lambda state: only_toons(state, player) and yugioh06_difficulty(state, player, 2),
|
||||
"LD27 All except Spirits forbidden":
|
||||
lambda state: only_spirit(state, player) and yugioh06_difficulty(state, player, 2),
|
||||
"LD28 All except Dragons forbidden":
|
||||
lambda state: only_dragon(state, player) and yugioh06_difficulty(state, player, 2),
|
||||
"LD29 All except Spellcasters forbidden":
|
||||
lambda state: only_spellcaster(state, player) and yugioh06_difficulty(state, player, 2),
|
||||
"LD30 All except Light forbidden":
|
||||
lambda state: only_light(state, player) and yugioh06_difficulty(state, player, 2),
|
||||
"LD31 All except Fire forbidden":
|
||||
lambda state: only_fire(state, player) and yugioh06_difficulty(state, player, 2),
|
||||
"LD32 Decks with multiples forbidden":
|
||||
lambda state: yugioh06_difficulty(state, player, 4),
|
||||
"LD33 Special Summons forbidden":
|
||||
lambda state: yugioh06_difficulty(state, player, 2),
|
||||
"LD34 Normal Summons forbidden":
|
||||
lambda state: state.has_all(["Polymerization", "King of the Swamp"], player) and
|
||||
count_has_materials(state, ["Elemental Hero Flame Wingman",
|
||||
"Elemental Hero Madballman",
|
||||
"Elemental Hero Rampart Blaster",
|
||||
"Elemental Hero Steam Healer",
|
||||
"Elemental Hero Shining Flare Wingman",
|
||||
"Elemental Hero Wildedge"], player) >= 3 and
|
||||
yugioh06_difficulty(state, player, 4),
|
||||
"LD35 All except Zombies forbidden":
|
||||
lambda state: only_zombie(state, player) and yugioh06_difficulty(state, player, 2),
|
||||
"LD36 All except Earth forbidden":
|
||||
lambda state: only_earth(state, player) and yugioh06_difficulty(state, player, 2),
|
||||
"LD37 All except Water forbidden":
|
||||
lambda state: only_water(state, player) and yugioh06_difficulty(state, player, 2),
|
||||
"LD38 Refer to Mar 04 Banlist":
|
||||
lambda state: yugioh06_difficulty(state, player, 4),
|
||||
"LD39 Monsters forbidden":
|
||||
lambda state: state.has_all(["Skull Zoma", "Embodiment of Apophis"], player)
|
||||
and yugioh06_difficulty(state, player, 5),
|
||||
"LD40 Refer to Sept 05 Banlist":
|
||||
lambda state: yugioh06_difficulty(state, player, 5),
|
||||
"LD41 Refer to Sept 03 Banlist":
|
||||
lambda state: yugioh06_difficulty(state, player, 5),
|
||||
# Theme Duels
|
||||
"TD01 Battle Damage":
|
||||
lambda state: yugioh06_difficulty(state, player, 1),
|
||||
"TD02 Deflected Damage":
|
||||
lambda state: state.has("Fairy Box", player) and yugioh06_difficulty(state, player, 1),
|
||||
"TD03 Normal Summon":
|
||||
lambda state: yugioh06_difficulty(state, player, 3),
|
||||
"TD04 Ritual Summon":
|
||||
lambda state: yugioh06_difficulty(state, player, 3) and
|
||||
state.has_all(["Contract with the Abyss",
|
||||
"Manju of the Ten Thousand Hands",
|
||||
"Senju of the Thousand Hands",
|
||||
"Sonic Bird",
|
||||
"Pot of Avarice",
|
||||
"Dark Master - Zorc",
|
||||
"Demise, King of Armageddon",
|
||||
"The Masked Beast",
|
||||
"Magician of Black Chaos",
|
||||
"Dark Magic Ritual"], player),
|
||||
"TD05 Special Summon A":
|
||||
lambda state: yugioh06_difficulty(state, player, 3),
|
||||
"TD06 20x Spell":
|
||||
lambda state: state.has("Magical Blast", player) and yugioh06_difficulty(state, player, 3),
|
||||
"TD07 10x Trap":
|
||||
lambda state: yugioh06_difficulty(state, player, 3),
|
||||
"TD08 Draw":
|
||||
lambda state: state.has_any(["Self-Destruct Button", "Dark Snake Syndrome"], player) and
|
||||
yugioh06_difficulty(state, player, 3),
|
||||
"TD09 Hand Destruction":
|
||||
lambda state: state.has_all(["Cyber Jar",
|
||||
"Morphing Jar",
|
||||
"Book of Moon",
|
||||
"Book of Taiyou",
|
||||
"Card Destruction",
|
||||
"Serial Spell",
|
||||
"Spell Reproduction",
|
||||
"The Shallow Grave"], player) and yugioh06_difficulty(state, player, 3),
|
||||
"TD10 During Opponent's Turn":
|
||||
lambda state: yugioh06_difficulty(state, player, 3),
|
||||
"TD11 Recover":
|
||||
lambda state: can_gain_lp_every_turn(state, player) and yugioh06_difficulty(state, player, 3),
|
||||
"TD12 Remove Monsters by Effect":
|
||||
lambda state: state.has("Soul Release", player) and yugioh06_difficulty(state, player, 2),
|
||||
"TD13 Flip Summon":
|
||||
lambda state: pacman_deck(state, player) and yugioh06_difficulty(state, player, 2),
|
||||
"TD14 Special Summon B":
|
||||
lambda state: state.has_any(["Manticore of Darkness", "Treeborn Frog"], player) and
|
||||
state.has("Foolish Burial", player) and
|
||||
yugioh06_difficulty(state, player, 2),
|
||||
"TD15 Token":
|
||||
lambda state: state.has_all(["Dandylion", "Ojama Trio", "Stray Lambs"], player) and
|
||||
yugioh06_difficulty(state, player, 3),
|
||||
"TD16 Union":
|
||||
lambda state: equip_unions(state, player) and
|
||||
yugioh06_difficulty(state, player, 2),
|
||||
"TD17 10x Quick Spell":
|
||||
lambda state: quick_plays(state, player) and
|
||||
yugioh06_difficulty(state, player, 3),
|
||||
"TD18 The Forbidden":
|
||||
lambda state: state.has("Can Exodia Win", player),
|
||||
"TD19 20 Turns":
|
||||
lambda state: state.has("Final Countdown", player) and state.has("Can Stall with ST", player) and
|
||||
yugioh06_difficulty(state, player, 3),
|
||||
"TD20 Deck Destruction":
|
||||
lambda state: state.has_any(["Cyber Jar", "Morphing Jar", "Morphing Jar #2", "Needle Worm"], player)
|
||||
and state.has_any(["The Shallow Grave", "Spear Cretin"],
|
||||
player) and yugioh06_difficulty(state, player, 2),
|
||||
"TD21 Victory D.":
|
||||
lambda state: state.has("Victory D.", player) and only_dragon(state, player)
|
||||
and yugioh06_difficulty(state, player, 3),
|
||||
"TD22 The Preventers Fight Back":
|
||||
lambda state: state.has("Ojama Delta Hurricane and required cards", player) and
|
||||
state.has_all(["Rescue Cat", "Enchanting Fitting Room", "Jerry Beans Man"], player) and
|
||||
yugioh06_difficulty(state, player, 3),
|
||||
"TD23 Huge Revolution":
|
||||
lambda state: state.has("Huge Revolution and its required cards", player) and
|
||||
state.has_all(["Enchanting Fitting Room", "Jerry Beans Man"], player) and
|
||||
yugioh06_difficulty(state, player, 3),
|
||||
"TD24 Victory in 5 Turns":
|
||||
lambda state: yugioh06_difficulty(state, player, 3),
|
||||
"TD25 Moth Grows Up":
|
||||
lambda state: state.has("Perfectly Ultimate Great Moth and its required cards", player) and
|
||||
state.has_all(["Gokipon", "Howling Insect"], player) and
|
||||
yugioh06_difficulty(state, player, 3),
|
||||
"TD26 Magnetic Power":
|
||||
lambda state: state.has("Valkyrion the Magna Warrior and its pieces", player) and
|
||||
yugioh06_difficulty(state, player, 2),
|
||||
"TD27 Dark Sage":
|
||||
lambda state: state.has("Dark Sage and its required cards", player) and
|
||||
state.has_any(["Skilled Dark Magician", "Dark Magic Curtain"], player) and
|
||||
yugioh06_difficulty(state, player, 2),
|
||||
"TD28 Direct Damage":
|
||||
lambda state: yugioh06_difficulty(state, player, 2),
|
||||
"TD29 Destroy Monsters in Battle":
|
||||
lambda state: yugioh06_difficulty(state, player, 2),
|
||||
"TD30 Tribute Summon":
|
||||
lambda state: state.has("Treeborn Frog", player) and yugioh06_difficulty(state, player, 2),
|
||||
"TD31 Special Summon C":
|
||||
lambda state: state.count_from_list_exclusive(
|
||||
["Aqua Spirit", "Rock Spirit", "Spirit of Flames",
|
||||
"Garuda the Wind Spirit", "Gigantes", "Inferno", "Megarock Dragon", "Silpheed"],
|
||||
player) > 4 and yugioh06_difficulty(state, player, 3),
|
||||
"TD32 Toon":
|
||||
lambda state: only_toons(state, player) and yugioh06_difficulty(state, player, 3),
|
||||
"TD33 10x Counter":
|
||||
lambda state: counter_traps(state, player) and yugioh06_difficulty(state, player, 2),
|
||||
"TD34 Destiny Board":
|
||||
lambda state: state.has("Destiny Board and its letters", player)
|
||||
and state.has("Can Stall with Monsters", player)
|
||||
and state.has("A Cat of Ill Omen", player)
|
||||
and yugioh06_difficulty(state, player, 2),
|
||||
"TD35 Huge Damage in a Turn":
|
||||
lambda state: state.has_all(["Cyber-Stein", "Cyber Twin Dragon", "Megamorph"], player)
|
||||
and yugioh06_difficulty(state, player, 3),
|
||||
"TD36 V-Z In the House":
|
||||
lambda state: state.has("VWXYZ-Dragon Catapult Cannon and the fusion materials", player)
|
||||
and yugioh06_difficulty(state, player, 3),
|
||||
"TD37 Uria, Lord of Searing Flames":
|
||||
lambda state: state.has_all(["Uria, Lord of Searing Flames",
|
||||
"Embodiment of Apophis",
|
||||
"Skull Zoma",
|
||||
"Metal Reflect Slime"], player)
|
||||
and yugioh06_difficulty(state, player, 3),
|
||||
"TD38 Hamon, Lord of Striking Thunder":
|
||||
lambda state: state.has("Hamon, Lord of Striking Thunder", player)
|
||||
and yugioh06_difficulty(state, player, 3),
|
||||
"TD39 Raviel, Lord of Phantasms":
|
||||
lambda state: state.has_all(["Raviel, Lord of Phantasms", "Giant Germ"], player) and
|
||||
state.count_from_list_exclusive(["Archfiend Soldier",
|
||||
"Skull Descovery Knight",
|
||||
"Slate Warrior",
|
||||
"D. D. Trainer",
|
||||
"Earthbound Spirit"], player) >= 3
|
||||
and yugioh06_difficulty(state, player, 3),
|
||||
"TD40 Make a Chain":
|
||||
lambda state: state.has("Ultimate Offering", player)
|
||||
and yugioh06_difficulty(state, player, 4),
|
||||
"TD41 The Gatekeeper Stands Tall":
|
||||
lambda state: state.has("Gate Guardian and its pieces", player) and
|
||||
state.has_all(["Treeborn Frog", "Tribute Doll"], player)
|
||||
and yugioh06_difficulty(state, player, 4),
|
||||
"TD42 Serious Damage":
|
||||
lambda state: yugioh06_difficulty(state, player, 3),
|
||||
"TD43 Return Monsters with Effects":
|
||||
lambda state: state.has_all(["Penguin Soldier", "Messenger of Peace"], player)
|
||||
and yugioh06_difficulty(state, player, 4),
|
||||
"TD44 Fusion Summon":
|
||||
lambda state: state.has_all(["Fusion Gate", "Terraforming", "Dimension Fusion",
|
||||
"Return from the Different Dimension"], player) and
|
||||
count_has_materials(state, ["Elemental Hero Flame Wingman",
|
||||
"Elemental Hero Madballman",
|
||||
"Elemental Hero Rampart Blaster",
|
||||
"Elemental Hero Steam Healer",
|
||||
"Elemental Hero Shining Flare Wingman",
|
||||
"Elemental Hero Wildedge"], player) >= 4 and
|
||||
yugioh06_difficulty(state, player, 7),
|
||||
"TD45 Big Damage at once":
|
||||
lambda state: state.has("Wave-Motion Cannon", player)
|
||||
and yugioh06_difficulty(state, player, 3),
|
||||
"TD46 XYZ In the House":
|
||||
lambda state: state.has("XYZ-Dragon Cannon fusions and their materials", player) and
|
||||
state.has("Dimension Fusion", player),
|
||||
"TD47 Spell Counter":
|
||||
lambda state: spell_counter(state, player) and yugioh06_difficulty(state, player, 3),
|
||||
"TD48 Destroy Monsters with Effects":
|
||||
lambda state: state.has_all(["Blade Rabbit", "Dream Clown"], player) and
|
||||
state.has("Can Stall with ST", player) and
|
||||
yugioh06_difficulty(state, player, 3),
|
||||
"TD49 Plunder":
|
||||
lambda state: take_control(state, player) and yugioh06_difficulty(state, player, 5),
|
||||
"TD50 Dark Scorpion Combination":
|
||||
lambda state: state.has("Dark Scorpion Combination and its required cards", player) and
|
||||
state.has_all(["Reinforcement of the Army", "Mystic Tomato"], player) and
|
||||
yugioh06_difficulty(state, player, 3)
|
||||
}
|
||||
multiworld.completion_condition[player] = lambda state: state.has("Goal", player)
|
||||
|
||||
for loc in multiworld.get_locations(player):
|
||||
if loc.name in location_rules:
|
||||
add_rule(loc, location_rules[loc.name])
|
||||
if loc.name in access_rules:
|
||||
add_rule(multiworld.get_entrance(loc.name, player), access_rules[loc.name])
|
||||
|
||||
|
||||
def only_light(state, player):
|
||||
return state.has_from_list_exclusive([
|
||||
"Dunames Dark Witch",
|
||||
"X-Head Cannon",
|
||||
"Homunculus the Alchemic Being",
|
||||
"Hysteric Fairy",
|
||||
"Ninja Grandmaster Sasuke"], player, 2)\
|
||||
and state.has_from_list_exclusive([
|
||||
"Chaos Command Magician",
|
||||
"Cybernetic Magician",
|
||||
"Kaiser Glider",
|
||||
"The Agent of Judgment - Saturn",
|
||||
"Zaborg the Thunder Monarch",
|
||||
"Cyber Dragon"], player, 1) \
|
||||
and state.has_from_list_exclusive([
|
||||
"D.D. Warrior Lady",
|
||||
"Mystic Swordsman LV2",
|
||||
"Y-Dragon Head",
|
||||
"Z-Metal Tank",
|
||||
], player, 2) and state.has("Shining Angel", player)
|
||||
|
||||
|
||||
def only_dark(state, player):
|
||||
return state.has_from_list_exclusive([
|
||||
"Dark Elf",
|
||||
"Archfiend Soldier",
|
||||
"Mad Dog of Darkness",
|
||||
"Vorse Raider",
|
||||
"Skilled Dark Magician",
|
||||
"Skull Descovery Knight",
|
||||
"Mechanicalchaser",
|
||||
"Dark Blade",
|
||||
"Gil Garth",
|
||||
"La Jinn the Mystical Genie of the Lamp",
|
||||
"Opticlops",
|
||||
"Zure, Knight of Dark World",
|
||||
"Brron, Mad King of Dark World",
|
||||
"D.D. Survivor",
|
||||
"Exarion Universe",
|
||||
"Kycoo the Ghost Destroyer",
|
||||
"Regenerating Mummy"
|
||||
], player, 2) \
|
||||
and state.has_any([
|
||||
"Summoned Skull",
|
||||
"Skull Archfiend of Lightning",
|
||||
"The End of Anubis",
|
||||
"Dark Ruler Ha Des",
|
||||
"Beast of Talwar",
|
||||
"Inferno Hammer",
|
||||
"Jinzo",
|
||||
"Ryu Kokki"
|
||||
], player) \
|
||||
and state.has_from_list_exclusive([
|
||||
"Legendary Fiend",
|
||||
"Don Zaloog",
|
||||
"Newdoria",
|
||||
"Sangan",
|
||||
"Spirit Reaper",
|
||||
"Giant Germ"
|
||||
], player, 2) and state.has("Mystic Tomato", player)
|
||||
|
||||
|
||||
def only_earth(state, player):
|
||||
return state.has_from_list_exclusive([
|
||||
"Berserk Gorilla",
|
||||
"Gemini Elf",
|
||||
"Insect Knight",
|
||||
"Toon Gemini Elf",
|
||||
"Familiar-Possessed - Aussa",
|
||||
"Neo Bug",
|
||||
"Blindly Loyal Goblin",
|
||||
"Chiron the Mage",
|
||||
"Gearfried the Iron Knight"
|
||||
], player, 2) and state.has_any([
|
||||
"Dark Driceratops",
|
||||
"Granmarg the Rock Monarch",
|
||||
"Hieracosphinx",
|
||||
"Saber Beetle"
|
||||
], player) and state.has_from_list_exclusive([
|
||||
"Hyper Hammerhead",
|
||||
"Green Gadget",
|
||||
"Red Gadget",
|
||||
"Yellow Gadget",
|
||||
"Dimensional Warrior",
|
||||
"Enraged Muka Muka",
|
||||
"Exiled Force"
|
||||
], player, 2) and state.has("Giant Rat", player)
|
||||
|
||||
|
||||
def only_water(state, player):
|
||||
return state.has_from_list_exclusive([
|
||||
"Gagagigo",
|
||||
"Familiar-Possessed - Eria",
|
||||
"7 Colored Fish",
|
||||
"Sea Serpent Warrior of Darkness",
|
||||
"Abyss Soldier"
|
||||
], player, 2) and state.has_any([
|
||||
"Giga Gagagigo",
|
||||
"Amphibian Beast",
|
||||
"Terrorking Salmon",
|
||||
"Mobius the Frost Monarch"
|
||||
], player) and state.has_from_list_exclusive([
|
||||
"Revival Jam",
|
||||
"Yomi Ship",
|
||||
"Treeborn Frog"
|
||||
], player, 2) and state.has("Mother Grizzly", player)
|
||||
|
||||
|
||||
def only_fire(state, player):
|
||||
return state.has_from_list_exclusive([
|
||||
"Blazing Inpachi",
|
||||
"Familiar-Possessed - Hiita",
|
||||
"Great Angus",
|
||||
"Fire Beaters"
|
||||
], player, 2) and state.has_any([
|
||||
"Thestalos the Firestorm Monarch",
|
||||
"Horus the Black Flame Dragon LV6"
|
||||
], player) and state.has_from_list_exclusive([
|
||||
"Solar Flare Dragon",
|
||||
"Tenkabito Shien",
|
||||
"Ultimate Baseball Kid"
|
||||
], player, 2) and state.has("UFO Turtle", player)
|
||||
|
||||
|
||||
def only_wind(state, player):
|
||||
return state.has_from_list_exclusive([
|
||||
"Luster Dragon",
|
||||
"Slate Warrior",
|
||||
"Spear Dragon",
|
||||
"Familiar-Possessed - Wynn",
|
||||
"Harpie's Brother",
|
||||
"Nin-Ken Dog",
|
||||
"Cyber Harpie Lady",
|
||||
"Oxygeddon"
|
||||
], player, 2) and state.has_any([
|
||||
"Cyber-Tech Alligator",
|
||||
"Luster Dragon #2",
|
||||
"Armed Dragon LV5",
|
||||
"Roc from the Valley of Haze"
|
||||
], player) and state.has_from_list_exclusive([
|
||||
"Armed Dragon LV3",
|
||||
"Twin-Headed Behemoth",
|
||||
"Harpie Lady 1"
|
||||
], player, 2) and state.has("Flying Kamakiri 1", player)
|
||||
|
||||
|
||||
def only_fairy(state, player):
|
||||
return state.has_any([
|
||||
"Dunames Dark Witch",
|
||||
"Hysteric Fairy"
|
||||
], player) and (state.count_from_list_exclusive([
|
||||
"Dunames Dark Witch",
|
||||
"Hysteric Fairy",
|
||||
"Dancing Fairy",
|
||||
"Zolga",
|
||||
"Shining Angel",
|
||||
"Kelbek",
|
||||
"Mudora",
|
||||
"Asura Priest",
|
||||
"Cestus of Dagla"
|
||||
], player) + (state.has_any([
|
||||
"The Agent of Judgment - Saturn",
|
||||
"Airknight Parshath"
|
||||
], player))) >= 7
|
||||
|
||||
|
||||
def only_warrior(state, player):
|
||||
return state.has_any([
|
||||
"Dark Blade",
|
||||
"Blindly Loyal Goblin",
|
||||
"D.D. Survivor",
|
||||
"Gearfried the Iron knight",
|
||||
"Ninja Grandmaster Sasuke",
|
||||
"Warrior Beaters"
|
||||
], player) and (state.count_from_list_exclusive([
|
||||
"Warrior Lady of the Wasteland",
|
||||
"Exiled Force",
|
||||
"Mystic Swordsman LV2",
|
||||
"Dimensional Warrior",
|
||||
"Dandylion",
|
||||
"D.D. Assailant",
|
||||
"Blade Knight",
|
||||
"D.D. Warrior Lady",
|
||||
"Marauding Captain",
|
||||
"Command Knight",
|
||||
"Reinforcement of the Army"
|
||||
], player) + (state.has_any([
|
||||
"Freed the Matchless General",
|
||||
"Holy Knight Ishzark",
|
||||
"Silent Swordsman Lv5"
|
||||
], player))) >= 7
|
||||
|
||||
|
||||
def only_zombie(state, player):
|
||||
return state.has("Pyramid Turtle", player) \
|
||||
and state.has_from_list_exclusive([
|
||||
"Regenerating Mummy",
|
||||
"Ryu Kokki",
|
||||
"Spirit Reaper",
|
||||
"Master Kyonshee",
|
||||
"Curse of Vampire",
|
||||
"Vampire Lord",
|
||||
"Goblin Zombie",
|
||||
"Curse of Vampire",
|
||||
"Vampire Lord",
|
||||
"Goblin Zombie",
|
||||
"Book of Life",
|
||||
"Call of the Mummy"
|
||||
], player, 6)
|
||||
|
||||
|
||||
def only_dragon(state, player):
|
||||
return state.has_any([
|
||||
"Luster Dragon",
|
||||
"Spear Dragon",
|
||||
"Cave Dragon"
|
||||
], player) and (state.count_from_list_exclusive([
|
||||
"Luster Dragon",
|
||||
"Spear Dragon",
|
||||
"Cave Dragon"
|
||||
"Armed Dragon LV3",
|
||||
"Masked Dragon",
|
||||
"Twin-Headed Behemoth",
|
||||
"Element Dragon",
|
||||
"Troop Dragon",
|
||||
"Horus the Black Flame Dragon LV4",
|
||||
"Stamping Destruction"
|
||||
], player) + (state.has_any([
|
||||
"Luster Dragon #2",
|
||||
"Armed Dragon LV5",
|
||||
"Kaiser Glider",
|
||||
"Horus the Black Flame Dragon LV6"
|
||||
], player))) >= 7
|
||||
|
||||
|
||||
def only_spellcaster(state, player):
|
||||
return state.has_any([
|
||||
"Dark Elf",
|
||||
"Gemini Elf",
|
||||
"Skilled Dark Magician",
|
||||
"Toon Gemini Elf",
|
||||
"Kycoo the Ghost Destroyer",
|
||||
"Familiar-Possessed - Aussa"
|
||||
], player) and (state.count_from_list_exclusive([
|
||||
"Dark Elf",
|
||||
"Gemini Elf",
|
||||
"Skilled Dark Magician",
|
||||
"Toon Gemini Elf",
|
||||
"Kycoo the Ghost Destroyer",
|
||||
"Familiar-Possessed - Aussa",
|
||||
"Breaker the magical Warrior",
|
||||
"The Tricky",
|
||||
"Injection Fairy Lily",
|
||||
"Magician of Faith",
|
||||
"Tsukuyomi",
|
||||
"Gravekeeper's Spy",
|
||||
"Gravekeeper's Guard",
|
||||
"Summon Priest",
|
||||
"Old Vindictive Magician",
|
||||
"Apprentice Magician",
|
||||
"Magical Dimension"
|
||||
], player) + (state.has_any([
|
||||
"Chaos Command Magician",
|
||||
"Cybernetic Magician"
|
||||
], player))) >= 7
|
||||
|
||||
|
||||
def equip_unions(state, player):
|
||||
return (state.has_all(["Burning Beast", "Freezing Beast",
|
||||
"Metallizing Parasite - Lunatite", "Mother Grizzly"], player) or
|
||||
state.has_all(["Dark Blade", "Pitch-Dark Dragon",
|
||||
"Giant Orc", "Second Goblin", "Mystic Tomato"], player) or
|
||||
state.has_all(["Decayed Commander", "Zombie Tiger",
|
||||
"Vampire Orchis", "Des Dendle", "Giant Rat"], player) or
|
||||
state.has_all(["Indomitable Fighter Lei Lei", "Protective Soul Ailin",
|
||||
"V-Tiger Jet", "W-Wing Catapult", "Shining Angel"], player) or
|
||||
state.has_all(["X-Head Cannon", "Y-Dragon Head", "Z-Metal Tank", "Shining Angel"], player)) and\
|
||||
state.has_any(["Frontline Base", "Formation Union", "Roll Out!"], player)
|
||||
|
||||
|
||||
def can_gain_lp_every_turn(state, player):
|
||||
return state.count_from_list_exclusive([
|
||||
"Solemn Wishes",
|
||||
"Cure Mermaid",
|
||||
"Dancing Fairy",
|
||||
"Princess Pikeru",
|
||||
"Kiseitai"], player) >= 3
|
||||
|
||||
|
||||
def only_normal(state, player):
|
||||
return (state.has_from_list_exclusive([
|
||||
"Archfiend Soldier",
|
||||
"Gemini Elf",
|
||||
"Insect Knight",
|
||||
"Luster Dragon",
|
||||
"Mad Dog of Darkness",
|
||||
"Vorse Raider",
|
||||
"Blazing Inpachi",
|
||||
"Gagagigo",
|
||||
"Mechanicalchaser",
|
||||
"7 Colored Fish",
|
||||
"Dark Blade",
|
||||
"Dunames Dark Witch",
|
||||
"Giant Red Snake",
|
||||
"Gil Garth",
|
||||
"Great Angus",
|
||||
"Harpie's Brother",
|
||||
"La Jinn the Mystical Genie of the Lamp",
|
||||
"Neo Bug",
|
||||
"Nin-Ken Dog",
|
||||
"Opticlops",
|
||||
"Sea Serpent Warrior of Darkness",
|
||||
"X-Head Cannon",
|
||||
"Zure, Knight of Dark World"], player, 6) and
|
||||
state.has_any([
|
||||
"Cyber-Tech Alligator",
|
||||
"Summoned Skull",
|
||||
"Giga Gagagigo",
|
||||
"Amphibian Beast",
|
||||
"Beast of Talwar",
|
||||
"Luster Dragon #2",
|
||||
"Terrorking Salmon"], player))
|
||||
|
||||
|
||||
def only_level(state, player):
|
||||
return (state.has("Level Up!", player) and
|
||||
(state.has_all(["Armed Dragon LV3", "Armed Dragon LV5"], player) +
|
||||
state.has_all(["Horus the Black Flame Dragon LV4", "Horus the Black Flame Dragon LV6"], player) +
|
||||
state.has_all(["Mystic Swordsman LV4", "Mystic Swordsman LV6"], player) +
|
||||
state.has_all(["Silent Swordsman Lv3", "Silent Swordsman Lv5"], player) +
|
||||
state.has_all(["Ultimate Insect Lv3", "Ultimate Insect Lv5"], player)) >= 3)
|
||||
|
||||
|
||||
def spell_counter(state, player):
|
||||
return (state.has("Pitch-Black Power Stone", player) and
|
||||
state.has_from_list_exclusive(["Blast Magician",
|
||||
"Magical Marionette",
|
||||
"Mythical Beast Cerberus",
|
||||
"Royal Magical Library",
|
||||
"Spell-Counter Cards"], player, 2))
|
||||
|
||||
|
||||
def take_control(state, player):
|
||||
return state.has_from_list_exclusive(["Aussa the Earth Charmer",
|
||||
"Jowls of Dark Demise",
|
||||
"Brain Control",
|
||||
"Creature Swap",
|
||||
"Enemy Controller",
|
||||
"Mind Control",
|
||||
"Magician of Faith"], player, 5)
|
||||
|
||||
|
||||
def only_toons(state, player):
|
||||
return state.has_all(["Toon Gemini Elf",
|
||||
"Toon Goblin Attack Force",
|
||||
"Toon Masked Sorcerer",
|
||||
"Toon Mermaid",
|
||||
"Toon Dark Magician Girl",
|
||||
"Toon World"], player)
|
||||
|
||||
|
||||
def only_spirit(state, player):
|
||||
return state.has_all(["Asura Priest",
|
||||
"Fushi No Tori",
|
||||
"Maharaghi",
|
||||
"Susa Soldier"], player)
|
||||
|
||||
|
||||
def pacman_deck(state, player):
|
||||
return state.has_from_list_exclusive(["Des Lacooda",
|
||||
"Swarm of Locusts",
|
||||
"Swarm of Scarabs",
|
||||
"Wandering Mummy",
|
||||
"Golem Sentry",
|
||||
"Great Spirit",
|
||||
"Royal Keeper",
|
||||
"Stealth Bird"], player, 4)
|
||||
|
||||
|
||||
def quick_plays(state, player):
|
||||
return state.has_from_list_exclusive(["Collapse",
|
||||
"Emergency Provisions",
|
||||
"Enemy Controller",
|
||||
"Graceful Dice",
|
||||
"Mystik Wok",
|
||||
"Offerings to the Doomed",
|
||||
"Poison of the Old Man",
|
||||
"Reload",
|
||||
"Rush Recklessly",
|
||||
"The Reliable Guardian"], player, 4)
|
||||
|
||||
|
||||
def counter_traps(state, player):
|
||||
return state.has_from_list_exclusive(["Cursed Seal of the Forbidden Spell",
|
||||
"Divine Wrath",
|
||||
"Horn of Heaven",
|
||||
"Magic Drain",
|
||||
"Magic Jammer",
|
||||
"Negate Attack",
|
||||
"Seven Tools of the Bandit",
|
||||
"Solemn Judgment",
|
||||
"Spell Shield Type-8"], player, 5)
|
||||
|
||||
|
||||
def back_row_removal(state, player):
|
||||
return state.has_from_list_exclusive(["Anteatereatingant",
|
||||
"B.E.S. Tetran",
|
||||
"Breaker the Magical Warrior",
|
||||
"Calamity of the Wicked",
|
||||
"Chiron the Mage",
|
||||
"Dust Tornado",
|
||||
"Heavy Storm",
|
||||
"Mystical Space Typhoon",
|
||||
"Mobius the Frost Monarch",
|
||||
"Raigeki Break",
|
||||
"Stamping Destruction",
|
||||
"Swarm of Locusts"], player, 2)
|
||||
81
worlds/yugioh06/structure_deck.py
Normal file
81
worlds/yugioh06/structure_deck.py
Normal file
@@ -0,0 +1,81 @@
|
||||
structure_contents: dict[str, set] = {
|
||||
"dragons_roar": {
|
||||
"Luster Dragon",
|
||||
"Armed Dragon LV3",
|
||||
"Armed Dragon LV5",
|
||||
"Masked Dragon",
|
||||
"Twin-Headed Behemoth",
|
||||
"Stamping Destruction",
|
||||
"Nobleman of Crossout",
|
||||
"Creature Swap",
|
||||
"Reload",
|
||||
"Stamping Destruction",
|
||||
"Heavy Storm",
|
||||
"Dust Tornado",
|
||||
"Mystical Space Typhoon",
|
||||
},
|
||||
"zombie_madness": {
|
||||
"Pyramid Turtle",
|
||||
"Regenerating Mummy",
|
||||
"Ryu Kokki",
|
||||
"Book of Life",
|
||||
"Call of the Mummy",
|
||||
"Creature Swap",
|
||||
"Reload",
|
||||
"Heavy Storm",
|
||||
"Dust Tornado",
|
||||
"Mystical Space Typhoon",
|
||||
},
|
||||
"blazing_destruction": {
|
||||
"Inferno",
|
||||
"Solar Flare Dragon",
|
||||
"UFO Turtle",
|
||||
"Ultimate Baseball Kid",
|
||||
"Fire Beaters",
|
||||
"Tribute to The Doomed",
|
||||
"Level Limit - Area B",
|
||||
"Heavy Storm",
|
||||
"Dust Tornado",
|
||||
"Mystical Space Typhoon",
|
||||
},
|
||||
"fury_from_the_deep": {
|
||||
"Mother Grizzly",
|
||||
"Water Beaters",
|
||||
"Gravity Bind",
|
||||
"Reload",
|
||||
"Mobius the Frost Monarch",
|
||||
"Heavy Storm",
|
||||
"Dust Tornado",
|
||||
"Mystical Space Typhoon",
|
||||
},
|
||||
"warriors_triumph": {
|
||||
"Gearfried the Iron Knight",
|
||||
"D.D. Warrior Lady",
|
||||
"Marauding Captain",
|
||||
"Exiled Force",
|
||||
"Reinforcement of the Army",
|
||||
"Warrior Beaters",
|
||||
"Reload",
|
||||
"Heavy Storm",
|
||||
"Dust Tornado",
|
||||
"Mystical Space Typhoon",
|
||||
},
|
||||
"spellcasters_judgement": {
|
||||
"Dark Magician",
|
||||
"Apprentice Magician",
|
||||
"Breaker the Magical Warrior",
|
||||
"Magician of Faith",
|
||||
"Skilled Dark Magician",
|
||||
"Tsukuyomi",
|
||||
"Magical Dimension",
|
||||
"Mage PowerSpell-Counter Cards",
|
||||
"Heavy Storm",
|
||||
"Dust Tornado",
|
||||
"Mystical Space Typhoon",
|
||||
},
|
||||
"none": {},
|
||||
}
|
||||
|
||||
|
||||
def get_deck_content_locations(deck: str) -> dict[str, str]:
|
||||
return {f"{deck} {i}": content for i, content in enumerate(structure_contents[deck])}
|
||||
Reference in New Issue
Block a user