Skip to content

Commit

Permalink
Merge branch 'ArchipelagoMW:main' into mp9-manual-3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Joethepic authored May 8, 2023
2 parents 85e3746 + c74577d commit 55aff8a
Show file tree
Hide file tree
Showing 68 changed files with 1,205 additions and 785 deletions.
1 change: 0 additions & 1 deletion BaseClasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ def __init__(self, players: int):
def set_player_attr(attr, val):
self.__dict__.setdefault(attr, {})[player] = val

set_player_attr('tech_tree_layout_prerequisites', {})
set_player_attr('_region_cache', {})
set_player_attr('shuffle', "vanilla")
set_player_attr('logic', "noglitches")
Expand Down
15 changes: 11 additions & 4 deletions CommonClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ class CommonContext:
disconnected_intentionally: bool = False
server: typing.Optional[Endpoint] = None
server_version: Version = Version(0, 0, 0)
generator_version: Version = Version(0, 0, 0)
current_energy_link_value: typing.Optional[int] = None # to display in UI, gets set by server

last_death_link: float = time.time() # last send/received death link on AP layer
Expand Down Expand Up @@ -260,6 +261,7 @@ def reset_server_state(self):
self.items_received = []
self.locations_info = {}
self.server_version = Version(0, 0, 0)
self.generator_version = Version(0, 0, 0)
self.server = None
self.server_task = None
self.hint_cost = None
Expand Down Expand Up @@ -646,11 +648,16 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
logger.info('Room Information:')
logger.info('--------------------------------')
version = args["version"]
ctx.server_version = tuple(version)
version = ".".join(str(item) for item in version)
ctx.server_version = Version(*version)

logger.info(f'Server protocol version: {version}')
logger.info("Server protocol tags: " + ", ".join(args["tags"]))
if "generator_version" in args:
ctx.generator_version = Version(*args["generator_version"])
logger.info(f'Server protocol version: {ctx.server_version.as_simple_string()}, '
f'generator version: {ctx.generator_version.as_simple_string()}, '
f'tags: {", ".join(args["tags"])}')
else:
logger.info(f'Server protocol version: {ctx.server_version.as_simple_string()}, '
f'tags: {", ".join(args["tags"])}')
if args['password']:
logger.info('Password required')
ctx.update_permissions(args.get("permissions", {}))
Expand Down
30 changes: 22 additions & 8 deletions KH2Client.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,10 @@ def __init__(self, server_address, password):
self.AbilityQuantityDict = {item: self.item_name_to_data[item].quantity for item in self.all_abilities}
# Growth:[level 1,level 4,slot]
self.growth_values_dict = {"High Jump": [0x05E, 0x061, 0x25DA],
"Quick Run": [0x62, 0x65, 0x25DC],
"Quick Run": [0x62, 0x65, 0x25DC],
"Dodge Roll": [0x234, 0x237, 0x25DE],
"Aerial Dodge": [0x066, 0x069, 0x25E0],
"Glide": [0x6A, 0x6D, 0x25E2]}
"Glide": [0x6A, 0x6D, 0x25E2]}
self.boost_to_anchor_dict = {
"Power Boost": 0x24F9,
"Magic Boost": 0x24FA,
Expand Down Expand Up @@ -271,6 +271,21 @@ def on_package(self, cmd: str, args: dict):

if cmd in {"ReceivedItems"}:
start_index = args["index"]
if start_index == 0:
self.kh2seedsave["itemIndex"] = - 1
self.kh2seedsave["AmountInvo"]["ServerItems"] = {
"Ability": {},
"Amount": {},
"Growth": {"High Jump": 0, "Quick Run": 0, "Dodge Roll": 0,
"Aerial Dodge": 0,
"Glide": 0},
"Bitmask": [],
"Weapon": {"Sora": [], "Donald": [], "Goofy": []},
"Equipment": [],
"Magic": {},
"StatIncrease": {},
"Boost": {},
}
if start_index > self.kh2seedsave["itemIndex"]:
self.kh2seedsave["itemIndex"] = start_index
for item in args['items']:
Expand All @@ -295,7 +310,6 @@ async def checkWorldLocations(self):
and (int.from_bytes(
self.kh2.read_bytes(self.kh2.base_address + self.Save + data.addrObtained, 1),
"big") & 0x1 << data.bitIndex) > 0:

self.sending = self.sending + [(int(locationId))]
except Exception as e:
logger.info("Line 285")
Expand Down Expand Up @@ -342,15 +356,14 @@ async def checkSlots(self):
if locationId not in self.locations_checked:
if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + data.addrObtained, 1),
"big") > 0:

