diff --git a/src/ManualClient.py b/src/ManualClient.py index 0f2a4c4e..cfa1b97d 100644 --- a/src/ManualClient.py +++ b/src/ManualClient.py @@ -889,8 +889,18 @@ async def game_watcher_manual(ctx: ManualContext): await asyncio.sleep(0.1) -def read_apmanual_file(apmanual_file): +def read_apmanual_file(apmanual_file) -> dict[str, Any]: + import zipfile from base64 import b64decode + from .container import APManualFile + + if zipfile.is_zipfile(apmanual_file): + try: + container = APManualFile(apmanual_file) + container.read() + return container.as_dict() + except Exception as e: + print("Error reading APManual file:", e) with open(apmanual_file, 'r') as f: return json.loads(b64decode(f.read())) diff --git a/src/__init__.py b/src/__init__.py index 8c56e4b5..2b794687 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,7 +1,5 @@ -from base64 import b64encode import logging import os -import json from typing import Callable, Optional import webbrowser @@ -9,7 +7,7 @@ from worlds.generic.Rules import forbid_items_for_player from worlds.LauncherComponents import Component, SuffixIdentifier, components, Type, launch_subprocess, icon_paths -from .Data import item_table, location_table, region_table, category_table +from .Data import item_table, location_table, category_table from .Game import game_name, filler_item_name, starting_items from .Meta import world_description, world_webworld, enable_region_diagram from .Locations import location_id_to_name, location_name_to_id, location_name_to_location, location_name_groups, victory_names @@ -21,6 +19,7 @@ from .Rules import set_rules from .Options import manual_options_data from .Helpers import is_item_enabled, get_option_value, get_items_for_player, resolve_yaml_option, format_state_prog_items_key, ProgItemsCat +from .container import APManualFile from BaseClasses import CollectionState, ItemClassification, Item from Options import PerGameCommonOptions @@ -386,10 +385,12 @@ def fill_slot_data(self): return slot_data def generate_output(self, output_directory: str): - data = self.client_data() filename = f"{self.multiworld.get_out_file_name_base(self.player)}.apmanual" - with open(os.path.join(output_directory, filename), 'wb') as f: - f.write(b64encode(bytes(json.dumps(data), 'utf-8'))) + zf_path = os.path.join(output_directory, filename) + + apmanual = APManualFile(zf_path, player=self.player, player_name=self.player_name) + apmanual.write() + def write_spoiler(self, spoiler_handle): before_write_spoiler(self, self.multiworld, spoiler_handle) @@ -482,18 +483,6 @@ def get_item_counts(self, player: Optional[int] = None, reset: bool = False) -> self.item_counts[player] = {i.name: real_pool.count(i) for i in real_pool} return self.item_counts.get(player) - def client_data(self): - return { - "game": self.game, - 'player_name': self.multiworld.get_player_name(self.player), - 'player_id': self.player, - 'items': self.item_name_to_item, - 'locations': self.location_name_to_location, - # todo: extract connections out of multiworld.get_regions() instead, in case hooks have modified the regions. - 'regions': region_table, - 'categories': category_table - } - ### # Non-world client methods ### diff --git a/src/container.py b/src/container.py new file mode 100644 index 00000000..493e06b3 --- /dev/null +++ b/src/container.py @@ -0,0 +1,44 @@ +import json +import zipfile +from typing import Any + +from worlds import Files + +from .Data import region_table +from .Game import game_name +from .Locations import location_name_to_location +from .Items import item_name_to_item + +if hasattr(Files, 'APPlayerContainer'): + APPlayerContainer = Files.APPlayerContainer +else: + # Prior to 0.6.2, all containers were player containers. + APPlayerContainer = Files.APContainer + +class APManualFile(APPlayerContainer): + game = game_name + patch_file_ending = ".apmanual" + + def __init__(self, *args: Any, **kwargs: Any): + super().__init__(*args, **kwargs) + + + def write_contents(self, opened_zipfile: zipfile.ZipFile): + super().write_contents(opened_zipfile) + opened_zipfile.writestr("items.json", json.dumps(item_name_to_item, indent=2)) + opened_zipfile.writestr("locations.json", json.dumps(location_name_to_location, indent=2)) + opened_zipfile.writestr("regions.json", json.dumps(region_table, indent=2)) + + def read_contents(self, opened_zipfile: zipfile.ZipFile) -> dict[str, Any]: + manifest = super().read_contents(opened_zipfile) + self.items = json.loads(opened_zipfile.read("items.json")) + self.locations = json.loads(opened_zipfile.read("locations.json")) + self.regions = json.loads(opened_zipfile.read("regions.json")) + return manifest + + def as_dict(self) -> dict[str, Any]: + data = {} + data["items"] = self.items + data["locations"] = self.locations + data["regions"] = self.regions + return data