mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-04-06 14:08:17 -07:00
Merge branch 'main' into player-tracker
This commit is contained in:
@@ -126,7 +126,6 @@ class MultiWorld():
|
||||
set_player_attr('beemizer_total_chance', 0)
|
||||
set_player_attr('beemizer_trap_chance', 0)
|
||||
set_player_attr('escape_assist', [])
|
||||
set_player_attr('open_pyramid', False)
|
||||
set_player_attr('treasure_hunt_icon', 'Triforce Piece')
|
||||
set_player_attr('treasure_hunt_count', 0)
|
||||
set_player_attr('clock_mode', False)
|
||||
@@ -390,20 +389,14 @@ class MultiWorld():
|
||||
self.state.collect(item, True)
|
||||
|
||||
def push_item(self, location: Location, item: Item, collect: bool = True):
|
||||
if not isinstance(location, Location):
|
||||
raise RuntimeError(
|
||||
'Cannot assign item %s to invalid location %s (player %d).' % (item, location, item.player))
|
||||
assert location.can_fill(self.state, item, False), f"Cannot place {item} into {location}."
|
||||
location.item = item
|
||||
item.location = location
|
||||
item.world = self # try to not have this here anymore and create it with item?
|
||||
if collect:
|
||||
self.state.collect(item, location.event, location)
|
||||
|
||||
if location.can_fill(self.state, item, False):
|
||||
location.item = item
|
||||
item.location = location
|
||||
item.world = self # try to not have this here anymore
|
||||
if collect:
|
||||
self.state.collect(item, location.event, location)
|
||||
|
||||
logging.debug('Placed %s at %s', item, location)
|
||||
else:
|
||||
raise RuntimeError('Cannot assign item %s to location %s.' % (item, location))
|
||||
logging.debug('Placed %s at %s', item, location)
|
||||
|
||||
def get_entrances(self) -> List[Entrance]:
|
||||
if self._cached_entrances is None:
|
||||
@@ -1431,8 +1424,6 @@ class Spoiler():
|
||||
outfile.write('Entrance Shuffle: %s\n' % self.world.shuffle[player])
|
||||
if self.world.shuffle[player] != "vanilla":
|
||||
outfile.write('Entrance Shuffle Seed %s\n' % self.world.worlds[player].er_seed)
|
||||
outfile.write('Pyramid hole pre-opened: %s\n' % (
|
||||
'Yes' if self.world.open_pyramid[player] else 'No'))
|
||||
outfile.write('Shop inventory shuffle: %s\n' %
|
||||
bool_to_text("i" in self.world.shop_shuffle[player]))
|
||||
outfile.write('Shop price shuffle: %s\n' %
|
||||
|
||||
@@ -583,9 +583,6 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
|
||||
|
||||
ret.goal = goals[goal]
|
||||
|
||||
# TODO consider moving open_pyramid to an automatic variable in the core roller, set to True when
|
||||
# fast ganon + ganon at hole
|
||||
ret.open_pyramid = get_choice_legacy('open_pyramid', weights, 'goal')
|
||||
|
||||
extra_pieces = get_choice_legacy('triforce_pieces_mode', weights, 'available')
|
||||
|
||||
|
||||
4
Main.py
4
Main.py
@@ -47,7 +47,6 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
||||
world.item_functionality = args.item_functionality.copy()
|
||||
world.timer = args.timer.copy()
|
||||
world.goal = args.goal.copy()
|
||||
world.open_pyramid = args.open_pyramid.copy()
|
||||
world.boss_shuffle = args.shufflebosses.copy()
|
||||
world.enemy_health = args.enemy_health.copy()
|
||||
world.enemy_damage = args.enemy_damage.copy()
|
||||
@@ -364,7 +363,8 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
||||
for location in world.get_filled_locations():
|
||||
if type(location.address) == int:
|
||||
assert location.item.code is not None, "item code None should be event, " \
|
||||
"location.address should then also be None"
|
||||
"location.address should then also be None. Location: " \
|
||||
f" {location}"
|
||||
locations_data[location.player][location.address] = \
|
||||
location.item.code, location.item.player, location.item.flags
|
||||
if location.name in world.start_location_hints[location.player]:
|
||||
|
||||
@@ -720,16 +720,16 @@ def get_players_string(ctx: Context):
|
||||
return f'{len(auth_clients)} players of {total} connected ' + text[:-1]
|
||||
|
||||
|
||||
def get_status_string(ctx: Context, team: int):
|
||||
text = "Player Status on your team:"
|
||||
def get_status_string(ctx: Context, team: int, tag: str):
|
||||
text = f"Player Status on team {team}:"
|
||||
for slot in ctx.locations:
|
||||
connected = len(ctx.clients[team][slot])
|
||||
death_link = len([client for client in ctx.clients[team][slot] if "DeathLink" in client.tags])
|
||||
tagged = len([client for client in ctx.clients[team][slot] if tag in client.tags])
|
||||
completion_text = f"({len(ctx.location_checks[team, slot])}/{len(ctx.locations[slot])})"
|
||||
death_text = f" {death_link} of which are death link" if connected else ""
|
||||
tag_text = f" {tagged} of which are tagged {tag}" if connected and tag else ""
|
||||
goal_text = " and has finished." if ctx.client_game_state[team, slot] == ClientStatus.CLIENT_GOAL else "."
|
||||
text += f"\n{ctx.get_aliased_name(team, slot)} has {connected} connection{'' if connected == 1 else 's'}" \
|
||||
f"{death_text}{goal_text} {completion_text}"
|
||||
f"{tag_text}{goal_text} {completion_text}"
|
||||
return text
|
||||
|
||||
|
||||
@@ -1113,9 +1113,11 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
||||
self.output(get_players_string(self.ctx))
|
||||
return True
|
||||
|
||||
def _cmd_status(self) -> bool:
|
||||
"""Get status information about your team."""
|
||||
self.output(get_status_string(self.ctx, self.client.team))
|
||||
def _cmd_status(self, tag:str="") -> bool:
|
||||
"""Get status information about your team.
|
||||
Optionally mention a Tag name and get information on who has that Tag.
|
||||
For example: DeathLink or EnergyLink."""
|
||||
self.output(get_status_string(self.ctx, self.client.team, tag))
|
||||
return True
|
||||
|
||||
def _cmd_release(self) -> bool:
|
||||
@@ -1306,6 +1308,8 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
||||
can_pay = 1000
|
||||
|
||||
self.ctx.random.shuffle(not_found_hints)
|
||||
# By popular vote, make hints prefer non-local placements
|
||||
not_found_hints.sort(key=lambda hint: int(hint.receiving_player != hint.finding_player))
|
||||
|
||||
hints = found_hints
|
||||
while can_pay > 0:
|
||||
@@ -1657,6 +1661,14 @@ class ServerCommandProcessor(CommonCommandProcessor):
|
||||
self.output(get_players_string(self.ctx))
|
||||
return True
|
||||
|
||||
def _cmd_status(self, tag: str = "") -> bool:
|
||||
"""Get status information about teams.
|
||||
Optionally mention a Tag name and get information on who has that Tag.
|
||||
For example: DeathLink or EnergyLink."""
|
||||
for team in self.ctx.clients:
|
||||
self.output(get_status_string(self.ctx, team, tag))
|
||||
return True
|
||||
|
||||
def _cmd_exit(self) -> bool:
|
||||
"""Shutdown the server"""
|
||||
asyncio.create_task(self.ctx.server.ws_server._close())
|
||||
|
||||
@@ -62,7 +62,7 @@ class SNIClientCommandProcessor(ClientCommandProcessor):
|
||||
def _cmd_snes(self, snes_options: str = "") -> bool:
|
||||
"""Connect to a snes. Optionally include network address of a snes to connect to,
|
||||
otherwise show available devices; and a SNES device number if more than one SNES is detected.
|
||||
Examples: "/snes", "/snes 1", "/snes localhost:8080 1" """
|
||||
Examples: "/snes", "/snes 1", "/snes localhost:23074 1" """
|
||||
|
||||
snes_address = self.ctx.snes_address
|
||||
snes_device_number = -1
|
||||
@@ -1296,7 +1296,7 @@ async def main():
|
||||
parser = get_base_parser()
|
||||
parser.add_argument('diff_file', default="", type=str, nargs="?",
|
||||
help='Path to a Archipelago Binary Patch file')
|
||||
parser.add_argument('--snes', default='localhost:8080', help='Address of the SNI server.')
|
||||
parser.add_argument('--snes', default='localhost:23074', help='Address of the SNI server.')
|
||||
parser.add_argument('--loglevel', default='info', choices=['debug', 'info', 'warning', 'error', 'critical'])
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
@@ -19,7 +19,13 @@ from worlds.sc2wol.Items import lookup_id_to_name, item_table
|
||||
from worlds.sc2wol.Locations import SC2WOL_LOC_ID_OFFSET
|
||||
from worlds.sc2wol import SC2WoLWorld
|
||||
|
||||
from Utils import init_logging
|
||||
from pathlib import Path
|
||||
import re
|
||||
from MultiServer import mark_raw
|
||||
import ctypes
|
||||
import sys
|
||||
|
||||
from Utils import init_logging, is_windows
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_logging("SC2Client", exception_logger="Client")
|
||||
@@ -73,6 +79,17 @@ class StarcraftClientProcessor(ClientCommandProcessor):
|
||||
request_unfinished_missions(self.ctx.checked_locations, self.ctx.mission_req_table, self.ctx.ui, self.ctx)
|
||||
return True
|
||||
|
||||
@mark_raw
|
||||
def _cmd_set_path(self, path: str = '') -> bool:
|
||||
"""Manually set the SC2 install directory (if the automatic detection fails)."""
|
||||
if path:
|
||||
os.environ["SC2PATH"] = path
|
||||
check_mod_install()
|
||||
return True
|
||||
else:
|
||||
sc2_logger.warning("When using set_path, you must type the path to your SC2 install directory.")
|
||||
return False
|
||||
|
||||
|
||||
class SC2Context(CommonContext):
|
||||
command_processor = StarcraftClientProcessor
|
||||
@@ -111,6 +128,11 @@ class SC2Context(CommonContext):
|
||||
for mission in slot_req_table:
|
||||
self.mission_req_table[mission] = MissionInfo(**slot_req_table[mission])
|
||||
|
||||
# Look for and set SC2PATH.
|
||||
# check_game_install_path() returns True if and only if it finds + sets SC2PATH.
|
||||
if "SC2PATH" not in os.environ and check_game_install_path():
|
||||
check_mod_install()
|
||||
|
||||
if cmd in {"PrintJSON"}:
|
||||
if "receiving" in args:
|
||||
if self.slot_concerns_self(args["receiving"]):
|
||||
@@ -415,8 +437,9 @@ async def starcraft_launch(ctx: SC2Context, mission_id):
|
||||
|
||||
sc2_logger.info(f"Launching {lookup_id_to_mission[mission_id]}. If game does not launch check log file for errors.")
|
||||
|
||||
run_game(sc2.maps.get(maps_table[mission_id - 1]), [Bot(Race.Terran, ArchipelagoBot(ctx, mission_id),
|
||||
name="Archipelago", fullscreen=True)], realtime=True)
|
||||
with DllDirectory(None):
|
||||
run_game(sc2.maps.get(maps_table[mission_id - 1]), [Bot(Race.Terran, ArchipelagoBot(ctx, mission_id),
|
||||
name="Archipelago", fullscreen=True)], realtime=True)
|
||||
|
||||
|
||||
class ArchipelagoBot(sc2.bot_ai.BotAI):
|
||||
@@ -796,6 +819,101 @@ def initialize_blank_mission_dict(location_table):
|
||||
return unlocks
|
||||
|
||||
|
||||
def check_game_install_path() -> bool:
|
||||
# First thing: go to the default location for ExecuteInfo.
|
||||
# An exception for Windows is included because it's very difficult to find ~\Documents if the user moved it.
|
||||
if is_windows:
|
||||
# The next five lines of utterly inscrutable code are brought to you by copy-paste from Stack Overflow.
|
||||
# https://stackoverflow.com/questions/6227590/finding-the-users-my-documents-path/30924555#
|
||||
import ctypes.wintypes
|
||||
CSIDL_PERSONAL = 5 # My Documents
|
||||
SHGFP_TYPE_CURRENT = 0 # Get current, not default value
|
||||
|
||||
buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)
|
||||
ctypes.windll.shell32.SHGetFolderPathW(None, CSIDL_PERSONAL, None, SHGFP_TYPE_CURRENT, buf)
|
||||
documentspath = buf.value
|
||||
einfo = str(documentspath / Path("StarCraft II\\ExecuteInfo.txt"))
|
||||
else:
|
||||
einfo = str(sc2.paths.get_home() / Path(sc2.paths.USERPATH[sc2.paths.PF]))
|
||||
|
||||
# Check if the file exists.
|
||||
if os.path.isfile(einfo):
|
||||
|
||||
# Open the file and read it, picking out the latest executable's path.
|
||||
with open(einfo) as f:
|
||||
content = f.read()
|
||||
if content:
|
||||
base = re.search(r" = (.*)Versions", content).group(1)
|
||||
if os.path.exists(base):
|
||||
executable = sc2.paths.latest_executeble(Path(base).expanduser() / "Versions")
|
||||
|
||||
# Finally, check the path for an actual executable.
|
||||
# If we find one, great. Set up the SC2PATH.
|
||||
if os.path.isfile(executable):
|
||||
sc2_logger.info(f"Found an SC2 install at {base}!")
|
||||
sc2_logger.debug(f"Latest executable at {executable}.")
|
||||
os.environ["SC2PATH"] = base
|
||||
sc2_logger.debug(f"SC2PATH set to {base}.")
|
||||
return True
|
||||
else:
|
||||
sc2_logger.warning(f"We may have found an SC2 install at {base}, but couldn't find {executable}.")
|
||||
else:
|
||||
sc2_logger.warning(f"{einfo} pointed to {base}, but we could not find an SC2 install there.")
|
||||
else:
|
||||
sc2_logger.warning(f"Couldn't find {einfo}. Please run /set_path with your SC2 install directory.")
|
||||
return False
|
||||
|
||||
|
||||
def check_mod_install() -> bool:
|
||||
# Pull up the SC2PATH if set. If not, encourage the user to manually run /set_path.
|
||||
try:
|
||||
# Check inside the Mods folder for Archipelago.SC2Mod. If found, tell user. If not, tell user.
|
||||
if os.path.isfile(modfile := (os.environ["SC2PATH"] / Path("Mods") / Path("Archipelago.SC2Mod"))):
|
||||
sc2_logger.info(f"Archipelago mod found at {modfile}.")
|
||||
return True
|
||||
else:
|
||||
sc2_logger.warning(f"Archipelago mod could not be found at {modfile}. Please install the mod file there.")
|
||||
except KeyError:
|
||||
sc2_logger.warning(f"SC2PATH isn't set. Please run /set_path with the path to your SC2 install.")
|
||||
return False
|
||||
|
||||
|
||||
class DllDirectory:
|
||||
# Credit to Black Sliver for this code.
|
||||
# More info: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setdlldirectoryw
|
||||
_old: typing.Optional[str] = None
|
||||
_new: typing.Optional[str] = None
|
||||
|
||||
def __init__(self, new: typing.Optional[str]):
|
||||
self._new = new
|
||||
|
||||
def __enter__(self):
|
||||
old = self.get()
|
||||
if self.set(self._new):
|
||||
self._old = old
|
||||
|
||||
def __exit__(self, *args):
|
||||
if self._old is not None:
|
||||
self.set(self._old)
|
||||
|
||||
@staticmethod
|
||||
def get() -> str:
|
||||
if sys.platform == "win32":
|
||||
n = ctypes.windll.kernel32.GetDllDirectoryW(0, None)
|
||||
buf = ctypes.create_unicode_buffer(n)
|
||||
ctypes.windll.kernel32.GetDllDirectoryW(n, buf)
|
||||
return buf.value
|
||||
# NOTE: other OS may support os.environ["LD_LIBRARY_PATH"], but this fix is windows-specific
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def set(s: typing.Optional[str]) -> bool:
|
||||
if sys.platform == "win32":
|
||||
return ctypes.windll.kernel32.SetDllDirectoryW(s) != 0
|
||||
# NOTE: other OS may support os.environ["LD_LIBRARY_PATH"], but this fix is windows-specific
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
colorama.init()
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -154,8 +154,10 @@ def autogen(config: dict):
|
||||
while 1:
|
||||
time.sleep(0.1)
|
||||
with db_session:
|
||||
# for update locks the database row(s) during transaction, preventing writes from elsewhere
|
||||
to_start = select(
|
||||
generation for generation in Generation if generation.state == STATE_QUEUED)
|
||||
generation for generation in Generation
|
||||
if generation.state == STATE_QUEUED).for_update()
|
||||
for generation in to_start:
|
||||
launch_generator(generator_pool, generation)
|
||||
except AlreadyRunningException:
|
||||
|
||||
@@ -4,7 +4,7 @@ import random
|
||||
import json
|
||||
import zipfile
|
||||
from collections import Counter
|
||||
from typing import Dict, Optional as TypeOptional
|
||||
from typing import Dict, Optional, Any
|
||||
from Utils import __version__
|
||||
|
||||
from flask import request, flash, redirect, url_for, session, render_template
|
||||
@@ -15,7 +15,7 @@ from BaseClasses import seeddigits, get_seed
|
||||
from Generate import handle_name, PlandoSettings
|
||||
import pickle
|
||||
|
||||
from .models import *
|
||||
from .models import Generation, STATE_ERROR, STATE_QUEUED, commit, db_session, Seed, UUID
|
||||
from WebHostLib import app
|
||||
from .check import get_yaml_data, roll_options
|
||||
from .upload import upload_zip_to_db
|
||||
@@ -30,16 +30,15 @@ def get_meta(options_source: dict) -> dict:
|
||||
}
|
||||
plando_options -= {""}
|
||||
|
||||
meta = {
|
||||
server_options = {
|
||||
"hint_cost": int(options_source.get("hint_cost", 10)),
|
||||
"forfeit_mode": options_source.get("forfeit_mode", "goal"),
|
||||
"remaining_mode": options_source.get("remaining_mode", "disabled"),
|
||||
"collect_mode": options_source.get("collect_mode", "disabled"),
|
||||
"item_cheat": bool(int(options_source.get("item_cheat", 1))),
|
||||
"server_password": options_source.get("server_password", None),
|
||||
"plando_options": list(plando_options)
|
||||
}
|
||||
return meta
|
||||
return {"server_options": server_options, "plando_options": list(plando_options)}
|
||||
|
||||
|
||||
@app.route('/generate', methods=['GET', 'POST'])
|
||||
@@ -60,13 +59,13 @@ def generate(race=False):
|
||||
results, gen_options = roll_options(options, meta["plando_options"])
|
||||
|
||||
if race:
|
||||
meta["item_cheat"] = False
|
||||
meta["remaining_mode"] = "disabled"
|
||||
meta["server_options"]["item_cheat"] = False
|
||||
meta["server_options"]["remaining_mode"] = "disabled"
|
||||
|
||||
if any(type(result) == str for result in results.values()):
|
||||
return render_template("checkResult.html", results=results)
|
||||
elif len(gen_options) > app.config["MAX_ROLL"]:
|
||||
flash(f"Sorry, generating of multiworlds is limited to {app.config['MAX_ROLL']} players for now. "
|
||||
flash(f"Sorry, generating of multiworlds is limited to {app.config['MAX_ROLL']} players. "
|
||||
f"If you have a larger group, please generate it yourself and upload it.")
|
||||
elif len(gen_options) >= app.config["JOB_THRESHOLD"]:
|
||||
gen = Generation(
|
||||
@@ -92,23 +91,22 @@ def generate(race=False):
|
||||
return render_template("generate.html", race=race, version=__version__)
|
||||
|
||||
|
||||
def gen_game(gen_options, meta: TypeOptional[Dict[str, object]] = None, owner=None, sid=None):
|
||||
def gen_game(gen_options, meta: Optional[Dict[str, Any]] = None, owner=None, sid=None):
|
||||
if not meta:
|
||||
meta: Dict[str, object] = {}
|
||||
meta: Dict[str, Any] = {}
|
||||
|
||||
meta.setdefault("server_options", {}).setdefault("hint_cost", 10)
|
||||
race = meta.setdefault("race", False)
|
||||
|
||||
meta.setdefault("hint_cost", 10)
|
||||
race = meta.get("race", False)
|
||||
del (meta["race"])
|
||||
plando_options = meta.get("plando", {"bosses", "items", "connections", "texts"})
|
||||
del (meta["plando_options"])
|
||||
try:
|
||||
target = tempfile.TemporaryDirectory()
|
||||
playercount = len(gen_options)
|
||||
seed = get_seed()
|
||||
random.seed(seed)
|
||||
|
||||
if race:
|
||||
random.seed() # reset to time-based random source
|
||||
random.seed() # use time-based random source
|
||||
else:
|
||||
random.seed(seed)
|
||||
|
||||
seedname = "W" + (f"{random.randint(0, pow(10, seeddigits) - 1)}".zfill(seeddigits))
|
||||
|
||||
@@ -120,7 +118,8 @@ def gen_game(gen_options, meta: TypeOptional[Dict[str, object]] = None, owner=No
|
||||
erargs.outputname = seedname
|
||||
erargs.outputpath = target.name
|
||||
erargs.teams = 1
|
||||
erargs.plando_options = PlandoSettings.from_set(plando_options)
|
||||
erargs.plando_options = PlandoSettings.from_set(meta.setdefault("plando_options",
|
||||
{"bosses", "items", "connections", "texts"}))
|
||||
|
||||
name_counter = Counter()
|
||||
for player, (playerfile, settings) in enumerate(gen_options.items(), 1):
|
||||
@@ -136,7 +135,7 @@ def gen_game(gen_options, meta: TypeOptional[Dict[str, object]] = None, owner=No
|
||||
erargs.name[player] = handle_name(erargs.name[player], player, name_counter)
|
||||
if len(set(erargs.name.values())) != len(erargs.name):
|
||||
raise Exception(f"Names have to be unique. Names: {Counter(erargs.name.values())}")
|
||||
ERmain(erargs, seed, baked_server_options=meta)
|
||||
ERmain(erargs, seed, baked_server_options=meta["server_options"])
|
||||
|
||||
return upload_to_db(target.name, sid, owner, race)
|
||||
except BaseException as e:
|
||||
@@ -148,7 +147,6 @@ def gen_game(gen_options, meta: TypeOptional[Dict[str, object]] = None, owner=No
|
||||
meta = json.loads(gen.meta)
|
||||
meta["error"] = (e.__class__.__name__ + ": " + str(e))
|
||||
gen.meta = json.dumps(meta)
|
||||
|
||||
commit()
|
||||
raise
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class Room(db.Entity):
|
||||
seed = Required('Seed', index=True)
|
||||
multisave = Optional(buffer, lazy=True)
|
||||
show_spoiler = Required(int, default=0) # 0 -> never, 1 -> after completion, -> 2 always
|
||||
timeout = Required(int, default=lambda: 6 * 60 * 60) # seconds since last activity to shutdown
|
||||
timeout = Required(int, default=lambda: 2 * 60 * 60) # seconds since last activity to shutdown
|
||||
tracker = Optional(UUID, index=True)
|
||||
last_port = Optional(int, default=lambda: 0)
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ def create():
|
||||
|
||||
for game_name, world in AutoWorldRegister.world_types.items():
|
||||
|
||||
all_options = {**world.options, **Options.per_game_common_options}
|
||||
all_options = {**Options.per_game_common_options, **world.options}
|
||||
res = Template(open(os.path.join("WebHostLib", "templates", "options.yaml")).read()).render(
|
||||
options=all_options,
|
||||
__version__=__version__, game=game_name, yaml_dump=yaml.dump,
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
This room has a <a href="{{ url_for("getTracker", tracker=room.tracker) }}">Multiworld Tracker</a> enabled.
|
||||
<br />
|
||||
{% endif %}
|
||||
This room will be closed after {{ room.timeout//60//60 }} hours of inactivity. Should you wish to continue
|
||||
later,
|
||||
you can simply refresh this page and the server will be started again.<br>
|
||||
The server for this room will be paused after {{ room.timeout//60//60 }} hours of inactivity.
|
||||
Should you wish to continue later,
|
||||
anyone can simply refresh this page and the server will resume.<br>
|
||||
{% if room.last_port %}
|
||||
You can connect to this room by using <span class="interactive"
|
||||
data-tooltip="This means address/ip is {{ config['PATCH_TARGET'] }} and port is {{ room.last_port }}.">
|
||||
|
||||
49
docs/style.md
Normal file
49
docs/style.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Style Guide
|
||||
|
||||
## Generic
|
||||
|
||||
* This guide can be ignored for data files that are not to be viewed in an editor.
|
||||
* 120 character per line for all source files.
|
||||
* Avoid white space errors like trailing spaces.
|
||||
|
||||
|
||||
## Python Code
|
||||
|
||||
* We mostly follow [PEP8](https://peps.python.org/pep-0008/). Read below to see the differences.
|
||||
* 120 characters per line. PyCharm does this automatically, other editors can be configured for it.
|
||||
* Strings in core code will be `"strings"`. In other words: double quote your strings.
|
||||
* Strings in worlds should use double quotes as well, but imported code may differ.
|
||||
* Prefer [format string literals](https://peps.python.org/pep-0498/) over string concatenation,
|
||||
use single quotes inside them: `f"Like {dct['key']}"`
|
||||
* Use type annotation where possible.
|
||||
|
||||
|
||||
## Markdown
|
||||
|
||||
* We almost follow [Google's styleguide](https://google.github.io/styleguide/docguide/style.html).
|
||||
Read below for differences.
|
||||
* For existing documents, try to follow its style or ask to completely reformat it.
|
||||
* 120 characters per line.
|
||||
* One space between bullet/number and text.
|
||||
* No lazy numbering.
|
||||
|
||||
|
||||
## HTML
|
||||
|
||||
* Indent with 2 spaces for new code.
|
||||
* kebab-case for ids and classes.
|
||||
|
||||
|
||||
## CSS
|
||||
|
||||
* Indent with 2 spaces for new code.
|
||||
* `{` on the same line as the selector.
|
||||
* No space between selector and `{`.
|
||||
|
||||
|
||||
## JS
|
||||
|
||||
* Indent with 2 spaces.
|
||||
* Indent `case` inside `switch ` with 2 spaces.
|
||||
* Use single quotes.
|
||||
* Semicolons are required after every statement.
|
||||
@@ -236,7 +236,7 @@ class MyGameLocation(Location):
|
||||
game: str = "My Game"
|
||||
|
||||
# override constructor to automatically mark event locations as such
|
||||
def __init__(self, player: int, name = '', code = None, parent = None):
|
||||
def __init__(self, player: int, name = "", code = None, parent = None):
|
||||
super(MyGameLocation, self).__init__(player, name, code, parent)
|
||||
self.event = code is None
|
||||
```
|
||||
@@ -487,14 +487,14 @@ def create_items(self) -> None:
|
||||
for item in map(self.create_item, mygame_items):
|
||||
if item in exclude:
|
||||
exclude.remove(item) # this is destructive. create unique list above
|
||||
self.world.itempool.append(self.create_item('nothing'))
|
||||
self.world.itempool.append(self.create_item("nothing"))
|
||||
else:
|
||||
self.world.itempool.append(item)
|
||||
|
||||
# itempool and number of locations should match up.
|
||||
# If this is not the case we want to fill the itempool with junk.
|
||||
junk = 0 # calculate this based on player settings
|
||||
self.world.itempool += [self.create_item('nothing') for _ in range(junk)]
|
||||
self.world.itempool += [self.create_item("nothing") for _ in range(junk)]
|
||||
```
|
||||
|
||||
#### create_regions
|
||||
@@ -628,7 +628,7 @@ class MyGameLogic(LogicMixin):
|
||||
def _mygame_has_key(self, world: MultiWorld, player: int):
|
||||
# Arguments above are free to choose
|
||||
# it may make sense to use World as argument instead of MultiWorld
|
||||
return self.has('key', player) # or whatever
|
||||
return self.has("key", player) # or whatever
|
||||
```
|
||||
```python
|
||||
# __init__.py
|
||||
|
||||
@@ -129,6 +129,7 @@ Type: dirifempty; Name: "{app}"
|
||||
|
||||
[InstallDelete]
|
||||
Type: files; Name: "{app}\ArchipelagoLttPClient.exe"
|
||||
Type: filesandordirs; Name: "{app}\lib\worlds\rogue-legacy*"
|
||||
|
||||
[Registry]
|
||||
|
||||
|
||||
@@ -175,12 +175,15 @@ A Link to the Past:
|
||||
retro_caves:
|
||||
on: 0 # Zelda-1 like mode. There are randomly placed take-any caves that contain one Sword and choices of Heart Container/Blue Potion.
|
||||
off: 50
|
||||
hints: # Vendors: King Zora and Bottle Merchant say what they're selling.
|
||||
# On/Full: Put item and entrance placement hints on telepathic tiles and some NPCs, Full removes joke hints.
|
||||
hints: # On/Full: Put item and entrance placement hints on telepathic tiles and some NPCs, Full removes joke hints.
|
||||
'on': 50
|
||||
vendors: 0
|
||||
'off': 0
|
||||
full: 0
|
||||
scams: # If on, these Merchants will no longer tell you what they're selling.
|
||||
'off': 50
|
||||
'king_zora': 0
|
||||
'bottle_merchant': 0
|
||||
'all': 0
|
||||
swordless:
|
||||
on: 0 # Your swords are replaced by rupees. Gameplay changes have been made to accommodate this change
|
||||
off: 1
|
||||
@@ -273,6 +276,7 @@ A Link to the Past:
|
||||
p: 0 # Randomize the prices of the items in shop inventories
|
||||
u: 0 # Shuffle capacity upgrades into the item pool (and allow them to traverse the multiworld)
|
||||
w: 0 # Consider witch's hut like any other shop and shuffle/randomize it too
|
||||
P: 0 # Prices of the items in shop inventories cost hearts, arrow, or bombs instead of rupees
|
||||
ip: 0 # Shuffle inventories and randomize prices
|
||||
fpu: 0 # Generate new inventories, randomize prices and shuffle capacity upgrades into item pool
|
||||
uip: 0 # Shuffle inventories, randomize prices and shuffle capacity upgrades into the item pool
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import typing
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from Options import Choice, Range, Option, Toggle, DefaultOnToggle, DeathLink
|
||||
|
||||
|
||||
@@ -27,6 +28,35 @@ class Goal(Choice):
|
||||
option_hand_in = 2
|
||||
|
||||
|
||||
class OpenPyramid(Choice):
|
||||
"""Determines whether the hole at the top of pyramid is open.
|
||||
Goal will open the pyramid if the goal requires you to kill Ganon, without needing to kill Agahnim 2.
|
||||
Auto is the same as goal except if Ganon's dropdown is in another location, the hole will be closed."""
|
||||
display_name = "Open Pyramid Hole"
|
||||
option_closed = 0
|
||||
option_open = 1
|
||||
option_goal = 2
|
||||
option_auto = 3
|
||||
default = option_goal
|
||||
|
||||
alias_true = option_open
|
||||
alias_false = option_closed
|
||||
alias_yes = option_open
|
||||
alias_no = option_closed
|
||||
|
||||
def to_bool(self, world: MultiWorld, player: int) -> bool:
|
||||
if self.value == self.option_goal:
|
||||
return world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'}
|
||||
elif self.value == self.option_auto:
|
||||
return world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'} \
|
||||
and (world.shuffle[player] in {'vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed'} or not
|
||||
world.shuffle_ganon)
|
||||
elif self.value == self.option_open:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class DungeonItem(Choice):
|
||||
value: int
|
||||
option_original_dungeon = 0
|
||||
@@ -331,6 +361,7 @@ class AllowCollect(Toggle):
|
||||
alttp_options: typing.Dict[str, type(Option)] = {
|
||||
"crystals_needed_for_gt": CrystalsTower,
|
||||
"crystals_needed_for_ganon": CrystalsGanon,
|
||||
"open_pyramid": OpenPyramid,
|
||||
"bigkey_shuffle": bigkey_shuffle,
|
||||
"smallkey_shuffle": smallkey_shuffle,
|
||||
"compass_shuffle": compass_shuffle,
|
||||
|
||||
@@ -1247,7 +1247,7 @@ def patch_rom(world, rom, player, enemized):
|
||||
rom.write_bytes(0x50563, [0x3F, 0x14]) # disable below ganon chest
|
||||
rom.write_byte(0x50599, 0x00) # disable below ganon chest
|
||||
rom.write_bytes(0xE9A5, [0x7E, 0x00, 0x24]) # disable below ganon chest
|
||||
rom.write_byte(0x18008B, 0x01 if world.open_pyramid[player] else 0x00) # pre-open Pyramid Hole
|
||||
rom.write_byte(0x18008B, 0x01 if world.open_pyramid[player].to_bool(world, player) else 0x00) # pre-open Pyramid Hole
|
||||
rom.write_byte(0x18008C, 0x01 if world.crystals_needed_for_gt[
|
||||
player] == 0 else 0x00) # GT pre-opened if crystal requirement is 0
|
||||
rom.write_byte(0xF5D73, 0xF0) # bees are catchable
|
||||
|
||||
@@ -402,17 +402,6 @@ class ALTTPWorld(World):
|
||||
def create_regions(self):
|
||||
player = self.player
|
||||
world = self.world
|
||||
if world.open_pyramid[player] == 'goal':
|
||||
world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt',
|
||||
'localganontriforcehunt', 'ganonpedestal'}
|
||||
elif world.open_pyramid[player] == 'auto':
|
||||
world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt',
|
||||
'localganontriforcehunt', 'ganonpedestal'} and \
|
||||
(world.shuffle[player] in {'vanilla', 'dungeonssimple', 'dungeonsfull',
|
||||
'dungeonscrossed'} or not world.shuffle_ganon)
|
||||
else:
|
||||
world.open_pyramid[player] = {'on': True, 'off': False, 'yes': True, 'no': False}.get(
|
||||
world.open_pyramid[player], 'auto')
|
||||
|
||||
world.triforce_pieces_available[player] = max(world.triforce_pieces_available[player],
|
||||
world.triforce_pieces_required[player])
|
||||
|
||||
@@ -1 +1 @@
|
||||
factorio-rcon-py>=1.2.1
|
||||
factorio-rcon-py==1.2.1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from typing import Dict
|
||||
from BaseClasses import Item, Location, MultiWorld, Tutorial
|
||||
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
|
||||
@@ -55,7 +55,7 @@ class FF1World(World):
|
||||
rules = get_options(self.world, 'rules', self.player)
|
||||
menu_region = self.ff1_locations.create_menu_region(self.player, locations, rules)
|
||||
terminated_event = Location(self.player, CHAOS_TERMINATED_EVENT, EventId, menu_region)
|
||||
terminated_item = Item(CHAOS_TERMINATED_EVENT, True, EventId, self.player)
|
||||
terminated_item = Item(CHAOS_TERMINATED_EVENT, ItemClassification.progression, EventId, self.player)
|
||||
terminated_event.place_locked_item(terminated_item)
|
||||
|
||||
items = get_options(self.world, 'items', self.player)
|
||||
@@ -114,5 +114,6 @@ class FF1World(World):
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choice(["Heal", "Pure", "Soft", "Tent", "Cabin", "House"])
|
||||
|
||||
|
||||
def get_options(world: MultiWorld, name: str, player: int):
|
||||
return getattr(world, name, None)[player].value
|
||||
|
||||
@@ -1088,10 +1088,10 @@ def get_pool_core(world):
|
||||
placed_items['Hideout Jail Guard (4 Torches)'] = 'Recovery Heart'
|
||||
skip_in_spoiler_locations.extend(['Hideout Jail Guard (2 Torches)', 'Hideout Jail Guard (3 Torches)', 'Hideout Jail Guard (4 Torches)'])
|
||||
else:
|
||||
placed_items['Hideout Jail Guard (1 Torch)'] = 'Small Key (Gerudo Fortress)'
|
||||
placed_items['Hideout Jail Guard (2 Torches)'] = 'Small Key (Gerudo Fortress)'
|
||||
placed_items['Hideout Jail Guard (3 Torches)'] = 'Small Key (Gerudo Fortress)'
|
||||
placed_items['Hideout Jail Guard (4 Torches)'] = 'Small Key (Gerudo Fortress)'
|
||||
placed_items['Hideout Jail Guard (1 Torch)'] = 'Small Key (Thieves Hideout)'
|
||||
placed_items['Hideout Jail Guard (2 Torches)'] = 'Small Key (Thieves Hideout)'
|
||||
placed_items['Hideout Jail Guard (3 Torches)'] = 'Small Key (Thieves Hideout)'
|
||||
placed_items['Hideout Jail Guard (4 Torches)'] = 'Small Key (Thieves Hideout)'
|
||||
|
||||
if world.shuffle_gerudo_card and world.gerudo_fortress != 'open':
|
||||
pool.append('Gerudo Membership Card')
|
||||
|
||||
@@ -6,7 +6,7 @@ class TotalLocations(Range):
|
||||
"""Number of location checks which are added to the Risk of Rain playthrough."""
|
||||
display_name = "Total Locations"
|
||||
range_start = 10
|
||||
range_end = 500
|
||||
range_end = 250
|
||||
default = 20
|
||||
|
||||
|
||||
@@ -36,10 +36,14 @@ class AllowLunarItems(DefaultOnToggle):
|
||||
class StartWithRevive(DefaultOnToggle):
|
||||
"""Start the game with a `Dio's Best Friend` item."""
|
||||
display_name = "Start with a Revive"
|
||||
|
||||
class FinalStageDeath(DefaultOnToggle):
|
||||
"""Death on the final boss stage counts as a win."""
|
||||
display_name = "Final Stage Death is Win"
|
||||
|
||||
|
||||
class GreenScrap(Range):
|
||||
"""Weight of Green Scraps in the item pool."""
|
||||
"""Weight of Green Scraps in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
||||
display_name = "Green Scraps"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
@@ -47,7 +51,7 @@ class GreenScrap(Range):
|
||||
|
||||
|
||||
class RedScrap(Range):
|
||||
"""Weight of Red Scraps in the item pool."""
|
||||
"""Weight of Red Scraps in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
||||
display_name = "Red Scraps"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
@@ -55,7 +59,7 @@ class RedScrap(Range):
|
||||
|
||||
|
||||
class YellowScrap(Range):
|
||||
"""Weight of yellow scraps in the item pool."""
|
||||
"""Weight of yellow scraps in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
||||
display_name = "Yellow Scraps"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
@@ -63,7 +67,7 @@ class YellowScrap(Range):
|
||||
|
||||
|
||||
class WhiteScrap(Range):
|
||||
"""Weight of white scraps in the item pool."""
|
||||
"""Weight of white scraps in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
||||
display_name = "White Scraps"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
@@ -71,7 +75,7 @@ class WhiteScrap(Range):
|
||||
|
||||
|
||||
class CommonItem(Range):
|
||||
"""Weight of common items in the item pool."""
|
||||
"""Weight of common items in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
||||
display_name = "Common Items"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
@@ -79,7 +83,7 @@ class CommonItem(Range):
|
||||
|
||||
|
||||
class UncommonItem(Range):
|
||||
"""Weight of uncommon items in the item pool."""
|
||||
"""Weight of uncommon items in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
||||
display_name = "Uncommon Items"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
@@ -87,7 +91,7 @@ class UncommonItem(Range):
|
||||
|
||||
|
||||
class LegendaryItem(Range):
|
||||
"""Weight of legendary items in the item pool."""
|
||||
"""Weight of legendary items in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
||||
display_name = "Legendary Items"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
@@ -95,7 +99,7 @@ class LegendaryItem(Range):
|
||||
|
||||
|
||||
class BossItem(Range):
|
||||
"""Weight of boss items in the item pool."""
|
||||
"""Weight of boss items in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
||||
display_name = "Boss Items"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
@@ -103,7 +107,7 @@ class BossItem(Range):
|
||||
|
||||
|
||||
class LunarItem(Range):
|
||||
"""Weight of lunar items in the item pool."""
|
||||
"""Weight of lunar items in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
||||
display_name = "Lunar Items"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
@@ -111,7 +115,7 @@ class LunarItem(Range):
|
||||
|
||||
|
||||
class Equipment(Range):
|
||||
"""Weight of equipment items in the item pool."""
|
||||
"""Weight of equipment items in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
||||
display_name = "Equipment"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
@@ -122,15 +126,16 @@ class ItemPoolPresetToggle(DefaultOnToggle):
|
||||
"""Will use the item weight presets when set to true, otherwise will use the custom set item pool weights."""
|
||||
display_name = "Item Weight Presets"
|
||||
|
||||
|
||||
class ItemWeights(Choice):
|
||||
"""Preset choices for determining the weights of the item pool.<br>
|
||||
New is a test for a potential adjustment to the default weights.<br>
|
||||
Uncommon puts a large number of uncommon items in the pool.<br>
|
||||
Legendary puts a large number of legendary items in the pool.<br>
|
||||
Lunartic makes everything a lunar item.<br>
|
||||
Chaos generates the pool completely at random with rarer items having a slight cap to prevent this option being too easy.<br>
|
||||
No Scraps removes all scrap items from the item pool.<br>
|
||||
Even generates the item pool with every item having an even weight.<br>
|
||||
"""Preset choices for determining the weights of the item pool.
|
||||
New is a test for a potential adjustment to the default weights.
|
||||
Uncommon puts a large number of uncommon items in the pool.
|
||||
Legendary puts a large number of legendary items in the pool.
|
||||
Lunartic makes everything a lunar item.
|
||||
Chaos generates the pool completely at random with rarer items having a slight cap to prevent this option being too easy.
|
||||
No Scraps removes all scrap items from the item pool.
|
||||
Even generates the item pool with every item having an even weight.
|
||||
Scraps Only will be only scrap items in the item pool."""
|
||||
display_name = "Item Weights"
|
||||
option_default = 0
|
||||
@@ -143,7 +148,8 @@ class ItemWeights(Choice):
|
||||
option_even = 7
|
||||
option_scraps_only = 8
|
||||
|
||||
#define a dictionary for the weights of the generated item pool.
|
||||
|
||||
# define a dictionary for the weights of the generated item pool.
|
||||
ror2_weights: typing.Dict[str, type(Option)] = {
|
||||
"green_scrap": GreenScrap,
|
||||
"red_scrap": RedScrap,
|
||||
@@ -161,6 +167,7 @@ ror2_options: typing.Dict[str, type(Option)] = {
|
||||
"total_locations": TotalLocations,
|
||||
"total_revivals": TotalRevivals,
|
||||
"start_with_revive": StartWithRevive,
|
||||
"final_stage_death": FinalStageDeath,
|
||||
"item_pickup_step": ItemPickupStep,
|
||||
"enable_lunar": AllowLunarItems,
|
||||
"item_weights": ItemWeights,
|
||||
|
||||
@@ -110,15 +110,16 @@ class RiskOfRainWorld(World):
|
||||
"seed": "".join(self.world.slot_seeds[self.player].choice(string.digits) for i in range(16)),
|
||||
"totalLocations": self.world.total_locations[self.player].value,
|
||||
"totalRevivals": self.world.total_revivals[self.player].value,
|
||||
"startWithDio": self.world.start_with_revive[self.player].value
|
||||
"startWithDio": self.world.start_with_revive[self.player].value,
|
||||
"FinalStageDeath": self.world.final_stage_death[self.player].value
|
||||
}
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
item_id = item_table[name]
|
||||
item = RiskOfRainItem(name, ItemClassification.filler, item_id, self.player)
|
||||
if name == 'Dio\'s Best Friend':
|
||||
if name == "Dio's Best Friend":
|
||||
item.classification = ItemClassification.progression
|
||||
elif name == 'Equipment':
|
||||
elif name in {"Equipment", "Legendary Item"}:
|
||||
item.classification = ItemClassification.useful
|
||||
return item
|
||||
|
||||
|
||||
@@ -12,6 +12,32 @@ functionality in which certain chests (made clear via a location check progress
|
||||
multiworld. The items that _would have been_ in those chests will be returned to the Risk of Rain player via grants by
|
||||
other players in other worlds.
|
||||
|
||||
## What is the goal of Risk of Rain 2 in Archipelago?
|
||||
|
||||
Just like in the original game, any way to "beat the game or obliterate" counts as a win. By default, if you die while
|
||||
on a final boss stage, that also counts as a win. (You can turn this off in your player settings.) **You do not need to
|
||||
complete all the location checks** to win; any item you don't collect is automatically sent out to the multiworld when
|
||||
you meet your goal.
|
||||
|
||||
If you die before you accomplish your goal, you can start a new run. You will start the run with any items that you
|
||||
received from other players. Any items that you picked up the "normal" way will be lost.
|
||||
|
||||
Note, you can play Simulacrum mode as part of an Archipelago, but you can't achieve any of the victory conditions in
|
||||
Simulacrum. So you could, for example, collect most of your items through a Simulacrum run, then finish a normal mode
|
||||
run while keeping the items you received via the multiworld.
|
||||
|
||||
## Can you play multiplayer?
|
||||
|
||||
Yes! You can have a single multiplayer instance as one world in the multiworld. All the players involved need to have
|
||||
the Archipelago mod, but only the host needs to configure the Archipelago settings. When someone finds an item for your
|
||||
world, all the connected players will receive a copy of the item, and the location check bar will increase whenever any
|
||||
player finds an item in Risk of Rain.
|
||||
|
||||
You cannot have players with different player slots in the same co-op game instance. Only the host's Archipelago
|
||||
settings apply, so each Risk of Rain 2 player slot in the multiworld needs to be a separate game instance. You could,
|
||||
for example, have two players trade off hosting and making progress on each other's player slot, but a single co-op
|
||||
instance can't make progress towards multiple player slots in the multiworld.
|
||||
|
||||
## What Risk of Rain items can appear in other players' worlds?
|
||||
|
||||
The Risk of Rain items are:
|
||||
@@ -31,13 +57,34 @@ in-game item of that tier will appear in the Risk of Rain player's inventory. If
|
||||
the player already has an equipment item equipped then the _item that was equipped_ will be dropped on the ground and _
|
||||
the new equipment_ will take it's place. (If you want the old one back, pick it up.)
|
||||
|
||||
### How many items are there?
|
||||
|
||||
Since a Risk of Rain 2 run can go on indefinitely, you have to configure how many collectible items (also known as
|
||||
"checks") the game has for purposes of Archipelago when you set up a multiworld. You can configure anywhere from **10
|
||||
to 250** items. The number of items will be randomized between all players, so you may want to adjust the number and
|
||||
item pickup step based on how many items the other players in the multiworld have. (Around 100 seems to be a good
|
||||
ballpark if you want to have a similar number of items to most other games.)
|
||||
|
||||
After you have completed the specified number of checks, you won't send anything else to the multiworld. You can
|
||||
receive up to the specified number of randomized items from the multiworld as the players find them. In either case,
|
||||
you can continue to collect items as normal in Risk of Rain 2 if you've already found all your location checks.
|
||||
|
||||
## What does another world's item look like in Risk of Rain?
|
||||
|
||||
When the Risk of Rain player fills up their location check bar then the next spawned item will become an item grant for
|
||||
another player's world. The item in Risk of Rain will disappear in a poof of smoke and the grant will automatically go
|
||||
out to the multiworld.
|
||||
another player's world (or possibly get sent back to yourself). The item in Risk of Rain will disappear in a poof of
|
||||
smoke and the grant will automatically go out to the multiworld.
|
||||
|
||||
## What is the item pickup step?
|
||||
|
||||
The item pickup step is a YAML setting which allows you to set how many items you need to spawn before the _next_ item
|
||||
that is spawned disappears (in a poof of smoke) and goes out to the multiworld.
|
||||
|
||||
## Is Archipelago compatible with other Risk of Rain 2 mods?
|
||||
|
||||
Mostly, yes. Not every mod will work; in particular, anything that causes items to go directly into your inventory
|
||||
rather than spawning onto the map will interfere with the way the Archipelago mod works. However, many common mods work
|
||||
just fine with Archipelago.
|
||||
|
||||
For competitive play, of course, you should only use mods that are agreed-upon by the competitors so that you don't
|
||||
have an unfair advantage.
|
||||
|
||||
@@ -7,6 +7,8 @@ import threading
|
||||
import base64
|
||||
from typing import Set, List, TextIO
|
||||
|
||||
from worlds.sm.variaRandomizer.graph.graph_utils import GraphUtils
|
||||
|
||||
logger = logging.getLogger("Super Metroid")
|
||||
|
||||
from .Locations import lookup_name_to_id as locations_lookup_name_to_id
|
||||
@@ -654,11 +656,10 @@ class SMLocation(Location):
|
||||
def can_comeback(self, state: CollectionState, item: Item):
|
||||
randoExec = state.world.worlds[self.player].variaRando.randoExec
|
||||
for key in locationsDict[self.name].AccessFrom.keys():
|
||||
if (randoExec.areaGraph.canAccess( state.smbm[self.player],
|
||||
key,
|
||||
randoExec.graphSettings.startAP,
|
||||
state.smbm[self.player].maxDiff,
|
||||
None)):
|
||||
if (randoExec.areaGraph.canAccessList( state.smbm[self.player],
|
||||
key,
|
||||
[randoExec.graphSettings.startAP, 'Landing Site'] if not GraphUtils.isStandardStart(randoExec.graphSettings.startAP) else ['Landing Site'],
|
||||
state.smbm[self.player].maxDiff)):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@@ -23,7 +23,8 @@ certain items to your own world.
|
||||
|
||||
## What does another world's item look like in Super Metroid?
|
||||
|
||||
A unique item sprite has been added to the game to represent items belonging to another world.
|
||||
Two unique item sprites have been added to the game to represent items belonging to another world. Progression items have
|
||||
a small up arrow on the sprite and non-progression don't.
|
||||
|
||||
## When the player receives an item, what happens?
|
||||
|
||||
|
||||
@@ -367,6 +367,22 @@ class AccessGraph(object):
|
||||
#print("canAccess: {}".format(can))
|
||||
return can
|
||||
|
||||
# test access from an access point to a list of others, given an optional item
|
||||
def canAccessList(self, smbm, srcAccessPointName, destAccessPointNameList, maxDiff, item=None):
|
||||
if item is not None:
|
||||
smbm.addItem(item)
|
||||
#print("canAccess: item: {}, src: {}, dest: {}".format(item, srcAccessPointName, destAccessPointName))
|
||||
destAccessPointList = [self.accessPoints[destAccessPointName] for destAccessPointName in destAccessPointNameList]
|
||||
srcAccessPoint = self.accessPoints[srcAccessPointName]
|
||||
availAccessPoints = self.getAvailableAccessPoints(srcAccessPoint, smbm, maxDiff, item)
|
||||
can = any(ap in availAccessPoints for ap in destAccessPointList)
|
||||
# if not can:
|
||||
# self.log.debug("canAccess KO: avail = {}".format([ap.Name for ap in availAccessPoints.keys()]))
|
||||
if item is not None:
|
||||
smbm.removeItem(item)
|
||||
#print("canAccess: {}".format(can))
|
||||
return can
|
||||
|
||||
# returns a list of AccessPoint instances from srcAccessPointName to destAccessPointName
|
||||
# (not including source ap)
|
||||
# or None if no possible path
|
||||
|
||||
@@ -341,6 +341,8 @@ class VariaRandomizer:
|
||||
if preset == 'custom':
|
||||
PresetLoader.factory(world.custom_preset[player].value).load(self.player)
|
||||
elif preset == 'varia_custom':
|
||||
if len(world.varia_custom_preset[player].value) == 0:
|
||||
raise Exception("varia_custom was chosen but varia_custom_preset is missing.")
|
||||
url = 'https://randommetroidsolver.pythonanywhere.com/presetWebService'
|
||||
preset_name = next(iter(world.varia_custom_preset[player].value))
|
||||
payload = '{{"preset": "{}"}}'.format(preset_name)
|
||||
|
||||
@@ -13,9 +13,28 @@ class StrictCannonRequirements(DefaultOnToggle):
|
||||
"""If disabled, Stars that expect cannons may have to be acquired without them. Only makes a difference if Buddy Checks are enabled"""
|
||||
display_name = "Strict Cannon Requirements"
|
||||
|
||||
class FirstBowserStarDoorCost(Range):
|
||||
"""How many stars are required at the Star Door to Bowser in the Dark World"""
|
||||
range_start = 0
|
||||
range_end = 20
|
||||
default = 8
|
||||
|
||||
class BasementStarDoorCost(Range):
|
||||
"""How many stars are required at the Star Door in the Basement"""
|
||||
range_start = 0
|
||||
range_end = 50
|
||||
default = 30
|
||||
|
||||
class SecondFloorStarDoorCost(Range):
|
||||
"""How many stars are required to access the third floor"""
|
||||
range_start = 0
|
||||
range_end = 50
|
||||
default = 50
|
||||
|
||||
class StarsToFinish(Range):
|
||||
"""How many stars are required at the infinite stairs"""
|
||||
range_start = 50
|
||||
display_name = "Endless Stairs Stars"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
default = 70
|
||||
|
||||
@@ -43,6 +62,9 @@ sm64_options: typing.Dict[str,type(Option)] = {
|
||||
"EnableCoinStars": EnableCoinStars,
|
||||
"StrictCapRequirements": StrictCapRequirements,
|
||||
"StrictCannonRequirements": StrictCannonRequirements,
|
||||
"FirstBowserStarDoorCost": FirstBowserStarDoorCost,
|
||||
"BasementStarDoorCost": BasementStarDoorCost,
|
||||
"SecondFloorStarDoorCost": SecondFloorStarDoorCost,
|
||||
"StarsToFinish": StarsToFinish,
|
||||
"ExtraStars": ExtraStars,
|
||||
"death_link": DeathLink,
|
||||
|
||||
@@ -11,6 +11,8 @@ sm64courses = ["Bob-omb Battlefield", "Whomp's Fortress", "Jolly Roger Bay", "Co
|
||||
"Wet-Dry World",
|
||||
"Tall, Tall Mountain", "Tiny-Huge Island", "Tick Tock Clock", "Rainbow Ride"]
|
||||
|
||||
# sm64paintings is list of strings for quick reference for Painting IDs (NOT warp node IDs!)
|
||||
sm64paintings = ["BOB", "WF", "JRB", "CCM", "BBH", "HMC", "LLL", "SSL", "DDD", "SL", "WDW", "TTM", "THI Tiny", "THI Huge", "TTC", "RR"]
|
||||
|
||||
def create_regions(world: MultiWorld, player: int):
|
||||
regSS = Region("Menu", RegionType.Generic, "Castle Area", player, world)
|
||||
|
||||
@@ -1,40 +1,50 @@
|
||||
from ..generic.Rules import add_rule
|
||||
from .Regions import connect_regions, sm64courses
|
||||
from .Regions import connect_regions, sm64courses, sm64paintings
|
||||
|
||||
|
||||
def set_rules(world, player: int, area_connections):
|
||||
courseshuffle = list(range(len(sm64courses)))
|
||||
entrance_ids = list(range(len(sm64paintings)))
|
||||
destination_courses = list(range(13)) + [12,13,14] # Two instances of Destination Course THI
|
||||
if world.AreaRandomizer[player]:
|
||||
world.random.shuffle(courseshuffle)
|
||||
area_connections.update({index: value for index, value in enumerate(courseshuffle)})
|
||||
world.random.shuffle(entrance_ids)
|
||||
temp_assign = dict(zip(entrance_ids,destination_courses)) # Used for Rules only
|
||||
|
||||
connect_regions(world, player, "Menu", sm64courses[area_connections[0]])
|
||||
connect_regions(world, player, "Menu", sm64courses[area_connections[1]], lambda state: state.has("Power Star", player, 1))
|
||||
connect_regions(world, player, "Menu", sm64courses[area_connections[2]], lambda state: state.has("Power Star", player, 3))
|
||||
connect_regions(world, player, "Menu", sm64courses[area_connections[3]], lambda state: state.has("Power Star", player, 3))
|
||||
connect_regions(world, player, "Menu", "Bowser in the Dark World", lambda state: state.has("Power Star", player, 8))
|
||||
connect_regions(world, player, "Menu", sm64courses[area_connections[4]], lambda state: state.has("Power Star", player, 12))
|
||||
# Destination Format: LVL | AREA with LVL = Course ID, 0-indexed, AREA = Area as used in sm64 code
|
||||
area_connections.update({entrance: (destination_course*10 + 1) for entrance, destination_course in temp_assign.items()})
|
||||
for i in range(len(area_connections)):
|
||||
if (int(area_connections[i]/10) == 12):
|
||||
# Change first occurence of course 12 (THI) to Area 2 (THI Tiny)
|
||||
area_connections[i] = 12*10 + 2
|
||||
break
|
||||
|
||||
connect_regions(world, player, "Menu", sm64courses[temp_assign[0]])
|
||||
connect_regions(world, player, "Menu", sm64courses[temp_assign[1]], lambda state: state.has("Power Star", player, 1))
|
||||
connect_regions(world, player, "Menu", sm64courses[temp_assign[2]], lambda state: state.has("Power Star", player, 3))
|
||||
connect_regions(world, player, "Menu", sm64courses[temp_assign[3]], lambda state: state.has("Power Star", player, 3))
|
||||
connect_regions(world, player, "Menu", "Bowser in the Dark World", lambda state: state.has("Power Star", player, world.FirstBowserStarDoorCost[player].value))
|
||||
connect_regions(world, player, "Menu", sm64courses[temp_assign[4]], lambda state: state.has("Power Star", player, 12))
|
||||
|
||||
connect_regions(world, player, "Menu", "Basement", lambda state: state.has("Basement Key", player) or state.has("Progressive Key", player, 1))
|
||||
|
||||
connect_regions(world, player, "Basement", sm64courses[area_connections[5]])
|
||||
connect_regions(world, player, "Basement", sm64courses[area_connections[6]])
|
||||
connect_regions(world, player, "Basement", sm64courses[area_connections[7]])
|
||||
connect_regions(world, player, "Basement", sm64courses[area_connections[8]], lambda state: state.has("Power Star", player, 30))
|
||||
connect_regions(world, player, "Basement", "Bowser in the Fire Sea", lambda state: state.has("Power Star", player, 30) and
|
||||
connect_regions(world, player, "Basement", sm64courses[temp_assign[5]])
|
||||
connect_regions(world, player, "Basement", sm64courses[temp_assign[6]])
|
||||
connect_regions(world, player, "Basement", sm64courses[temp_assign[7]])
|
||||
connect_regions(world, player, "Basement", sm64courses[temp_assign[8]], lambda state: state.has("Power Star", player, world.BasementStarDoorCost[player].value))
|
||||
connect_regions(world, player, "Basement", "Bowser in the Fire Sea", lambda state: state.has("Power Star", player, world.BasementStarDoorCost[player].value) and
|
||||
state.can_reach("DDD: Board Bowser's Sub", 'Location', player))
|
||||
|
||||
connect_regions(world, player, "Menu", "Second Floor", lambda state: state.has("Second Floor Key", player) or state.has("Progressive Key", player, 2))
|
||||
|
||||
connect_regions(world, player, "Second Floor", sm64courses[area_connections[9]])
|
||||
connect_regions(world, player, "Second Floor", sm64courses[area_connections[10]])
|
||||
connect_regions(world, player, "Second Floor", sm64courses[area_connections[11]])
|
||||
connect_regions(world, player, "Second Floor", sm64courses[area_connections[12]])
|
||||
connect_regions(world, player, "Second Floor", sm64courses[temp_assign[9]])
|
||||
connect_regions(world, player, "Second Floor", sm64courses[temp_assign[10]])
|
||||
connect_regions(world, player, "Second Floor", sm64courses[temp_assign[11]])
|
||||
connect_regions(world, player, "Second Floor", sm64courses[temp_assign[12]]) # THI Tiny
|
||||
connect_regions(world, player, "Second Floor", sm64courses[temp_assign[13]]) # THI Huge
|
||||
|
||||
connect_regions(world, player, "Second Floor", "Third Floor", lambda state: state.has("Power Star", player, 50))
|
||||
connect_regions(world, player, "Second Floor", "Third Floor", lambda state: state.has("Power Star", player, world.SecondFloorStarDoorCost[player].value))
|
||||
|
||||
connect_regions(world, player, "Third Floor", sm64courses[area_connections[13]])
|
||||
connect_regions(world, player, "Third Floor", sm64courses[area_connections[14]])
|
||||
connect_regions(world, player, "Third Floor", sm64courses[temp_assign[14]])
|
||||
connect_regions(world, player, "Third Floor", sm64courses[temp_assign[15]])
|
||||
|
||||
#Special Rules for some Locations
|
||||
add_rule(world.get_location("Tower of the Wing Cap Switch", player), lambda state: state.has("Power Star", player, 10))
|
||||
|
||||
@@ -5,13 +5,10 @@ from .Items import item_table, cannon_item_table, SM64Item
|
||||
from .Locations import location_table, SM64Location
|
||||
from .Options import sm64_options
|
||||
from .Rules import set_rules
|
||||
from .Regions import create_regions, sm64courses
|
||||
from .Regions import create_regions, sm64courses, sm64paintings
|
||||
from BaseClasses import Item, Tutorial, ItemClassification
|
||||
from ..AutoWorld import World, WebWorld
|
||||
|
||||
client_version = 1
|
||||
|
||||
|
||||
class SM64Web(WebWorld):
|
||||
tutorials = [Tutorial(
|
||||
"Multiworld Setup Guide",
|
||||
@@ -38,6 +35,8 @@ class SM64World(World):
|
||||
location_name_to_id = location_table
|
||||
|
||||
data_version = 6
|
||||
required_client_version = (0,3,0)
|
||||
|
||||
forced_auto_forfeit = False
|
||||
|
||||
area_connections: typing.Dict[int, int]
|
||||
@@ -55,10 +54,10 @@ class SM64World(World):
|
||||
set_rules(self.world, self.player, self.area_connections)
|
||||
if self.topology_present:
|
||||
# Write area_connections to spoiler log
|
||||
for painting_id, course_id in self.area_connections.items():
|
||||
for painting_id, destination in self.area_connections.items():
|
||||
self.world.spoiler.set_entrance(
|
||||
sm64courses[painting_id] + " Painting",
|
||||
sm64courses[course_id],
|
||||
sm64paintings[painting_id] + " Painting",
|
||||
sm64courses[destination // 10],
|
||||
'entrance', self.player)
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
@@ -115,6 +114,9 @@ class SM64World(World):
|
||||
def fill_slot_data(self):
|
||||
return {
|
||||
"AreaRando": self.area_connections,
|
||||
"FirstBowserDoorCost": self.world.FirstBowserStarDoorCost[self.player].value,
|
||||
"BasementDoorCost": self.world.BasementStarDoorCost[self.player].value,
|
||||
"SecondFloorCost": self.world.SecondFloorStarDoorCost[self.player].value,
|
||||
"StarsToFinish": self.world.StarsToFinish[self.player].value,
|
||||
"DeathLink": self.world.death_link[self.player].value,
|
||||
}
|
||||
@@ -143,8 +145,8 @@ class SM64World(World):
|
||||
def modify_multidata(self, multidata):
|
||||
if self.topology_present:
|
||||
er_hint_data = {}
|
||||
for painting_id, course_id in self.area_connections.items():
|
||||
region = self.world.get_region(sm64courses[course_id], self.player)
|
||||
for painting_id, destination in self.area_connections.items():
|
||||
region = self.world.get_region(sm64courses[destination // 10], self.player)
|
||||
for location in region.locations:
|
||||
er_hint_data[location.address] = sm64courses[painting_id]
|
||||
er_hint_data[location.address] = sm64paintings[painting_id]
|
||||
multidata['er_hint_data'][self.player] = er_hint_data
|
||||
|
||||
@@ -619,6 +619,7 @@ class Patch:
|
||||
if (self.myWorld.Config.Keysanity):
|
||||
self.patches.append((Snes(0x40003B), [ 1 ])) #// MapMode #$00 = Always On (default) - #$01 = Require Map Item
|
||||
self.patches.append((Snes(0x400045), [ 0x0f ])) #// display ----dcba a: Small Keys, b: Big Key, c: Map, d: Compass
|
||||
self.patches.append((Snes(0x40016A), [ 0x01 ])) #// enable local item dialog boxes for dungeon and keycard items
|
||||
|
||||
def WriteSMKeyCardDoors(self):
|
||||
if (not self.myWorld.Config.Keysanity):
|
||||
|
||||
@@ -380,6 +380,10 @@ Items:
|
||||
Keycard: |-
|
||||
A key from
|
||||
the future?
|
||||
|
||||
Something: |-
|
||||
A small victory!
|
||||
|
||||
default: |-
|
||||
Don't waste
|
||||
your time!
|
||||
|
||||
@@ -27,14 +27,18 @@ class SMZ3CollectionState(metaclass=AutoLogicRegister):
|
||||
# for unit tests where MultiWorld is instantiated before worlds
|
||||
if hasattr(parent, "state"):
|
||||
self.smz3state = {player: TotalSMZ3Item.Progression([]) for player in parent.get_game_players("SMZ3")}
|
||||
for player, group in parent.groups.items():
|
||||
if (group["game"] == "SMZ3"):
|
||||
self.smz3state[player] = TotalSMZ3Item.Progression([])
|
||||
if player not in parent.state.smz3state:
|
||||
parent.state.smz3state[player] = TotalSMZ3Item.Progression([])
|
||||
else:
|
||||
self.smz3state = {}
|
||||
|
||||
def copy_mixin(self, ret) -> CollectionState:
|
||||
ret.smz3state = {player: copy.deepcopy(self.smz3state[player]) for player in self.world.get_game_players("SMZ3")}
|
||||
ret.smz3state = {player: copy.deepcopy(self.smz3state[player]) for player in self.smz3state}
|
||||
return ret
|
||||
|
||||
|
||||
class SMZ3Web(WebWorld):
|
||||
tutorials = [Tutorial(
|
||||
"Multiworld Setup Guide",
|
||||
@@ -106,6 +110,7 @@ class SMZ3World(World):
|
||||
niceItems = TotalSMZ3Item.Item.CreateNicePool(self.smz3World)
|
||||
junkItems = TotalSMZ3Item.Item.CreateJunkPool(self.smz3World)
|
||||
allJunkItems = niceItems + junkItems
|
||||
self.junkItemsNames = [item.Type.name for item in junkItems]
|
||||
|
||||
if (self.smz3World.Config.Keysanity):
|
||||
progressionItems = self.progression + self.dungeon + self.keyCardsItems
|
||||
@@ -256,11 +261,11 @@ class SMZ3World(World):
|
||||
base_combined_rom = basepatch.apply(base_combined_rom)
|
||||
|
||||
patcher = TotalSMZ3Patch(self.smz3World,
|
||||
[world.smz3World for key, world in self.world.worlds.items() if isinstance(world, SMZ3World)],
|
||||
[world.smz3World for key, world in self.world.worlds.items() if isinstance(world, SMZ3World) and hasattr(world, "smz3World")],
|
||||
self.world.seed_name,
|
||||
self.world.seed,
|
||||
self.local_random,
|
||||
self.world.world_name_lookup,
|
||||
{v: k for k, v in self.world.player_name.items()},
|
||||
next(iter(loc.player for loc in self.world.get_locations() if (loc.item.name == "SilverArrows" and loc.item.player == self.player))))
|
||||
patches = patcher.Create(self.smz3World.Config)
|
||||
patches.update(self.apply_sm_custom_sprite())
|
||||
@@ -312,7 +317,7 @@ class SMZ3World(World):
|
||||
return slot_data
|
||||
|
||||
def collect(self, state: CollectionState, item: Item) -> bool:
|
||||
state.smz3state[item.player].Add([TotalSMZ3Item.Item(TotalSMZ3Item.ItemType[item.name], self.smz3World)])
|
||||
state.smz3state[self.player].Add([TotalSMZ3Item.Item(TotalSMZ3Item.ItemType[item.name], self.smz3World if hasattr(self, "smz3World") else None)])
|
||||
if item.advancement:
|
||||
state.prog_items[item.name, item.player] += 1
|
||||
return True # indicate that a logical state change has occured
|
||||
@@ -321,7 +326,7 @@ class SMZ3World(World):
|
||||
def remove(self, state: CollectionState, item: Item) -> bool:
|
||||
name = self.collect_item(state, item, True)
|
||||
if name:
|
||||
state.smz3state[item.player].Remove([TotalSMZ3Item.Item(TotalSMZ3Item.ItemType[item.name], self.smz3World)])
|
||||
state.smz3state[item.player].Remove([TotalSMZ3Item.Item(TotalSMZ3Item.ItemType[item.name], self.smz3World if hasattr(self, "smz3World") else None)])
|
||||
state.prog_items[name, item.player] -= 1
|
||||
if state.prog_items[name, item.player] < 1:
|
||||
del (state.prog_items[name, item.player])
|
||||
@@ -330,7 +335,9 @@ class SMZ3World(World):
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return SMZ3Item(name, ItemClassification.progression,
|
||||
TotalSMZ3Item.ItemType[name], self.item_name_to_id[name], player = self.player)
|
||||
TotalSMZ3Item.ItemType[name], self.item_name_to_id[name],
|
||||
self.player,
|
||||
TotalSMZ3Item.Item(TotalSMZ3Item.ItemType[name], self))
|
||||
|
||||
def pre_fill(self):
|
||||
from Fill import fill_restrictive
|
||||
@@ -364,6 +371,9 @@ class SMZ3World(World):
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choice(self.junkItemsNames)
|
||||
|
||||
def write_spoiler(self, spoiler_handle: TextIO):
|
||||
self.world.spoiler.unreachables.update(self.unreachable)
|
||||
|
||||
|
||||
Binary file not shown.
@@ -23,7 +23,8 @@ certain items to your own world.
|
||||
|
||||
## What does another world's item look like in Super Metroid?
|
||||
|
||||
A unique item sprite has been added to the game to represent items belonging to another world.
|
||||
Two unique item sprites have been added to the game to represent items belonging to another world. Progression items have
|
||||
a small up arrow on the sprite and non-progression don't.
|
||||
|
||||
## What does another world's item look like in LttP?
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from BaseClasses import MultiWorld
|
||||
from ..AutoWorld import LogicMixin
|
||||
from .Options import EnergyCore
|
||||
from typing import Set
|
||||
# TODO: Options may preset certain progress steps (i.e. P_ROCK_SKIP), set in generate_early?
|
||||
|
||||
@@ -8,9 +9,9 @@ from . import pyevermizer
|
||||
# TODO: resolve/flatten/expand rules to get rid of recursion below where possible
|
||||
# Logic.rules are all rules including locations, excluding those with no progress (i.e. locations that only drop items)
|
||||
rules = [rule for rule in pyevermizer.get_logic() if len(rule.provides) > 0]
|
||||
# Logic.items are all items excluding non-progression items and duplicates
|
||||
# Logic.items are all items and extra items excluding non-progression items and duplicates
|
||||
item_names: Set[str] = set()
|
||||
items = [item for item in filter(lambda item: item.progression, pyevermizer.get_items())
|
||||
items = [item for item in filter(lambda item: item.progression, pyevermizer.get_items() + pyevermizer.get_extra_items())
|
||||
if item.name not in item_names and not item_names.add(item.name)]
|
||||
|
||||
|
||||
@@ -47,4 +48,9 @@ class SecretOfEvermoreLogic(LogicMixin):
|
||||
"""
|
||||
Returns True if count of one of evermizer's progress steps is reached based on collected items. i.e. 2 * P_DE
|
||||
"""
|
||||
if progress == pyevermizer.P_ENERGY_CORE: # logic is shared between worlds, so we override in the call
|
||||
w = world.worlds[player]
|
||||
if w.energy_core == EnergyCore.option_fragments:
|
||||
progress = pyevermizer.P_CORE_FRAGMENT
|
||||
count = w.required_fragments
|
||||
return self._soe_count(progress, world, player, count) >= count
|
||||
|
||||
@@ -37,6 +37,32 @@ class Difficulty(EvermizerFlags, Choice):
|
||||
flags = ['e', 'n', 'h', 'x']
|
||||
|
||||
|
||||
class EnergyCore(EvermizerFlags, Choice):
|
||||
"""How to obtain the Energy Core"""
|
||||
display_name = "Energy Core"
|
||||
option_vanilla = 0
|
||||
option_shuffle = 1
|
||||
option_fragments = 2
|
||||
default = 1
|
||||
flags = ['z', '', 'Z']
|
||||
|
||||
|
||||
class RequiredFragments(Range):
|
||||
"""Required fragment count for Energy Core = Fragments"""
|
||||
display_name = "Required Fragments"
|
||||
range_start = 1
|
||||
range_end = 99
|
||||
default = 10
|
||||
|
||||
|
||||
class AvailableFragments(Range):
|
||||
"""Placed fragment count for Energy Core = Fragments"""
|
||||
display_name = "Available Fragments"
|
||||
range_start = 1
|
||||
range_end = 99
|
||||
default = 11
|
||||
|
||||
|
||||
class MoneyModifier(Range):
|
||||
"""Money multiplier in %"""
|
||||
display_name = "Money Modifier"
|
||||
@@ -186,10 +212,15 @@ class TrapChanceOHKO(TrapChance):
|
||||
|
||||
class SoEProgressionBalancing(ProgressionBalancing):
|
||||
default = 30
|
||||
__doc__ = ProgressionBalancing.__doc__.replace(f"default {ProgressionBalancing.default}", f"default {default}")
|
||||
special_range_names = {**ProgressionBalancing.special_range_names, "normal": default}
|
||||
|
||||
|
||||
soe_options: typing.Dict[str, type(Option)] = {
|
||||
"difficulty": Difficulty,
|
||||
"energy_core": EnergyCore,
|
||||
"required_fragments": RequiredFragments,
|
||||
"available_fragments": AvailableFragments,
|
||||
"money_modifier": MoneyModifier,
|
||||
"exp_modifier": ExpModifier,
|
||||
"fix_sequence": FixSequence,
|
||||
|
||||
@@ -16,7 +16,7 @@ except ImportError:
|
||||
from . import pyevermizer # as part of the source tree
|
||||
|
||||
from . import Logic # load logic mixin
|
||||
from .Options import soe_options
|
||||
from .Options import soe_options, EnergyCore, RequiredFragments, AvailableFragments
|
||||
from .Patch import SoEDeltaPatch, get_base_rom_path
|
||||
|
||||
"""
|
||||
@@ -52,7 +52,6 @@ Item grouping currently supports
|
||||
* Ingredients - Matches all ingredient drops
|
||||
* Alchemy - Matches all alchemy formulas
|
||||
* Weapons - Matches all weapons but Bazooka, Bone Crusher, Neutron Blade
|
||||
* Bazooka - Matches all bazookas (currently only one)
|
||||
* Traps - Matches all traps
|
||||
"""
|
||||
|
||||
@@ -63,12 +62,14 @@ _id_offset: typing.Dict[int, int] = {
|
||||
pyevermizer.CHECK_GOURD: _id_base + 100, # gourds 64100..64399
|
||||
pyevermizer.CHECK_NPC: _id_base + 400, # npc 64400..64499
|
||||
# TODO: sniff 64500..64799
|
||||
pyevermizer.CHECK_TRAP: _id_base + 900, # npc 64900..64999
|
||||
pyevermizer.CHECK_EXTRA: _id_base + 800, # extra items 64800..64899
|
||||
pyevermizer.CHECK_TRAP: _id_base + 900, # trap 64900..64999
|
||||
}
|
||||
|
||||
# cache native evermizer items and locations
|
||||
_items = pyevermizer.get_items()
|
||||
_traps = pyevermizer.get_traps()
|
||||
_extras = pyevermizer.get_extra_items() # items that are not placed by default
|
||||
_locations = pyevermizer.get_locations()
|
||||
# fix up texts for AP
|
||||
for _loc in _locations:
|
||||
@@ -104,7 +105,7 @@ def _get_location_mapping() -> typing.Tuple[typing.Dict[str, int], typing.Dict[i
|
||||
def _get_item_mapping() -> typing.Tuple[typing.Dict[str, int], typing.Dict[int, pyevermizer.Item]]:
|
||||
name_to_id = {}
|
||||
id_to_raw = {}
|
||||
for item in itertools.chain(_items, _traps):
|
||||
for item in itertools.chain(_items, _extras, _traps):
|
||||
if item.name in name_to_id:
|
||||
continue
|
||||
ap_id = _id_offset[item.type] + item.index
|
||||
@@ -127,7 +128,6 @@ def _get_item_grouping() -> typing.Dict[str, typing.Set[str]]:
|
||||
groups['Alchemy'] = set(item.name for item in _items if item.type == pyevermizer.CHECK_ALCHEMY)
|
||||
groups['Weapons'] = {'Spider Claw', 'Horn Spear', 'Gladiator Sword', 'Bronze Axe', 'Bronze Spear', 'Crusader Sword',
|
||||
'Lance (Weapon)', 'Knight Basher', 'Atom Smasher', 'Laser Lance'}
|
||||
groups['Bazooka'] = {'Bazooka+Shells / Shining Armor / 5k Gold'}
|
||||
groups['Traps'] = {trap.name for trap in _traps}
|
||||
return groups
|
||||
|
||||
@@ -136,7 +136,8 @@ class SoEWebWorld(WebWorld):
|
||||
theme = 'jungle'
|
||||
tutorials = [Tutorial(
|
||||
"Multiworld Setup Guide",
|
||||
"A guide to playing Secret of Evermore randomizer. This guide covers single-player, multiworld and related software.",
|
||||
"A guide to playing Secret of Evermore randomizer. This guide covers single-player, multiworld and related"
|
||||
" software.",
|
||||
"English",
|
||||
"multiworld_en.md",
|
||||
"multiworld/en",
|
||||
@@ -153,9 +154,9 @@ class SoEWorld(World):
|
||||
options = soe_options
|
||||
topology_present = False
|
||||
remote_items = False
|
||||
data_version = 2
|
||||
data_version = 3
|
||||
web = SoEWebWorld()
|
||||
required_client_version = (0, 2, 6)
|
||||
required_client_version = (0, 3, 3)
|
||||
|
||||
item_name_to_id, item_id_to_raw = _get_item_mapping()
|
||||
location_name_to_id, location_id_to_raw = _get_location_mapping()
|
||||
@@ -165,6 +166,9 @@ class SoEWorld(World):
|
||||
|
||||
evermizer_seed: int
|
||||
connect_name: str
|
||||
energy_core: int
|
||||
available_fragments: int
|
||||
required_fragments: int
|
||||
|
||||
_halls_ne_chest_names: typing.List[str] = [loc.name for loc in _locations if 'Halls NE' in loc.name]
|
||||
|
||||
@@ -172,6 +176,14 @@ class SoEWorld(World):
|
||||
self.connect_name_available_event = threading.Event()
|
||||
super(SoEWorld, self).__init__(*args, **kwargs)
|
||||
|
||||
def generate_early(self) -> None:
|
||||
# store option values that change logic
|
||||
self.energy_core = self.world.energy_core[self.player].value
|
||||
self.required_fragments = self.world.required_fragments[self.player].value
|
||||
if self.required_fragments > self.world.available_fragments[self.player].value:
|
||||
self.world.available_fragments[self.player].value = self.required_fragments
|
||||
self.available_fragments = self.world.available_fragments[self.player].value
|
||||
|
||||
def create_event(self, event: str) -> Item:
|
||||
return SoEItem(event, ItemClassification.progression, None, self.player)
|
||||
|
||||
@@ -182,6 +194,8 @@ class SoEWorld(World):
|
||||
classification = ItemClassification.trap
|
||||
elif item.progression:
|
||||
classification = ItemClassification.progression
|
||||
elif item.useful:
|
||||
classification = ItemClassification.useful
|
||||
else:
|
||||
classification = ItemClassification.filler
|
||||
|
||||
@@ -208,9 +222,33 @@ class SoEWorld(World):
|
||||
self.world.get_entrance('New Game', self.player).connect(self.world.get_region('Ingame', self.player))
|
||||
|
||||
def create_items(self):
|
||||
# add items to the pool
|
||||
items = list(map(lambda item: self.create_item(item), _items))
|
||||
# add regular items to the pool
|
||||
exclusions: typing.List[str] = []
|
||||
if self.energy_core != EnergyCore.option_shuffle:
|
||||
exclusions.append("Energy Core") # will be placed in generate_basic or replaced by a fragment below
|
||||
items = list(map(lambda item: self.create_item(item), (item for item in _items if item.name not in exclusions)))
|
||||
|
||||
# remove one pair of wings that will be placed in generate_basic
|
||||
items.remove(self.create_item("Wings"))
|
||||
|
||||
def is_ingredient(item):
|
||||
for ingredient in _ingredients:
|
||||
if _match_item_name(item, ingredient):
|
||||
return True
|
||||
return False
|
||||
|
||||
# add energy core fragments to the pool
|
||||
ingredients = [n for n, item in enumerate(items) if is_ingredient(item)]
|
||||
if self.energy_core == EnergyCore.option_fragments:
|
||||
items.append(self.create_item("Energy Core Fragment")) # replaces the vanilla energy core
|
||||
for _ in range(self.available_fragments - 1):
|
||||
if len(ingredients) < 1:
|
||||
break # out of ingredients to replace
|
||||
r = self.world.random.choice(ingredients)
|
||||
ingredients.remove(r)
|
||||
items[r] = self.create_item("Energy Core Fragment")
|
||||
|
||||
# add traps to the pool
|
||||
trap_count = self.world.trap_count[self.player].value
|
||||
trap_chances = {}
|
||||
trap_names = {}
|
||||
@@ -232,13 +270,12 @@ class SoEWorld(World):
|
||||
return self.create_item(trap_names[t])
|
||||
v -= c
|
||||
|
||||
while trap_count > 0:
|
||||
r = self.world.random.randrange(len(items))
|
||||
for ingredient in _ingredients:
|
||||
if _match_item_name(items[r], ingredient):
|
||||
items[r] = create_trap()
|
||||
trap_count -= 1
|
||||
break
|
||||
for _ in range(trap_count):
|
||||
if len(ingredients) < 1:
|
||||
break # out of ingredients to replace
|
||||
r = self.world.random.choice(ingredients)
|
||||
ingredients.remove(r)
|
||||
items[r] = create_trap()
|
||||
|
||||
self.world.itempool += items
|
||||
|
||||
@@ -271,7 +308,10 @@ class SoEWorld(World):
|
||||
wings_location = self.world.random.choice(self._halls_ne_chest_names)
|
||||
wings_item = self.create_item('Wings')
|
||||
self.world.get_location(wings_location, self.player).place_locked_item(wings_item)
|
||||
self.world.itempool.remove(wings_item)
|
||||
# place energy core at vanilla location for vanilla mode
|
||||
if self.energy_core == EnergyCore.option_vanilla:
|
||||
energy_core = self.create_item('Energy Core')
|
||||
self.world.get_location('Energy Core #285', self.player).place_locked_item(energy_core)
|
||||
# generate stuff for later
|
||||
self.evermizer_seed = self.world.random.randint(0, 2 ** 16 - 1) # TODO: make this an option for "full" plando?
|
||||
|
||||
@@ -286,9 +326,12 @@ class SoEWorld(World):
|
||||
try:
|
||||
money = self.world.money_modifier[self.player].value
|
||||
exp = self.world.exp_modifier[self.player].value
|
||||
switches = []
|
||||
switches: typing.List[str] = []
|
||||
if self.world.death_link[self.player].value:
|
||||
switches.append("--death-link")
|
||||
if self.energy_core == EnergyCore.option_fragments:
|
||||
switches.extend(('--available-fragments', str(self.available_fragments),
|
||||
'--required-fragments', str(self.required_fragments)))
|
||||
rom_file = get_base_rom_path()
|
||||
out_base = output_path(output_directory, f'AP_{self.world.seed_name}_P{self.player}_'
|
||||
f'{self.world.get_file_safe_player_name(self.player)}')
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp38-cp38-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.8'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp39-cp39-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp310-cp310-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.10'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.8'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.10'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.8'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.10'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp38-cp38-macosx_10_9_x86_64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.8'
|
||||
#https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp39-cp39-macosx_10_9_x86_64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp39-cp39-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2-cp310-cp310-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.10'
|
||||
#https://github.com/black-sliver/pyevermizer/releases/download/v0.41.2/pyevermizer-0.41.2.tar.gz#egg=pyevermizer; python_version == '3.11'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp38-cp38-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.8'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp39-cp39-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp310-cp310-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.10'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.8'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.10'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.8'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.10'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp38-cp38-macosx_10_9_x86_64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.8'
|
||||
#https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp39-cp39-macosx_10_9_x86_64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp39-cp39-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3-cp310-cp310-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.10'
|
||||
#https://github.com/black-sliver/pyevermizer/releases/download/v0.41.3/pyevermizer-0.41.3.tar.gz#egg=pyevermizer; python_version == '3.11'
|
||||
|
||||
82
worlds/subnautica/Creatures.py
Normal file
82
worlds/subnautica/Creatures.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from typing import Dict, Set, List
|
||||
|
||||
# EN Locale Creature Name to rough depth in meters found at
|
||||
all_creatures: Dict[str, int] = {
|
||||
"Gasopod": 0,
|
||||
"Bladderfish": 0,
|
||||
"Ancient Floater": 0,
|
||||
"Skyray": 0,
|
||||
"Garryfish": 0,
|
||||
"Peeper": 0,
|
||||
"Shuttlebug": 0,
|
||||
"Rabbit Ray": 0,
|
||||
"Stalker": 0,
|
||||
"Floater": 0,
|
||||
"Holefish": 0,
|
||||
"Cave Crawler": 0,
|
||||
"Hoopfish": 0,
|
||||
"Crashfish": 0,
|
||||
"Hoverfish": 0,
|
||||
"Spadefish": 0,
|
||||
"Reefback Leviathan": 0,
|
||||
"Reaper Leviathan": 0,
|
||||
"Warper": 0,
|
||||
"Boomerang": 0,
|
||||
"Biter": 200,
|
||||
"Sand Shark": 200,
|
||||
"Bleeder": 200,
|
||||
"Crabsnake": 300,
|
||||
"Jellyray": 300,
|
||||
"Oculus": 300,
|
||||
"Mesmer": 300,
|
||||
"Eyeye": 300,
|
||||
"Reginald": 400,
|
||||
"Sea Treader Leviathan": 400,
|
||||
"Crabsquid": 400,
|
||||
"Ampeel": 400,
|
||||
"Boneshark": 400,
|
||||
"Rockgrub": 400,
|
||||
"Ghost Leviathan": 500,
|
||||
"Ghost Leviathan Juvenile": 500,
|
||||
"Spinefish": 600,
|
||||
"Blighter": 600,
|
||||
"Blood Crawler": 600,
|
||||
"Ghostray": 1000,
|
||||
"Amoeboid": 1000,
|
||||
"River Prowler": 1000,
|
||||
"Red Eyeye": 1300,
|
||||
"Magmarang": 1300,
|
||||
"Crimson Ray": 1300,
|
||||
"Lava Larva": 1300,
|
||||
"Lava Lizard": 1300,
|
||||
"Sea Dragon Leviathan": 1300,
|
||||
"Sea Emperor Leviathan": 1700,
|
||||
"Sea Emperor Juvenile": 1700,
|
||||
|
||||
# "Cuddlefish": 300, # maybe at some point, needs hatching in containment chamber (20 real-life minutes)
|
||||
}
|
||||
|
||||
# be nice and make these require Stasis Rifle
|
||||
aggressive: Set[str] = {
|
||||
"Cave Crawler", # is very easy without Stasis Rifle, but included for consistency
|
||||
"Crashfish",
|
||||
"Bleeder",
|
||||
"Mesmer",
|
||||
"Reaper Leviathan",
|
||||
"Crabsquid",
|
||||
"Warper",
|
||||
"Crabsnake",
|
||||
"Ampeel",
|
||||
"Boneshark",
|
||||
"Lava Lizard",
|
||||
"Sea Dragon Leviathan",
|
||||
"River Prowler",
|
||||
}
|
||||
|
||||
suffix: str = " Scan"
|
||||
|
||||
creature_locations: Dict[str, int] = {
|
||||
creature+suffix: creature_id for creature_id, creature in enumerate(all_creatures, start=34000)
|
||||
}
|
||||
|
||||
all_creatures_presorted: List[str] = sorted(all_creatures)
|
||||
@@ -1,23 +1,353 @@
|
||||
import json
|
||||
import os
|
||||
from BaseClasses import ItemClassification
|
||||
from typing import TypedDict, Dict, Set
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), 'items.json'), 'r') as file:
|
||||
item_table = json.loads(file.read())
|
||||
|
||||
lookup_id_to_name = {}
|
||||
lookup_name_to_item = {}
|
||||
advancement_item_names = set()
|
||||
non_advancement_item_names = set()
|
||||
class ItemDict(TypedDict):
|
||||
classification: ItemClassification
|
||||
count: int
|
||||
name: str
|
||||
tech_type: str
|
||||
|
||||
for item in item_table:
|
||||
item_name = item["name"]
|
||||
lookup_id_to_name[item["id"]] = item_name
|
||||
lookup_name_to_item[item_name] = item
|
||||
if item["progression"]:
|
||||
|
||||
item_table: Dict[int, ItemDict] = {
|
||||
35000: {'classification': ItemClassification.useful,
|
||||
'count': 1,
|
||||
'name': 'Compass',
|
||||
'tech_type': 'Compass'},
|
||||
35001: {'classification': ItemClassification.progression,
|
||||
'count': 1,
|
||||
'name': 'Lightweight High Capacity Tank',
|
||||
'tech_type': 'PlasteelTank'},
|
||||
35002: {'classification': ItemClassification.progression,
|
||||
'count': 1,
|
||||
'name': 'Vehicle Upgrade Console',
|
||||
'tech_type': 'BaseUpgradeConsole'},
|
||||
35003: {'classification': ItemClassification.progression,
|
||||
'count': 1,
|
||||
'name': 'Ultra Glide Fins',
|
||||
'tech_type': 'UltraGlideFins'},
|
||||
35004: {'classification': ItemClassification.useful,
|
||||
'count': 1,
|
||||
'name': 'Cyclops Sonar Upgrade',
|
||||
'tech_type': 'CyclopsSonarModule'},
|
||||
35005: {'classification': ItemClassification.useful,
|
||||
'count': 1,
|
||||
'name': 'Reinforced Dive Suit',
|
||||
'tech_type': 'ReinforcedDiveSuit'},
|
||||
35006: {'classification': ItemClassification.useful,
|
||||
'count': 1,
|
||||
'name': 'Cyclops Thermal Reactor Module',
|
||||
'tech_type': 'CyclopsThermalReactorModule'},
|
||||
35007: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Stillsuit',
|
||||
'tech_type': 'Stillsuit'},
|
||||
35008: {'classification': ItemClassification.filler,
|
||||
'count': 2,
|
||||
'name': 'Alien Containment Fragment',
|
||||
'tech_type': 'BaseWaterParkFragment'},
|
||||
35009: {'classification': ItemClassification.useful,
|
||||
'count': 1,
|
||||
'name': 'Creature Decoy',
|
||||
'tech_type': 'CyclopsDecoy'},
|
||||
35010: {'classification': ItemClassification.useful,
|
||||
'count': 1,
|
||||
'name': 'Cyclops Fire Suppression System',
|
||||
'tech_type': 'CyclopsFireSuppressionModule'},
|
||||
35011: {'classification': ItemClassification.useful,
|
||||
'count': 1,
|
||||
'name': 'Swim Charge Fins',
|
||||
'tech_type': 'SwimChargeFins'},
|
||||
35012: {'classification': ItemClassification.useful,
|
||||
'count': 1,
|
||||
'name': 'Repulsion Cannon',
|
||||
'tech_type': 'RepulsionCannon'},
|
||||
35013: {'classification': ItemClassification.useful,
|
||||
'count': 1,
|
||||
'name': 'Cyclops Decoy Tube Upgrade',
|
||||
'tech_type': 'CyclopsDecoyModule'},
|
||||
35014: {'classification': ItemClassification.progression,
|
||||
'count': 1,
|
||||
'name': 'Cyclops Shield Generator',
|
||||
'tech_type': 'CyclopsShieldModule'},
|
||||
35015: {'classification': ItemClassification.progression,
|
||||
'count': 1,
|
||||
'name': 'Cyclops Depth Module MK1',
|
||||
'tech_type': 'CyclopsHullModule1'},
|
||||
35016: {'classification': ItemClassification.useful,
|
||||
'count': 1,
|
||||
'name': 'Cyclops Docking Bay Repair Module',
|
||||
'tech_type': 'CyclopsSeamothRepairModule'},
|
||||
35017: {'classification': ItemClassification.useful,
|
||||
'count': 2,
|
||||
'name': 'Battery Charger fragment',
|
||||
'tech_type': 'BatteryChargerFragment'},
|
||||
35018: {'classification': ItemClassification.filler,
|
||||
'count': 2,
|
||||
'name': 'Beacon Fragment',
|
||||
'tech_type': 'BeaconFragment'},
|
||||
35019: {'classification': ItemClassification.useful,
|
||||
'count': 2,
|
||||
'name': 'Bioreactor Fragment',
|
||||
'tech_type': 'BaseBioReactorFragment'},
|
||||
35020: {'classification': ItemClassification.progression,
|
||||
'count': 3,
|
||||
'name': 'Cyclops Bridge Fragment',
|
||||
'tech_type': 'CyclopsBridgeFragment'},
|
||||
35021: {'classification': ItemClassification.progression,
|
||||
'count': 3,
|
||||
'name': 'Cyclops Engine Fragment',
|
||||
'tech_type': 'CyclopsEngineFragment'},
|
||||
35022: {'classification': ItemClassification.progression,
|
||||
'count': 3,
|
||||
'name': 'Cyclops Hull Fragment',
|
||||
'tech_type': 'CyclopsHullFragment'},
|
||||
35023: {'classification': ItemClassification.filler,
|
||||
'count': 2,
|
||||
'name': 'Grav Trap Fragment',
|
||||
'tech_type': 'GravSphereFragment'},
|
||||
35024: {'classification': ItemClassification.progression,
|
||||
'count': 3,
|
||||
'name': 'Laser Cutter Fragment',
|
||||
'tech_type': 'LaserCutterFragment'},
|
||||
35025: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Light Stick Fragment',
|
||||
'tech_type': 'TechlightFragment'},
|
||||
35026: {'classification': ItemClassification.progression,
|
||||
'count': 3,
|
||||
'name': 'Mobile Vehicle Bay Fragment',
|
||||
'tech_type': 'ConstructorFragment'},
|
||||
35027: {'classification': ItemClassification.progression,
|
||||
'count': 3,
|
||||
'name': 'Modification Station Fragment',
|
||||
'tech_type': 'WorkbenchFragment'},
|
||||
35028: {'classification': ItemClassification.progression,
|
||||
'count': 2,
|
||||
'name': 'Moonpool Fragment',
|
||||
'tech_type': 'MoonpoolFragment'},
|
||||
35029: {'classification': ItemClassification.useful,
|
||||
'count': 3,
|
||||
'name': 'Nuclear Reactor Fragment',
|
||||
'tech_type': 'BaseNuclearReactorFragment'},
|
||||
35030: {'classification': ItemClassification.useful,
|
||||
'count': 2,
|
||||
'name': 'Power Cell Charger Fragment',
|
||||
'tech_type': 'PowerCellChargerFragment'},
|
||||
35031: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Power Transmitter Fragment',
|
||||
'tech_type': 'PowerTransmitterFragment'},
|
||||
35032: {'classification': ItemClassification.progression,
|
||||
'count': 4,
|
||||
'name': 'Prawn Suit Fragment',
|
||||
'tech_type': 'ExosuitFragment'},
|
||||
35033: {'classification': ItemClassification.useful,
|
||||
'count': 2,
|
||||
'name': 'Prawn Suit Drill Arm Fragment',
|
||||
'tech_type': 'ExosuitDrillArmFragment'},
|
||||
35034: {'classification': ItemClassification.useful,
|
||||
'count': 2,
|
||||
'name': 'Prawn Suit Grappling Arm Fragment',
|
||||
'tech_type': 'ExosuitGrapplingArmFragment'},
|
||||
35035: {'classification': ItemClassification.useful,
|
||||
'count': 2,
|
||||
'name': 'Prawn Suit Propulsion Cannon Fragment',
|
||||
'tech_type': 'ExosuitPropulsionArmFragment'},
|
||||
35036: {'classification': ItemClassification.useful,
|
||||
'count': 2,
|
||||
'name': 'Prawn Suit Torpedo Arm Fragment',
|
||||
'tech_type': 'ExosuitTorpedoArmFragment'},
|
||||
35037: {'classification': ItemClassification.useful,
|
||||
'count': 3,
|
||||
'name': 'Scanner Room Fragment',
|
||||
'tech_type': 'BaseMapRoomFragment'},
|
||||
35038: {'classification': ItemClassification.progression,
|
||||
'count': 5,
|
||||
'name': 'Seamoth Fragment',
|
||||
'tech_type': 'SeamothFragment'},
|
||||
35039: {'classification': ItemClassification.progression,
|
||||
'count': 2,
|
||||
'name': 'Stasis Rifle Fragment',
|
||||
'tech_type': 'StasisRifleFragment'},
|
||||
35040: {'classification': ItemClassification.useful,
|
||||
'count': 2,
|
||||
'name': 'Thermal Plant Fragment',
|
||||
'tech_type': 'ThermalPlantFragment'},
|
||||
35041: {'classification': ItemClassification.progression,
|
||||
'count': 2,
|
||||
'name': 'Seaglide Fragment',
|
||||
'tech_type': 'SeaglideFragment'},
|
||||
35042: {'classification': ItemClassification.progression,
|
||||
'count': 1,
|
||||
'name': 'Radiation Suit',
|
||||
'tech_type': 'RadiationSuit'},
|
||||
35043: {'classification': ItemClassification.progression,
|
||||
'count': 2,
|
||||
'name': 'Propulsion Cannon Fragment',
|
||||
'tech_type': 'PropulsionCannonFragment'},
|
||||
35044: {'classification': ItemClassification.progression,
|
||||
'count': 1,
|
||||
'name': 'Neptune Launch Platform',
|
||||
'tech_type': 'RocketBase'},
|
||||
35045: {'classification': ItemClassification.progression,
|
||||
'count': 1,
|
||||
'name': 'Ion Power Cell',
|
||||
'tech_type': 'PrecursorIonPowerCell'},
|
||||
35046: {'classification': ItemClassification.filler,
|
||||
'count': 2,
|
||||
'name': 'Exterior Growbed Fragment',
|
||||
'tech_type': 'FarmingTrayFragment'},
|
||||
35047: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Picture Frame',
|
||||
'tech_type': 'PictureFrameFragment'},
|
||||
35048: {'classification': ItemClassification.filler,
|
||||
'count': 2,
|
||||
'name': 'Bench Fragment',
|
||||
'tech_type': 'BenchFragment'},
|
||||
35049: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Basic Plant Pot',
|
||||
'tech_type': 'PlanterPotFragment'},
|
||||
35050: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Interior Growbed',
|
||||
'tech_type': 'PlanterBoxFragment'},
|
||||
35051: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Plant Shelf',
|
||||
'tech_type': 'PlanterShelfFragment'},
|
||||
35052: {'classification': ItemClassification.filler,
|
||||
'count': 2,
|
||||
'name': 'Observatory Fragment',
|
||||
'tech_type': 'BaseObservatoryFragment'},
|
||||
35053: {'classification': ItemClassification.filler,
|
||||
'count': 2,
|
||||
'name': 'Multipurpose Room Fragment',
|
||||
'tech_type': 'BaseRoomFragment'},
|
||||
35054: {'classification': ItemClassification.useful,
|
||||
'count': 2,
|
||||
'name': 'Bulkhead Fragment',
|
||||
'tech_type': 'BaseBulkheadFragment'},
|
||||
35055: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Spotlight',
|
||||
'tech_type': 'Spotlight'},
|
||||
35056: {'classification': ItemClassification.filler,
|
||||
'count': 2,
|
||||
'name': 'Desk',
|
||||
'tech_type': 'StarshipDesk'},
|
||||
35057: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Swivel Chair',
|
||||
'tech_type': 'StarshipChair'},
|
||||
35058: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Office Chair',
|
||||
'tech_type': 'StarshipChair2'},
|
||||
35059: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Command Chair',
|
||||
'tech_type': 'StarshipChair3'},
|
||||
35060: {'classification': ItemClassification.filler,
|
||||
'count': 2,
|
||||
'name': 'Counter',
|
||||
'tech_type': 'LabCounter'},
|
||||
35061: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Single Bed',
|
||||
'tech_type': 'NarrowBed'},
|
||||
35062: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Basic Double Bed',
|
||||
'tech_type': 'Bed1'},
|
||||
35063: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Quilted Double Bed',
|
||||
'tech_type': 'Bed2'},
|
||||
35064: {'classification': ItemClassification.filler,
|
||||
'count': 2,
|
||||
'name': 'Coffee Vending Machine',
|
||||
'tech_type': 'CoffeeVendingMachine'},
|
||||
35065: {'classification': ItemClassification.filler,
|
||||
'count': 2,
|
||||
'name': 'Trash Can',
|
||||
'tech_type': 'Trashcans'},
|
||||
35066: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Floodlight',
|
||||
'tech_type': 'Techlight'},
|
||||
35067: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Bar Table',
|
||||
'tech_type': 'BarTable'},
|
||||
35068: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Vending Machine',
|
||||
'tech_type': 'VendingMachine'},
|
||||
35069: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Single Wall Shelf',
|
||||
'tech_type': 'SingleWallShelf'},
|
||||
35070: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Wall Shelves',
|
||||
'tech_type': 'WallShelves'},
|
||||
35071: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Round Plant Pot',
|
||||
'tech_type': 'PlanterPot2'},
|
||||
35072: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Chic Plant Pot',
|
||||
'tech_type': 'PlanterPot3'},
|
||||
35073: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Nuclear Waste Disposal',
|
||||
'tech_type': 'LabTrashcan'},
|
||||
35074: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Wall Planter',
|
||||
'tech_type': 'BasePlanter'},
|
||||
35075: {'classification': ItemClassification.progression,
|
||||
'count': 1,
|
||||
'name': 'Ion Battery',
|
||||
'tech_type': 'PrecursorIonBattery'},
|
||||
35076: {'classification': ItemClassification.progression,
|
||||
'count': 1,
|
||||
'name': 'Neptune Gantry',
|
||||
'tech_type': 'RocketBaseLadder'},
|
||||
35077: {'classification': ItemClassification.progression,
|
||||
'count': 1,
|
||||
'name': 'Neptune Boosters',
|
||||
'tech_type': 'RocketStage1'},
|
||||
35078: {'classification': ItemClassification.progression,
|
||||
'count': 1,
|
||||
'name': 'Neptune Fuel Reserve',
|
||||
'tech_type': 'RocketStage2'},
|
||||
35079: {'classification': ItemClassification.progression,
|
||||
'count': 1,
|
||||
'name': 'Neptune Cockpit',
|
||||
'tech_type': 'RocketStage3'},
|
||||
35080: {'classification': ItemClassification.filler,
|
||||
'count': 1,
|
||||
'name': 'Water Filtration Machine',
|
||||
'tech_type': 'BaseFiltrationMachine'}}
|
||||
|
||||
advancement_item_names: Set[str] = set()
|
||||
non_advancement_item_names: Set[str] = set()
|
||||
|
||||
for item_id, item_data in item_table.items():
|
||||
item_name = item_data["name"]
|
||||
if ItemClassification.progression in item_data["classification"]:
|
||||
advancement_item_names.add(item_name)
|
||||
else:
|
||||
non_advancement_item_names.add(item_name)
|
||||
|
||||
lookup_id_to_name[None] = "Victory"
|
||||
if False: # turn to True to export for Subnautica mod
|
||||
payload = {item_id: item_data["tech_type"] for item_id, item_data in item_table.items()}
|
||||
import json
|
||||
|
||||
lookup_name_to_id = {name: id for id, name in lookup_id_to_name.items()}
|
||||
with open("items.json", "w") as f:
|
||||
json.dump(payload, f)
|
||||
|
||||
@@ -1,12 +1,570 @@
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, TypedDict, List
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), 'locations.json'), 'r') as file:
|
||||
location_table = json.loads(file.read())
|
||||
|
||||
lookup_id_to_name = {}
|
||||
for item in location_table:
|
||||
lookup_id_to_name[item["id"]] = item["name"]
|
||||
class Vector(TypedDict):
|
||||
x: float
|
||||
y: float
|
||||
z: float
|
||||
|
||||
lookup_id_to_name[None] = "Neptune Launch"
|
||||
lookup_name_to_id = {name: id for id, name in lookup_id_to_name.items()}
|
||||
|
||||
class LocationDict(TypedDict, total=False):
|
||||
name: str
|
||||
can_slip_through: bool
|
||||
need_laser_cutter: bool
|
||||
position: Vector
|
||||
need_propulsion_cannon: bool
|
||||
|
||||
|
||||
events: List[str] = ["Neptune Launch", "Disable Quarantine", "Full Infection", "Repair Aurora Drive"]
|
||||
|
||||
location_table: Dict[int, LocationDict] = {
|
||||
33000: {'can_slip_through': False,
|
||||
'name': 'Blood Kelp Trench Wreck - Outside Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -1234.3, 'y': -349.7, 'z': -396.0}},
|
||||
33001: {'can_slip_through': False,
|
||||
'name': 'Blood Kelp Trench Wreck - Inside Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -1208.0, 'y': -349.6, 'z': -383.0}},
|
||||
33002: {'can_slip_through': False,
|
||||
'name': 'Blood Kelp Trench Wreck - PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -1210.6, 'y': -340.7, 'z': -393.4}},
|
||||
33003: {'can_slip_through': False,
|
||||
'name': 'Bulb Zone West Wreck - Outside Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 903.8, 'y': -220.3, 'z': 590.9}},
|
||||
33004: {'can_slip_through': False,
|
||||
'name': 'Bulb Zone West Wreck - Under Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 910.9, 'y': -201.8, 'z': 623.5}},
|
||||
33005: {'can_slip_through': False,
|
||||
'name': 'Bulb Zone West Wreck - Inside Databox',
|
||||
'need_laser_cutter': True,
|
||||
'position': {'x': 914.9, 'y': -202.1, 'z': 611.8}},
|
||||
33006: {'can_slip_through': False,
|
||||
'name': 'Bulb Zone West Wreck - PDA',
|
||||
'need_laser_cutter': True,
|
||||
'position': {'x': 912.6, 'y': -202.0, 'z': 609.5}},
|
||||
33007: {'can_slip_through': False,
|
||||
'name': 'Bulb Zone East Wreck - Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 1327.1, 'y': -234.9, 'z': 575.8}},
|
||||
33008: {'can_slip_through': False,
|
||||
'name': 'Dunes North Wreck - Outside Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -1407.7, 'y': -344.2, 'z': 721.5}},
|
||||
33009: {'can_slip_through': False,
|
||||
'name': 'Dunes North Wreck - Office Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -1393.9, 'y': -329.7, 'z': 733.5}},
|
||||
33010: {'can_slip_through': False,
|
||||
'name': 'Dunes North Wreck - PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -1396.3, 'y': -330.8, 'z': 730.0}},
|
||||
33011: {'can_slip_through': False,
|
||||
'name': 'Dunes North Wreck - Cargo Databox',
|
||||
'need_laser_cutter': True,
|
||||
'position': {'x': -1409.8, 'y': -332.4, 'z': 706.9}},
|
||||
33012: {'can_slip_through': False,
|
||||
'name': 'Dunes West Wreck - Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -1626.2, 'y': -357.5, 'z': 99.5}},
|
||||
33013: {'can_slip_through': False,
|
||||
'name': 'Dunes East Wreck - Outside Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -1196.3, 'y': -223.0, 'z': 12.5}},
|
||||
33014: {'can_slip_through': False,
|
||||
'name': 'Dunes East Wreck - Inside Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -1206.4, 'y': -225.6, 'z': 4.0}},
|
||||
33015: {'can_slip_through': False,
|
||||
'name': 'Grand Reef North Wreck - Outside Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -269.7, 'y': -262.8, 'z': -764.3}},
|
||||
33016: {'can_slip_through': False,
|
||||
'name': 'Grand Reef North Wreck - Elevator Databox',
|
||||
'need_laser_cutter': True,
|
||||
'position': {'x': -285.8, 'y': -240.2, 'z': -786.5}},
|
||||
33017: {'can_slip_through': False,
|
||||
'name': 'Grand Reef North Wreck - Bottom Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -285.2, 'y': -262.4, 'z': -788.4}},
|
||||
33018: {'can_slip_through': False,
|
||||
'name': 'Grand Reef North Wreck - Hangar PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -272.5, 'y': -254.7, 'z': -788.5}},
|
||||
33019: {'can_slip_through': False,
|
||||
'name': 'Grand Reef South Wreck - Trench Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -850.9, 'y': -473.2, 'z': -1414.6}},
|
||||
33020: {'can_slip_through': False,
|
||||
'name': 'Grand Reef South Wreck - Comms Databox',
|
||||
'need_laser_cutter': True,
|
||||
'position': {'x': -889.4, 'y': -433.8, 'z': -1424.8}},
|
||||
33021: {'can_slip_through': False,
|
||||
'name': 'Grand Reef South Wreck - Outside Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -862.4, 'y': -437.5, 'z': -1444.1}},
|
||||
33022: {'can_slip_through': False,
|
||||
'name': 'Grand Reef South Wreck - PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -887.9, 'y': -446.0, 'z': -1422.7}},
|
||||
33023: {'can_slip_through': False,
|
||||
'name': 'Grassy Plateaus South Wreck - Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -23.3, 'y': -105.8, 'z': -604.2}},
|
||||
33024: {'can_slip_through': False,
|
||||
'name': 'Grassy Plateaus South Wreck - PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -27.3, 'y': -106.8, 'z': -607.2}},
|
||||
33025: {'can_slip_through': True,
|
||||
'name': 'Grassy Plateaus East Wreck - Breach Databox',
|
||||
'need_laser_cutter': True,
|
||||
'position': {'x': 313.9, 'y': -91.8, 'z': 432.6}},
|
||||
33026: {'can_slip_through': True,
|
||||
'name': 'Grassy Plateaus East Wreck - Hangar Databox',
|
||||
'need_laser_cutter': True,
|
||||
'position': {'x': 319.4, 'y': -104.3, 'z': 441.5}},
|
||||
33027: {'can_slip_through': False,
|
||||
'name': 'Grassy Plateaus West Wreck - Locker PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -632.3, 'y': -75.0, 'z': -8.9}},
|
||||
33028: {'can_slip_through': False,
|
||||
'name': 'Grassy Plateaus West Wreck - Data Terminal',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -664.4, 'y': -97.8, 'z': -8.0}},
|
||||
33029: {'can_slip_through': False,
|
||||
'name': 'Grassy Plateaus West Wreck - Databox',
|
||||
'need_laser_cutter': True,
|
||||
'position': {'x': -421.4, 'y': -107.8, 'z': -266.5}},
|
||||
33030: {'can_slip_through': False,
|
||||
'name': 'Safe Shallows Wreck - PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -44.0, 'y': -29.1, 'z': -403.6}},
|
||||
33031: {'can_slip_through': False,
|
||||
'name': 'Kelp Forest Wreck - Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -317.6, 'y': -78.8, 'z': 247.4}},
|
||||
33032: {'can_slip_through': False,
|
||||
'name': 'Kelp Forest Wreck - PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 63.2, 'y': -38.5, 'z': 382.9}},
|
||||
33033: {'can_slip_through': False,
|
||||
'name': 'Mountains West Wreck - Outside Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 740.3, 'y': -389.2, 'z': 1179.8}},
|
||||
33034: {'can_slip_through': False,
|
||||
'name': 'Mountains West Wreck - Data Terminal',
|
||||
'need_laser_cutter': True,
|
||||
'position': {'x': 703.7, 'y': -365.9, 'z': 1199.3}},
|
||||
33035: {'can_slip_through': False,
|
||||
'name': 'Mountains West Wreck - Hangar Databox',
|
||||
'need_laser_cutter': True,
|
||||
'position': {'x': 698.2, 'y': -350.8, 'z': 1186.9}},
|
||||
33036: {'can_slip_through': False,
|
||||
'name': 'Mountains West Wreck - Office Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 676.3, 'y': -343.6, 'z': 1204.6}},
|
||||
33037: {'can_slip_through': False,
|
||||
'name': 'Mountains East Wreck - Comms Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 1068.5, 'y': -283.4, 'z': 1345.3}},
|
||||
33038: {'can_slip_through': False,
|
||||
'name': 'Mountains East Wreck - Outside Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 1075.7, 'y': -288.9, 'z': 1321.8}},
|
||||
33039: {'can_slip_through': False,
|
||||
'name': 'Northwestern Mushroom Forest Wreck - Cargo Databox',
|
||||
'need_laser_cutter': True,
|
||||
'position': {'x': -655.1, 'y': -109.6, 'z': 791.0}},
|
||||
33040: {'can_slip_through': False,
|
||||
'name': 'Northwestern Mushroom Forest Wreck - Office Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -663.4, 'y': -111.9, 'z': 777.9}},
|
||||
33041: {'can_slip_through': False,
|
||||
'name': 'Northwestern Mushroom Forest Wreck - PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -662.2, 'y': -113.4, 'z': 777.7}},
|
||||
33042: {'can_slip_through': False,
|
||||
'name': "Sea Treader's Path Wreck - Outside Databox",
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -1161.1, 'y': -191.7, 'z': -758.3}},
|
||||
33043: {'can_slip_through': False,
|
||||
'name': "Sea Treader's Path Wreck - Hangar Databox",
|
||||
'need_laser_cutter': True,
|
||||
'position': {'x': -1129.5, 'y': -155.2, 'z': -729.3}},
|
||||
33044: {'can_slip_through': False,
|
||||
'name': "Sea Treader's Path Wreck - Lobby Databox",
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -1115.9, 'y': -175.3, 'z': -724.5}},
|
||||
33045: {'can_slip_through': False,
|
||||
'name': "Sea Treader's Path Wreck - PDA",
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -1136.8, 'y': -157.0, 'z': -734.6}},
|
||||
33046: {'can_slip_through': False,
|
||||
'name': 'Sparse Reef Wreck - Locker Databox',
|
||||
'need_laser_cutter': True,
|
||||
'position': {'x': -789.8, 'y': -216.1, 'z': -711.0}},
|
||||
33047: {'can_slip_through': False,
|
||||
'name': 'Sparse Reef Wreck - Outside Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -810.7, 'y': -209.3, 'z': -685.5}},
|
||||
33048: {'can_slip_through': False,
|
||||
'name': 'Sparse Reef Wreck - Lab Databox',
|
||||
'need_laser_cutter': True,
|
||||
'position': {'x': -795.5, 'y': -204.1, 'z': -774.7}},
|
||||
33049: {'can_slip_through': False,
|
||||
'name': 'Underwater Islands Wreck - Outside Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -170.8, 'y': -187.6, 'z': 880.7}},
|
||||
33050: {'can_slip_through': False,
|
||||
'name': 'Underwater Islands Wreck - Hangar Databox',
|
||||
'need_laser_cutter': True,
|
||||
'position': {'x': -138.4, 'y': -193.6, 'z': 888.7}},
|
||||
33051: {'can_slip_through': False,
|
||||
'name': 'Underwater Islands Wreck - Data Terminal',
|
||||
'need_laser_cutter': True,
|
||||
'position': {'x': -130.7, 'y': -193.2, 'z': 883.3}},
|
||||
33052: {'can_slip_through': False,
|
||||
'name': 'Underwater Islands Wreck - Cable Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -137.8, 'y': -193.4, 'z': 879.4}},
|
||||
33053: {'can_slip_through': False,
|
||||
'name': 'Underwater Islands Wreck - Pipes Databox 1',
|
||||
'need_laser_cutter': False,
|
||||
'need_propulsion_cannon': True,
|
||||
'position': {'x': -124.4, 'y': -200.7, 'z': 853.0}},
|
||||
33054: {'can_slip_through': False,
|
||||
'name': 'Underwater Islands Wreck - Pipes Databox 2',
|
||||
'need_laser_cutter': False,
|
||||
'need_propulsion_cannon': True,
|
||||
'position': {'x': -126.8, 'y': -201.1, 'z': 852.1}},
|
||||
33055: {'can_slip_through': False,
|
||||
'name': 'Degasi Seabase - Deep Grand Reef - Bedroom Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -643.8, 'y': -509.9, 'z': -941.9}},
|
||||
33056: {'can_slip_through': False,
|
||||
'name': 'Degasi Seabase - Deep Grand Reef - Observatory Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -635.1, 'y': -502.7, 'z': -951.4}},
|
||||
33057: {'can_slip_through': False,
|
||||
'name': 'Degasi Seabase - Deep Grand Reef - Bedroom PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -645.8, 'y': -508.7, 'z': -943.0}},
|
||||
33058: {'can_slip_through': False,
|
||||
'name': 'Degasi Seabase - Deep Grand Reef - Outside PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -630.5, 'y': -511.1, 'z': -936.1}},
|
||||
33059: {'can_slip_through': False,
|
||||
'name': 'Degasi Seabase - Deep Grand Reef - Observatory PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -647.7, 'y': -502.6, 'z': -935.8}},
|
||||
33060: {'can_slip_through': False,
|
||||
'name': 'Degasi Seabase - Deep Grand Reef - Lab PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -639.6, 'y': -505.9, 'z': -946.6}},
|
||||
33061: {'can_slip_through': False,
|
||||
'name': 'Floating Island - Lake PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -707.2, 'y': 0.5, 'z': -1096.7}},
|
||||
33062: {'can_slip_through': False,
|
||||
'name': 'Degasi Seabase - Floating Island - Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -765.7, 'y': 17.6, 'z': -1116.4}},
|
||||
33063: {'can_slip_through': False,
|
||||
'name': 'Degasi Seabase - Floating Island - Room PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -754.9, 'y': 14.6, 'z': -1108.9}},
|
||||
33064: {'can_slip_through': False,
|
||||
'name': 'Degasi Seabase - Floating Island - Green Wall PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -765.3, 'y': 14.1, 'z': -1115.0}},
|
||||
33065: {'can_slip_through': False,
|
||||
'name': 'Degasi Seabase - Floating Island - Corridor PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -758.6, 'y': 14.1, 'z': -1111.3}},
|
||||
33066: {'can_slip_through': False,
|
||||
'name': 'Degasi Seabase - Floating Island - North Observatory PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -805.4, 'y': 76.9, 'z': -1055.7}},
|
||||
33067: {'can_slip_through': False,
|
||||
'name': 'Degasi Seabase - Floating Island - South Observatory PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -715.9, 'y': 75.4, 'z': -1168.8}},
|
||||
33068: {'can_slip_through': False,
|
||||
'name': 'Jellyshroom Cave - PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -540.5, 'y': -250.8, 'z': -83.4}},
|
||||
33069: {'can_slip_through': False,
|
||||
'name': 'Degasi Seabase - Jellyshroom Cave - Bedroom Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 110.6, 'y': -264.9, 'z': -369.0}},
|
||||
33070: {'can_slip_through': False,
|
||||
'name': 'Degasi Seabase - Jellyshroom Cave - Detached PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 80.6, 'y': -268.6, 'z': -358.3}},
|
||||
33071: {'can_slip_through': False,
|
||||
'name': 'Degasi Seabase - Jellyshroom Cave - Office PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 78.2, 'y': -265.0, 'z': -373.4}},
|
||||
33072: {'can_slip_through': False,
|
||||
'name': 'Degasi Seabase - Jellyshroom Cave - Locker PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 85.1, 'y': -264.1, 'z': -372.8}},
|
||||
33073: {'can_slip_through': False,
|
||||
'name': 'Degasi Seabase - Jellyshroom Cave - Bedroom PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 112.3, 'y': -264.9, 'z': -369.3}},
|
||||
33074: {'can_slip_through': False,
|
||||
'name': 'Degasi Seabase - Jellyshroom Cave - Observatory PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 95.5, 'y': -258.9, 'z': -366.5}},
|
||||
33075: {'can_slip_through': False,
|
||||
'name': 'Lifepod 2 - Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -483.6, 'y': -504.7, 'z': 1326.6}},
|
||||
33076: {'can_slip_through': False,
|
||||
'name': 'Lifepod 2 - PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -481.4, 'y': -503.6, 'z': 1324.1}},
|
||||
33077: {'can_slip_through': False,
|
||||
'name': 'Lifepod 3 - Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -34.2, 'y': -22.4, 'z': 410.5}},
|
||||
33078: {'can_slip_through': False,
|
||||
'name': 'Lifepod 3 - PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -33.8, 'y': -22.5, 'z': 408.8}},
|
||||
33079: {'can_slip_through': False,
|
||||
'name': 'Lifepod 4 - Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 712.4, 'y': -3.4, 'z': 160.8}},
|
||||
33080: {'can_slip_through': False,
|
||||
'name': 'Lifepod 4 - PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 712.0, 'y': -3.5, 'z': 161.5}},
|
||||
33081: {'can_slip_through': False,
|
||||
'name': 'Lifepod 6 - Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 358.7, 'y': -117.1, 'z': 306.8}},
|
||||
33082: {'can_slip_through': False,
|
||||
'name': 'Lifepod 6 - Inside PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 361.8, 'y': -116.2, 'z': 309.5}},
|
||||
33083: {'can_slip_through': False,
|
||||
'name': 'Lifepod 6 - Outside PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 359.9, 'y': -117.0, 'z': 312.1}},
|
||||
33084: {'can_slip_through': False,
|
||||
'name': 'Lifepod 7 - PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -56.0, 'y': -182.0, 'z': -1039.0}},
|
||||
33085: {'can_slip_through': False,
|
||||
'name': 'Lifepod 12 - Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 1119.5, 'y': -271.7, 'z': 561.7}},
|
||||
33086: {'can_slip_through': False,
|
||||
'name': 'Lifepod 12 - PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 1116.1, 'y': -271.3, 'z': 566.9}},
|
||||
33087: {'can_slip_through': False,
|
||||
'name': 'Lifepod 13 - Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -926.4, 'y': -185.2, 'z': 501.8}},
|
||||
33088: {'can_slip_through': False,
|
||||
'name': 'Lifepod 13 - PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -926.8, 'y': -184.4, 'z': 506.6}},
|
||||
33089: {'can_slip_through': False,
|
||||
'name': 'Lifepod 17 - PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -514.5, 'y': -98.1, 'z': -56.5}},
|
||||
33090: {'can_slip_through': False,
|
||||
'name': 'Lifepod 19 - Databox',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -809.8, 'y': -302.2, 'z': -876.9}},
|
||||
33091: {'can_slip_through': False,
|
||||
'name': 'Lifepod 19 - Outside PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -806.1, 'y': -294.1, 'z': -866.0}},
|
||||
33092: {'can_slip_through': False,
|
||||
'name': 'Lifepod 19 - Inside PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -810.5, 'y': -299.4, 'z': -873.1}},
|
||||
33093: {'can_slip_through': False,
|
||||
'name': 'Aurora Seamoth Bay - Upgrade Console',
|
||||
'need_laser_cutter': False,
|
||||
'need_propulsion_cannon': True,
|
||||
'position': {'x': 903.5, 'y': -0.2, 'z': 16.1}},
|
||||
33094: {'can_slip_through': False,
|
||||
'name': 'Aurora Drive Room - Upgrade Console',
|
||||
'need_laser_cutter': False,
|
||||
'need_propulsion_cannon': True,
|
||||
'position': {'x': 872.5, 'y': 2.7, 'z': -0.7}},
|
||||
33095: {'can_slip_through': False,
|
||||
'name': 'Aurora Prawn Suit Bay - Upgrade Console',
|
||||
'need_laser_cutter': True,
|
||||
'need_propulsion_cannon': True,
|
||||
'position': {'x': 991.6, 'y': 3.2, 'z': -31.0}},
|
||||
33096: {'can_slip_through': False,
|
||||
'name': 'Aurora - Office PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 952.1, 'y': 41.2, 'z': 113.9}},
|
||||
33097: {'can_slip_through': False,
|
||||
'name': 'Aurora - Corridor PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 977.2, 'y': 39.1, 'z': 83.0}},
|
||||
33098: {'can_slip_through': False,
|
||||
'name': 'Aurora - Cargo Bay PDA',
|
||||
'need_laser_cutter': False,
|
||||
'need_propulsion_cannon': True,
|
||||
'position': {'x': 954.9, 'y': 11.2, 'z': 3.4}},
|
||||
33099: {'can_slip_through': False,
|
||||
'name': 'Aurora - Seamoth Bay PDA',
|
||||
'need_laser_cutter': False,
|
||||
'need_propulsion_cannon': True,
|
||||
'position': {'x': 907.1, 'y': -1.5, 'z': 15.3}},
|
||||
33100: {'can_slip_through': False,
|
||||
'name': 'Aurora - Medkit Locker PDA',
|
||||
'need_laser_cutter': True,
|
||||
'need_propulsion_cannon': True,
|
||||
'position': {'x': 951.8, 'y': -2.3, 'z': -34.7}},
|
||||
33101: {'can_slip_through': False,
|
||||
'name': 'Aurora - Locker PDA',
|
||||
'need_laser_cutter': True,
|
||||
'need_propulsion_cannon': True,
|
||||
'position': {'x': 952.0, 'y': -3.7, 'z': -23.4}},
|
||||
33102: {'can_slip_through': False,
|
||||
'name': 'Aurora - Canteen PDA',
|
||||
'need_laser_cutter': True,
|
||||
'need_propulsion_cannon': True,
|
||||
'position': {'x': 986.5, 'y': 9.6, 'z': -48.6}},
|
||||
33103: {'can_slip_through': False,
|
||||
'name': 'Aurora - Cabin 4 PDA',
|
||||
'need_laser_cutter': True,
|
||||
'need_propulsion_cannon': True,
|
||||
'position': {'x': 951.3, 'y': 11.2, 'z': -51.0}},
|
||||
33104: {'can_slip_through': False,
|
||||
'name': 'Aurora - Cabin 7 PDA',
|
||||
'need_laser_cutter': True,
|
||||
'need_propulsion_cannon': True,
|
||||
'position': {'x': 967.1, 'y': 10.4, 'z': -47.4}},
|
||||
33105: {'can_slip_through': False,
|
||||
'name': 'Aurora - Cabin 1 PDA',
|
||||
'need_laser_cutter': True,
|
||||
'need_propulsion_cannon': True,
|
||||
'position': {'x': 964.1, 'y': 11.1, 'z': -61.9}},
|
||||
33106: {'can_slip_through': False,
|
||||
'name': 'Aurora - Captain PDA',
|
||||
'need_laser_cutter': True,
|
||||
'need_propulsion_cannon': True,
|
||||
'position': {'x': 971.2, 'y': 10.8, 'z': -70.4}},
|
||||
33107: {'can_slip_through': False,
|
||||
'name': 'Aurora - Ring PDA',
|
||||
'need_laser_cutter': False,
|
||||
'need_propulsion_cannon': True,
|
||||
'position': {'x': 1033.6, 'y': -8.5, 'z': 16.2}},
|
||||
33108: {'can_slip_through': False,
|
||||
'name': 'Aurora - Lab PDA',
|
||||
'need_laser_cutter': False,
|
||||
'need_propulsion_cannon': True,
|
||||
'position': {'x': 1032.5, 'y': -7.8, 'z': 32.4}},
|
||||
33109: {'can_slip_through': False,
|
||||
'name': 'Aurora - Office Data Terminal',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 945.8, 'y': 40.8, 'z': 115.1}},
|
||||
33110: {'can_slip_through': False,
|
||||
'name': 'Aurora - Captain Data Terminal',
|
||||
'need_laser_cutter': True,
|
||||
'need_propulsion_cannon': True,
|
||||
'position': {'x': 974.8, 'y': 10.0, 'z': -77.0}},
|
||||
33111: {'can_slip_through': False,
|
||||
'name': 'Aurora - Battery Room Data Terminal',
|
||||
'need_laser_cutter': True,
|
||||
'need_propulsion_cannon': True,
|
||||
'position': {'x': 1040.8, 'y': -11.4, 'z': -3.4}},
|
||||
33112: {'can_slip_through': False,
|
||||
'name': 'Aurora - Lab Data Terminal',
|
||||
'need_laser_cutter': False,
|
||||
'need_propulsion_cannon': True,
|
||||
'position': {'x': 1029.5, 'y': -8.7, 'z': 35.9}},
|
||||
33113: {'can_slip_through': False,
|
||||
'name': "Quarantine Enforcement Platform's - Upper Alien Data "
|
||||
'Terminal',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 432.2, 'y': 3.0, 'z': 1193.2}},
|
||||
33114: {'can_slip_through': False,
|
||||
'name': "Quarantine Enforcement Platform's - Mid Alien Data Terminal",
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 474.4, 'y': -4.5, 'z': 1224.4}},
|
||||
33115: {'can_slip_through': False,
|
||||
'name': 'Dunes Sanctuary - Alien Data Terminal',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -1224.2, 'y': -400.4, 'z': 1057.9}},
|
||||
33116: {'can_slip_through': False,
|
||||
'name': 'Deep Sparse Reef Sanctuary - Alien Data Terminal',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -895.5, 'y': -311.6, 'z': -838.1}},
|
||||
33117: {'can_slip_through': False,
|
||||
'name': 'Northern Blood Kelp Zone Sanctuary - Alien Data Terminal',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -642.9, 'y': -563.5, 'z': 1485.5}},
|
||||
33118: {'can_slip_through': False,
|
||||
'name': 'Lost River Laboratory Cache - Alien Data Terminal',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -1112.3, 'y': -687.3, 'z': -695.5}},
|
||||
33119: {'can_slip_through': False,
|
||||
'name': 'Disease Research Facility - Upper Alien Data Terminal',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -280.2, 'y': -804.3, 'z': 305.1}},
|
||||
33120: {'can_slip_through': False,
|
||||
'name': 'Disease Research Facility - Mid Alien Data Terminal',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -267.9, 'y': -806.6, 'z': 250.0}},
|
||||
33121: {'can_slip_through': False,
|
||||
'name': 'Disease Research Facility - Lower Alien Data Terminal',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -286.2, 'y': -815.6, 'z': 297.8}},
|
||||
33122: {'can_slip_through': False,
|
||||
'name': 'Alien Thermal Plant - Entrance Alien Data Terminal',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -71.3, 'y': -1227.2, 'z': 104.8}},
|
||||
33123: {'can_slip_through': False,
|
||||
'name': 'Alien Thermal Plant - Green Alien Data Terminal',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -38.7, 'y': -1226.6, 'z': 111.8}},
|
||||
33124: {'can_slip_through': False,
|
||||
'name': 'Alien Thermal Plant - Yellow Alien Data Terminal',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -30.4, 'y': -1220.3, 'z': 111.8}},
|
||||
33125: {'can_slip_through': False,
|
||||
'name': "Primary Containment Facility's Antechamber - Alien Data "
|
||||
'Terminal',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 245.8, 'y': -1430.6, 'z': -311.5}},
|
||||
33126: {'can_slip_through': False,
|
||||
'name': "Primary Containment Facility's Egg Laboratory - Alien Data "
|
||||
'Terminal',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 165.5, 'y': -1442.4, 'z': -385.8}},
|
||||
33127: {'can_slip_through': False,
|
||||
'name': "Primary Containment Facility's Pipe Room - Alien Data "
|
||||
'Terminal',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': 348.7, 'y': -1443.5, 'z': -291.9}},
|
||||
33128: {'can_slip_through': False,
|
||||
'name': 'Grassy Plateaus West Wreck - Beam PDA',
|
||||
'need_laser_cutter': True,
|
||||
'position': {'x': -641.8, 'y': -111.3, 'z': -19.7}},
|
||||
33129: {'can_slip_through': False,
|
||||
'name': 'Floating Island - Cave Entrance PDA',
|
||||
'need_laser_cutter': False,
|
||||
'position': {'x': -748.9, 'y': 14.4, 'z': -1179.5}}}
|
||||
|
||||
if False: # turn to True to export for Subnautica mod
|
||||
payload = {location_id: location_data["position"] for location_id, location_data in location_table.items()}
|
||||
import json
|
||||
|
||||
with open("locations.json", "w") as f:
|
||||
json.dump(payload, f)
|
||||
|
||||
@@ -1,14 +1,46 @@
|
||||
from Options import Choice
|
||||
from Options import Choice, Range
|
||||
from .Creatures import all_creatures
|
||||
|
||||
|
||||
class ItemPool(Choice):
|
||||
"""Valuable item pool moves all not progression relevant items to starting inventory and
|
||||
creates random duplicates of important items in their place."""
|
||||
"""Valuable item pool leaves all filler items in their vanilla locations and
|
||||
creates random duplicates of important items into freed spots."""
|
||||
display_name = "Item Pool"
|
||||
option_standard = 0
|
||||
option_valuable = 1
|
||||
|
||||
|
||||
class Goal(Choice):
|
||||
"""Goal to complete.
|
||||
Launch: Leave the planet.
|
||||
Free: Disable quarantine.
|
||||
Infected: Reach maximum infection level.
|
||||
Drive: Repair the Aurora's Drive Core"""
|
||||
auto_display_name = True
|
||||
display_name = "Goal"
|
||||
option_launch = 0
|
||||
option_free = 1
|
||||
option_infected = 2
|
||||
option_drive = 3
|
||||
|
||||
def get_event_name(self) -> str:
|
||||
return {
|
||||
self.option_launch: "Neptune Launch",
|
||||
self.option_infected: "Full Infection",
|
||||
self.option_free: "Disable Quarantine",
|
||||
self.option_drive: "Repair Aurora Drive"
|
||||
}[self.value]
|
||||
|
||||
|
||||
class CreatureScans(Range):
|
||||
"""Place items on specific creature scans.
|
||||
Warning: Includes aggressive Leviathans."""
|
||||
display_name = "Creature Scans"
|
||||
range_end = len(all_creatures)
|
||||
|
||||
|
||||
options = {
|
||||
"item_pool": ItemPool
|
||||
"item_pool": ItemPool,
|
||||
"goal": Goal,
|
||||
"creature_scans": CreatureScans
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
def create_regions(world, player: int):
|
||||
from . import create_region
|
||||
from .Locations import lookup_name_to_id as location_lookup_name_to_id
|
||||
|
||||
world.regions += [
|
||||
create_region(world, player, 'Menu', None, ['Lifepod 5']),
|
||||
create_region(world, player, 'Planet 4546B', [location for location in location_lookup_name_to_id])
|
||||
]
|
||||
@@ -1,113 +1,122 @@
|
||||
from ..generic.Rules import set_rule
|
||||
from .Locations import location_table
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from worlds.generic.Rules import set_rule
|
||||
from .Locations import location_table, LocationDict
|
||||
from .Creatures import all_creatures, aggressive, suffix
|
||||
import math
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import SubnauticaWorld
|
||||
|
||||
def has_seaglide(state, player):
|
||||
|
||||
def has_seaglide(state, player: int):
|
||||
return state.has("Seaglide Fragment", player, 2)
|
||||
|
||||
|
||||
def has_modification_station(state, player):
|
||||
def has_modification_station(state, player: int):
|
||||
return state.has("Modification Station Fragment", player, 3)
|
||||
|
||||
|
||||
def has_mobile_vehicle_bay(state, player):
|
||||
def has_mobile_vehicle_bay(state, player: int):
|
||||
return state.has("Mobile Vehicle Bay Fragment", player, 3)
|
||||
|
||||
|
||||
def has_moonpool(state, player):
|
||||
def has_moonpool(state, player: int):
|
||||
return state.has("Moonpool Fragment", player, 2)
|
||||
|
||||
|
||||
def has_vehicle_upgrade_console(state, player):
|
||||
def has_vehicle_upgrade_console(state, player: int):
|
||||
return state.has("Vehicle Upgrade Console", player) and \
|
||||
has_moonpool(state, player)
|
||||
|
||||
|
||||
def has_seamoth(state, player):
|
||||
def has_seamoth(state, player: int):
|
||||
return state.has("Seamoth Fragment", player, 3) and \
|
||||
has_mobile_vehicle_bay(state, player)
|
||||
|
||||
|
||||
def has_seamoth_depth_module_mk1(state, player):
|
||||
def has_seamoth_depth_module_mk1(state, player: int):
|
||||
return has_vehicle_upgrade_console(state, player)
|
||||
|
||||
|
||||
def has_seamoth_depth_module_mk2(state, player):
|
||||
def has_seamoth_depth_module_mk2(state, player: int):
|
||||
return has_seamoth_depth_module_mk1(state, player) and \
|
||||
has_modification_station(state, player)
|
||||
|
||||
|
||||
def has_seamoth_depth_module_mk3(state, player):
|
||||
def has_seamoth_depth_module_mk3(state, player: int):
|
||||
return has_seamoth_depth_module_mk2(state, player) and \
|
||||
has_modification_station(state, player)
|
||||
|
||||
|
||||
def has_cyclops_bridge(state, player):
|
||||
def has_cyclops_bridge(state, player: int):
|
||||
return state.has("Cyclops Bridge Fragment", player, 3)
|
||||
|
||||
|
||||
def has_cyclops_engine(state, player):
|
||||
def has_cyclops_engine(state, player: int):
|
||||
return state.has("Cyclops Engine Fragment", player, 3)
|
||||
|
||||
|
||||
def has_cyclops_hull(state, player):
|
||||
def has_cyclops_hull(state, player: int):
|
||||
return state.has("Cyclops Hull Fragment", player, 3)
|
||||
|
||||
|
||||
def has_cyclops(state, player):
|
||||
def has_cyclops(state, player: int):
|
||||
return has_cyclops_bridge(state, player) and \
|
||||
has_cyclops_engine(state, player) and \
|
||||
has_cyclops_hull(state, player) and \
|
||||
has_mobile_vehicle_bay(state, player)
|
||||
|
||||
|
||||
def has_cyclops_depth_module_mk1(state, player):
|
||||
def has_cyclops_depth_module_mk1(state, player: int):
|
||||
return state.has("Cyclops Depth Module MK1", player) and \
|
||||
has_modification_station(state, player)
|
||||
|
||||
|
||||
def has_cyclops_depth_module_mk2(state, player):
|
||||
def has_cyclops_depth_module_mk2(state, player: int):
|
||||
return has_cyclops_depth_module_mk1(state, player) and \
|
||||
has_modification_station(state, player)
|
||||
|
||||
|
||||
def has_cyclops_depth_module_mk3(state, player):
|
||||
def has_cyclops_depth_module_mk3(state, player: int):
|
||||
return has_cyclops_depth_module_mk2(state, player) and \
|
||||
has_modification_station(state, player)
|
||||
|
||||
|
||||
def has_prawn(state, player):
|
||||
def has_prawn(state, player: int):
|
||||
return state.has("Prawn Suit Fragment", player, 4) and \
|
||||
has_mobile_vehicle_bay(state, player)
|
||||
|
||||
|
||||
def has_praw_propulsion_arm(state, player):
|
||||
def has_praw_propulsion_arm(state, player: int):
|
||||
return state.has("Prawn Suit Propulsion Cannon Fragment", player, 2) and \
|
||||
has_vehicle_upgrade_console(state, player)
|
||||
|
||||
|
||||
def has_prawn_depth_module_mk1(state, player):
|
||||
def has_prawn_depth_module_mk1(state, player: int):
|
||||
return has_vehicle_upgrade_console(state, player)
|
||||
|
||||
|
||||
def has_prawn_depth_module_mk2(state, player):
|
||||
def has_prawn_depth_module_mk2(state, player: int):
|
||||
return has_prawn_depth_module_mk1(state, player) and \
|
||||
has_modification_station(state, player)
|
||||
|
||||
|
||||
def has_laser_cutter(state, player):
|
||||
def has_laser_cutter(state, player: int):
|
||||
return state.has("Laser Cutter Fragment", player, 3)
|
||||
|
||||
|
||||
def has_stasis_rile(state, player: int):
|
||||
return state.has("Stasis Rifle Fragment", player, 2)
|
||||
|
||||
|
||||
# Either we have propulsion cannon, or prawn + propulsion cannon arm
|
||||
def has_propulsion_cannon(state, player):
|
||||
def has_propulsion_cannon(state, player: int):
|
||||
return state.has("Propulsion Cannon Fragment", player, 2) or \
|
||||
(has_prawn(state, player) and has_praw_propulsion_arm(state, player))
|
||||
|
||||
|
||||
def has_cyclops_shield(state, player):
|
||||
def has_cyclops_shield(state, player: int):
|
||||
return has_cyclops(state, player) and \
|
||||
state.has("Cyclops Shield Generator", player)
|
||||
|
||||
@@ -120,7 +129,7 @@ def has_cyclops_shield(state, player):
|
||||
# negligeable with from high capacity tank. 430m -> 460m
|
||||
# Fins are not used when using seaglide
|
||||
#
|
||||
def get_max_swim_depth(state, player):
|
||||
def get_max_swim_depth(state, player: int):
|
||||
# TODO, Make this a difficulty setting.
|
||||
# Only go up to 200m without any submarines for now.
|
||||
return 200
|
||||
@@ -131,7 +140,7 @@ def get_max_swim_depth(state, player):
|
||||
# has_ultra_glide_fins = state.has("Ultra Glide Fins", player)
|
||||
|
||||
# max_depth = 400 # More like 430m. Give some room
|
||||
# if has_seaglide(state, player):
|
||||
# if has_seaglide(state, player: int):
|
||||
# if has_ultra_high_capacity_tank:
|
||||
# max_depth = 750 # It's about 50m more. Give some room
|
||||
# else:
|
||||
@@ -147,7 +156,7 @@ def get_max_swim_depth(state, player):
|
||||
# return max_depth
|
||||
|
||||
|
||||
def get_seamoth_max_depth(state, player):
|
||||
def get_seamoth_max_depth(state, player: int):
|
||||
if has_seamoth(state, player):
|
||||
if has_seamoth_depth_module_mk3(state, player):
|
||||
return 900
|
||||
@@ -187,7 +196,7 @@ def get_prawn_max_depth(state, player):
|
||||
return 0
|
||||
|
||||
|
||||
def get_max_depth(state, player):
|
||||
def get_max_depth(state, player: int):
|
||||
# TODO, Difficulty option, we can add vehicle depth + swim depth
|
||||
# But at this point, we have to consider traver distance in caves, not
|
||||
# just depth
|
||||
@@ -197,54 +206,82 @@ def get_max_depth(state, player):
|
||||
get_prawn_max_depth(state, player))
|
||||
|
||||
|
||||
def can_access_location(state, player, loc):
|
||||
pos_x = loc.get("position").get("x")
|
||||
pos_y = loc.get("position").get("y")
|
||||
pos_z = loc.get("position").get("z")
|
||||
depth = -pos_y # y-up
|
||||
map_center_dist = math.sqrt(pos_x ** 2 + pos_z ** 2)
|
||||
aurora_dist = math.sqrt((pos_x - 1038.0) ** 2 + (pos_y - -3.4) ** 2 + (pos_z - -163.1) ** 2)
|
||||
|
||||
need_radiation_suit = aurora_dist < 950
|
||||
def can_access_location(state, player: int, loc: LocationDict) -> bool:
|
||||
need_laser_cutter = loc.get("need_laser_cutter", False)
|
||||
need_propulsion_cannon = loc.get("need_propulsion_cannon", False)
|
||||
|
||||
if need_laser_cutter and not has_laser_cutter(state, player):
|
||||
return False
|
||||
|
||||
if need_radiation_suit and not state.has("Radiation Suit", player):
|
||||
need_propulsion_cannon = loc.get("need_propulsion_cannon", False)
|
||||
if need_propulsion_cannon and not has_propulsion_cannon(state, player):
|
||||
return False
|
||||
|
||||
if need_propulsion_cannon and not has_propulsion_cannon(state, player):
|
||||
pos = loc["position"]
|
||||
pos_x = pos["x"]
|
||||
pos_y = pos["y"]
|
||||
pos_z = pos["z"]
|
||||
|
||||
aurora_dist = math.sqrt((pos_x - 1038.0) ** 2 + (pos_y - -3.4) ** 2 + (pos_z - -163.1) ** 2)
|
||||
need_radiation_suit = aurora_dist < 950
|
||||
if need_radiation_suit and not state.has("Radiation Suit", player):
|
||||
return False
|
||||
|
||||
# Seaglide doesn't unlock anything specific, but just allows for faster movement.
|
||||
# Otherwise the game is painfully slow.
|
||||
map_center_dist = math.sqrt(pos_x ** 2 + pos_z ** 2)
|
||||
if (map_center_dist > 800 or pos_y < -200) and not has_seaglide(state, player):
|
||||
return False
|
||||
|
||||
depth = -pos_y # y-up
|
||||
return get_max_depth(state, player) >= depth
|
||||
|
||||
|
||||
def set_location_rule(world, player, loc):
|
||||
def set_location_rule(world, player: int, loc: LocationDict):
|
||||
set_rule(world.get_location(loc["name"], player), lambda state: can_access_location(state, player, loc))
|
||||
|
||||
|
||||
def set_rules(world, player):
|
||||
for loc in location_table:
|
||||
def can_scan_creature(state, player: int, creature: str) -> bool:
|
||||
if not has_seaglide(state, player):
|
||||
return False
|
||||
if creature in aggressive and not has_stasis_rile(state, player):
|
||||
return False
|
||||
return get_max_depth(state, player) >= all_creatures[creature]
|
||||
|
||||
|
||||
def set_creature_rule(world, player, creature_name: str):
|
||||
set_rule(world.get_location(creature_name + suffix, player),
|
||||
lambda state: can_scan_creature(state, player, creature_name))
|
||||
|
||||
|
||||
def set_rules(subnautica_world: "SubnauticaWorld"):
|
||||
player = subnautica_world.player
|
||||
world = subnautica_world.world
|
||||
|
||||
for loc in location_table.values():
|
||||
set_location_rule(world, player, loc)
|
||||
|
||||
# Victory location
|
||||
set_rule(world.get_location("Neptune Launch", player), lambda state: \
|
||||
get_max_depth(state, player) >= 1444 and \
|
||||
has_mobile_vehicle_bay(state, player) and \
|
||||
state.has('Neptune Launch Platform', player) and \
|
||||
state.has('Neptune Gantry', player) and \
|
||||
state.has('Neptune Boosters', player) and \
|
||||
state.has('Neptune Fuel Reserve', player) and \
|
||||
state.has('Neptune Cockpit', player) and \
|
||||
state.has('Ion Power Cell', player) and \
|
||||
state.has('Ion Battery', player) and \
|
||||
for creature_name in subnautica_world.creatures_to_scan:
|
||||
set_creature_rule(world, player, creature_name)
|
||||
|
||||
# Victory locations
|
||||
set_rule(world.get_location("Neptune Launch", player), lambda state:
|
||||
get_max_depth(state, player) >= 1444 and
|
||||
has_mobile_vehicle_bay(state, player) and
|
||||
state.has("Neptune Launch Platform", player) and
|
||||
state.has("Neptune Gantry", player) and
|
||||
state.has("Neptune Boosters", player) and
|
||||
state.has("Neptune Fuel Reserve", player) and
|
||||
state.has("Neptune Cockpit", player) and
|
||||
state.has("Ion Power Cell", player) and
|
||||
state.has("Ion Battery", player) and
|
||||
has_cyclops_shield(state, player))
|
||||
|
||||
world.completion_condition[player] = lambda state: state.has('Victory', player)
|
||||
set_rule(world.get_location("Disable Quarantine", player), lambda state:
|
||||
get_max_depth(state, player) >= 1444)
|
||||
|
||||
set_rule(world.get_location("Full Infection", player), lambda state:
|
||||
get_max_depth(state, player) >= 900)
|
||||
|
||||
room = world.get_location("Aurora Drive Room - Upgrade Console", player)
|
||||
set_rule(world.get_location("Repair Aurora Drive", player), lambda state: room.can_reach(state))
|
||||
|
||||
world.completion_condition[player] = lambda state: state.has("Victory", player)
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import logging
|
||||
from typing import List, Dict, Any
|
||||
|
||||
from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification, RegionType
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from . import Items
|
||||
from . import Locations
|
||||
from . import Creatures
|
||||
from . import Options
|
||||
from .Items import item_table
|
||||
from .Rules import set_rules
|
||||
|
||||
logger = logging.getLogger("Subnautica")
|
||||
|
||||
from .Locations import lookup_name_to_id as locations_lookup_name_to_id
|
||||
from .Items import item_table, lookup_name_to_item, advancement_item_names
|
||||
from .Items import lookup_name_to_id as items_lookup_name_to_id
|
||||
|
||||
from .Regions import create_regions
|
||||
from .Rules import set_rules
|
||||
from .Options import options
|
||||
|
||||
from BaseClasses import Region, Entrance, Location, MultiWorld, Item, Tutorial, ItemClassification, RegionType
|
||||
from ..AutoWorld import World, WebWorld
|
||||
|
||||
|
||||
class SubnaticaWeb(WebWorld):
|
||||
tutorials = [Tutorial(
|
||||
@@ -25,6 +24,10 @@ class SubnaticaWeb(WebWorld):
|
||||
)]
|
||||
|
||||
|
||||
all_locations = {data["name"]: loc_id for loc_id, data in Locations.location_table.items()}
|
||||
all_locations.update(Creatures.creature_locations)
|
||||
|
||||
|
||||
class SubnauticaWorld(World):
|
||||
"""
|
||||
Subnautica is an undersea exploration game. Stranded on an alien world, you become infected by
|
||||
@@ -34,34 +37,56 @@ class SubnauticaWorld(World):
|
||||
game: str = "Subnautica"
|
||||
web = SubnaticaWeb()
|
||||
|
||||
item_name_to_id = items_lookup_name_to_id
|
||||
location_name_to_id = locations_lookup_name_to_id
|
||||
options = options
|
||||
item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()}
|
||||
location_name_to_id = all_locations
|
||||
options = Options.options
|
||||
|
||||
data_version = 2
|
||||
required_client_version = (0, 1, 9)
|
||||
data_version = 3
|
||||
required_client_version = (0, 3, 3)
|
||||
|
||||
prefill_items: List[Item]
|
||||
creatures_to_scan: List[str]
|
||||
|
||||
def generate_early(self) -> None:
|
||||
self.prefill_items = [
|
||||
self.create_item("Seaglide Fragment"),
|
||||
self.create_item("Seaglide Fragment")
|
||||
]
|
||||
self.creatures_to_scan = self.world.random.sample(Creatures.all_creatures_presorted,
|
||||
self.world.creature_scans[self.player].value)
|
||||
|
||||
def create_regions(self):
|
||||
self.world.regions += [
|
||||
self.create_region("Menu", None, ["Lifepod 5"]),
|
||||
self.create_region("Planet 4546B",
|
||||
Locations.events +
|
||||
[location["name"] for location in Locations.location_table.values()] +
|
||||
[creature+Creatures.suffix for creature in self.creatures_to_scan])
|
||||
]
|
||||
|
||||
# refer to Rules.py
|
||||
set_rules = set_rules
|
||||
|
||||
def generate_basic(self):
|
||||
# Link regions
|
||||
self.world.get_entrance('Lifepod 5', self.player).connect(self.world.get_region('Planet 4546B', self.player))
|
||||
self.world.get_entrance("Lifepod 5", self.player).connect(self.world.get_region("Planet 4546B", self.player))
|
||||
|
||||
# Generate item pool
|
||||
pool = []
|
||||
neptune_launch_platform = None
|
||||
extras = 0
|
||||
valuable = self.world.item_pool[self.player] == "valuable"
|
||||
for item in item_table:
|
||||
extras = self.world.creature_scans[self.player].value
|
||||
valuable = self.world.item_pool[self.player] == Options.ItemPool.option_valuable
|
||||
for item in item_table.values():
|
||||
for i in range(item["count"]):
|
||||
subnautica_item = self.create_item(item["name"])
|
||||
if item["name"] == "Neptune Launch Platform":
|
||||
neptune_launch_platform = subnautica_item
|
||||
elif valuable and not item["progression"]:
|
||||
self.world.push_precollected(subnautica_item)
|
||||
elif valuable and ItemClassification.filler == item["classification"]:
|
||||
extras += 1
|
||||
else:
|
||||
pool.append(subnautica_item)
|
||||
|
||||
for item_name in self.world.random.choices(sorted(advancement_item_names - {"Neptune Launch Platform"}),
|
||||
for item_name in self.world.random.choices(sorted(Items.advancement_item_names - {"Neptune Launch Platform"}),
|
||||
k=extras):
|
||||
item = self.create_item(item_name)
|
||||
item.classification = ItemClassification.filler # as it's an extra, just fast-fill it somewhere
|
||||
@@ -72,39 +97,58 @@ class SubnauticaWorld(World):
|
||||
# Victory item
|
||||
self.world.get_location("Aurora - Captain Data Terminal", self.player).place_locked_item(
|
||||
neptune_launch_platform)
|
||||
self.world.get_location("Neptune Launch", self.player).place_locked_item(
|
||||
SubnauticaItem("Victory", ItemClassification.progression, None, player=self.player))
|
||||
for event in Locations.events:
|
||||
self.world.get_location(event, self.player).place_locked_item(
|
||||
SubnauticaItem(event, ItemClassification.progression, None, player=self.player))
|
||||
# make the goal event the victory "item"
|
||||
self.world.get_location(self.world.goal[self.player].get_event_name(), self.player).item.name = "Victory"
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
goal: Options.Goal = self.world.goal[self.player]
|
||||
item_pool: Options.ItemPool = self.world.item_pool[self.player]
|
||||
vanilla_tech: List[str] = []
|
||||
if item_pool == Options.ItemPool.option_valuable:
|
||||
for item in Items.item_table.values():
|
||||
if item["classification"] == ItemClassification.filler:
|
||||
vanilla_tech.append(item["tech_type"])
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self.world, self.player)
|
||||
slot_data: Dict[str, Any] = {
|
||||
"goal": goal.current_key,
|
||||
"vanilla_tech": vanilla_tech,
|
||||
"creatures_to_scan": self.creatures_to_scan
|
||||
}
|
||||
|
||||
def fill_slot_data(self):
|
||||
slot_data = {}
|
||||
return slot_data
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
item = lookup_name_to_item[name]
|
||||
item_id: int = self.item_name_to_id[name]
|
||||
|
||||
return SubnauticaItem(name,
|
||||
ItemClassification.progression if item["progression"] else ItemClassification.filler,
|
||||
item["id"], player=self.player)
|
||||
item_table[item_id]["classification"],
|
||||
item_id, player=self.player)
|
||||
|
||||
def create_region(self, name: str, locations=None, exits=None):
|
||||
ret = Region(name, RegionType.Generic, name, self.player)
|
||||
ret.world = self.world
|
||||
if locations:
|
||||
for location in locations:
|
||||
loc_id = self.location_name_to_id.get(location, None)
|
||||
location = SubnauticaLocation(self.player, location, loc_id, ret)
|
||||
ret.locations.append(location)
|
||||
if exits:
|
||||
for region_exit in exits:
|
||||
ret.exits.append(Entrance(self.player, region_exit, ret))
|
||||
return ret
|
||||
|
||||
def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None):
|
||||
ret = Region(name, RegionType.Generic, name, player)
|
||||
ret.world = world
|
||||
if locations:
|
||||
for location in locations:
|
||||
loc_id = locations_lookup_name_to_id.get(location, 0)
|
||||
location = SubnauticaLocation(player, location, loc_id, ret)
|
||||
ret.locations.append(location)
|
||||
if exits:
|
||||
for exit in exits:
|
||||
ret.exits.append(Entrance(player, exit, ret))
|
||||
def get_pre_fill_items(self) -> List[Item]:
|
||||
return self.prefill_items
|
||||
|
||||
return ret
|
||||
def pre_fill(self) -> None:
|
||||
reachable = self.world.get_reachable_locations(player=self.player)
|
||||
self.world.random.shuffle(reachable)
|
||||
items = self.prefill_items.copy()
|
||||
for item in items:
|
||||
reachable.pop().place_locked_item(item)
|
||||
|
||||
|
||||
class SubnauticaLocation(Location):
|
||||
@@ -112,4 +156,4 @@ class SubnauticaLocation(Location):
|
||||
|
||||
|
||||
class SubnauticaItem(Item):
|
||||
game = "Subnautica"
|
||||
game: str = "Subnautica"
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
[
|
||||
{ "id": 35000, "count": 1, "progression": false, "tech_type": "Compass", "name": "Compass" },
|
||||
{ "id": 35001, "count": 1, "progression": true, "tech_type": "PlasteelTank", "name": "Lightweight High Capacity Tank" },
|
||||
{ "id": 35002, "count": 1, "progression": true, "tech_type": "BaseUpgradeConsole", "name": "Vehicle Upgrade Console" },
|
||||
{ "id": 35003, "count": 1, "progression": true, "tech_type": "UltraGlideFins", "name": "Ultra Glide Fins" },
|
||||
{ "id": 35004, "count": 1, "progression": false, "tech_type": "CyclopsSonarModule", "name": "Cyclops Sonar Upgrade" },
|
||||
{ "id": 35005, "count": 1, "progression": false, "tech_type": "ReinforcedDiveSuit", "name": "Reinforced Dive Suit" },
|
||||
{ "id": 35006, "count": 1, "progression": false, "tech_type": "CyclopsThermalReactorModule", "name": "Cyclops Thermal Reactor Module" },
|
||||
{ "id": 35007, "count": 1, "progression": false, "tech_type": "Stillsuit", "name": "Stillsuit" },
|
||||
{ "id": 35008, "count": 2, "progression": false, "tech_type": "BaseWaterParkFragment", "name": "Alien Containment Fragment" },
|
||||
{ "id": 35009, "count": 1, "progression": false, "tech_type": "CyclopsDecoy", "name": "Creature Decoy" },
|
||||
{ "id": 35010, "count": 1, "progression": false, "tech_type": "CyclopsFireSuppressionModule", "name": "Cyclops Fire Suppression System" },
|
||||
{ "id": 35011, "count": 1, "progression": false, "tech_type": "SwimChargeFins", "name": "Swim Charge Fins" },
|
||||
{ "id": 35012, "count": 1, "progression": false, "tech_type": "RepulsionCannon", "name": "Repulsion Cannon" },
|
||||
{ "id": 35013, "count": 1, "progression": false, "tech_type": "CyclopsDecoyModule", "name": "Cyclops Decoy Tube Upgrade" },
|
||||
{ "id": 35014, "count": 1, "progression": true, "tech_type": "CyclopsShieldModule", "name": "Cyclops Shield Generator" },
|
||||
{ "id": 35015, "count": 1, "progression": true, "tech_type": "CyclopsHullModule1", "name": "Cyclops Depth Module MK1" },
|
||||
{ "id": 35016, "count": 1, "progression": false, "tech_type": "CyclopsSeamothRepairModule", "name": "Cyclops Docking Bay Repair Module" },
|
||||
{ "id": 35017, "count": 2, "progression": false, "tech_type": "BatteryChargerFragment", "name": "Battery Charger fragment" },
|
||||
{ "id": 35018, "count": 2, "progression": false, "tech_type": "BeaconFragment", "name": "Beacon Fragment" },
|
||||
{ "id": 35019, "count": 2, "progression": false, "tech_type": "BaseBioReactorFragment", "name": "Bioreactor Fragment" },
|
||||
{ "id": 35020, "count": 3, "progression": true, "tech_type": "CyclopsBridgeFragment", "name": "Cyclops Bridge Fragment" },
|
||||
{ "id": 35021, "count": 3, "progression": true, "tech_type": "CyclopsEngineFragment", "name": "Cyclops Engine Fragment" },
|
||||
{ "id": 35022, "count": 3, "progression": true, "tech_type": "CyclopsHullFragment", "name": "Cyclops Hull Fragment" },
|
||||
{ "id": 35023, "count": 2, "progression": false, "tech_type": "GravSphereFragment", "name": "Grav Trap Fragment" },
|
||||
{ "id": 35024, "count": 3, "progression": true, "tech_type": "LaserCutterFragment", "name": "Laser Cutter Fragment" },
|
||||
{ "id": 35025, "count": 1, "progression": false, "tech_type": "TechlightFragment", "name": "Light Stick Fragment" },
|
||||
{ "id": 35026, "count": 3, "progression": true, "tech_type": "ConstructorFragment", "name": "Mobile Vehicle Bay Fragment" },
|
||||
{ "id": 35027, "count": 3, "progression": true, "tech_type": "WorkbenchFragment", "name": "Modification Station Fragment" },
|
||||
{ "id": 35028, "count": 2, "progression": true, "tech_type": "MoonpoolFragment", "name": "Moonpool Fragment" },
|
||||
{ "id": 35029, "count": 3, "progression": false, "tech_type": "BaseNuclearReactorFragment", "name": "Nuclear Reactor Fragment" },
|
||||
{ "id": 35030, "count": 2, "progression": false, "tech_type": "PowerCellChargerFragment", "name": "Power Cell Charger Fragment" },
|
||||
{ "id": 35031, "count": 1, "progression": false, "tech_type": "PowerTransmitterFragment", "name": "Power Transmitter Fragment" },
|
||||
{ "id": 35032, "count": 4, "progression": true, "tech_type": "ExosuitFragment", "name": "Prawn Suit Fragment" },
|
||||
{ "id": 35033, "count": 2, "progression": false, "tech_type": "ExosuitDrillArmFragment", "name": "Prawn Suit Drill Arm Fragment" },
|
||||
{ "id": 35034, "count": 2, "progression": false, "tech_type": "ExosuitGrapplingArmFragment", "name": "Prawn Suit Grappling Arm Fragment" },
|
||||
{ "id": 35035, "count": 2, "progression": false, "tech_type": "ExosuitPropulsionArmFragment", "name": "Prawn Suit Propulsion Cannon Fragment" },
|
||||
{ "id": 35036, "count": 2, "progression": false, "tech_type": "ExosuitTorpedoArmFragment", "name": "Prawn Suit Torpedo Arm Fragment" },
|
||||
{ "id": 35037, "count": 3, "progression": false, "tech_type": "BaseMapRoomFragment", "name": "Scanner Room Fragment" },
|
||||
{ "id": 35038, "count": 5, "progression": true, "tech_type": "SeamothFragment", "name": "Seamoth Fragment" },
|
||||
{ "id": 35039, "count": 2, "progression": false, "tech_type": "StasisRifleFragment", "name": "Stasis Rifle Fragment" },
|
||||
{ "id": 35040, "count": 2, "progression": false, "tech_type": "ThermalPlantFragment", "name": "Thermal Plant Fragment" },
|
||||
{ "id": 35041, "count": 4, "progression": true, "tech_type": "SeaglideFragment", "name": "Seaglide Fragment" },
|
||||
{ "id": 35042, "count": 1, "progression": true, "tech_type": "RadiationSuit", "name": "Radiation Suit" },
|
||||
{ "id": 35043, "count": 2, "progression": true, "tech_type": "PropulsionCannonFragment", "name": "Propulsion Cannon Fragment" },
|
||||
{ "id": 35044, "count": 1, "progression": true, "tech_type": "RocketBase", "name": "Neptune Launch Platform" },
|
||||
{ "id": 35045, "count": 1, "progression": true, "tech_type": "PrecursorIonPowerCell", "name": "Ion Power Cell" },
|
||||
{ "id": 35046, "count": 2, "progression": false, "tech_type": "FarmingTrayFragment", "name": "Exterior Growbed Fragment" },
|
||||
{ "id": 35047, "count": 1, "progression": false, "tech_type": "PictureFrameFragment", "name": "Picture Frame" },
|
||||
{ "id": 35048, "count": 2, "progression": false, "tech_type": "BenchFragment", "name": "Bench Fragment" },
|
||||
{ "id": 35049, "count": 1, "progression": false, "tech_type": "PlanterPotFragment", "name": "Basic Plant Pot" },
|
||||
{ "id": 35050, "count": 1, "progression": false, "tech_type": "PlanterBoxFragment", "name": "Interior Growbed" },
|
||||
{ "id": 35051, "count": 1, "progression": false, "tech_type": "PlanterShelfFragment", "name": "Plant Shelf" },
|
||||
{ "id": 35052, "count": 2, "progression": false, "tech_type": "BaseObservatoryFragment", "name": "Observatory Fragment" },
|
||||
{ "id": 35053, "count": 2, "progression": false, "tech_type": "BaseRoomFragment", "name": "Multipurpose Room Fragment" },
|
||||
{ "id": 35054, "count": 2, "progression": false, "tech_type": "BaseBulkheadFragment", "name": "Bulkhead Fragment" },
|
||||
{ "id": 35055, "count": 1, "progression": false, "tech_type": "Spotlight", "name": "Spotlight" },
|
||||
{ "id": 35056, "count": 2, "progression": false, "tech_type": "StarshipDesk", "name": "Desk" },
|
||||
{ "id": 35057, "count": 1, "progression": false, "tech_type": "StarshipChair", "name": "Swivel Chair" },
|
||||
{ "id": 35058, "count": 1, "progression": false, "tech_type": "StarshipChair2", "name": "Office Chair" },
|
||||
{ "id": 35059, "count": 1, "progression": false, "tech_type": "StarshipChair3", "name": "Command Chair" },
|
||||
{ "id": 35060, "count": 2, "progression": false, "tech_type": "LabCounter", "name": "Counter" },
|
||||
{ "id": 35061, "count": 1, "progression": false, "tech_type": "NarrowBed", "name": "Single Bed" },
|
||||
{ "id": 35062, "count": 1, "progression": false, "tech_type": "Bed1", "name": "Basic Double Bed" },
|
||||
{ "id": 35063, "count": 1, "progression": false, "tech_type": "Bed2", "name": "Quilted Double Bed" },
|
||||
{ "id": 35064, "count": 2, "progression": false, "tech_type": "CoffeeVendingMachine", "name": "Coffee Vending Machine" },
|
||||
{ "id": 35065, "count": 2, "progression": false, "tech_type": "Trashcans", "name": "Trash Can" },
|
||||
{ "id": 35066, "count": 1, "progression": false, "tech_type": "Techlight", "name": "Floodlight" },
|
||||
{ "id": 35067, "count": 1, "progression": false, "tech_type": "BarTable", "name": "Bar Table" },
|
||||
{ "id": 35068, "count": 1, "progression": false, "tech_type": "VendingMachine", "name": "Vending Machine" },
|
||||
{ "id": 35069, "count": 1, "progression": false, "tech_type": "SingleWallShelf", "name": "Single Wall Shelf" },
|
||||
{ "id": 35070, "count": 1, "progression": false, "tech_type": "WallShelves", "name": "Wall Shelves" },
|
||||
{ "id": 35071, "count": 1, "progression": false, "tech_type": "PlanterPot2", "name": "Round Plant Pot" },
|
||||
{ "id": 35072, "count": 1, "progression": false, "tech_type": "PlanterPot3", "name": "Chic Plant Pot" },
|
||||
{ "id": 35073, "count": 1, "progression": false, "tech_type": "LabTrashcan", "name": "Nuclear Waste Disposal" },
|
||||
{ "id": 35074, "count": 1, "progression": false, "tech_type": "BasePlanter", "name": "Wall Planter" },
|
||||
{ "id": 35075, "count": 1, "progression": true, "tech_type": "PrecursorIonBattery", "name": "Ion Battery" },
|
||||
{ "id": 35076, "count": 1, "progression": true, "tech_type": "RocketBaseLadder", "name": "Neptune Gantry" },
|
||||
{ "id": 35077, "count": 1, "progression": true, "tech_type": "RocketStage1", "name": "Neptune Boosters" },
|
||||
{ "id": 35078, "count": 1, "progression": true, "tech_type": "RocketStage2", "name": "Neptune Fuel Reserve" },
|
||||
{ "id": 35079, "count": 1, "progression": true, "tech_type": "RocketStage3", "name": "Neptune Cockpit" },
|
||||
{ "id": 35080, "count": 1, "progression": false, "tech_type": "BaseFiltrationMachine", "name": "Water Filtration Machine" }
|
||||
]
|
||||
@@ -1,521 +0,0 @@
|
||||
[
|
||||
{ "id": 33000, "position": { "x": -1234.3, "y": -349.7, "z": -396.0},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Blood Kelp Trench Wreck - Outside Databox" },
|
||||
|
||||
{ "id": 33001, "position": { "x": -1208.0, "y": -349.6, "z": -383.0},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Blood Kelp Trench Wreck - Inside Databox" },
|
||||
|
||||
{ "id": 33002, "position": { "x": -1210.6, "y": -340.7, "z": -393.4},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Blood Kelp Trench Wreck - PDA" },
|
||||
|
||||
{ "id": 33003, "position": { "x": 903.8, "y": -220.3, "z": 590.9},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Bulb Zone West Wreck - Outside Databox" },
|
||||
|
||||
{ "id": 33004, "position": { "x": 910.9, "y": -201.8, "z": 623.5},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Bulb Zone West Wreck - Under Databox" },
|
||||
|
||||
{ "id": 33005, "position": { "x": 914.9, "y": -202.1, "z": 611.8},
|
||||
"need_laser_cutter": true, "can_slip_through": false,
|
||||
"name": "Bulb Zone West Wreck - Inside Databox" },
|
||||
|
||||
{ "id": 33006, "position": { "x": 912.6, "y": -202.0, "z": 609.5},
|
||||
"need_laser_cutter": true, "can_slip_through": false,
|
||||
"name": "Bulb Zone West Wreck - PDA" },
|
||||
|
||||
{ "id": 33007, "position": { "x": 1327.1, "y": -234.9, "z": 575.8},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Bulb Zone East Wreck - Databox" },
|
||||
|
||||
{ "id": 33008, "position": { "x": -1407.7, "y": -344.2, "z": 721.5},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Dunes North Wreck - Outside Databox" },
|
||||
|
||||
{ "id": 33009, "position": { "x": -1393.9, "y": -329.7, "z": 733.5},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Dunes North Wreck - Office Databox" },
|
||||
|
||||
{ "id": 33010, "position": { "x": -1396.3, "y": -330.8, "z": 730.0},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Dunes North Wreck - PDA" },
|
||||
|
||||
{ "id": 33011, "position": { "x": -1409.8, "y": -332.4, "z": 706.9},
|
||||
"need_laser_cutter": true, "can_slip_through": false,
|
||||
"name": "Dunes North Wreck - Cargo Databox" },
|
||||
|
||||
{ "id": 33012, "position": { "x": -1626.2, "y": -357.5, "z": 99.5},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Dunes West Wreck - Databox" },
|
||||
|
||||
{ "id": 33013, "position": { "x": -1196.3, "y": -223.0, "z": 12.5},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Dunes East Wreck - Outside Databox" },
|
||||
|
||||
{ "id": 33014, "position": { "x": -1206.4, "y": -225.6, "z": 4.0},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Dunes East Wreck - Inside Databox" },
|
||||
|
||||
{ "id": 33015, "position": { "x": -269.7, "y": -262.8, "z": -764.3},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Grand Reef North Wreck - Outside Databox" },
|
||||
|
||||
{ "id": 33016, "position": { "x": -285.8, "y": -240.2, "z": -786.5},
|
||||
"need_laser_cutter": true, "can_slip_through": false,
|
||||
"name": "Grand Reef North Wreck - Elevator Databox" },
|
||||
|
||||
{ "id": 33017, "position": { "x": -285.2, "y": -262.4, "z": -788.4},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Grand Reef North Wreck - Bottom Databox" },
|
||||
|
||||
{ "id": 33018, "position": { "x": -272.5, "y": -254.7, "z": -788.5},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Grand Reef North Wreck - Hangar PDA" },
|
||||
|
||||
{ "id": 33019, "position": { "x": -850.9, "y": -473.2, "z": -1414.6},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Grand Reef South Wreck - Trench Databox" },
|
||||
|
||||
{ "id": 33020, "position": { "x": -889.4, "y": -433.8, "z": -1424.8},
|
||||
"need_laser_cutter": true, "can_slip_through": false,
|
||||
"name": "Grand Reef South Wreck - Comms Databox" },
|
||||
|
||||
{ "id": 33021, "position": { "x": -862.4, "y": -437.5, "z": -1444.1},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Grand Reef South Wreck - Outside Databox" },
|
||||
|
||||
{ "id": 33022, "position": { "x": -887.9, "y": -446.0, "z": -1422.7},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Grand Reef South Wreck - PDA" },
|
||||
|
||||
{ "id": 33023, "position": { "x": -23.3, "y": -105.8, "z": -604.2},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Grassy Plateaus South Wreck - Databox" },
|
||||
|
||||
{ "id": 33024, "position": { "x": -27.3, "y": -106.8, "z": -607.2},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Grassy Plateaus South Wreck - PDA" },
|
||||
|
||||
{ "id": 33025, "position": { "x": 313.9, "y": -91.8, "z": 432.6},
|
||||
"need_laser_cutter": true, "can_slip_through": true,
|
||||
"name": "Grassy Plateaus East Wreck - Breach Databox" },
|
||||
|
||||
{ "id": 33026, "position": { "x": 319.4, "y": -104.3, "z": 441.5},
|
||||
"need_laser_cutter": true, "can_slip_through": true,
|
||||
"name": "Grassy Plateaus East Wreck - Hangar Databox" },
|
||||
|
||||
{ "id": 33027, "position": { "x": -632.3, "y": -75.0, "z": -8.9},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Grassy Plateaus West Wreck - Locker PDA" },
|
||||
|
||||
{ "id": 33028, "position": { "x": -664.4, "y": -97.8, "z": -8.0},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Grassy Plateaus West Wreck - Data Terminal" },
|
||||
|
||||
{ "id": 33029, "position": { "x": -421.4, "y": -107.8, "z": -266.5},
|
||||
"need_laser_cutter": true, "can_slip_through": false,
|
||||
"name": "Grassy Plateaus West Wreck - Databox" },
|
||||
|
||||
{ "id": 33030, "position": { "x": -44.0, "y": -29.1, "z": -403.6},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Safe Shallows Wreck - PDA" },
|
||||
|
||||
{ "id": 33031, "position": { "x": -317.1, "y": -79.0, "z": 248.5},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Kelp Forest Wreck - Databox" },
|
||||
|
||||
{ "id": 33032, "position": { "x": 63.2, "y": -38.5, "z": 382.9},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Kelp Forest Wreck - PDA" },
|
||||
|
||||
{ "id": 33033, "position": { "x": 740.3, "y": -389.2, "z": 1179.8},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Mountains West Wreck - Outside Databox" },
|
||||
|
||||
{ "id": 33034, "position": { "x": 703.7, "y": -365.9, "z": 1199.3},
|
||||
"need_laser_cutter": true, "can_slip_through": false,
|
||||
"name": "Mountains West Wreck - Data Terminal" },
|
||||
|
||||
{ "id": 33035, "position": { "x": 698.2, "y": -350.8, "z": 1186.9},
|
||||
"need_laser_cutter": true, "can_slip_through": false,
|
||||
"name": "Mountains West Wreck - Hangar Databox" },
|
||||
|
||||
{ "id": 33036, "position": { "x": 676.3, "y": -343.6, "z": 1204.6},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Mountains West Wreck - Office Databox" },
|
||||
|
||||
{ "id": 33037, "position": { "x": 1068.5, "y": -283.4, "z": 1345.3},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Mountains East Wreck - Comms Databox" },
|
||||
|
||||
{ "id": 33038, "position": { "x": 1075.7, "y": -288.9, "z": 1321.8},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Mountains East Wreck - Outside Databox" },
|
||||
|
||||
{ "id": 33039, "position": { "x": -655.1, "y": -109.6, "z": 791.0},
|
||||
"need_laser_cutter": true, "can_slip_through": false,
|
||||
"name": "Northwestern Mushroom Forest Wreck - Cargo Databox" },
|
||||
|
||||
{ "id": 33040, "position": { "x": -663.4, "y": -111.9, "z": 777.9},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Northwestern Mushroom Forest Wreck - Office Databox" },
|
||||
|
||||
{ "id": 33041, "position": { "x": -662.2, "y": -113.4, "z": 777.7},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Northwestern Mushroom Forest Wreck - PDA" },
|
||||
|
||||
{ "id": 33042, "position": { "x": -1161.1, "y": -191.7, "z": -758.3},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Sea Treader's Path Wreck - Outside Databox" },
|
||||
|
||||
{ "id": 33043, "position": { "x": -1129.5, "y": -155.2, "z": -729.3},
|
||||
"need_laser_cutter": true, "can_slip_through": false,
|
||||
"name": "Sea Treader's Path Wreck - Hangar Databox" },
|
||||
|
||||
{ "id": 33044, "position": { "x": -1115.9, "y": -175.3, "z": -724.5},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Sea Treader's Path Wreck - Lobby Databox" },
|
||||
|
||||
{ "id": 33045, "position": { "x": -1136.8, "y": -157.0, "z": -734.6},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Sea Treader's Path Wreck - PDA" },
|
||||
|
||||
{ "id": 33046, "position": { "x": -789.8, "y": -216.1, "z": -711.0},
|
||||
"need_laser_cutter": true, "can_slip_through": false,
|
||||
"name": "Sparse Reef Wreck - Locker Databox" },
|
||||
|
||||
{ "id": 33047, "position": { "x": -810.7, "y": -209.3, "z": -685.5},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Sparse Reef Wreck - Outside Databox" },
|
||||
|
||||
{ "id": 33048, "position": { "x": -795.5, "y": -204.1, "z": -774.7},
|
||||
"need_laser_cutter": true, "can_slip_through": false,
|
||||
"name": "Sparse Reef Wreck - Lab Databox" },
|
||||
|
||||
{ "id": 33049, "position": { "x": -170.8, "y": -187.6, "z": 880.7},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Underwater Islands Wreck - Outside Databox" },
|
||||
|
||||
{ "id": 33050, "position": { "x": -138.4, "y": -193.6, "z": 888.7},
|
||||
"need_laser_cutter": true, "can_slip_through": false,
|
||||
"name": "Underwater Islands Wreck - Hangar Databox" },
|
||||
|
||||
{ "id": 33051, "position": { "x": -130.7, "y": -193.2, "z": 883.3},
|
||||
"need_laser_cutter": true, "can_slip_through": false,
|
||||
"name": "Underwater Islands Wreck - Data Terminal" },
|
||||
|
||||
{ "id": 33052, "position": { "x": -137.8, "y": -193.4, "z": 879.4},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Underwater Islands Wreck - Cable Databox" },
|
||||
|
||||
{ "id": 33053, "position": { "x": -124.4, "y": -200.7, "z": 853.0},
|
||||
"need_laser_cutter": false, "can_slip_through": false, "need_propulsion_cannon": true,
|
||||
"name": "Underwater Islands Wreck - Pipes Databox 1" },
|
||||
|
||||
{ "id": 33054, "position": { "x": -126.8, "y": -201.1, "z": 852.1},
|
||||
"need_laser_cutter": false, "can_slip_through": false, "need_propulsion_cannon": true,
|
||||
"name": "Underwater Islands Wreck - Pipes Databox 2" },
|
||||
|
||||
{ "id": 33055, "position": { "x": -643.8, "y": -509.9, "z": -941.9},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Degasi Seabase - Deep Grand Reef - Bedroom Databox" },
|
||||
|
||||
{ "id": 33056, "position": { "x": -635.1, "y": -502.7, "z": -951.4},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Degasi Seabase - Deep Grand Reef - Observatory Databox" },
|
||||
|
||||
{ "id": 33057, "position": { "x": -645.8, "y": -508.7, "z": -943.0},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Degasi Seabase - Deep Grand Reef - Bedroom PDA" },
|
||||
|
||||
{ "id": 33058, "position": { "x": -630.5, "y": -511.1, "z": -936.1},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Degasi Seabase - Deep Grand Reef - Outside PDA" },
|
||||
|
||||
{ "id": 33059, "position": { "x": -647.7, "y": -502.6, "z": -935.8},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Degasi Seabase - Deep Grand Reef - Observatory PDA" },
|
||||
|
||||
{ "id": 33060, "position": { "x": -639.6, "y": -505.9, "z": -946.6},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Degasi Seabase - Deep Grand Reef - Lab PDA" },
|
||||
|
||||
{ "id": 33061, "position": { "x": -707.2, "y": 0.5, "z": -1096.7},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Floating Island - Lake PDA" },
|
||||
|
||||
{ "id": 33062, "position": { "x": -765.7, "y": 17.6, "z": -1116.4},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Degasi Seabase - Floating Island - Databox" },
|
||||
|
||||
{ "id": 33063, "position": { "x": -754.9, "y": 14.6, "z": -1108.9},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Degasi Seabase - Floating Island - Room PDA" },
|
||||
|
||||
{ "id": 33064, "position": { "x": -765.3, "y": 14.1, "z": -1115.0},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Degasi Seabase - Floating Island - Green Wall PDA" },
|
||||
|
||||
{ "id": 33065, "position": { "x": -758.6, "y": 14.1, "z": -1111.3},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Degasi Seabase - Floating Island - Corridor PDA" },
|
||||
|
||||
{ "id": 33066, "position": { "x": -805.4, "y": 76.9, "z": -1055.7},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Degasi Seabase - Floating Island - North Observatory PDA" },
|
||||
|
||||
{ "id": 33067, "position": { "x": -715.9, "y": 75.4, "z": -1168.8},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Degasi Seabase - Floating Island - South Observatory PDA" },
|
||||
|
||||
{ "id": 33068, "position": { "x": -540.5, "y": -250.8, "z": -83.4},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Jellyshroom Cave - PDA" },
|
||||
|
||||
{ "id": 33069, "position": { "x": 110.6, "y": -264.9, "z": -369.0},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Degasi Seabase - Jellyshroom Cave - Bedroom Databox" },
|
||||
|
||||
{ "id": 33070, "position": { "x": 80.6, "y": -268.6, "z": -358.3},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Degasi Seabase - Jellyshroom Cave - Detached PDA" },
|
||||
|
||||
{ "id": 33071, "position": { "x": 78.2, "y": -265.0, "z": -373.4},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Degasi Seabase - Jellyshroom Cave - Office PDA" },
|
||||
|
||||
{ "id": 33072, "position": { "x": 85.1, "y": -264.1, "z": -372.8},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Degasi Seabase - Jellyshroom Cave - Locker PDA" },
|
||||
|
||||
{ "id": 33073, "position": { "x": 112.3, "y": -264.9, "z": -369.3},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Degasi Seabase - Jellyshroom Cave - Bedroom PDA" },
|
||||
|
||||
{ "id": 33074, "position": { "x": 95.5, "y": -258.9, "z": -366.5},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Degasi Seabase - Jellyshroom Cave - Observatory PDA" },
|
||||
|
||||
{ "id": 33075, "position": { "x": -483.6, "y": -504.7, "z": 1326.6},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Lifepod 2 - Databox" },
|
||||
|
||||
{ "id": 33076, "position": { "x": -481.4, "y": -503.6, "z": 1324.1},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Lifepod 2 - PDA" },
|
||||
|
||||
{ "id": 33077, "position": { "x": -34.2, "y": -22.4, "z": 410.5},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Lifepod 3 - Databox" },
|
||||
|
||||
{ "id": 33078, "position": { "x": -33.8, "y": -22.5, "z": 408.8},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Lifepod 3 - PDA" },
|
||||
|
||||
{ "id": 33079, "position": { "x": 712.4, "y": -3.4, "z": 160.8},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Lifepod 4 - Databox" },
|
||||
|
||||
{ "id": 33080, "position": { "x": 712.0, "y": -3.5, "z": 161.5},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Lifepod 4 - PDA" },
|
||||
|
||||
{ "id": 33081, "position": { "x": 358.7, "y": -117.1, "z": 306.8},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Lifepod 6 - Databox" },
|
||||
|
||||
{ "id": 33082, "position": { "x": 361.8, "y": -116.2, "z": 309.5},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Lifepod 6 - Inside PDA" },
|
||||
|
||||
{ "id": 33083, "position": { "x": 359.9, "y": -117.0, "z": 312.1},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Lifepod 6 - Outside PDA" },
|
||||
|
||||
{ "id": 33084, "position": { "x": -56.0, "y": -182.0, "z": -1039.0},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Lifepod 7 - PDA" },
|
||||
|
||||
{ "id": 33085, "position": { "x": 1119.5, "y": -271.7, "z": 561.7},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Lifepod 12 - Databox" },
|
||||
|
||||
{ "id": 33086, "position": { "x": 1116.1, "y": -271.3, "z": 566.9},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Lifepod 12 - PDA" },
|
||||
|
||||
{ "id": 33087, "position": { "x": -926.4, "y": -185.2, "z": 501.8},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Lifepod 13 - Databox" },
|
||||
|
||||
{ "id": 33088, "position": { "x": -926.8, "y": -184.4, "z": 506.6},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Lifepod 13 - PDA" },
|
||||
|
||||
{ "id": 33089, "position": { "x": -514.5, "y": -98.1, "z": -56.5},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Lifepod 17 - PDA" },
|
||||
|
||||
{ "id": 33090, "position": { "x": -809.8, "y": -302.2, "z": -876.9},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Lifepod 19 - Databox" },
|
||||
|
||||
{ "id": 33091, "position": { "x": -806.1, "y": -294.1, "z": -866.0},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Lifepod 19 - Outside PDA" },
|
||||
|
||||
{ "id": 33092, "position": { "x": -810.5, "y": -299.4, "z": -873.1},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Lifepod 19 - Inside PDA" },
|
||||
|
||||
{ "id": 33093, "position": { "x": 903.5, "y": -0.2, "z": 16.1},
|
||||
"need_laser_cutter": false, "can_slip_through": false, "need_propulsion_cannon": true,
|
||||
"name": "Aurora Seamoth Bay - Upgrade Console" },
|
||||
|
||||
{ "id": 33094, "position": { "x": 872.5, "y": 2.7, "z": -0.7},
|
||||
"need_laser_cutter": false, "can_slip_through": false, "need_propulsion_cannon": true,
|
||||
"name": "Aurora Drive Room - Upgrade Console" },
|
||||
|
||||
{ "id": 33095, "position": { "x": 991.6, "y": 3.2, "z": -31.0},
|
||||
"need_laser_cutter": true, "can_slip_through": false, "need_propulsion_cannon": true,
|
||||
"name": "Aurora Prawn Suit Bay - Upgrade Console" },
|
||||
|
||||
{ "id": 33096, "position": { "x": 952.1, "y": 41.2, "z": 113.9},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Aurora - Office PDA" },
|
||||
|
||||
{ "id": 33097, "position": { "x": 977.2, "y": 39.1, "z": 83.0},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Aurora - Corridor PDA" },
|
||||
|
||||
{ "id": 33098, "position": { "x": 954.9, "y": 11.2, "z": 3.4},
|
||||
"need_laser_cutter": false, "can_slip_through": false, "need_propulsion_cannon": true,
|
||||
"name": "Aurora - Cargo Bay PDA" },
|
||||
|
||||
{ "id": 33099, "position": { "x": 907.1, "y": -1.5, "z": 15.3},
|
||||
"need_laser_cutter": false, "can_slip_through": false, "need_propulsion_cannon": true,
|
||||
"name": "Aurora - Seamoth Bay PDA" },
|
||||
|
||||
{ "id": 33100, "position": { "x": 951.8, "y": -2.3, "z": -34.7},
|
||||
"need_laser_cutter": true, "can_slip_through": false, "need_propulsion_cannon": true,
|
||||
"name": "Aurora - Medkit Locker PDA" },
|
||||
|
||||
{ "id": 33101, "position": { "x": 952.0, "y": -3.7, "z": -23.4},
|
||||
"need_laser_cutter": true, "can_slip_through": false, "need_propulsion_cannon": true,
|
||||
"name": "Aurora - Locker PDA" },
|
||||
|
||||
{ "id": 33102, "position": { "x": 986.5, "y": 9.6, "z": -48.6},
|
||||
"need_laser_cutter": true, "can_slip_through": false, "need_propulsion_cannon": true,
|
||||
"name": "Aurora - Canteen PDA" },
|
||||
|
||||
{ "id": 33103, "position": { "x": 951.3, "y": 11.2, "z": -51.0},
|
||||
"need_laser_cutter": true, "can_slip_through": false, "need_propulsion_cannon": true,
|
||||
"name": "Aurora - Cabin 4 PDA" },
|
||||
|
||||
{ "id": 33104, "position": { "x": 967.1, "y": 10.4, "z": -47.4},
|
||||
"need_laser_cutter": true, "can_slip_through": false, "need_propulsion_cannon": true,
|
||||
"name": "Aurora - Cabin 7 PDA" },
|
||||
|
||||
{ "id": 33105, "position": { "x": 964.1, "y": 11.1, "z": -61.9},
|
||||
"need_laser_cutter": true, "can_slip_through": false, "need_propulsion_cannon": true,
|
||||
"name": "Aurora - Cabin 1 PDA" },
|
||||
|
||||
{ "id": 33106, "position": { "x": 971.2, "y": 10.8, "z": -70.4},
|
||||
"need_laser_cutter": true, "can_slip_through": false, "need_propulsion_cannon": true,
|
||||
"name": "Aurora - Captain PDA" },
|
||||
|
||||
{ "id": 33107, "position": { "x": 1033.6, "y": -8.5, "z": 16.2},
|
||||
"need_laser_cutter": false, "can_slip_through": false, "need_propulsion_cannon": true,
|
||||
"name": "Aurora - Ring PDA" },
|
||||
|
||||
{ "id": 33108, "position": { "x": 1032.5, "y": -7.8, "z": 32.4},
|
||||
"need_laser_cutter": false, "can_slip_through": false, "need_propulsion_cannon": true,
|
||||
"name": "Aurora - Lab PDA" },
|
||||
|
||||
{ "id": 33109, "position": { "x": 945.8, "y": 40.8, "z": 115.1},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Aurora - Office Data Terminal" },
|
||||
|
||||
{ "id": 33110, "position": { "x": 974.8, "y": 10.0, "z": -77.0},
|
||||
"need_laser_cutter": true, "can_slip_through": false, "need_propulsion_cannon": true,
|
||||
"name": "Aurora - Captain Data Terminal" },
|
||||
|
||||
{ "id": 33111, "position": { "x": 1040.8, "y": -11.4, "z": -3.4},
|
||||
"need_laser_cutter": true, "can_slip_through": false, "need_propulsion_cannon": true,
|
||||
"name": "Aurora - Battery Room Data Terminal" },
|
||||
|
||||
{ "id": 33112, "position": { "x": 1029.5, "y": -8.7, "z": 35.9},
|
||||
"need_laser_cutter": false, "can_slip_through": false, "need_propulsion_cannon": true,
|
||||
"name": "Aurora - Lab Data Terminal" },
|
||||
|
||||
{ "id": 33113, "position": { "x": 432.2, "y": 3.0, "z": 1193.2},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Quarantine Enforcement Platform's - Upper Alien Data Terminal" },
|
||||
|
||||
{ "id": 33114, "position": { "x": 474.4, "y": -4.5, "z": 1224.4},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Quarantine Enforcement Platform's - Mid Alien Data Terminal" },
|
||||
|
||||
{ "id": 33115, "position": { "x": -1224.2, "y": -400.4, "z": 1057.9},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Dunes Sanctuary - Alien Data Terminal" },
|
||||
|
||||
{ "id": 33116, "position": { "x": -895.5, "y": -311.6, "z": -838.1},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Deep Sparse Reef Sanctuary - Alien Data Terminal" },
|
||||
|
||||
{ "id": 33117, "position": { "x": -642.9, "y": -563.5, "z": 1485.5},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Northern Blood Kelp Zone Sanctuary - Alien Data Terminal" },
|
||||
|
||||
{ "id": 33118, "position": { "x": -1112.3, "y": -687.3, "z": -695.5},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Lost River Laboratory Cache - Alien Data Terminal" },
|
||||
|
||||
{ "id": 33119, "position": { "x": -280.2, "y": -804.3, "z": 305.1},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Disease Research Facility - Upper Alien Data Terminal" },
|
||||
|
||||
{ "id": 33120, "position": { "x": -267.9, "y": -806.6, "z": 250.0},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Disease Research Facility - Mid Alien Data Terminal" },
|
||||
|
||||
{ "id": 33121, "position": { "x": -286.2, "y": -815.6, "z": 297.8},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Disease Research Facility - Lower Alien Data Terminal" },
|
||||
|
||||
{ "id": 33122, "position": { "x": -71.3, "y": -1227.2, "z": 104.8},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Alien Thermal Plant - Entrance Alien Data Terminal" },
|
||||
|
||||
{ "id": 33123, "position": { "x": -38.7, "y": -1226.6, "z": 111.8},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Alien Thermal Plant - Green Alien Data Terminal" },
|
||||
|
||||
{ "id": 33124, "position": { "x": -30.4, "y": -1220.3, "z": 111.8},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Alien Thermal Plant - Yellow Alien Data Terminal" },
|
||||
|
||||
{ "id": 33125, "position": { "x": 245.8, "y": -1430.6, "z": -311.5},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Primary Containment Facility's Antechamber - Alien Data Terminal" },
|
||||
|
||||
{ "id": 33126, "position": { "x": 165.5, "y": -1442.4, "z": -385.8},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Primary Containment Facility's Egg Laboratory - Alien Data Terminal" },
|
||||
|
||||
{ "id": 33127, "position": { "x": 348.7, "y": -1443.5, "z": -291.9},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Primary Containment Facility's Pipe Room - Alien Data Terminal" },
|
||||
|
||||
{ "id": 33128, "position": { "x": -641.8, "y": -111.3, "z": -19.7},
|
||||
"need_laser_cutter": true, "can_slip_through": false,
|
||||
"name": "Grassy Plateaus West Wreck - Beam PDA" },
|
||||
|
||||
{ "id": 33129, "position": { "x": -748.9, "y": 14.4, "z": -1179.5},
|
||||
"need_laser_cutter": false, "can_slip_through": false,
|
||||
"name": "Floating Island - Cave Entrance PDA" }
|
||||
]
|
||||
@@ -1,30 +0,0 @@
|
||||
100 - 0x01A54 - None - Glass Factory Entry Door
|
||||
105 - 0x000B0 - 0x0343A - Door to Symmetry Island Lower
|
||||
107 - 0x1C349 - 0x00076 - Door to Symmetry Island Upper
|
||||
110 - 0x0C339 - 0x09F94 - Door to Desert Flood Light Room
|
||||
111 - 0x1C2DF,0x1831E,0x1C260,0x1831C,0x1C2F3,0x1831D,0x1C2B1,0x1831B - None - Desert Flood Room Flood Controls
|
||||
120 - 0x03678 - None - Quarry Mill Ramp Control
|
||||
122 - 0x03679 - 0x014E8 - Quarry Mill Elevator Control
|
||||
125 - 0x03852 - 0x034D4,0x021D5 - Quarry Boathouse Ramp Height Control
|
||||
127 - 0x03858 - 0x021AE - Quarry Boathouse Ramp Horizontal Control
|
||||
131 - 0x334DB,0x334DC - None - Shadows Door Timer
|
||||
150 - 0x00B10 - None - Monastery Entry Door Left
|
||||
151 - 0x00C92 - None - Monastery Entry Door Right
|
||||
162 - 0x28998 - None - Town Door to RGB House
|
||||
163 - 0x28A0D - 0x28998 - Town Door to Church
|
||||
166 - 0x28A79 - None - Town Maze Panel (Drop-Down Staircase)
|
||||
169 - 0x17F5F - None - Windmill Door
|
||||
200 - 0x0288C - None - Treehouse First & Second Door
|
||||
202 - 0x0A182 - None - Treehouse Third Door
|
||||
205 - 0x2700B - None - Treehouse Laser House Door Timer
|
||||
208 - 0x17CBC - None - Treehouse Shortcut Drop-Down Bridge
|
||||
175 - 0x17CAB - 0x002C7 - Jungle Popup Wall
|
||||
180 - 0x17C2E - None - Bunker Entry Door
|
||||
183 - 0x0A099 - 0x09DAF - Inside Bunker Door to Bunker Proper
|
||||
186 - 0x0A079 - None - Bunker Elevator Control
|
||||
190 - 0x0056E - None - Swamp Entry Door
|
||||
192 - 0x00609,0x18488 - 0x181A9 - Swamp Sliding Bridge
|
||||
195 - 0x181F5 - None - Swamp Rotating Bridge
|
||||
197 - 0x17C0A - None - Swamp Maze Control
|
||||
300 - 0x0042D - None - Mountaintop River Shape Panel (Shortcut to Secret Area)
|
||||
310 - 0x17CDF,0x17CC8,0x17CA6,0x09DB8,0x17C95,0x0A054 - None - Boat
|
||||
@@ -1,5 +0,0 @@
|
||||
Event Items:
|
||||
Shortcut to Secret Area Opens - 0x0042D
|
||||
|
||||
Region Changes:
|
||||
Inside Mountain Secret Area (Inside Mountain Secret Area) - Inside Mountain Path to Secret Area - 0x00FF8 - Main Island - 0x021D7 | 0x0042D - Main Island - 0x17CF2
|
||||
@@ -7,37 +7,48 @@ from Options import Toggle, DefaultOnToggle, Option, Range, Choice
|
||||
# "Play the randomizer in hardmode"
|
||||
# display_name = "Hard Mode"
|
||||
|
||||
# class UnlockSymbols(DefaultOnToggle):
|
||||
# "All Puzzle symbols of a specific panel need to be unlocked before the panel can be used"
|
||||
# display_name = "Unlock Symbols"
|
||||
|
||||
class DisableNonRandomizedPuzzles(DefaultOnToggle):
|
||||
"""Disable puzzles that cannot be randomized.
|
||||
Non randomized puzzles are Shadows, Monastery, and Greenhouse.
|
||||
"""Disables puzzles that cannot be randomized.
|
||||
This includes many puzzles that heavily involve the environment, such as Shadows, Monastery or Orchard.
|
||||
The lasers for those areas will be activated as you solve optional puzzles throughout the island."""
|
||||
display_name = "Disable non randomized puzzles"
|
||||
|
||||
|
||||
class EarlySecretArea(Toggle):
|
||||
"""The Mountainside shortcut to the Mountain Secret Area is open from the start.
|
||||
"""Opens the Mountainside shortcut to the Mountain Secret Area from the start.
|
||||
(Otherwise known as "UTM", "Caves" or the "Challenge Area")"""
|
||||
display_name = "Early Secret Area"
|
||||
|
||||
|
||||
class ShuffleSymbols(DefaultOnToggle):
|
||||
"""You will need to unlock puzzle symbols as items to be able to solve the panels that contain those symbols."""
|
||||
"""You will need to unlock puzzle symbols as items to be able to solve the panels that contain those symbols.
|
||||
If you turn this off, there will be no progression items in the game unless you turn on door shuffle."""
|
||||
display_name = "Shuffle Symbols"
|
||||
|
||||
|
||||
class ShuffleDoors(Toggle):
|
||||
"""Many doors around the island will have their panels turned off initially.
|
||||
You will need to find the items that power the panels to open those doors."""
|
||||
class ShuffleLasers(Toggle):
|
||||
"""If on, the 11 lasers are turned into items and will activate on their own upon receiving them.
|
||||
Note: There is a visual bug that can occur with the Desert Laser. It does not affect gameplay - The Laser can still
|
||||
be redirected as normal, for both applications of redirection."""
|
||||
display_name = "Shuffle Lasers"
|
||||
|
||||
|
||||
class ShuffleDoors(Choice):
|
||||
"""If on, opening doors will require their respective "keys".
|
||||
If set to "panels", those keys will unlock the panels on doors.
|
||||
In "doors_simple" and "doors_complex", the doors will magically open by themselves upon receiving the key."""
|
||||
display_name = "Shuffle Doors"
|
||||
option_none = 0
|
||||
option_panels = 1
|
||||
option_doors_simple = 2
|
||||
option_doors_complex = 3
|
||||
option_max = 4
|
||||
|
||||
|
||||
class ShuffleDiscardedPanels(Toggle):
|
||||
"""Discarded Panels will have items on them.
|
||||
Solving certain Discarded Panels may still be necessary even if off!"""
|
||||
"""Add Discarded Panels into the location pool.
|
||||
Solving certain Discarded Panels may still be necessary to beat the game, even if this is off."""
|
||||
|
||||
display_name = "Shuffle Discarded Panels"
|
||||
|
||||
|
||||
@@ -52,9 +63,10 @@ class ShuffleUncommonLocations(Toggle):
|
||||
display_name = "Shuffle Uncommon Locations"
|
||||
|
||||
|
||||
class ShuffleHardLocations(Toggle):
|
||||
"""Adds some harder locations into the game, e.g. Mountain Secret Area panels"""
|
||||
display_name = "Shuffle Hard Locations"
|
||||
class ShufflePostgame(Toggle):
|
||||
"""Adds locations into the pool that are guaranteed to be locked behind your goal. Use this if you don't play with
|
||||
forfeit on victory."""
|
||||
display_name = "Shuffle Postgame"
|
||||
|
||||
|
||||
class VictoryCondition(Choice):
|
||||
@@ -103,16 +115,17 @@ class PuzzleSkipAmount(Range):
|
||||
|
||||
the_witness_options: Dict[str, type] = {
|
||||
# "hard_mode": HardMode,
|
||||
"shuffle_symbols": ShuffleSymbols,
|
||||
"shuffle_doors": ShuffleDoors,
|
||||
"shuffle_lasers": ShuffleLasers,
|
||||
"disable_non_randomized_puzzles": DisableNonRandomizedPuzzles,
|
||||
"shuffle_discarded_panels": ShuffleDiscardedPanels,
|
||||
"shuffle_vault_boxes": ShuffleVaultBoxes,
|
||||
"shuffle_uncommon": ShuffleUncommonLocations,
|
||||
"shuffle_hard": ShuffleHardLocations,
|
||||
"shuffle_postgame": ShufflePostgame,
|
||||
"victory_condition": VictoryCondition,
|
||||
"trap_percentage": TrapPercentage,
|
||||
"early_secret_area": EarlySecretArea,
|
||||
# "shuffle_symbols": ShuffleSymbols,
|
||||
# "shuffle_doors": ShuffleDoors,
|
||||
"mountain_lasers": MountainLasers,
|
||||
"challenge_lasers": ChallengeLasers,
|
||||
"puzzle_skip_amount": PuzzleSkipAmount,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
Progression:
|
||||
0 - Dots
|
||||
1 - Colored Dots
|
||||
2 - Full Dots
|
||||
3 - Invisible Dots
|
||||
5 - Sound Dots
|
||||
10 - Symmetry
|
||||
20 - Triangles
|
||||
@@ -12,6 +14,7 @@ Progression:
|
||||
61 - Stars + Same Colored Symbol
|
||||
71 - Black/White Squares
|
||||
72 - Colored Squares
|
||||
80 - Arrows
|
||||
|
||||
Usefuls:
|
||||
101 - Functioning Brain - False
|
||||
@@ -23,3 +26,173 @@ Boosts:
|
||||
Traps:
|
||||
600 - Slowness
|
||||
610 - Power Surge
|
||||
|
||||
Doors:
|
||||
1100 - Glass Factory Entry Door (Panel) - 0x01A54
|
||||
1105 - Door to Symmetry Island Lower (Panel) - 0x000B0
|
||||
1107 - Door to Symmetry Island Upper (Panel) - 0x1C349
|
||||
1110 - Door to Desert Flood Light Room (Panel) - 0x0C339
|
||||
1111 - Desert Flood Room Flood Controls (Panel) - 0x1C2DF,0x1831E,0x1C260,0x1831C,0x1C2F3,0x1831D,0x1C2B1,0x1831B
|
||||
1119 - Quarry Door to Mill (Panel) - 0x01E5A,0x01E59
|
||||
1120 - Quarry Mill Ramp Controls (Panel) - 0x03678,0x03676
|
||||
1122 - Quarry Mill Elevator Controls (Panel) - 0x03679,0x03675
|
||||
1125 - Quarry Boathouse Ramp Height Control (Panel) - 0x03852
|
||||
1127 - Quarry Boathouse Ramp Horizontal Control (Panel) - 0x03858
|
||||
1131 - Shadows Door Timer (Panel) - 0x334DB,0x334DC
|
||||
1150 - Monastery Entry Door Left (Panel) - 0x00B10
|
||||
1151 - Monastery Entry Door Right (Panel) - 0x00C92
|
||||
1162 - Town Door to RGB House (Panel) - 0x28998
|
||||
1163 - Town Door to Church (Panel) - 0x28A0D
|
||||
1166 - Town Maze Panel (Drop-Down Staircase) (Panel) - 0x28A79
|
||||
1169 - Windmill Door (Panel) - 0x17F5F
|
||||
1200 - Treehouse First & Second Doors (Panel) - 0x0288C,0x02886
|
||||
1202 - Treehouse Third Door (Panel) - 0x0A182
|
||||
1205 - Treehouse Laser House Door Timer (Panel) - 0x2700B,0x334DC
|
||||
1208 - Treehouse Shortcut Drop-Down Bridge (Panel) - 0x17CBC
|
||||
1175 - Jungle Popup Wall (Panel) - 0x17CAB
|
||||
1180 - Bunker Entry Door (Panel) - 0x17C2E
|
||||
1183 - Inside Bunker Door to Bunker Proper (Panel) - 0x0A099
|
||||
1186 - Bunker Elevator Control (Panel) - 0x0A079
|
||||
1190 - Swamp Entry Door (Panel) - 0x0056E
|
||||
1192 - Swamp Sliding Bridge (Panel) - 0x00609,0x18488
|
||||
1195 - Swamp Rotating Bridge (Panel) - 0x181F5
|
||||
1197 - Swamp Maze Control (Panel) - 0x17C0A
|
||||
1310 - Boat - 0x17CDF,0x17CC8,0x17CA6,0x09DB8,0x17C95,0x0A054
|
||||
|
||||
1400 - Caves Mountain Shortcut - 0x2D73F
|
||||
|
||||
1500 - Symmetry Laser - 0x00509
|
||||
1501 - Desert Laser - 0x012FB,0x01317
|
||||
1502 - Quarry Laser - 0x01539
|
||||
1503 - Shadows Laser - 0x181B3
|
||||
1504 - Keep Laser - 0x014BB
|
||||
1505 - Monastery Laser - 0x17C65
|
||||
1506 - Town Laser - 0x032F9
|
||||
1507 - Jungle Laser - 0x00274
|
||||
1508 - Bunker Laser - 0x0C2B2
|
||||
1509 - Swamp Laser - 0x00BF6
|
||||
1510 - Treehouse Laser - 0x028A4
|
||||
|
||||
1600 - Outside Tutorial Optional Door - 0x03BA2
|
||||
1603 - Outside Tutorial Outpost Entry Door - 0x0A170
|
||||
1606 - Outside Tutorial Outpost Exit Door - 0x04CA3
|
||||
1609 - Glass Factory Entry Door - 0x01A29
|
||||
1612 - Glass Factory Back Wall - 0x0D7ED
|
||||
1615 - Symmetry Island Lower Door - 0x17F3E
|
||||
1618 - Symmetry Island Upper Door - 0x18269
|
||||
1619 - Orchard Middle Gate - 0x03307
|
||||
1620 - Orchard Final Gate - 0x03313
|
||||
1621 - Desert Door to Flood Light Room - 0x09FEE
|
||||
1624 - Desert Door to Pond Room - 0x0C2C3
|
||||
1627 - Desert Door to Water Levels Room - 0x0A24B
|
||||
1630 - Desert Door to Elevator Room - 0x0C316
|
||||
1633 - Quarry Main Entry 1 - 0x09D6F
|
||||
1636 - Quarry Main Entry 2 - 0x17C07
|
||||
1639 - Quarry Door to Mill - 0x02010
|
||||
1642 - Quarry Mill Side Door - 0x275FF
|
||||
1645 - Quarry Mill Rooftop Shortcut - 0x17CE8
|
||||
1648 - Quarry Mill Stairs - 0x0368A
|
||||
1651 - Quarry Boathouse Boat Staircase - 0x2769B,0x27163
|
||||
1653 - Quarry Boathouse First Barrier - 0x17C50
|
||||
1654 - Quarry Boathouse Shortcut - 0x3865F
|
||||
1656 - Shadows Timed Door - 0x19B24
|
||||
1657 - Shadows Laser Room Right Door - 0x194B2
|
||||
1660 - Shadows Laser Room Left Door - 0x19665
|
||||
1663 - Shadows Barrier to Quarry - 0x19865,0x0A2DF
|
||||
1666 - Shadows Barrier to Ledge - 0x1855B,0x19ADE
|
||||
1669 - Keep Hedge Maze 1 Exit Door - 0x01954
|
||||
1672 - Keep Pressure Plates 1 Exit Door - 0x01BEC
|
||||
1675 - Keep Hedge Maze 2 Shortcut - 0x018CE
|
||||
1678 - Keep Hedge Maze 2 Exit Door - 0x019D8
|
||||
1681 - Keep Hedge Maze 3 Shortcut - 0x019B5
|
||||
1684 - Keep Hedge Maze 3 Exit Door - 0x019E6
|
||||
1687 - Keep Hedge Maze 4 Shortcut - 0x0199A
|
||||
1690 - Keep Hedge Maze 4 Exit Door - 0x01A0E
|
||||
1693 - Keep Pressure Plates 2 Exit Door - 0x01BEA
|
||||
1696 - Keep Pressure Plates 3 Exit Door - 0x01CD5
|
||||
1699 - Keep Pressure Plates 4 Exit Door - 0x01D40
|
||||
1702 - Keep Shortcut to Shadows - 0x09E3D
|
||||
1705 - Keep Tower Shortcut - 0x04F8F
|
||||
1708 - Monastery Shortcut - 0x0364E
|
||||
1711 - Monastery Inner Door - 0x0C128
|
||||
1714 - Monastery Outer Door - 0x0C153
|
||||
1717 - Monastery Door to Garden - 0x03750
|
||||
1718 - Town Cargo Box Door - 0x0A0C9
|
||||
1720 - Town Wooden Roof Staircase - 0x034F5
|
||||
1723 - Town Tinted Door to RGB House - 0x28A61
|
||||
1726 - Town Door to Church - 0x03BB0
|
||||
1729 - Town Maze Staircase - 0x28AA2
|
||||
1732 - Town Windmill Door - 0x1845B
|
||||
1735 - Town RGB House Staircase - 0x2897B
|
||||
1738 - Town Tower Blue Panels Door - 0x27798
|
||||
1741 - Town Tower Lattice Door - 0x27799
|
||||
1744 - Town Tower Environmental Set Door - 0x2779A
|
||||
1747 - Town Tower Wooden Roof Set Door - 0x2779C
|
||||
1750 - Theater Entry Door - 0x17F88
|
||||
1753 - Theater Exit Door Left - 0x0A16D
|
||||
1756 - Theater Exit Door Right - 0x3CCDF
|
||||
1759 - Jungle Bamboo Shortcut to River - 0x3873B
|
||||
1760 - Jungle Popup Wall - 0x1475B
|
||||
1762 - River Shortcut to Monastery Garden - 0x0CF2A
|
||||
1765 - Bunker Bunker Entry Door - 0x0C2A4
|
||||
1768 - Bunker Tinted Glass Door - 0x17C79
|
||||
1771 - Bunker Door to Ultraviolet Room - 0x0C2A3
|
||||
1774 - Bunker Door to Elevator - 0x0A08D
|
||||
1777 - Swamp Entry Door - 0x00C1C
|
||||
1780 - Swamp Door to Broken Shapers - 0x184B7
|
||||
1783 - Swamp Platform Shortcut Door - 0x38AE6
|
||||
1786 - Swamp Cyan Water Pump - 0x04B7F
|
||||
1789 - Swamp Door to Rotated Shapers - 0x18507
|
||||
1792 - Swamp Red Water Pump - 0x183F2
|
||||
1795 - Swamp Red Underwater Exit - 0x305D5
|
||||
1798 - Swamp Blue Water Pump - 0x18482
|
||||
1801 - Swamp Purple Water Pump - 0x0A1D6
|
||||
1804 - Swamp Near Laser Shortcut - 0x2D880
|
||||
1807 - Treehouse First Door - 0x0C309
|
||||
1810 - Treehouse Second Door - 0x0C310
|
||||
1813 - Treehouse Beyond Yellow Bridge Door - 0x0A181
|
||||
1816 - Treehouse Drawbridge - 0x0C32D
|
||||
1819 - Treehouse Timed Door to Laser House - 0x0C323
|
||||
1822 - Inside Mountain First Layer Exit Door - 0x09E54
|
||||
1825 - Inside Mountain Second Layer Staircase Near - 0x09FFB
|
||||
1828 - Inside Mountain Second Layer Exit Door - 0x09EDD
|
||||
1831 - Inside Mountain Second Layer Staircase Far - 0x09E07
|
||||
1834 - Inside Mountain Giant Puzzle Exit Door - 0x09F89
|
||||
1840 - Inside Mountain Door to Final Room - 0x0C141
|
||||
1843 - Inside Mountain Bottom Layer Rock - 0x17F33
|
||||
1846 - Inside Mountain Door to Secret Area - 0x2D77D
|
||||
1849 - Caves Pillar Door - 0x019A5
|
||||
1855 - Caves Swamp Shortcut - 0x2D859
|
||||
1858 - Challenge Entry Door - 0x0A19A
|
||||
1861 - Challenge Door to Theater Walkway - 0x0348A
|
||||
1864 - Theater Walkway Door to Windmill Interior - 0x27739
|
||||
1867 - Theater Walkway Door to Desert Elevator Room - 0x27263
|
||||
1870 - Theater Walkway Door to Town - 0x09E87
|
||||
|
||||
1903 - Outside Tutorial Outpost Doors - 0x03BA2,0x0A170,0x04CA3
|
||||
1906 - Symmetry Island Doors - 0x17F3E,0x18269
|
||||
1909 - Orchard Gates - 0x03313,0x03307
|
||||
1912 - Desert Doors - 0x09FEE,0x0C2C3,0x0A24B,0x0C316
|
||||
1915 - Quarry Main Entry - 0x09D6F
|
||||
1918 - Quarry Mill Shortcuts - 0x17C07,0x17CE8,0x0368A
|
||||
1921 - Quarry Boathouse Barriers - 0x17C50,0x3865F
|
||||
1924 - Shadows Laser Room Door - 0x194B2,0x19665
|
||||
1927 - Shadows Barriers - 0x19865,0x0A2DF,0x1855B,0x19ADE
|
||||
1930 - Keep Hedge Maze Doors - 0x01954,0x018CE,0x019D8,0x019B5,0x019E6,0x0199A,0x01A0E
|
||||
1933 - Keep Pressure Plates Doors - 0x01BEC,0x01BEA,0x01CD5,0x01D40
|
||||
1936 - Keep Shortcuts - 0x09E3D,0x04F8F
|
||||
1939 - Monastery Entry Door - 0x0C128,0x0C153
|
||||
1942 - Monastery Shortcuts - 0x0364E,0x03750
|
||||
1945 - Town Doors - 0x0A0C9,0x034F5,0x28A61,0x03BB0,0x28AA2,0x1845B,0x2897B
|
||||
1948 - Town Tower Doors - 0x27798,0x27799,0x2779A,0x2779C
|
||||
1951 - Theater Exit Door - 0x0A16D,0x3CCDF
|
||||
1954 - Jungle & River Shortcuts - 0x3873B,0x0CF2A
|
||||
1957 - Bunker Doors - 0x0C2A4,0x17C79,0x0C2A3,0x0A08D
|
||||
1960 - Swamp Doors - 0x00C1C,0x184B7,0x38AE6,0x18507
|
||||
1963 - Swamp Water Pumps - 0x04B7F,0x183F2,0x305D5,0x18482,0x0A1D6
|
||||
1966 - Treehouse Entry Doors - 0x0C309,0x0C310,0x0A181
|
||||
1975 - Inside Mountain Second Layer Stairs & Doors - 0x09FFB,0x09EDD,0x09E07
|
||||
1978 - Inside Mountain Bottom Layer Doors to Caves - 0x17F33,0x2D77D
|
||||
1981 - Caves Doors to Challenge - 0x019A5,0x0A19A
|
||||
1984 - Caves Exits to Main Island - 0x2D859,0x2D73F
|
||||
1987 - Theater Walkway Doors - 0x27739,0x27263,0x09E87
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,7 +35,7 @@ class WitnessWorld(World):
|
||||
"""
|
||||
game = "The Witness"
|
||||
topology_present = False
|
||||
data_version = 2
|
||||
data_version = 5
|
||||
|
||||
static_logic = StaticWitnessLogic()
|
||||
static_locat = StaticWitnessLocations()
|
||||
@@ -53,11 +53,18 @@ class WitnessWorld(World):
|
||||
'seed': self.world.random.randint(0, 1000000),
|
||||
'victory_location': int(self.player_logic.VICTORY_LOCATION, 16),
|
||||
'panelhex_to_id': self.locat.CHECK_PANELHEX_TO_ID,
|
||||
'doorhex_to_id': self.player_logic.DOOR_DICT_FOR_CLIENT,
|
||||
'door_connections_to_sever': self.player_logic.DOOR_CONNECTIONS_TO_SEVER
|
||||
'item_id_to_door_hexes': self.items.ITEM_ID_TO_DOOR_HEX,
|
||||
'door_hexes': self.items.DOORS,
|
||||
'symbols_not_in_the_game': self.items.SYMBOLS_NOT_IN_THE_GAME
|
||||
}
|
||||
|
||||
def generate_early(self):
|
||||
if not (is_option_enabled(self.world, self.player, "shuffle_symbols")
|
||||
or get_option_value(self.world, self.player, "shuffle_doors")
|
||||
or is_option_enabled(self.world, self.player, "shuffle_lasers")):
|
||||
raise Exception("This Witness world doesn't have any progression items. Please turn on Symbol Shuffle, Door"
|
||||
" Shuffle or Laser Shuffle")
|
||||
|
||||
self.player_logic = WitnessPlayerLogic(self.world, self.player)
|
||||
self.locat = WitnessPlayerLocations(self.world, self.player, self.player_logic)
|
||||
self.items = WitnessPlayerItems(self.locat, self.world, self.player, self.player_logic)
|
||||
@@ -78,11 +85,11 @@ class WitnessWorld(World):
|
||||
less_junk = 0
|
||||
|
||||
# Put good item on first check if symbol shuffle is on
|
||||
# symbols = is_option_enabled(self.world, self.player, "shuffle_symbols")
|
||||
symbols = True
|
||||
symbols = is_option_enabled(self.world, self.player, "shuffle_symbols")
|
||||
|
||||
if symbols:
|
||||
random_good_item = self.world.random.choice(self.items.GOOD_ITEMS)
|
||||
|
||||
first_check = self.world.get_location(
|
||||
"Tutorial Gate Open", self.player
|
||||
)
|
||||
@@ -91,6 +98,10 @@ class WitnessWorld(World):
|
||||
|
||||
less_junk = 1
|
||||
|
||||
for item in self.player_logic.STARTING_INVENTORY:
|
||||
self.world.push_precollected(items_by_name[item])
|
||||
pool.remove(items_by_name[item])
|
||||
|
||||
for item in self.items.EXTRA_AMOUNTS:
|
||||
witness_item = self.create_item(item)
|
||||
for i in range(0, self.items.EXTRA_AMOUNTS[item]):
|
||||
|
||||
@@ -51,12 +51,15 @@ class StaticWitnessItems:
|
||||
def __init__(self):
|
||||
item_tab = dict()
|
||||
|
||||
for item in StaticWitnessLogic.ALL_SYMBOL_ITEMS.union(StaticWitnessLogic.ALL_DOOR_ITEMS):
|
||||
for item in StaticWitnessLogic.ALL_SYMBOL_ITEMS:
|
||||
if item[0] == "11 Lasers" or item == "7 Lasers":
|
||||
continue
|
||||
|
||||
item_tab[item[0]] = ItemData(158000 + item[1], True, False)
|
||||
|
||||
for item in StaticWitnessLogic.ALL_DOOR_ITEMS:
|
||||
item_tab[item[0]] = ItemData(158000 + item[1], True, False)
|
||||
|
||||
for item in StaticWitnessLogic.ALL_TRAPS:
|
||||
item_tab[item[0]] = ItemData(
|
||||
158000 + item[1], False, False, True
|
||||
@@ -89,23 +92,39 @@ class WitnessPlayerItems:
|
||||
self.ITEM_TABLE = copy.copy(StaticWitnessItems.ALL_ITEM_TABLE)
|
||||
self.PROGRESSION_TABLE = dict()
|
||||
|
||||
self.ITEM_ID_TO_DOOR_HEX = dict()
|
||||
self.DOORS = set()
|
||||
|
||||
self.SYMBOLS_NOT_IN_THE_GAME = set()
|
||||
|
||||
self.EXTRA_AMOUNTS = {
|
||||
"Functioning Brain": 1,
|
||||
"Puzzle Skip": get_option_value(world, player, "puzzle_skip_amount")
|
||||
}
|
||||
|
||||
for item in StaticWitnessLogic.ALL_SYMBOL_ITEMS.union(StaticWitnessLogic.ALL_DOOR_ITEMS):
|
||||
if item not in player_logic.PROG_ITEMS_ACTUALLY_IN_THE_GAME:
|
||||
if item[0] not in player_logic.PROG_ITEMS_ACTUALLY_IN_THE_GAME:
|
||||
del self.ITEM_TABLE[item[0]]
|
||||
if item in StaticWitnessLogic.ALL_SYMBOL_ITEMS:
|
||||
self.SYMBOLS_NOT_IN_THE_GAME.add(StaticWitnessItems.ALL_ITEM_TABLE[item[0]].code)
|
||||
else:
|
||||
self.PROGRESSION_TABLE[item[0]] = self.ITEM_TABLE[item[0]]
|
||||
|
||||
for entity_hex, items in player_logic.DOOR_ITEMS_BY_ID.items():
|
||||
entity_hex_int = int(entity_hex, 16)
|
||||
|
||||
self.DOORS.add(entity_hex_int)
|
||||
|
||||
for item in items:
|
||||
item_id = StaticWitnessItems.ALL_ITEM_TABLE[item].code
|
||||
self.ITEM_ID_TO_DOOR_HEX.setdefault(item_id, set()).add(entity_hex_int)
|
||||
|
||||
symbols = is_option_enabled(world, player, "shuffle_symbols")
|
||||
|
||||
if "shuffle_symbols" not in the_witness_options.keys():
|
||||
symbols = True
|
||||
|
||||
doors = is_option_enabled(world, player, "shuffle_doors")
|
||||
doors = get_option_value(world, player, "shuffle_doors")
|
||||
|
||||
if doors and symbols:
|
||||
self.GOOD_ITEMS = [
|
||||
@@ -117,10 +136,10 @@ class WitnessPlayerItems:
|
||||
"Shapers", "Symmetry"
|
||||
]
|
||||
|
||||
if is_option_enabled(world, player, "shuffle_discarded_panels"):
|
||||
self.GOOD_ITEMS.append("Triangles")
|
||||
if not is_option_enabled(world, player, "disable_non_randomized_puzzles"):
|
||||
self.GOOD_ITEMS.append("Colored Squares")
|
||||
if is_option_enabled(world, player, "shuffle_discarded_panels"):
|
||||
self.GOOD_ITEMS.append("Triangles")
|
||||
if not is_option_enabled(world, player, "disable_non_randomized_puzzles"):
|
||||
self.GOOD_ITEMS.append("Colored Squares")
|
||||
|
||||
for event_location in locat.EVENT_LOCATION_TABLE:
|
||||
location = player_logic.EVENT_ITEM_PAIRS[event_location]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Defines constants for different types of locations in the game
|
||||
"""
|
||||
|
||||
from .Options import is_option_enabled
|
||||
from .Options import is_option_enabled, get_option_value
|
||||
from .player_logic import StaticWitnessLogic, WitnessPlayerLogic
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class StaticWitnessLocations:
|
||||
"Symmetry Island Colored Dots 6",
|
||||
"Symmetry Island Fading Lines 7",
|
||||
"Symmetry Island Scenery Outlines 5",
|
||||
"Symmetry Island Laser",
|
||||
"Symmetry Island Laser Panel",
|
||||
|
||||
"Orchard Apple Tree 5",
|
||||
|
||||
@@ -52,7 +52,7 @@ class StaticWitnessLocations:
|
||||
"Desert Artificial Light Reflection 3",
|
||||
"Desert Pond Reflection 5",
|
||||
"Desert Flood Reflection 6",
|
||||
"Desert Laser",
|
||||
"Desert Laser Panel",
|
||||
|
||||
"Quarry Mill Eraser and Dots 6",
|
||||
"Quarry Mill Eraser and Squares 8",
|
||||
@@ -63,34 +63,34 @@ class StaticWitnessLocations:
|
||||
"Quarry Boathouse Stars & Eraser & Shapers 2",
|
||||
"Quarry Boathouse Stars & Eraser & Shapers 5",
|
||||
"Quarry Discard",
|
||||
"Quarry Laser",
|
||||
"Quarry Laser Panel",
|
||||
|
||||
"Shadows Lower Avoid 8",
|
||||
"Shadows Environmental Avoid 8",
|
||||
"Shadows Follow 5",
|
||||
"Shadows Laser",
|
||||
"Shadows Laser Panel",
|
||||
|
||||
"Keep Hedge Maze 4",
|
||||
"Keep Pressure Plates 4",
|
||||
"Keep Discard",
|
||||
"Keep Laser Hedges",
|
||||
"Keep Laser Pressure Plates",
|
||||
"Keep Laser Panel Hedges",
|
||||
"Keep Laser Panel Pressure Plates",
|
||||
|
||||
"Shipwreck Vault Box",
|
||||
"Shipwreck Discard",
|
||||
|
||||
"Monastery Rhombic Avoid 3",
|
||||
"Monastery Branch Follow 2",
|
||||
"Monastery Laser",
|
||||
"Monastery Laser Panel",
|
||||
|
||||
"Town Cargo Box Discard",
|
||||
"Town Hexagonal Reflection",
|
||||
"Town Square Avoid",
|
||||
"Town Church Lattice",
|
||||
"Town Rooftop Discard",
|
||||
"Town Symmetry Squares 5 + Dots",
|
||||
"Town Full Dot Grid Shapers 5",
|
||||
"Town Shapers & Dots & Eraser",
|
||||
"Town Laser",
|
||||
"Town Laser Panel",
|
||||
|
||||
"Theater Discard",
|
||||
|
||||
@@ -98,7 +98,7 @@ class StaticWitnessLocations:
|
||||
"Jungle Waves 3",
|
||||
"Jungle Waves 7",
|
||||
"Jungle Popup Wall 6",
|
||||
"Jungle Laser",
|
||||
"Jungle Laser Panel",
|
||||
|
||||
"River Vault Box",
|
||||
|
||||
@@ -106,7 +106,7 @@ class StaticWitnessLocations:
|
||||
"Bunker Drawn Squares 9",
|
||||
"Bunker Drawn Squares through Tinted Glass 3",
|
||||
"Bunker Drop-Down Door Squares 2",
|
||||
"Bunker Laser",
|
||||
"Bunker Laser Panel",
|
||||
|
||||
"Swamp Seperatable Shapers 6",
|
||||
"Swamp Combinable Shapers 8",
|
||||
@@ -117,7 +117,7 @@ class StaticWitnessLocations:
|
||||
"Swamp Red Underwater Negative Shapers 4",
|
||||
"Swamp More Rotated Shapers 4",
|
||||
"Swamp Blue Underwater Negative Shapers 5",
|
||||
"Swamp Laser",
|
||||
"Swamp Laser Panel",
|
||||
|
||||
"Treehouse Yellow Bridge 9",
|
||||
"Treehouse First Purple Bridge 5",
|
||||
@@ -125,21 +125,12 @@ class StaticWitnessLocations:
|
||||
"Treehouse Green Bridge 7",
|
||||
"Treehouse Green Bridge Discard",
|
||||
"Treehouse Left Orange Bridge 15",
|
||||
"Treehouse Burned House Discard",
|
||||
"Treehouse Burnt House Discard",
|
||||
"Treehouse Right Orange Bridge 12",
|
||||
"Treehouse Laser",
|
||||
"Treehouse Laser Panel",
|
||||
|
||||
"Mountaintop Discard",
|
||||
"Mountaintop Vault Box",
|
||||
|
||||
"Inside Mountain Obscured Vision 5",
|
||||
"Inside Mountain Moving Background 7",
|
||||
"Inside Mountain Physically Obstructed 3",
|
||||
"Inside Mountain Angled Inside Trash 2",
|
||||
"Inside Mountain Color Cycle 5",
|
||||
"Inside Mountain Same Solution 6",
|
||||
"Inside Mountain Elevator Discard",
|
||||
"Inside Mountain Giant Puzzle",
|
||||
}
|
||||
|
||||
UNCOMMON_LOCATIONS = {
|
||||
@@ -156,35 +147,53 @@ class StaticWitnessLocations:
|
||||
"Swamp Underwater Back Optional",
|
||||
}
|
||||
|
||||
HARD_LOCATIONS = {
|
||||
"Inside Mountain Secret Area Dot Grid Triangles 4",
|
||||
"Inside Mountain Secret Area Symmetry Triangles",
|
||||
"Inside Mountain Secret Area Stars & Squares and Triangles 2",
|
||||
"Inside Mountain Secret Area Shapers and Triangles 2",
|
||||
"Inside Mountain Secret Area Symmetry Shapers",
|
||||
"Inside Mountain Secret Area Broken and Negative Shapers",
|
||||
"Inside Mountain Secret Area Broken Shapers",
|
||||
CAVES_LOCATIONS = {
|
||||
"Inside Mountain Caves Dot Grid Triangles 4",
|
||||
"Inside Mountain Caves Symmetry Triangles",
|
||||
"Inside Mountain Caves Stars & Squares and Triangles 2",
|
||||
"Inside Mountain Caves Shapers and Triangles 2",
|
||||
"Inside Mountain Caves Symmetry Shapers",
|
||||
"Inside Mountain Caves Broken and Negative Shapers",
|
||||
"Inside Mountain Caves Broken Shapers",
|
||||
|
||||
"Inside Mountain Secret Area Rainbow Squares",
|
||||
"Inside Mountain Secret Area Squares & Stars and Colored Eraser",
|
||||
"Inside Mountain Secret Area Rotated Broken Shapers",
|
||||
"Inside Mountain Secret Area Stars and Squares",
|
||||
"Inside Mountain Secret Area Lone Pillar",
|
||||
"Inside Mountain Secret Area Wooden Beam Shapers",
|
||||
"Inside Mountain Secret Area Wooden Beam Squares and Shapers",
|
||||
"Inside Mountain Secret Area Wooden Beam Stars and Squares",
|
||||
"Inside Mountain Secret Area Wooden Beam Shapers and Stars",
|
||||
"Inside Mountain Secret Area Upstairs Invisible Dots 8",
|
||||
"Inside Mountain Secret Area Upstairs Invisible Dot Symmetry 3",
|
||||
"Inside Mountain Secret Area Upstairs Dot Grid Negative Shapers",
|
||||
"Inside Mountain Secret Area Upstairs Dot Grid Rotated Shapers",
|
||||
"Inside Mountain Caves Rainbow Squares",
|
||||
"Inside Mountain Caves Squares & Stars and Colored Eraser",
|
||||
"Inside Mountain Caves Rotated Broken Shapers",
|
||||
"Inside Mountain Caves Stars and Squares",
|
||||
"Inside Mountain Caves Lone Pillar",
|
||||
"Inside Mountain Caves Wooden Beam Shapers",
|
||||
"Inside Mountain Caves Wooden Beam Squares and Shapers",
|
||||
"Inside Mountain Caves Wooden Beam Stars and Squares",
|
||||
"Inside Mountain Caves Wooden Beam Shapers and Stars",
|
||||
"Inside Mountain Caves Upstairs Invisible Dots 8",
|
||||
"Inside Mountain Caves Upstairs Invisible Dot Symmetry 3",
|
||||
"Inside Mountain Caves Upstairs Dot Grid Negative Shapers",
|
||||
"Inside Mountain Caves Upstairs Dot Grid Rotated Shapers",
|
||||
|
||||
"Challenge Vault Box",
|
||||
"Theater Walkway Vault Box",
|
||||
"Inside Mountain Bottom Layer Discard",
|
||||
"Theater Challenge Video",
|
||||
}
|
||||
|
||||
MOUNTAIN_UNREACHABLE_FROM_BEHIND = {
|
||||
"Mountaintop Trap Door Triple Exit",
|
||||
|
||||
"Inside Mountain Obscured Vision 5",
|
||||
"Inside Mountain Moving Background 7",
|
||||
"Inside Mountain Physically Obstructed 3",
|
||||
"Inside Mountain Angled Inside Trash 2",
|
||||
"Inside Mountain Color Cycle 5",
|
||||
"Inside Mountain Same Solution 6",
|
||||
}
|
||||
|
||||
MOUNTAIN_REACHABLE_FROM_BEHIND = {
|
||||
"Inside Mountain Elevator Discard",
|
||||
"Inside Mountain Giant Puzzle",
|
||||
|
||||
"Inside Mountain Final Room Left Pillar 4",
|
||||
"Inside Mountain Final Room Right Pillar 4",
|
||||
}
|
||||
|
||||
ALL_LOCATIONS_TO_ID = dict()
|
||||
|
||||
@staticmethod
|
||||
@@ -193,12 +202,7 @@ class StaticWitnessLocations:
|
||||
Calculates the location ID for any given location
|
||||
"""
|
||||
|
||||
panel_offset = StaticWitnessLogic.CHECKS_BY_HEX[chex]["idOffset"]
|
||||
type_offset = StaticWitnessLocations.TYPE_OFFSETS[
|
||||
StaticWitnessLogic.CHECKS_BY_HEX[chex]["panelType"]
|
||||
]
|
||||
|
||||
return StaticWitnessLocations.ID_START + panel_offset + type_offset
|
||||
return StaticWitnessLogic.CHECKS_BY_HEX[chex]["id"]
|
||||
|
||||
@staticmethod
|
||||
def get_event_name(panel_hex):
|
||||
@@ -213,6 +217,7 @@ class StaticWitnessLocations:
|
||||
all_loc_to_id = {
|
||||
panel_obj["checkName"]: self.get_id(chex)
|
||||
for chex, panel_obj in StaticWitnessLogic.CHECKS_BY_HEX.items()
|
||||
if panel_obj["id"]
|
||||
}
|
||||
|
||||
all_loc_to_id = dict(
|
||||
@@ -229,12 +234,34 @@ class WitnessPlayerLocations:
|
||||
"""
|
||||
|
||||
def __init__(self, world, player, player_logic: WitnessPlayerLogic):
|
||||
"""Defines locations AFTER logic changes due to options"""
|
||||
|
||||
self.PANEL_TYPES_TO_SHUFFLE = {"General", "Laser"}
|
||||
self.CHECK_LOCATIONS = (
|
||||
StaticWitnessLocations.GENERAL_LOCATIONS
|
||||
)
|
||||
|
||||
"""Defines locations AFTER logic changes due to options"""
|
||||
doors = get_option_value(world, player, "shuffle_doors")
|
||||
earlyutm = is_option_enabled(world, player, "early_secret_area")
|
||||
victory = get_option_value(world, player, "victory_condition")
|
||||
lasers = get_option_value(world, player, "challenge_lasers")
|
||||
laser_shuffle = get_option_value(world, player, "shuffle_lasers")
|
||||
|
||||
postgame = set()
|
||||
postgame = postgame | StaticWitnessLocations.CAVES_LOCATIONS
|
||||
postgame = postgame | StaticWitnessLocations.MOUNTAIN_REACHABLE_FROM_BEHIND
|
||||
postgame = postgame | StaticWitnessLocations.MOUNTAIN_UNREACHABLE_FROM_BEHIND
|
||||
|
||||
self.CHECK_LOCATIONS = self.CHECK_LOCATIONS | postgame
|
||||
|
||||
if earlyutm or doors >= 2 or (victory == 1 and (lasers <= 11 or laser_shuffle)):
|
||||
postgame -= StaticWitnessLocations.CAVES_LOCATIONS
|
||||
|
||||
if doors >= 2:
|
||||
postgame -= StaticWitnessLocations.MOUNTAIN_REACHABLE_FROM_BEHIND
|
||||
|
||||
if victory != 2:
|
||||
postgame -= StaticWitnessLocations.MOUNTAIN_UNREACHABLE_FROM_BEHIND
|
||||
|
||||
if is_option_enabled(world, player, "shuffle_discarded_panels"):
|
||||
self.PANEL_TYPES_TO_SHUFFLE.add("Discard")
|
||||
@@ -245,18 +272,11 @@ class WitnessPlayerLocations:
|
||||
if is_option_enabled(world, player, "shuffle_uncommon"):
|
||||
self.CHECK_LOCATIONS = self.CHECK_LOCATIONS | StaticWitnessLocations.UNCOMMON_LOCATIONS
|
||||
|
||||
if is_option_enabled(world, player, "shuffle_hard"):
|
||||
self.CHECK_LOCATIONS = self.CHECK_LOCATIONS | StaticWitnessLocations.HARD_LOCATIONS
|
||||
|
||||
if is_option_enabled(world, player, "shuffle_symbols") and is_option_enabled(world, player, "shuffle_doors"):
|
||||
if is_option_enabled(world, player, "disable_non_randomized_puzzles"):
|
||||
# This particular combination of logic settings leads to logic so restrictive that generation can fail
|
||||
# Hence, we add some extra sphere 0 locations
|
||||
|
||||
self.CHECK_LOCATIONS = self.CHECK_LOCATIONS | StaticWitnessLocations.EXTRA_LOCATIONS
|
||||
|
||||
self.CHECK_LOCATIONS = self.CHECK_LOCATIONS | player_logic.ADDED_CHECKS
|
||||
|
||||
if not is_option_enabled(world, player, "shuffle_postgame"):
|
||||
self.CHECK_LOCATIONS -= postgame
|
||||
|
||||
self.CHECK_LOCATIONS = self.CHECK_LOCATIONS - {
|
||||
StaticWitnessLogic.CHECKS_BY_HEX[check_hex]["checkName"]
|
||||
for check_hex in player_logic.COMPLETELY_DISABLED_CHECKS
|
||||
@@ -272,7 +292,7 @@ class WitnessPlayerLocations:
|
||||
)
|
||||
|
||||
event_locations = {
|
||||
p for p in player_logic.NECESSARY_EVENT_PANELS
|
||||
p for p in player_logic.EVENT_PANELS
|
||||
}
|
||||
|
||||
self.EVENT_LOCATION_TABLE = {
|
||||
|
||||
@@ -18,22 +18,15 @@ When the world has parsed its options, a second function is called to finalize t
|
||||
import copy
|
||||
from BaseClasses import MultiWorld
|
||||
from .static_logic import StaticWitnessLogic
|
||||
from .utils import define_new_region, get_disable_unrandomized_list, parse_lambda, get_early_utm_list
|
||||
from .utils import define_new_region, get_disable_unrandomized_list, parse_lambda, get_early_utm_list, \
|
||||
get_symbol_shuffle_list, get_door_panel_shuffle_list, get_doors_complex_list, get_doors_max_list, \
|
||||
get_doors_simple_list, get_laser_shuffle
|
||||
from .Options import is_option_enabled, get_option_value, the_witness_options
|
||||
|
||||
|
||||
class WitnessPlayerLogic:
|
||||
"""WITNESS LOGIC CLASS"""
|
||||
|
||||
def update_door_dict(self, panel_hex):
|
||||
item_id = StaticWitnessLogic.ALL_DOOR_ITEM_IDS_BY_HEX.get(panel_hex)
|
||||
|
||||
if item_id is None:
|
||||
return
|
||||
|
||||
self.DOOR_DICT_FOR_CLIENT[panel_hex] = item_id
|
||||
self.DOOR_CONNECTIONS_TO_SEVER.update(StaticWitnessLogic.CONNECTIONS_TO_SEVER_BY_DOOR_HEX[panel_hex])
|
||||
|
||||
def reduce_req_within_region(self, panel_hex):
|
||||
"""
|
||||
Panels in this game often only turn on when other panels are solved.
|
||||
@@ -43,35 +36,42 @@ class WitnessPlayerLogic:
|
||||
Panels outside of the same region will still be checked manually.
|
||||
"""
|
||||
|
||||
these_items = self.DEPENDENT_REQUIREMENTS_BY_HEX[panel_hex]["items"]
|
||||
check_obj = StaticWitnessLogic.CHECKS_BY_HEX[panel_hex]
|
||||
|
||||
real_items = {item[0] for item in self.PROG_ITEMS_ACTUALLY_IN_THE_GAME}
|
||||
these_items = frozenset({frozenset()})
|
||||
|
||||
if check_obj["id"]:
|
||||
these_items = self.DEPENDENT_REQUIREMENTS_BY_HEX[panel_hex]["items"]
|
||||
|
||||
these_items = frozenset({
|
||||
subset.intersection(real_items)
|
||||
subset.intersection(self.PROG_ITEMS_ACTUALLY_IN_THE_GAME)
|
||||
for subset in these_items
|
||||
})
|
||||
|
||||
if panel_hex in self.DOOR_ITEMS_BY_ID:
|
||||
door_items = frozenset({frozenset([item]) for item in self.DOOR_ITEMS_BY_ID[panel_hex]})
|
||||
|
||||
all_options = set()
|
||||
|
||||
for items_option in these_items:
|
||||
for dependentItem in door_items:
|
||||
all_options.add(items_option.union(dependentItem))
|
||||
|
||||
return frozenset(all_options)
|
||||
|
||||
these_panels = self.DEPENDENT_REQUIREMENTS_BY_HEX[panel_hex]["panels"]
|
||||
|
||||
if StaticWitnessLogic.DOOR_NAMES_BY_HEX.get(panel_hex) in real_items:
|
||||
self.update_door_dict(panel_hex)
|
||||
|
||||
these_panels = frozenset({frozenset()})
|
||||
|
||||
if these_panels == frozenset({frozenset()}):
|
||||
return these_items
|
||||
|
||||
all_options = set()
|
||||
|
||||
check_obj = StaticWitnessLogic.CHECKS_BY_HEX[panel_hex]
|
||||
|
||||
for option in these_panels:
|
||||
dependent_items_for_option = frozenset({frozenset()})
|
||||
|
||||
for option_panel in option:
|
||||
new_items = set()
|
||||
dep_obj = StaticWitnessLogic.CHECKS_BY_HEX.get(option_panel)
|
||||
|
||||
if option_panel in {"7 Lasers", "11 Lasers"}:
|
||||
new_items = frozenset({frozenset([option_panel])})
|
||||
# If a panel turns on when a panel in a different region turns on,
|
||||
@@ -101,8 +101,34 @@ class WitnessPlayerLogic:
|
||||
return frozenset(all_options)
|
||||
|
||||
def make_single_adjustment(self, adj_type, line):
|
||||
from . import StaticWitnessItems
|
||||
"""Makes a single logic adjustment based on additional logic file"""
|
||||
|
||||
if adj_type == "Items":
|
||||
if line not in StaticWitnessItems.ALL_ITEM_TABLE:
|
||||
raise RuntimeError("Item \"" + line + "\" does not exit.")
|
||||
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME.add(line)
|
||||
|
||||
if line in StaticWitnessLogic.ALL_DOOR_ITEMS_AS_DICT:
|
||||
panel_hexes = StaticWitnessLogic.ALL_DOOR_ITEMS_AS_DICT[line][2]
|
||||
for panel_hex in panel_hexes:
|
||||
self.DOOR_ITEMS_BY_ID.setdefault(panel_hex, set()).add(line)
|
||||
|
||||
return
|
||||
|
||||
if adj_type == "Remove Items":
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME.discard(line)
|
||||
|
||||
if line in StaticWitnessLogic.ALL_DOOR_ITEMS_AS_DICT:
|
||||
panel_hexes = StaticWitnessLogic.ALL_DOOR_ITEMS_AS_DICT[line][2]
|
||||
for panel_hex in panel_hexes:
|
||||
if panel_hex in self.DOOR_ITEMS_BY_ID:
|
||||
self.DOOR_ITEMS_BY_ID[panel_hex].discard(line)
|
||||
|
||||
if adj_type == "Starting Inventory":
|
||||
self.STARTING_INVENTORY.add(line)
|
||||
|
||||
if adj_type == "Event Items":
|
||||
line_split = line.split(" - ")
|
||||
hex_set = line_split[1].split(",")
|
||||
@@ -130,18 +156,20 @@ class WitnessPlayerLogic:
|
||||
if adj_type == "Requirement Changes":
|
||||
line_split = line.split(" - ")
|
||||
|
||||
required_items = parse_lambda(line_split[2])
|
||||
items_actually_in_the_game = {item[0] for item in StaticWitnessLogic.ALL_SYMBOL_ITEMS}
|
||||
required_items = frozenset(
|
||||
subset.intersection(items_actually_in_the_game)
|
||||
for subset in required_items
|
||||
)
|
||||
|
||||
requirement = {
|
||||
"panels": parse_lambda(line_split[1]),
|
||||
"items": required_items
|
||||
}
|
||||
|
||||
if len(line_split) > 2:
|
||||
required_items = parse_lambda(line_split[2])
|
||||
items_actually_in_the_game = {item[0] for item in StaticWitnessLogic.ALL_SYMBOL_ITEMS}
|
||||
required_items = frozenset(
|
||||
subset.intersection(items_actually_in_the_game)
|
||||
for subset in required_items
|
||||
)
|
||||
|
||||
requirement["items"] = required_items
|
||||
|
||||
self.DEPENDENT_REQUIREMENTS_BY_HEX[line_split[0]] = requirement
|
||||
|
||||
return
|
||||
@@ -151,11 +179,6 @@ class WitnessPlayerLogic:
|
||||
|
||||
self.COMPLETELY_DISABLED_CHECKS.add(panel_hex)
|
||||
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME = {
|
||||
item for item in self.PROG_ITEMS_ACTUALLY_IN_THE_GAME
|
||||
if item[0] != StaticWitnessLogic.DOOR_NAMES_BY_HEX.get(panel_hex)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
if adj_type == "Region Changes":
|
||||
@@ -189,18 +212,25 @@ class WitnessPlayerLogic:
|
||||
adjustment_linesets_in_order.append(get_disable_unrandomized_list())
|
||||
|
||||
if is_option_enabled(world, player, "shuffle_symbols") or "shuffle_symbols" not in the_witness_options.keys():
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME.update(StaticWitnessLogic.ALL_SYMBOL_ITEMS)
|
||||
adjustment_linesets_in_order.append(get_symbol_shuffle_list())
|
||||
|
||||
if is_option_enabled(world, player, "shuffle_doors"):
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME.update(StaticWitnessLogic.ALL_DOOR_ITEMS)
|
||||
if get_option_value(world, player, "shuffle_doors") == 1:
|
||||
adjustment_linesets_in_order.append(get_door_panel_shuffle_list())
|
||||
|
||||
if get_option_value(world, player, "shuffle_doors") == 2:
|
||||
adjustment_linesets_in_order.append(get_doors_simple_list())
|
||||
|
||||
if get_option_value(world, player, "shuffle_doors") == 3:
|
||||
adjustment_linesets_in_order.append(get_doors_complex_list())
|
||||
|
||||
if get_option_value(world, player, "shuffle_doors") == 4:
|
||||
adjustment_linesets_in_order.append(get_doors_max_list())
|
||||
|
||||
if is_option_enabled(world, player, "early_secret_area"):
|
||||
adjustment_linesets_in_order.append(get_early_utm_list())
|
||||
else:
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME = {
|
||||
item for item in self.PROG_ITEMS_ACTUALLY_IN_THE_GAME
|
||||
if item[0] != "Mountaintop River Shape Power On"
|
||||
}
|
||||
|
||||
if is_option_enabled(world, player, "shuffle_lasers"):
|
||||
adjustment_linesets_in_order.append(get_laser_shuffle())
|
||||
|
||||
for adjustment_lineset in adjustment_linesets_in_order:
|
||||
current_adjustment_type = None
|
||||
@@ -233,62 +263,32 @@ class WitnessPlayerLogic:
|
||||
pair = (name, self.EVENT_ITEM_NAMES[panel])
|
||||
return pair
|
||||
|
||||
def _regions_are_adjacent(self, region1, region2):
|
||||
for connection in self.CONNECTIONS_BY_REGION_NAME[region1]:
|
||||
if connection[0] == region2:
|
||||
return True
|
||||
|
||||
for connection in self.CONNECTIONS_BY_REGION_NAME[region2]:
|
||||
if connection[0] == region1:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def make_event_panel_lists(self):
|
||||
"""
|
||||
Special event panel data structures
|
||||
"""
|
||||
|
||||
for region_conn in self.CONNECTIONS_BY_REGION_NAME.values():
|
||||
for region_and_option in region_conn:
|
||||
for panelset in region_and_option[1]:
|
||||
for panel in panelset:
|
||||
self.EVENT_PANELS_FROM_REGIONS.add(panel)
|
||||
|
||||
self.ALWAYS_EVENT_NAMES_BY_HEX[self.VICTORY_LOCATION] = "Victory"
|
||||
|
||||
self.ORIGINAL_EVENT_PANELS.update(self.EVENT_PANELS_FROM_PANELS)
|
||||
self.ORIGINAL_EVENT_PANELS.update(self.EVENT_PANELS_FROM_REGIONS)
|
||||
for region_name, connections in self.CONNECTIONS_BY_REGION_NAME.items():
|
||||
for connection in connections:
|
||||
for panel_req in connection[1]:
|
||||
for panel in panel_req:
|
||||
if panel == "TrueOneWay":
|
||||
continue
|
||||
|
||||
for panel in self.EVENT_PANELS_FROM_REGIONS:
|
||||
for region_name, region in StaticWitnessLogic.ALL_REGIONS_BY_NAME.items():
|
||||
for connection in self.CONNECTIONS_BY_REGION_NAME[region_name]:
|
||||
connected_r = connection[0]
|
||||
if connected_r not in StaticWitnessLogic.ALL_REGIONS_BY_NAME:
|
||||
continue
|
||||
if region_name == "Boat" or connected_r == "Boat":
|
||||
continue
|
||||
connected_r = StaticWitnessLogic.ALL_REGIONS_BY_NAME[connected_r]
|
||||
if not any([panel in option for option in connection[1]]):
|
||||
continue
|
||||
if panel not in region["panels"] | connected_r["panels"]:
|
||||
self.NECESSARY_EVENT_PANELS.add(panel)
|
||||
if StaticWitnessLogic.CHECKS_BY_HEX[panel]["region"]["name"] != region_name:
|
||||
self.EVENT_PANELS_FROM_REGIONS.add(panel)
|
||||
|
||||
for event_panel in self.EVENT_PANELS_FROM_PANELS:
|
||||
for panel, panel_req in self.REQUIREMENTS_BY_HEX.items():
|
||||
if any([event_panel in item_set for item_set in panel_req]):
|
||||
region1 = StaticWitnessLogic.CHECKS_BY_HEX[panel]["region"]["name"]
|
||||
region2 = StaticWitnessLogic.CHECKS_BY_HEX[event_panel]["region"]["name"]
|
||||
|
||||
if not self._regions_are_adjacent(region1, region2):
|
||||
self.NECESSARY_EVENT_PANELS.add(event_panel)
|
||||
self.EVENT_PANELS.update(self.EVENT_PANELS_FROM_PANELS)
|
||||
self.EVENT_PANELS.update(self.EVENT_PANELS_FROM_REGIONS)
|
||||
|
||||
for always_hex, always_item in self.ALWAYS_EVENT_NAMES_BY_HEX.items():
|
||||
self.ALWAYS_EVENT_HEX_CODES.add(always_hex)
|
||||
self.NECESSARY_EVENT_PANELS.add(always_hex)
|
||||
self.EVENT_PANELS.add(always_hex)
|
||||
self.EVENT_ITEM_NAMES[always_hex] = always_item
|
||||
|
||||
for panel in self.NECESSARY_EVENT_PANELS:
|
||||
for panel in self.EVENT_PANELS:
|
||||
pair = self.make_event_item_pair(panel)
|
||||
self.EVENT_ITEM_PAIRS[pair[0]] = pair[1]
|
||||
|
||||
@@ -297,8 +297,8 @@ class WitnessPlayerLogic:
|
||||
self.EVENT_PANELS_FROM_REGIONS = set()
|
||||
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME = set()
|
||||
self.DOOR_DICT_FOR_CLIENT = dict()
|
||||
self.DOOR_CONNECTIONS_TO_SEVER = set()
|
||||
self.DOOR_ITEMS_BY_ID = dict()
|
||||
self.STARTING_INVENTORY = set()
|
||||
|
||||
self.CONNECTIONS_BY_REGION_NAME = copy.copy(StaticWitnessLogic.STATIC_CONNECTIONS_BY_REGION_NAME)
|
||||
self.DEPENDENT_REQUIREMENTS_BY_HEX = copy.copy(StaticWitnessLogic.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX)
|
||||
@@ -306,8 +306,7 @@ class WitnessPlayerLogic:
|
||||
|
||||
# Determining which panels need to be events is a difficult process.
|
||||
# At the end, we will have EVENT_ITEM_PAIRS for all the necessary ones.
|
||||
self.ORIGINAL_EVENT_PANELS = set()
|
||||
self.NECESSARY_EVENT_PANELS = set()
|
||||
self.EVENT_PANELS = set()
|
||||
self.EVENT_ITEM_PAIRS = dict()
|
||||
self.ALWAYS_EVENT_HEX_CODES = set()
|
||||
self.COMPLETELY_DISABLED_CHECKS = set()
|
||||
@@ -320,42 +319,63 @@ class WitnessPlayerLogic:
|
||||
"0x00037": "Monastery Branch Panels Activate",
|
||||
"0x0A079": "Access to Bunker Laser",
|
||||
"0x0A3B5": "Door to Tutorial Discard Opens",
|
||||
"0x00139": "Keep Hedges 2 Turns On",
|
||||
"0x019DC": "Keep Hedges 3 Turns On",
|
||||
"0x019E7": "Keep Hedges 4 Turns On",
|
||||
"0x01D3F": "Keep Laser Panel (Pressure Plates) Activates",
|
||||
"0x09F7F": "Mountain Access",
|
||||
"0x0367C": "Quarry Laser Mill Requirement Met",
|
||||
"0x009A1": "Swamp Rotating Bridge Near Side",
|
||||
"0x009A1": "Swamp Rotated Shapers 1 Activates",
|
||||
"0x00006": "Swamp Cyan Water Drains",
|
||||
"0x00990": "Swamp Broken Shapers 1 Activates",
|
||||
"0x0A8DC": "Lower Avoid 6 Activates",
|
||||
"0x0000A": "Swamp More Rotated Shapers 1 Access",
|
||||
"0x09ED8": "Inside Mountain Second Layer Both Light Bridges Solved",
|
||||
"0x09E86": "Inside Mountain Second Layer Blue Bridge Access",
|
||||
"0x09ED8": "Inside Mountain Second Layer Yellow Bridge Access",
|
||||
"0x0A3D0": "Quarry Laser Boathouse Requirement Met",
|
||||
"0x00596": "Swamp Red Water Drains",
|
||||
"0x28B39": "Town Tower 4th Door Opens",
|
||||
"0x00E3A": "Swamp Purple Water Drains",
|
||||
"0x0343A": "Door to Symmetry Island Powers On",
|
||||
"0xFFF00": "Inside Mountain Bottom Layer Discard Turns On"
|
||||
"0xFFF00": "Inside Mountain Bottom Layer Discard Turns On",
|
||||
"0x17CA6": "All Boat Panels Turn On",
|
||||
"0x17CDF": "All Boat Panels Turn On",
|
||||
"0x09DB8": "All Boat Panels Turn On",
|
||||
"0x17C95": "All Boat Panels Turn On",
|
||||
"0x03BB0": "Town Church Lattice Vision From Outside",
|
||||
"0x28AC1": "Town Shapers & Dots & Eraser Turns On",
|
||||
"0x28A69": "Town Tower 1st Door Opens",
|
||||
"0x28ACC": "Town Tower 2nd Door Opens",
|
||||
"0x28AD9": "Town Tower 3rd Door Opens",
|
||||
"0x28B39": "Town Tower 4th Door Opens",
|
||||
"0x03675": "Quarry Mill Ramp Activation From Above",
|
||||
"0x03679": "Quarry Mill Lift Lowering While Standing On It",
|
||||
"0x2FAF6": "Tutorial Gate Secret Solution Knowledge",
|
||||
"0x079DF": "Town Hexagonal Reflection Turns On",
|
||||
"0x17DA2": "Right Orange Bridge Fully Extended",
|
||||
"0x19B24": "Shadows Lower Avoid Patterns Visible",
|
||||
"0x2700B": "Open Door to Treehouse Laser House",
|
||||
"0x00055": "Orchard Apple Trees 4 Turns On",
|
||||
}
|
||||
|
||||
self.ALWAYS_EVENT_NAMES_BY_HEX = {
|
||||
"0x0360D": "Symmetry Laser Activation",
|
||||
"0x03608": "Desert Laser Activation",
|
||||
"0x00509": "Symmetry Laser Activation",
|
||||
"0x012FB": "Desert Laser Activation",
|
||||
"0x09F98": "Desert Laser Redirection",
|
||||
"0x03612": "Quarry Laser Activation",
|
||||
"0x19650": "Shadows Laser Activation",
|
||||
"0x0360E": "Keep Laser Activation",
|
||||
"0x03317": "Keep Laser Activation",
|
||||
"0x17CA4": "Monastery Laser Activation",
|
||||
"0x032F5": "Town Laser Activation",
|
||||
"0x03616": "Jungle Laser Activation",
|
||||
"0x09DE0": "Bunker Laser Activation",
|
||||
"0x03615": "Swamp Laser Activation",
|
||||
"0x03613": "Treehouse Laser Activation",
|
||||
"0x01539": "Quarry Laser Activation",
|
||||
"0x181B3": "Shadows Laser Activation",
|
||||
"0x014BB": "Keep Laser Activation",
|
||||
"0x17C65": "Monastery Laser Activation",
|
||||
"0x032F9": "Town Laser Activation",
|
||||
"0x00274": "Jungle Laser Activation",
|
||||
"0x0C2B2": "Bunker Laser Activation",
|
||||
"0x00BF6": "Swamp Laser Activation",
|
||||
"0x028A4": "Treehouse Laser Activation",
|
||||
"0x03535": "Shipwreck Video Pattern Knowledge",
|
||||
"0x03542": "Mountain Video Pattern Knowledge",
|
||||
"0x0339E": "Desert Video Pattern Knowledge",
|
||||
"0x03481": "Tutorial Video Pattern Knowledge",
|
||||
"0x03702": "Jungle Video Pattern Knowledge",
|
||||
"0x2FAF6": "Theater Walkway Video Pattern Knowledge",
|
||||
"0x0356B": "Challenge Video Pattern Knowledge",
|
||||
"0x09F7F": "Mountaintop Trap Door Turns On",
|
||||
"0x17C34": "Mountain Access",
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ class WitnessRegions:
|
||||
source_region = world.get_region(source, player)
|
||||
target_region = world.get_region(target, player)
|
||||
|
||||
#print(source_region)
|
||||
#print(target_region)
|
||||
#print("---")
|
||||
|
||||
connection = Entrance(
|
||||
player,
|
||||
source + " to " + target + " via " + str(panel_hex_to_solve_set),
|
||||
@@ -76,10 +80,17 @@ class WitnessRegions:
|
||||
for connection in player_logic.CONNECTIONS_BY_REGION_NAME[region_name]:
|
||||
if connection[0] == "Entry":
|
||||
continue
|
||||
self.connect(world, player, region_name,
|
||||
connection[0], player_logic, connection[1])
|
||||
self.connect(world, player, connection[0],
|
||||
region_name, player_logic, connection[1])
|
||||
|
||||
if connection[1] == frozenset({frozenset(["TrueOneWay"])}):
|
||||
self.connect(world, player, region_name, connection[0], player_logic, frozenset({frozenset()}))
|
||||
continue
|
||||
|
||||
for subset in connection[1]:
|
||||
if all({panel in player_logic.DOOR_ITEMS_BY_ID for panel in subset}):
|
||||
if all({StaticWitnessLogic.CHECKS_BY_HEX[panel]["id"] is None for panel in subset}):
|
||||
self.connect(world, player, connection[0], region_name, player_logic, frozenset({subset}))
|
||||
|
||||
self.connect(world, player, region_name, connection[0], player_logic, connection[1])
|
||||
|
||||
world.get_entrance("The Splashscreen?", player).connect(
|
||||
world.get_region('First Hallway', player)
|
||||
|
||||
@@ -22,6 +22,21 @@ class WitnessLogic(LogicMixin):
|
||||
def _witness_has_lasers(self, world, player: int, amount: int) -> bool:
|
||||
lasers = 0
|
||||
|
||||
if is_option_enabled(world, player, "shuffle_lasers"):
|
||||
lasers += int(self.has("Symmetry Laser", player))
|
||||
lasers += int(self.has("Desert Laser", player)
|
||||
and self.has("Desert Laser Redirection", player))
|
||||
lasers += int(self.has("Town Laser", player))
|
||||
lasers += int(self.has("Monastery Laser", player))
|
||||
lasers += int(self.has("Keep Laser", player))
|
||||
lasers += int(self.has("Quarry Laser", player))
|
||||
lasers += int(self.has("Treehouse Laser", player))
|
||||
lasers += int(self.has("Jungle Laser", player))
|
||||
lasers += int(self.has("Bunker Laser", player))
|
||||
lasers += int(self.has("Swamp Laser", player))
|
||||
lasers += int(self.has("Shadows Laser", player))
|
||||
return lasers >= amount
|
||||
|
||||
lasers += int(self.has("Symmetry Laser Activation", player))
|
||||
lasers += int(self.has("Desert Laser Activation", player)
|
||||
and self.has("Desert Laser Redirection", player))
|
||||
@@ -48,11 +63,8 @@ class WitnessLogic(LogicMixin):
|
||||
if (check_name + " Solved" in locat.EVENT_LOCATION_TABLE
|
||||
and not self.has(player_logic.EVENT_ITEM_PAIRS[check_name + " Solved"], player)):
|
||||
return False
|
||||
if panel not in player_logic.ORIGINAL_EVENT_PANELS and not self.can_reach(check_name, "Location", player):
|
||||
return False
|
||||
if (panel in player_logic.ORIGINAL_EVENT_PANELS
|
||||
and check_name + " Solved" not in locat.EVENT_LOCATION_TABLE
|
||||
and not self._witness_safe_manual_panel_check(panel, world, player, player_logic, locat)):
|
||||
if (check_name + " Solved" not in locat.EVENT_LOCATION_TABLE
|
||||
and not self._witness_meets_item_requirements(panel, world, player, player_logic, locat)):
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -79,8 +91,10 @@ class WitnessLogic(LogicMixin):
|
||||
if not self._witness_has_lasers(world, player, get_option_value(world, player, "challenge_lasers")):
|
||||
valid_option = False
|
||||
break
|
||||
elif item in player_logic.ORIGINAL_EVENT_PANELS:
|
||||
valid_option = self._witness_can_solve_panel(item, world, player, player_logic, locat)
|
||||
elif item in player_logic.EVENT_PANELS:
|
||||
if not self._witness_can_solve_panel(item, world, player, player_logic, locat):
|
||||
valid_option = False
|
||||
break
|
||||
elif not self.has(item, player):
|
||||
valid_option = False
|
||||
break
|
||||
@@ -90,24 +104,6 @@ class WitnessLogic(LogicMixin):
|
||||
|
||||
return False
|
||||
|
||||
def _witness_safe_manual_panel_check(self, panel, world, player, player_logic: WitnessPlayerLogic, locat):
|
||||
"""
|
||||
nested can_reach can cause problems, but only if the region being
|
||||
checked is neither of the two original regions from the first
|
||||
can_reach.
|
||||
A nested can_reach is okay here because the only panels this
|
||||
function is called on are panels that exist on either side of all
|
||||
connections they are required for.
|
||||
The spoiler log looks so much nicer this way,
|
||||
it gets rid of a bunch of event items, only leaving a couple. :)
|
||||
"""
|
||||
region = StaticWitnessLogic.CHECKS_BY_HEX[panel]["region"]["name"]
|
||||
|
||||
return (
|
||||
self._witness_meets_item_requirements(panel, world, player, player_logic, locat)
|
||||
and self.can_reach(region, "Region", player)
|
||||
)
|
||||
|
||||
def _witness_can_solve_panels(self, panel_hex_to_solve_set, world, player, player_logic: WitnessPlayerLogic, locat):
|
||||
"""
|
||||
Checks whether a set of panels can be solved.
|
||||
@@ -120,7 +116,12 @@ class WitnessLogic(LogicMixin):
|
||||
valid_option = True
|
||||
|
||||
for panel in option:
|
||||
if not self._witness_can_solve_panel(panel, world, player, player_logic, locat):
|
||||
if panel in player_logic.DOOR_ITEMS_BY_ID:
|
||||
if all({not self.has(item, player) for item in player_logic.DOOR_ITEMS_BY_ID[panel]}):
|
||||
valid_option = False
|
||||
break
|
||||
|
||||
elif not self._witness_can_solve_panel(panel, world, player, player_logic, locat):
|
||||
valid_option = False
|
||||
break
|
||||
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
Event Items:
|
||||
Shadows Laser Activation - 0x00021,0x17D28,0x17C71
|
||||
Keep Laser Activation - 0x03317
|
||||
Bunker Laser Activation - 0x00061,0x17D01,0x17C42
|
||||
Monastery Laser Activation - 0x00A5B,0x17CE7,0x17FA9,0x17CA4
|
||||
Town Tower 4th Door Opens - 0x17CFB,0x3C12B,0x00B8D,0x17CF7
|
||||
Monastery Laser Activation - 0x00A5B,0x17CE7,0x17FA9,0x17CA4
|
||||
Bunker Laser Activation - 0x00061,0x17D01,0x17C42
|
||||
Shadows Laser Activation - 0x00021,0x17D28,0x17C71
|
||||
|
||||
Requirement Changes:
|
||||
0x17CA4 - True - True
|
||||
0x28B39 - 0x2896A - Reflection
|
||||
0x17C65 - 0x00A5B | 0x17CE7 | 0x17FA9 | 0x17CA4
|
||||
0x0C2B2 - 0x00061 | 0x17D01 | 0x17C42
|
||||
0x181B3 - 0x00021 | 0x17D28 | 0x17C71
|
||||
0x28B39 - True - Reflection
|
||||
0x17CAB - True - True
|
||||
|
||||
Region Changes:
|
||||
Quarry (Quarry) - Outside Quarry - 0x17C09 - Quarry Mill - 0x275ED - Quarry Mill - 0x17CAC
|
||||
|
||||
Disabled Locations:
|
||||
0x03505 (Tutorial Gate Close)
|
||||
0x0C335 (Tutorial Pillar)
|
||||
31
worlds/witness/settings/Door_Panel_Shuffle.txt
Normal file
31
worlds/witness/settings/Door_Panel_Shuffle.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
Items:
|
||||
Glass Factory Entry Door (Panel)
|
||||
Door to Symmetry Island Lower (Panel)
|
||||
Door to Symmetry Island Upper (Panel)
|
||||
Door to Desert Flood Light Room (Panel)
|
||||
Desert Flood Room Flood Controls (Panel)
|
||||
Quarry Door to Mill (Panel)
|
||||
Quarry Mill Ramp Controls (Panel)
|
||||
Quarry Mill Elevator Controls (Panel)
|
||||
Quarry Boathouse Ramp Height Control (Panel)
|
||||
Quarry Boathouse Ramp Horizontal Control (Panel)
|
||||
Shadows Door Timer (Panel)
|
||||
Monastery Entry Door Left (Panel)
|
||||
Monastery Entry Door Right (Panel)
|
||||
Town Door to RGB House (Panel)
|
||||
Town Door to Church (Panel)
|
||||
Town Maze Panel (Drop-Down Staircase) (Panel)
|
||||
Windmill Door (Panel)
|
||||
Treehouse First & Second Doors (Panel)
|
||||
Treehouse Third Door (Panel)
|
||||
Treehouse Laser House Door Timer (Panel)
|
||||
Treehouse Shortcut Drop-Down Bridge (Panel)
|
||||
Jungle Popup Wall (Panel)
|
||||
Bunker Entry Door (Panel)
|
||||
Inside Bunker Door to Bunker Proper (Panel)
|
||||
Bunker Elevator Control (Panel)
|
||||
Swamp Entry Door (Panel)
|
||||
Swamp Sliding Bridge (Panel)
|
||||
Swamp Rotating Bridge (Panel)
|
||||
Swamp Maze Control (Panel)
|
||||
Boat
|
||||
201
worlds/witness/settings/Doors_Complex.txt
Normal file
201
worlds/witness/settings/Doors_Complex.txt
Normal file
@@ -0,0 +1,201 @@
|
||||
Items:
|
||||
Outside Tutorial Optional Door
|
||||
Outside Tutorial Outpost Entry Door
|
||||
Outside Tutorial Outpost Exit Door
|
||||
Glass Factory Entry Door
|
||||
Glass Factory Back Wall
|
||||
Symmetry Island Lower Door
|
||||
Symmetry Island Upper Door
|
||||
Orchard Middle Gate
|
||||
Orchard Final Gate
|
||||
Desert Door to Flood Light Room
|
||||
Desert Door to Pond Room
|
||||
Desert Door to Water Levels Room
|
||||
Desert Door to Elevator Room
|
||||
Quarry Main Entry 1
|
||||
Quarry Main Entry 2
|
||||
Quarry Door to Mill
|
||||
Quarry Mill Side Door
|
||||
Quarry Mill Rooftop Shortcut
|
||||
Quarry Mill Stairs
|
||||
Quarry Boathouse Boat Staircase
|
||||
Quarry Boathouse First Barrier
|
||||
Quarry Boathouse Shortcut
|
||||
Shadows Timed Door
|
||||
Shadows Laser Room Right Door
|
||||
Shadows Laser Room Left Door
|
||||
Shadows Barrier to Quarry
|
||||
Shadows Barrier to Ledge
|
||||
Keep Hedge Maze 1 Exit Door
|
||||
Keep Pressure Plates 1 Exit Door
|
||||
Keep Hedge Maze 2 Shortcut
|
||||
Keep Hedge Maze 2 Exit Door
|
||||
Keep Hedge Maze 3 Shortcut
|
||||
Keep Hedge Maze 3 Exit Door
|
||||
Keep Hedge Maze 4 Shortcut
|
||||
Keep Hedge Maze 4 Exit Door
|
||||
Keep Pressure Plates 2 Exit Door
|
||||
Keep Pressure Plates 3 Exit Door
|
||||
Keep Pressure Plates 4 Exit Door
|
||||
Keep Shortcut to Shadows
|
||||
Keep Tower Shortcut
|
||||
Monastery Shortcut
|
||||
Monastery Inner Door
|
||||
Monastery Outer Door
|
||||
Monastery Door to Garden
|
||||
Town Cargo Box Door
|
||||
Town Wooden Roof Staircase
|
||||
Town Tinted Door to RGB House
|
||||
Town Door to Church
|
||||
Town Maze Staircase
|
||||
Town Windmill Door
|
||||
Town RGB House Staircase
|
||||
Town Tower Blue Panels Door
|
||||
Town Tower Lattice Door
|
||||
Town Tower Environmental Set Door
|
||||
Town Tower Wooden Roof Set Door
|
||||
Theater Entry Door
|
||||
Theater Exit Door Left
|
||||
Theater Exit Door Right
|
||||
Jungle Bamboo Shortcut to River
|
||||
Jungle Popup Wall
|
||||
River Shortcut to Monastery Garden
|
||||
Bunker Bunker Entry Door
|
||||
Bunker Tinted Glass Door
|
||||
Bunker Door to Ultraviolet Room
|
||||
Bunker Door to Elevator
|
||||
Swamp Entry Door
|
||||
Swamp Door to Broken Shapers
|
||||
Swamp Platform Shortcut Door
|
||||
Swamp Cyan Water Pump
|
||||
Swamp Door to Rotated Shapers
|
||||
Swamp Red Water Pump
|
||||
Swamp Red Underwater Exit
|
||||
Swamp Blue Water Pump
|
||||
Swamp Purple Water Pump
|
||||
Swamp Near Laser Shortcut
|
||||
Treehouse First Door
|
||||
Treehouse Second Door
|
||||
Treehouse Beyond Yellow Bridge Door
|
||||
Treehouse Drawbridge
|
||||
Treehouse Timed Door to Laser House
|
||||
Inside Mountain First Layer Exit Door
|
||||
Inside Mountain Second Layer Staircase Near
|
||||
Inside Mountain Second Layer Exit Door
|
||||
Inside Mountain Second Layer Staircase Far
|
||||
Inside Mountain Giant Puzzle Exit Door
|
||||
Inside Mountain Door to Final Room
|
||||
Inside Mountain Bottom Layer Rock
|
||||
Inside Mountain Door to Secret Area
|
||||
Caves Pillar Door
|
||||
Caves Mountain Shortcut
|
||||
Caves Swamp Shortcut
|
||||
Challenge Entry Door
|
||||
Challenge Door to Theater Walkway
|
||||
Theater Walkway Door to Windmill Interior
|
||||
Theater Walkway Door to Desert Elevator Room
|
||||
Theater Walkway Door to Town
|
||||
|
||||
Added Locations:
|
||||
Outside Tutorial Door to Outpost Panel
|
||||
Outside Tutorial Exit Door from Outpost Panel
|
||||
Glass Factory Entry Door Panel
|
||||
Glass Factory Vertical Symmetry 5
|
||||
Symmetry Island Door to Symmetry Island Lower Panel
|
||||
Symmetry Island Door to Symmetry Island Upper Panel
|
||||
Orchard Apple Tree 3
|
||||
Orchard Apple Tree 5
|
||||
Desert Door to Desert Flood Light Room Panel
|
||||
Desert Artificial Light Reflection 3
|
||||
Desert Door to Water Levels Room Panel
|
||||
Desert Flood Reflection 6
|
||||
Quarry Door to Quarry 1 Panel
|
||||
Quarry Door to Quarry 2 Panel
|
||||
Quarry Door to Mill Right
|
||||
Quarry Door to Mill Left
|
||||
Quarry Mill Ground Floor Shortcut Door Panel
|
||||
Quarry Mill Door to Outside Quarry Stairs Panel
|
||||
Quarry Mill Stair Control
|
||||
Quarry Boathouse Shortcut Door Panel
|
||||
Shadows Door Timer Inside
|
||||
Shadows Door Timer Outside
|
||||
Shadows Environmental Avoid 8
|
||||
Shadows Follow 5
|
||||
Shadows Lower Avoid 3
|
||||
Shadows Lower Avoid 5
|
||||
Keep Hedge Maze 1
|
||||
Keep Pressure Plates 1
|
||||
Keep Hedge Maze 2
|
||||
Keep Hedge Maze 3
|
||||
Keep Hedge Maze 4
|
||||
Keep Pressure Plates 2
|
||||
Keep Pressure Plates 3
|
||||
Keep Pressure Plates 4
|
||||
Keep Shortcut to Shadows Panel
|
||||
Keep Tower Shortcut to Keep Panel
|
||||
Monastery Shortcut Door Panel
|
||||
Monastery Door Open Left
|
||||
Monastery Door Open Right
|
||||
Monastery Rhombic Avoid 3
|
||||
Town Cargo Box Panel
|
||||
Town Full Dot Grid Shapers 5
|
||||
Town Tinted Door Panel
|
||||
Town Door to Church Stars Panel
|
||||
Town Maze Stair Control
|
||||
Town Windmill Door Panel
|
||||
Town Sound Room Left
|
||||
Town Sound Room Right
|
||||
Town Symmetry Squares 5 + Dots
|
||||
Town Church Lattice
|
||||
Town Hexagonal Reflection
|
||||
Town Shapers & Dots & Eraser
|
||||
Windmill Door to Front of Theater Panel
|
||||
Theater Door to Cargo Box Left Panel
|
||||
Theater Door to Cargo Box Right Panel
|
||||
Jungle Shortcut to River Panel
|
||||
Jungle Popup Wall Control
|
||||
River Rhombic Avoid to Monastery Garden
|
||||
Bunker Bunker Entry Panel
|
||||
Bunker Door to Bunker Proper Panel
|
||||
Bunker Drawn Squares through Tinted Glass 3
|
||||
Bunker Drop-Down Door Squares 2
|
||||
Swamp Entry Panel
|
||||
Swamp Platform Shapers 4
|
||||
Swamp Platform Shortcut Right Panel
|
||||
Swamp Blue Underwater Negative Shapers 5
|
||||
Swamp Broken Shapers 4
|
||||
Swamp Cyan Underwater Negative Shapers 5
|
||||
Swamp Red Underwater Negative Shapers 4
|
||||
Swamp More Rotated Shapers 4
|
||||
Swamp More Rotated Shapers 4
|
||||
Swamp Near Laser Shortcut Right Panel
|
||||
Treehouse First Door Panel
|
||||
Treehouse Second Door Panel
|
||||
Treehouse Beyond Yellow Bridge Door Panel
|
||||
Treehouse Bridge Control
|
||||
Treehouse Left Orange Bridge 15
|
||||
Treehouse Right Orange Bridge 12
|
||||
Treehouse Laser House Door Timer Outside Control
|
||||
Treehouse Laser House Door Timer Inside Control
|
||||
Inside Mountain Moving Background 7
|
||||
Inside Mountain Obscured Vision 5
|
||||
Inside Mountain Physically Obstructed 3
|
||||
Inside Mountain Angled Inside Trash 2
|
||||
Inside Mountain Color Cycle 5
|
||||
Inside Mountain Light Bridge Controller 2
|
||||
Inside Mountain Light Bridge Controller 3
|
||||
Inside Mountain Same Solution 6
|
||||
Inside Mountain Giant Puzzle
|
||||
Inside Mountain Door to Final Room Left
|
||||
Inside Mountain Door to Final Room Right
|
||||
Inside Mountain Bottom Layer Discard
|
||||
Inside Mountain Rock Control
|
||||
Inside Mountain Secret Area Entry Panel
|
||||
Inside Mountain Caves Lone Pillar
|
||||
Inside Mountain Caves Shortcut to Mountain Panel
|
||||
Inside Mountain Caves Shortcut to Swamp Panel
|
||||
Inside Mountain Caves Challenge Entry Panel
|
||||
Challenge Door to Theater Walkway Panel
|
||||
Theater Walkway Theater Shortcut Panel
|
||||
Theater Walkway Desert Shortcut Panel
|
||||
Theater Walkway Town Shortcut Panel
|
||||
212
worlds/witness/settings/Doors_Max.txt
Normal file
212
worlds/witness/settings/Doors_Max.txt
Normal file
@@ -0,0 +1,212 @@
|
||||
Items:
|
||||
Outside Tutorial Optional Door
|
||||
Outside Tutorial Outpost Entry Door
|
||||
Outside Tutorial Outpost Exit Door
|
||||
Glass Factory Entry Door
|
||||
Glass Factory Back Wall
|
||||
Symmetry Island Lower Door
|
||||
Symmetry Island Upper Door
|
||||
Orchard Middle Gate
|
||||
Orchard Final Gate
|
||||
Desert Door to Flood Light Room
|
||||
Desert Door to Pond Room
|
||||
Desert Door to Water Levels Room
|
||||
Desert Door to Elevator Room
|
||||
Quarry Main Entry 1
|
||||
Quarry Main Entry 2
|
||||
Quarry Door to Mill
|
||||
Quarry Mill Side Door
|
||||
Quarry Mill Rooftop Shortcut
|
||||
Quarry Mill Stairs
|
||||
Quarry Boathouse Boat Staircase
|
||||
Quarry Boathouse First Barrier
|
||||
Quarry Boathouse Shortcut
|
||||
Shadows Timed Door
|
||||
Shadows Laser Room Right Door
|
||||
Shadows Laser Room Left Door
|
||||
Shadows Barrier to Quarry
|
||||
Shadows Barrier to Ledge
|
||||
Keep Hedge Maze 1 Exit Door
|
||||
Keep Pressure Plates 1 Exit Door
|
||||
Keep Hedge Maze 2 Shortcut
|
||||
Keep Hedge Maze 2 Exit Door
|
||||
Keep Hedge Maze 3 Shortcut
|
||||
Keep Hedge Maze 3 Exit Door
|
||||
Keep Hedge Maze 4 Shortcut
|
||||
Keep Hedge Maze 4 Exit Door
|
||||
Keep Pressure Plates 2 Exit Door
|
||||
Keep Pressure Plates 3 Exit Door
|
||||
Keep Pressure Plates 4 Exit Door
|
||||
Keep Shortcut to Shadows
|
||||
Keep Tower Shortcut
|
||||
Monastery Shortcut
|
||||
Monastery Inner Door
|
||||
Monastery Outer Door
|
||||
Monastery Door to Garden
|
||||
Town Cargo Box Door
|
||||
Town Wooden Roof Staircase
|
||||
Town Tinted Door to RGB House
|
||||
Town Door to Church
|
||||
Town Maze Staircase
|
||||
Town Windmill Door
|
||||
Town RGB House Staircase
|
||||
Town Tower Blue Panels Door
|
||||
Town Tower Lattice Door
|
||||
Town Tower Environmental Set Door
|
||||
Town Tower Wooden Roof Set Door
|
||||
Theater Entry Door
|
||||
Theater Exit Door Left
|
||||
Theater Exit Door Right
|
||||
Jungle Bamboo Shortcut to River
|
||||
Jungle Popup Wall
|
||||
River Shortcut to Monastery Garden
|
||||
Bunker Bunker Entry Door
|
||||
Bunker Tinted Glass Door
|
||||
Bunker Door to Ultraviolet Room
|
||||
Bunker Door to Elevator
|
||||
Swamp Entry Door
|
||||
Swamp Door to Broken Shapers
|
||||
Swamp Platform Shortcut Door
|
||||
Swamp Cyan Water Pump
|
||||
Swamp Door to Rotated Shapers
|
||||
Swamp Red Water Pump
|
||||
Swamp Red Underwater Exit
|
||||
Swamp Blue Water Pump
|
||||
Swamp Purple Water Pump
|
||||
Swamp Near Laser Shortcut
|
||||
Treehouse First Door
|
||||
Treehouse Second Door
|
||||
Treehouse Beyond Yellow Bridge Door
|
||||
Treehouse Drawbridge
|
||||
Treehouse Timed Door to Laser House
|
||||
Inside Mountain First Layer Exit Door
|
||||
Inside Mountain Second Layer Staircase Near
|
||||
Inside Mountain Second Layer Exit Door
|
||||
Inside Mountain Second Layer Staircase Far
|
||||
Inside Mountain Giant Puzzle Exit Door
|
||||
Inside Mountain Door to Final Room
|
||||
Inside Mountain Bottom Layer Rock
|
||||
Inside Mountain Door to Secret Area
|
||||
Caves Pillar Door
|
||||
Caves Mountain Shortcut
|
||||
Caves Swamp Shortcut
|
||||
Challenge Entry Door
|
||||
Challenge Door to Theater Walkway
|
||||
Theater Walkway Door to Windmill Interior
|
||||
Theater Walkway Door to Desert Elevator Room
|
||||
Theater Walkway Door to Town
|
||||
|
||||
Desert Flood Room Flood Controls (Panel)
|
||||
Quarry Mill Ramp Controls (Panel)
|
||||
Quarry Mill Elevator Controls (Panel)
|
||||
Quarry Boathouse Ramp Height Control (Panel)
|
||||
Quarry Boathouse Ramp Horizontal Control (Panel)
|
||||
Bunker Elevator Control (Panel)
|
||||
Swamp Sliding Bridge (Panel)
|
||||
Swamp Rotating Bridge (Panel)
|
||||
Swamp Maze Control (Panel)
|
||||
Boat
|
||||
|
||||
Added Locations:
|
||||
Outside Tutorial Door to Outpost Panel
|
||||
Outside Tutorial Exit Door from Outpost Panel
|
||||
Glass Factory Entry Door Panel
|
||||
Glass Factory Vertical Symmetry 5
|
||||
Symmetry Island Door to Symmetry Island Lower Panel
|
||||
Symmetry Island Door to Symmetry Island Upper Panel
|
||||
Orchard Apple Tree 3
|
||||
Orchard Apple Tree 5
|
||||
Desert Door to Desert Flood Light Room Panel
|
||||
Desert Artificial Light Reflection 3
|
||||
Desert Door to Water Levels Room Panel
|
||||
Desert Flood Reflection 6
|
||||
Quarry Door to Quarry 1 Panel
|
||||
Quarry Door to Quarry 2 Panel
|
||||
Quarry Door to Mill Right
|
||||
Quarry Door to Mill Left
|
||||
Quarry Mill Ground Floor Shortcut Door Panel
|
||||
Quarry Mill Door to Outside Quarry Stairs Panel
|
||||
Quarry Mill Stair Control
|
||||
Quarry Boathouse Shortcut Door Panel
|
||||
Shadows Door Timer Inside
|
||||
Shadows Door Timer Outside
|
||||
Shadows Environmental Avoid 8
|
||||
Shadows Follow 5
|
||||
Shadows Lower Avoid 3
|
||||
Shadows Lower Avoid 5
|
||||
Keep Hedge Maze 1
|
||||
Keep Pressure Plates 1
|
||||
Keep Hedge Maze 2
|
||||
Keep Hedge Maze 3
|
||||
Keep Hedge Maze 4
|
||||
Keep Pressure Plates 2
|
||||
Keep Pressure Plates 3
|
||||
Keep Pressure Plates 4
|
||||
Keep Shortcut to Shadows Panel
|
||||
Keep Tower Shortcut to Keep Panel
|
||||
Monastery Shortcut Door Panel
|
||||
Monastery Door Open Left
|
||||
Monastery Door Open Right
|
||||
Monastery Rhombic Avoid 3
|
||||
Town Cargo Box Panel
|
||||
Town Full Dot Grid Shapers 5
|
||||
Town Tinted Door Panel
|
||||
Town Door to Church Stars Panel
|
||||
Town Maze Stair Control
|
||||
Town Windmill Door Panel
|
||||
Town Sound Room Left
|
||||
Town Sound Room Right
|
||||
Town Symmetry Squares 5 + Dots
|
||||
Town Church Lattice
|
||||
Town Hexagonal Reflection
|
||||
Town Shapers & Dots & Eraser
|
||||
Windmill Door to Front of Theater Panel
|
||||
Theater Door to Cargo Box Left Panel
|
||||
Theater Door to Cargo Box Right Panel
|
||||
Jungle Shortcut to River Panel
|
||||
Jungle Popup Wall Control
|
||||
River Rhombic Avoid to Monastery Garden
|
||||
Bunker Bunker Entry Panel
|
||||
Bunker Door to Bunker Proper Panel
|
||||
Bunker Drawn Squares through Tinted Glass 3
|
||||
Bunker Drop-Down Door Squares 2
|
||||
Swamp Entry Panel
|
||||
Swamp Platform Shapers 4
|
||||
Swamp Platform Shortcut Right Panel
|
||||
Swamp Blue Underwater Negative Shapers 5
|
||||
Swamp Broken Shapers 4
|
||||
Swamp Cyan Underwater Negative Shapers 5
|
||||
Swamp Red Underwater Negative Shapers 4
|
||||
Swamp More Rotated Shapers 4
|
||||
Swamp More Rotated Shapers 4
|
||||
Swamp Near Laser Shortcut Right Panel
|
||||
Treehouse First Door Panel
|
||||
Treehouse Second Door Panel
|
||||
Treehouse Beyond Yellow Bridge Door Panel
|
||||
Treehouse Bridge Control
|
||||
Treehouse Left Orange Bridge 15
|
||||
Treehouse Right Orange Bridge 12
|
||||
Treehouse Laser House Door Timer Outside Control
|
||||
Treehouse Laser House Door Timer Inside Control
|
||||
Inside Mountain Moving Background 7
|
||||
Inside Mountain Obscured Vision 5
|
||||
Inside Mountain Physically Obstructed 3
|
||||
Inside Mountain Angled Inside Trash 2
|
||||
Inside Mountain Color Cycle 5
|
||||
Inside Mountain Light Bridge Controller 2
|
||||
Inside Mountain Light Bridge Controller 3
|
||||
Inside Mountain Same Solution 6
|
||||
Inside Mountain Giant Puzzle
|
||||
Inside Mountain Door to Final Room Left
|
||||
Inside Mountain Door to Final Room Right
|
||||
Inside Mountain Bottom Layer Discard
|
||||
Inside Mountain Rock Control
|
||||
Inside Mountain Secret Area Entry Panel
|
||||
Inside Mountain Caves Lone Pillar
|
||||
Inside Mountain Caves Shortcut to Mountain Panel
|
||||
Inside Mountain Caves Shortcut to Swamp Panel
|
||||
Inside Mountain Caves Challenge Entry Panel
|
||||
Challenge Door to Theater Walkway Panel
|
||||
Theater Walkway Theater Shortcut Panel
|
||||
Theater Walkway Desert Shortcut Panel
|
||||
Theater Walkway Town Shortcut Panel
|
||||
146
worlds/witness/settings/Doors_Simple.txt
Normal file
146
worlds/witness/settings/Doors_Simple.txt
Normal file
@@ -0,0 +1,146 @@
|
||||
Items:
|
||||
Glass Factory Back Wall
|
||||
Quarry Boathouse Boat Staircase
|
||||
Outside Tutorial Outpost Doors
|
||||
Glass Factory Entry Door
|
||||
Symmetry Island Doors
|
||||
Orchard Gates
|
||||
Desert Doors
|
||||
Quarry Main Entry
|
||||
Quarry Door to Mill
|
||||
Quarry Mill Shortcuts
|
||||
Quarry Boathouse Barriers
|
||||
Shadows Timed Door
|
||||
Shadows Laser Room Door
|
||||
Shadows Barriers
|
||||
Keep Hedge Maze Doors
|
||||
Keep Pressure Plates Doors
|
||||
Keep Shortcuts
|
||||
Monastery Entry Door
|
||||
Monastery Shortcuts
|
||||
Town Doors
|
||||
Town Tower Doors
|
||||
Theater Entry Door
|
||||
Theater Exit Door
|
||||
Jungle & River Shortcuts
|
||||
Jungle Popup Wall
|
||||
Bunker Doors
|
||||
Swamp Doors
|
||||
Swamp Near Laser Shortcut
|
||||
Swamp Water Pumps
|
||||
Treehouse Entry Doors
|
||||
Treehouse Drawbridge
|
||||
Treehouse Timed Door to Laser House
|
||||
Inside Mountain First Layer Exit Door
|
||||
Inside Mountain Second Layer Stairs & Doors
|
||||
Inside Mountain Giant Puzzle Exit Door
|
||||
Inside Mountain Door to Final Room
|
||||
Inside Mountain Bottom Layer Doors to Caves
|
||||
Caves Doors to Challenge
|
||||
Caves Exits to Main Island
|
||||
Challenge Door to Theater Walkway
|
||||
Theater Walkway Doors
|
||||
|
||||
Added Locations:
|
||||
Outside Tutorial Door to Outpost Panel
|
||||
Outside Tutorial Exit Door from Outpost Panel
|
||||
Glass Factory Entry Door Panel
|
||||
Glass Factory Vertical Symmetry 5
|
||||
Symmetry Island Door to Symmetry Island Lower Panel
|
||||
Symmetry Island Door to Symmetry Island Upper Panel
|
||||
Orchard Apple Tree 3
|
||||
Orchard Apple Tree 5
|
||||
Desert Door to Desert Flood Light Room Panel
|
||||
Desert Artificial Light Reflection 3
|
||||
Desert Door to Water Levels Room Panel
|
||||
Desert Flood Reflection 6
|
||||
Quarry Door to Quarry 1 Panel
|
||||
Quarry Door to Quarry 2 Panel
|
||||
Quarry Door to Mill Right
|
||||
Quarry Door to Mill Left
|
||||
Quarry Mill Ground Floor Shortcut Door Panel
|
||||
Quarry Mill Door to Outside Quarry Stairs Panel
|
||||
Quarry Mill Stair Control
|
||||
Quarry Boathouse Shortcut Door Panel
|
||||
Shadows Door Timer Inside
|
||||
Shadows Door Timer Outside
|
||||
Shadows Environmental Avoid 8
|
||||
Shadows Follow 5
|
||||
Shadows Lower Avoid 3
|
||||
Shadows Lower Avoid 5
|
||||
Keep Hedge Maze 1
|
||||
Keep Pressure Plates 1
|
||||
Keep Hedge Maze 2
|
||||
Keep Hedge Maze 3
|
||||
Keep Hedge Maze 4
|
||||
Keep Pressure Plates 2
|
||||
Keep Pressure Plates 3
|
||||
Keep Pressure Plates 4
|
||||
Keep Shortcut to Shadows Panel
|
||||
Keep Tower Shortcut to Keep Panel
|
||||
Monastery Shortcut Door Panel
|
||||
Monastery Door Open Left
|
||||
Monastery Door Open Right
|
||||
Monastery Rhombic Avoid 3
|
||||
Town Cargo Box Panel
|
||||
Town Full Dot Grid Shapers 5
|
||||
Town Tinted Door Panel
|
||||
Town Door to Church Stars Panel
|
||||
Town Maze Stair Control
|
||||
Town Windmill Door Panel
|
||||
Town Sound Room Left
|
||||
Town Sound Room Right
|
||||
Town Symmetry Squares 5 + Dots
|
||||
Town Church Lattice
|
||||
Town Hexagonal Reflection
|
||||
Town Shapers & Dots & Eraser
|
||||
Windmill Door to Front of Theater Panel
|
||||
Theater Door to Cargo Box Left Panel
|
||||
Theater Door to Cargo Box Right Panel
|
||||
Jungle Shortcut to River Panel
|
||||
Jungle Popup Wall Control
|
||||
River Rhombic Avoid to Monastery Garden
|
||||
Bunker Bunker Entry Panel
|
||||
Bunker Door to Bunker Proper Panel
|
||||
Bunker Drawn Squares through Tinted Glass 3
|
||||
Bunker Drop-Down Door Squares 2
|
||||
Swamp Entry Panel
|
||||
Swamp Platform Shapers 4
|
||||
Swamp Platform Shortcut Right Panel
|
||||
Swamp Blue Underwater Negative Shapers 5
|
||||
Swamp Broken Shapers 4
|
||||
Swamp Cyan Underwater Negative Shapers 5
|
||||
Swamp Red Underwater Negative Shapers 4
|
||||
Swamp More Rotated Shapers 4
|
||||
Swamp More Rotated Shapers 4
|
||||
Swamp Near Laser Shortcut Right Panel
|
||||
Treehouse First Door Panel
|
||||
Treehouse Second Door Panel
|
||||
Treehouse Beyond Yellow Bridge Door Panel
|
||||
Treehouse Bridge Control
|
||||
Treehouse Left Orange Bridge 15
|
||||
Treehouse Right Orange Bridge 12
|
||||
Treehouse Laser House Door Timer Outside Control
|
||||
Treehouse Laser House Door Timer Inside Control
|
||||
Inside Mountain Moving Background 7
|
||||
Inside Mountain Obscured Vision 5
|
||||
Inside Mountain Physically Obstructed 3
|
||||
Inside Mountain Angled Inside Trash 2
|
||||
Inside Mountain Color Cycle 5
|
||||
Inside Mountain Light Bridge Controller 2
|
||||
Inside Mountain Light Bridge Controller 3
|
||||
Inside Mountain Same Solution 6
|
||||
Inside Mountain Giant Puzzle
|
||||
Inside Mountain Door to Final Room Left
|
||||
Inside Mountain Door to Final Room Right
|
||||
Inside Mountain Bottom Layer Discard
|
||||
Inside Mountain Rock Control
|
||||
Inside Mountain Secret Area Entry Panel
|
||||
Inside Mountain Caves Lone Pillar
|
||||
Inside Mountain Caves Shortcut to Mountain Panel
|
||||
Inside Mountain Caves Shortcut to Swamp Panel
|
||||
Inside Mountain Caves Challenge Entry Panel
|
||||
Challenge Door to Theater Walkway Panel
|
||||
Theater Walkway Theater Shortcut Panel
|
||||
Theater Walkway Desert Shortcut Panel
|
||||
Theater Walkway Town Shortcut Panel
|
||||
9
worlds/witness/settings/Early_UTM.txt
Normal file
9
worlds/witness/settings/Early_UTM.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
Items:
|
||||
Caves Exits to Main Island
|
||||
|
||||
Starting Inventory:
|
||||
Caves Exits to Main Island
|
||||
|
||||
Remove Items:
|
||||
Caves Mountain Shortcut
|
||||
Caves Swamp Shortcut
|
||||
12
worlds/witness/settings/Laser_Shuffle.txt
Normal file
12
worlds/witness/settings/Laser_Shuffle.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
Items:
|
||||
Symmetry Laser
|
||||
Desert Laser
|
||||
Keep Laser
|
||||
Shadows Laser
|
||||
Quarry Laser
|
||||
Town Laser
|
||||
Swamp Laser
|
||||
Jungle Laser
|
||||
Bunker Laser
|
||||
Monastery Laser
|
||||
Treehouse Laser
|
||||
14
worlds/witness/settings/Symbol_Shuffle.txt
Normal file
14
worlds/witness/settings/Symbol_Shuffle.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
Items:
|
||||
Dots
|
||||
Colored Dots
|
||||
Sound Dots
|
||||
Symmetry
|
||||
Triangles
|
||||
Eraser
|
||||
Shapers
|
||||
Rotated Shapers
|
||||
Negative Shapers
|
||||
Stars
|
||||
Stars + Same Colored Symbol
|
||||
Black/White Squares
|
||||
Colored Squares
|
||||
@@ -5,12 +5,11 @@ from .utils import define_new_region, parse_lambda
|
||||
|
||||
class StaticWitnessLogic:
|
||||
ALL_SYMBOL_ITEMS = set()
|
||||
ALL_DOOR_ITEMS = set()
|
||||
ALL_DOOR_ITEMS_AS_DICT = dict()
|
||||
ALL_USEFULS = set()
|
||||
ALL_TRAPS = set()
|
||||
ALL_BOOSTS = set()
|
||||
ALL_DOOR_ITEM_IDS_BY_HEX = dict()
|
||||
DOOR_NAMES_BY_HEX = dict()
|
||||
ALL_DOOR_ITEMS = set()
|
||||
CONNECTIONS_TO_SEVER_BY_DOOR_HEX = dict()
|
||||
|
||||
EVENT_PANELS_FROM_REGIONS = set()
|
||||
@@ -47,35 +46,23 @@ class StaticWitnessLogic:
|
||||
if line == "Usefuls:":
|
||||
current_set = self.ALL_USEFULS
|
||||
continue
|
||||
if line == "Doors:":
|
||||
current_set = self.ALL_DOOR_ITEMS
|
||||
continue
|
||||
if line == "":
|
||||
continue
|
||||
|
||||
line_split = line.split(" - ")
|
||||
|
||||
if current_set is not self.ALL_USEFULS:
|
||||
current_set.add((line_split[1], int(line_split[0])))
|
||||
else:
|
||||
if current_set is self.ALL_USEFULS:
|
||||
current_set.add((line_split[1], int(line_split[0]), line_split[2] == "True"))
|
||||
elif current_set is self.ALL_DOOR_ITEMS:
|
||||
new_door = (line_split[1], int(line_split[0]), frozenset(line_split[2].split(",")))
|
||||
current_set.add(new_door)
|
||||
self.ALL_DOOR_ITEMS_AS_DICT[line_split[1]] = new_door
|
||||
else:
|
||||
current_set.add((line_split[1], int(line_split[0])))
|
||||
|
||||
path = os.path.join(os.path.dirname(__file__), "Door_Shuffle.txt")
|
||||
with open(path, "r", encoding="utf-8") as file:
|
||||
for line in file.readlines():
|
||||
line = line.strip()
|
||||
|
||||
line_split = line.split(" - ")
|
||||
|
||||
hex_set_split = line_split[1].split(",")
|
||||
|
||||
sever_list = line_split[2].split(",")
|
||||
sever_set = {sever_panel for sever_panel in sever_list if sever_panel != "None"}
|
||||
|
||||
for door_hex in hex_set_split:
|
||||
self.ALL_DOOR_ITEM_IDS_BY_HEX[door_hex] = int(line_split[0])
|
||||
self.CONNECTIONS_TO_SEVER_BY_DOOR_HEX[door_hex] = sever_set
|
||||
|
||||
if len(line_split) > 3:
|
||||
self.DOOR_NAMES_BY_HEX[door_hex] = line_split[3]
|
||||
|
||||
def read_logic_file(self):
|
||||
"""
|
||||
Reads the logic file and does the initial population of data structures
|
||||
@@ -84,10 +71,7 @@ class StaticWitnessLogic:
|
||||
with open(path, "r", encoding="utf-8") as file:
|
||||
current_region = dict()
|
||||
|
||||
discard_ids = 0
|
||||
normal_panel_ids = 0
|
||||
vault_ids = 0
|
||||
laser_ids = 0
|
||||
counter = 0
|
||||
|
||||
for line in file.readlines():
|
||||
line = line.strip()
|
||||
@@ -95,7 +79,7 @@ class StaticWitnessLogic:
|
||||
if line == "":
|
||||
continue
|
||||
|
||||
if line[0] != "0":
|
||||
if line[-1] == ":":
|
||||
new_region_and_connections = define_new_region(line)
|
||||
current_region = new_region_and_connections[0]
|
||||
region_name = current_region["name"]
|
||||
@@ -105,12 +89,33 @@ class StaticWitnessLogic:
|
||||
|
||||
line_split = line.split(" - ")
|
||||
|
||||
location_id = line_split.pop(0)
|
||||
|
||||
check_name_full = line_split.pop(0)
|
||||
|
||||
check_hex = check_name_full[0:7]
|
||||
check_name = check_name_full[9:-1]
|
||||
|
||||
required_panel_lambda = line_split.pop(0)
|
||||
|
||||
if location_id == "Door" or location_id == "Laser":
|
||||
self.CHECKS_BY_HEX[check_hex] = {
|
||||
"checkName": current_region["shortName"] + " " + check_name,
|
||||
"checkHex": check_hex,
|
||||
"region": current_region,
|
||||
"id": None,
|
||||
"panelType": location_id
|
||||
}
|
||||
|
||||
self.CHECKS_BY_NAME[self.CHECKS_BY_HEX[check_hex]["checkName"]] = self.CHECKS_BY_HEX[check_hex]
|
||||
|
||||
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[check_hex] = {
|
||||
"panels": parse_lambda(required_panel_lambda)
|
||||
}
|
||||
|
||||
current_region["panels"].add(check_hex)
|
||||
continue
|
||||
|
||||
required_item_lambda = line_split.pop(0)
|
||||
|
||||
laser_names = {
|
||||
@@ -123,53 +128,14 @@ class StaticWitnessLogic:
|
||||
|
||||
if "Discard" in check_name:
|
||||
location_type = "Discard"
|
||||
location_id = discard_ids
|
||||
discard_ids += 1
|
||||
elif is_vault_or_video or check_name == "Tutorial Gate Close":
|
||||
location_type = "Vault"
|
||||
location_id = vault_ids
|
||||
vault_ids += 1
|
||||
elif check_name in laser_names:
|
||||
location_type = "Laser"
|
||||
location_id = laser_ids
|
||||
laser_ids += 1
|
||||
else:
|
||||
location_type = "General"
|
||||
|
||||
if check_hex == "0x012D7": # Compatibility
|
||||
normal_panel_ids += 1
|
||||
|
||||
if check_hex == "0x17E07": # Compatibility
|
||||
location_id = 112
|
||||
|
||||
elif check_hex == "0xFFF00":
|
||||
location_id = 800
|
||||
|
||||
else:
|
||||
location_id = normal_panel_ids
|
||||
normal_panel_ids += 1
|
||||
|
||||
required_items = parse_lambda(required_item_lambda)
|
||||
items_actually_in_the_game = {item[0] for item in self.ALL_SYMBOL_ITEMS}
|
||||
required_items = set(
|
||||
subset.intersection(items_actually_in_the_game)
|
||||
for subset in required_items
|
||||
)
|
||||
|
||||
doors_in_the_game = self.ALL_DOOR_ITEM_IDS_BY_HEX.keys()
|
||||
if check_hex in doors_in_the_game:
|
||||
door_name = current_region["shortName"] + " " + check_name + " Power On"
|
||||
if check_hex in self.DOOR_NAMES_BY_HEX.keys():
|
||||
door_name = self.DOOR_NAMES_BY_HEX[check_hex]
|
||||
|
||||
required_items = set(
|
||||
subset.union(frozenset({door_name}))
|
||||
for subset in required_items
|
||||
)
|
||||
|
||||
self.ALL_DOOR_ITEMS.add(
|
||||
(door_name, self.ALL_DOOR_ITEM_IDS_BY_HEX[check_hex])
|
||||
)
|
||||
|
||||
required_items = frozenset(required_items)
|
||||
|
||||
@@ -182,7 +148,7 @@ class StaticWitnessLogic:
|
||||
"checkName": current_region["shortName"] + " " + check_name,
|
||||
"checkHex": check_hex,
|
||||
"region": current_region,
|
||||
"idOffset": location_id,
|
||||
"id": int(location_id),
|
||||
"panelType": location_type
|
||||
}
|
||||
|
||||
|
||||
@@ -98,9 +98,39 @@ def get_adjustment_file(adjustment_file):
|
||||
|
||||
@cache_argsless
|
||||
def get_disable_unrandomized_list():
|
||||
return get_adjustment_file("Disable_Unrandomized.txt")
|
||||
return get_adjustment_file("settings/Disable_Unrandomized.txt")
|
||||
|
||||
|
||||
@cache_argsless
|
||||
def get_early_utm_list():
|
||||
return get_adjustment_file("Early_UTM.txt")
|
||||
return get_adjustment_file("settings/Early_UTM.txt")
|
||||
|
||||
|
||||
@cache_argsless
|
||||
def get_symbol_shuffle_list():
|
||||
return get_adjustment_file("settings/Symbol_Shuffle.txt")
|
||||
|
||||
|
||||
@cache_argsless
|
||||
def get_door_panel_shuffle_list():
|
||||
return get_adjustment_file("settings/Door_Panel_Shuffle.txt")
|
||||
|
||||
|
||||
@cache_argsless
|
||||
def get_doors_simple_list():
|
||||
return get_adjustment_file("settings/Doors_Simple.txt")
|
||||
|
||||
|
||||
@cache_argsless
|
||||
def get_doors_complex_list():
|
||||
return get_adjustment_file("settings/Doors_Complex.txt")
|
||||
|
||||
|
||||
@cache_argsless
|
||||
def get_doors_max_list():
|
||||
return get_adjustment_file("settings/Doors_Max.txt")
|
||||
|
||||
|
||||
@cache_argsless
|
||||
def get_laser_shuffle():
|
||||
return get_adjustment_file("settings/Laser_Shuffle.txt")
|
||||
|
||||
Reference in New Issue
Block a user