self.sending = self.sending + [(int(locationId))]

for location, data in formSlots.items():
locationId = kh2_loc_name_to_id[location]
if locationId not in self.locations_checked:
if int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + self.Save + data.addrObtained, 1),
"big") & 0x1 << data.bitIndex > 0:
#self.locations_checked
# self.locations_checked
self.sending = self.sending + [(int(locationId))]

except Exception as e:
Expand Down Expand Up @@ -631,8 +644,8 @@ async def verifyItems(self):
self.kh2.write_short(self.kh2.base_address + self.Save + slot, itemData.memaddr)
# removes the duped ability if client gave faster than the game.
for charInvo in {"SoraInvo", "DonaldInvo", "GoofyInvo"}:
if self.kh2.read_short(self.kh2.base_address + self.Save + self.kh2seedsave[charInvo][1]) != 0 and\
self.kh2seedsave[charInvo][1]+2 < self.kh2seedsave[charInvo][0]:
if self.kh2.read_short(self.kh2.base_address + self.Save + self.kh2seedsave[charInvo][1]) != 0 and \
self.kh2seedsave[charInvo][1] + 2 < self.kh2seedsave[charInvo][0]:
self.kh2.write_short(self.kh2.base_address + self.Save + self.kh2seedsave[charInvo][1], 0)
# remove the dummy level 1 growths if they are in these invo slots.
for inventorySlot in {0x25CE, 0x25D0, 0x25D2, 0x25D4, 0x25D6, 0x25D8}:
Expand Down Expand Up @@ -738,7 +751,8 @@ async def verifyItems(self):
if itemName == "AP Boost":
amountOfUsedBoosts -= 50
totalBoosts = (amountOfBoostsInInvo + amountOfUsedBoosts)
if totalBoosts <= amountOfItems - self.kh2seedsave["SoldBoosts"][itemName] and amountOfBoostsInInvo < 255:
if totalBoosts <= amountOfItems - self.kh2seedsave["SoldBoosts"][
itemName] and amountOfBoostsInInvo < 255:
self.kh2.write_bytes(self.kh2.base_address + self.Save + itemData.memaddr,
(amountOfBoostsInInvo + 1).to_bytes(1, 'big'), 1)

Expand Down
7 changes: 5 additions & 2 deletions Launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import argparse
import itertools
import multiprocessing
import shlex
import subprocess
import sys
Expand Down Expand Up @@ -146,7 +147,7 @@ def launch(exe, in_terminal=False):

def run_gui():
from kvui import App, ContainerLayout, GridLayout, Button, Label
from kivy.uix.image import Image
from kivy.uix.image import AsyncImage
from kivy.uix.relativelayout import RelativeLayout

class Launcher(App):
Expand Down Expand Up @@ -188,7 +189,8 @@ def build_button(component: Component):
button.component = component
button.bind(on_release=self.component_action)
if component.icon != "icon":
image = Image(source=icon_paths[component.icon], size=(38, 38), size_hint=(None, 1), pos=(5, 0))
image = AsyncImage(source=icon_paths[component.icon],
size=(38, 38), size_hint=(None, 1), pos=(5, 0))
box_layout = RelativeLayout()
box_layout.add_widget(button)
box_layout.add_widget(image)
Expand Down Expand Up @@ -244,6 +246,7 @@ def main(args: Optional[Union[argparse.Namespace, dict]] = None):

if __name__ == '__main__':
init_logging('Launcher')
multiprocessing.freeze_support()
parser = argparse.ArgumentParser(description='Archipelago Launcher')
parser.add_argument('Patch|Game|Component', type=str, nargs='?',
help="Pass either a patch file, a generated game or the name of a component to run.")
Expand Down
15 changes: 10 additions & 5 deletions Main.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,19 +165,24 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
for count in items.values():
new_items.append(player_world.create_filler())
target: int = sum(sum(items.values()) for items in depletion_pool.values())
for item in world.itempool:
for i, item in enumerate(world.itempool):
if depletion_pool[item.player].get(item.name, 0):
target -= 1
depletion_pool[item.player][item.name] -= 1
# quick abort if we have found all items
if not target:
new_items.extend(world.itempool[i+1:])
break
else:
new_items.append(item)
for player, remaining_items in depletion_pool.items():
if remaining_items:
raise Exception(f"{world.get_player_name(player)}"
f" is trying to remove items from their pool that don't exist: {remaining_items}")

