update Archipelago
This commit is contained in:
parent
08ca4245c1
commit
8ebd36b5a7
|
@ -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
4
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)
|
||||
|
||||
|
|
|
@ -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.')
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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():
|
||||
|
|
5
Patch.py
5
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"):
|
||||
|
|
62
README.md
62
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.
|
9
Utils.py
9
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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: ""
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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: ""
|
||||
|
||||
|
||||
|
||||
|
|
14
setup.py
14
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": {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue