diff --git a/CommonClient.py b/CommonClient.py index 0b2c22cfd8..f830035425 100644 --- a/CommonClient.py +++ b/CommonClient.py @@ -493,7 +493,8 @@ async def server_loop(ctx: CommonContext, address=None): logger.info(f'Connecting to Archipelago server at {address}') try: socket = await websockets.connect(address, port=port, ping_timeout=None, ping_interval=None) - ctx.ui.update_address_bar(server_url.netloc) + if ctx.ui is not None: + ctx.ui.update_address_bar(server_url.netloc) ctx.server = Endpoint(socket) logger.info('Connected') ctx.server_address = address diff --git a/Utils.py b/Utils.py index bb29166f75..c621e31c9a 100644 --- a/Utils.py +++ b/Utils.py @@ -422,6 +422,10 @@ def get_text_between(text: str, start: str, end: str) -> str: return text[text.index(start) + len(start): text.rindex(end)] +def get_text_after(text: str, start: str) -> str: + return text[text.index(start) + len(start):] + + loglevel_mapping = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG} diff --git a/WebHost.py b/WebHost.py index 3d3c8678e2..db802193a6 100644 --- a/WebHost.py +++ b/WebHost.py @@ -42,12 +42,11 @@ def get_app(): def create_ordered_tutorials_file() -> typing.List[typing.Dict[str, typing.Any]]: import json import shutil - import pathlib import zipfile zfile: zipfile.ZipInfo - from worlds.AutoWorld import AutoWorldRegister, __file__ + from worlds.AutoWorld import AutoWorldRegister worlds = {} data = [] for game, world in AutoWorldRegister.world_types.items(): @@ -60,8 +59,8 @@ def create_ordered_tutorials_file() -> typing.List[typing.Dict[str, typing.Any]] target_path = os.path.join(base_target_path, game) os.makedirs(target_path, exist_ok=True) - if world.is_zip: - zipfile_path = pathlib.Path(world.__file__).parents[1] + if world.zip_path: + zipfile_path = world.zip_path assert os.path.isfile(zipfile_path), f"{zipfile_path} is not a valid file(path)." assert zipfile.is_zipfile(zipfile_path), f"{zipfile_path} is not a valid zipfile." diff --git a/WebHostLib/tracker.py b/WebHostLib/tracker.py index a637b6abf2..0f181eb11a 100644 --- a/WebHostLib/tracker.py +++ b/WebHostLib/tracker.py @@ -768,10 +768,10 @@ def getTracker(tracker: UUID): if game_state == 30: inventory[team][player][106] = 1 # Triforce - player_big_key_locations = {playernumber: set() for playernumber in range(1, len(names[0]) + 1) if playernumber not in groups} - player_small_key_locations = {playernumber: set() for playernumber in range(1, len(names[0]) + 1) if playernumber not in groups} + player_big_key_locations = {playernumber: set() for playernumber in range(1, len(names[0]) + 1)} + player_small_key_locations = {playernumber: set() for playernumber in range(1, len(names[0]) + 1)} for loc_data in locations.values(): - for values in loc_data.values(): + for values in loc_data.values(): item_id, item_player, flags = values if item_id in ids_big_key: diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index 04a9d00f42..eac5ce502a 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -2,6 +2,7 @@ from __future__ import annotations import logging import sys +import pathlib from typing import Dict, FrozenSet, Set, Tuple, List, Optional, TextIO, Any, Callable, Union, TYPE_CHECKING from Options import Option @@ -48,13 +49,14 @@ class AutoWorldRegister(type): raise RuntimeError(f"""Game {dct["game"]} already registered.""") AutoWorldRegister.world_types[dct["game"]] = new_class new_class.__file__ = sys.modules[new_class.__module__].__file__ - new_class.is_zip = ".apworld" in new_class.__file__ + if ".apworld" in new_class.__file__: + new_class.zip_path = pathlib.Path(new_class.__file__).parents[1] return new_class class AutoLogicRegister(type): - def __new__(cls, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoLogicRegister: - new_class = super().__new__(cls, name, bases, dct) + def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoLogicRegister: + new_class = super().__new__(mcs, name, bases, dct) function: Callable[..., Any] for item_name, function in dct.items(): if item_name == "copy_mixin": @@ -189,7 +191,7 @@ class World(metaclass=AutoWorldRegister): item_names: Set[str] # set of all potential item names location_names: Set[str] # set of all potential location names - is_zip: bool # was loaded from a .apworld ? + zip_path: Optional[pathlib.Path] = None # If loaded from a .apworld, this is the Path to it. __file__: str # path it was loaded from def __init__(self, world: "MultiWorld", player: int): diff --git a/worlds/__init__.py b/worlds/__init__.py index caa170d5c6..46b383b303 100644 --- a/worlds/__init__.py +++ b/worlds/__init__.py @@ -37,7 +37,6 @@ for file in os.scandir(folder): world_sources.sort() for world_source in world_sources: if world_source.is_zip: - importer = zipimport.zipimporter(os.path.join(folder, world_source.path)) importer.load_module(world_source.path.split(".", 1)[0]) else: diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index f7bf18ca52..9882fbfb3d 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -1,26 +1,23 @@ -import random import logging import os +import random import threading import typing from BaseClasses import Item, CollectionState, Tutorial -from .SubClasses import ALttPItem -from ..AutoWorld import World, WebWorld, LogicMixin -from .Options import alttp_options, smallkey_shuffle -from .Items import item_init_table, item_name_groups, item_table, GetBeemizerItem -from .Regions import lookup_name_to_id, create_regions, mark_light_world_regions -from .Rules import set_rules -from .ItemPool import generate_itempool, difficulties -from .Shops import create_shops, ShopSlotFill from .Dungeons import create_dungeons +from .EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect +from .InvertedRegions import create_inverted_regions, mark_dark_world_regions +from .ItemPool import generate_itempool, difficulties +from .Items import item_init_table, item_name_groups, item_table, GetBeemizerItem +from .Options import alttp_options, smallkey_shuffle +from .Regions import lookup_name_to_id, create_regions, mark_light_world_regions from .Rom import LocalRom, patch_rom, patch_race_rom, check_enemizer, patch_enemizer, apply_rom_settings, \ get_hash_string, get_base_rom_path, LttPDeltaPatch -import Patch -from itertools import chain - -from .InvertedRegions import create_inverted_regions, mark_dark_world_regions -from .EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect +from .Rules import set_rules +from .Shops import create_shops, ShopSlotFill +from .SubClasses import ALttPItem +from ..AutoWorld import World, WebWorld, LogicMixin lttp_logger = logging.getLogger("A Link to the Past") diff --git a/worlds/factorio/Mod.py b/worlds/factorio/Mod.py index 8c7dcf669c..37c503b047 100644 --- a/worlds/factorio/Mod.py +++ b/worlds/factorio/Mod.py @@ -78,9 +78,14 @@ def generate_mod(world, output_directory: str): global data_final_template, locale_template, control_template, data_template, settings_template with template_load_lock: if not data_final_template: - mod_template_folder = os.path.join(os.path.dirname(__file__), "data", "mod_template") + def load_template(name: str): + import pkgutil + data = pkgutil.get_data(__name__, "data/mod_template/" + name).decode() + return data, name, lambda: False + template_env: Optional[jinja2.Environment] = \ - jinja2.Environment(loader=jinja2.FileSystemLoader([mod_template_folder])) + jinja2.Environment(loader=jinja2.FunctionLoader(load_template)) + data_template = template_env.get_template("data.lua") data_final_template = template_env.get_template("data-final-fixes.lua") locale_template = template_env.get_template(r"locale/en/locale.cfg") @@ -158,7 +163,21 @@ def generate_mod(world, output_directory: str): mod_dir = os.path.join(output_directory, mod_name + "_" + Utils.__version__) en_locale_dir = os.path.join(mod_dir, "locale", "en") os.makedirs(en_locale_dir, exist_ok=True) - shutil.copytree(os.path.join(os.path.dirname(__file__), "data", "mod"), mod_dir, dirs_exist_ok=True) + + if world.zip_path: + # Maybe investigate read from zip, write to zip, without temp file? + with zipfile.ZipFile(world.zip_path) as zf: + for file in zf.infolist(): + if not file.is_dir() and "/data/mod/" in file.filename: + path_part = Utils.get_text_after(file.filename, "/data/mod/") + target = os.path.join(mod_dir, path_part) + os.makedirs(os.path.split(target)[0], exist_ok=True) + + with open(target, "wb") as f: + f.write(zf.read(file)) + else: + shutil.copytree(os.path.join(os.path.dirname(__file__), "data", "mod"), mod_dir, dirs_exist_ok=True) + with open(os.path.join(mod_dir, "data.lua"), "wt") as f: f.write(data_template_code) with open(os.path.join(mod_dir, "data-final-fixes.lua"), "wt") as f: diff --git a/worlds/factorio/Shapes.py b/worlds/factorio/Shapes.py index eaf44ac1ba..f42da4d20c 100644 --- a/worlds/factorio/Shapes.py +++ b/worlds/factorio/Shapes.py @@ -1,7 +1,7 @@ from typing import Dict, List, Set from collections import deque -from worlds.factorio.Options import TechTreeLayout +from .Options import TechTreeLayout funnel_layers = {TechTreeLayout.option_small_funnels: 3, TechTreeLayout.option_medium_funnels: 4, diff --git a/worlds/factorio/Technologies.py b/worlds/factorio/Technologies.py index f89dc53ee3..b88cc9b1ad 100644 --- a/worlds/factorio/Technologies.py +++ b/worlds/factorio/Technologies.py @@ -19,8 +19,8 @@ pool = ThreadPoolExecutor(1) def load_json_data(data_name: str) -> Union[List[str], Dict[str, Any]]: - with open(os.path.join(source_folder, f"{data_name}.json")) as f: - return json.load(f) + import pkgutil + return json.loads(pkgutil.get_data(__name__, "data/" + data_name + ".json").decode()) techs_future = pool.submit(load_json_data, "techs") diff --git a/worlds/factorio/__init__.py b/worlds/factorio/__init__.py index 26e761d4d3..a01abac748 100644 --- a/worlds/factorio/__init__.py +++ b/worlds/factorio/__init__.py @@ -1,7 +1,7 @@ import collections import typing -from ..AutoWorld import World, WebWorld +from worlds.AutoWorld import World, WebWorld from BaseClasses import Region, Entrance, Location, Item, RegionType, Tutorial, ItemClassification from .Technologies import base_tech_table, recipe_sources, base_technology_table, \ diff --git a/worlds/minecraft/docs/minecraft_en.md b/worlds/minecraft/docs/minecraft_en.md index b33710f73c..e8b1a3642e 100644 --- a/worlds/minecraft/docs/minecraft_en.md +++ b/worlds/minecraft/docs/minecraft_en.md @@ -33,12 +33,12 @@ leave this window open as this is your server console. ### Connect to the MultiServer -Using Minecraft 1.18.2 connect to the server `localhost`. +Open Minecraft, go to `Multiplayer > Direct Connection`, and join the `localhost` server address. If you are using the website to host the game then it should auto-connect to the AP server without the need to `/connect` otherwise once you are in game type `/connect (Port) (Password)` where `` is the address of the -Archipelago server. `(Port)` is only required if the Archipelago server is not using the default port of 38281. +Archipelago server. `(Port)` is only required if the Archipelago server is not using the default port of 38281. Note that there is no colon between `` and `(Port)`. `(Password)` is only required if the Archipelago server you are using has a password set. ### Play the game diff --git a/worlds/oot/HintList.py b/worlds/oot/HintList.py index 06af1a9be3..7fc298b0d7 100644 --- a/worlds/oot/HintList.py +++ b/worlds/oot/HintList.py @@ -1,6 +1,7 @@ import random from BaseClasses import LocationProgressType +from .Items import OOTItem # Abbreviations # DMC Death Mountain Crater @@ -1260,7 +1261,7 @@ def hintExclusions(world, clear_cache=False): world.hint_exclusions = [] for location in world.get_locations(): - if (location.locked and (location.item.type != 'Song' or world.shuffle_song_items != 'song')) or location.progress_type == LocationProgressType.EXCLUDED: + if (location.locked and ((isinstance(location.item, OOTItem) and location.item.type != 'Song') or world.shuffle_song_items != 'song')) or location.progress_type == LocationProgressType.EXCLUDED: world.hint_exclusions.append(location.name) world_location_names = [ diff --git a/worlds/oot/Patches.py b/worlds/oot/Patches.py index 91f656b4e9..7bf31c4f7a 100644 --- a/worlds/oot/Patches.py +++ b/worlds/oot/Patches.py @@ -2104,7 +2104,7 @@ def place_shop_items(rom, world, shop_items, messages, locations, init_shop_id=F shop_objs = { 0x0148 } # "Sold Out" object for location in locations: - if location.item.type == 'Shop': + if isinstance(location.item, OOTItem) and location.item.type == 'Shop': shop_objs.add(location.item.special['object']) rom.write_int16(location.address1, location.item.index) else: