Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4aed2be93b | ||
|
|
974bab2b24 | ||
|
|
98d61b32af | ||
|
|
4141a50d8c | ||
|
|
93c18cd9a7 | ||
|
|
5af47425b0 | ||
|
|
b41a1e69b4 | ||
|
|
124113f3d3 | ||
|
|
2b69820619 | ||
|
|
f147f9e5a0 | ||
|
|
db7c0c9db9 | ||
|
|
b40fba0840 | ||
|
|
ea799c494e | ||
|
|
b4b8426def | ||
|
|
39a50da55c | ||
|
|
9931605f94 | ||
|
|
8834ba88aa | ||
|
|
5e46967b7d | ||
|
|
638d6807db | ||
|
|
d471dcc067 | ||
|
|
4a27fae1ab | ||
|
|
794959e182 | ||
|
|
aff852fb45 | ||
|
|
a0eea3a650 | ||
|
|
0012584e51 | ||
|
|
6e02a4ca3c | ||
|
|
2ef05a1799 | ||
|
|
fa2891f785 | ||
|
|
d5d13a6d4d | ||
|
|
b24037e9d9 | ||
|
|
6d6de4a98e | ||
|
|
0e7c7bd1bf | ||
|
|
9312f14ffb | ||
|
|
ce8f07b347 | ||
|
|
cff6c7c4da | ||
|
|
f9120c620f | ||
|
|
44f1a93d31 | ||
|
|
6d61eae522 | ||
|
|
f05a9ecd2f | ||
|
|
648d682add | ||
|
|
47cf3e06c0 | ||
|
|
fdac50523b | ||
|
|
7522a32ad6 | ||
|
|
8ee743ac8a | ||
|
|
c3cfbf8e1c | ||
|
|
1756a30acc | ||
|
|
57c13ff273 | ||
|
|
3d9837678c | ||
|
|
3e95ccd06c | ||
|
|
0e21a3e121 | ||
|
|
5eef7a34d3 | ||
|
|
6c844750ae | ||
|
|
8649b15787 | ||
|
|
fbd64651e4 | ||
|
|
e01eb4e00c | ||
|
|
72b44be41c | ||
|
|
2bdb1b2029 | ||
|
|
bf685dc850 | ||
|
|
faf4887616 | ||
|
|
a1418ccb66 | ||
|
|
29f8053d6e | ||
|
|
f6dafa2b56 | ||
|
|
2b9e8fa273 | ||
|
|
5368451867 |
8
Fill.py
@@ -840,12 +840,12 @@ def distribute_planned(world: MultiWorld) -> None:
|
|||||||
|
|
||||||
if "early_locations" in locations:
|
if "early_locations" in locations:
|
||||||
locations.remove("early_locations")
|
locations.remove("early_locations")
|
||||||
for player in worlds:
|
for target_player in worlds:
|
||||||
locations += early_locations[player]
|
locations += early_locations[target_player]
|
||||||
if "non_early_locations" in locations:
|
if "non_early_locations" in locations:
|
||||||
locations.remove("non_early_locations")
|
locations.remove("non_early_locations")
|
||||||
for player in worlds:
|
for target_player in worlds:
|
||||||
locations += non_early_locations[player]
|
locations += non_early_locations[target_player]
|
||||||
|
|
||||||
block['locations'] = locations
|
block['locations'] = locations
|
||||||
|
|
||||||
|
|||||||
15
Main.py
@@ -139,7 +139,13 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
|||||||
exclusion_rules(world, player, world.exclude_locations[player].value)
|
exclusion_rules(world, player, world.exclude_locations[player].value)
|
||||||
world.priority_locations[player].value -= world.exclude_locations[player].value
|
world.priority_locations[player].value -= world.exclude_locations[player].value
|
||||||
for location_name in world.priority_locations[player].value:
|
for location_name in world.priority_locations[player].value:
|
||||||
world.get_location(location_name, player).progress_type = LocationProgressType.PRIORITY
|
try:
|
||||||
|
location = world.get_location(location_name, player)
|
||||||
|
except KeyError as e: # failed to find the given location. Check if it's a legitimate location
|
||||||
|
if location_name not in world.worlds[player].location_name_to_id:
|
||||||
|
raise Exception(f"Unable to prioritize location {location_name} in player {player}'s world.") from e
|
||||||
|
else:
|
||||||
|
location.progress_type = LocationProgressType.PRIORITY
|
||||||
|
|
||||||
# Set local and non-local item rules.
|
# Set local and non-local item rules.
|
||||||
if world.players > 1:
|
if world.players > 1:
|
||||||
@@ -159,7 +165,8 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
|||||||
for player, items in depletion_pool.items():
|
for player, items in depletion_pool.items():
|
||||||
player_world: AutoWorld.World = world.worlds[player]
|
player_world: AutoWorld.World = world.worlds[player]
|
||||||
for count in items.values():
|
for count in items.values():
|
||||||
new_items.append(player_world.create_filler())
|
for _ in range(count):
|
||||||
|
new_items.append(player_world.create_filler())
|
||||||
target: int = sum(sum(items.values()) for items in depletion_pool.values())
|
target: int = sum(sum(items.values()) for items in depletion_pool.values())
|
||||||
for i, item in enumerate(world.itempool):
|
for i, item in enumerate(world.itempool):
|
||||||
if depletion_pool[item.player].get(item.name, 0):
|
if depletion_pool[item.player].get(item.name, 0):
|
||||||
@@ -179,6 +186,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
|||||||
if remaining_items:
|
if remaining_items:
|
||||||
raise Exception(f"{world.get_player_name(player)}"
|
raise Exception(f"{world.get_player_name(player)}"
|
||||||
f" is trying to remove items from their pool that don't exist: {remaining_items}")
|
f" is trying to remove items from their pool that don't exist: {remaining_items}")
|
||||||
|
assert len(world.itempool) == len(new_items), "Item Pool amounts should not change."
|
||||||
world.itempool[:] = new_items
|
world.itempool[:] = new_items
|
||||||
|
|
||||||
# temporary home for item links, should be moved out of Main
|
# temporary home for item links, should be moved out of Main
|
||||||
@@ -392,7 +400,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
|||||||
f.write(bytes([3])) # version of format
|
f.write(bytes([3])) # version of format
|
||||||
f.write(multidata)
|
f.write(multidata)
|
||||||
|
|
||||||
multidata_task = pool.submit(write_multidata)
|
output_file_futures.append(pool.submit(write_multidata))
|
||||||
if not check_accessibility_task.result():
|
if not check_accessibility_task.result():
|
||||||
if not world.can_beat_game():
|
if not world.can_beat_game():
|
||||||
raise Exception("Game appears as unbeatable. Aborting.")
|
raise Exception("Game appears as unbeatable. Aborting.")
|
||||||
@@ -400,7 +408,6 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
|||||||
logger.warning("Location Accessibility requirements not fulfilled.")
|
logger.warning("Location Accessibility requirements not fulfilled.")
|
||||||
|
|
||||||
# retrieve exceptions via .result() if they occurred.
|
# retrieve exceptions via .result() if they occurred.
|
||||||
multidata_task.result()
|
|
||||||
for i, future in enumerate(concurrent.futures.as_completed(output_file_futures), start=1):
|
for i, future in enumerate(concurrent.futures.as_completed(output_file_futures), start=1):
|
||||||
if i % 10 == 0 or i == len(output_file_futures):
|
if i % 10 == 0 or i == len(output_file_futures):
|
||||||
logger.info(f'Generating output files ({i}/{len(output_file_futures)}).')
|
logger.info(f'Generating output files ({i}/{len(output_file_futures)}).')
|
||||||
|
|||||||
24
NetUtils.py
@@ -407,14 +407,22 @@ class _LocationStore(dict, typing.MutableMapping[int, typing.Dict[int, typing.Tu
|
|||||||
if typing.TYPE_CHECKING: # type-check with pure python implementation until we have a typing stub
|
if typing.TYPE_CHECKING: # type-check with pure python implementation until we have a typing stub
|
||||||
LocationStore = _LocationStore
|
LocationStore = _LocationStore
|
||||||
else:
|
else:
|
||||||
try:
|
|
||||||
import pyximport
|
|
||||||
pyximport.install()
|
|
||||||
except ImportError:
|
|
||||||
pyximport = None
|
|
||||||
try:
|
try:
|
||||||
from _speedups import LocationStore
|
from _speedups import LocationStore
|
||||||
|
import _speedups
|
||||||
|
import os.path
|
||||||
|
if os.path.isfile("_speedups.pyx") and os.path.getctime(_speedups.__file__) < os.path.getctime("_speedups.pyx"):
|
||||||
|
warnings.warn(f"{_speedups.__file__} outdated! "
|
||||||
|
f"Please rebuild with `cythonize -b -i _speedups.pyx` or delete it!")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
warnings.warn("_speedups not available. Falling back to pure python LocationStore. "
|
try:
|
||||||
"Install a matching C++ compiler for your platform to compile _speedups.")
|
import pyximport
|
||||||
LocationStore = _LocationStore
|
pyximport.install()
|
||||||
|
except ImportError:
|
||||||
|
pyximport = None
|
||||||
|
try:
|
||||||
|
from _speedups import LocationStore
|
||||||
|
except ImportError:
|
||||||
|
warnings.warn("_speedups not available. Falling back to pure python LocationStore. "
|
||||||
|
"Install a matching C++ compiler for your platform to compile _speedups.")
|
||||||
|
LocationStore = _LocationStore
|
||||||
|
|||||||
@@ -68,12 +68,11 @@ class SNIClientCommandProcessor(ClientCommandProcessor):
|
|||||||
options = snes_options.split()
|
options = snes_options.split()
|
||||||
num_options = len(options)
|
num_options = len(options)
|
||||||
|
|
||||||
if num_options > 0:
|
|
||||||
snes_device_number = int(options[0])
|
|
||||||
|
|
||||||
if num_options > 1:
|
if num_options > 1:
|
||||||
snes_address = options[0]
|
snes_address = options[0]
|
||||||
snes_device_number = int(options[1])
|
snes_device_number = int(options[1])
|
||||||
|
elif num_options > 0:
|
||||||
|
snes_device_number = int(options[0])
|
||||||
|
|
||||||
self.ctx.snes_reconnect_address = None
|
self.ctx.snes_reconnect_address = None
|
||||||
if self.ctx.snes_connect_task:
|
if self.ctx.snes_connect_task:
|
||||||
|
|||||||
1050
Starcraft2Client.py
@@ -29,31 +29,31 @@ class UndertaleCommandProcessor(ClientCommandProcessor):
|
|||||||
def _cmd_patch(self):
|
def _cmd_patch(self):
|
||||||
"""Patch the game."""
|
"""Patch the game."""
|
||||||
if isinstance(self.ctx, UndertaleContext):
|
if isinstance(self.ctx, UndertaleContext):
|
||||||
os.makedirs(name=os.getcwd() + "\\Undertale", exist_ok=True)
|
os.makedirs(name=os.path.join(os.getcwd(), "Undertale"), exist_ok=True)
|
||||||
self.ctx.patch_game()
|
self.ctx.patch_game()
|
||||||
self.output("Patched.")
|
self.output("Patched.")
|
||||||
|
|
||||||
def _cmd_savepath(self, directory: str):
|
def _cmd_savepath(self, directory: str):
|
||||||
"""Redirect to proper save data folder. (Use before connecting!)"""
|
"""Redirect to proper save data folder. (Use before connecting!)"""
|
||||||
if isinstance(self.ctx, UndertaleContext):
|
if isinstance(self.ctx, UndertaleContext):
|
||||||
UndertaleContext.save_game_folder = directory
|
self.ctx.save_game_folder = directory
|
||||||
self.output("Changed to the following directory: " + directory)
|
self.output("Changed to the following directory: " + self.ctx.save_game_folder)
|
||||||
|
|
||||||
@mark_raw
|
@mark_raw
|
||||||
def _cmd_auto_patch(self, steaminstall: typing.Optional[str] = None):
|
def _cmd_auto_patch(self, steaminstall: typing.Optional[str] = None):
|
||||||
"""Patch the game automatically."""
|
"""Patch the game automatically."""
|
||||||
if isinstance(self.ctx, UndertaleContext):
|
if isinstance(self.ctx, UndertaleContext):
|
||||||
os.makedirs(name=os.getcwd() + "\\Undertale", exist_ok=True)
|
os.makedirs(name=os.path.join(os.getcwd(), "Undertale"), exist_ok=True)
|
||||||
tempInstall = steaminstall
|
tempInstall = steaminstall
|
||||||
if not os.path.isfile(os.path.join(tempInstall, "data.win")):
|
if not os.path.isfile(os.path.join(tempInstall, "data.win")):
|
||||||
tempInstall = None
|
tempInstall = None
|
||||||
if tempInstall is None:
|
if tempInstall is None:
|
||||||
tempInstall = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Undertale"
|
tempInstall = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Undertale"
|
||||||
if not os.path.exists("C:\\Program Files (x86)\\Steam\\steamapps\\common\\Undertale"):
|
if not os.path.exists(tempInstall):
|
||||||
tempInstall = "C:\\Program Files\\Steam\\steamapps\\common\\Undertale"
|
tempInstall = "C:\\Program Files\\Steam\\steamapps\\common\\Undertale"
|
||||||
elif not os.path.exists(tempInstall):
|
elif not os.path.exists(tempInstall):
|
||||||
tempInstall = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Undertale"
|
tempInstall = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Undertale"
|
||||||
if not os.path.exists("C:\\Program Files (x86)\\Steam\\steamapps\\common\\Undertale"):
|
if not os.path.exists(tempInstall):
|
||||||
tempInstall = "C:\\Program Files\\Steam\\steamapps\\common\\Undertale"
|
tempInstall = "C:\\Program Files\\Steam\\steamapps\\common\\Undertale"
|
||||||
if not os.path.exists(tempInstall) or not os.path.exists(tempInstall) or not os.path.isfile(os.path.join(tempInstall, "data.win")):
|
if not os.path.exists(tempInstall) or not os.path.exists(tempInstall) or not os.path.isfile(os.path.join(tempInstall, "data.win")):
|
||||||
self.output("ERROR: Cannot find Undertale. Please rerun the command with the correct folder."
|
self.output("ERROR: Cannot find Undertale. Please rerun the command with the correct folder."
|
||||||
@@ -61,8 +61,8 @@ class UndertaleCommandProcessor(ClientCommandProcessor):
|
|||||||
else:
|
else:
|
||||||
for file_name in os.listdir(tempInstall):
|
for file_name in os.listdir(tempInstall):
|
||||||
if file_name != "steam_api.dll":
|
if file_name != "steam_api.dll":
|
||||||
shutil.copy(tempInstall+"\\"+file_name,
|
shutil.copy(os.path.join(tempInstall, file_name),
|
||||||
os.getcwd() + "\\Undertale\\" + file_name)
|
os.path.join(os.getcwd(), "Undertale", file_name))
|
||||||
self.ctx.patch_game()
|
self.ctx.patch_game()
|
||||||
self.output("Patching successful!")
|
self.output("Patching successful!")
|
||||||
|
|
||||||
@@ -111,13 +111,13 @@ class UndertaleContext(CommonContext):
|
|||||||
self.save_game_folder = os.path.expandvars(r"%localappdata%/UNDERTALE")
|
self.save_game_folder = os.path.expandvars(r"%localappdata%/UNDERTALE")
|
||||||
|
|
||||||
def patch_game(self):
|
def patch_game(self):
|
||||||
with open(os.getcwd() + "/Undertale/data.win", "rb") as f:
|
with open(os.path.join(os.getcwd(), "Undertale", "data.win"), "rb") as f:
|
||||||
patchedFile = bsdiff4.patch(f.read(), undertale.data_path("patch.bsdiff"))
|
patchedFile = bsdiff4.patch(f.read(), undertale.data_path("patch.bsdiff"))
|
||||||
with open(os.getcwd() + "/Undertale/data.win", "wb") as f:
|
with open(os.path.join(os.getcwd(), "Undertale", "data.win"), "wb") as f:
|
||||||
f.write(patchedFile)
|
f.write(patchedFile)
|
||||||
os.makedirs(name=os.getcwd() + "\\Undertale\\" + "Custom Sprites", exist_ok=True)
|
os.makedirs(name=os.path.join(os.getcwd(), "Undertale", "Custom Sprites"), exist_ok=True)
|
||||||
with open(os.path.expandvars(os.getcwd() + "\\Undertale\\" + "Custom Sprites\\" +
|
with open(os.path.expandvars(os.path.join(os.getcwd(), "Undertale", "Custom Sprites",
|
||||||
"Which Character.txt"), "w") as f:
|
"Which Character.txt")), "w") as f:
|
||||||
f.writelines(["// Put the folder name of the sprites you want to play as, make sure it is the only "
|
f.writelines(["// Put the folder name of the sprites you want to play as, make sure it is the only "
|
||||||
"line other than this one.\n", "frisk"])
|
"line other than this one.\n", "frisk"])
|
||||||
f.close()
|
f.close()
|
||||||
@@ -385,7 +385,7 @@ async def multi_watcher(ctx: UndertaleContext):
|
|||||||
for root, dirs, files in os.walk(path):
|
for root, dirs, files in os.walk(path):
|
||||||
for file in files:
|
for file in files:
|
||||||
if "spots.mine" in file and "Online" in ctx.tags:
|
if "spots.mine" in file and "Online" in ctx.tags:
|
||||||
with open(root + "/" + file, "r") as mine:
|
with open(os.path.join(root, file), "r") as mine:
|
||||||
this_x = mine.readline()
|
this_x = mine.readline()
|
||||||
this_y = mine.readline()
|
this_y = mine.readline()
|
||||||
this_room = mine.readline()
|
this_room = mine.readline()
|
||||||
@@ -408,7 +408,7 @@ async def game_watcher(ctx: UndertaleContext):
|
|||||||
for root, dirs, files in os.walk(path):
|
for root, dirs, files in os.walk(path):
|
||||||
for file in files:
|
for file in files:
|
||||||
if ".item" in file:
|
if ".item" in file:
|
||||||
os.remove(root+"/"+file)
|
os.remove(os.path.join(root, file))
|
||||||
sync_msg = [{"cmd": "Sync"}]
|
sync_msg = [{"cmd": "Sync"}]
|
||||||
if ctx.locations_checked:
|
if ctx.locations_checked:
|
||||||
sync_msg.append({"cmd": "LocationChecks", "locations": list(ctx.locations_checked)})
|
sync_msg.append({"cmd": "LocationChecks", "locations": list(ctx.locations_checked)})
|
||||||
@@ -424,13 +424,13 @@ async def game_watcher(ctx: UndertaleContext):
|
|||||||
for root, dirs, files in os.walk(path):
|
for root, dirs, files in os.walk(path):
|
||||||
for file in files:
|
for file in files:
|
||||||
if "DontBeMad.mad" in file:
|
if "DontBeMad.mad" in file:
|
||||||
os.remove(root+"/"+file)
|
os.remove(os.path.join(root, file))
|
||||||
if "DeathLink" in ctx.tags:
|
if "DeathLink" in ctx.tags:
|
||||||
await ctx.send_death()
|
await ctx.send_death()
|
||||||
if "scout" == file:
|
if "scout" == file:
|
||||||
sending = []
|
sending = []
|
||||||
try:
|
try:
|
||||||
with open(root+"/"+file, "r") as f:
|
with open(os.path.join(root, file), "r") as f:
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
for l in lines:
|
for l in lines:
|
||||||
if ctx.server_locations.__contains__(int(l)+12000):
|
if ctx.server_locations.__contains__(int(l)+12000):
|
||||||
@@ -438,11 +438,11 @@ async def game_watcher(ctx: UndertaleContext):
|
|||||||
finally:
|
finally:
|
||||||
await ctx.send_msgs([{"cmd": "LocationScouts", "locations": sending,
|
await ctx.send_msgs([{"cmd": "LocationScouts", "locations": sending,
|
||||||
"create_as_hint": int(2)}])
|
"create_as_hint": int(2)}])
|
||||||
os.remove(root+"/"+file)
|
os.remove(os.path.join(root, file))
|
||||||
if "check.spot" in file:
|
if "check.spot" in file:
|
||||||
sending = []
|
sending = []
|
||||||
try:
|
try:
|
||||||
with open(root+"/"+file, "r") as f:
|
with open(os.path.join(root, file), "r") as f:
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
for l in lines:
|
for l in lines:
|
||||||
sending = sending+[(int(l.rstrip('\n')))+12000]
|
sending = sending+[(int(l.rstrip('\n')))+12000]
|
||||||
@@ -451,7 +451,7 @@ async def game_watcher(ctx: UndertaleContext):
|
|||||||
if "victory" in file and str(ctx.route) in file:
|
if "victory" in file and str(ctx.route) in file:
|
||||||
victory = True
|
victory = True
|
||||||
if ".playerspot" in file and "Online" not in ctx.tags:
|
if ".playerspot" in file and "Online" not in ctx.tags:
|
||||||
os.remove(root+"/"+file)
|
os.remove(os.path.join(root, file))
|
||||||
if "victory" in file:
|
if "victory" in file:
|
||||||
if str(ctx.route) == "all_routes":
|
if str(ctx.route) == "all_routes":
|
||||||
if "neutral" in file and ctx.completed_routes["neutral"] != 1:
|
if "neutral" in file and ctx.completed_routes["neutral"] != 1:
|
||||||
|
|||||||
29
Utils.py
@@ -44,7 +44,7 @@ class Version(typing.NamedTuple):
|
|||||||
return ".".join(str(item) for item in self)
|
return ".".join(str(item) for item in self)
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.4.2"
|
__version__ = "0.4.3"
|
||||||
version_tuple = tuplize_version(__version__)
|
version_tuple = tuplize_version(__version__)
|
||||||
|
|
||||||
is_linux = sys.platform.startswith("linux")
|
is_linux = sys.platform.startswith("linux")
|
||||||
@@ -359,11 +359,13 @@ safe_builtins = frozenset((
|
|||||||
|
|
||||||
|
|
||||||
class RestrictedUnpickler(pickle.Unpickler):
|
class RestrictedUnpickler(pickle.Unpickler):
|
||||||
|
generic_properties_module: Optional[object]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(RestrictedUnpickler, self).__init__(*args, **kwargs)
|
super(RestrictedUnpickler, self).__init__(*args, **kwargs)
|
||||||
self.options_module = importlib.import_module("Options")
|
self.options_module = importlib.import_module("Options")
|
||||||
self.net_utils_module = importlib.import_module("NetUtils")
|
self.net_utils_module = importlib.import_module("NetUtils")
|
||||||
self.generic_properties_module = importlib.import_module("worlds.generic")
|
self.generic_properties_module = None
|
||||||
|
|
||||||
def find_class(self, module, name):
|
def find_class(self, module, name):
|
||||||
if module == "builtins" and name in safe_builtins:
|
if module == "builtins" and name in safe_builtins:
|
||||||
@@ -373,6 +375,8 @@ class RestrictedUnpickler(pickle.Unpickler):
|
|||||||
return getattr(self.net_utils_module, name)
|
return getattr(self.net_utils_module, name)
|
||||||
# Options and Plando are unpickled by WebHost -> Generate
|
# Options and Plando are unpickled by WebHost -> Generate
|
||||||
if module == "worlds.generic" and name in {"PlandoItem", "PlandoConnection"}:
|
if module == "worlds.generic" and name in {"PlandoItem", "PlandoConnection"}:
|
||||||
|
if not self.generic_properties_module:
|
||||||
|
self.generic_properties_module = importlib.import_module("worlds.generic")
|
||||||
return getattr(self.generic_properties_module, name)
|
return getattr(self.generic_properties_module, name)
|
||||||
# pep 8 specifies that modules should have "all-lowercase names" (options, not Options)
|
# pep 8 specifies that modules should have "all-lowercase names" (options, not Options)
|
||||||
if module.lower().endswith("options"):
|
if module.lower().endswith("options"):
|
||||||
@@ -572,7 +576,7 @@ def open_filename(title: str, filetypes: typing.Sequence[typing.Tuple[str, typin
|
|||||||
zenity = which("zenity")
|
zenity = which("zenity")
|
||||||
if zenity:
|
if zenity:
|
||||||
z_filters = (f'--file-filter={text} ({", ".join(ext)}) | *{" *".join(ext)}' for (text, ext) in filetypes)
|
z_filters = (f'--file-filter={text} ({", ".join(ext)}) | *{" *".join(ext)}' for (text, ext) in filetypes)
|
||||||
selection = (f'--filename="{suggest}',) if suggest else ()
|
selection = (f"--filename={suggest}",) if suggest else ()
|
||||||
return run(zenity, f"--title={title}", "--file-selection", *z_filters, *selection)
|
return run(zenity, f"--title={title}", "--file-selection", *z_filters, *selection)
|
||||||
|
|
||||||
# fall back to tk
|
# fall back to tk
|
||||||
@@ -584,7 +588,10 @@ def open_filename(title: str, filetypes: typing.Sequence[typing.Tuple[str, typin
|
|||||||
f'This attempt was made because open_filename was used for "{title}".')
|
f'This attempt was made because open_filename was used for "{title}".')
|
||||||
raise e
|
raise e
|
||||||
else:
|
else:
|
||||||
root = tkinter.Tk()
|
try:
|
||||||
|
root = tkinter.Tk()
|
||||||
|
except tkinter.TclError:
|
||||||
|
return None # GUI not available. None is the same as a user clicking "cancel"
|
||||||
root.withdraw()
|
root.withdraw()
|
||||||
return tkinter.filedialog.askopenfilename(title=title, filetypes=((t[0], ' '.join(t[1])) for t in filetypes),
|
return tkinter.filedialog.askopenfilename(title=title, filetypes=((t[0], ' '.join(t[1])) for t in filetypes),
|
||||||
initialfile=suggest or None)
|
initialfile=suggest or None)
|
||||||
@@ -597,13 +604,14 @@ def open_directory(title: str, suggest: str = "") -> typing.Optional[str]:
|
|||||||
if is_linux:
|
if is_linux:
|
||||||
# prefer native dialog
|
# prefer native dialog
|
||||||
from shutil import which
|
from shutil import which
|
||||||
kdialog = None#which("kdialog")
|
kdialog = which("kdialog")
|
||||||
if kdialog:
|
if kdialog:
|
||||||
return run(kdialog, f"--title={title}", "--getexistingdirectory", suggest or ".")
|
return run(kdialog, f"--title={title}", "--getexistingdirectory",
|
||||||
zenity = None#which("zenity")
|
os.path.abspath(suggest) if suggest else ".")
|
||||||
|
zenity = which("zenity")
|
||||||
if zenity:
|
if zenity:
|
||||||
z_filters = ("--directory",)
|
z_filters = ("--directory",)
|
||||||
selection = (f'--filename="{suggest}',) if suggest else ()
|
selection = (f"--filename={os.path.abspath(suggest)}/",) if suggest else ()
|
||||||
return run(zenity, f"--title={title}", "--file-selection", *z_filters, *selection)
|
return run(zenity, f"--title={title}", "--file-selection", *z_filters, *selection)
|
||||||
|
|
||||||
# fall back to tk
|
# fall back to tk
|
||||||
@@ -615,7 +623,10 @@ def open_directory(title: str, suggest: str = "") -> typing.Optional[str]:
|
|||||||
f'This attempt was made because open_filename was used for "{title}".')
|
f'This attempt was made because open_filename was used for "{title}".')
|
||||||
raise e
|
raise e
|
||||||
else:
|
else:
|
||||||
root = tkinter.Tk()
|
try:
|
||||||
|
root = tkinter.Tk()
|
||||||
|
except tkinter.TclError:
|
||||||
|
return None # GUI not available. None is the same as a user clicking "cancel"
|
||||||
root.withdraw()
|
root.withdraw()
|
||||||
return tkinter.filedialog.askdirectory(title=title, mustexist=True, initialdir=suggest or None)
|
return tkinter.filedialog.askdirectory(title=title, mustexist=True, initialdir=suggest or None)
|
||||||
|
|
||||||
|
|||||||
19
WebHost.py
@@ -13,15 +13,6 @@ import Utils
|
|||||||
import settings
|
import settings
|
||||||
|
|
||||||
Utils.local_path.cached_path = os.path.dirname(__file__) or "." # py3.8 is not abs. remove "." when dropping 3.8
|
Utils.local_path.cached_path = os.path.dirname(__file__) or "." # py3.8 is not abs. remove "." when dropping 3.8
|
||||||
|
|
||||||
from WebHostLib import register, app as raw_app
|
|
||||||
from waitress import serve
|
|
||||||
|
|
||||||
from WebHostLib.models import db
|
|
||||||
from WebHostLib.autolauncher import autohost, autogen
|
|
||||||
from WebHostLib.lttpsprites import update_sprites_lttp
|
|
||||||
from WebHostLib.options import create as create_options_files
|
|
||||||
|
|
||||||
settings.no_gui = True
|
settings.no_gui = True
|
||||||
configpath = os.path.abspath("config.yaml")
|
configpath = os.path.abspath("config.yaml")
|
||||||
if not os.path.exists(configpath): # fall back to config.yaml in home
|
if not os.path.exists(configpath): # fall back to config.yaml in home
|
||||||
@@ -29,6 +20,9 @@ if not os.path.exists(configpath): # fall back to config.yaml in home
|
|||||||
|
|
||||||
|
|
||||||
def get_app():
|
def get_app():
|
||||||
|
from WebHostLib import register, cache, app as raw_app
|
||||||
|
from WebHostLib.models import db
|
||||||
|
|
||||||
register()
|
register()
|
||||||
app = raw_app
|
app = raw_app
|
||||||
if os.path.exists(configpath) and not app.config["TESTING"]:
|
if os.path.exists(configpath) and not app.config["TESTING"]:
|
||||||
@@ -40,6 +34,7 @@ def get_app():
|
|||||||
app.config["HOST_ADDRESS"] = Utils.get_public_ipv4()
|
app.config["HOST_ADDRESS"] = Utils.get_public_ipv4()
|
||||||
logging.info(f"HOST_ADDRESS was set to {app.config['HOST_ADDRESS']}")
|
logging.info(f"HOST_ADDRESS was set to {app.config['HOST_ADDRESS']}")
|
||||||
|
|
||||||
|
cache.init_app(app)
|
||||||
db.bind(**app.config["PONY"])
|
db.bind(**app.config["PONY"])
|
||||||
db.generate_mapping(create_tables=True)
|
db.generate_mapping(create_tables=True)
|
||||||
return app
|
return app
|
||||||
@@ -120,6 +115,11 @@ if __name__ == "__main__":
|
|||||||
multiprocessing.freeze_support()
|
multiprocessing.freeze_support()
|
||||||
multiprocessing.set_start_method('spawn')
|
multiprocessing.set_start_method('spawn')
|
||||||
logging.basicConfig(format='[%(asctime)s] %(message)s', level=logging.INFO)
|
logging.basicConfig(format='[%(asctime)s] %(message)s', level=logging.INFO)
|
||||||
|
|
||||||
|
from WebHostLib.lttpsprites import update_sprites_lttp
|
||||||
|
from WebHostLib.autolauncher import autohost, autogen
|
||||||
|
from WebHostLib.options import create as create_options_files
|
||||||
|
|
||||||
try:
|
try:
|
||||||
update_sprites_lttp()
|
update_sprites_lttp()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -136,4 +136,5 @@ if __name__ == "__main__":
|
|||||||
if app.config["DEBUG"]:
|
if app.config["DEBUG"]:
|
||||||
app.run(debug=True, port=app.config["PORT"])
|
app.run(debug=True, port=app.config["PORT"])
|
||||||
else:
|
else:
|
||||||
|
from waitress import serve
|
||||||
serve(app, port=app.config["PORT"], threads=app.config["WAITRESS_THREADS"])
|
serve(app, port=app.config["PORT"], threads=app.config["WAITRESS_THREADS"])
|
||||||
|
|||||||
@@ -49,11 +49,11 @@ app.config["PONY"] = {
|
|||||||
'create_db': True
|
'create_db': True
|
||||||
}
|
}
|
||||||
app.config["MAX_ROLL"] = 20
|
app.config["MAX_ROLL"] = 20
|
||||||
app.config["CACHE_TYPE"] = "flask_caching.backends.SimpleCache"
|
app.config["CACHE_TYPE"] = "SimpleCache"
|
||||||
app.config["JSON_AS_ASCII"] = False
|
app.config["JSON_AS_ASCII"] = False
|
||||||
app.config["HOST_ADDRESS"] = ""
|
app.config["HOST_ADDRESS"] = ""
|
||||||
|
|
||||||
cache = Cache(app)
|
cache = Cache()
|
||||||
Compress(app)
|
Compress(app)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ from __future__ import annotations
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import typing
|
import typing
|
||||||
@@ -13,55 +11,7 @@ from datetime import timedelta, datetime
|
|||||||
from pony.orm import db_session, select, commit
|
from pony.orm import db_session, select, commit
|
||||||
|
|
||||||
from Utils import restricted_loads
|
from Utils import restricted_loads
|
||||||
|
from .locker import Locker, AlreadyRunningException
|
||||||
|
|
||||||
class CommonLocker():
|
|
||||||
"""Uses a file lock to signal that something is already running"""
|
|
||||||
lock_folder = "file_locks"
|
|
||||||
|
|
||||||
def __init__(self, lockname: str, folder=None):
|
|
||||||
if folder:
|
|
||||||
self.lock_folder = folder
|
|
||||||
os.makedirs(self.lock_folder, exist_ok=True)
|
|
||||||
self.lockname = lockname
|
|
||||||
self.lockfile = os.path.join(self.lock_folder, f"{self.lockname}.lck")
|
|
||||||
|
|
||||||
|
|
||||||
class AlreadyRunningException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
if sys.platform == 'win32':
|
|
||||||
class Locker(CommonLocker):
|
|
||||||
def __enter__(self):
|
|
||||||
try:
|
|
||||||
if os.path.exists(self.lockfile):
|
|
||||||
os.unlink(self.lockfile)
|
|
||||||
self.fp = os.open(
|
|
||||||
self.lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR)
|
|
||||||
except OSError as e:
|
|
||||||
raise AlreadyRunningException() from e
|
|
||||||
|
|
||||||
def __exit__(self, _type, value, tb):
|
|
||||||
fp = getattr(self, "fp", None)
|
|
||||||
if fp:
|
|
||||||
os.close(self.fp)
|
|
||||||
os.unlink(self.lockfile)
|
|
||||||
else: # unix
|
|
||||||
import fcntl
|
|
||||||
|
|
||||||
|
|
||||||
class Locker(CommonLocker):
|
|
||||||
def __enter__(self):
|
|
||||||
try:
|
|
||||||
self.fp = open(self.lockfile, "wb")
|
|
||||||
fcntl.flock(self.fp.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
||||||
except OSError as e:
|
|
||||||
raise AlreadyRunningException() from e
|
|
||||||
|
|
||||||
def __exit__(self, _type, value, tb):
|
|
||||||
fcntl.flock(self.fp.fileno(), fcntl.LOCK_UN)
|
|
||||||
self.fp.close()
|
|
||||||
|
|
||||||
|
|
||||||
def launch_room(room: Room, config: dict):
|
def launch_room(room: Room, config: dict):
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ def check():
|
|||||||
if 'file' not in request.files:
|
if 'file' not in request.files:
|
||||||
flash('No file part')
|
flash('No file part')
|
||||||
else:
|
else:
|
||||||
file = request.files['file']
|
files = request.files.getlist('file')
|
||||||
options = get_yaml_data(file)
|
options = get_yaml_data(files)
|
||||||
if isinstance(options, str):
|
if isinstance(options, str):
|
||||||
flash(options)
|
flash(options)
|
||||||
else:
|
else:
|
||||||
@@ -39,30 +39,33 @@ def mysterycheck():
|
|||||||
return redirect(url_for("check"), 301)
|
return redirect(url_for("check"), 301)
|
||||||
|
|
||||||
|
|
||||||
def get_yaml_data(file) -> Union[Dict[str, str], str, Markup]:
|
def get_yaml_data(files) -> Union[Dict[str, str], str, Markup]:
|
||||||
options = {}
|
options = {}
|
||||||
# if user does not select file, browser also
|
for file in files:
|
||||||
# submit an empty part without filename
|
# if user does not select file, browser also
|
||||||
if file.filename == '':
|
# submit an empty part without filename
|
||||||
return 'No selected file'
|
if file.filename == '':
|
||||||
elif file and allowed_file(file.filename):
|
return 'No selected file'
|
||||||
if file.filename.endswith(".zip"):
|
elif file.filename in options:
|
||||||
|
return f'Conflicting files named {file.filename} submitted'
|
||||||
|
elif file and allowed_file(file.filename):
|
||||||
|
if file.filename.endswith(".zip"):
|
||||||
|
|
||||||
with zipfile.ZipFile(file, 'r') as zfile:
|
with zipfile.ZipFile(file, 'r') as zfile:
|
||||||
infolist = zfile.infolist()
|
infolist = zfile.infolist()
|
||||||
|
|
||||||
if any(file.filename.endswith(".archipelago") for file in infolist):
|
if any(file.filename.endswith(".archipelago") for file in infolist):
|
||||||
return Markup("Error: Your .zip file contains an .archipelago file. "
|
return Markup("Error: Your .zip file contains an .archipelago file. "
|
||||||
'Did you mean to <a href="/uploads">host a game</a>?')
|
'Did you mean to <a href="/uploads">host a game</a>?')
|
||||||
|
|
||||||
for file in infolist:
|
for file in infolist:
|
||||||
if file.filename.endswith(banned_zip_contents):
|
if file.filename.endswith(banned_zip_contents):
|
||||||
return "Uploaded data contained a rom file, which is likely to contain copyrighted material. " \
|
return "Uploaded data contained a rom file, which is likely to contain copyrighted material. " \
|
||||||
"Your file was deleted."
|
"Your file was deleted."
|
||||||
elif file.filename.endswith((".yaml", ".json", ".yml", ".txt")):
|
elif file.filename.endswith((".yaml", ".json", ".yml", ".txt")):
|
||||||
options[file.filename] = zfile.open(file, "r").read()
|
options[file.filename] = zfile.open(file, "r").read()
|
||||||
else:
|
else:
|
||||||
options = {file.filename: file.read()}
|
options[file.filename] = file.read()
|
||||||
if not options:
|
if not options:
|
||||||
return "Did not find a .yaml file to process."
|
return "Did not find a .yaml file to process."
|
||||||
return options
|
return options
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import Utils
|
|||||||
|
|
||||||
from MultiServer import Context, server, auto_shutdown, ServerCommandProcessor, ClientMessageProcessor, load_server_cert
|
from MultiServer import Context, server, auto_shutdown, ServerCommandProcessor, ClientMessageProcessor, load_server_cert
|
||||||
from Utils import restricted_loads, cache_argsless
|
from Utils import restricted_loads, cache_argsless
|
||||||
|
from .locker import Locker
|
||||||
from .models import Command, GameDataPackage, Room, db
|
from .models import Command, GameDataPackage, Room, db
|
||||||
|
|
||||||
|
|
||||||
@@ -163,16 +164,19 @@ def run_server_process(room_id, ponyconfig: dict, static_server_data: dict,
|
|||||||
db.generate_mapping(check_tables=False)
|
db.generate_mapping(check_tables=False)
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
|
import gc
|
||||||
|
|
||||||
Utils.init_logging(str(room_id), write_mode="a")
|
Utils.init_logging(str(room_id), write_mode="a")
|
||||||
ctx = WebHostContext(static_server_data)
|
ctx = WebHostContext(static_server_data)
|
||||||
ctx.load(room_id)
|
ctx.load(room_id)
|
||||||
ctx.init_save()
|
ctx.init_save()
|
||||||
ssl_context = load_server_cert(cert_file, cert_key_file) if cert_file else None
|
ssl_context = load_server_cert(cert_file, cert_key_file) if cert_file else None
|
||||||
|
gc.collect() # free intermediate objects used during setup
|
||||||
try:
|
try:
|
||||||
ctx.server = websockets.serve(functools.partial(server, ctx=ctx), ctx.host, ctx.port, ssl=ssl_context)
|
ctx.server = websockets.serve(functools.partial(server, ctx=ctx), ctx.host, ctx.port, ssl=ssl_context)
|
||||||
|
|
||||||
await ctx.server
|
await ctx.server
|
||||||
except Exception: # likely port in use - in windows this is OSError, but I didn't check the others
|
except OSError: # likely port in use
|
||||||
ctx.server = websockets.serve(functools.partial(server, ctx=ctx), ctx.host, 0, ssl=ssl_context)
|
ctx.server = websockets.serve(functools.partial(server, ctx=ctx), ctx.host, 0, ssl=ssl_context)
|
||||||
|
|
||||||
await ctx.server
|
await ctx.server
|
||||||
@@ -198,16 +202,15 @@ def run_server_process(room_id, ponyconfig: dict, static_server_data: dict,
|
|||||||
await ctx.shutdown_task
|
await ctx.shutdown_task
|
||||||
logging.info("Shutting down")
|
logging.info("Shutting down")
|
||||||
|
|
||||||
from .autolauncher import Locker
|
|
||||||
with Locker(room_id):
|
with Locker(room_id):
|
||||||
try:
|
try:
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
except KeyboardInterrupt:
|
except (KeyboardInterrupt, SystemExit):
|
||||||
with db_session:
|
with db_session:
|
||||||
room = Room.get(id=room_id)
|
room = Room.get(id=room_id)
|
||||||
# ensure the Room does not spin up again on its own, minute of safety buffer
|
# ensure the Room does not spin up again on its own, minute of safety buffer
|
||||||
room.last_activity = datetime.datetime.utcnow() - datetime.timedelta(minutes=1, seconds=room.timeout)
|
room.last_activity = datetime.datetime.utcnow() - datetime.timedelta(minutes=1, seconds=room.timeout)
|
||||||
except:
|
except Exception:
|
||||||
with db_session:
|
with db_session:
|
||||||
room = Room.get(id=room_id)
|
room = Room.get(id=room_id)
|
||||||
room.last_port = -1
|
room.last_port = -1
|
||||||
|
|||||||
@@ -64,8 +64,8 @@ def generate(race=False):
|
|||||||
if 'file' not in request.files:
|
if 'file' not in request.files:
|
||||||
flash('No file part')
|
flash('No file part')
|
||||||
else:
|
else:
|
||||||
file = request.files['file']
|
files = request.files.getlist('file')
|
||||||
options = get_yaml_data(file)
|
options = get_yaml_data(files)
|
||||||
if isinstance(options, str):
|
if isinstance(options, str):
|
||||||
flash(options)
|
flash(options)
|
||||||
else:
|
else:
|
||||||
|
|||||||
51
WebHostLib/locker.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
class CommonLocker:
|
||||||
|
"""Uses a file lock to signal that something is already running"""
|
||||||
|
lock_folder = "file_locks"
|
||||||
|
|
||||||
|
def __init__(self, lockname: str, folder=None):
|
||||||
|
if folder:
|
||||||
|
self.lock_folder = folder
|
||||||
|
os.makedirs(self.lock_folder, exist_ok=True)
|
||||||
|
self.lockname = lockname
|
||||||
|
self.lockfile = os.path.join(self.lock_folder, f"{self.lockname}.lck")
|
||||||
|
|
||||||
|
|
||||||
|
class AlreadyRunningException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
class Locker(CommonLocker):
|
||||||
|
def __enter__(self):
|
||||||
|
try:
|
||||||
|
if os.path.exists(self.lockfile):
|
||||||
|
os.unlink(self.lockfile)
|
||||||
|
self.fp = os.open(
|
||||||
|
self.lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR)
|
||||||
|
except OSError as e:
|
||||||
|
raise AlreadyRunningException() from e
|
||||||
|
|
||||||
|
def __exit__(self, _type, value, tb):
|
||||||
|
fp = getattr(self, "fp", None)
|
||||||
|
if fp:
|
||||||
|
os.close(self.fp)
|
||||||
|
os.unlink(self.lockfile)
|
||||||
|
else: # unix
|
||||||
|
import fcntl
|
||||||
|
|
||||||
|
|
||||||
|
class Locker(CommonLocker):
|
||||||
|
def __enter__(self):
|
||||||
|
try:
|
||||||
|
self.fp = open(self.lockfile, "wb")
|
||||||
|
fcntl.flock(self.fp.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||||
|
except OSError as e:
|
||||||
|
raise AlreadyRunningException() from e
|
||||||
|
|
||||||
|
def __exit__(self, _type, value, tb):
|
||||||
|
fcntl.flock(self.fp.fileno(), fcntl.LOCK_UN)
|
||||||
|
self.fp.close()
|
||||||
@@ -3,7 +3,8 @@ pony>=0.7.16; python_version <= '3.10'
|
|||||||
pony @ https://github.com/Berserker66/pony/releases/download/v0.7.16/pony-0.7.16-py3-none-any.whl#0.7.16 ; python_version >= '3.11'
|
pony @ https://github.com/Berserker66/pony/releases/download/v0.7.16/pony-0.7.16-py3-none-any.whl#0.7.16 ; python_version >= '3.11'
|
||||||
waitress>=2.1.2
|
waitress>=2.1.2
|
||||||
Flask-Caching>=2.0.2
|
Flask-Caching>=2.0.2
|
||||||
Flask-Compress>=1.13
|
Flask-Compress>=1.14
|
||||||
Flask-Limiter>=3.3.0
|
Flask-Limiter>=3.5.0
|
||||||
bokeh>=3.1.1
|
bokeh>=3.1.1; python_version <= '3.8'
|
||||||
|
bokeh>=3.2.2; python_version >= '3.9'
|
||||||
markupsafe>=2.1.3
|
markupsafe>=2.1.3
|
||||||
|
|||||||
83
WebHostLib/static/assets/supportedGames.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
window.addEventListener('load', () => {
|
||||||
|
const gameHeaders = document.getElementsByClassName('collapse-toggle');
|
||||||
|
Array.from(gameHeaders).forEach((header) => {
|
||||||
|
const gameName = header.getAttribute('data-game');
|
||||||
|
header.addEventListener('click', () => {
|
||||||
|
const gameArrow = document.getElementById(`${gameName}-arrow`);
|
||||||
|
const gameInfo = document.getElementById(gameName);
|
||||||
|
if (gameInfo.classList.contains('collapsed')) {
|
||||||
|
gameArrow.innerText = '▼';
|
||||||
|
gameInfo.classList.remove('collapsed');
|
||||||
|
} else {
|
||||||
|
gameArrow.innerText = '▶';
|
||||||
|
gameInfo.classList.add('collapsed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle game filter input
|
||||||
|
const gameSearch = document.getElementById('game-search');
|
||||||
|
gameSearch.value = '';
|
||||||
|
|
||||||
|
gameSearch.addEventListener('input', (evt) => {
|
||||||
|
if (!evt.target.value.trim()) {
|
||||||
|
// If input is empty, display all collapsed games
|
||||||
|
return Array.from(gameHeaders).forEach((header) => {
|
||||||
|
header.style.display = null;
|
||||||
|
const gameName = header.getAttribute('data-game');
|
||||||
|
document.getElementById(`${gameName}-arrow`).innerText = '▶';
|
||||||
|
document.getElementById(gameName).classList.add('collapsed');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop over all the games
|
||||||
|
Array.from(gameHeaders).forEach((header) => {
|
||||||
|
const gameName = header.getAttribute('data-game');
|
||||||
|
const gameArrow = document.getElementById(`${gameName}-arrow`);
|
||||||
|
const gameInfo = document.getElementById(gameName);
|
||||||
|
|
||||||
|
// If the game name includes the search string, display the game. If not, hide it
|
||||||
|
if (gameName.toLowerCase().includes(evt.target.value.toLowerCase())) {
|
||||||
|
header.style.display = null;
|
||||||
|
gameArrow.innerText = '▼';
|
||||||
|
gameInfo.classList.remove('collapsed');
|
||||||
|
} else {
|
||||||
|
console.log(header);
|
||||||
|
header.style.display = 'none';
|
||||||
|
gameArrow.innerText = '▶';
|
||||||
|
gameInfo.classList.add('collapsed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('expand-all').addEventListener('click', expandAll);
|
||||||
|
document.getElementById('collapse-all').addEventListener('click', collapseAll);
|
||||||
|
});
|
||||||
|
|
||||||
|
const expandAll = () => {
|
||||||
|
const gameHeaders = document.getElementsByClassName('collapse-toggle');
|
||||||
|
// Loop over all the games
|
||||||
|
Array.from(gameHeaders).forEach((header) => {
|
||||||
|
const gameName = header.getAttribute('data-game');
|
||||||
|
const gameArrow = document.getElementById(`${gameName}-arrow`);
|
||||||
|
const gameInfo = document.getElementById(gameName);
|
||||||
|
|
||||||
|
if (header.style.display === 'none') { return; }
|
||||||
|
gameArrow.innerText = '▼';
|
||||||
|
gameInfo.classList.remove('collapsed');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const collapseAll = () => {
|
||||||
|
const gameHeaders = document.getElementsByClassName('collapse-toggle');
|
||||||
|
// Loop over all the games
|
||||||
|
Array.from(gameHeaders).forEach((header) => {
|
||||||
|
const gameName = header.getAttribute('data-game');
|
||||||
|
const gameArrow = document.getElementById(`${gameName}-arrow`);
|
||||||
|
const gameInfo = document.getElementById(gameName);
|
||||||
|
|
||||||
|
if (header.style.display === 'none') { return; }
|
||||||
|
gameArrow.innerText = '▶';
|
||||||
|
gameInfo.classList.add('collapsed');
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -160,6 +160,7 @@ const buildUI = (settingData) => {
|
|||||||
weightedSettingsDiv.classList.add('invisible');
|
weightedSettingsDiv.classList.add('invisible');
|
||||||
itemPoolDiv.classList.add('invisible');
|
itemPoolDiv.classList.add('invisible');
|
||||||
hintsDiv.classList.add('invisible');
|
hintsDiv.classList.add('invisible');
|
||||||
|
locationsDiv.classList.add('invisible');
|
||||||
expandButton.classList.remove('invisible');
|
expandButton.classList.remove('invisible');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -168,6 +169,7 @@ const buildUI = (settingData) => {
|
|||||||
weightedSettingsDiv.classList.remove('invisible');
|
weightedSettingsDiv.classList.remove('invisible');
|
||||||
itemPoolDiv.classList.remove('invisible');
|
itemPoolDiv.classList.remove('invisible');
|
||||||
hintsDiv.classList.remove('invisible');
|
hintsDiv.classList.remove('invisible');
|
||||||
|
locationsDiv.classList.remove('invisible');
|
||||||
expandButton.classList.add('invisible');
|
expandButton.classList.add('invisible');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1134,8 +1136,8 @@ const validateSettings = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove any disabled options
|
|
||||||
Object.keys(settings[game]).forEach((setting) => {
|
Object.keys(settings[game]).forEach((setting) => {
|
||||||
|
// Remove any disabled options
|
||||||
Object.keys(settings[game][setting]).forEach((option) => {
|
Object.keys(settings[game][setting]).forEach((option) => {
|
||||||
if (settings[game][setting][option] === 0) {
|
if (settings[game][setting][option] === 0) {
|
||||||
delete settings[game][setting][option];
|
delete settings[game][setting][option];
|
||||||
@@ -1149,6 +1151,32 @@ const validateSettings = () => {
|
|||||||
) {
|
) {
|
||||||
errorMessage = `${game} // ${setting} has no values above zero!`;
|
errorMessage = `${game} // ${setting} has no values above zero!`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove weights from options with only one possibility
|
||||||
|
if (
|
||||||
|
Object.keys(settings[game][setting]).length === 1 &&
|
||||||
|
!Array.isArray(settings[game][setting]) &&
|
||||||
|
setting !== 'start_inventory'
|
||||||
|
) {
|
||||||
|
settings[game][setting] = Object.keys(settings[game][setting])[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove empty arrays
|
||||||
|
else if (
|
||||||
|
['exclude_locations', 'priority_locations', 'local_items',
|
||||||
|
'non_local_items', 'start_hints', 'start_location_hints'].includes(setting) &&
|
||||||
|
settings[game][setting].length === 0
|
||||||
|
) {
|
||||||
|
delete settings[game][setting];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove empty start inventory
|
||||||
|
else if (
|
||||||
|
setting === 'start_inventory' &&
|
||||||
|
Object.keys(settings[game]['start_inventory']).length === 0
|
||||||
|
) {
|
||||||
|
delete settings[game]['start_inventory'];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1156,6 +1184,11 @@ const validateSettings = () => {
|
|||||||
errorMessage = 'You have not chosen a game to play!';
|
errorMessage = 'You have not chosen a game to play!';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove weights if there is only one game
|
||||||
|
else if (Object.keys(settings.game).length === 1) {
|
||||||
|
settings.game = Object.keys(settings.game)[0];
|
||||||
|
}
|
||||||
|
|
||||||
// If an error occurred, alert the user and do not export the file
|
// If an error occurred, alert the user and do not export the file
|
||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
userMessage.innerText = errorMessage;
|
userMessage.innerText = errorMessage;
|
||||||
|
|||||||
BIN
WebHostLib/static/static/icons/sc2/SC2_Lab_BioSteel_L1.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
WebHostLib/static/static/icons/sc2/SC2_Lab_BioSteel_L2.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
WebHostLib/static/static/icons/sc2/advanceballistics.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
WebHostLib/static/static/icons/sc2/autoturretblackops.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
WebHostLib/static/static/icons/sc2/biomechanicaldrone.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
WebHostLib/static/static/icons/sc2/burstcapacitors.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
WebHostLib/static/static/icons/sc2/crossspectrumdampeners.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
WebHostLib/static/static/icons/sc2/cyclone.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
WebHostLib/static/static/icons/sc2/cyclonerangeupgrade.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
WebHostLib/static/static/icons/sc2/drillingclaws.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
WebHostLib/static/static/icons/sc2/emergencythrusters.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
WebHostLib/static/static/icons/sc2/hellionbattlemode.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
WebHostLib/static/static/icons/sc2/high-explosive-spidermine.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
WebHostLib/static/static/icons/sc2/hyperflightrotors.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
WebHostLib/static/static/icons/sc2/hyperfluxor.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
WebHostLib/static/static/icons/sc2/impalerrounds.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
WebHostLib/static/static/icons/sc2/improvedburstlaser.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
WebHostLib/static/static/icons/sc2/improvedsiegemode.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
WebHostLib/static/static/icons/sc2/interferencematrix.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
WebHostLib/static/static/icons/sc2/internalizedtechmodule.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
WebHostLib/static/static/icons/sc2/jotunboosters.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
WebHostLib/static/static/icons/sc2/jumpjets.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
WebHostLib/static/static/icons/sc2/lasertargetingsystem.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
WebHostLib/static/static/icons/sc2/liberator.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
WebHostLib/static/static/icons/sc2/lockdown.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
WebHostLib/static/static/icons/sc2/magfieldaccelerator.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
WebHostLib/static/static/icons/sc2/magrailmunitions.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
WebHostLib/static/static/icons/sc2/medivacemergencythrusters.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
WebHostLib/static/static/icons/sc2/neosteelfortifiedarmor.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
WebHostLib/static/static/icons/sc2/opticalflare.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
WebHostLib/static/static/icons/sc2/optimizedlogistics.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
WebHostLib/static/static/icons/sc2/reapercombatdrugs.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
WebHostLib/static/static/icons/sc2/restoration.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
WebHostLib/static/static/icons/sc2/ripwavemissiles.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
WebHostLib/static/static/icons/sc2/shreddermissile.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
WebHostLib/static/static/icons/sc2/siegetank-spidermines.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
WebHostLib/static/static/icons/sc2/siegetankrange.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
WebHostLib/static/static/icons/sc2/specialordance.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
WebHostLib/static/static/icons/sc2/spidermine.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
WebHostLib/static/static/icons/sc2/staticempblast.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
WebHostLib/static/static/icons/sc2/superstimpack.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
WebHostLib/static/static/icons/sc2/targetingoptics.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
WebHostLib/static/static/icons/sc2/terran-cloak-color.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
WebHostLib/static/static/icons/sc2/terran-emp-color.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 14 KiB |
BIN
WebHostLib/static/static/icons/sc2/thorsiegemode.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
WebHostLib/static/static/icons/sc2/transformationservos.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
WebHostLib/static/static/icons/sc2/valkyrie.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
WebHostLib/static/static/icons/sc2/warpjump.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
WebHostLib/static/static/icons/sc2/widowmine-attackrange.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
WebHostLib/static/static/icons/sc2/widowmine-deathblossom.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
WebHostLib/static/static/icons/sc2/widowmine.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
WebHostLib/static/static/icons/sc2/widowminehidden.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
@@ -9,7 +9,7 @@
|
|||||||
border-top-left-radius: 4px;
|
border-top-left-radius: 4px;
|
||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
padding: 3px 3px 10px;
|
padding: 3px 3px 10px;
|
||||||
width: 500px;
|
width: 710px;
|
||||||
background-color: #525494;
|
background-color: #525494;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,10 +34,12 @@
|
|||||||
max-height: 40px;
|
max-height: 40px;
|
||||||
border: 1px solid #000000;
|
border: 1px solid #000000;
|
||||||
filter: grayscale(100%) contrast(75%) brightness(20%);
|
filter: grayscale(100%) contrast(75%) brightness(20%);
|
||||||
|
background-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
#inventory-table img.acquired{
|
#inventory-table img.acquired{
|
||||||
filter: none;
|
filter: none;
|
||||||
|
background-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
#inventory-table div.counted-item {
|
#inventory-table div.counted-item {
|
||||||
@@ -52,7 +54,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#location-table{
|
#location-table{
|
||||||
width: 500px;
|
width: 710px;
|
||||||
border-left: 2px solid #000000;
|
border-left: 2px solid #000000;
|
||||||
border-right: 2px solid #000000;
|
border-right: 2px solid #000000;
|
||||||
border-bottom: 2px solid #000000;
|
border-bottom: 2px solid #000000;
|
||||||
|
|||||||
@@ -18,6 +18,16 @@
|
|||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#games h2 .collapse-arrow{
|
||||||
|
font-size: 20px;
|
||||||
|
vertical-align: middle;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#games p.collapsed{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
#games a{
|
#games a{
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
@@ -31,3 +41,13 @@
|
|||||||
line-height: 25px;
|
line-height: 25px;
|
||||||
margin-bottom: 7px;
|
margin-bottom: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#games #page-controls{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#games #page-controls button{
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,9 +17,9 @@
|
|||||||
</p>
|
</p>
|
||||||
<div id="check-form-wrapper">
|
<div id="check-form-wrapper">
|
||||||
<form id="check-form" method="post" enctype="multipart/form-data">
|
<form id="check-form" method="post" enctype="multipart/form-data">
|
||||||
<input id="file-input" type="file" name="file">
|
<input id="file-input" type="file" name="file" multiple>
|
||||||
</form>
|
</form>
|
||||||
<button id="check-button">Upload</button>
|
<button id="check-button">Upload File(s)</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -203,10 +203,10 @@ Warning: playthrough can take a significant amount of time for larger multiworld
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="generate-form-button-row">
|
<div id="generate-form-button-row">
|
||||||
<input id="file-input" type="file" name="file">
|
<input id="file-input" type="file" name="file" multiple>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<button id="generate-game-button">Upload File</button>
|
<button id="generate-game-button">Upload File(s)</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,14 +27,14 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block custom_table_row scoped %}
|
{% block custom_table_row scoped %}
|
||||||
{% if games[player] == "Factorio" %}
|
{% if games[player] == "Factorio" %}
|
||||||
{% set player_inventory = inventory[team][player] %}
|
{% set player_inventory = named_inventory[team][player] %}
|
||||||
{% set prog_science = player_inventory[custom_items["progressive-science-pack"]] %}
|
{% set prog_science = player_inventory["progressive-science-pack"] %}
|
||||||
<td class="center-column">{% if player_inventory[custom_items["logistic-science-pack"]] or prog_science %}✔{% endif %}</td>
|
<td class="center-column">{% if player_inventory["logistic-science-pack"] or prog_science %}✔{% endif %}</td>
|
||||||
<td class="center-column">{% if player_inventory[custom_items["military-science-pack"]] or prog_science > 1%}✔{% endif %}</td>
|
<td class="center-column">{% if player_inventory["military-science-pack"] or prog_science > 1%}✔{% endif %}</td>
|
||||||
<td class="center-column">{% if player_inventory[custom_items["chemical-science-pack"]] or prog_science > 2%}✔{% endif %}</td>
|
<td class="center-column">{% if player_inventory["chemical-science-pack"] or prog_science > 2%}✔{% endif %}</td>
|
||||||
<td class="center-column">{% if player_inventory[custom_items["production-science-pack"]] or prog_science > 3%}✔{% endif %}</td>
|
<td class="center-column">{% if player_inventory["production-science-pack"] or prog_science > 3%}✔{% endif %}</td>
|
||||||
<td class="center-column">{% if player_inventory[custom_items["utility-science-pack"]] or prog_science > 4%}✔{% endif %}</td>
|
<td class="center-column">{% if player_inventory["utility-science-pack"] or prog_science > 4%}✔{% endif %}</td>
|
||||||
<td class="center-column">{% if player_inventory[custom_items["space-science-pack"]] or prog_science > 5%}✔{% endif %}</td>
|
<td class="center-column">{% if player_inventory["space-science-pack"] or prog_science > 5%}✔{% endif %}</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td class="center-column">❌</td>
|
<td class="center-column">❌</td>
|
||||||
<td class="center-column">❌</td>
|
<td class="center-column">❌</td>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<div id="player-tracker-wrapper" data-tracker="{{ room.tracker|suuid }}">
|
<div id="player-tracker-wrapper" data-tracker="{{ room.tracker|suuid }}">
|
||||||
<table id="inventory-table">
|
<table id="inventory-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="10" class="title">
|
<td colspan="15" class="title">
|
||||||
Starting Resources
|
Starting Resources
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
-->
|
-->
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="10" class="title">
|
<td colspan="15" class="title">
|
||||||
Weapon & Armor Upgrades
|
Weapon & Armor Upgrades
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -37,120 +37,266 @@
|
|||||||
<td><img src="{{ vehicle_armor_url }}" class="{{ 'acquired' if 'Progressive Vehicle Armor' in acquired_items }}" title="Progressive Vehicle Armor{% if vehicle_armor_level > 0 %} (Level {{ vehicle_armor_level }}){% endif %}" /></td>
|
<td><img src="{{ vehicle_armor_url }}" class="{{ 'acquired' if 'Progressive Vehicle Armor' in acquired_items }}" title="Progressive Vehicle Armor{% if vehicle_armor_level > 0 %} (Level {{ vehicle_armor_level }}){% endif %}" /></td>
|
||||||
<td><img src="{{ ship_weapon_url }}" class="{{ 'acquired' if 'Progressive Ship Weapon' in acquired_items }}" title="Progressive Ship Weapons{% if ship_weapon_level > 0 %} (Level {{ ship_weapon_level }}){% endif %}" /></td>
|
<td><img src="{{ ship_weapon_url }}" class="{{ 'acquired' if 'Progressive Ship Weapon' in acquired_items }}" title="Progressive Ship Weapons{% if ship_weapon_level > 0 %} (Level {{ ship_weapon_level }}){% endif %}" /></td>
|
||||||
<td><img src="{{ ship_armor_url }}" class="{{ 'acquired' if 'Progressive Ship Armor' in acquired_items }}" title="Progressive Ship Armor{% if ship_armor_level > 0 %} (Level {{ ship_armor_level }}){% endif %}" /></td>
|
<td><img src="{{ ship_armor_url }}" class="{{ 'acquired' if 'Progressive Ship Armor' in acquired_items }}" title="Progressive Ship Armor{% if ship_armor_level > 0 %} (Level {{ ship_armor_level }}){% endif %}" /></td>
|
||||||
|
<td colspan="2"></td>
|
||||||
|
<td><img src="{{ icons['Ultra-Capacitors'] }}" class="{{ 'acquired' if 'Ultra-Capacitors' in acquired_items }}" title="Ultra-Capacitors" /></td>
|
||||||
|
<td><img src="{{ icons['Vanadium Plating'] }}" class="{{ 'acquired' if 'Vanadium Plating' in acquired_items }}" title="Vanadium Plating" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="10" class="title">
|
<td colspan="15" class="title">
|
||||||
Base
|
Base
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2"><img src="{{ icons['Bunker'] }}" class="{{ 'acquired' if 'Bunker' in acquired_items }}" title="Bunker" /></td>
|
<td><img src="{{ icons['Bunker'] }}" class="{{ 'acquired' if 'Bunker' in acquired_items }}" title="Bunker" /></td>
|
||||||
<td colspan="2"><img src="{{ icons['Missile Turret'] }}" class="{{ 'acquired' if 'Missile Turret' in acquired_items }}" title="Missile Turret" /></td>
|
|
||||||
<td colspan="2"><img src="{{ icons['Sensor Tower'] }}" class="{{ 'acquired' if 'Sensor Tower' in acquired_items }}" title="Sensor Tower" /></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><img src="{{ icons['Projectile Accelerator (Bunker)'] }}" class="{{ 'acquired' if 'Projectile Accelerator (Bunker)' in acquired_items }}" title="Projectile Accelerator (Bunker)" /></td>
|
<td><img src="{{ icons['Projectile Accelerator (Bunker)'] }}" class="{{ 'acquired' if 'Projectile Accelerator (Bunker)' in acquired_items }}" title="Projectile Accelerator (Bunker)" /></td>
|
||||||
<td><img src="{{ icons['Neosteel Bunker (Bunker)'] }}" class="{{ 'acquired' if 'Neosteel Bunker (Bunker)' in acquired_items }}" title="Neosteel Bunker (Bunker)" /></td>
|
<td><img src="{{ icons['Neosteel Bunker (Bunker)'] }}" class="{{ 'acquired' if 'Neosteel Bunker (Bunker)' in acquired_items }}" title="Neosteel Bunker (Bunker)" /></td>
|
||||||
|
<td><img src="{{ icons['Shrike Turret (Bunker)'] }}" class="{{ 'acquired' if 'Shrike Turret (Bunker)' in acquired_items }}" title="Shrike Turret (Bunker)" /></td>
|
||||||
|
<td><img src="{{ icons['Fortified Bunker (Bunker)'] }}" class="{{ 'acquired' if 'Fortified Bunker (Bunker)' in acquired_items }}" title="Fortified Bunker (Bunker)" /></td>
|
||||||
|
<td colspan="3"></td>
|
||||||
|
<td><img src="{{ icons['Missile Turret'] }}" class="{{ 'acquired' if 'Missile Turret' in acquired_items }}" title="Missile Turret" /></td>
|
||||||
<td><img src="{{ icons['Titanium Housing (Missile Turret)'] }}" class="{{ 'acquired' if 'Titanium Housing (Missile Turret)' in acquired_items }}" title="Titanium Housing (Missile Turret)" /></td>
|
<td><img src="{{ icons['Titanium Housing (Missile Turret)'] }}" class="{{ 'acquired' if 'Titanium Housing (Missile Turret)' in acquired_items }}" title="Titanium Housing (Missile Turret)" /></td>
|
||||||
<td><img src="{{ icons['Hellstorm Batteries (Missile Turret)'] }}" class="{{ 'acquired' if 'Hellstorm Batteries (Missile Turret)' in acquired_items }}" title="Hellstorm Batteries (Missile Turret)" /></td>
|
<td><img src="{{ icons['Hellstorm Batteries (Missile Turret)'] }}" class="{{ 'acquired' if 'Hellstorm Batteries (Missile Turret)' in acquired_items }}" title="Hellstorm Batteries (Missile Turret)" /></td>
|
||||||
<td colspan="2"> </td>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="{{ icons['Orbital Command (Building)'] }}" class="{{ 'acquired' if 'Orbital Command (Building)' in acquired_items }}" title="Orbital Command (Building)" /></td>
|
||||||
|
<td><img src="{{ icons['Command Center Reactor'] }}" class="{{ 'acquired' if 'Command Center Reactor' in acquired_items }}" title="Command Center Reactor" /></td>
|
||||||
|
<td><img src="{{ icons['Planetary Fortress'] }}" class="{{ 'acquired' if 'Planetary Fortress' in acquired_items }}" title="Planetary Fortress" /></td>
|
||||||
|
<td></td>
|
||||||
<td><img src="{{ icons['Advanced Construction (SCV)'] }}" class="{{ 'acquired' if 'Advanced Construction (SCV)' in acquired_items }}" title="Advanced Construction (SCV)" /></td>
|
<td><img src="{{ icons['Advanced Construction (SCV)'] }}" class="{{ 'acquired' if 'Advanced Construction (SCV)' in acquired_items }}" title="Advanced Construction (SCV)" /></td>
|
||||||
<td><img src="{{ icons['Dual-Fusion Welders (SCV)'] }}" class="{{ 'acquired' if 'Dual-Fusion Welders (SCV)' in acquired_items }}" title="Dual-Fusion Welders (SCV)" /></td>
|
<td><img src="{{ icons['Dual-Fusion Welders (SCV)'] }}" class="{{ 'acquired' if 'Dual-Fusion Welders (SCV)' in acquired_items }}" title="Dual-Fusion Welders (SCV)" /></td>
|
||||||
<td><img src="{{ icons['Fire-Suppression System (Building)'] }}" class="{{ 'acquired' if 'Fire-Suppression System (Building)' in acquired_items }}" title="Fire-Suppression System (Building)" /></td>
|
<td></td>
|
||||||
<td><img src="{{ icons['Orbital Command (Building)'] }}" class="{{ 'acquired' if 'Orbital Command (Building)' in acquired_items }}" title="Orbital Command (Building)" /></td>
|
<td><img src="{{ icons['Micro-Filtering'] }}" class="{{ 'acquired' if 'Micro-Filtering' in acquired_items }}" title="Micro-Filtering" /></td>
|
||||||
|
<td><img src="{{ icons['Automated Refinery'] }}" class="{{ 'acquired' if 'Automated Refinery' in acquired_items }}" title="Automated Refinery" /></td>
|
||||||
|
<td></td>
|
||||||
|
<td><img src="{{ icons['Tech Reactor'] }}" class="{{ 'acquired' if 'Tech Reactor' in acquired_items }}" title="Tech Reactor" /></td>
|
||||||
|
<td></td>
|
||||||
|
<td><img src="{{ icons['Orbital Depots'] }}" class="{{ 'acquired' if 'Orbital Depots' in acquired_items }}" title="Orbital Depots" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="10" class="title">
|
<td><img src="{{ icons['Sensor Tower'] }}" class="{{ 'acquired' if 'Sensor Tower' in acquired_items }}" title="Sensor Tower" /></td>
|
||||||
|
<td></td>
|
||||||
|
<td><img src="{{ icons['Perdition Turret'] }}" class="{{ 'acquired' if 'Perdition Turret' in acquired_items }}" title="Perdition Turret" /></td>
|
||||||
|
<td></td>
|
||||||
|
<td><img src="{{ icons['Hive Mind Emulator'] }}" class="{{ 'acquired' if 'Hive Mind Emulator' in acquired_items }}" title="Hive Mind Emulator" /></td>
|
||||||
|
<td></td>
|
||||||
|
<td><img src="{{ icons['Psi Disrupter'] }}" class="{{ 'acquired' if 'Psi Disrupter' in acquired_items }}" title="Psi Disrupter" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="title">
|
||||||
Infantry
|
Infantry
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
<td></td>
|
||||||
<tr>
|
<td colspan="7" class="title">
|
||||||
<td colspan="2"><img src="{{ icons['Marine'] }}" class="{{ 'acquired' if 'Marine' in acquired_items }}" title="Marine" /></td>
|
|
||||||
<td colspan="2"><img src="{{ icons['Medic'] }}" class="{{ 'acquired' if 'Medic' in acquired_items }}" title="Medic" /></td>
|
|
||||||
<td colspan="2"><img src="{{ icons['Firebat'] }}" class="{{ 'acquired' if 'Firebat' in acquired_items }}" title="Firebat" /></td>
|
|
||||||
<td colspan="2"><img src="{{ icons['Marauder'] }}" class="{{ 'acquired' if 'Marauder' in acquired_items }}" title="Marauder" /></td>
|
|
||||||
<td colspan="2"><img src="{{ icons['Reaper'] }}" class="{{ 'acquired' if 'Reaper' in acquired_items }}" title="Reaper" /></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><img src="{{ icons['Stimpack (Marine)'] }}" class="{{ 'acquired' if 'Stimpack (Marine)' in acquired_items }}" title="Stimpack (Marine)" /></td>
|
|
||||||
<td><img src="{{ icons['Combat Shield (Marine)'] }}" class="{{ 'acquired' if 'Combat Shield (Marine)' in acquired_items }}" title="Combat Shield (Marine)" /></td>
|
|
||||||
<td><img src="{{ icons['Advanced Medic Facilities (Medic)'] }}" class="{{ 'acquired' if 'Advanced Medic Facilities (Medic)' in acquired_items }}" title="Advanced Medic Facilities (Medic)" /></td>
|
|
||||||
<td><img src="{{ icons['Stabilizer Medpacks (Medic)'] }}" class="{{ 'acquired' if 'Stabilizer Medpacks (Medic)' in acquired_items }}" title="Stabilizer Medpacks (Medic)" /></td>
|
|
||||||
<td><img src="{{ icons['Incinerator Gauntlets (Firebat)'] }}" class="{{ 'acquired' if 'Incinerator Gauntlets (Firebat)' in acquired_items }}" title="Incinerator Gauntlets (Firebat)" /></td>
|
|
||||||
<td><img src="{{ icons['Juggernaut Plating (Firebat)'] }}" class="{{ 'acquired' if 'Juggernaut Plating (Firebat)' in acquired_items }}" title="Juggernaut Plating (Firebat)" /></td>
|
|
||||||
<td><img src="{{ icons['Concussive Shells (Marauder)'] }}" class="{{ 'acquired' if 'Concussive Shells (Marauder)' in acquired_items }}" title="Concussive Shells (Marauder)" /></td>
|
|
||||||
<td><img src="{{ icons['Kinetic Foam (Marauder)'] }}" class="{{ 'acquired' if 'Kinetic Foam (Marauder)' in acquired_items }}" title="Kinetic Foam (Marauder)" /></td>
|
|
||||||
<td><img src="{{ icons['U-238 Rounds (Reaper)'] }}" class="{{ 'acquired' if 'U-238 Rounds (Reaper)' in acquired_items }}" title="U-238 Rounds (Reaper)" /></td>
|
|
||||||
<td><img src="{{ icons['G-4 Clusterbomb (Reaper)'] }}" class="{{ 'acquired' if 'G-4 Clusterbomb (Reaper)' in acquired_items }}" title="G-4 Clusterbomb (Reaper)" /></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="10" class="title">
|
|
||||||
Vehicles
|
Vehicles
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2"><img src="{{ icons['Hellion'] }}" class="{{ 'acquired' if 'Hellion' in acquired_items }}" title="Hellion" /></td>
|
<td><img src="{{ icons['Marine'] }}" class="{{ 'acquired' if 'Marine' in acquired_items }}" title="Marine" /></td>
|
||||||
<td colspan="2"><img src="{{ icons['Vulture'] }}" class="{{ 'acquired' if 'Vulture' in acquired_items }}" title="Vulture" /></td>
|
<td><img src="{{ stimpack_marine_url }}" class="{{ 'acquired' if 'Progressive Stimpack (Marine)' in acquired_items }}" title="{{ stimpack_marine_name }}" /></td>
|
||||||
<td colspan="2"><img src="{{ icons['Goliath'] }}" class="{{ 'acquired' if 'Goliath' in acquired_items }}" title="Goliath" /></td>
|
<td><img src="{{ icons['Combat Shield (Marine)'] }}" class="{{ 'acquired' if 'Combat Shield (Marine)' in acquired_items }}" title="Combat Shield (Marine)" /></td>
|
||||||
<td colspan="2"><img src="{{ icons['Diamondback'] }}" class="{{ 'acquired' if 'Diamondback' in acquired_items }}" title="Diamondback" /></td>
|
<td><img src="{{ icons['Laser Targeting System (Marine)'] }}" class="{{ 'acquired' if 'Laser Targeting System (Marine)' in acquired_items }}" title="Laser Targeting System (Marine)" /></td>
|
||||||
<td colspan="2"><img src="{{ icons['Siege Tank'] }}" class="{{ 'acquired' if 'Siege Tank' in acquired_items }}" title="Siege Tank" /></td>
|
<td><img src="{{ icons['Magrail Munitions (Marine)'] }}" class="{{ 'acquired' if 'Magrail Munitions (Marine)' in acquired_items }}" title="Magrail Munitions (Marine)" /></td>
|
||||||
</tr>
|
<td><img src="{{ icons['Optimized Logistics (Marine)'] }}" class="{{ 'acquired' if 'Optimized Logistics (Marine)' in acquired_items }}" title="Optimized Logistics (Marine)" /></td>
|
||||||
<tr>
|
<td colspan="2"></td>
|
||||||
|
<td><img src="{{ icons['Hellion'] }}" class="{{ 'acquired' if 'Hellion' in acquired_items }}" title="Hellion" /></td>
|
||||||
<td><img src="{{ icons['Twin-Linked Flamethrower (Hellion)'] }}" class="{{ 'acquired' if 'Twin-Linked Flamethrower (Hellion)' in acquired_items }}" title="Twin-Linked Flamethrower (Hellion)" /></td>
|
<td><img src="{{ icons['Twin-Linked Flamethrower (Hellion)'] }}" class="{{ 'acquired' if 'Twin-Linked Flamethrower (Hellion)' in acquired_items }}" title="Twin-Linked Flamethrower (Hellion)" /></td>
|
||||||
<td><img src="{{ icons['Thermite Filaments (Hellion)'] }}" class="{{ 'acquired' if 'Thermite Filaments (Hellion)' in acquired_items }}" title="Thermite Filaments (Hellion)" /></td>
|
<td><img src="{{ icons['Thermite Filaments (Hellion)'] }}" class="{{ 'acquired' if 'Thermite Filaments (Hellion)' in acquired_items }}" title="Thermite Filaments (Hellion)" /></td>
|
||||||
<td><img src="{{ icons['Cerberus Mine (Vulture)'] }}" class="{{ 'acquired' if 'Cerberus Mine (Vulture)' in acquired_items }}" title="Cerberus Mine (Vulture)" /></td>
|
<td><img src="{{ icons['Hellbat Aspect (Hellion)'] }}" class="{{ 'acquired' if 'Hellbat Aspect (Hellion)' in acquired_items }}" title="Hellbat Aspect (Hellion)" /></td>
|
||||||
<td><img src="{{ icons['Replenishable Magazine (Vulture)'] }}" class="{{ 'acquired' if 'Replenishable Magazine (Vulture)' in acquired_items }}" title="Replenishable Magazine (Vulture)" /></td>
|
<td><img src="{{ icons['Smart Servos (Hellion)'] }}" class="{{ 'acquired' if 'Smart Servos (Hellion)' in acquired_items }}" title="Smart Servos (Hellion)" /></td>
|
||||||
<td><img src="{{ icons['Multi-Lock Weapons System (Goliath)'] }}" class="{{ 'acquired' if 'Multi-Lock Weapons System (Goliath)' in acquired_items }}" title="Multi-Lock Weapons System (Goliath)" /></td>
|
<td><img src="{{ icons['Optimized Logistics (Hellion)'] }}" class="{{ 'acquired' if 'Optimized Logistics (Hellion)' in acquired_items }}" title="Optimized Logistics (Hellion)" /></td>
|
||||||
<td><img src="{{ icons['Ares-Class Targeting System (Goliath)'] }}" class="{{ 'acquired' if 'Ares-Class Targeting System (Goliath)' in acquired_items }}" title="Ares-Class Targeting System (Goliath)" /></td>
|
<td><img src="{{ icons['Jump Jets (Hellion)'] }}" class="{{ 'acquired' if 'Jump Jets (Hellion)' in acquired_items }}" title="Jump Jets (Hellion)" /></td>
|
||||||
<td><img src="{{ icons['Tri-Lithium Power Cell (Diamondback)'] }}" class="{{ 'acquired' if 'Tri-Lithium Power Cell (Diamondback)' in acquired_items }}" title="Tri-Lithium Power Cell (Diamondback)" /></td>
|
|
||||||
<td><img src="{{ icons['Shaped Hull (Diamondback)'] }}" class="{{ 'acquired' if 'Shaped Hull (Diamondback)' in acquired_items }}" title="Shaped Hull (Diamondback)" /></td>
|
|
||||||
<td><img src="{{ icons['Maelstrom Rounds (Siege Tank)'] }}" class="{{ 'acquired' if 'Maelstrom Rounds (Siege Tank)' in acquired_items }}" title="Maelstrom Rounds (Siege Tank)" /></td>
|
|
||||||
<td><img src="{{ icons['Shaped Blast (Siege Tank)'] }}" class="{{ 'acquired' if 'Shaped Blast (Siege Tank)' in acquired_items }}" title="Shaped Blast (Siege Tank)" /></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="10" class="title">
|
<td><img src="{{ icons['Medic'] }}" class="{{ 'acquired' if 'Medic' in acquired_items }}" title="Medic" /></td>
|
||||||
|
<td><img src="{{ icons['Advanced Medic Facilities (Medic)'] }}" class="{{ 'acquired' if 'Advanced Medic Facilities (Medic)' in acquired_items }}" title="Advanced Medic Facilities (Medic)" /></td>
|
||||||
|
<td><img src="{{ icons['Stabilizer Medpacks (Medic)'] }}" class="{{ 'acquired' if 'Stabilizer Medpacks (Medic)' in acquired_items }}" title="Stabilizer Medpacks (Medic)" /></td>
|
||||||
|
<td><img src="{{ icons['Restoration (Medic)'] }}" class="{{ 'acquired' if 'Restoration (Medic)' in acquired_items }}" title="Restoration (Medic)" /></td>
|
||||||
|
<td><img src="{{ icons['Optical Flare (Medic)'] }}" class="{{ 'acquired' if 'Optical Flare (Medic)' in acquired_items }}" title="Optical Flare (Medic)" /></td>
|
||||||
|
<td><img src="{{ icons['Optimized Logistics (Medic)'] }}" class="{{ 'acquired' if 'Optimized Logistics (Medic)' in acquired_items }}" title="Optimized Logistics (Medic)" /></td>
|
||||||
|
<td colspan="2"></td>
|
||||||
|
<td></td>
|
||||||
|
<td><img src="{{ stimpack_hellion_url }}" class="{{ 'acquired' if 'Progressive Stimpack (Hellion)' in acquired_items }}" title="{{ stimpack_hellion_name }}" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="{{ icons['Firebat'] }}" class="{{ 'acquired' if 'Firebat' in acquired_items }}" title="Firebat" /></td>
|
||||||
|
<td><img src="{{ icons['Incinerator Gauntlets (Firebat)'] }}" class="{{ 'acquired' if 'Incinerator Gauntlets (Firebat)' in acquired_items }}" title="Incinerator Gauntlets (Firebat)" /></td>
|
||||||
|
<td><img src="{{ icons['Juggernaut Plating (Firebat)'] }}" class="{{ 'acquired' if 'Juggernaut Plating (Firebat)' in acquired_items }}" title="Juggernaut Plating (Firebat)" /></td>
|
||||||
|
<td><img src="{{ stimpack_firebat_url }}" class="{{ 'acquired' if 'Progressive Stimpack (Firebat)' in acquired_items }}" title="{{ stimpack_firebat_name }}" /></td>
|
||||||
|
<td><img src="{{ icons['Optimized Logistics (Firebat)'] }}" class="{{ 'acquired' if 'Optimized Logistics (Firebat)' in acquired_items }}" title="Optimized Logistics (Firebat)" /></td>
|
||||||
|
<td colspan="3"></td>
|
||||||
|
<td><img src="{{ icons['Vulture'] }}" class="{{ 'acquired' if 'Vulture' in acquired_items }}" title="Vulture" /></td>
|
||||||
|
<td><img src="{{ icons['Replenishable Magazine (Vulture)'] }}" class="{{ 'acquired' if 'Replenishable Magazine (Vulture)' in acquired_items }}" title="Replenishable Magazine (Vulture)" /></td>
|
||||||
|
<td><img src="{{ icons['Ion Thrusters (Vulture)'] }}" class="{{ 'acquired' if 'Ion Thrusters (Vulture)' in acquired_items }}" title="Ion Thrusters (Vulture)" /></td>
|
||||||
|
<td><img src="{{ icons['Auto Launchers (Vulture)'] }}" class="{{ 'acquired' if 'Auto Launchers (Vulture)' in acquired_items }}" title="Auto Launchers (Vulture)" /></td>
|
||||||
|
<td></td>
|
||||||
|
<td><img src="{{ icons['Cerberus Mine (Spider Mine)'] }}" class="{{ 'acquired' if 'Cerberus Mine (Spider Mine)' in acquired_items }}" title="Cerberus Mine (Spider Mine)" /></td>
|
||||||
|
<td><img src="{{ icons['High Explosive Munition (Spider Mine)'] }}" class="{{ 'acquired' if 'High Explosive Munition (Spider Mine)' in acquired_items }}" title="High Explosive Munition (Spider Mine)" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="{{ icons['Marauder'] }}" class="{{ 'acquired' if 'Marauder' in acquired_items }}" title="Marauder" /></td>
|
||||||
|
<td><img src="{{ icons['Concussive Shells (Marauder)'] }}" class="{{ 'acquired' if 'Concussive Shells (Marauder)' in acquired_items }}" title="Concussive Shells (Marauder)" /></td>
|
||||||
|
<td><img src="{{ icons['Kinetic Foam (Marauder)'] }}" class="{{ 'acquired' if 'Kinetic Foam (Marauder)' in acquired_items }}" title="Kinetic Foam (Marauder)" /></td>
|
||||||
|
<td><img src="{{ stimpack_marauder_url }}" class="{{ 'acquired' if 'Progressive Stimpack (Marauder)' in acquired_items }}" title="{{ stimpack_marauder_name }}" /></td>
|
||||||
|
<td><img src="{{ icons['Laser Targeting System (Marauder)'] }}" class="{{ 'acquired' if 'Laser Targeting System (Marauder)' in acquired_items }}" title="Laser Targeting System (Marauder)" /></td>
|
||||||
|
<td><img src="{{ icons['Magrail Munitions (Marauder)'] }}" class="{{ 'acquired' if 'Magrail Munitions (Marauder)' in acquired_items }}" title="Magrail Munitions (Marauder)" /></td>
|
||||||
|
<td><img src="{{ icons['Internal Tech Module (Marauder)'] }}" class="{{ 'acquired' if 'Internal Tech Module (Marauder)' in acquired_items }}" title="Internal Tech Module (Marauder)" /></td>
|
||||||
|
<td></td>
|
||||||
|
<td><img src="{{ icons['Goliath'] }}" class="{{ 'acquired' if 'Goliath' in acquired_items }}" title="Goliath" /></td>
|
||||||
|
<td><img src="{{ icons['Multi-Lock Weapons System (Goliath)'] }}" class="{{ 'acquired' if 'Multi-Lock Weapons System (Goliath)' in acquired_items }}" title="Multi-Lock Weapons System (Goliath)" /></td>
|
||||||
|
<td><img src="{{ icons['Ares-Class Targeting System (Goliath)'] }}" class="{{ 'acquired' if 'Ares-Class Targeting System (Goliath)' in acquired_items }}" title="Ares-Class Targeting System (Goliath)" /></td>
|
||||||
|
<td><img src="{{ icons['Jump Jets (Goliath)'] }}" class="{{ 'acquired' if 'Jump Jets (Goliath)' in acquired_items }}" title="Jump Jets (Goliath)" /></td>
|
||||||
|
<td><img src="{{ icons['Optimized Logistics (Goliath)'] }}" class="{{ 'acquired' if 'Optimized Logistics (Goliath)' in acquired_items }}" title="Optimized Logistics (Goliath)" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="{{ icons['Reaper'] }}" class="{{ 'acquired' if 'Reaper' in acquired_items }}" title="Reaper" /></td>
|
||||||
|
<td><img src="{{ icons['U-238 Rounds (Reaper)'] }}" class="{{ 'acquired' if 'U-238 Rounds (Reaper)' in acquired_items }}" title="U-238 Rounds (Reaper)" /></td>
|
||||||
|
<td><img src="{{ icons['G-4 Clusterbomb (Reaper)'] }}" class="{{ 'acquired' if 'G-4 Clusterbomb (Reaper)' in acquired_items }}" title="G-4 Clusterbomb (Reaper)" /></td>
|
||||||
|
<td><img src="{{ stimpack_reaper_url }}" class="{{ 'acquired' if 'Progressive Stimpack (Reaper)' in acquired_items }}" title="{{ stimpack_reaper_name }}" /></td>
|
||||||
|
<td><img src="{{ icons['Laser Targeting System (Reaper)'] }}" class="{{ 'acquired' if 'Laser Targeting System (Reaper)' in acquired_items }}" title="Laser Targeting System (Reaper)" /></td>
|
||||||
|
<td><img src="{{ icons['Advanced Cloaking Field (Reaper)'] }}" class="{{ 'acquired' if 'Advanced Cloaking Field (Reaper)' in acquired_items }}" title="Advanced Cloaking Field (Reaper)" /></td>
|
||||||
|
<td><img src="{{ icons['Spider Mines (Reaper)'] }}" class="{{ 'acquired' if 'Spider Mines (Reaper)' in acquired_items }}" title="Spider Mines (Reaper)" /></td>
|
||||||
|
<td></td>
|
||||||
|
<td><img src="{{ icons['Diamondback'] }}" class="{{ 'acquired' if 'Diamondback' in acquired_items }}" title="Diamondback" /></td>
|
||||||
|
<td><img src="{{ icons['Tri-Lithium Power Cell (Diamondback)'] }}" class="{{ 'acquired' if 'Tri-Lithium Power Cell (Diamondback)' in acquired_items }}" title="Tri-Lithium Power Cell (Diamondback)" /></td>
|
||||||
|
<td><img src="{{ icons['Shaped Hull (Diamondback)'] }}" class="{{ 'acquired' if 'Shaped Hull (Diamondback)' in acquired_items }}" title="Shaped Hull (Diamondback)" /></td>
|
||||||
|
<td><img src="{{ icons['Hyperfluxor (Diamondback)'] }}" class="{{ 'acquired' if 'Hyperfluxor (Diamondback)' in acquired_items }}" title="Hyperfluxor (Diamondback)" /></td>
|
||||||
|
<td><img src="{{ icons['Burst Capacitors (Diamondback)'] }}" class="{{ 'acquired' if 'Burst Capacitors (Diamondback)' in acquired_items }}" title="Burst Capacitors (Diamondback)" /></td>
|
||||||
|
<td><img src="{{ icons['Optimized Logistics (Diamondback)'] }}" class="{{ 'acquired' if 'Optimized Logistics (Diamondback)' in acquired_items }}" title="Optimized Logistics (Diamondback)" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td><img src="{{ icons['Combat Drugs (Reaper)'] }}" class="{{ 'acquired' if 'Combat Drugs (Reaper)' in acquired_items }}" title="Combat Drugs (Reaper)" /></td>
|
||||||
|
<td colspan="6"></td>
|
||||||
|
<td><img src="{{ icons['Siege Tank'] }}" class="{{ 'acquired' if 'Siege Tank' in acquired_items }}" title="Siege Tank" /></td>
|
||||||
|
<td><img src="{{ icons['Maelstrom Rounds (Siege Tank)'] }}" class="{{ 'acquired' if 'Maelstrom Rounds (Siege Tank)' in acquired_items }}" title="Maelstrom Rounds (Siege Tank)" /></td>
|
||||||
|
<td><img src="{{ icons['Shaped Blast (Siege Tank)'] }}" class="{{ 'acquired' if 'Shaped Blast (Siege Tank)' in acquired_items }}" title="Shaped Blast (Siege Tank)" /></td>
|
||||||
|
<td><img src="{{ icons['Jump Jets (Siege Tank)'] }}" class="{{ 'acquired' if 'Jump Jets (Siege Tank)' in acquired_items }}" title="Jump Jets (Siege Tank)" /></td>
|
||||||
|
<td><img src="{{ icons['Spider Mines (Siege Tank)'] }}" class="{{ 'acquired' if 'Spider Mines (Siege Tank)' in acquired_items }}" title="Spider Mines (Siege Tank)" /></td>
|
||||||
|
<td><img src="{{ icons['Smart Servos (Siege Tank)'] }}" class="{{ 'acquired' if 'Smart Servos (Siege Tank)' in acquired_items }}" title="Smart Servos (Siege Tank)" /></td>
|
||||||
|
<td><img src="{{ icons['Graduating Range (Siege Tank)'] }}" class="{{ 'acquired' if 'Graduating Range (Siege Tank)' in acquired_items }}" title="Graduating Range (Siege Tank)" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="{{ icons['Ghost'] }}" class="{{ 'acquired' if 'Ghost' in acquired_items }}" title="Ghost" /></td>
|
||||||
|
<td><img src="{{ icons['Ocular Implants (Ghost)'] }}" class="{{ 'acquired' if 'Ocular Implants (Ghost)' in acquired_items }}" title="Ocular Implants (Ghost)" /></td>
|
||||||
|
<td><img src="{{ icons['Crius Suit (Ghost)'] }}" class="{{ 'acquired' if 'Crius Suit (Ghost)' in acquired_items }}" title="Crius Suit (Ghost)" /></td>
|
||||||
|
<td><img src="{{ icons['EMP Rounds (Ghost)'] }}" class="{{ 'acquired' if 'EMP Rounds (Ghost)' in acquired_items }}" title="EMP Rounds (Ghost)" /></td>
|
||||||
|
<td><img src="{{ icons['Lockdown (Ghost)'] }}" class="{{ 'acquired' if 'Lockdown (Ghost)' in acquired_items }}" title="Lockdown (Ghost)" /></td>
|
||||||
|
<td colspan="3"></td>
|
||||||
|
<td></td>
|
||||||
|
<td><img src="{{ icons['Laser Targeting System (Siege Tank)'] }}" class="{{ 'acquired' if 'Laser Targeting System (Siege Tank)' in acquired_items }}" title="Laser Targeting System (Siege Tank)" /></td>
|
||||||
|
<td><img src="{{ icons['Advanced Siege Tech (Siege Tank)'] }}" class="{{ 'acquired' if 'Advanced Siege Tech (Siege Tank)' in acquired_items }}" title="Advanced Siege Tech (Siege Tank)" /></td>
|
||||||
|
<td><img src="{{ icons['Internal Tech Module (Siege Tank)'] }}" class="{{ 'acquired' if 'Internal Tech Module (Siege Tank)' in acquired_items }}" title="Internal Tech Module (Siege Tank)" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="{{ icons['Spectre'] }}" class="{{ 'acquired' if 'Spectre' in acquired_items }}" title="Spectre" /></td>
|
||||||
|
<td><img src="{{ icons['Psionic Lash (Spectre)'] }}" class="{{ 'acquired' if 'Psionic Lash (Spectre)' in acquired_items }}" title="Psionic Lash (Spectre)" /></td>
|
||||||
|
<td><img src="{{ icons['Nyx-Class Cloaking Module (Spectre)'] }}" class="{{ 'acquired' if 'Nyx-Class Cloaking Module (Spectre)' in acquired_items }}" title="Nyx-Class Cloaking Module (Spectre)" /></td>
|
||||||
|
<td><img src="{{ icons['Impaler Rounds (Spectre)'] }}" class="{{ 'acquired' if 'Impaler Rounds (Spectre)' in acquired_items }}" title="Impaler Rounds (Spectre)" /></td>
|
||||||
|
<td colspan="4"></td>
|
||||||
|
<td><img src="{{ icons['Thor'] }}" class="{{ 'acquired' if 'Thor' in acquired_items }}" title="Thor" /></td>
|
||||||
|
<td><img src="{{ icons['330mm Barrage Cannon (Thor)'] }}" class="{{ 'acquired' if '330mm Barrage Cannon (Thor)' in acquired_items }}" title="330mm Barrage Cannon (Thor)" /></td>
|
||||||
|
<td><img src="{{ icons['Immortality Protocol (Thor)'] }}" class="{{ 'acquired' if 'Immortality Protocol (Thor)' in acquired_items }}" title="Immortality Protocol (Thor)" /></td>
|
||||||
|
<td><img src="{{ high_impact_payload_thor_url }}" class="{{ 'acquired' if 'Progressive High Impact Payload (Thor)' in acquired_items }}" title="{{ high_impact_payload_thor_name }}" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="8"></td>
|
||||||
|
<td><img src="{{ icons['Predator'] }}" class="{{ 'acquired' if 'Predator' in acquired_items }}" title="Predator" /></td>
|
||||||
|
<td><img src="{{ icons['Optimized Logistics (Predator)'] }}" class="{{ 'acquired' if 'Optimized Logistics (Predator)' in acquired_items }}" title="Optimized Logistics (Predator)" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="8"></td>
|
||||||
|
<td><img src="{{ icons['Widow Mine'] }}" class="{{ 'acquired' if 'Widow Mine' in acquired_items }}" title="Widow Mine" /></td>
|
||||||
|
<td><img src="{{ icons['Drilling Claws (Widow Mine)'] }}" class="{{ 'acquired' if 'Drilling Claws (Widow Mine)' in acquired_items }}" title="Drilling Claws (Widow Mine)" /></td>
|
||||||
|
<td><img src="{{ icons['Concealment (Widow Mine)'] }}" class="{{ 'acquired' if 'Concealment (Widow Mine)' in acquired_items }}" title="Concealment (Widow Mine)" /></td>
|
||||||
|
<td><img src="{{ icons['Black Market Launchers (Widow Mine)'] }}" class="{{ 'acquired' if 'Black Market Launchers (Widow Mine)' in acquired_items }}" title="Black Market Launchers (Widow Mine)" /></td>
|
||||||
|
<td><img src="{{ icons['Executioner Missiles (Widow Mine)'] }}" class="{{ 'acquired' if 'Executioner Missiles (Widow Mine)' in acquired_items }}" title="Executioner Missiles (Widow Mine)" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="8"></td>
|
||||||
|
<td><img src="{{ icons['Cyclone'] }}" class="{{ 'acquired' if 'Cyclone' in acquired_items }}" title="Cyclone" /></td>
|
||||||
|
<td><img src="{{ icons['Mag-Field Accelerators (Cyclone)'] }}" class="{{ 'acquired' if 'Mag-Field Accelerators (Cyclone)' in acquired_items }}" title="Mag-Field Accelerators (Cyclone)" /></td>
|
||||||
|
<td><img src="{{ icons['Mag-Field Launchers (Cyclone)'] }}" class="{{ 'acquired' if 'Mag-Field Launchers (Cyclone)' in acquired_items }}" title="Mag-Field Launchers (Cyclone)" /></td>
|
||||||
|
<td><img src="{{ icons['Targeting Optics (Cyclone)'] }}" class="{{ 'acquired' if 'Targeting Optics (Cyclone)' in acquired_items }}" title="Targeting Optics (Cyclone)" /></td>
|
||||||
|
<td><img src="{{ icons['Rapid Fire Launchers (Cyclone)'] }}" class="{{ 'acquired' if 'Rapid Fire Launchers (Cyclone)' in acquired_items }}" title="Rapid Fire Launchers (Cyclone)" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="15" class="title">
|
||||||
Starships
|
Starships
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2"><img src="{{ icons['Medivac'] }}" class="{{ 'acquired' if 'Medivac' in acquired_items }}" title="Medivac" /></td>
|
<td><img src="{{ icons['Medivac'] }}" class="{{ 'acquired' if 'Medivac' in acquired_items }}" title="Medivac" /></td>
|
||||||
<td colspan="2"><img src="{{ icons['Wraith'] }}" class="{{ 'acquired' if 'Wraith' in acquired_items }}" title="Wraith" /></td>
|
|
||||||
<td colspan="2"><img src="{{ icons['Viking'] }}" class="{{ 'acquired' if 'Viking' in acquired_items }}" title="Viking" /></td>
|
|
||||||
<td colspan="2"><img src="{{ icons['Banshee'] }}" class="{{ 'acquired' if 'Banshee' in acquired_items }}" title="Banshee" /></td>
|
|
||||||
<td colspan="2"><img src="{{ icons['Battlecruiser'] }}" class="{{ 'acquired' if 'Battlecruiser' in acquired_items }}" title="Battlecruiser" /></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><img src="{{ icons['Rapid Deployment Tube (Medivac)'] }}" class="{{ 'acquired' if 'Rapid Deployment Tube (Medivac)' in acquired_items }}" title="Rapid Deployment Tube (Medivac)" /></td>
|
<td><img src="{{ icons['Rapid Deployment Tube (Medivac)'] }}" class="{{ 'acquired' if 'Rapid Deployment Tube (Medivac)' in acquired_items }}" title="Rapid Deployment Tube (Medivac)" /></td>
|
||||||
<td><img src="{{ icons['Advanced Healing AI (Medivac)'] }}" class="{{ 'acquired' if 'Advanced Healing AI (Medivac)' in acquired_items }}" title="Advanced Healing AI (Medivac)" /></td>
|
<td><img src="{{ icons['Advanced Healing AI (Medivac)'] }}" class="{{ 'acquired' if 'Advanced Healing AI (Medivac)' in acquired_items }}" title="Advanced Healing AI (Medivac)" /></td>
|
||||||
|
<td><img src="{{ icons['Expanded Hull (Medivac)'] }}" class="{{ 'acquired' if 'Expanded Hull (Medivac)' in acquired_items }}" title="Expanded Hull (Medivac)" /></td>
|
||||||
|
<td><img src="{{ icons['Afterburners (Medivac)'] }}" class="{{ 'acquired' if 'Afterburners (Medivac)' in acquired_items }}" title="Afterburners (Medivac)" /></td>
|
||||||
|
<td colspan="3"></td>
|
||||||
|
<td><img src="{{ icons['Raven'] }}" class="{{ 'acquired' if 'Raven' in acquired_items }}" title="Raven" /></td>
|
||||||
|
<td><img src="{{ icons['Bio Mechanical Repair Drone (Raven)'] }}" class="{{ 'acquired' if 'Bio Mechanical Repair Drone (Raven)' in acquired_items }}" title="Bio Mechanical Repair Drone (Raven)" /></td>
|
||||||
|
<td><img src="{{ icons['Spider Mines (Raven)'] }}" class="{{ 'acquired' if 'Spider Mines (Raven)' in acquired_items }}" title="Spider Mines (Raven)" /></td>
|
||||||
|
<td><img src="{{ icons['Railgun Turret (Raven)'] }}" class="{{ 'acquired' if 'Railgun Turret (Raven)' in acquired_items }}" title="Railgun Turret (Raven)" /></td>
|
||||||
|
<td><img src="{{ icons['Hunter-Seeker Weapon (Raven)'] }}" class="{{ 'acquired' if 'Hunter-Seeker Weapon (Raven)' in acquired_items }}" title="Hunter-Seeker Weapon (Raven)" /></td>
|
||||||
|
<td><img src="{{ icons['Interference Matrix (Raven)'] }}" class="{{ 'acquired' if 'Interference Matrix (Raven)' in acquired_items }}" title="Interference Matrix (Raven)" /></td>
|
||||||
|
<td><img src="{{ icons['Anti-Armor Missile (Raven)'] }}" class="{{ 'acquired' if 'Anti-Armor Missile (Raven)' in acquired_items }}" title="Anti-Armor Missile (Raven)" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="{{ icons['Wraith'] }}" class="{{ 'acquired' if 'Wraith' in acquired_items }}" title="Wraith" /></td>
|
||||||
<td><img src="{{ icons['Tomahawk Power Cells (Wraith)'] }}" class="{{ 'acquired' if 'Tomahawk Power Cells (Wraith)' in acquired_items }}" title="Tomahawk Power Cells (Wraith)" /></td>
|
<td><img src="{{ icons['Tomahawk Power Cells (Wraith)'] }}" class="{{ 'acquired' if 'Tomahawk Power Cells (Wraith)' in acquired_items }}" title="Tomahawk Power Cells (Wraith)" /></td>
|
||||||
<td><img src="{{ icons['Displacement Field (Wraith)'] }}" class="{{ 'acquired' if 'Displacement Field (Wraith)' in acquired_items }}" title="Displacement Field (Wraith)" /></td>
|
<td><img src="{{ icons['Displacement Field (Wraith)'] }}" class="{{ 'acquired' if 'Displacement Field (Wraith)' in acquired_items }}" title="Displacement Field (Wraith)" /></td>
|
||||||
|
<td><img src="{{ icons['Advanced Laser Technology (Wraith)'] }}" class="{{ 'acquired' if 'Advanced Laser Technology (Wraith)' in acquired_items }}" title="Advanced Laser Technology (Wraith)" /></td>
|
||||||
|
<td colspan="4"></td>
|
||||||
|
<td></td>
|
||||||
|
<td><img src="{{ icons['Internal Tech Module (Raven)'] }}" class="{{ 'acquired' if 'Internal Tech Module (Raven)' in acquired_items }}" title="Internal Tech Module (Raven)" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="{{ icons['Viking'] }}" class="{{ 'acquired' if 'Viking' in acquired_items }}" title="Viking" /></td>
|
||||||
<td><img src="{{ icons['Ripwave Missiles (Viking)'] }}" class="{{ 'acquired' if 'Ripwave Missiles (Viking)' in acquired_items }}" title="Ripwave Missiles (Viking)" /></td>
|
<td><img src="{{ icons['Ripwave Missiles (Viking)'] }}" class="{{ 'acquired' if 'Ripwave Missiles (Viking)' in acquired_items }}" title="Ripwave Missiles (Viking)" /></td>
|
||||||
<td><img src="{{ icons['Phobos-Class Weapons System (Viking)'] }}" class="{{ 'acquired' if 'Phobos-Class Weapons System (Viking)' in acquired_items }}" title="Phobos-Class Weapons System (Viking)" /></td>
|
<td><img src="{{ icons['Phobos-Class Weapons System (Viking)'] }}" class="{{ 'acquired' if 'Phobos-Class Weapons System (Viking)' in acquired_items }}" title="Phobos-Class Weapons System (Viking)" /></td>
|
||||||
<td><img src="{{ icons['Cross-Spectrum Dampeners (Banshee)'] }}" class="{{ 'acquired' if 'Cross-Spectrum Dampeners (Banshee)' in acquired_items }}" title="Cross-Spectrum Dampeners (Banshee)" /></td>
|
<td><img src="{{ icons['Smart Servos (Viking)'] }}" class="{{ 'acquired' if 'Smart Servos (Viking)' in acquired_items }}" title="Smart Servos (Viking)" /></td>
|
||||||
|
<td><img src="{{ icons['Magrail Munitions (Viking)'] }}" class="{{ 'acquired' if 'Magrail Munitions (Viking)' in acquired_items }}" title="Magrail Munitions (Viking)" /></td>
|
||||||
|
<td colspan="3"></td>
|
||||||
|
<td><img src="{{ icons['Science Vessel'] }}" class="{{ 'acquired' if 'Science Vessel' in acquired_items }}" title="Science Vessel" /></td>
|
||||||
|
<td><img src="{{ icons['EMP Shockwave (Science Vessel)'] }}" class="{{ 'acquired' if 'EMP Shockwave (Science Vessel)' in acquired_items }}" title="EMP Shockwave (Science Vessel)" /></td>
|
||||||
|
<td><img src="{{ icons['Defensive Matrix (Science Vessel)'] }}" class="{{ 'acquired' if 'Defensive Matrix (Science Vessel)' in acquired_items }}" title="Defensive Matrix (Science Vessel)" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="{{ icons['Banshee'] }}" class="{{ 'acquired' if 'Banshee' in acquired_items }}" title="Banshee" /></td>
|
||||||
|
<td><img src="{{ crossspectrum_dampeners_banshee_url }}" class="{{ 'acquired' if 'Progressive Cross-Spectrum Dampeners (Banshee)' in acquired_items }}" title="{{ crossspectrum_dampeners_banshee_name }}" /></td>
|
||||||
<td><img src="{{ icons['Shockwave Missile Battery (Banshee)'] }}" class="{{ 'acquired' if 'Shockwave Missile Battery (Banshee)' in acquired_items }}" title="Shockwave Missile Battery (Banshee)" /></td>
|
<td><img src="{{ icons['Shockwave Missile Battery (Banshee)'] }}" class="{{ 'acquired' if 'Shockwave Missile Battery (Banshee)' in acquired_items }}" title="Shockwave Missile Battery (Banshee)" /></td>
|
||||||
|
<td><img src="{{ icons['Hyperflight Rotors (Banshee)'] }}" class="{{ 'acquired' if 'Hyperflight Rotors (Banshee)' in acquired_items }}" title="Hyperflight Rotors (Banshee)" /></td>
|
||||||
|
<td><img src="{{ icons['Laser Targeting System (Banshee)'] }}" class="{{ 'acquired' if 'Laser Targeting System (Banshee)' in acquired_items }}" title="Laser Targeting System (Banshee)" /></td>
|
||||||
|
<td><img src="{{ icons['Internal Tech Module (Banshee)'] }}" class="{{ 'acquired' if 'Internal Tech Module (Banshee)' in acquired_items }}" title="Internal Tech Module (Banshee)" /></td>
|
||||||
|
<td colspan="2"></td>
|
||||||
|
<td><img src="{{ icons['Hercules'] }}" class="{{ 'acquired' if 'Hercules' in acquired_items }}" title="Hercules" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="{{ icons['Battlecruiser'] }}" class="{{ 'acquired' if 'Battlecruiser' in acquired_items }}" title="Battlecruiser" /></td>
|
||||||
<td><img src="{{ icons['Missile Pods (Battlecruiser)'] }}" class="{{ 'acquired' if 'Missile Pods (Battlecruiser)' in acquired_items }}" title="Missile Pods (Battlecruiser)" /></td>
|
<td><img src="{{ icons['Missile Pods (Battlecruiser)'] }}" class="{{ 'acquired' if 'Missile Pods (Battlecruiser)' in acquired_items }}" title="Missile Pods (Battlecruiser)" /></td>
|
||||||
<td><img src="{{ icons['Defensive Matrix (Battlecruiser)'] }}" class="{{ 'acquired' if 'Defensive Matrix (Battlecruiser)' in acquired_items }}" title="Defensive Matrix (Battlecruiser)" /></td>
|
<td><img src="{{ icons['Defensive Matrix (Battlecruiser)'] }}" class="{{ 'acquired' if 'Defensive Matrix (Battlecruiser)' in acquired_items }}" title="Defensive Matrix (Battlecruiser)" /></td>
|
||||||
|
<td><img src="{{ icons['Tactical Jump (Battlecruiser)'] }}" class="{{ 'acquired' if 'Tactical Jump (Battlecruiser)' in acquired_items }}" title="Tactical Jump (Battlecruiser)" /></td>
|
||||||
|
<td><img src="{{ icons['Cloak (Battlecruiser)'] }}" class="{{ 'acquired' if 'Cloak (Battlecruiser)' in acquired_items }}" title="Cloak (Battlecruiser)" /></td>
|
||||||
|
<td><img src="{{ icons['ATX Laser Battery (Battlecruiser)'] }}" class="{{ 'acquired' if 'ATX Laser Battery (Battlecruiser)' in acquired_items }}" title="ATX Laser Battery (Battlecruiser)" /></td>
|
||||||
|
<td><img src="{{ icons['Optimized Logistics (Battlecruiser)'] }}" class="{{ 'acquired' if 'Optimized Logistics (Battlecruiser)' in acquired_items }}" title="Optimized Logistics (Battlecruiser)" /></td>
|
||||||
|
<td></td>
|
||||||
|
<td><img src="{{ icons['Liberator'] }}" class="{{ 'acquired' if 'Liberator' in acquired_items }}" title="Liberator" /></td>
|
||||||
|
<td><img src="{{ icons['Advanced Ballistics (Liberator)'] }}" class="{{ 'acquired' if 'Advanced Ballistics (Liberator)' in acquired_items }}" title="Advanced Ballistics (Liberator)" /></td>
|
||||||
|
<td><img src="{{ icons['Raid Artillery (Liberator)'] }}" class="{{ 'acquired' if 'Raid Artillery (Liberator)' in acquired_items }}" title="Raid Artillery (Liberator)" /></td>
|
||||||
|
<td><img src="{{ icons['Cloak (Liberator)'] }}" class="{{ 'acquired' if 'Cloak (Liberator)' in acquired_items }}" title="Cloak (Liberator)" /></td>
|
||||||
|
<td><img src="{{ icons['Laser Targeting System (Liberator)'] }}" class="{{ 'acquired' if 'Laser Targeting System (Liberator)' in acquired_items }}" title="Laser Targeting System (Liberator)" /></td>
|
||||||
|
<td><img src="{{ icons['Optimized Logistics (Liberator)'] }}" class="{{ 'acquired' if 'Optimized Logistics (Liberator)' in acquired_items }}" title="Optimized Logistics (Liberator)" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="10" class="title">
|
<td></td>
|
||||||
Dominion
|
<td><img src="{{ icons['Internal Tech Module (Battlecruiser)'] }}" class="{{ 'acquired' if 'Internal Tech Module (Battlecruiser)' in acquired_items }}" title="Internal Tech Module (Battlecruiser)" /></td>
|
||||||
</td>
|
<td colspan="6"></td>
|
||||||
|
<td><img src="{{ icons['Valkyrie'] }}" class="{{ 'acquired' if 'Valkyrie' in acquired_items }}" title="Valkyrie" /></td>
|
||||||
|
<td><img src="{{ icons['Enhanced Cluster Launchers (Valkyrie)'] }}" class="{{ 'acquired' if 'Enhanced Cluster Launchers (Valkyrie)' in acquired_items }}" title="Enhanced Cluster Launchers (Valkyrie)" /></td>
|
||||||
|
<td><img src="{{ icons['Shaped Hull (Valkyrie)'] }}" class="{{ 'acquired' if 'Shaped Hull (Valkyrie)' in acquired_items }}" title="Shaped Hull (Valkyrie)" /></td>
|
||||||
|
<td><img src="{{ icons['Burst Lasers (Valkyrie)'] }}" class="{{ 'acquired' if 'Burst Lasers (Valkyrie)' in acquired_items }}" title="Burst Lasers (Valkyrie)" /></td>
|
||||||
|
<td><img src="{{ icons['Afterburners (Valkyrie)'] }}" class="{{ 'acquired' if 'Afterburners (Valkyrie)' in acquired_items }}" title="Afterburners (Valkyrie)" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2"><img src="{{ icons['Ghost'] }}" class="{{ 'acquired' if 'Ghost' in acquired_items }}" title="Ghost" /></td>
|
<td colspan="15" class="title">
|
||||||
<td colspan="2"><img src="{{ icons['Spectre'] }}" class="{{ 'acquired' if 'Spectre' in acquired_items }}" title="Spectre" /></td>
|
|
||||||
<td colspan="2"><img src="{{ icons['Thor'] }}" class="{{ 'acquired' if 'Thor' in acquired_items }}" title="Thor" /></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><img src="{{ icons['Ocular Implants (Ghost)'] }}" class="{{ 'acquired' if 'Ocular Implants (Ghost)' in acquired_items }}" title="Ocular Implants (Ghost)" /></td>
|
|
||||||
<td><img src="{{ icons['Crius Suit (Ghost)'] }}" class="{{ 'acquired' if 'Crius Suit (Ghost)' in acquired_items }}" title="Crius Suit (Ghost)" /></td>
|
|
||||||
<td><img src="{{ icons['Psionic Lash (Spectre)'] }}" class="{{ 'acquired' if 'Psionic Lash (Spectre)' in acquired_items }}" title="Psionic Lash (Spectre)" /></td>
|
|
||||||
<td><img src="{{ icons['Nyx-Class Cloaking Module (Spectre)'] }}" class="{{ 'acquired' if 'Nyx-Class Cloaking Module (Spectre)' in acquired_items }}" title="Nyx-Class Cloaking Module (Spectre)" /></td>
|
|
||||||
<td><img src="{{ icons['330mm Barrage Cannon (Thor)'] }}" class="{{ 'acquired' if '330mm Barrage Cannon (Thor)' in acquired_items }}" title="330mm Barrage Cannon (Thor)" /></td>
|
|
||||||
<td><img src="{{ icons['Immortality Protocol (Thor)'] }}" class="{{ 'acquired' if 'Immortality Protocol (Thor)' in acquired_items }}" title="Immortality Protocol (Thor)" /></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="10" class="title">
|
|
||||||
Mercenaries
|
Mercenaries
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -165,36 +311,18 @@
|
|||||||
<td><img src="{{ icons['Jackson\'s Revenge'] }}" class="{{ 'acquired' if 'Jackson\'s Revenge' in acquired_items }}" title="Jackson's Revenge" /></td>
|
<td><img src="{{ icons['Jackson\'s Revenge'] }}" class="{{ 'acquired' if 'Jackson\'s Revenge' in acquired_items }}" title="Jackson's Revenge" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="10" class="title">
|
<td colspan="15" class="title">
|
||||||
Lab Upgrades
|
General Upgrades
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="{{ icons['Ultra-Capacitors'] }}" class="{{ 'acquired' if 'Ultra-Capacitors' in acquired_items }}" title="Ultra-Capacitors" /></td>
|
<td><img src="{{ icons['Fire-Suppression System (Building)'] }}" class="{{ 'acquired' if 'Fire-Suppression System (Building)' in acquired_items }}" title="Fire-Suppression System (Building)" /></td>
|
||||||
<td><img src="{{ icons['Vanadium Plating'] }}" class="{{ 'acquired' if 'Vanadium Plating' in acquired_items }}" title="Vanadium Plating" /></td>
|
|
||||||
<td><img src="{{ icons['Orbital Depots'] }}" class="{{ 'acquired' if 'Orbital Depots' in acquired_items }}" title="Orbital Depots" /></td>
|
|
||||||
<td><img src="{{ icons['Micro-Filtering'] }}" class="{{ 'acquired' if 'Micro-Filtering' in acquired_items }}" title="Micro-Filtering" /></td>
|
|
||||||
<td><img src="{{ icons['Automated Refinery'] }}" class="{{ 'acquired' if 'Automated Refinery' in acquired_items }}" title="Automated Refinery" /></td>
|
|
||||||
<td><img src="{{ icons['Command Center Reactor'] }}" class="{{ 'acquired' if 'Command Center Reactor' in acquired_items }}" title="Command Center Reactor" /></td>
|
|
||||||
<td><img src="{{ icons['Raven'] }}" class="{{ 'acquired' if 'Raven' in acquired_items }}" title="Raven" /></td>
|
|
||||||
<td><img src="{{ icons['Science Vessel'] }}" class="{{ 'acquired' if 'Science Vessel' in acquired_items }}" title="Science Vessel" /></td>
|
|
||||||
<td><img src="{{ icons['Tech Reactor'] }}" class="{{ 'acquired' if 'Tech Reactor' in acquired_items }}" title="Tech Reactor" /></td>
|
|
||||||
<td><img src="{{ icons['Orbital Strike'] }}" class="{{ 'acquired' if 'Orbital Strike' in acquired_items }}" title="Orbital Strike" /></td>
|
<td><img src="{{ icons['Orbital Strike'] }}" class="{{ 'acquired' if 'Orbital Strike' in acquired_items }}" title="Orbital Strike" /></td>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><img src="{{ icons['Shrike Turret'] }}" class="{{ 'acquired' if 'Shrike Turret' in acquired_items }}" title="Shrike Turret" /></td>
|
|
||||||
<td><img src="{{ icons['Fortified Bunker'] }}" class="{{ 'acquired' if 'Fortified Bunker' in acquired_items }}" title="Fortified Bunker" /></td>
|
|
||||||
<td><img src="{{ icons['Planetary Fortress'] }}" class="{{ 'acquired' if 'Planetary Fortress' in acquired_items }}" title="Planetary Fortress" /></td>
|
|
||||||
<td><img src="{{ icons['Perdition Turret'] }}" class="{{ 'acquired' if 'Perdition Turret' in acquired_items }}" title="Perdition Turret" /></td>
|
|
||||||
<td><img src="{{ icons['Predator'] }}" class="{{ 'acquired' if 'Predator' in acquired_items }}" title="Predator" /></td>
|
|
||||||
<td><img src="{{ icons['Hercules'] }}" class="{{ 'acquired' if 'Hercules' in acquired_items }}" title="Hercules" /></td>
|
|
||||||
<td><img src="{{ icons['Cellular Reactor'] }}" class="{{ 'acquired' if 'Cellular Reactor' in acquired_items }}" title="Cellular Reactor" /></td>
|
<td><img src="{{ icons['Cellular Reactor'] }}" class="{{ 'acquired' if 'Cellular Reactor' in acquired_items }}" title="Cellular Reactor" /></td>
|
||||||
<td><img src="{{ icons['Regenerative Bio-Steel'] }}" class="{{ 'acquired' if 'Regenerative Bio-Steel' in acquired_items }}" title="Regenerative Bio-Steel" /></td>
|
<td><img src="{{ regenerative_biosteel_url }}" class="{{ 'acquired' if 'Progressive Regenerative Bio-Steel' in acquired_items }}" title="Progressive Regenerative Bio-Steel{% if regenerative_biosteel_level > 0 %} (Level {{ regenerative_biosteel_level }}){% endif %}" /></td>
|
||||||
<td><img src="{{ icons['Hive Mind Emulator'] }}" class="{{ 'acquired' if 'Hive Mind Emulator' in acquired_items }}" title="Hive Mind Emulator" /></td>
|
|
||||||
<td><img src="{{ icons['Psi Disrupter'] }}" class="{{ 'acquired' if 'Psi Disrupter' in acquired_items }}" title="Psi Disrupter" /></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="10" class="title">
|
<td colspan="15" class="title">
|
||||||
Protoss Units
|
Protoss Units
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -4,16 +4,27 @@
|
|||||||
<title>Supported Games</title>
|
<title>Supported Games</title>
|
||||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/markdown.css") }}" />
|
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/markdown.css") }}" />
|
||||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/supportedGames.css") }}" />
|
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/supportedGames.css") }}" />
|
||||||
|
<script type="application/ecmascript" src="{{ url_for('static', filename="assets/supportedGames.js") }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{% include 'header/oceanHeader.html' %}
|
{% include 'header/oceanHeader.html' %}
|
||||||
<div id="games" class="markdown">
|
<div id="games" class="markdown">
|
||||||
<h1>Currently Supported Games</h1>
|
<h1>Currently Supported Games</h1>
|
||||||
|
<div>
|
||||||
|
<label for="game-search">Search for your game below!</label><br />
|
||||||
|
<div id="page-controls">
|
||||||
|
<input id="game-search" placeholder="Search by title..." autofocus />
|
||||||
|
<button id="expand-all">Expand All</button>
|
||||||
|
<button id="collapse-all">Collapse All</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% for game_name in worlds | title_sorted %}
|
{% for game_name in worlds | title_sorted %}
|
||||||
{% set world = worlds[game_name] %}
|
{% set world = worlds[game_name] %}
|
||||||
<h2>{{ game_name }}</h2>
|
<h2 class="collapse-toggle" data-game="{{ game_name }}">
|
||||||
<p>
|
<span id="{{ game_name }}-arrow" class="collapse-arrow">▶</span> {{ game_name }}
|
||||||
|
</h2>
|
||||||
|
<p id="{{ game_name }}" class="collapsed">
|
||||||
{{ world.__doc__ | default("No description provided.", true) }}<br />
|
{{ world.__doc__ | default("No description provided.", true) }}<br />
|
||||||
<a href="{{ url_for("game_info", game=game_name, lang="en") }}">Game Page</a>
|
<a href="{{ url_for("game_info", game=game_name, lang="en") }}">Game Page</a>
|
||||||
{% if world.web.tutorials %}
|
{% if world.web.tutorials %}
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ from jinja2 import pass_context, runtime
|
|||||||
from werkzeug.exceptions import abort
|
from werkzeug.exceptions import abort
|
||||||
|
|
||||||
from MultiServer import Context, get_saving_second
|
from MultiServer import Context, get_saving_second
|
||||||
from NetUtils import SlotType, NetworkSlot
|
from NetUtils import ClientStatus, SlotType, NetworkSlot
|
||||||
from Utils import restricted_loads
|
from Utils import restricted_loads
|
||||||
from worlds import lookup_any_item_id_to_name, lookup_any_location_id_to_name, network_data_package
|
from worlds import lookup_any_item_id_to_name, lookup_any_location_id_to_name, network_data_package, games
|
||||||
from worlds.alttp import Items
|
from worlds.alttp import Items
|
||||||
from . import app, cache
|
from . import app, cache
|
||||||
from .models import GameDataPackage, Room
|
from .models import GameDataPackage, Room
|
||||||
@@ -990,6 +990,7 @@ def __renderSC2WoLTracker(multisave: Dict[str, Any], room: Room, locations: Dict
|
|||||||
SC2WOL_LOC_ID_OFFSET = 1000
|
SC2WOL_LOC_ID_OFFSET = 1000
|
||||||
SC2WOL_ITEM_ID_OFFSET = 1000
|
SC2WOL_ITEM_ID_OFFSET = 1000
|
||||||
|
|
||||||
|
|
||||||
icons = {
|
icons = {
|
||||||
"Starting Minerals": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/icons/icon-mineral-protoss.png",
|
"Starting Minerals": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/icons/icon-mineral-protoss.png",
|
||||||
"Starting Vespene": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/icons/icon-gas-terran.png",
|
"Starting Vespene": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/icons/icon-gas-terran.png",
|
||||||
@@ -1034,15 +1035,36 @@ def __renderSC2WoLTracker(multisave: Dict[str, Any], room: Room, locations: Dict
|
|||||||
"Reaper": "https://static.wikia.nocookie.net/starcraft/images/7/7d/Reaper_SC2_Icon1.jpg",
|
"Reaper": "https://static.wikia.nocookie.net/starcraft/images/7/7d/Reaper_SC2_Icon1.jpg",
|
||||||
|
|
||||||
"Stimpack (Marine)": "https://0rganics.org/archipelago/sc2wol/StimpacksCampaign.png",
|
"Stimpack (Marine)": "https://0rganics.org/archipelago/sc2wol/StimpacksCampaign.png",
|
||||||
|
"Super Stimpack (Marine)": "/static/static/icons/sc2/superstimpack.png",
|
||||||
"Combat Shield (Marine)": "https://0rganics.org/archipelago/sc2wol/CombatShieldCampaign.png",
|
"Combat Shield (Marine)": "https://0rganics.org/archipelago/sc2wol/CombatShieldCampaign.png",
|
||||||
|
"Laser Targeting System (Marine)": "/static/static/icons/sc2/lasertargetingsystem.png",
|
||||||
|
"Magrail Munitions (Marine)": "/static/static/icons/sc2/magrailmunitions.png",
|
||||||
|
"Optimized Logistics (Marine)": "/static/static/icons/sc2/optimizedlogistics.png",
|
||||||
"Advanced Medic Facilities (Medic)": "https://0rganics.org/archipelago/sc2wol/AdvancedMedicFacilities.png",
|
"Advanced Medic Facilities (Medic)": "https://0rganics.org/archipelago/sc2wol/AdvancedMedicFacilities.png",
|
||||||
"Stabilizer Medpacks (Medic)": "https://0rganics.org/archipelago/sc2wol/StabilizerMedpacks.png",
|
"Stabilizer Medpacks (Medic)": "https://0rganics.org/archipelago/sc2wol/StabilizerMedpacks.png",
|
||||||
|
"Restoration (Medic)": "/static/static/icons/sc2/restoration.png",
|
||||||
|
"Optical Flare (Medic)": "/static/static/icons/sc2/opticalflare.png",
|
||||||
|
"Optimized Logistics (Medic)": "/static/static/icons/sc2/optimizedlogistics.png",
|
||||||
"Incinerator Gauntlets (Firebat)": "https://0rganics.org/archipelago/sc2wol/IncineratorGauntlets.png",
|
"Incinerator Gauntlets (Firebat)": "https://0rganics.org/archipelago/sc2wol/IncineratorGauntlets.png",
|
||||||
"Juggernaut Plating (Firebat)": "https://0rganics.org/archipelago/sc2wol/JuggernautPlating.png",
|
"Juggernaut Plating (Firebat)": "https://0rganics.org/archipelago/sc2wol/JuggernautPlating.png",
|
||||||
|
"Stimpack (Firebat)": "https://0rganics.org/archipelago/sc2wol/StimpacksCampaign.png",
|
||||||
|
"Super Stimpack (Firebat)": "/static/static/icons/sc2/superstimpack.png",
|
||||||
|
"Optimized Logistics (Firebat)": "/static/static/icons/sc2/optimizedlogistics.png",
|
||||||
"Concussive Shells (Marauder)": "https://0rganics.org/archipelago/sc2wol/ConcussiveShellsCampaign.png",
|
"Concussive Shells (Marauder)": "https://0rganics.org/archipelago/sc2wol/ConcussiveShellsCampaign.png",
|
||||||
"Kinetic Foam (Marauder)": "https://0rganics.org/archipelago/sc2wol/KineticFoam.png",
|
"Kinetic Foam (Marauder)": "https://0rganics.org/archipelago/sc2wol/KineticFoam.png",
|
||||||
|
"Stimpack (Marauder)": "https://0rganics.org/archipelago/sc2wol/StimpacksCampaign.png",
|
||||||
|
"Super Stimpack (Marauder)": "/static/static/icons/sc2/superstimpack.png",
|
||||||
|
"Laser Targeting System (Marauder)": "/static/static/icons/sc2/lasertargetingsystem.png",
|
||||||
|
"Magrail Munitions (Marauder)": "/static/static/icons/sc2/magrailmunitions.png",
|
||||||
|
"Internal Tech Module (Marauder)": "/static/static/icons/sc2/internalizedtechmodule.png",
|
||||||
"U-238 Rounds (Reaper)": "https://0rganics.org/archipelago/sc2wol/U-238Rounds.png",
|
"U-238 Rounds (Reaper)": "https://0rganics.org/archipelago/sc2wol/U-238Rounds.png",
|
||||||
"G-4 Clusterbomb (Reaper)": "https://0rganics.org/archipelago/sc2wol/G-4Clusterbomb.png",
|
"G-4 Clusterbomb (Reaper)": "https://0rganics.org/archipelago/sc2wol/G-4Clusterbomb.png",
|
||||||
|
"Stimpack (Reaper)": "https://0rganics.org/archipelago/sc2wol/StimpacksCampaign.png",
|
||||||
|
"Super Stimpack (Reaper)": "/static/static/icons/sc2/superstimpack.png",
|
||||||
|
"Laser Targeting System (Reaper)": "/static/static/icons/sc2/lasertargetingsystem.png",
|
||||||
|
"Advanced Cloaking Field (Reaper)": "/static/static/icons/sc2/terran-cloak-color.png",
|
||||||
|
"Spider Mines (Reaper)": "/static/static/icons/sc2/spidermine.png",
|
||||||
|
"Combat Drugs (Reaper)": "/static/static/icons/sc2/reapercombatdrugs.png",
|
||||||
|
|
||||||
"Hellion": "https://static.wikia.nocookie.net/starcraft/images/5/56/Hellion_SC2_Icon1.jpg",
|
"Hellion": "https://static.wikia.nocookie.net/starcraft/images/5/56/Hellion_SC2_Icon1.jpg",
|
||||||
"Vulture": "https://static.wikia.nocookie.net/starcraft/images/d/da/Vulture_WoL.jpg",
|
"Vulture": "https://static.wikia.nocookie.net/starcraft/images/d/da/Vulture_WoL.jpg",
|
||||||
@@ -1052,14 +1074,35 @@ def __renderSC2WoLTracker(multisave: Dict[str, Any], room: Room, locations: Dict
|
|||||||
|
|
||||||
"Twin-Linked Flamethrower (Hellion)": "https://0rganics.org/archipelago/sc2wol/Twin-LinkedFlamethrower.png",
|
"Twin-Linked Flamethrower (Hellion)": "https://0rganics.org/archipelago/sc2wol/Twin-LinkedFlamethrower.png",
|
||||||
"Thermite Filaments (Hellion)": "https://0rganics.org/archipelago/sc2wol/ThermiteFilaments.png",
|
"Thermite Filaments (Hellion)": "https://0rganics.org/archipelago/sc2wol/ThermiteFilaments.png",
|
||||||
"Cerberus Mine (Vulture)": "https://0rganics.org/archipelago/sc2wol/CerberusMine.png",
|
"Hellbat Aspect (Hellion)": "/static/static/icons/sc2/hellionbattlemode.png",
|
||||||
|
"Smart Servos (Hellion)": "/static/static/icons/sc2/transformationservos.png",
|
||||||
|
"Optimized Logistics (Hellion)": "/static/static/icons/sc2/optimizedlogistics.png",
|
||||||
|
"Jump Jets (Hellion)": "/static/static/icons/sc2/jumpjets.png",
|
||||||
|
"Stimpack (Hellion)": "https://0rganics.org/archipelago/sc2wol/StimpacksCampaign.png",
|
||||||
|
"Super Stimpack (Hellion)": "/static/static/icons/sc2/superstimpack.png",
|
||||||
|
"Cerberus Mine (Spider Mine)": "https://0rganics.org/archipelago/sc2wol/CerberusMine.png",
|
||||||
|
"High Explosive Munition (Spider Mine)": "/static/static/icons/sc2/high-explosive-spidermine.png",
|
||||||
"Replenishable Magazine (Vulture)": "https://0rganics.org/archipelago/sc2wol/ReplenishableMagazine.png",
|
"Replenishable Magazine (Vulture)": "https://0rganics.org/archipelago/sc2wol/ReplenishableMagazine.png",
|
||||||
|
"Ion Thrusters (Vulture)": "/static/static/icons/sc2/emergencythrusters.png",
|
||||||
|
"Auto Launchers (Vulture)": "/static/static/icons/sc2/jotunboosters.png",
|
||||||
"Multi-Lock Weapons System (Goliath)": "https://0rganics.org/archipelago/sc2wol/Multi-LockWeaponsSystem.png",
|
"Multi-Lock Weapons System (Goliath)": "https://0rganics.org/archipelago/sc2wol/Multi-LockWeaponsSystem.png",
|
||||||
"Ares-Class Targeting System (Goliath)": "https://0rganics.org/archipelago/sc2wol/Ares-ClassTargetingSystem.png",
|
"Ares-Class Targeting System (Goliath)": "https://0rganics.org/archipelago/sc2wol/Ares-ClassTargetingSystem.png",
|
||||||
|
"Jump Jets (Goliath)": "/static/static/icons/sc2/jumpjets.png",
|
||||||
|
"Optimized Logistics (Goliath)": "/static/static/icons/sc2/optimizedlogistics.png",
|
||||||
"Tri-Lithium Power Cell (Diamondback)": "https://0rganics.org/archipelago/sc2wol/Tri-LithiumPowerCell.png",
|
"Tri-Lithium Power Cell (Diamondback)": "https://0rganics.org/archipelago/sc2wol/Tri-LithiumPowerCell.png",
|
||||||
"Shaped Hull (Diamondback)": "https://0rganics.org/archipelago/sc2wol/ShapedHull.png",
|
"Shaped Hull (Diamondback)": "https://0rganics.org/archipelago/sc2wol/ShapedHull.png",
|
||||||
|
"Hyperfluxor (Diamondback)": "/static/static/icons/sc2/hyperfluxor.png",
|
||||||
|
"Burst Capacitors (Diamondback)": "/static/static/icons/sc2/burstcapacitors.png",
|
||||||
|
"Optimized Logistics (Diamondback)": "/static/static/icons/sc2/optimizedlogistics.png",
|
||||||
"Maelstrom Rounds (Siege Tank)": "https://0rganics.org/archipelago/sc2wol/MaelstromRounds.png",
|
"Maelstrom Rounds (Siege Tank)": "https://0rganics.org/archipelago/sc2wol/MaelstromRounds.png",
|
||||||
"Shaped Blast (Siege Tank)": "https://0rganics.org/archipelago/sc2wol/ShapedBlast.png",
|
"Shaped Blast (Siege Tank)": "https://0rganics.org/archipelago/sc2wol/ShapedBlast.png",
|
||||||
|
"Jump Jets (Siege Tank)": "/static/static/icons/sc2/jumpjets.png",
|
||||||
|
"Spider Mines (Siege Tank)": "/static/static/icons/sc2/siegetank-spidermines.png",
|
||||||
|
"Smart Servos (Siege Tank)": "/static/static/icons/sc2/transformationservos.png",
|
||||||
|
"Graduating Range (Siege Tank)": "/static/static/icons/sc2/siegetankrange.png",
|
||||||
|
"Laser Targeting System (Siege Tank)": "/static/static/icons/sc2/lasertargetingsystem.png",
|
||||||
|
"Advanced Siege Tech (Siege Tank)": "/static/static/icons/sc2/improvedsiegemode.png",
|
||||||
|
"Internal Tech Module (Siege Tank)": "/static/static/icons/sc2/internalizedtechmodule.png",
|
||||||
|
|
||||||
"Medivac": "https://static.wikia.nocookie.net/starcraft/images/d/db/Medivac_SC2_Icon1.jpg",
|
"Medivac": "https://static.wikia.nocookie.net/starcraft/images/d/db/Medivac_SC2_Icon1.jpg",
|
||||||
"Wraith": "https://static.wikia.nocookie.net/starcraft/images/7/75/Wraith_WoL.jpg",
|
"Wraith": "https://static.wikia.nocookie.net/starcraft/images/7/75/Wraith_WoL.jpg",
|
||||||
@@ -1069,25 +1112,77 @@ def __renderSC2WoLTracker(multisave: Dict[str, Any], room: Room, locations: Dict
|
|||||||
|
|
||||||
"Rapid Deployment Tube (Medivac)": "https://0rganics.org/archipelago/sc2wol/RapidDeploymentTube.png",
|
"Rapid Deployment Tube (Medivac)": "https://0rganics.org/archipelago/sc2wol/RapidDeploymentTube.png",
|
||||||
"Advanced Healing AI (Medivac)": "https://0rganics.org/archipelago/sc2wol/AdvancedHealingAI.png",
|
"Advanced Healing AI (Medivac)": "https://0rganics.org/archipelago/sc2wol/AdvancedHealingAI.png",
|
||||||
|
"Expanded Hull (Medivac)": "/static/static/icons/sc2/neosteelfortifiedarmor.png",
|
||||||
|
"Afterburners (Medivac)": "/static/static/icons/sc2/medivacemergencythrusters.png",
|
||||||
"Tomahawk Power Cells (Wraith)": "https://0rganics.org/archipelago/sc2wol/TomahawkPowerCells.png",
|
"Tomahawk Power Cells (Wraith)": "https://0rganics.org/archipelago/sc2wol/TomahawkPowerCells.png",
|
||||||
"Displacement Field (Wraith)": "https://0rganics.org/archipelago/sc2wol/DisplacementField.png",
|
"Displacement Field (Wraith)": "https://0rganics.org/archipelago/sc2wol/DisplacementField.png",
|
||||||
|
"Advanced Laser Technology (Wraith)": "/static/static/icons/sc2/improvedburstlaser.png",
|
||||||
"Ripwave Missiles (Viking)": "https://0rganics.org/archipelago/sc2wol/RipwaveMissiles.png",
|
"Ripwave Missiles (Viking)": "https://0rganics.org/archipelago/sc2wol/RipwaveMissiles.png",
|
||||||
"Phobos-Class Weapons System (Viking)": "https://0rganics.org/archipelago/sc2wol/Phobos-ClassWeaponsSystem.png",
|
"Phobos-Class Weapons System (Viking)": "https://0rganics.org/archipelago/sc2wol/Phobos-ClassWeaponsSystem.png",
|
||||||
"Cross-Spectrum Dampeners (Banshee)": "https://0rganics.org/archipelago/sc2wol/Cross-SpectrumDampeners.png",
|
"Smart Servos (Viking)": "/static/static/icons/sc2/transformationservos.png",
|
||||||
|
"Magrail Munitions (Viking)": "/static/static/icons/sc2/magrailmunitions.png",
|
||||||
|
"Cross-Spectrum Dampeners (Banshee)": "/static/static/icons/sc2/crossspectrumdampeners.png",
|
||||||
|
"Advanced Cross-Spectrum Dampeners (Banshee)": "https://0rganics.org/archipelago/sc2wol/Cross-SpectrumDampeners.png",
|
||||||
"Shockwave Missile Battery (Banshee)": "https://0rganics.org/archipelago/sc2wol/ShockwaveMissileBattery.png",
|
"Shockwave Missile Battery (Banshee)": "https://0rganics.org/archipelago/sc2wol/ShockwaveMissileBattery.png",
|
||||||
|
"Hyperflight Rotors (Banshee)": "/static/static/icons/sc2/hyperflightrotors.png",
|
||||||
|
"Laser Targeting System (Banshee)": "/static/static/icons/sc2/lasertargetingsystem.png",
|
||||||
|
"Internal Tech Module (Banshee)": "/static/static/icons/sc2/internalizedtechmodule.png",
|
||||||
"Missile Pods (Battlecruiser)": "https://0rganics.org/archipelago/sc2wol/MissilePods.png",
|
"Missile Pods (Battlecruiser)": "https://0rganics.org/archipelago/sc2wol/MissilePods.png",
|
||||||
"Defensive Matrix (Battlecruiser)": "https://0rganics.org/archipelago/sc2wol/DefensiveMatrix.png",
|
"Defensive Matrix (Battlecruiser)": "https://0rganics.org/archipelago/sc2wol/DefensiveMatrix.png",
|
||||||
|
"Tactical Jump (Battlecruiser)": "/static/static/icons/sc2/warpjump.png",
|
||||||
|
"Cloak (Battlecruiser)": "/static/static/icons/sc2/terran-cloak-color.png",
|
||||||
|
"ATX Laser Battery (Battlecruiser)": "/static/static/icons/sc2/specialordance.png",
|
||||||
|
"Optimized Logistics (Battlecruiser)": "/static/static/icons/sc2/optimizedlogistics.png",
|
||||||
|
"Internal Tech Module (Battlecruiser)": "/static/static/icons/sc2/internalizedtechmodule.png",
|
||||||
|
|
||||||
"Ghost": "https://static.wikia.nocookie.net/starcraft/images/6/6e/Ghost_SC2_Icon1.jpg",
|
"Ghost": "https://static.wikia.nocookie.net/starcraft/images/6/6e/Ghost_SC2_Icon1.jpg",
|
||||||
"Spectre": "https://static.wikia.nocookie.net/starcraft/images/0/0d/Spectre_WoL.jpg",
|
"Spectre": "https://static.wikia.nocookie.net/starcraft/images/0/0d/Spectre_WoL.jpg",
|
||||||
"Thor": "https://static.wikia.nocookie.net/starcraft/images/e/ef/Thor_SC2_Icon1.jpg",
|
"Thor": "https://static.wikia.nocookie.net/starcraft/images/e/ef/Thor_SC2_Icon1.jpg",
|
||||||
|
|
||||||
|
"Widow Mine": "/static/static/icons/sc2/widowmine.png",
|
||||||
|
"Cyclone": "/static/static/icons/sc2/cyclone.png",
|
||||||
|
"Liberator": "/static/static/icons/sc2/liberator.png",
|
||||||
|
"Valkyrie": "/static/static/icons/sc2/valkyrie.png",
|
||||||
|
|
||||||
"Ocular Implants (Ghost)": "https://0rganics.org/archipelago/sc2wol/OcularImplants.png",
|
"Ocular Implants (Ghost)": "https://0rganics.org/archipelago/sc2wol/OcularImplants.png",
|
||||||
"Crius Suit (Ghost)": "https://0rganics.org/archipelago/sc2wol/CriusSuit.png",
|
"Crius Suit (Ghost)": "https://0rganics.org/archipelago/sc2wol/CriusSuit.png",
|
||||||
|
"EMP Rounds (Ghost)": "/static/static/icons/sc2/terran-emp-color.png",
|
||||||
|
"Lockdown (Ghost)": "/static/static/icons/sc2/lockdown.png",
|
||||||
"Psionic Lash (Spectre)": "https://0rganics.org/archipelago/sc2wol/PsionicLash.png",
|
"Psionic Lash (Spectre)": "https://0rganics.org/archipelago/sc2wol/PsionicLash.png",
|
||||||
"Nyx-Class Cloaking Module (Spectre)": "https://0rganics.org/archipelago/sc2wol/Nyx-ClassCloakingModule.png",
|
"Nyx-Class Cloaking Module (Spectre)": "https://0rganics.org/archipelago/sc2wol/Nyx-ClassCloakingModule.png",
|
||||||
|
"Impaler Rounds (Spectre)": "/static/static/icons/sc2/impalerrounds.png",
|
||||||
"330mm Barrage Cannon (Thor)": "https://0rganics.org/archipelago/sc2wol/330mmBarrageCannon.png",
|
"330mm Barrage Cannon (Thor)": "https://0rganics.org/archipelago/sc2wol/330mmBarrageCannon.png",
|
||||||
"Immortality Protocol (Thor)": "https://0rganics.org/archipelago/sc2wol/ImmortalityProtocol.png",
|
"Immortality Protocol (Thor)": "https://0rganics.org/archipelago/sc2wol/ImmortalityProtocol.png",
|
||||||
|
"High Impact Payload (Thor)": "/static/static/icons/sc2/thorsiegemode.png",
|
||||||
|
"Smart Servos (Thor)": "/static/static/icons/sc2/transformationservos.png",
|
||||||
|
|
||||||
|
"Optimized Logistics (Predator)": "/static/static/icons/sc2/optimizedlogistics.png",
|
||||||
|
"Drilling Claws (Widow Mine)": "/static/static/icons/sc2/drillingclaws.png",
|
||||||
|
"Concealment (Widow Mine)": "/static/static/icons/sc2/widowminehidden.png",
|
||||||
|
"Black Market Launchers (Widow Mine)": "/static/static/icons/sc2/widowmine-attackrange.png",
|
||||||
|
"Executioner Missiles (Widow Mine)": "/static/static/icons/sc2/widowmine-deathblossom.png",
|
||||||
|
"Mag-Field Accelerators (Cyclone)": "/static/static/icons/sc2/magfieldaccelerator.png",
|
||||||
|
"Mag-Field Launchers (Cyclone)": "/static/static/icons/sc2/cyclonerangeupgrade.png",
|
||||||
|
"Targeting Optics (Cyclone)": "/static/static/icons/sc2/targetingoptics.png",
|
||||||
|
"Rapid Fire Launchers (Cyclone)": "/static/static/icons/sc2/ripwavemissiles.png",
|
||||||
|
"Bio Mechanical Repair Drone (Raven)": "/static/static/icons/sc2/biomechanicaldrone.png",
|
||||||
|
"Spider Mines (Raven)": "/static/static/icons/sc2/siegetank-spidermines.png",
|
||||||
|
"Railgun Turret (Raven)": "/static/static/icons/sc2/autoturretblackops.png",
|
||||||
|
"Hunter-Seeker Weapon (Raven)": "/static/static/icons/sc2/specialordance.png",
|
||||||
|
"Interference Matrix (Raven)": "/static/static/icons/sc2/interferencematrix.png",
|
||||||
|
"Anti-Armor Missile (Raven)": "/static/static/icons/sc2/shreddermissile.png",
|
||||||
|
"Internal Tech Module (Raven)": "/static/static/icons/sc2/internalizedtechmodule.png",
|
||||||
|
"EMP Shockwave (Science Vessel)": "/static/static/icons/sc2/staticempblast.png",
|
||||||
|
"Defensive Matrix (Science Vessel)": "https://0rganics.org/archipelago/sc2wol/DefensiveMatrix.png",
|
||||||
|
"Advanced Ballistics (Liberator)": "/static/static/icons/sc2/advanceballistics.png",
|
||||||
|
"Raid Artillery (Liberator)": "/static/static/icons/sc2/terrandefendermodestructureattack.png",
|
||||||
|
"Cloak (Liberator)": "/static/static/icons/sc2/terran-cloak-color.png",
|
||||||
|
"Laser Targeting System (Liberator)": "/static/static/icons/sc2/lasertargetingsystem.png",
|
||||||
|
"Optimized Logistics (Liberator)": "/static/static/icons/sc2/optimizedlogistics.png",
|
||||||
|
"Enhanced Cluster Launchers (Valkyrie)": "https://0rganics.org/archipelago/sc2wol/HellstormBatteries.png",
|
||||||
|
"Shaped Hull (Valkyrie)": "https://0rganics.org/archipelago/sc2wol/ShapedHull.png",
|
||||||
|
"Burst Lasers (Valkyrie)": "/static/static/icons/sc2/improvedburstlaser.png",
|
||||||
|
"Afterburners (Valkyrie)": "/static/static/icons/sc2/medivacemergencythrusters.png",
|
||||||
|
|
||||||
"War Pigs": "https://static.wikia.nocookie.net/starcraft/images/e/ed/WarPigs_SC2_Icon1.jpg",
|
"War Pigs": "https://static.wikia.nocookie.net/starcraft/images/e/ed/WarPigs_SC2_Icon1.jpg",
|
||||||
"Devil Dogs": "https://static.wikia.nocookie.net/starcraft/images/3/33/DevilDogs_SC2_Icon1.jpg",
|
"Devil Dogs": "https://static.wikia.nocookie.net/starcraft/images/3/33/DevilDogs_SC2_Icon1.jpg",
|
||||||
@@ -1109,14 +1204,15 @@ def __renderSC2WoLTracker(multisave: Dict[str, Any], room: Room, locations: Dict
|
|||||||
"Tech Reactor": "https://static.wikia.nocookie.net/starcraft/images/c/c5/SC2_Lab_Tech_Reactor_Icon.png",
|
"Tech Reactor": "https://static.wikia.nocookie.net/starcraft/images/c/c5/SC2_Lab_Tech_Reactor_Icon.png",
|
||||||
"Orbital Strike": "https://static.wikia.nocookie.net/starcraft/images/d/df/SC2_Lab_Orb_Strike_Icon.png",
|
"Orbital Strike": "https://static.wikia.nocookie.net/starcraft/images/d/df/SC2_Lab_Orb_Strike_Icon.png",
|
||||||
|
|
||||||
"Shrike Turret": "https://static.wikia.nocookie.net/starcraft/images/4/44/SC2_Lab_Shrike_Turret_Icon.png",
|
"Shrike Turret (Bunker)": "https://static.wikia.nocookie.net/starcraft/images/4/44/SC2_Lab_Shrike_Turret_Icon.png",
|
||||||
"Fortified Bunker": "https://static.wikia.nocookie.net/starcraft/images/4/4f/SC2_Lab_FortBunker_Icon.png",
|
"Fortified Bunker (Bunker)": "https://static.wikia.nocookie.net/starcraft/images/4/4f/SC2_Lab_FortBunker_Icon.png",
|
||||||
"Planetary Fortress": "https://static.wikia.nocookie.net/starcraft/images/0/0b/SC2_Lab_PlanetFortress_Icon.png",
|
"Planetary Fortress": "https://static.wikia.nocookie.net/starcraft/images/0/0b/SC2_Lab_PlanetFortress_Icon.png",
|
||||||
"Perdition Turret": "https://static.wikia.nocookie.net/starcraft/images/a/af/SC2_Lab_PerdTurret_Icon.png",
|
"Perdition Turret": "https://static.wikia.nocookie.net/starcraft/images/a/af/SC2_Lab_PerdTurret_Icon.png",
|
||||||
"Predator": "https://static.wikia.nocookie.net/starcraft/images/8/83/SC2_Lab_Predator_Icon.png",
|
"Predator": "https://static.wikia.nocookie.net/starcraft/images/8/83/SC2_Lab_Predator_Icon.png",
|
||||||
"Hercules": "https://static.wikia.nocookie.net/starcraft/images/4/40/SC2_Lab_Hercules_Icon.png",
|
"Hercules": "https://static.wikia.nocookie.net/starcraft/images/4/40/SC2_Lab_Hercules_Icon.png",
|
||||||
"Cellular Reactor": "https://static.wikia.nocookie.net/starcraft/images/d/d8/SC2_Lab_CellReactor_Icon.png",
|
"Cellular Reactor": "https://static.wikia.nocookie.net/starcraft/images/d/d8/SC2_Lab_CellReactor_Icon.png",
|
||||||
"Regenerative Bio-Steel": "https://static.wikia.nocookie.net/starcraft/images/d/d3/SC2_Lab_BioSteel_Icon.png",
|
"Regenerative Bio-Steel Level 1": "/static/static/icons/sc2/SC2_Lab_BioSteel_L1.png",
|
||||||
|
"Regenerative Bio-Steel Level 2": "/static/static/icons/sc2/SC2_Lab_BioSteel_L2.png",
|
||||||
"Hive Mind Emulator": "https://static.wikia.nocookie.net/starcraft/images/b/bc/SC2_Lab_Hive_Emulator_Icon.png",
|
"Hive Mind Emulator": "https://static.wikia.nocookie.net/starcraft/images/b/bc/SC2_Lab_Hive_Emulator_Icon.png",
|
||||||
"Psi Disrupter": "https://static.wikia.nocookie.net/starcraft/images/c/cf/SC2_Lab_Psi_Disruptor_Icon.png",
|
"Psi Disrupter": "https://static.wikia.nocookie.net/starcraft/images/c/cf/SC2_Lab_Psi_Disruptor_Icon.png",
|
||||||
|
|
||||||
@@ -1132,40 +1228,71 @@ def __renderSC2WoLTracker(multisave: Dict[str, Any], room: Room, locations: Dict
|
|||||||
|
|
||||||
"Nothing": "",
|
"Nothing": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
sc2wol_location_ids = {
|
sc2wol_location_ids = {
|
||||||
"Liberation Day": [SC2WOL_LOC_ID_OFFSET + 100, SC2WOL_LOC_ID_OFFSET + 101, SC2WOL_LOC_ID_OFFSET + 102, SC2WOL_LOC_ID_OFFSET + 103, SC2WOL_LOC_ID_OFFSET + 104, SC2WOL_LOC_ID_OFFSET + 105, SC2WOL_LOC_ID_OFFSET + 106],
|
"Liberation Day": range(SC2WOL_LOC_ID_OFFSET + 100, SC2WOL_LOC_ID_OFFSET + 200),
|
||||||
"The Outlaws": [SC2WOL_LOC_ID_OFFSET + 200, SC2WOL_LOC_ID_OFFSET + 201],
|
"The Outlaws": range(SC2WOL_LOC_ID_OFFSET + 200, SC2WOL_LOC_ID_OFFSET + 300),
|
||||||
"Zero Hour": [SC2WOL_LOC_ID_OFFSET + 300, SC2WOL_LOC_ID_OFFSET + 301, SC2WOL_LOC_ID_OFFSET + 302, SC2WOL_LOC_ID_OFFSET + 303],
|
"Zero Hour": range(SC2WOL_LOC_ID_OFFSET + 300, SC2WOL_LOC_ID_OFFSET + 400),
|
||||||
"Evacuation": [SC2WOL_LOC_ID_OFFSET + 400, SC2WOL_LOC_ID_OFFSET + 401, SC2WOL_LOC_ID_OFFSET + 402, SC2WOL_LOC_ID_OFFSET + 403],
|
"Evacuation": range(SC2WOL_LOC_ID_OFFSET + 400, SC2WOL_LOC_ID_OFFSET + 500),
|
||||||
"Outbreak": [SC2WOL_LOC_ID_OFFSET + 500, SC2WOL_LOC_ID_OFFSET + 501, SC2WOL_LOC_ID_OFFSET + 502],
|
"Outbreak": range(SC2WOL_LOC_ID_OFFSET + 500, SC2WOL_LOC_ID_OFFSET + 600),
|
||||||
"Safe Haven": [SC2WOL_LOC_ID_OFFSET + 600, SC2WOL_LOC_ID_OFFSET + 601, SC2WOL_LOC_ID_OFFSET + 602, SC2WOL_LOC_ID_OFFSET + 603],
|
"Safe Haven": range(SC2WOL_LOC_ID_OFFSET + 600, SC2WOL_LOC_ID_OFFSET + 700),
|
||||||
"Haven's Fall": [SC2WOL_LOC_ID_OFFSET + 700, SC2WOL_LOC_ID_OFFSET + 701, SC2WOL_LOC_ID_OFFSET + 702, SC2WOL_LOC_ID_OFFSET + 703],
|
"Haven's Fall": range(SC2WOL_LOC_ID_OFFSET + 700, SC2WOL_LOC_ID_OFFSET + 800),
|
||||||
"Smash and Grab": [SC2WOL_LOC_ID_OFFSET + 800, SC2WOL_LOC_ID_OFFSET + 801, SC2WOL_LOC_ID_OFFSET + 802, SC2WOL_LOC_ID_OFFSET + 803, SC2WOL_LOC_ID_OFFSET + 804],
|
"Smash and Grab": range(SC2WOL_LOC_ID_OFFSET + 800, SC2WOL_LOC_ID_OFFSET + 900),
|
||||||
"The Dig": [SC2WOL_LOC_ID_OFFSET + 900, SC2WOL_LOC_ID_OFFSET + 901, SC2WOL_LOC_ID_OFFSET + 902, SC2WOL_LOC_ID_OFFSET + 903],
|
"The Dig": range(SC2WOL_LOC_ID_OFFSET + 900, SC2WOL_LOC_ID_OFFSET + 1000),
|
||||||
"The Moebius Factor": [SC2WOL_LOC_ID_OFFSET + 1000, SC2WOL_LOC_ID_OFFSET + 1003, SC2WOL_LOC_ID_OFFSET + 1004, SC2WOL_LOC_ID_OFFSET + 1005, SC2WOL_LOC_ID_OFFSET + 1006, SC2WOL_LOC_ID_OFFSET + 1007, SC2WOL_LOC_ID_OFFSET + 1008],
|
"The Moebius Factor": range(SC2WOL_LOC_ID_OFFSET + 1000, SC2WOL_LOC_ID_OFFSET + 1100),
|
||||||
"Supernova": [SC2WOL_LOC_ID_OFFSET + 1100, SC2WOL_LOC_ID_OFFSET + 1101, SC2WOL_LOC_ID_OFFSET + 1102, SC2WOL_LOC_ID_OFFSET + 1103, SC2WOL_LOC_ID_OFFSET + 1104],
|
"Supernova": range(SC2WOL_LOC_ID_OFFSET + 1100, SC2WOL_LOC_ID_OFFSET + 1200),
|
||||||
"Maw of the Void": [SC2WOL_LOC_ID_OFFSET + 1200, SC2WOL_LOC_ID_OFFSET + 1201, SC2WOL_LOC_ID_OFFSET + 1202, SC2WOL_LOC_ID_OFFSET + 1203, SC2WOL_LOC_ID_OFFSET + 1204, SC2WOL_LOC_ID_OFFSET + 1205],
|
"Maw of the Void": range(SC2WOL_LOC_ID_OFFSET + 1200, SC2WOL_LOC_ID_OFFSET + 1300),
|
||||||
"Devil's Playground": [SC2WOL_LOC_ID_OFFSET + 1300, SC2WOL_LOC_ID_OFFSET + 1301, SC2WOL_LOC_ID_OFFSET + 1302],
|
"Devil's Playground": range(SC2WOL_LOC_ID_OFFSET + 1300, SC2WOL_LOC_ID_OFFSET + 1400),
|
||||||
"Welcome to the Jungle": [SC2WOL_LOC_ID_OFFSET + 1400, SC2WOL_LOC_ID_OFFSET + 1401, SC2WOL_LOC_ID_OFFSET + 1402, SC2WOL_LOC_ID_OFFSET + 1403],
|
"Welcome to the Jungle": range(SC2WOL_LOC_ID_OFFSET + 1400, SC2WOL_LOC_ID_OFFSET + 1500),
|
||||||
"Breakout": [SC2WOL_LOC_ID_OFFSET + 1500, SC2WOL_LOC_ID_OFFSET + 1501, SC2WOL_LOC_ID_OFFSET + 1502],
|
"Breakout": range(SC2WOL_LOC_ID_OFFSET + 1500, SC2WOL_LOC_ID_OFFSET + 1600),
|
||||||
"Ghost of a Chance": [SC2WOL_LOC_ID_OFFSET + 1600, SC2WOL_LOC_ID_OFFSET + 1601, SC2WOL_LOC_ID_OFFSET + 1602, SC2WOL_LOC_ID_OFFSET + 1603, SC2WOL_LOC_ID_OFFSET + 1604, SC2WOL_LOC_ID_OFFSET + 1605],
|
"Ghost of a Chance": range(SC2WOL_LOC_ID_OFFSET + 1600, SC2WOL_LOC_ID_OFFSET + 1700),
|
||||||
"The Great Train Robbery": [SC2WOL_LOC_ID_OFFSET + 1700, SC2WOL_LOC_ID_OFFSET + 1701, SC2WOL_LOC_ID_OFFSET + 1702, SC2WOL_LOC_ID_OFFSET + 1703],
|
"The Great Train Robbery": range(SC2WOL_LOC_ID_OFFSET + 1700, SC2WOL_LOC_ID_OFFSET + 1800),
|
||||||
"Cutthroat": [SC2WOL_LOC_ID_OFFSET + 1800, SC2WOL_LOC_ID_OFFSET + 1801, SC2WOL_LOC_ID_OFFSET + 1802, SC2WOL_LOC_ID_OFFSET + 1803, SC2WOL_LOC_ID_OFFSET + 1804],
|
"Cutthroat": range(SC2WOL_LOC_ID_OFFSET + 1800, SC2WOL_LOC_ID_OFFSET + 1900),
|
||||||
"Engine of Destruction": [SC2WOL_LOC_ID_OFFSET + 1900, SC2WOL_LOC_ID_OFFSET + 1901, SC2WOL_LOC_ID_OFFSET + 1902, SC2WOL_LOC_ID_OFFSET + 1903, SC2WOL_LOC_ID_OFFSET + 1904, SC2WOL_LOC_ID_OFFSET + 1905],
|
"Engine of Destruction": range(SC2WOL_LOC_ID_OFFSET + 1900, SC2WOL_LOC_ID_OFFSET + 2000),
|
||||||
"Media Blitz": [SC2WOL_LOC_ID_OFFSET + 2000, SC2WOL_LOC_ID_OFFSET + 2001, SC2WOL_LOC_ID_OFFSET + 2002, SC2WOL_LOC_ID_OFFSET + 2003, SC2WOL_LOC_ID_OFFSET + 2004],
|
"Media Blitz": range(SC2WOL_LOC_ID_OFFSET + 2000, SC2WOL_LOC_ID_OFFSET + 2100),
|
||||||
"Piercing the Shroud": [SC2WOL_LOC_ID_OFFSET + 2100, SC2WOL_LOC_ID_OFFSET + 2101, SC2WOL_LOC_ID_OFFSET + 2102, SC2WOL_LOC_ID_OFFSET + 2103, SC2WOL_LOC_ID_OFFSET + 2104, SC2WOL_LOC_ID_OFFSET + 2105],
|
"Piercing the Shroud": range(SC2WOL_LOC_ID_OFFSET + 2100, SC2WOL_LOC_ID_OFFSET + 2200),
|
||||||
"Whispers of Doom": [SC2WOL_LOC_ID_OFFSET + 2200, SC2WOL_LOC_ID_OFFSET + 2201, SC2WOL_LOC_ID_OFFSET + 2202, SC2WOL_LOC_ID_OFFSET + 2203],
|
"Whispers of Doom": range(SC2WOL_LOC_ID_OFFSET + 2200, SC2WOL_LOC_ID_OFFSET + 2300),
|
||||||
"A Sinister Turn": [SC2WOL_LOC_ID_OFFSET + 2300, SC2WOL_LOC_ID_OFFSET + 2301, SC2WOL_LOC_ID_OFFSET + 2302, SC2WOL_LOC_ID_OFFSET + 2303],
|
"A Sinister Turn": range(SC2WOL_LOC_ID_OFFSET + 2300, SC2WOL_LOC_ID_OFFSET + 2400),
|
||||||
"Echoes of the Future": [SC2WOL_LOC_ID_OFFSET + 2400, SC2WOL_LOC_ID_OFFSET + 2401, SC2WOL_LOC_ID_OFFSET + 2402],
|
"Echoes of the Future": range(SC2WOL_LOC_ID_OFFSET + 2400, SC2WOL_LOC_ID_OFFSET + 2500),
|
||||||
"In Utter Darkness": [SC2WOL_LOC_ID_OFFSET + 2500, SC2WOL_LOC_ID_OFFSET + 2501, SC2WOL_LOC_ID_OFFSET + 2502],
|
"In Utter Darkness": range(SC2WOL_LOC_ID_OFFSET + 2500, SC2WOL_LOC_ID_OFFSET + 2600),
|
||||||
"Gates of Hell": [SC2WOL_LOC_ID_OFFSET + 2600, SC2WOL_LOC_ID_OFFSET + 2601],
|
"Gates of Hell": range(SC2WOL_LOC_ID_OFFSET + 2600, SC2WOL_LOC_ID_OFFSET + 2700),
|
||||||
"Belly of the Beast": [SC2WOL_LOC_ID_OFFSET + 2700, SC2WOL_LOC_ID_OFFSET + 2701, SC2WOL_LOC_ID_OFFSET + 2702, SC2WOL_LOC_ID_OFFSET + 2703],
|
"Belly of the Beast": range(SC2WOL_LOC_ID_OFFSET + 2700, SC2WOL_LOC_ID_OFFSET + 2800),
|
||||||
"Shatter the Sky": [SC2WOL_LOC_ID_OFFSET + 2800, SC2WOL_LOC_ID_OFFSET + 2801, SC2WOL_LOC_ID_OFFSET + 2802, SC2WOL_LOC_ID_OFFSET + 2803, SC2WOL_LOC_ID_OFFSET + 2804, SC2WOL_LOC_ID_OFFSET + 2805],
|
"Shatter the Sky": range(SC2WOL_LOC_ID_OFFSET + 2800, SC2WOL_LOC_ID_OFFSET + 2900),
|
||||||
}
|
}
|
||||||
|
|
||||||
display_data = {}
|
display_data = {}
|
||||||
|
|
||||||
|
# Grouped Items
|
||||||
|
grouped_item_ids = {
|
||||||
|
"Progressive Weapon Upgrade": 107 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
|
"Progressive Armor Upgrade": 108 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
|
"Progressive Infantry Upgrade": 109 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
|
"Progressive Vehicle Upgrade": 110 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
|
"Progressive Ship Upgrade": 111 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
|
"Progressive Weapon/Armor Upgrade": 112 + SC2WOL_ITEM_ID_OFFSET
|
||||||
|
}
|
||||||
|
grouped_item_replacements = {
|
||||||
|
"Progressive Weapon Upgrade": ["Progressive Infantry Weapon", "Progressive Vehicle Weapon", "Progressive Ship Weapon"],
|
||||||
|
"Progressive Armor Upgrade": ["Progressive Infantry Armor", "Progressive Vehicle Armor", "Progressive Ship Armor"],
|
||||||
|
"Progressive Infantry Upgrade": ["Progressive Infantry Weapon", "Progressive Infantry Armor"],
|
||||||
|
"Progressive Vehicle Upgrade": ["Progressive Vehicle Weapon", "Progressive Vehicle Armor"],
|
||||||
|
"Progressive Ship Upgrade": ["Progressive Ship Weapon", "Progressive Ship Armor"]
|
||||||
|
}
|
||||||
|
grouped_item_replacements["Progressive Weapon/Armor Upgrade"] = grouped_item_replacements["Progressive Weapon Upgrade"] + grouped_item_replacements["Progressive Armor Upgrade"]
|
||||||
|
replacement_item_ids = {
|
||||||
|
"Progressive Infantry Weapon": 100 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
|
"Progressive Infantry Armor": 102 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
|
"Progressive Vehicle Weapon": 103 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
|
"Progressive Vehicle Armor": 104 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
|
"Progressive Ship Weapon": 105 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
|
"Progressive Ship Armor": 106 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
|
}
|
||||||
|
for grouped_item_name, grouped_item_id in grouped_item_ids.items():
|
||||||
|
count: int = inventory[grouped_item_id]
|
||||||
|
if count > 0:
|
||||||
|
for replacement_item in grouped_item_replacements[grouped_item_name]:
|
||||||
|
replacement_id: int = replacement_item_ids[replacement_item]
|
||||||
|
inventory[replacement_id] = count
|
||||||
|
|
||||||
# Determine display for progressive items
|
# Determine display for progressive items
|
||||||
progressive_items = {
|
progressive_items = {
|
||||||
"Progressive Infantry Weapon": 100 + SC2WOL_ITEM_ID_OFFSET,
|
"Progressive Infantry Weapon": 100 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
@@ -1173,7 +1300,15 @@ def __renderSC2WoLTracker(multisave: Dict[str, Any], room: Room, locations: Dict
|
|||||||
"Progressive Vehicle Weapon": 103 + SC2WOL_ITEM_ID_OFFSET,
|
"Progressive Vehicle Weapon": 103 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
"Progressive Vehicle Armor": 104 + SC2WOL_ITEM_ID_OFFSET,
|
"Progressive Vehicle Armor": 104 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
"Progressive Ship Weapon": 105 + SC2WOL_ITEM_ID_OFFSET,
|
"Progressive Ship Weapon": 105 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
"Progressive Ship Armor": 106 + SC2WOL_ITEM_ID_OFFSET
|
"Progressive Ship Armor": 106 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
|
"Progressive Stimpack (Marine)": 208 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
|
"Progressive Stimpack (Firebat)": 226 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
|
"Progressive Stimpack (Marauder)": 228 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
|
"Progressive Stimpack (Reaper)": 250 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
|
"Progressive Stimpack (Hellion)": 259 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
|
"Progressive High Impact Payload (Thor)": 361 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
|
"Progressive Cross-Spectrum Dampeners (Banshee)": 316 + SC2WOL_ITEM_ID_OFFSET,
|
||||||
|
"Progressive Regenerative Bio-Steel": 617 + SC2WOL_ITEM_ID_OFFSET
|
||||||
}
|
}
|
||||||
progressive_names = {
|
progressive_names = {
|
||||||
"Progressive Infantry Weapon": ["Infantry Weapons Level 1", "Infantry Weapons Level 1", "Infantry Weapons Level 2", "Infantry Weapons Level 3"],
|
"Progressive Infantry Weapon": ["Infantry Weapons Level 1", "Infantry Weapons Level 1", "Infantry Weapons Level 2", "Infantry Weapons Level 3"],
|
||||||
@@ -1181,14 +1316,27 @@ def __renderSC2WoLTracker(multisave: Dict[str, Any], room: Room, locations: Dict
|
|||||||
"Progressive Vehicle Weapon": ["Vehicle Weapons Level 1", "Vehicle Weapons Level 1", "Vehicle Weapons Level 2", "Vehicle Weapons Level 3"],
|
"Progressive Vehicle Weapon": ["Vehicle Weapons Level 1", "Vehicle Weapons Level 1", "Vehicle Weapons Level 2", "Vehicle Weapons Level 3"],
|
||||||
"Progressive Vehicle Armor": ["Vehicle Armor Level 1", "Vehicle Armor Level 1", "Vehicle Armor Level 2", "Vehicle Armor Level 3"],
|
"Progressive Vehicle Armor": ["Vehicle Armor Level 1", "Vehicle Armor Level 1", "Vehicle Armor Level 2", "Vehicle Armor Level 3"],
|
||||||
"Progressive Ship Weapon": ["Ship Weapons Level 1", "Ship Weapons Level 1", "Ship Weapons Level 2", "Ship Weapons Level 3"],
|
"Progressive Ship Weapon": ["Ship Weapons Level 1", "Ship Weapons Level 1", "Ship Weapons Level 2", "Ship Weapons Level 3"],
|
||||||
"Progressive Ship Armor": ["Ship Armor Level 1", "Ship Armor Level 1", "Ship Armor Level 2", "Ship Armor Level 3"]
|
"Progressive Ship Armor": ["Ship Armor Level 1", "Ship Armor Level 1", "Ship Armor Level 2", "Ship Armor Level 3"],
|
||||||
|
"Progressive Stimpack (Marine)": ["Stimpack (Marine)", "Stimpack (Marine)", "Super Stimpack (Marine)"],
|
||||||
|
"Progressive Stimpack (Firebat)": ["Stimpack (Firebat)", "Stimpack (Firebat)", "Super Stimpack (Firebat)"],
|
||||||
|
"Progressive Stimpack (Marauder)": ["Stimpack (Marauder)", "Stimpack (Marauder)", "Super Stimpack (Marauder)"],
|
||||||
|
"Progressive Stimpack (Reaper)": ["Stimpack (Reaper)", "Stimpack (Reaper)", "Super Stimpack (Reaper)"],
|
||||||
|
"Progressive Stimpack (Hellion)": ["Stimpack (Hellion)", "Stimpack (Hellion)", "Super Stimpack (Hellion)"],
|
||||||
|
"Progressive High Impact Payload (Thor)": ["High Impact Payload (Thor)", "High Impact Payload (Thor)", "Smart Servos (Thor)"],
|
||||||
|
"Progressive Cross-Spectrum Dampeners (Banshee)": ["Cross-Spectrum Dampeners (Banshee)", "Cross-Spectrum Dampeners (Banshee)", "Advanced Cross-Spectrum Dampeners (Banshee)"],
|
||||||
|
"Progressive Regenerative Bio-Steel": ["Regenerative Bio-Steel Level 1", "Regenerative Bio-Steel Level 1", "Regenerative Bio-Steel Level 2"]
|
||||||
}
|
}
|
||||||
for item_name, item_id in progressive_items.items():
|
for item_name, item_id in progressive_items.items():
|
||||||
level = min(inventory[item_id], len(progressive_names[item_name]) - 1)
|
level = min(inventory[item_id], len(progressive_names[item_name]) - 1)
|
||||||
display_name = progressive_names[item_name][level]
|
display_name = progressive_names[item_name][level]
|
||||||
base_name = item_name.split(maxsplit=1)[1].lower().replace(' ', '_')
|
base_name = (item_name.split(maxsplit=1)[1].lower()
|
||||||
|
.replace(' ', '_')
|
||||||
|
.replace("-", "")
|
||||||
|
.replace("(", "")
|
||||||
|
.replace(")", ""))
|
||||||
display_data[base_name + "_level"] = level
|
display_data[base_name + "_level"] = level
|
||||||
display_data[base_name + "_url"] = icons[display_name]
|
display_data[base_name + "_url"] = icons[display_name]
|
||||||
|
display_data[base_name + "_name"] = display_name
|
||||||
|
|
||||||
# Multi-items
|
# Multi-items
|
||||||
multi_items = {
|
multi_items = {
|
||||||
@@ -1220,12 +1368,12 @@ def __renderSC2WoLTracker(multisave: Dict[str, Any], room: Room, locations: Dict
|
|||||||
checks_in_area['Total'] = sum(checks_in_area.values())
|
checks_in_area['Total'] = sum(checks_in_area.values())
|
||||||
|
|
||||||
return render_template("sc2wolTracker.html",
|
return render_template("sc2wolTracker.html",
|
||||||
inventory=inventory, icons=icons,
|
inventory=inventory, icons=icons,
|
||||||
acquired_items={lookup_any_item_id_to_name[id] for id in inventory if
|
acquired_items={lookup_any_item_id_to_name[id] for id in inventory if
|
||||||
id in lookup_any_item_id_to_name},
|
id in lookup_any_item_id_to_name},
|
||||||
player=player, team=team, room=room, player_name=playerName,
|
player=player, team=team, room=room, player_name=playerName,
|
||||||
checks_done=checks_done, checks_in_area=checks_in_area, location_info=location_info,
|
checks_done=checks_done, checks_in_area=checks_in_area, location_info=location_info,
|
||||||
**display_data)
|
**display_data)
|
||||||
|
|
||||||
def __renderChecksfinder(multisave: Dict[str, Any], room: Room, locations: Dict[int, Dict[int, Tuple[int, int, int]]],
|
def __renderChecksfinder(multisave: Dict[str, Any], room: Room, locations: Dict[int, Dict[int, Tuple[int, int, int]]],
|
||||||
inventory: Counter, team: int, player: int, playerName: str,
|
inventory: Counter, team: int, player: int, playerName: str,
|
||||||
@@ -1400,7 +1548,7 @@ def _get_multiworld_tracker_data(tracker: UUID) -> typing.Optional[typing.Dict[s
|
|||||||
for player, name in enumerate(names, 1):
|
for player, name in enumerate(names, 1):
|
||||||
player_names[team, player] = name
|
player_names[team, player] = name
|
||||||
states[team, player] = multisave.get("client_game_state", {}).get((team, player), 0)
|
states[team, player] = multisave.get("client_game_state", {}).get((team, player), 0)
|
||||||
if states[team, player] == 30: # Goal Completed
|
if states[team, player] == ClientStatus.CLIENT_GOAL and player not in groups:
|
||||||
completed_worlds += 1
|
completed_worlds += 1
|
||||||
long_player_names = player_names.copy()
|
long_player_names = player_names.copy()
|
||||||
for (team, player), alias in multisave.get("name_aliases", {}).items():
|
for (team, player), alias in multisave.get("name_aliases", {}).items():
|
||||||
@@ -1423,9 +1571,12 @@ def _get_multiworld_tracker_data(tracker: UUID) -> typing.Optional[typing.Dict[s
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _get_inventory_data(data: typing.Dict[str, typing.Any]) -> typing.Dict[int, typing.Dict[int, int]]:
|
def _get_inventory_data(data: typing.Dict[str, typing.Any]) \
|
||||||
inventory = {teamnumber: {playernumber: collections.Counter() for playernumber in team_data}
|
-> typing.Dict[int, typing.Dict[int, typing.Dict[int, int]]]:
|
||||||
for teamnumber, team_data in data["checks_done"].items()}
|
inventory: typing.Dict[int, typing.Dict[int, typing.Dict[int, int]]] = {
|
||||||
|
teamnumber: {playernumber: collections.Counter() for playernumber in team_data}
|
||||||
|
for teamnumber, team_data in data["checks_done"].items()
|
||||||
|
}
|
||||||
|
|
||||||
groups = data["groups"]
|
groups = data["groups"]
|
||||||
|
|
||||||
@@ -1444,6 +1595,17 @@ def _get_inventory_data(data: typing.Dict[str, typing.Any]) -> typing.Dict[int,
|
|||||||
return inventory
|
return inventory
|
||||||
|
|
||||||
|
|
||||||
|
def _get_named_inventory(inventory: typing.Dict[int, int], custom_items: typing.Dict[int, str] = None) \
|
||||||
|
-> typing.Dict[str, int]:
|
||||||
|
"""slow"""
|
||||||
|
if custom_items:
|
||||||
|
mapping = collections.ChainMap(custom_items, lookup_any_item_id_to_name)
|
||||||
|
else:
|
||||||
|
mapping = lookup_any_item_id_to_name
|
||||||
|
|
||||||
|
return collections.Counter({mapping.get(item_id, None): count for item_id, count in inventory.items()})
|
||||||
|
|
||||||
|
|
||||||
@app.route('/tracker/<suuid:tracker>')
|
@app.route('/tracker/<suuid:tracker>')
|
||||||
@cache.memoize(timeout=60) # multisave is currently created at most every minute
|
@cache.memoize(timeout=60) # multisave is currently created at most every minute
|
||||||
def get_multiworld_tracker(tracker: UUID):
|
def get_multiworld_tracker(tracker: UUID):
|
||||||
@@ -1455,18 +1617,22 @@ def get_multiworld_tracker(tracker: UUID):
|
|||||||
|
|
||||||
return render_template("multiTracker.html", **data)
|
return render_template("multiTracker.html", **data)
|
||||||
|
|
||||||
|
if "Factorio" in games:
|
||||||
|
@app.route('/tracker/<suuid:tracker>/Factorio')
|
||||||
|
@cache.memoize(timeout=60) # multisave is currently created at most every minute
|
||||||
|
def get_Factorio_multiworld_tracker(tracker: UUID):
|
||||||
|
data = _get_multiworld_tracker_data(tracker)
|
||||||
|
if not data:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
@app.route('/tracker/<suuid:tracker>/Factorio')
|
data["inventory"] = _get_inventory_data(data)
|
||||||
@cache.memoize(timeout=60) # multisave is currently created at most every minute
|
data["named_inventory"] = {team_id : {
|
||||||
def get_Factorio_multiworld_tracker(tracker: UUID):
|
player_id: _get_named_inventory(inventory, data["custom_items"])
|
||||||
data = _get_multiworld_tracker_data(tracker)
|
for player_id, inventory in team_inventory.items()
|
||||||
if not data:
|
} for team_id, team_inventory in data["inventory"].items()}
|
||||||
abort(404)
|
data["enabled_multiworld_trackers"] = get_enabled_multiworld_trackers(data["room"], "Factorio")
|
||||||
|
|
||||||
data["inventory"] = _get_inventory_data(data)
|
return render_template("multiFactorioTracker.html", **data)
|
||||||
data["enabled_multiworld_trackers"] = get_enabled_multiworld_trackers(data["room"], "Factorio")
|
|
||||||
|
|
||||||
return render_template("multiFactorioTracker.html", **data)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/tracker/<suuid:tracker>/A Link to the Past')
|
@app.route('/tracker/<suuid:tracker>/A Link to the Past')
|
||||||
@@ -1517,7 +1683,7 @@ def get_LttP_multiworld_tracker(tracker: UUID):
|
|||||||
for item_id in precollected:
|
for item_id in precollected:
|
||||||
attribute_item(team, player, item_id)
|
attribute_item(team, player, item_id)
|
||||||
for location in locations_checked:
|
for location in locations_checked:
|
||||||
if location not in player_locations or location not in player_location_to_area[player]:
|
if location not in player_locations or location not in player_location_to_area.get(player, {}):
|
||||||
continue
|
continue
|
||||||
item, recipient, flags = player_locations[location]
|
item, recipient, flags = player_locations[location]
|
||||||
recipients = groups.get(recipient, [recipient])
|
recipients = groups.get(recipient, [recipient])
|
||||||
@@ -1596,5 +1762,7 @@ game_specific_trackers: typing.Dict[str, typing.Callable] = {
|
|||||||
|
|
||||||
multi_trackers: typing.Dict[str, typing.Callable] = {
|
multi_trackers: typing.Dict[str, typing.Callable] = {
|
||||||
"A Link to the Past": get_LttP_multiworld_tracker,
|
"A Link to the Past": get_LttP_multiworld_tracker,
|
||||||
"Factorio": get_Factorio_multiworld_tracker,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if "Factorio" in games:
|
||||||
|
multi_trackers["Factorio"] = get_Factorio_multiworld_tracker
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ local itemsObtained = 0x0677
|
|||||||
local takeAnyCavesChecked = 0x0678
|
local takeAnyCavesChecked = 0x0678
|
||||||
local localTriforce = 0x0679
|
local localTriforce = 0x0679
|
||||||
local bonusItemsObtained = 0x067A
|
local bonusItemsObtained = 0x067A
|
||||||
|
local itemsObtainedHigh = 0x067B
|
||||||
|
|
||||||
itemAPids = {
|
itemAPids = {
|
||||||
["Boomerang"] = 7100,
|
["Boomerang"] = 7100,
|
||||||
@@ -173,11 +174,18 @@ for key, value in pairs(itemAPids) do
|
|||||||
itemIDNames[value] = key
|
itemIDNames[value] = key
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function getItemsObtained()
|
||||||
|
return bit.bor(bit.lshift(u8(itemsObtainedHigh), 8), u8(itemsObtained))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function setItemsObtained(value)
|
||||||
|
wU8(itemsObtainedHigh, bit.rshift(value, 8))
|
||||||
|
wU8(itemsObtained, bit.band(value, 0xFF))
|
||||||
|
end
|
||||||
|
|
||||||
local function determineItem(array)
|
local function determineItem(array)
|
||||||
memdomain.ram()
|
memdomain.ram()
|
||||||
currentItemsObtained = u8(itemsObtained)
|
currentItemsObtained = getItemsObtained()
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -364,8 +372,8 @@ local function gotItem(item)
|
|||||||
wU8(0x505, itemCode)
|
wU8(0x505, itemCode)
|
||||||
wU8(0x506, 128)
|
wU8(0x506, 128)
|
||||||
wU8(0x602, 4)
|
wU8(0x602, 4)
|
||||||
numberObtained = u8(itemsObtained) + 1
|
numberObtained = getItemsObtained() + 1
|
||||||
wU8(itemsObtained, numberObtained)
|
setItemsObtained(numberObtained)
|
||||||
if itemName == "Boomerang" then gotBoomerang() end
|
if itemName == "Boomerang" then gotBoomerang() end
|
||||||
if itemName == "Bow" then gotBow() end
|
if itemName == "Bow" then gotBow() end
|
||||||
if itemName == "Magical Boomerang" then gotMagicalBoomerang() end
|
if itemName == "Magical Boomerang" then gotMagicalBoomerang() end
|
||||||
@@ -476,7 +484,7 @@ function processBlock(block)
|
|||||||
if i > u8(bonusItemsObtained) then
|
if i > u8(bonusItemsObtained) then
|
||||||
if u8(0x505) == 0 then
|
if u8(0x505) == 0 then
|
||||||
gotItem(item)
|
gotItem(item)
|
||||||
wU8(itemsObtained, u8(itemsObtained) - 1)
|
setItemsObtained(getItemsObtained() - 1)
|
||||||
wU8(bonusItemsObtained, u8(bonusItemsObtained) + 1)
|
wU8(bonusItemsObtained, u8(bonusItemsObtained) + 1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -494,7 +502,7 @@ function processBlock(block)
|
|||||||
for i, item in ipairs(itemsBlock) do
|
for i, item in ipairs(itemsBlock) do
|
||||||
memDomain.ram()
|
memDomain.ram()
|
||||||
if u8(0x505) == 0 then
|
if u8(0x505) == 0 then
|
||||||
if i > u8(itemsObtained) then
|
if i > getItemsObtained() then
|
||||||
gotItem(item)
|
gotItem(item)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -546,7 +554,7 @@ function receive()
|
|||||||
retTable["gameMode"] = gameMode
|
retTable["gameMode"] = gameMode
|
||||||
retTable["overworldHC"] = getHCLocation()
|
retTable["overworldHC"] = getHCLocation()
|
||||||
retTable["overworldPB"] = getPBLocation()
|
retTable["overworldPB"] = getPBLocation()
|
||||||
retTable["itemsObtained"] = u8(itemsObtained)
|
retTable["itemsObtained"] = getItemsObtained()
|
||||||
msg = json.encode(retTable).."\n"
|
msg = json.encode(retTable).."\n"
|
||||||
local ret, error = zeldaSocket:send(msg)
|
local ret, error = zeldaSocket:send(msg)
|
||||||
if ret == nil then
|
if ret == nil then
|
||||||
|
|||||||
7
kvui.py
@@ -1,7 +1,14 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
if sys.platform == "win32":
|
||||||
|
import ctypes
|
||||||
|
# kivy 2.2.0 introduced DPI awareness on Windows, but it makes the UI enter an infinitely recursive re-layout
|
||||||
|
# by setting the application to not DPI Aware, Windows handles scaling the entire window on its own, ignoring kivy's
|
||||||
|
ctypes.windll.shcore.SetProcessDpiAwareness(0)
|
||||||
|
|
||||||
os.environ["KIVY_NO_CONSOLELOG"] = "1"
|
os.environ["KIVY_NO_CONSOLELOG"] = "1"
|
||||||
os.environ["KIVY_NO_FILELOG"] = "1"
|
os.environ["KIVY_NO_FILELOG"] = "1"
|
||||||
os.environ["KIVY_NO_ARGS"] = "1"
|
os.environ["KIVY_NO_ARGS"] = "1"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
colorama>=0.4.5
|
colorama>=0.4.5
|
||||||
websockets>=11.0.3
|
websockets>=11.0.3
|
||||||
PyYAML>=6.0.1
|
PyYAML>=6.0.1
|
||||||
jellyfish>=1.0.0
|
jellyfish>=1.0.1
|
||||||
jinja2>=3.1.2
|
jinja2>=3.1.2
|
||||||
schema>=0.7.5
|
schema>=0.7.5
|
||||||
kivy>=2.2.0
|
kivy>=2.2.0
|
||||||
@@ -9,4 +9,4 @@ bsdiff4>=1.2.3
|
|||||||
platformdirs>=3.9.1
|
platformdirs>=3.9.1
|
||||||
certifi>=2023.7.22
|
certifi>=2023.7.22
|
||||||
cython>=0.29.35
|
cython>=0.29.35
|
||||||
cymem>=2.0.7
|
cymem>=2.0.8
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ class Group:
|
|||||||
cls._type_cache = typing.get_type_hints(cls, globalns=mod_dict, localns=cls.__dict__)
|
cls._type_cache = typing.get_type_hints(cls, globalns=mod_dict, localns=cls.__dict__)
|
||||||
return cls._type_cache
|
return cls._type_cache
|
||||||
|
|
||||||
def get(self, key: str, default: Any) -> Any:
|
def get(self, key: str, default: Any = None) -> Any:
|
||||||
if key in self:
|
if key in self:
|
||||||
return self[key]
|
return self[key]
|
||||||
return default
|
return default
|
||||||
|
|||||||
2
setup.py
@@ -80,7 +80,6 @@ non_apworlds: set = {
|
|||||||
"Raft",
|
"Raft",
|
||||||
"Secret of Evermore",
|
"Secret of Evermore",
|
||||||
"Slay the Spire",
|
"Slay the Spire",
|
||||||
"Starcraft 2 Wings of Liberty",
|
|
||||||
"Sudoku",
|
"Sudoku",
|
||||||
"Super Mario 64",
|
"Super Mario 64",
|
||||||
"VVVVVV",
|
"VVVVVV",
|
||||||
@@ -91,6 +90,7 @@ non_apworlds: set = {
|
|||||||
# LogicMixin is broken before 3.10 import revamp
|
# LogicMixin is broken before 3.10 import revamp
|
||||||
if sys.version_info < (3,10):
|
if sys.version_info < (3,10):
|
||||||
non_apworlds.add("Hollow Knight")
|
non_apworlds.add("Hollow Knight")
|
||||||
|
non_apworlds.add("Starcraft 2 Wings of Liberty")
|
||||||
|
|
||||||
def download_SNI():
|
def download_SNI():
|
||||||
print("Updating SNI")
|
print("Updating SNI")
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import json
|
|||||||
class TestDocs(unittest.TestCase):
|
class TestDocs(unittest.TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls) -> None:
|
def setUpClass(cls) -> None:
|
||||||
from WebHost import get_app, raw_app
|
from WebHostLib import app as raw_app
|
||||||
|
from WebHost import get_app
|
||||||
raw_app.config["PONY"] = {
|
raw_app.config["PONY"] = {
|
||||||
"provider": "sqlite",
|
"provider": "sqlite",
|
||||||
"filename": ":memory:",
|
"filename": ":memory:",
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ class TestFileGeneration(unittest.TestCase):
|
|||||||
cls.incorrect_path = os.path.join(os.path.split(os.path.dirname(__file__))[0], "WebHostLib")
|
cls.incorrect_path = os.path.join(os.path.split(os.path.dirname(__file__))[0], "WebHostLib")
|
||||||
|
|
||||||
def testOptions(self):
|
def testOptions(self):
|
||||||
WebHost.create_options_files()
|
from WebHostLib.options import create as create_options_files
|
||||||
|
create_options_files()
|
||||||
target = os.path.join(self.correct_path, "static", "generated", "configs")
|
target = os.path.join(self.correct_path, "static", "generated", "configs")
|
||||||
self.assertTrue(os.path.exists(target))
|
self.assertTrue(os.path.exists(target))
|
||||||
self.assertFalse(os.path.exists(os.path.join(self.incorrect_path, "static", "generated", "configs")))
|
self.assertFalse(os.path.exists(os.path.join(self.incorrect_path, "static", "generated", "configs")))
|
||||||
|
|||||||
@@ -8,18 +8,31 @@ from .paths import Paths
|
|||||||
|
|
||||||
|
|
||||||
def get(name: str) -> Map:
|
def get(name: str) -> Map:
|
||||||
# Iterate through 2 folder depths
|
|
||||||
for map_dir in (p for p in Paths.MAPS.iterdir()):
|
for map_dir in (p for p in Paths.MAPS.iterdir()):
|
||||||
if map_dir.is_dir():
|
map = find_map_in_dir(name, map_dir)
|
||||||
for map_file in (p for p in map_dir.iterdir()):
|
if map is not None:
|
||||||
if Map.matches_target_map_name(map_file, name):
|
return map
|
||||||
return Map(map_file)
|
|
||||||
elif Map.matches_target_map_name(map_dir, name):
|
|
||||||
return Map(map_dir)
|
|
||||||
|
|
||||||
raise KeyError(f"Map '{name}' was not found. Please put the map file in \"/StarCraft II/Maps/\".")
|
raise KeyError(f"Map '{name}' was not found. Please put the map file in \"/StarCraft II/Maps/\".")
|
||||||
|
|
||||||
|
|
||||||
|
# Go deeper
|
||||||
|
def find_map_in_dir(name, path):
|
||||||
|
if Map.matches_target_map_name(path, name):
|
||||||
|
return Map(path)
|
||||||
|
|
||||||
|
if path.name.endswith("SC2Map"):
|
||||||
|
return None
|
||||||
|
|
||||||
|
if path.is_dir():
|
||||||
|
for childPath in (p for p in path.iterdir()):
|
||||||
|
map = find_map_in_dir(name, childPath)
|
||||||
|
if map is not None:
|
||||||
|
return map
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class Map:
|
class Map:
|
||||||
|
|
||||||
def __init__(self, path: Path):
|
def __init__(self, path: Path):
|
||||||
|
|||||||
@@ -367,25 +367,25 @@ def can_beat_boss(state: CollectionState, boss: str, logic: int, player: int) ->
|
|||||||
elif boss == "Graveyard":
|
elif boss == "Graveyard":
|
||||||
return (
|
return (
|
||||||
has_boss_strength("amanecida")
|
has_boss_strength("amanecida")
|
||||||
and state.has_all({"D01BZ07S01[Santos]", "D02Z03S23[E]", "D02Z02S14[W]", "Wall Climb Ability"}, player)
|
and state.has_all({"D01Z06S01[Santos]", "D02Z03S23[E]", "D02Z02S14[W]", "Wall Climb Ability"}, player)
|
||||||
)
|
)
|
||||||
elif boss == "Jondo":
|
elif boss == "Jondo":
|
||||||
return (
|
return (
|
||||||
has_boss_strength("amanecida")
|
has_boss_strength("amanecida")
|
||||||
and state.has("D01BZ07S01[Santos]", player)
|
and state.has("D01Z06S01[Santos]", player)
|
||||||
and state.has_any({"D20Z01S05[W]", "D20Z01S05[E]"}, player)
|
and state.has_any({"D20Z01S05[W]", "D20Z01S05[E]"}, player)
|
||||||
and state.has_any({"D03Z01S03[W]", "D03Z01S03[SW]"}, player)
|
and state.has_any({"D03Z01S03[W]", "D03Z01S03[SW]"}, player)
|
||||||
)
|
)
|
||||||
elif boss == "Patio":
|
elif boss == "Patio":
|
||||||
return (
|
return (
|
||||||
has_boss_strength("amanecida")
|
has_boss_strength("amanecida")
|
||||||
and state.has_all({"D01BZ07S01[Santos]", "D06Z01S18[E]"}, player)
|
and state.has_all({"D01Z06S01[Santos]", "D06Z01S18[E]"}, player)
|
||||||
and state.has_any({"D04Z01S04[W]", "D04Z01S04[E]", "D04Z01S04[Cherubs]"}, player)
|
and state.has_any({"D04Z01S04[W]", "D04Z01S04[E]", "D04Z01S04[Cherubs]"}, player)
|
||||||
)
|
)
|
||||||
elif boss == "Wall":
|
elif boss == "Wall":
|
||||||
return (
|
return (
|
||||||
has_boss_strength("amanecida")
|
has_boss_strength("amanecida")
|
||||||
and state.has_all({"D01BZ07S01[Santos]", "D09BZ01S01[Cell24]"}, player)
|
and state.has_all({"D01Z06S01[Santos]", "D09BZ01S01[Cell24]"}, player)
|
||||||
and state.has_any({"D09Z01S01[W]", "D09Z01S01[E]"}, player)
|
and state.has_any({"D09Z01S01[W]", "D09Z01S01[E]"}, player)
|
||||||
)
|
)
|
||||||
elif boss == "Hall":
|
elif boss == "Hall":
|
||||||
@@ -2451,6 +2451,8 @@ def rules(blasphemousworld):
|
|||||||
# Items
|
# Items
|
||||||
set_rule(world.get_location("PotSS: 4th meeting with Redento", player),
|
set_rule(world.get_location("PotSS: 4th meeting with Redento", player),
|
||||||
lambda state: redento(state, blasphemousworld, player, 4))
|
lambda state: redento(state, blasphemousworld, player, 4))
|
||||||
|
set_rule(world.get_location("PotSS: Amanecida of the Chiselled Steel", player),
|
||||||
|
lambda state: can_beat_boss(state, "Patio", logic, player))
|
||||||
# No doors
|
# No doors
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
## Required Software
|
## Required Software
|
||||||
|
|
||||||
Download the game from the [Bumper Stickers GitHub releases page](https://github.com/FelicitusNeko/FlixelBumpStik/releases).
|
Download the game from the [Bumper Stickers GitHub releases page](https://github.com/FelicitusNeko/FlixelBumpStik/releases), or from the [Bumper Stickers AP Itch page](https://kewliomzx.itch.io/bumpstik-ap), where you can also play it in your browser.
|
||||||
|
|
||||||
*A web version will be made available on itch.io at a later time.*
|
|
||||||
|
|
||||||
## Installation Procedures
|
## Installation Procedures
|
||||||
|
|
||||||
Simply download the latest version of Bumper Stickers from the link above, and extract it wherever you like.
|
Simply download the latest version of Bumper Stickers from the link above, and extract it wherever you like.
|
||||||
|
|
||||||
- ⚠️ Do not extract Bumper Stickers to Program Files, as this will cause file access issues.
|
- ⚠️ It is not recommended to copy this game, or any files, directly into your Program Files folder under Windows.
|
||||||
|
|
||||||
## Joining a Multiworld Game
|
## Joining a Multiworld Game
|
||||||
|
|
||||||
1. Run `BumpStik-AP.exe`.
|
1. Run `BumpStikAP.exe`.
|
||||||
2. Select "Archipelago Mode".
|
2. Select "Archipelago Mode".
|
||||||
3. Enter your server details in the fields provided, and click "Start".
|
3. Enter your server details in the fields provided, and click "Start".
|
||||||
- ※ If you are connecting to a WSS server (such as archipelago.gg), specify `wss://` in the host name. Otherwise, the game will assume `ws://`.
|
- The game will attempt to automatically detect whether to connect via normal (WS) or secure (WSS) server, but you can specify `ws://` or `wss://` to prioritise one or the other.
|
||||||
|
|
||||||
## How to play Bumper Stickers (Classic)
|
## How to play Bumper Stickers (Classic)
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ class DarkSouls3Location(Location):
|
|||||||
"Progressive Items 3",
|
"Progressive Items 3",
|
||||||
"Progressive Items 4",
|
"Progressive Items 4",
|
||||||
"Progressive Items DLC",
|
"Progressive Items DLC",
|
||||||
|
"Progressive Items Health",
|
||||||
]
|
]
|
||||||
|
|
||||||
output = {}
|
output = {}
|
||||||
@@ -581,11 +582,7 @@ location_tables = {
|
|||||||
[DS3LocationData(f"Titanite Shard #{i + 1}", "Titanite Shard", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(26)] +
|
[DS3LocationData(f"Titanite Shard #{i + 1}", "Titanite Shard", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(26)] +
|
||||||
[DS3LocationData(f"Large Titanite Shard #{i + 1}", "Large Titanite Shard", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(28)] +
|
[DS3LocationData(f"Large Titanite Shard #{i + 1}", "Large Titanite Shard", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(28)] +
|
||||||
[DS3LocationData(f"Titanite Slab #{i + 1}", "Titanite Slab", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] +
|
[DS3LocationData(f"Titanite Slab #{i + 1}", "Titanite Slab", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(3)] +
|
||||||
[DS3LocationData(f"Twinkling Titanite #{i + 1}", "Twinkling Titanite", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(15)] +
|
[DS3LocationData(f"Twinkling Titanite #{i + 1}", "Twinkling Titanite", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(15)],
|
||||||
|
|
||||||
# Healing
|
|
||||||
[DS3LocationData(f"Estus Shard #{i + 1}", "Estus Shard", DS3LocationCategory.HEALTH) for i in range(11)] +
|
|
||||||
[DS3LocationData(f"Undead Bone Shard #{i + 1}", "Undead Bone Shard", DS3LocationCategory.HEALTH) for i in range(10)],
|
|
||||||
|
|
||||||
"Progressive Items 2": [] +
|
"Progressive Items 2": [] +
|
||||||
# Items
|
# Items
|
||||||
@@ -683,7 +680,12 @@ location_tables = {
|
|||||||
[DS3LocationData(f"Dark Gem ${i + 1}", "Dark Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] +
|
[DS3LocationData(f"Dark Gem ${i + 1}", "Dark Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] +
|
||||||
[DS3LocationData(f"Blood Gem ${i + 1}", "Blood Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] +
|
[DS3LocationData(f"Blood Gem ${i + 1}", "Blood Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(1)] +
|
||||||
[DS3LocationData(f"Blessed Gem ${i + 1}", "Blessed Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] +
|
[DS3LocationData(f"Blessed Gem ${i + 1}", "Blessed Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)] +
|
||||||
[DS3LocationData(f"Hollow Gem ${i + 1}", "Hollow Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)]
|
[DS3LocationData(f"Hollow Gem ${i + 1}", "Hollow Gem", DS3LocationCategory.PROGRESSIVE_ITEM) for i in range(2)],
|
||||||
|
|
||||||
|
"Progressive Items Health": [] +
|
||||||
|
# Healing
|
||||||
|
[DS3LocationData(f"Estus Shard #{i + 1}", "Estus Shard", DS3LocationCategory.HEALTH) for i in range(11)] +
|
||||||
|
[DS3LocationData(f"Undead Bone Shard #{i + 1}", "Undead Bone Shard", DS3LocationCategory.HEALTH) for i in range(10)],
|
||||||
}
|
}
|
||||||
|
|
||||||
location_dictionary: Dict[str, DS3LocationData] = {}
|
location_dictionary: Dict[str, DS3LocationData] = {}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class DarkSouls3World(World):
|
|||||||
option_definitions = dark_souls_options
|
option_definitions = dark_souls_options
|
||||||
topology_present: bool = True
|
topology_present: bool = True
|
||||||
web = DarkSouls3Web()
|
web = DarkSouls3Web()
|
||||||
data_version = 7
|
data_version = 8
|
||||||
base_id = 100000
|
base_id = 100000
|
||||||
enabled_location_categories: Set[DS3LocationCategory]
|
enabled_location_categories: Set[DS3LocationCategory]
|
||||||
required_client_version = (0, 4, 2)
|
required_client_version = (0, 4, 2)
|
||||||
@@ -89,7 +89,7 @@ class DarkSouls3World(World):
|
|||||||
|
|
||||||
def create_regions(self):
|
def create_regions(self):
|
||||||
progressive_location_table = []
|
progressive_location_table = []
|
||||||
if self.multiworld.enable_progressive_locations[self.player].value:
|
if self.multiworld.enable_progressive_locations[self.player]:
|
||||||
progressive_location_table = [] + \
|
progressive_location_table = [] + \
|
||||||
location_tables["Progressive Items 1"] + \
|
location_tables["Progressive Items 1"] + \
|
||||||
location_tables["Progressive Items 2"] + \
|
location_tables["Progressive Items 2"] + \
|
||||||
@@ -99,6 +99,9 @@ class DarkSouls3World(World):
|
|||||||
if self.multiworld.enable_dlc[self.player].value:
|
if self.multiworld.enable_dlc[self.player].value:
|
||||||
progressive_location_table += location_tables["Progressive Items DLC"]
|
progressive_location_table += location_tables["Progressive Items DLC"]
|
||||||
|
|
||||||
|
if self.multiworld.enable_health_upgrade_locations[self.player]:
|
||||||
|
progressive_location_table += location_tables["Progressive Items Health"]
|
||||||
|
|
||||||
# Create Vanilla Regions
|
# Create Vanilla Regions
|
||||||
regions: Dict[str, Region] = {}
|
regions: Dict[str, Region] = {}
|
||||||
regions["Menu"] = self.create_region("Menu", progressive_location_table)
|
regions["Menu"] = self.create_region("Menu", progressive_location_table)
|
||||||
@@ -502,6 +505,15 @@ class DarkSouls3World(World):
|
|||||||
|
|
||||||
slot_data = {
|
slot_data = {
|
||||||
"options": {
|
"options": {
|
||||||
|
"enable_weapon_locations": self.multiworld.enable_weapon_locations[self.player].value,
|
||||||
|
"enable_shield_locations": self.multiworld.enable_shield_locations[self.player].value,
|
||||||
|
"enable_armor_locations": self.multiworld.enable_armor_locations[self.player].value,
|
||||||
|
"enable_ring_locations": self.multiworld.enable_ring_locations[self.player].value,
|
||||||
|
"enable_spell_locations": self.multiworld.enable_spell_locations[self.player].value,
|
||||||
|
"enable_key_locations": self.multiworld.enable_key_locations[self.player].value,
|
||||||
|
"enable_boss_locations": self.multiworld.enable_boss_locations[self.player].value,
|
||||||
|
"enable_npc_locations": self.multiworld.enable_npc_locations[self.player].value,
|
||||||
|
"enable_misc_locations": self.multiworld.enable_misc_locations[self.player].value,
|
||||||
"auto_equip": self.multiworld.auto_equip[self.player].value,
|
"auto_equip": self.multiworld.auto_equip[self.player].value,
|
||||||
"lock_equip": self.multiworld.lock_equip[self.player].value,
|
"lock_equip": self.multiworld.lock_equip[self.player].value,
|
||||||
"no_weapon_requirements": self.multiworld.no_weapon_requirements[self.player].value,
|
"no_weapon_requirements": self.multiworld.no_weapon_requirements[self.player].value,
|
||||||
|
|||||||
@@ -7,20 +7,22 @@ config file.
|
|||||||
|
|
||||||
## What does randomization do to this game?
|
## What does randomization do to this game?
|
||||||
|
|
||||||
In Dark Souls III, all unique items you can earn from a static corpse, a chest or the death of a Boss/NPC are
|
Items that can be picked up from static corpses, taken from chests, or earned from defeating enemies or NPCs can be
|
||||||
randomized.
|
randomized. Common pickups like titanite shards or firebombs can be randomized as "progressive" items. That is, the
|
||||||
An option is available from the settings page to also randomize the upgrade materials, the Estus shards and the
|
location "Titanite Shard #5" is the fifth titanite shard you pick up, no matter where it was from. This is also what
|
||||||
consumables.
|
happens when you randomize Estus Shards and Undead Bone Shards.
|
||||||
Another option is available to randomize the level of the generated weapons(from +0 to +10/+5)
|
|
||||||
|
|
||||||
To beat the game you need to collect the 4 "Cinders of a Lord" randomized in the multiworld
|
It's also possible to randomize the upgrade level of weapons and shields as well as their infusions (if they can have
|
||||||
and kill the final boss "Soul of Cinder"
|
one). Additionally, there are settings that can make the randomized experience more convenient or more interesting, such as
|
||||||
|
removing weapon requirements or auto-equipping whatever equipment you most recently received.
|
||||||
|
|
||||||
|
The goal is to find the four "Cinders of a Lord" items randomized into the multiworld and defeat the Soul of Cinder.
|
||||||
|
|
||||||
## What Dark Souls III items can appear in other players' worlds?
|
## What Dark Souls III items can appear in other players' worlds?
|
||||||
|
|
||||||
Every unique item from Dark Souls III can appear in other player's worlds, such as a piece of armor, an upgraded weapon,
|
Practically anything can be found in other worlds including pieces of armor, upgraded weapons, key items, consumables,
|
||||||
or a key item.
|
spells, upgrade materials, etc...
|
||||||
|
|
||||||
## What does another world's item look like in Dark Souls III?
|
## What does another world's item look like in Dark Souls III?
|
||||||
|
|
||||||
In Dark Souls III, items which need to be sent to other worlds appear as a Prism Stone.
|
In Dark Souls III, items which are sent to other worlds appear as Prism Stones.
|
||||||
|
|||||||
@@ -87,8 +87,7 @@ first time launching, you may be prompted to allow it to communicate through the
|
|||||||
3. Click on **New Lua Script Window...**
|
3. Click on **New Lua Script Window...**
|
||||||
4. In the new window, click **Browse...**
|
4. In the new window, click **Browse...**
|
||||||
5. Select the connector lua file included with your client
|
5. Select the connector lua file included with your client
|
||||||
- Look in the Archipelago folder for `/SNI/lua/x64` or `/SNI/lua/x86` depending on if the
|
- Look in the Archipelago folder for `/SNI/lua/Connector.lua`.
|
||||||
emulator is 64-bit or 32-bit.
|
|
||||||
6. If you see an error while loading the script that states `socket.dll missing` or similar, navigate to the folder of
|
6. If you see an error while loading the script that states `socket.dll missing` or similar, navigate to the folder of
|
||||||
the lua you are using in your file explorer and copy the `socket.dll` to the base folder of your snes9x install.
|
the lua you are using in your file explorer and copy the `socket.dll` to the base folder of your snes9x install.
|
||||||
|
|
||||||
@@ -100,8 +99,7 @@ the lua you are using in your file explorer and copy the `socket.dll` to the bas
|
|||||||
2. Load your ROM file if it hasn't already been loaded.
|
2. Load your ROM file if it hasn't already been loaded.
|
||||||
If you changed your core preference after loading the ROM, don't forget to reload it (default hotkey: Ctrl+R).
|
If you changed your core preference after loading the ROM, don't forget to reload it (default hotkey: Ctrl+R).
|
||||||
3. Drag+drop the `Connector.lua` file included with your client onto the main EmuHawk window.
|
3. Drag+drop the `Connector.lua` file included with your client onto the main EmuHawk window.
|
||||||
- Look in the Archipelago folder for `/SNI/lua/x64` or `/SNI/lua/x86` depending on if the
|
- Look in the Archipelago folder for `/SNI/lua/Connector.lua`.
|
||||||
emulator is 64-bit or 32-bit. Please note the most recent versions of BizHawk are 64-bit only.
|
|
||||||
- You could instead open the Lua Console manually, click `Script` 〉 `Open Script`, and navigate to `Connector.lua`
|
- You could instead open the Lua Console manually, click `Script` 〉 `Open Script`, and navigate to `Connector.lua`
|
||||||
with the file picker.
|
with the file picker.
|
||||||
|
|
||||||
|
|||||||
@@ -1794,13 +1794,13 @@ location_table: Dict[int, LocationDict] = {
|
|||||||
'map': 7,
|
'map': 7,
|
||||||
'index': 65,
|
'index': 65,
|
||||||
'doom_type': 2004,
|
'doom_type': 2004,
|
||||||
'region': "Limbo (E3M7) Red"},
|
'region': "Limbo (E3M7) Green"},
|
||||||
351297: {'name': 'Limbo (E3M7) - Armor',
|
351297: {'name': 'Limbo (E3M7) - Armor',
|
||||||
'episode': 3,
|
'episode': 3,
|
||||||
'map': 7,
|
'map': 7,
|
||||||
'index': 67,
|
'index': 67,
|
||||||
'doom_type': 2018,
|
'doom_type': 2018,
|
||||||
'region': "Limbo (E3M7) Red"},
|
'region': "Limbo (E3M7) Green"},
|
||||||
351298: {'name': 'Limbo (E3M7) - Yellow skull key',
|
351298: {'name': 'Limbo (E3M7) - Yellow skull key',
|
||||||
'episode': 3,
|
'episode': 3,
|
||||||
'map': 7,
|
'map': 7,
|
||||||
@@ -2496,19 +2496,19 @@ location_table: Dict[int, LocationDict] = {
|
|||||||
'map': 6,
|
'map': 6,
|
||||||
'index': 77,
|
'index': 77,
|
||||||
'doom_type': 38,
|
'doom_type': 38,
|
||||||
'region': "Against Thee Wickedly (E4M6) Yellow"},
|
'region': "Against Thee Wickedly (E4M6) Magenta"},
|
||||||
351414: {'name': 'Against Thee Wickedly (E4M6) - Invulnerability',
|
351414: {'name': 'Against Thee Wickedly (E4M6) - Invulnerability',
|
||||||
'episode': 4,
|
'episode': 4,
|
||||||
'map': 6,
|
'map': 6,
|
||||||
'index': 78,
|
'index': 78,
|
||||||
'doom_type': 2022,
|
'doom_type': 2022,
|
||||||
'region': "Against Thee Wickedly (E4M6) Red"},
|
'region': "Against Thee Wickedly (E4M6) Pink"},
|
||||||
351415: {'name': 'Against Thee Wickedly (E4M6) - Invulnerability 2',
|
351415: {'name': 'Against Thee Wickedly (E4M6) - Invulnerability 2',
|
||||||
'episode': 4,
|
'episode': 4,
|
||||||
'map': 6,
|
'map': 6,
|
||||||
'index': 89,
|
'index': 89,
|
||||||
'doom_type': 2022,
|
'doom_type': 2022,
|
||||||
'region': "Against Thee Wickedly (E4M6) Red"},
|
'region': "Against Thee Wickedly (E4M6) Magenta"},
|
||||||
351416: {'name': 'Against Thee Wickedly (E4M6) - BFG9000',
|
351416: {'name': 'Against Thee Wickedly (E4M6) - BFG9000',
|
||||||
'episode': 4,
|
'episode': 4,
|
||||||
'map': 6,
|
'map': 6,
|
||||||
@@ -2520,7 +2520,7 @@ location_table: Dict[int, LocationDict] = {
|
|||||||
'map': 6,
|
'map': 6,
|
||||||
'index': 102,
|
'index': 102,
|
||||||
'doom_type': 8,
|
'doom_type': 8,
|
||||||
'region': "Against Thee Wickedly (E4M6) Red"},
|
'region': "Against Thee Wickedly (E4M6) Pink"},
|
||||||
351418: {'name': 'Against Thee Wickedly (E4M6) - Berserk',
|
351418: {'name': 'Against Thee Wickedly (E4M6) - Berserk',
|
||||||
'episode': 4,
|
'episode': 4,
|
||||||
'map': 6,
|
'map': 6,
|
||||||
@@ -2550,7 +2550,7 @@ location_table: Dict[int, LocationDict] = {
|
|||||||
'map': 6,
|
'map': 6,
|
||||||
'index': -1,
|
'index': -1,
|
||||||
'doom_type': -1,
|
'doom_type': -1,
|
||||||
'region': "Against Thee Wickedly (E4M6) Red"},
|
'region': "Against Thee Wickedly (E4M6) Magenta"},
|
||||||
351423: {'name': 'And Hell Followed (E4M7) - Shotgun',
|
351423: {'name': 'And Hell Followed (E4M7) - Shotgun',
|
||||||
'episode': 4,
|
'episode': 4,
|
||||||
'map': 7,
|
'map': 7,
|
||||||
@@ -2628,7 +2628,7 @@ location_table: Dict[int, LocationDict] = {
|
|||||||
'map': 7,
|
'map': 7,
|
||||||
'index': 182,
|
'index': 182,
|
||||||
'doom_type': 39,
|
'doom_type': 39,
|
||||||
'region': "And Hell Followed (E4M7) Main"},
|
'region': "And Hell Followed (E4M7) Blue"},
|
||||||
351436: {'name': 'And Hell Followed (E4M7) - Red skull key',
|
351436: {'name': 'And Hell Followed (E4M7) - Red skull key',
|
||||||
'episode': 4,
|
'episode': 4,
|
||||||
'map': 7,
|
'map': 7,
|
||||||
@@ -3414,6 +3414,7 @@ death_logic_locations = [
|
|||||||
"Command Control (E1M4) - Supercharge",
|
"Command Control (E1M4) - Supercharge",
|
||||||
"Command Control (E1M4) - Mega Armor",
|
"Command Control (E1M4) - Mega Armor",
|
||||||
"Containment Area (E2M2) - Supercharge",
|
"Containment Area (E2M2) - Supercharge",
|
||||||
|
"Containment Area (E2M2) - Plasma gun",
|
||||||
"Pandemonium (E3M3) - Mega Armor",
|
"Pandemonium (E3M3) - Mega Armor",
|
||||||
"House of Pain (E3M4) - Chaingun",
|
"House of Pain (E3M4) - Chaingun",
|
||||||
"House of Pain (E3M4) - Invulnerability",
|
"House of Pain (E3M4) - Invulnerability",
|
||||||
|
|||||||
@@ -394,7 +394,8 @@ regions:List[RegionDict] = [
|
|||||||
"episode":3,
|
"episode":3,
|
||||||
"connections":[
|
"connections":[
|
||||||
"Limbo (E3M7) Red",
|
"Limbo (E3M7) Red",
|
||||||
"Limbo (E3M7) Blue"]},
|
"Limbo (E3M7) Blue",
|
||||||
|
"Limbo (E3M7) Pink"]},
|
||||||
{"name":"Limbo (E3M7) Blue",
|
{"name":"Limbo (E3M7) Blue",
|
||||||
"connects_to_hub":False,
|
"connects_to_hub":False,
|
||||||
"episode":3,
|
"episode":3,
|
||||||
@@ -404,11 +405,24 @@ regions:List[RegionDict] = [
|
|||||||
"episode":3,
|
"episode":3,
|
||||||
"connections":[
|
"connections":[
|
||||||
"Limbo (E3M7) Main",
|
"Limbo (E3M7) Main",
|
||||||
"Limbo (E3M7) Yellow"]},
|
"Limbo (E3M7) Yellow",
|
||||||
|
"Limbo (E3M7) Green"]},
|
||||||
{"name":"Limbo (E3M7) Yellow",
|
{"name":"Limbo (E3M7) Yellow",
|
||||||
"connects_to_hub":False,
|
"connects_to_hub":False,
|
||||||
"episode":3,
|
"episode":3,
|
||||||
"connections":["Limbo (E3M7) Red"]},
|
"connections":["Limbo (E3M7) Red"]},
|
||||||
|
{"name":"Limbo (E3M7) Pink",
|
||||||
|
"connects_to_hub":False,
|
||||||
|
"episode":3,
|
||||||
|
"connections":[
|
||||||
|
"Limbo (E3M7) Green",
|
||||||
|
"Limbo (E3M7) Main"]},
|
||||||
|
{"name":"Limbo (E3M7) Green",
|
||||||
|
"connects_to_hub":False,
|
||||||
|
"episode":3,
|
||||||
|
"connections":[
|
||||||
|
"Limbo (E3M7) Pink",
|
||||||
|
"Limbo (E3M7) Red"]},
|
||||||
|
|
||||||
# Dis (E3M8)
|
# Dis (E3M8)
|
||||||
{"name":"Dis (E3M8) Main",
|
{"name":"Dis (E3M8) Main",
|
||||||
@@ -529,19 +543,32 @@ regions:List[RegionDict] = [
|
|||||||
{"name":"Against Thee Wickedly (E4M6) Main",
|
{"name":"Against Thee Wickedly (E4M6) Main",
|
||||||
"connects_to_hub":True,
|
"connects_to_hub":True,
|
||||||
"episode":4,
|
"episode":4,
|
||||||
"connections":[
|
"connections":["Against Thee Wickedly (E4M6) Blue"]},
|
||||||
"Against Thee Wickedly (E4M6) Blue",
|
|
||||||
"Against Thee Wickedly (E4M6) Yellow",
|
|
||||||
"Against Thee Wickedly (E4M6) Red"]},
|
|
||||||
{"name":"Against Thee Wickedly (E4M6) Red",
|
{"name":"Against Thee Wickedly (E4M6) Red",
|
||||||
"connects_to_hub":False,
|
"connects_to_hub":False,
|
||||||
"episode":4,
|
"episode":4,
|
||||||
"connections":["Against Thee Wickedly (E4M6) Main"]},
|
"connections":[
|
||||||
|
"Against Thee Wickedly (E4M6) Blue",
|
||||||
|
"Against Thee Wickedly (E4M6) Pink",
|
||||||
|
"Against Thee Wickedly (E4M6) Main"]},
|
||||||
{"name":"Against Thee Wickedly (E4M6) Blue",
|
{"name":"Against Thee Wickedly (E4M6) Blue",
|
||||||
|
"connects_to_hub":False,
|
||||||
|
"episode":4,
|
||||||
|
"connections":[
|
||||||
|
"Against Thee Wickedly (E4M6) Main",
|
||||||
|
"Against Thee Wickedly (E4M6) Yellow",
|
||||||
|
"Against Thee Wickedly (E4M6) Red"]},
|
||||||
|
{"name":"Against Thee Wickedly (E4M6) Magenta",
|
||||||
"connects_to_hub":False,
|
"connects_to_hub":False,
|
||||||
"episode":4,
|
"episode":4,
|
||||||
"connections":["Against Thee Wickedly (E4M6) Main"]},
|
"connections":["Against Thee Wickedly (E4M6) Main"]},
|
||||||
{"name":"Against Thee Wickedly (E4M6) Yellow",
|
{"name":"Against Thee Wickedly (E4M6) Yellow",
|
||||||
|
"connects_to_hub":False,
|
||||||
|
"episode":4,
|
||||||
|
"connections":[
|
||||||
|
"Against Thee Wickedly (E4M6) Blue",
|
||||||
|
"Against Thee Wickedly (E4M6) Magenta"]},
|
||||||
|
{"name":"Against Thee Wickedly (E4M6) Pink",
|
||||||
"connects_to_hub":False,
|
"connects_to_hub":False,
|
||||||
"episode":4,
|
"episode":4,
|
||||||
"connections":["Against Thee Wickedly (E4M6) Main"]},
|
"connections":["Against Thee Wickedly (E4M6) Main"]},
|
||||||
|
|||||||
@@ -50,9 +50,6 @@ def set_episode1_rules(player, world):
|
|||||||
set_rule(world.get_entrance("Command Control (E1M4) Blue -> Command Control (E1M4) Main", player), lambda state:
|
set_rule(world.get_entrance("Command Control (E1M4) Blue -> Command Control (E1M4) Main", player), lambda state:
|
||||||
state.has("Command Control (E1M4) - Yellow keycard", player, 1) or
|
state.has("Command Control (E1M4) - Yellow keycard", player, 1) or
|
||||||
state.has("Command Control (E1M4) - Blue keycard", player, 1))
|
state.has("Command Control (E1M4) - Blue keycard", player, 1))
|
||||||
set_rule(world.get_entrance("Command Control (E1M4) Yellow -> Command Control (E1M4) Main", player), lambda state:
|
|
||||||
state.has("Command Control (E1M4) - Yellow keycard", player, 1) or
|
|
||||||
state.has("Command Control (E1M4) - Blue keycard", player, 1))
|
|
||||||
|
|
||||||
# Phobos Lab (E1M5)
|
# Phobos Lab (E1M5)
|
||||||
set_rule(world.get_entrance("Hub -> Phobos Lab (E1M5) Main", player), lambda state:
|
set_rule(world.get_entrance("Hub -> Phobos Lab (E1M5) Main", player), lambda state:
|
||||||
@@ -354,8 +351,12 @@ def set_episode3_rules(player, world):
|
|||||||
state.has("Limbo (E3M7) - Red skull key", player, 1))
|
state.has("Limbo (E3M7) - Red skull key", player, 1))
|
||||||
set_rule(world.get_entrance("Limbo (E3M7) Main -> Limbo (E3M7) Blue", player), lambda state:
|
set_rule(world.get_entrance("Limbo (E3M7) Main -> Limbo (E3M7) Blue", player), lambda state:
|
||||||
state.has("Limbo (E3M7) - Blue skull key", player, 1))
|
state.has("Limbo (E3M7) - Blue skull key", player, 1))
|
||||||
|
set_rule(world.get_entrance("Limbo (E3M7) Main -> Limbo (E3M7) Pink", player), lambda state:
|
||||||
|
state.has("Limbo (E3M7) - Blue skull key", player, 1))
|
||||||
set_rule(world.get_entrance("Limbo (E3M7) Red -> Limbo (E3M7) Yellow", player), lambda state:
|
set_rule(world.get_entrance("Limbo (E3M7) Red -> Limbo (E3M7) Yellow", player), lambda state:
|
||||||
state.has("Limbo (E3M7) - Yellow skull key", player, 1))
|
state.has("Limbo (E3M7) - Yellow skull key", player, 1))
|
||||||
|
set_rule(world.get_entrance("Limbo (E3M7) Pink -> Limbo (E3M7) Green", player), lambda state:
|
||||||
|
state.has("Limbo (E3M7) - Red skull key", player, 1))
|
||||||
|
|
||||||
# Dis (E3M8)
|
# Dis (E3M8)
|
||||||
set_rule(world.get_entrance("Hub -> Dis (E3M8) Main", player), lambda state:
|
set_rule(world.get_entrance("Hub -> Dis (E3M8) Main", player), lambda state:
|
||||||
@@ -466,12 +467,10 @@ def set_episode4_rules(player, world):
|
|||||||
state.has("BFG9000", player, 1)))
|
state.has("BFG9000", player, 1)))
|
||||||
set_rule(world.get_entrance("Against Thee Wickedly (E4M6) Main -> Against Thee Wickedly (E4M6) Blue", player), lambda state:
|
set_rule(world.get_entrance("Against Thee Wickedly (E4M6) Main -> Against Thee Wickedly (E4M6) Blue", player), lambda state:
|
||||||
state.has("Against Thee Wickedly (E4M6) - Blue skull key", player, 1))
|
state.has("Against Thee Wickedly (E4M6) - Blue skull key", player, 1))
|
||||||
set_rule(world.get_entrance("Against Thee Wickedly (E4M6) Main -> Against Thee Wickedly (E4M6) Yellow", player), lambda state:
|
set_rule(world.get_entrance("Against Thee Wickedly (E4M6) Blue -> Against Thee Wickedly (E4M6) Yellow", player), lambda state:
|
||||||
state.has("Against Thee Wickedly (E4M6) - Yellow skull key", player, 1))
|
state.has("Against Thee Wickedly (E4M6) - Yellow skull key", player, 1))
|
||||||
set_rule(world.get_entrance("Against Thee Wickedly (E4M6) Main -> Against Thee Wickedly (E4M6) Red", player), lambda state:
|
set_rule(world.get_entrance("Against Thee Wickedly (E4M6) Blue -> Against Thee Wickedly (E4M6) Red", player), lambda state:
|
||||||
state.has("Against Thee Wickedly (E4M6) - Red skull key", player, 1))
|
state.has("Against Thee Wickedly (E4M6) - Red skull key", player, 1))
|
||||||
set_rule(world.get_entrance("Against Thee Wickedly (E4M6) Blue -> Against Thee Wickedly (E4M6) Main", player), lambda state:
|
|
||||||
state.has("Against Thee Wickedly (E4M6) - Blue skull key", player, 1))
|
|
||||||
|
|
||||||
# And Hell Followed (E4M7)
|
# And Hell Followed (E4M7)
|
||||||
set_rule(world.get_entrance("Hub -> And Hell Followed (E4M7) Main", player), lambda state:
|
set_rule(world.get_entrance("Hub -> And Hell Followed (E4M7) Main", player), lambda state:
|
||||||
|
|||||||
@@ -11,9 +11,9 @@
|
|||||||
|
|
||||||
## Installing AP Doom
|
## Installing AP Doom
|
||||||
1. Download [APDOOM.zip](https://github.com/Daivuk/apdoom/releases) and extract it.
|
1. Download [APDOOM.zip](https://github.com/Daivuk/apdoom/releases) and extract it.
|
||||||
2. Copy DOOM.WAD from your steam install into the extracted folder.
|
2. Copy `DOOM.WAD` from your game's installation directory into the newly extracted folder.
|
||||||
You can find the folder in steam by finding the game in your library,
|
You can find the folder in steam by finding the game in your library,
|
||||||
right clicking it and choosing *Manage→Browse Local Files*.
|
right-clicking it and choosing **Manage -> Browse Local Files**.
|
||||||
|
|
||||||
## Joining a MultiWorld Game
|
## Joining a MultiWorld Game
|
||||||
|
|
||||||
|
|||||||
@@ -446,6 +446,10 @@ async def factorio_spinup_server(ctx: FactorioContext) -> bool:
|
|||||||
logger.warning("It appears your mods are loaded from Appdata, "
|
logger.warning("It appears your mods are loaded from Appdata, "
|
||||||
"this can lead to problems with multiple Factorio instances. "
|
"this can lead to problems with multiple Factorio instances. "
|
||||||
"If this is the case, you will get a file locked error running Factorio.")
|
"If this is the case, you will get a file locked error running Factorio.")
|
||||||
|
elif "Couldn't create lock file" in msg:
|
||||||
|
raise Exception(f"This Factorio (at {executable}) is either already running, "
|
||||||
|
"or a Factorio sharing data directories is already running. "
|
||||||
|
"Server could not start up.")
|
||||||
if not rcon_client and "Starting RCON interface at IP ADDR:" in msg:
|
if not rcon_client and "Starting RCON interface at IP ADDR:" in msg:
|
||||||
rcon_client = factorio_rcon.RCONClient("localhost", rcon_port, rcon_password)
|
rcon_client = factorio_rcon.RCONClient("localhost", rcon_port, rcon_password)
|
||||||
if ctx.mod_version == ctx.__class__.mod_version:
|
if ctx.mod_version == ctx.__class__.mod_version:
|
||||||
|
|||||||
@@ -146,8 +146,8 @@ class TechTreeLayout(Choice):
|
|||||||
|
|
||||||
class TechTreeInformation(Choice):
|
class TechTreeInformation(Choice):
|
||||||
"""How much information should be displayed in the tech tree.
|
"""How much information should be displayed in the tech tree.
|
||||||
None: No indication what a research unlocks
|
None: No indication of what a research unlocks.
|
||||||
Advancement: Indicators which researches unlock items that are considered logical advancements
|
Advancement: Indicates if a research unlocks an item that is considered logical advancement, but not who it is for.
|
||||||
Full: Labels with exact names and recipients of unlocked items; all researches are prefilled into the !hint command.
|
Full: Labels with exact names and recipients of unlocked items; all researches are prefilled into the !hint command.
|
||||||
"""
|
"""
|
||||||
display_name = "Technology Tree Information"
|
display_name = "Technology Tree Information"
|
||||||
@@ -390,8 +390,8 @@ class FactorioWorldGen(OptionDict):
|
|||||||
def __init__(self, value: typing.Dict[str, typing.Any]):
|
def __init__(self, value: typing.Dict[str, typing.Any]):
|
||||||
advanced = {"pollution", "enemy_evolution", "enemy_expansion"}
|
advanced = {"pollution", "enemy_evolution", "enemy_expansion"}
|
||||||
self.value = {
|
self.value = {
|
||||||
"basic": {key: value[key] for key in value.keys() - advanced},
|
"basic": {k: v for k, v in value.items() if k not in advanced},
|
||||||
"advanced": {key: value[key] for key in value.keys() & advanced}
|
"advanced": {k: v for k, v in value.items() if k in advanced}
|
||||||
}
|
}
|
||||||
|
|
||||||
# verify min_values <= max_values
|
# verify min_values <= max_values
|
||||||
|
|||||||
@@ -8,19 +8,19 @@ website: [FF1R Website](https://finalfantasyrandomizer.com/)
|
|||||||
|
|
||||||
## What does randomization do to this game?
|
## What does randomization do to this game?
|
||||||
|
|
||||||
A better questions is what isn't randomized at this point. Enemies stats and spell, character spells, shop inventory and
|
Enemy stats and spell, boss stats and spells, character spells, and shop inventories are all commonly randomized. Unlike
|
||||||
boss stats and spells are all commonly randomized. Unlike most other randomizers it is also most standard to shuffle
|
most other randomizers, it is standard to shuffle progression items and non-progression items into separate pools
|
||||||
progression items and non-progression items into separate pools and then redistribute them to their respective
|
and then redistribute them to their respective locations. For example, Princess Sarah may have the CANOE instead
|
||||||
locations. So, for example, Princess Sarah may have the CANOE instead of the LUTE; however, she will never have a Heal
|
of the LUTE; however, she will never have a Heal Pot or armor.
|
||||||
Pot or some armor. There are plenty of other things that can be randomized on the main randomizer
|
|
||||||
site: [FF1R Website](https://finalfantasyrandomizer.com/)
|
Plenty of other things to be randomized can be found on the main randomizer site:
|
||||||
|
[FF1R Website](https://finalfantasyrandomizer.com/)
|
||||||
|
|
||||||
## What Final Fantasy items can appear in other players' worlds?
|
## What Final Fantasy items can appear in other players' worlds?
|
||||||
|
|
||||||
All items can appear in other players worlds. This includes consumables, shards, weapons, armor and, of course, key
|
All items can appear in other players worlds, including consumables, shards, weapons, armor, and key items.
|
||||||
items.
|
|
||||||
|
|
||||||
## What does another world's item look like in Final Fantasy
|
## What does another world's item look like in Final Fantasy
|
||||||
|
|
||||||
All local and remote items appear the same. It will say that you received an item and then BOTH the client log and the
|
All local and remote items appear the same. Final Fantasy will say that you received an item, then BOTH the client log and the
|
||||||
emulator will display what was found external to the in-game text box.
|
emulator will display what was found external to the in-game text box.
|
||||||
|
|||||||
@@ -32,14 +32,14 @@ Generate a game by going to the site and performing the following steps:
|
|||||||
prefer, or it is your first time we suggest starting with the 'Shard Hunt' preset (which requires you to collect a
|
prefer, or it is your first time we suggest starting with the 'Shard Hunt' preset (which requires you to collect a
|
||||||
number of shards to go to the end dungeon) or the 'Beginner' preset if you prefer to kill the original fiends.
|
number of shards to go to the end dungeon) or the 'Beginner' preset if you prefer to kill the original fiends.
|
||||||
2. Go to the `Goal` tab and ensure `Archipelago` is enabled. Set your player name to any name that represents you.
|
2. Go to the `Goal` tab and ensure `Archipelago` is enabled. Set your player name to any name that represents you.
|
||||||
3. Upload you `Final Fantasy(USA).nes` (and click `Remember ROM` for the future!)
|
3. Upload your `Final Fantasy(USA).nes` (and click `Remember ROM` for the future!)
|
||||||
4. Press the `NEW` button beside `Seed` a few times
|
4. Press the `NEW` button beside `Seed` a few times
|
||||||
5. Click `GENERATE ROM`
|
5. Click `GENERATE ROM`
|
||||||
|
|
||||||
It should download two files. One is the `*.nes` file which your emulator will run and the other is the yaml file
|
It should download two files. One is the `*.nes` file which your emulator will run, and the other is the yaml file
|
||||||
required by Archipelago.gg
|
required by Archipelago.gg
|
||||||
|
|
||||||
At this point you are ready to join the multiworld. If you are uncertain on how to generate, host or join a multiworld
|
At this point, you are ready to join the multiworld. If you are uncertain on how to generate, host, or join a multiworld,
|
||||||
please refer to the [game agnostic setup guide](/tutorial/Archipelago/setup/en).
|
please refer to the [game agnostic setup guide](/tutorial/Archipelago/setup/en).
|
||||||
|
|
||||||
## Running the Client Program and Connecting to the Server
|
## Running the Client Program and Connecting to the Server
|
||||||
@@ -67,7 +67,7 @@ Once the Archipelago server has been hosted:
|
|||||||
|
|
||||||
## Play the game
|
## Play the game
|
||||||
|
|
||||||
When the client shows both NES and server are connected you are good to go. You can check the connection status of the
|
When the client shows both NES and server are connected, you are good to go. You can check the connection status of the
|
||||||
NES at any time by running `/nes`
|
NES at any time by running `/nes`
|
||||||
|
|
||||||
### Other Client Commands
|
### Other Client Commands
|
||||||
|
|||||||