From 8ebd36b5a77d7709c1e940d3dd955a1b5e649635 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sun, 3 Jan 2021 14:32:32 +0100 Subject: [PATCH] update Archipelago --- BaseClasses.py | 17 +---- Gui.py | 4 +- MultiClient.py | 2 +- MultiMystery.py | 33 +++------- MultiServer.py | 37 ++++++----- Mystery.py | 4 +- Patch.py | 5 +- README.md | 62 ++----------------- Utils.py | 11 ++-- WebHostLib/__init__.py | 8 +-- WebHostLib/autolauncher.py | 5 +- WebHostLib/customserver.py | 4 +- WebHostLib/generate.py | 2 +- WebHostLib/models.py | 4 +- WebHostLib/upload.py | 6 +- host.yaml | 2 - inno_setup_38.iss | 18 +++--- inno_setup_39.iss | 18 +++--- setup.py | 14 ++--- .../alttp/AdjusterMain.py | 0 worlds/alttp/Main.py | 23 ++++--- worlds/generic/__init__.py | 14 +++++ 22 files changed, 116 insertions(+), 177 deletions(-) rename AdjusterMain.py => worlds/alttp/AdjusterMain.py (100%) diff --git a/BaseClasses.py b/BaseClasses.py index da6ccaba..057c8272 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -5,14 +5,14 @@ from enum import Enum, unique import logging import json from collections import OrderedDict, Counter, deque -from typing import Union, Optional, List, Dict, NamedTuple +from typing import Union, Optional, List, Dict import secrets import random -import worlds.alttp from worlds.alttp.EntranceShuffle import door_addresses, indirect_connections from Utils import int16_as_bytes from worlds.alttp.Items import item_name_groups +from worlds.generic import PlandoItem, PlandoConnection class World(): @@ -1312,7 +1312,7 @@ class Spoiler(object): with open(filename, 'w', encoding="utf-8-sig") as outfile: outfile.write( - 'ALttP Berserker\'s Multiworld Version %s - Seed: %s\n\n' % ( + 'Archipelago Version %s - Seed: %s\n\n' % ( self.metadata['version'], self.world.seed)) outfile.write('Filling Algorithm: %s\n' % self.world.algorithm) outfile.write('Players: %d\n' % self.world.players) @@ -1421,14 +1421,3 @@ class Spoiler(object): outfile.write('\n'.join(path_listings)) -class PlandoItem(NamedTuple): - item: str - location: str - world: Union[bool, str] = False # False -> own world, True -> not own world - from_pool: bool = True # if item should be removed from item pool - - -class PlandoConnection(NamedTuple): - entrance: str - exit: str - direction: str # entrance, exit or both diff --git a/Gui.py b/Gui.py index 57483e2c..d8454ba0 100755 --- a/Gui.py +++ b/Gui.py @@ -14,7 +14,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed import ModuleUpdate ModuleUpdate.update() -from AdjusterMain import adjust +from worlds.alttp.AdjusterMain import adjust from worlds.alttp.EntranceRandomizer import parse_arguments from GuiUtils import ToolTips, set_icon, BackgroundTaskProgress from worlds.alttp.Main import main, get_seed, __version__ as MWVersion @@ -24,7 +24,7 @@ from Utils import is_bundled, local_path, output_path, open_file def guiMain(args=None): mainWindow = Tk() - mainWindow.wm_title("Berserker's Multiworld %s" % MWVersion) + mainWindow.wm_title("Archipelago %s" % MWVersion) set_icon(mainWindow) diff --git a/MultiClient.py b/MultiClient.py index c2968123..c8ffedde 100644 --- a/MultiClient.py +++ b/MultiClient.py @@ -1378,7 +1378,7 @@ async def main(): multiprocessing.freeze_support() parser = argparse.ArgumentParser() parser.add_argument('diff_file', default="", type=str, nargs="?", - help='Path to a Berserker Multiworld Binary Patch file') + help='Path to a Archipelago Binary Patch file') parser.add_argument('--snes', default='localhost:8080', help='Address of the QUsb2snes server.') parser.add_argument('--connect', default=None, help='Address of the multiworld host.') parser.add_argument('--password', default=None, help='Password of the multiworld host.') diff --git a/MultiMystery.py b/MultiMystery.py index 0812d2cd..2057bc49 100644 --- a/MultiMystery.py +++ b/MultiMystery.py @@ -1,17 +1,3 @@ -__author__ = "Berserker55" # you can find me on discord.gg/8Z65BR2 - -""" -This script launches a Multiplayer "Multiworld" Mystery Game - -.yaml files for all participating players should be placed in a /Players folder. -For every player a mystery game is rolled and a ROM created. -After generation the server is automatically launched. -It is still up to the host to forward the correct port (38281 by default) and distribute the roms to the players. -Regular Mystery has to work for this first, such as a ALTTP Base ROM and Enemizer Setup. -A guide can be found here: https://docs.google.com/document/d/19FoqUkuyStMqhOq8uGiocskMo1KMjOW4nEeG81xrKoI/edit -Configuration can be found in host.yaml -""" - import os import subprocess import sys @@ -29,7 +15,6 @@ def feedback(text: str): if __name__ == "__main__": logging.basicConfig(format='%(message)s', level=logging.INFO) try: - logging.info(f"{__author__}'s MultiMystery Launcher") import ModuleUpdate ModuleUpdate.update() @@ -85,10 +70,10 @@ if __name__ == "__main__": for i, file in enumerate(player_files, 1): player_string += f"--p{i} \"{os.path.join(player_files_path, file)}\" " - if os.path.exists("BerserkerMultiServer.exe"): - basemysterycommand = "BerserkerMystery.exe" # compiled windows - elif os.path.exists("BerserkerMultiServer"): - basemysterycommand = "BerserkerMystery" # compiled linux + if os.path.exists("ArchipelagoMystery.exe"): + basemysterycommand = "ArchipelagoMystery.exe" # compiled windows + elif os.path.exists("ArchipelagoMystery"): + basemysterycommand = "ArchipelagoMystery" # compiled linux else: basemysterycommand = f"py -{py_version} Mystery.py" # source @@ -133,7 +118,7 @@ if __name__ == "__main__": seedname = segment break - multidataname = f"AP_{seedname}.multidata" + multidataname = f"AP_{seedname}.archipelago" spoilername = f"AP_{seedname}_Spoiler.txt" romfilename = "" @@ -216,10 +201,10 @@ if __name__ == "__main__": if not args.disable_autohost: if os.path.exists(os.path.join(output_path, multidataname)): - if os.path.exists("BerserkerMultiServer.exe"): - baseservercommand = "BerserkerMultiServer.exe" # compiled windows - elif os.path.exists("BerserkerMultiServer"): - baseservercommand = "BerserkerMultiServer" # compiled linux + if os.path.exists("ArchipelagoServer.exe"): + baseservercommand = "ArchipelagoServer.exe" # compiled windows + elif os.path.exists("ArchipelagoServer"): + baseservercommand = "ArchipelagoServer" # compiled linux else: baseservercommand = f"py -{py_version} MultiServer.py" # source # don't have a mac to test that. If you try to run compiled on mac, good luck. diff --git a/MultiServer.py b/MultiServer.py index b29a3d36..7263f69a 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -117,21 +117,26 @@ class Context(Node): def load(self, multidatapath: str, use_embedded_server_options: bool = False): with open(multidatapath, 'rb') as f: - self._load(restricted_loads(zlib.decompress(f.read())), - use_embedded_server_options) + data = f.read() + self._load(self._decompress(data), use_embedded_server_options) self.data_filename = multidatapath + def _decompress(self, data: bytes) -> dict: + format_version = data[0] + if format_version != 1: + raise Exception("Incompatible multidata.") + return restricted_loads(zlib.decompress(data[1:])) + def _load(self, decoded_obj: dict, use_embedded_server_options: bool): - if "minimum_versions" in jsonobj: - mdata_ver = tuple(jsonobj["minimum_versions"]["server"]) - if mdata_ver > Utils._version_tuple: - raise RuntimeError(f"Supplied Multidata requires a server of at least version {mdata_ver}," - f"however this server is of version {Utils._version_tuple}") - clients_ver = jsonobj["minimum_versions"].get("clients", []) - self.minimum_client_versions = {} - for team, player, version in clients_ver: - self.minimum_client_versions[team, player] = Utils.Version(*version) + mdata_ver = decoded_obj["minimum_versions"]["server"] + if mdata_ver > Utils._version_tuple: + raise RuntimeError(f"Supplied Multidata requires a server of at least version {mdata_ver}," + f"however this server is of version {Utils._version_tuple}") + clients_ver = decoded_obj["minimum_versions"].get("clients", []) + self.minimum_client_versions = {} + for team, player, version in clients_ver: + self.minimum_client_versions[team, player] = Utils.Version(*version) for team, names in enumerate(decoded_obj['names']): for player, name in enumerate(names, 1): @@ -191,8 +196,8 @@ class Context(Node): self.saving = enabled if self.saving: if not self.save_filename: - self.save_filename = (self.data_filename[:-9] if self.data_filename[-9:] == 'multidata' else ( - self.data_filename + '_')) + 'multisave' + self.save_filename = (self.data_filename[:-9] if self.data_filename.endswith('.archipelago') else ( + self.data_filename + '_')) + 'save' try: with open(self.save_filename, 'rb') as f: save_data = restricted_loads(zlib.decompress(f.read())) @@ -210,7 +215,7 @@ class Context(Node): while self.running: time.sleep(self.auto_save_interval) if self.save_dirty: - logging.debug("Saving multisave via thread.") + logging.debug("Saving via thread.") self.save_dirty = False self._save() @@ -1319,7 +1324,7 @@ def parse_args() -> argparse.Namespace: parser.add_argument('--compatibility', default=defaults["compatibility"], type=int, help=""" #2 -> recommended for casual/cooperative play, attempt to be compatible with everything across all versions - #1 -> recommended for friendly racing, only allow Berserker's Multiworld, to disallow old /getitem for example + #1 -> recommended for friendly racing, tries to block third party clients #0 -> recommended for tournaments to force a level playing field, only allow an exact version match """) args = parser.parse_args() @@ -1366,7 +1371,7 @@ async def main(args: argparse.Namespace): import tkinter.filedialog root = tkinter.Tk() root.withdraw() - data_filename = tkinter.filedialog.askopenfilename(filetypes=(("Multiworld data", "*.multidata"),)) + data_filename = tkinter.filedialog.askopenfilename(filetypes=(("Multiworld data", "*.archipelago"),)) ctx.load(data_filename, args.use_embedded_options) diff --git a/Mystery.py b/Mystery.py index 69d95533..d40c15e8 100644 --- a/Mystery.py +++ b/Mystery.py @@ -7,17 +7,17 @@ import typing import os import ModuleUpdate -from BaseClasses import PlandoItem, PlandoConnection +from worlds.generic import PlandoItem, PlandoConnection ModuleUpdate.update() -import Bosses from Utils import parse_yaml from worlds.alttp.Rom import Sprite from worlds.alttp.EntranceRandomizer import parse_arguments from worlds.alttp.Main import main as ERmain from worlds.alttp.Main import get_seed, seeddigits from worlds.alttp.Items import item_name_groups, item_table +from worlds.alttp import Bosses def mystery_argparse(): diff --git a/Patch.py b/Patch.py index 26a9d261..a46fde63 100644 --- a/Patch.py +++ b/Patch.py @@ -130,9 +130,10 @@ if __name__ == "__main__": Utils.persistent_store("servers", data['hash'], data['server']) print(f"Host is {data['server']}") - elif rom.endswith("multidata"): + elif rom.endswith(".archipelago"): import json import zlib + with open(rom, 'rb') as fr: multidata = zlib.decompress(fr.read()).decode("utf-8") @@ -144,7 +145,7 @@ if __name__ == "__main__": from Utils import get_options multidata["server_options"] = get_options()["server_options"] multidata = zlib.compress(json.dumps(multidata).encode("utf-8"), 9) - with open(rom+"_updated.multidata", 'wb') as f: + with open(rom + "_updated.archipelago", 'wb') as f: f.write(multidata) elif rom.endswith(".zip"): diff --git a/README.md b/README.md index 04e46e4f..bf8fdc5e 100644 --- a/README.md +++ b/README.md @@ -1,63 +1,9 @@ -Berserker's Multiworld +Archipelago ====================== A Multiworld implementation for the Legend of Zelda: A Link to the Past Randomizer. For setup and instructions there's a [Wiki](https://github.com/Berserker66/MultiWorld-Utilities/wiki). -Downloads can be found at [Releases](https://github.com/Berserker66/MultiWorld-Utilities/releases), including compiled windows binaries. +Downloads can be found at [Releases](https://github.com/Berserker66/MultiWorld-Utilities/releases), including compiled +windows binaries. -Additions/Changes compared to Bonta's V31 ------------------ - -Project - * Available in precompiled form and guided setup for Windows 64Bit on the [Releases](https://github.com/Berserker66/MultiWorld-Utilities/releases) page - * Compatible with Python 3.7 and 3.8. Forward Checks for Python 4.0 are done - * Update modules if they are too old to prevent crashes and other possible issues. - * Autoinstall missing modules - * Allow newer versions of modules than specified, as they will *usually* not break compatibility - * Uses "V32" MSU - * Has support for binary patching to allow legal distribution of multiworld rom files - * Various performance improvements (over 100% faster in most cases) - * Various fixes - * Overworld Glitches Logic - * Newer Entrance Randomizer Logic, allowing more potential item and boss locations - * New Goal: local triforce hunt - Keeps triforce pieces local to your world - -MultiMystery.py - * Allows you to generate a Multiworld with individual player mystery weights. Since weights can also be set to 100%, this also allows for individual settings for each player in a regular multiworld. -Basis is a .yaml file that sets these weights. You can find an [playerSettings.yaml](https://github.com/Berserker66/MultiWorld-Utilities/blob/master/playerSettings.yaml) in this project folder to get started - * Additional instructions are at the start of the file. Open with a text editor - * Configuration options can be found in the [host.yaml](https://github.com/Berserker66/MultiWorld-Utilities/blob/master/host.yaml) file - * Allows a new Mode called "Meta-Mystery", allowing certain mystery settings to apply to all players - * For example, everyone gets the same but random goal - - MultiServer.py - * Supports automatic port-forwarding, can be enabled in [host.yaml](https://github.com/Berserker66/MultiWorld-Utilities/blob/master/host.yaml) - * Added commands `/hint` and `!hint`. See [host.yaml](https://github.com/Berserker66/MultiWorld-Utilities/blob/master/host.yaml) for more information - * Updates have been made to the following commands: - * `!players` now displays the number of connected players, expected total player count, and which players are missing - * `forfeit` now works when a player is no longer connected - * `/send`, `/hint`, and various other commands now use "fuzzy text matching". It is no longer required to enter a location, player name or item name perfectly - * Some item groups also exist, so `/hint Bottles` lists all bottle varieties - -Mystery.py - * Defaults to generating a non-race ROM (Bonta's only makes race ROMs at this time). -If a race ROM is desired, pass --create-race as argument to it - * When an error is generated due to a broken .yaml file, it now mentions in the error trace which file, line, and character is the culprit - * Option for progressive items, allowing you to turn them off (see [playerSettings.yaml](https://github.com/Berserker66/MultiWorld-Utilities/blob/master/playerSettings.yaml) for more information) - * Option for "timer", allows you to configure a timer to display in game and/or options for timed one hit knock out - * Option for "dungeon_counters", allowing you to configure the dungeon item counter - * Option for "glitch_boots", allowing to run glitched modes without automatic boots - * Supports new Meta-Mystery mode. Read [meta.yaml](https://github.com/Berserker66/MultiWorld-Utilities/blob/master/meta.yaml) for details. - * Added `dungeonssimple` and `dungeonsfull` entrance randomizer modes - * Option for local items, allowing certain items to appear in your world only and not in other players' worlds - * Option for linked options - * Added 'l' to dungeon_items to have a local-world keysanity - -MultiClient.py - * Has a Webbrowser based UI now - * Awaits a QUsb2Snes connection when started, latching on when available - * Completely redesigned command interface, with `!help` and `/help` - * Running it with a patch file will patch out the multiworld rom and then automatically connect to the host that created the multiworld - * Cheating is now controlled by the server and can be disabled in [host.yaml](https://github.com/Berserker66/MultiWorld-Utilities/blob/master/host.yaml) - * Automatically starts QUsb2Snes, if it isn't running - * Better reconnect to both snes and server +Readme is a work in progress. \ No newline at end of file diff --git a/Utils.py b/Utils.py index d031986c..2dc5ab3b 100644 --- a/Utils.py +++ b/Utils.py @@ -19,10 +19,8 @@ import os import subprocess import sys import pickle -import io -import builtins - import functools +import io from yaml import load, dump, safe_load @@ -380,6 +378,11 @@ safe_builtins = { 'frozenset', } +safe_builtins = { + 'set', + 'frozenset', +} + class RestrictedUnpickler(pickle.Unpickler): def find_class(self, module, name): @@ -392,4 +395,4 @@ class RestrictedUnpickler(pickle.Unpickler): def restricted_loads(s): """Helper function analogous to pickle.loads().""" - return RestrictedUnpickler(io.BytesIO(s)).load() + return RestrictedUnpickler(io.BytesIO(s)).load() \ No newline at end of file diff --git a/WebHostLib/__init__.py b/WebHostLib/__init__.py index a6c395c9..8ea02e14 100644 --- a/WebHostLib/__init__.py +++ b/WebHostLib/__init__.py @@ -1,6 +1,3 @@ -"""Friendly reminder that if you want to host this somewhere on the internet, that it's licensed under MIT Berserker66 -So unless you're Berserker you need to include license information.""" - import os import uuid import base64 @@ -38,9 +35,9 @@ app.config["JOB_THRESHOLD"] = 2 app.config['SESSION_PERMANENT'] = True # waitress uses one thread for I/O, these are for processing of views that then get sent -# berserkermulti.world uses gunicorn + nginx; ignoring this option +# archipelago.gg uses gunicorn + nginx; ignoring this option app.config["WAITRESS_THREADS"] = 10 -# a default that just works. berserkermulti.world runs on mariadb +# a default that just works. archipelago.gg runs on mariadb app.config["PONY"] = { 'provider': 'sqlite', 'filename': os.path.abspath('db.db3'), @@ -51,7 +48,6 @@ app.config["CACHE_TYPE"] = "simple" app.config["JSON_AS_ASCII"] = False app.autoversion = True -app.config["HOSTNAME"] = "berserkermulti.world" av = Autoversion(app) cache = Cache(app) diff --git a/WebHostLib/autolauncher.py b/WebHostLib/autolauncher.py index 392e8feb..e58effb1 100644 --- a/WebHostLib/autolauncher.py +++ b/WebHostLib/autolauncher.py @@ -8,7 +8,6 @@ import time from pony.orm import db_session, select, commit -from Utils import restricted_loads class CommonLocker(): """Uses a file lock to signal that something is already running""" @@ -78,10 +77,10 @@ def handle_generation_failure(result: BaseException): def launch_generator(pool: multiprocessing.pool.Pool, generation: Generation): - options = restricted_loads(generation.options) + options = generation.options logging.info(f"Generating {generation.id} for {len(options)} players") - meta = restricted_loads(generation.meta) + meta = generation.meta pool.apply_async(gen_game, (options,), {"race": meta["race"], "sid": generation.id, "owner": generation.owner}, handle_generation_success, handle_generation_failure) diff --git a/WebHostLib/customserver.py b/WebHostLib/customserver.py index fb6cbb16..d8875161 100644 --- a/WebHostLib/customserver.py +++ b/WebHostLib/customserver.py @@ -15,7 +15,7 @@ import zlib from .models import * from MultiServer import Context, server, auto_shutdown, ServerCommandProcessor, ClientMessageProcessor -from Utils import get_public_ipv4, get_public_ipv6, restricted_loads +from Utils import get_public_ipv4, get_public_ipv6, parse_yaml class CustomClientMessageProcessor(ClientMessageProcessor): @@ -75,7 +75,7 @@ class WebHostContext(Context): else: self.port = get_random_port() - return self._load(restricted_loads(zlib.decompress(room.seed.multidata)), True) + return self._load(self._decompress(room.seed.multidata), True) @db_session def init_save(self, enabled: bool = True): diff --git a/WebHostLib/generate.py b/WebHostLib/generate.py index 4faebe96..ae9b7dec 100644 --- a/WebHostLib/generate.py +++ b/WebHostLib/generate.py @@ -155,4 +155,4 @@ def upload_to_db(folder, owner, sid, race:bool): gen.delete() return seed.id else: - raise Exception("Multidata required, but not found.") + raise Exception("Multidata required (.archipelago), but not found.") diff --git a/WebHostLib/models.py b/WebHostLib/models.py index 8065839f..a8cfb085 100644 --- a/WebHostLib/models.py +++ b/WebHostLib/models.py @@ -51,6 +51,6 @@ class Command(db.Entity): class Generation(db.Entity): id = PrimaryKey(UUID, default=uuid4) owner = Required(UUID) - options = Required(bytes, lazy=True) # these didn't work as JSON on mariaDB, so they're getting pickled now - meta = Required(bytes, lazy=True) # if state is -1 (error) this will contain an utf-8 encoded error message + options = Required(Json, lazy=True) + meta = Required(Json, lazy=True) state = Required(int, default=0, index=True) diff --git a/WebHostLib/upload.py b/WebHostLib/upload.py index 8fce1add..29c0ec77 100644 --- a/WebHostLib/upload.py +++ b/WebHostLib/upload.py @@ -10,7 +10,7 @@ from WebHostLib import app, Seed, Room, Patch accepted_zip_contents = {"patches": ".apbp", "spoiler": ".txt", - "multidata": "multidata"} + "multidata": ".archipelago"} banned_zip_contents = (".sfc",) @@ -43,7 +43,7 @@ def uploads(): patches.add(Patch(data=zfile.open(file, "r").read(), player=player)) elif file.filename.endswith(".txt"): spoiler = zfile.open(file, "r").read().decode("utf-8-sig") - elif file.filename.endswith("multidata"): + elif file.filename.endswith(".archipelago"): try: multidata = json.loads(zlib.decompress(zfile.open(file).read()).decode("utf-8-sig")) except: @@ -80,4 +80,4 @@ def user_content(): def allowed_file(filename): - return filename.endswith(('multidata', ".zip")) + return filename.endswith(('.archipelago', ".zip")) diff --git a/host.yaml b/host.yaml index bccfc9e5..558f0c34 100644 --- a/host.yaml +++ b/host.yaml @@ -34,14 +34,12 @@ server_options: # "enabled" -> clients can always forfeit # "auto" -> automatic forfeit on goal completion, "goal" -> clients can forfeit after achieving their goal # "auto-enabled" -> automatic forfeit on goal completion and manual forfeit is also enabled - # Warning: Only Berserker's Multiworld clients of version 2.1+ send game beaten information forfeit_mode: "goal" # Remaining modes # !remaining handling, that tells a client which items remain in their pool # "enabled" -> Client can always ask for remaining items # "disabled" -> Client can never ask for remaining items # "goal" -> Client can ask for remaining items after goal completion - # Warning: Only Berserker's Multiworld clients of version 2.1+ send game beaten information remaining_mode: "goal" # Automatically shut down the server after this many seconds without new location checks, 0 to keep running auto_shutdown: 0 diff --git a/inno_setup_38.iss b/inno_setup_38.iss index ff917b97..8a403080 100644 --- a/inno_setup_38.iss +++ b/inno_setup_38.iss @@ -1,6 +1,6 @@ #define sourcepath "build\exe.win-amd64-3.8\" -#define MyAppName "BerserkerMultiWorld" -#define MyAppExeName "BerserkerMultiClient.exe" +#define MyAppName "Archipelago" +#define MyAppExeName "ArchipelagoClient.exe" #define MyAppIcon "icon.ico" [Setup] @@ -11,7 +11,7 @@ AppName={#MyAppName} AppVerName={#MyAppName} DefaultDirName={commonappdata}\{#MyAppName} DisableProgramGroupPage=yes -DefaultGroupName=Berserker's Multiworld +DefaultGroupName=Archipelago OutputDir=setups OutputBaseFilename=Setup {#MyAppName} Compression=lzma2 @@ -52,7 +52,7 @@ Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: [Run] Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..." -Filename: "{app}\BerserkerMultiCreator"; Parameters: "update_sprites"; StatusMsg: "Updating Sprite Library..." +Filename: "{app}\ArchipelagoCreator"; Parameters: "update_sprites"; StatusMsg: "Updating Sprite Library..." [UninstallDelete] Type: dirifempty; Name: "{app}" @@ -60,14 +60,14 @@ Type: dirifempty; Name: "{app}" [Registry] Root: HKCR; Subkey: ".apbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "Berserker's Multiworld Binary Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "" +Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "Archipelago Binary Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "" Root: HKCR; Subkey: "{#MyAppName}patch\DefaultIcon"; ValueData: "{app}\{#MyAppExeName},0"; ValueType: string; ValueName: "" Root: HKCR; Subkey: "{#MyAppName}patch\shell\open\command"; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; ValueType: string; ValueName: "" -Root: HKCR; Subkey: ".multidata"; ValueData: "{#MyAppName}multidata"; Flags: uninsdeletevalue; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}multidata"; ValueData: "Berserker's Multiworld Server Data"; Flags: uninsdeletekey; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}multidata\DefaultIcon"; ValueData: "{app}\BerserkerMultiServer.exe,0"; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}multidata\shell\open\command"; ValueData: """{app}\BerserkerMultiServer.exe"" --multidata ""%1"""; ValueType: string; ValueName: "" +Root: HKCR; Subkey: ".archipelago"; ValueData: "{#MyAppName}multidata"; Flags: uninsdeletevalue; ValueType: string; ValueName: "" +Root: HKCR; Subkey: "{#MyAppName}multidata"; ValueData: "Archipelago Server Data"; Flags: uninsdeletekey; ValueType: string; ValueName: "" +Root: HKCR; Subkey: "{#MyAppName}multidata\DefaultIcon"; ValueData: "{app}\ArchipelagoServer.exe,0"; ValueType: string; ValueName: "" +Root: HKCR; Subkey: "{#MyAppName}multidata\shell\open\command"; ValueData: """{app}\ArchipelagoServer.exe"" --multidata ""%1"""; ValueType: string; ValueName: "" diff --git a/inno_setup_39.iss b/inno_setup_39.iss index 7f8d6b7d..221cf3eb 100644 --- a/inno_setup_39.iss +++ b/inno_setup_39.iss @@ -1,6 +1,6 @@ #define sourcepath "build\exe.win-amd64-3.9\" -#define MyAppName "BerserkerMultiWorld" -#define MyAppExeName "BerserkerMultiClient.exe" +#define MyAppName "Archipelago" +#define MyAppExeName "ArchipelagoClient.exe" #define MyAppIcon "icon.ico" [Setup] @@ -11,7 +11,7 @@ AppName={#MyAppName} AppVerName={#MyAppName} DefaultDirName={commonappdata}\{#MyAppName} DisableProgramGroupPage=yes -DefaultGroupName=Berserker's Multiworld +DefaultGroupName=Archipelago OutputDir=setups OutputBaseFilename=Setup {#MyAppName} Compression=lzma2 @@ -52,7 +52,7 @@ Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: [Run] Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..." -Filename: "{app}\BerserkerMultiCreator"; Parameters: "update_sprites"; StatusMsg: "Updating Sprite Library..." +Filename: "{app}\ArchipelagoCreator"; Parameters: "update_sprites"; StatusMsg: "Updating Sprite Library..." [UninstallDelete] Type: dirifempty; Name: "{app}" @@ -60,14 +60,14 @@ Type: dirifempty; Name: "{app}" [Registry] Root: HKCR; Subkey: ".bmbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "Berserker's Multiworld Binary Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "" +Root: HKCR; Subkey: "{#MyAppName}patch"; ValueData: "Archipelago Binary Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "" Root: HKCR; Subkey: "{#MyAppName}patch\DefaultIcon"; ValueData: "{app}\{#MyAppExeName},0"; ValueType: string; ValueName: "" Root: HKCR; Subkey: "{#MyAppName}patch\shell\open\command"; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; ValueType: string; ValueName: "" -Root: HKCR; Subkey: ".multidata"; ValueData: "{#MyAppName}multidata"; Flags: uninsdeletevalue; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}multidata"; ValueData: "Berserker's Multiworld Server Data"; Flags: uninsdeletekey; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}multidata\DefaultIcon"; ValueData: "{app}\BerserkerMultiServer.exe,0"; ValueType: string; ValueName: "" -Root: HKCR; Subkey: "{#MyAppName}multidata\shell\open\command"; ValueData: """{app}\BerserkerMultiServer.exe"" --multidata ""%1"""; ValueType: string; ValueName: "" +Root: HKCR; Subkey: ".archipelago"; ValueData: "{#MyAppName}multidata"; Flags: uninsdeletevalue; ValueType: string; ValueName: "" +Root: HKCR; Subkey: "{#MyAppName}multidata"; ValueData: "Archipelago Server Data"; Flags: uninsdeletekey; ValueType: string; ValueName: "" +Root: HKCR; Subkey: "{#MyAppName}multidata\DefaultIcon"; ValueData: "{app}\ArchipelagoServer.exe,0"; ValueType: string; ValueName: "" +Root: HKCR; Subkey: "{#MyAppName}multidata\shell\open\command"; ValueData: """{app}\ArchipelagoServer.exe"" --multidata ""%1"""; ValueType: string; ValueName: "" diff --git a/setup.py b/setup.py index 48872d7b..99ec0d64 100644 --- a/setup.py +++ b/setup.py @@ -54,11 +54,11 @@ def manifest_creation(): print("Created Manifest") -scripts = {"MultiClient.py": "BerserkerMultiClient", - "MultiMystery.py": "BerserkerMultiMystery", - "MultiServer.py": "BerserkerMultiServer", - "gui.py": "BerserkerMultiCreator", - "Mystery.py": "BerserkerMystery"} +scripts = {"MultiClient.py": "ArchipelagoClient", + "MultiMystery.py": "ArchipelagoMultiMystery", + "MultiServer.py": "ArchipelagoServer", + "gui.py": "ArchipelagoCreator", + "Mystery.py": "ArchipelagoMystery"} exes = [] @@ -74,9 +74,9 @@ import datetime buildtime = datetime.datetime.utcnow() cx_Freeze.setup( - name="BerserkerMultiWorld", + name="Archipelago", version=f"{buildtime.year}.{buildtime.month}.{buildtime.day}.{buildtime.hour}", - description="BerserkerMultiWorld", + description="Archipelago", executables=exes, options={ "build_exe": { diff --git a/AdjusterMain.py b/worlds/alttp/AdjusterMain.py similarity index 100% rename from AdjusterMain.py rename to worlds/alttp/AdjusterMain.py diff --git a/worlds/alttp/Main.py b/worlds/alttp/Main.py index 2e5b6b8e..3ca9e92d 100644 --- a/worlds/alttp/Main.py +++ b/worlds/alttp/Main.py @@ -7,12 +7,14 @@ import random import time import zlib import concurrent.futures +import pickle from BaseClasses import MultiWorld, CollectionState, Item, Region, Location -from worlds.alttp.Items import ItemFactory -from worlds.alttp.Regions import create_regions, create_shops, mark_light_world_regions, lookup_vanilla_location_to_entrance +from worlds.alttp.Items import ItemFactory, item_table, item_name_groups +from worlds.alttp.Regions import create_regions, create_shops, mark_light_world_regions, \ + lookup_vanilla_location_to_entrance from worlds.alttp.InvertedRegions import create_inverted_regions, mark_dark_world_regions -from worlds.alttp.EntranceShuffle import link_entrances, link_inverted_entrances +from worlds.alttp.EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect from worlds.alttp.Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, get_hash_string from worlds.alttp.Rules import set_rules from worlds.alttp.Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive @@ -94,7 +96,7 @@ def main(args, seed=None): world.rom_seeds = {player: random.Random(world.random.randint(0, 999999999)) for player in range(1, world.players + 1)} - logger.info('ALttP Berserker\'s Multiworld Version %s - Seed: %s\n', __version__, world.seed) + logger.info('Archipelago Version %s - Seed: %s\n', __version__, world.seed) parsed_names = parse_player_names(args.names, world.players, args.teams) world.teams = len(parsed_names) @@ -397,17 +399,17 @@ def main(args, seed=None): def write_multidata(roms): import base64 - import pickle for future in roms: rom_name = future.result() rom_names.append(rom_name) - minimum_versions = {"server": (1, 0, 0)} + minimum_versions = {"server": (0, 1, 0)} multidata = zlib.compress(pickle.dumps({"names": parsed_names, - "roms": {base64.b64encode(rom_name).decode(): (team, slot) for slot, team, rom_name in rom_names}, + "roms": {base64.b64encode(rom_name).decode(): (team, slot) for + slot, team, rom_name in rom_names}, "remote_items": {player for player in range(1, world.players + 1) if world.remote_items[player]}, "locations": { - (location.address, location.player) : + (location.address, location.player): (location.item.code, location.item.player) for location in world.get_filled_locations() if type(location.address) is int}, @@ -415,12 +417,13 @@ def main(args, seed=None): "server_options": get_options()["server_options"], "er_hint_data": er_hint_data, "precollected_items": precollected_items, - "version": _version_tuple, + "version": tuple(_version_tuple), "tags": ["AP"], "minimum_versions": minimum_versions, }), 9) - with open(output_path('%s.multidata' % outfilebase), 'wb') as f: + with open(output_path('%s.archipelago' % outfilebase), 'wb') as f: + f.write(bytes([1])) # version of format f.write(multidata) multidata_task = pool.submit(write_multidata, rom_futures) diff --git a/worlds/generic/__init__.py b/worlds/generic/__init__.py index e69de29b..52c51b2a 100644 --- a/worlds/generic/__init__.py +++ b/worlds/generic/__init__.py @@ -0,0 +1,14 @@ +from typing import NamedTuple, Union + + +class PlandoItem(NamedTuple): + item: str + location: str + world: Union[bool, str] = False # False -> own world, True -> not own world + from_pool: bool = True # if item should be removed from item pool + + +class PlandoConnection(NamedTuple): + entrance: str + exit: str + direction: str # entrance, exit or both