# leftovers?
if target:
for player, remaining_items in depletion_pool.items():
remaining_items = {name: count for name, count in remaining_items.items() if count}
if remaining_items:
raise Exception(f"{world.get_player_name(player)}"
f" is trying to remove items from their pool that don't exist: {remaining_items}")
world.itempool[:] = new_items

# temporary home for item links, should be moved out of Main
Expand Down
24 changes: 12 additions & 12 deletions MultiServer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
import argparse
import asyncio
import copy
import functools
import logging
import zlib
import collections
import datetime
import functools
Expand Down Expand Up @@ -162,7 +159,7 @@ class Context:
read_data: typing.Dict[str, object]
stored_data_notification_clients: typing.Dict[str, typing.Set[Client]]
slot_info: typing.Dict[int, NetworkSlot]

generator_version = Version(0, 0, 0)
checksums: typing.Dict[str, str]
item_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})')
item_name_groups: typing.Dict[str, typing.Dict[str, typing.Set[str]]]
Expand Down Expand Up @@ -226,7 +223,7 @@ def __init__(self, host: str, port: int, server_password: str, password: str, lo
self.save_dirty = False
self.tags = ['AP']
self.games: typing.Dict[int, str] = {}
self.minimum_client_versions: typing.Dict[int, Utils.Version] = {}
self.minimum_client_versions: typing.Dict[int, Version] = {}
self.seed_name = ""
self.groups = {}
self.group_collected: typing.Dict[int, typing.Set[int]] = {}
Expand Down Expand Up @@ -384,15 +381,17 @@ def decompress(data: bytes) -> dict:

def _load(self, decoded_obj: dict, game_data_packages: typing.Dict[str, typing.Any],
use_embedded_server_options: bool):

self.read_data = {}
mdata_ver = decoded_obj["minimum_versions"]["server"]
if mdata_ver > Utils.version_tuple:
if mdata_ver > version_tuple:
raise RuntimeError(f"Supplied Multidata (.archipelago) 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 {version_tuple}")
self.generator_version = Version(*decoded_obj["version"])
clients_ver = decoded_obj["minimum_versions"].get("clients", {})
self.minimum_client_versions = {}
for player, version in clients_ver.items():
self.minimum_client_versions[player] = max(Utils.Version(*version), min_client_version)
self.minimum_client_versions[player] = max(Version(*version), min_client_version)

self.slot_info = decoded_obj["slot_info"]
self.games = {slot: slot_info.game for slot, slot_info in self.slot_info.items()}
Expand Down Expand Up @@ -758,7 +757,8 @@ async def on_client_connected(ctx: Context, client: Client):
# tags are for additional features in the communication.
# Name them by feature or fork, as you feel is appropriate.
'tags': ctx.tags,
'version': Utils.version_tuple,
'version': version_tuple,
'generator_version': ctx.generator_version,
'permissions': get_permissions(ctx),
'hint_cost': ctx.hint_cost,
'location_check_points': ctx.location_check_points,
Expand Down Expand Up @@ -1865,7 +1865,7 @@ def _cmd_status(self, tag: str = "") -> bool:

def _cmd_exit(self) -> bool:
"""Shutdown the server"""
async_start(self.ctx.server.ws_server._close())
self.ctx.server.ws_server.close()
if self.ctx.shutdown_task:
self.ctx.shutdown_task.cancel()
self.ctx.exit_event.set()
Expand Down Expand Up @@ -2206,7 +2206,7 @@ async def auto_shutdown(ctx, to_cancel=None):
await asyncio.sleep(ctx.auto_shutdown)
while not ctx.exit_event.is_set():
if not ctx.client_activity_timers.values():
async_start(ctx.server.ws_server._close())
ctx.server.ws_server.close()
ctx.exit_event.set()
if to_cancel:
for task in to_cancel:
Expand All @@ -2217,7 +2217,7 @@ async def auto_shutdown(ctx, to_cancel=None):
delta = datetime.datetime.now(datetime.timezone.utc) - newest_activity
seconds = ctx.auto_shutdown - delta.total_seconds()
if seconds < 0:
async_start(ctx.server.ws_server._close())
ctx.server.ws_server.close()
ctx.exit_event.set()
if to_cancel:
for task in to_cancel:
Expand Down
8 changes: 4 additions & 4 deletions NetUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import websockets

from Utils import Version
from Utils import ByValue, Version


class JSONMessagePart(typing.TypedDict, total=False):
Expand All @@ -20,15 +20,15 @@ class JSONMessagePart(typing.TypedDict, total=False):
flags: int


class ClientStatus(enum.IntEnum):
class ClientStatus(ByValue, enum.IntEnum):
CLIENT_UNKNOWN = 0
CLIENT_CONNECTED = 5
CLIENT_READY = 10
CLIENT_PLAYING = 20
CLIENT_GOAL = 30


class SlotType(enum.IntFlag):
class SlotType(ByValue, enum.IntFlag):
spectator = 0b00
player = 0b01
group = 0b10
Expand All @@ -39,7 +39,7 @@ def always_goal(self) -> bool:
return self.value != 0b01


class Permission(enum.IntFlag):
class Permission(ByValue, enum.IntFlag):
disabled = 0b000 # 0, completely disables access
enabled = 0b001 # 1, allows manual use
goal = 0b010 # 2, allows manual use after goal completion
Expand Down
14 changes: 13 additions & 1 deletion Utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,11 @@ class Version(typing.NamedTuple):
minor: int
build: int

def as_simple_string(self) -> str:
return ".".join(str(item) for item in self)

__version__ = "0.4.0"

__version__ = "0.4.1"
version_tuple = tuplize_version(__version__)

is_linux = sys.platform.startswith("linux")
Expand Down Expand Up @@ -505,6 +508,15 @@ def restricted_loads(s):
return RestrictedUnpickler(io.BytesIO(s)).load()


class ByValue:
"""
Mixin for enums to pickle value instead of name (restores pre-3.11 behavior). Use as left-most parent.
See https://github.com/python/cpython/pull/26658 for why this exists.
"""
def __reduce_ex__(self, prot):
return self.__class__, (self._value_, )


class KeyedDefaultDict(collections.defaultdict):
"""defaultdict variant that uses the missing key as argument to default_factory"""
default_factory: typing.Callable[[typing.Any], typing.Any]
Expand Down
2 changes: 1 addition & 1 deletion WebHostLib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
# if you want to deploy, make sure you have a non-guessable secret key
app.config["SECRET_KEY"] = bytes(socket.gethostname(), encoding="utf-8")
# at what amount of worlds should scheduling be used, instead of rolling in the web-thread
app.config["JOB_THRESHOLD"] = 2
app.config["JOB_THRESHOLD"] = 1
# after what time in seconds should generation be aborted, freeing the queue slot. Can be set to None to disable.
app.config["JOB_TIME"] = 600
app.config['SESSION_PERMANENT'] = True
Expand Down
2 changes: 1 addition & 1 deletion WebHostLib/autolauncher.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def keep_running():
with Locker("autogen"):

with multiprocessing.Pool(config["GENERATORS"], initializer=init_db,
initargs=(config["PONY"],)) as generator_pool:
initargs=(config["PONY"],), maxtasksperchild=10) as generator_pool:
with db_session:
to_start = select(generation for generation in Generation if generation.state == STATE_STARTED)

Expand Down
6 changes: 5 additions & 1 deletion WebHostLib/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,11 @@ def display_log(room: UUID):
if room is None:
return abort(404)
if room.owner == session["_id"]:
return Response(_read_log(os.path.join("logs", str(room.id) + ".txt")), mimetype="text/plain;charset=UTF-8")
file_path = os.path.join("logs", str(room.id) + ".txt")
if os.path.exists(file_path):
return Response(_read_log(file_path), mimetype="text/plain;charset=UTF-8")
return "Log File does not exist."

return "Access Denied", 403


Expand Down
2 changes: 1 addition & 1 deletion WebHostLib/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def get_html_doc(option_type: type(Options.Option)) -> str:
"defaultValue": list(option.default)
}

elif issubclass(option, Options.VerifyKeys):
elif issubclass(option, Options.VerifyKeys) and not issubclass(option, Options.OptionDict):
if option.valid_keys:
game_options[option_name] = {
"type": "custom-list",
Expand Down
Loading

0 comments on commit 55aff8a

Please sign in to comment.