update Archipelago
This commit is contained in:
parent
08ca4245c1
commit
8ebd36b5a7
|
@ -5,14 +5,14 @@ from enum import Enum, unique
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
from collections import OrderedDict, Counter, deque
|
from collections import OrderedDict, Counter, deque
|
||||||
from typing import Union, Optional, List, Dict, NamedTuple
|
from typing import Union, Optional, List, Dict
|
||||||
import secrets
|
import secrets
|
||||||
import random
|
import random
|
||||||
|
|
||||||
import worlds.alttp
|
|
||||||
from worlds.alttp.EntranceShuffle import door_addresses, indirect_connections
|
from worlds.alttp.EntranceShuffle import door_addresses, indirect_connections
|
||||||
from Utils import int16_as_bytes
|
from Utils import int16_as_bytes
|
||||||
from worlds.alttp.Items import item_name_groups
|
from worlds.alttp.Items import item_name_groups
|
||||||
|
from worlds.generic import PlandoItem, PlandoConnection
|
||||||
|
|
||||||
|
|
||||||
class World():
|
class World():
|
||||||
|
@ -1312,7 +1312,7 @@ class Spoiler(object):
|
||||||
|
|
||||||
with open(filename, 'w', encoding="utf-8-sig") as outfile:
|
with open(filename, 'w', encoding="utf-8-sig") as outfile:
|
||||||
outfile.write(
|
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))
|
self.metadata['version'], self.world.seed))
|
||||||
outfile.write('Filling Algorithm: %s\n' % self.world.algorithm)
|
outfile.write('Filling Algorithm: %s\n' % self.world.algorithm)
|
||||||
outfile.write('Players: %d\n' % self.world.players)
|
outfile.write('Players: %d\n' % self.world.players)
|
||||||
|
@ -1421,14 +1421,3 @@ class Spoiler(object):
|
||||||
outfile.write('\n'.join(path_listings))
|
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
|
import ModuleUpdate
|
||||||
ModuleUpdate.update()
|
ModuleUpdate.update()
|
||||||
|
|
||||||
from AdjusterMain import adjust
|
from worlds.alttp.AdjusterMain import adjust
|
||||||
from worlds.alttp.EntranceRandomizer import parse_arguments
|
from worlds.alttp.EntranceRandomizer import parse_arguments
|
||||||
from GuiUtils import ToolTips, set_icon, BackgroundTaskProgress
|
from GuiUtils import ToolTips, set_icon, BackgroundTaskProgress
|
||||||
from worlds.alttp.Main import main, get_seed, __version__ as MWVersion
|
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):
|
def guiMain(args=None):
|
||||||
mainWindow = Tk()
|
mainWindow = Tk()
|
||||||
mainWindow.wm_title("Berserker's Multiworld %s" % MWVersion)
|
mainWindow.wm_title("Archipelago %s" % MWVersion)
|
||||||
|
|
||||||
set_icon(mainWindow)
|
set_icon(mainWindow)
|
||||||
|
|
||||||
|
|
|
@ -1378,7 +1378,7 @@ async def main():
|
||||||
multiprocessing.freeze_support()
|
multiprocessing.freeze_support()
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('diff_file', default="", type=str, nargs="?",
|
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('--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('--connect', default=None, help='Address of the multiworld host.')
|
||||||
parser.add_argument('--password', default=None, help='Password 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 os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
@ -29,7 +15,6 @@ def feedback(text: str):
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
logging.basicConfig(format='%(message)s', level=logging.INFO)
|
logging.basicConfig(format='%(message)s', level=logging.INFO)
|
||||||
try:
|
try:
|
||||||
logging.info(f"{__author__}'s MultiMystery Launcher")
|
|
||||||
import ModuleUpdate
|
import ModuleUpdate
|
||||||
|
|
||||||
ModuleUpdate.update()
|
ModuleUpdate.update()
|
||||||
|
@ -85,10 +70,10 @@ if __name__ == "__main__":
|
||||||
for i, file in enumerate(player_files, 1):
|
for i, file in enumerate(player_files, 1):
|
||||||
player_string += f"--p{i} \"{os.path.join(player_files_path, file)}\" "
|
player_string += f"--p{i} \"{os.path.join(player_files_path, file)}\" "
|
||||||
|
|
||||||
if os.path.exists("BerserkerMultiServer.exe"):
|
if os.path.exists("ArchipelagoMystery.exe"):
|
||||||
basemysterycommand = "BerserkerMystery.exe" # compiled windows
|
basemysterycommand = "ArchipelagoMystery.exe" # compiled windows
|
||||||
elif os.path.exists("BerserkerMultiServer"):
|
elif os.path.exists("ArchipelagoMystery"):
|
||||||
basemysterycommand = "BerserkerMystery" # compiled linux
|
basemysterycommand = "ArchipelagoMystery" # compiled linux
|
||||||
else:
|
else:
|
||||||
basemysterycommand = f"py -{py_version} Mystery.py" # source
|
basemysterycommand = f"py -{py_version} Mystery.py" # source
|
||||||
|
|
||||||
|
@ -133,7 +118,7 @@ if __name__ == "__main__":
|
||||||
seedname = segment
|
seedname = segment
|
||||||
break
|
break
|
||||||
|
|
||||||
multidataname = f"AP_{seedname}.multidata"
|
multidataname = f"AP_{seedname}.archipelago"
|
||||||
spoilername = f"AP_{seedname}_Spoiler.txt"
|
spoilername = f"AP_{seedname}_Spoiler.txt"
|
||||||
romfilename = ""
|
romfilename = ""
|
||||||
|
|
||||||
|
@ -216,10 +201,10 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
if not args.disable_autohost:
|
if not args.disable_autohost:
|
||||||
if os.path.exists(os.path.join(output_path, multidataname)):
|
if os.path.exists(os.path.join(output_path, multidataname)):
|
||||||
if os.path.exists("BerserkerMultiServer.exe"):
|
if os.path.exists("ArchipelagoServer.exe"):
|
||||||
baseservercommand = "BerserkerMultiServer.exe" # compiled windows
|
baseservercommand = "ArchipelagoServer.exe" # compiled windows
|
||||||
elif os.path.exists("BerserkerMultiServer"):
|
elif os.path.exists("ArchipelagoServer"):
|
||||||
baseservercommand = "BerserkerMultiServer" # compiled linux
|
baseservercommand = "ArchipelagoServer" # compiled linux
|
||||||
else:
|
else:
|
||||||
baseservercommand = f"py -{py_version} MultiServer.py" # source
|
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.
|
# 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):
|
def load(self, multidatapath: str, use_embedded_server_options: bool = False):
|
||||||
with open(multidatapath, 'rb') as f:
|
with open(multidatapath, 'rb') as f:
|
||||||
self._load(restricted_loads(zlib.decompress(f.read())),
|
data = f.read()
|
||||||
use_embedded_server_options)
|
|
||||||
|
|
||||||
|
self._load(self._decompress(data), use_embedded_server_options)
|
||||||
self.data_filename = multidatapath
|
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):
|
def _load(self, decoded_obj: dict, use_embedded_server_options: bool):
|
||||||
if "minimum_versions" in jsonobj:
|
mdata_ver = decoded_obj["minimum_versions"]["server"]
|
||||||
mdata_ver = tuple(jsonobj["minimum_versions"]["server"])
|
|
||||||
if mdata_ver > Utils._version_tuple:
|
if mdata_ver > Utils._version_tuple:
|
||||||
raise RuntimeError(f"Supplied Multidata requires a server of at least version {mdata_ver},"
|
raise RuntimeError(f"Supplied Multidata requires a server of at least version {mdata_ver},"
|
||||||
f"however this server is of version {Utils._version_tuple}")
|
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 = {}
|
self.minimum_client_versions = {}
|
||||||
for team, player, version in clients_ver:
|
for team, player, version in clients_ver:
|
||||||
self.minimum_client_versions[team, player] = Utils.Version(*version)
|
self.minimum_client_versions[team, player] = Utils.Version(*version)
|
||||||
|
@ -191,8 +196,8 @@ class Context(Node):
|
||||||
self.saving = enabled
|
self.saving = enabled
|
||||||
if self.saving:
|
if self.saving:
|
||||||
if not self.save_filename:
|
if not self.save_filename:
|
||||||
self.save_filename = (self.data_filename[:-9] if self.data_filename[-9:] == 'multidata' else (
|
self.save_filename = (self.data_filename[:-9] if self.data_filename.endswith('.archipelago') else (
|
||||||
self.data_filename + '_')) + 'multisave'
|
self.data_filename + '_')) + 'save'
|
||||||
try:
|
try:
|
||||||
with open(self.save_filename, 'rb') as f:
|
with open(self.save_filename, 'rb') as f:
|
||||||
save_data = restricted_loads(zlib.decompress(f.read()))
|
save_data = restricted_loads(zlib.decompress(f.read()))
|
||||||
|
@ -210,7 +215,7 @@ class Context(Node):
|
||||||
while self.running:
|
while self.running:
|
||||||
time.sleep(self.auto_save_interval)
|
time.sleep(self.auto_save_interval)
|
||||||
if self.save_dirty:
|
if self.save_dirty:
|
||||||
logging.debug("Saving multisave via thread.")
|
logging.debug("Saving via thread.")
|
||||||
self.save_dirty = False
|
self.save_dirty = False
|
||||||
self._save()
|
self._save()
|
||||||
|
|
||||||
|
@ -1319,7 +1324,7 @@ def parse_args() -> argparse.Namespace:
|
||||||
parser.add_argument('--compatibility', default=defaults["compatibility"], type=int,
|
parser.add_argument('--compatibility', default=defaults["compatibility"], type=int,
|
||||||
help="""
|
help="""
|
||||||
#2 -> recommended for casual/cooperative play, attempt to be compatible with everything across all versions
|
#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
|
#0 -> recommended for tournaments to force a level playing field, only allow an exact version match
|
||||||
""")
|
""")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
@ -1366,7 +1371,7 @@ async def main(args: argparse.Namespace):
|
||||||
import tkinter.filedialog
|
import tkinter.filedialog
|
||||||
root = tkinter.Tk()
|
root = tkinter.Tk()
|
||||||
root.withdraw()
|
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)
|
ctx.load(data_filename, args.use_embedded_options)
|
||||||
|
|
||||||
|
|
|
@ -7,17 +7,17 @@ import typing
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import ModuleUpdate
|
import ModuleUpdate
|
||||||
from BaseClasses import PlandoItem, PlandoConnection
|
from worlds.generic import PlandoItem, PlandoConnection
|
||||||
|
|
||||||
ModuleUpdate.update()
|
ModuleUpdate.update()
|
||||||
|
|
||||||
import Bosses
|
|
||||||
from Utils import parse_yaml
|
from Utils import parse_yaml
|
||||||
from worlds.alttp.Rom import Sprite
|
from worlds.alttp.Rom import Sprite
|
||||||
from worlds.alttp.EntranceRandomizer import parse_arguments
|
from worlds.alttp.EntranceRandomizer import parse_arguments
|
||||||
from worlds.alttp.Main import main as ERmain
|
from worlds.alttp.Main import main as ERmain
|
||||||
from worlds.alttp.Main import get_seed, seeddigits
|
from worlds.alttp.Main import get_seed, seeddigits
|
||||||
from worlds.alttp.Items import item_name_groups, item_table
|
from worlds.alttp.Items import item_name_groups, item_table
|
||||||
|
from worlds.alttp import Bosses
|
||||||
|
|
||||||
|
|
||||||
def mystery_argparse():
|
def mystery_argparse():
|
||||||
|
|
5
Patch.py
5
Patch.py
|
@ -130,9 +130,10 @@ if __name__ == "__main__":
|
||||||
Utils.persistent_store("servers", data['hash'], data['server'])
|
Utils.persistent_store("servers", data['hash'], data['server'])
|
||||||
print(f"Host is {data['server']}")
|
print(f"Host is {data['server']}")
|
||||||
|
|
||||||
elif rom.endswith("multidata"):
|
elif rom.endswith(".archipelago"):
|
||||||
import json
|
import json
|
||||||
import zlib
|
import zlib
|
||||||
|
|
||||||
with open(rom, 'rb') as fr:
|
with open(rom, 'rb') as fr:
|
||||||
|
|
||||||
multidata = zlib.decompress(fr.read()).decode("utf-8")
|
multidata = zlib.decompress(fr.read()).decode("utf-8")
|
||||||
|
@ -144,7 +145,7 @@ if __name__ == "__main__":
|
||||||
from Utils import get_options
|
from Utils import get_options
|
||||||
multidata["server_options"] = get_options()["server_options"]
|
multidata["server_options"] = get_options()["server_options"]
|
||||||
multidata = zlib.compress(json.dumps(multidata).encode("utf-8"), 9)
|
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)
|
f.write(multidata)
|
||||||
|
|
||||||
elif rom.endswith(".zip"):
|
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.
|
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).
|
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
|
Readme is a work in progress.
|
||||||
-----------------
|
|
||||||
|
|
||||||
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
|
|
9
Utils.py
9
Utils.py
|
@ -19,10 +19,8 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import pickle
|
import pickle
|
||||||
import io
|
|
||||||
import builtins
|
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
import io
|
||||||
|
|
||||||
from yaml import load, dump, safe_load
|
from yaml import load, dump, safe_load
|
||||||
|
|
||||||
|
@ -380,6 +378,11 @@ safe_builtins = {
|
||||||
'frozenset',
|
'frozenset',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
safe_builtins = {
|
||||||
|
'set',
|
||||||
|
'frozenset',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class RestrictedUnpickler(pickle.Unpickler):
|
class RestrictedUnpickler(pickle.Unpickler):
|
||||||
def find_class(self, module, name):
|
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 os
|
||||||
import uuid
|
import uuid
|
||||||
import base64
|
import base64
|
||||||
|
@ -38,9 +35,9 @@ app.config["JOB_THRESHOLD"] = 2
|
||||||
app.config['SESSION_PERMANENT'] = True
|
app.config['SESSION_PERMANENT'] = True
|
||||||
|
|
||||||
# waitress uses one thread for I/O, these are for processing of views that then get sent
|
# 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
|
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"] = {
|
app.config["PONY"] = {
|
||||||
'provider': 'sqlite',
|
'provider': 'sqlite',
|
||||||
'filename': os.path.abspath('db.db3'),
|
'filename': os.path.abspath('db.db3'),
|
||||||
|
@ -51,7 +48,6 @@ app.config["CACHE_TYPE"] = "simple"
|
||||||
app.config["JSON_AS_ASCII"] = False
|
app.config["JSON_AS_ASCII"] = False
|
||||||
|
|
||||||
app.autoversion = True
|
app.autoversion = True
|
||||||
app.config["HOSTNAME"] = "berserkermulti.world"
|
|
||||||
|
|
||||||
av = Autoversion(app)
|
av = Autoversion(app)
|
||||||
cache = Cache(app)
|
cache = Cache(app)
|
||||||
|
|
|
@ -8,7 +8,6 @@ import time
|
||||||
|
|
||||||
from pony.orm import db_session, select, commit
|
from pony.orm import db_session, select, commit
|
||||||
|
|
||||||
from Utils import restricted_loads
|
|
||||||
|
|
||||||
class CommonLocker():
|
class CommonLocker():
|
||||||
"""Uses a file lock to signal that something is already running"""
|
"""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):
|
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")
|
logging.info(f"Generating {generation.id} for {len(options)} players")
|
||||||
|
|
||||||
meta = restricted_loads(generation.meta)
|
meta = generation.meta
|
||||||
pool.apply_async(gen_game, (options,),
|
pool.apply_async(gen_game, (options,),
|
||||||
{"race": meta["race"], "sid": generation.id, "owner": generation.owner},
|
{"race": meta["race"], "sid": generation.id, "owner": generation.owner},
|
||||||
handle_generation_success, handle_generation_failure)
|
handle_generation_success, handle_generation_failure)
|
||||||
|
|
|
@ -15,7 +15,7 @@ import zlib
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
from MultiServer import Context, server, auto_shutdown, ServerCommandProcessor, ClientMessageProcessor
|
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):
|
class CustomClientMessageProcessor(ClientMessageProcessor):
|
||||||
|
@ -75,7 +75,7 @@ class WebHostContext(Context):
|
||||||
else:
|
else:
|
||||||
self.port = get_random_port()
|
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
|
@db_session
|
||||||
def init_save(self, enabled: bool = True):
|
def init_save(self, enabled: bool = True):
|
||||||
|
|
|
@ -155,4 +155,4 @@ def upload_to_db(folder, owner, sid, race:bool):
|
||||||
gen.delete()
|
gen.delete()
|
||||||
return seed.id
|
return seed.id
|
||||||
else:
|
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):
|
class Generation(db.Entity):
|
||||||
id = PrimaryKey(UUID, default=uuid4)
|
id = PrimaryKey(UUID, default=uuid4)
|
||||||
owner = Required(UUID)
|
owner = Required(UUID)
|
||||||
options = Required(bytes, lazy=True) # these didn't work as JSON on mariaDB, so they're getting pickled now
|
options = Required(Json, lazy=True)
|
||||||
meta = Required(bytes, lazy=True) # if state is -1 (error) this will contain an utf-8 encoded error message
|
meta = Required(Json, lazy=True)
|
||||||
state = Required(int, default=0, index=True)
|
state = Required(int, default=0, index=True)
|
||||||
|
|
|
@ -10,7 +10,7 @@ from WebHostLib import app, Seed, Room, Patch
|
||||||
|
|
||||||
accepted_zip_contents = {"patches": ".apbp",
|
accepted_zip_contents = {"patches": ".apbp",
|
||||||
"spoiler": ".txt",
|
"spoiler": ".txt",
|
||||||
"multidata": "multidata"}
|
"multidata": ".archipelago"}
|
||||||
|
|
||||||
banned_zip_contents = (".sfc",)
|
banned_zip_contents = (".sfc",)
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ def uploads():
|
||||||
patches.add(Patch(data=zfile.open(file, "r").read(), player=player))
|
patches.add(Patch(data=zfile.open(file, "r").read(), player=player))
|
||||||
elif file.filename.endswith(".txt"):
|
elif file.filename.endswith(".txt"):
|
||||||
spoiler = zfile.open(file, "r").read().decode("utf-8-sig")
|
spoiler = zfile.open(file, "r").read().decode("utf-8-sig")
|
||||||
elif file.filename.endswith("multidata"):
|
elif file.filename.endswith(".archipelago"):
|
||||||
try:
|
try:
|
||||||
multidata = json.loads(zlib.decompress(zfile.open(file).read()).decode("utf-8-sig"))
|
multidata = json.loads(zlib.decompress(zfile.open(file).read()).decode("utf-8-sig"))
|
||||||
except:
|
except:
|
||||||
|
@ -80,4 +80,4 @@ def user_content():
|
||||||
|
|
||||||
|
|
||||||
def allowed_file(filename):
|
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
|
# "enabled" -> clients can always forfeit
|
||||||
# "auto" -> automatic forfeit on goal completion, "goal" -> clients can forfeit after achieving their goal
|
# "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
|
# "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"
|
forfeit_mode: "goal"
|
||||||
# Remaining modes
|
# Remaining modes
|
||||||
# !remaining handling, that tells a client which items remain in their pool
|
# !remaining handling, that tells a client which items remain in their pool
|
||||||
# "enabled" -> Client can always ask for remaining items
|
# "enabled" -> Client can always ask for remaining items
|
||||||
# "disabled" -> Client can never ask for remaining items
|
# "disabled" -> Client can never ask for remaining items
|
||||||
# "goal" -> Client can ask for remaining items after goal completion
|
# "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"
|
remaining_mode: "goal"
|
||||||
# Automatically shut down the server after this many seconds without new location checks, 0 to keep running
|
# Automatically shut down the server after this many seconds without new location checks, 0 to keep running
|
||||||
auto_shutdown: 0
|
auto_shutdown: 0
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#define sourcepath "build\exe.win-amd64-3.8\"
|
#define sourcepath "build\exe.win-amd64-3.8\"
|
||||||
#define MyAppName "BerserkerMultiWorld"
|
#define MyAppName "Archipelago"
|
||||||
#define MyAppExeName "BerserkerMultiClient.exe"
|
#define MyAppExeName "ArchipelagoClient.exe"
|
||||||
#define MyAppIcon "icon.ico"
|
#define MyAppIcon "icon.ico"
|
||||||
|
|
||||||
[Setup]
|
[Setup]
|
||||||
|
@ -11,7 +11,7 @@ AppName={#MyAppName}
|
||||||
AppVerName={#MyAppName}
|
AppVerName={#MyAppName}
|
||||||
DefaultDirName={commonappdata}\{#MyAppName}
|
DefaultDirName={commonappdata}\{#MyAppName}
|
||||||
DisableProgramGroupPage=yes
|
DisableProgramGroupPage=yes
|
||||||
DefaultGroupName=Berserker's Multiworld
|
DefaultGroupName=Archipelago
|
||||||
OutputDir=setups
|
OutputDir=setups
|
||||||
OutputBaseFilename=Setup {#MyAppName}
|
OutputBaseFilename=Setup {#MyAppName}
|
||||||
Compression=lzma2
|
Compression=lzma2
|
||||||
|
@ -52,7 +52,7 @@ Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks:
|
||||||
|
|
||||||
[Run]
|
[Run]
|
||||||
Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..."
|
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]
|
[UninstallDelete]
|
||||||
Type: dirifempty; Name: "{app}"
|
Type: dirifempty; Name: "{app}"
|
||||||
|
@ -60,14 +60,14 @@ Type: dirifempty; Name: "{app}"
|
||||||
[Registry]
|
[Registry]
|
||||||
|
|
||||||
Root: HKCR; Subkey: ".apbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""
|
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\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: "{#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: ".archipelago"; 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"; ValueData: "Archipelago 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\DefaultIcon"; ValueData: "{app}\ArchipelagoServer.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: "{#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 sourcepath "build\exe.win-amd64-3.9\"
|
||||||
#define MyAppName "BerserkerMultiWorld"
|
#define MyAppName "Archipelago"
|
||||||
#define MyAppExeName "BerserkerMultiClient.exe"
|
#define MyAppExeName "ArchipelagoClient.exe"
|
||||||
#define MyAppIcon "icon.ico"
|
#define MyAppIcon "icon.ico"
|
||||||
|
|
||||||
[Setup]
|
[Setup]
|
||||||
|
@ -11,7 +11,7 @@ AppName={#MyAppName}
|
||||||
AppVerName={#MyAppName}
|
AppVerName={#MyAppName}
|
||||||
DefaultDirName={commonappdata}\{#MyAppName}
|
DefaultDirName={commonappdata}\{#MyAppName}
|
||||||
DisableProgramGroupPage=yes
|
DisableProgramGroupPage=yes
|
||||||
DefaultGroupName=Berserker's Multiworld
|
DefaultGroupName=Archipelago
|
||||||
OutputDir=setups
|
OutputDir=setups
|
||||||
OutputBaseFilename=Setup {#MyAppName}
|
OutputBaseFilename=Setup {#MyAppName}
|
||||||
Compression=lzma2
|
Compression=lzma2
|
||||||
|
@ -52,7 +52,7 @@ Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks:
|
||||||
|
|
||||||
[Run]
|
[Run]
|
||||||
Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..."
|
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]
|
[UninstallDelete]
|
||||||
Type: dirifempty; Name: "{app}"
|
Type: dirifempty; Name: "{app}"
|
||||||
|
@ -60,14 +60,14 @@ Type: dirifempty; Name: "{app}"
|
||||||
[Registry]
|
[Registry]
|
||||||
|
|
||||||
Root: HKCR; Subkey: ".bmbp"; ValueData: "{#MyAppName}patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""
|
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\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: "{#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: ".archipelago"; 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"; ValueData: "Archipelago 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\DefaultIcon"; ValueData: "{app}\ArchipelagoServer.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: "{#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")
|
print("Created Manifest")
|
||||||
|
|
||||||
|
|
||||||
scripts = {"MultiClient.py": "BerserkerMultiClient",
|
scripts = {"MultiClient.py": "ArchipelagoClient",
|
||||||
"MultiMystery.py": "BerserkerMultiMystery",
|
"MultiMystery.py": "ArchipelagoMultiMystery",
|
||||||
"MultiServer.py": "BerserkerMultiServer",
|
"MultiServer.py": "ArchipelagoServer",
|
||||||
"gui.py": "BerserkerMultiCreator",
|
"gui.py": "ArchipelagoCreator",
|
||||||
"Mystery.py": "BerserkerMystery"}
|
"Mystery.py": "ArchipelagoMystery"}
|
||||||
|
|
||||||
exes = []
|
exes = []
|
||||||
|
|
||||||
|
@ -74,9 +74,9 @@ import datetime
|
||||||
buildtime = datetime.datetime.utcnow()
|
buildtime = datetime.datetime.utcnow()
|
||||||
|
|
||||||
cx_Freeze.setup(
|
cx_Freeze.setup(
|
||||||
name="BerserkerMultiWorld",
|
name="Archipelago",
|
||||||
version=f"{buildtime.year}.{buildtime.month}.{buildtime.day}.{buildtime.hour}",
|
version=f"{buildtime.year}.{buildtime.month}.{buildtime.day}.{buildtime.hour}",
|
||||||
description="BerserkerMultiWorld",
|
description="Archipelago",
|
||||||
executables=exes,
|
executables=exes,
|
||||||
options={
|
options={
|
||||||
"build_exe": {
|
"build_exe": {
|
||||||
|
|
|
@ -7,12 +7,14 @@ import random
|
||||||
import time
|
import time
|
||||||
import zlib
|
import zlib
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
|
import pickle
|
||||||
|
|
||||||
from BaseClasses import MultiWorld, CollectionState, Item, Region, Location
|
from BaseClasses import MultiWorld, CollectionState, Item, Region, Location
|
||||||
from worlds.alttp.Items import ItemFactory
|
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.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.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.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.Rules import set_rules
|
||||||
from worlds.alttp.Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
|
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)}
|
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)
|
parsed_names = parse_player_names(args.names, world.players, args.teams)
|
||||||
world.teams = len(parsed_names)
|
world.teams = len(parsed_names)
|
||||||
|
@ -397,17 +399,17 @@ def main(args, seed=None):
|
||||||
|
|
||||||
def write_multidata(roms):
|
def write_multidata(roms):
|
||||||
import base64
|
import base64
|
||||||
import pickle
|
|
||||||
for future in roms:
|
for future in roms:
|
||||||
rom_name = future.result()
|
rom_name = future.result()
|
||||||
rom_names.append(rom_name)
|
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,
|
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
|
"remote_items": {player for player in range(1, world.players + 1) if
|
||||||
world.remote_items[player]},
|
world.remote_items[player]},
|
||||||
"locations": {
|
"locations": {
|
||||||
(location.address, location.player) :
|
(location.address, location.player):
|
||||||
(location.item.code, location.item.player)
|
(location.item.code, location.item.player)
|
||||||
for location in world.get_filled_locations() if
|
for location in world.get_filled_locations() if
|
||||||
type(location.address) is int},
|
type(location.address) is int},
|
||||||
|
@ -415,12 +417,13 @@ def main(args, seed=None):
|
||||||
"server_options": get_options()["server_options"],
|
"server_options": get_options()["server_options"],
|
||||||
"er_hint_data": er_hint_data,
|
"er_hint_data": er_hint_data,
|
||||||
"precollected_items": precollected_items,
|
"precollected_items": precollected_items,
|
||||||
"version": _version_tuple,
|
"version": tuple(_version_tuple),
|
||||||
"tags": ["AP"],
|
"tags": ["AP"],
|
||||||
"minimum_versions": minimum_versions,
|
"minimum_versions": minimum_versions,
|
||||||
}), 9)
|
}), 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)
|
f.write(multidata)
|
||||||
|
|
||||||
multidata_task = pool.submit(write_multidata, rom_futures)
|
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