update Archipelago

This commit is contained in:
Fabian Dill 2021-01-03 14:32:32 +01:00
parent 08ca4245c1
commit 8ebd36b5a7
22 changed files with 116 additions and 177 deletions

View File

@ -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

4
Gui.py
View File

@ -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)

View File

@ -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.')

View File

@ -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.

View File

@ -117,18 +117,23 @@ 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"])
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 = jsonobj["minimum_versions"].get("clients", [])
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)
@ -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)

View File

@ -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():

View File

@ -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"):

View File

@ -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.

View File

@ -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):

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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.")

View File

@ -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)

View File

@ -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"))

View File

@ -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

View File

@ -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: ""

View File

@ -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: ""

View File

@ -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": {

View File

@ -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,13 +399,13 @@ 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": {
@ -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)

View File

@ -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