mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-04-04 08:03:43 -07:00
Compare commits
5 Commits
a3e8f69909
...
core_manif
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df76c26fbb | ||
|
|
9a900e29e5 | ||
|
|
41eba5a2f6 | ||
|
|
b372b02273 | ||
|
|
f26313367e |
@@ -21,6 +21,7 @@ import time
|
||||
import typing
|
||||
import weakref
|
||||
import zlib
|
||||
from signal import SIGINT, SIGTERM
|
||||
|
||||
import ModuleUpdate
|
||||
|
||||
@@ -2571,6 +2572,8 @@ async def console(ctx: Context):
|
||||
input_text = await queue.get()
|
||||
queue.task_done()
|
||||
ctx.commandprocessor(input_text)
|
||||
except asyncio.exceptions.CancelledError:
|
||||
ctx.logger.info("ConsoleTask cancelled")
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
@@ -2737,6 +2740,15 @@ async def main(args: argparse.Namespace):
|
||||
console_task = asyncio.create_task(console(ctx))
|
||||
if ctx.auto_shutdown:
|
||||
ctx.shutdown_task = asyncio.create_task(auto_shutdown(ctx, [console_task]))
|
||||
|
||||
def stop():
|
||||
for remove_signal in [SIGINT, SIGTERM]:
|
||||
asyncio.get_event_loop().remove_signal_handler(remove_signal)
|
||||
ctx.commandprocessor._cmd_exit()
|
||||
|
||||
for signal in [SIGINT, SIGTERM]:
|
||||
asyncio.get_event_loop().add_signal_handler(signal, stop)
|
||||
|
||||
await ctx.exit_event.wait()
|
||||
console_task.cancel()
|
||||
if ctx.shutdown_task:
|
||||
|
||||
@@ -29,7 +29,7 @@ import webbrowser
|
||||
import re
|
||||
from urllib.parse import urlparse
|
||||
from worlds.AutoWorld import AutoWorldRegister, World
|
||||
from Options import (Option, Toggle, TextChoice, Choice, FreeText, NamedRange, Range, OptionSet, OptionList, Removed,
|
||||
from Options import (Option, Toggle, TextChoice, Choice, FreeText, NamedRange, Range, OptionSet, OptionList,
|
||||
OptionCounter, Visibility)
|
||||
|
||||
|
||||
@@ -318,26 +318,28 @@ class OptionsCreator(ThemedApp):
|
||||
else:
|
||||
self.show_result_snack("Name cannot be longer than 16 characters.")
|
||||
|
||||
def create_range(self, option: typing.Type[Range], name: str):
|
||||
def create_range(self, option: typing.Type[Range], name: str, bind=True):
|
||||
def update_text(range_box: VisualRange):
|
||||
self.options[name] = int(range_box.slider.value)
|
||||
range_box.tag.text = str(int(range_box.slider.value))
|
||||
return
|
||||
|
||||
box = VisualRange(option=option, name=name)
|
||||
box.slider.bind(on_touch_move=lambda _, _1: update_text(box))
|
||||
if bind:
|
||||
box.slider.bind(value=lambda _, _1: update_text(box))
|
||||
self.options[name] = option.default
|
||||
return box
|
||||
|
||||
def create_named_range(self, option: typing.Type[NamedRange], name: str):
|
||||
def set_to_custom(range_box: VisualNamedRange):
|
||||
if (not self.options[name] == range_box.range.slider.value) \
|
||||
and (not self.options[name] in option.special_range_names or
|
||||
range_box.range.slider.value != option.special_range_names[self.options[name]]):
|
||||
# we should validate the touch here,
|
||||
# but this is much cheaper
|
||||
range_box.range.tag.text = str(int(range_box.range.slider.value))
|
||||
if range_box.range.slider.value in option.special_range_names.values():
|
||||
value = next(key for key, val in option.special_range_names.items()
|
||||
if val == range_box.range.slider.value)
|
||||
self.options[name] = value
|
||||
set_button_text(box.choice, value.title())
|
||||
else:
|
||||
self.options[name] = int(range_box.range.slider.value)
|
||||
range_box.range.tag.text = str(int(range_box.range.slider.value))
|
||||
set_button_text(range_box.choice, "Custom")
|
||||
|
||||
def set_button_text(button: MDButton, text: str):
|
||||
@@ -346,7 +348,7 @@ class OptionsCreator(ThemedApp):
|
||||
def set_value(text: str, range_box: VisualNamedRange):
|
||||
range_box.range.slider.value = min(max(option.special_range_names[text.lower()], option.range_start),
|
||||
option.range_end)
|
||||
range_box.range.tag.text = str(int(range_box.range.slider.value))
|
||||
range_box.range.tag.text = str(option.special_range_names[text.lower()])
|
||||
set_button_text(range_box.choice, text)
|
||||
self.options[name] = text.lower()
|
||||
range_box.range.slider.dropdown.dismiss()
|
||||
@@ -355,13 +357,18 @@ class OptionsCreator(ThemedApp):
|
||||
# for some reason this fixes an issue causing some to not open
|
||||
box.range.slider.dropdown.open()
|
||||
|
||||
box = VisualNamedRange(option=option, name=name, range_widget=self.create_range(option, name))
|
||||
if option.default in option.special_range_names:
|
||||
box = VisualNamedRange(option=option, name=name, range_widget=self.create_range(option, name, bind=False))
|
||||
default: int | str = option.default
|
||||
if default in option.special_range_names:
|
||||
# value can get mismatched in this case
|
||||
box.range.slider.value = min(max(option.special_range_names[option.default], option.range_start),
|
||||
box.range.slider.value = min(max(option.special_range_names[default], option.range_start),
|
||||
option.range_end)
|
||||
box.range.tag.text = str(int(box.range.slider.value))
|
||||
box.range.slider.bind(on_touch_move=lambda _, _2: set_to_custom(box))
|
||||
elif default in option.special_range_names.values():
|
||||
# better visual
|
||||
default = next(key for key, val in option.special_range_names.items() if val == option.default)
|
||||
set_button_text(box.choice, default.title())
|
||||
box.range.slider.bind(value=lambda _, _2: set_to_custom(box))
|
||||
items = [
|
||||
{
|
||||
"text": choice.title(),
|
||||
@@ -371,7 +378,7 @@ class OptionsCreator(ThemedApp):
|
||||
]
|
||||
box.range.slider.dropdown = MDDropdownMenu(caller=box.choice, items=items)
|
||||
box.choice.bind(on_release=open_dropdown)
|
||||
self.options[name] = option.default
|
||||
self.options[name] = default
|
||||
return box
|
||||
|
||||
def create_free_text(self, option: typing.Type[FreeText] | typing.Type[TextChoice], name: str):
|
||||
@@ -447,8 +454,12 @@ class OptionsCreator(ThemedApp):
|
||||
valid_keys = sorted(option.valid_keys)
|
||||
if option.verify_item_name:
|
||||
valid_keys += list(world.item_name_to_id.keys())
|
||||
if option.convert_name_groups:
|
||||
valid_keys += list(world.item_name_groups.keys())
|
||||
if option.verify_location_name:
|
||||
valid_keys += list(world.location_name_to_id.keys())
|
||||
if option.convert_name_groups:
|
||||
valid_keys += list(world.location_name_groups.keys())
|
||||
|
||||
if not issubclass(option, OptionCounter):
|
||||
def apply_changes(button):
|
||||
@@ -470,14 +481,6 @@ class OptionsCreator(ThemedApp):
|
||||
dialog.scrollbox.layout.spacing = dp(5)
|
||||
dialog.scrollbox.layout.padding = [0, dp(5), 0, 0]
|
||||
|
||||
if name not in self.options:
|
||||
# convert from non-mutable to mutable
|
||||
# We use list syntax even for sets, set behavior is enforced through GUI
|
||||
if issubclass(option, OptionCounter):
|
||||
self.options[name] = deepcopy(option.default)
|
||||
else:
|
||||
self.options[name] = sorted(option.default)
|
||||
|
||||
if issubclass(option, OptionCounter):
|
||||
for value in sorted(self.options[name]):
|
||||
dialog.add_set_item(value, self.options[name].get(value, None))
|
||||
@@ -491,6 +494,15 @@ class OptionsCreator(ThemedApp):
|
||||
def create_option_set_list_counter(self, option: typing.Type[OptionList] | typing.Type[OptionSet] |
|
||||
typing.Type[OptionCounter], name: str, world: typing.Type[World]):
|
||||
main_button = MDButton(MDButtonText(text="Edit"), on_release=lambda x: self.create_popup(option, name, world))
|
||||
|
||||
if name not in self.options:
|
||||
# convert from non-mutable to mutable
|
||||
# We use list syntax even for sets, set behavior is enforced through GUI
|
||||
if issubclass(option, OptionCounter):
|
||||
self.options[name] = deepcopy(option.default)
|
||||
else:
|
||||
self.options[name] = sorted(option.default)
|
||||
|
||||
return main_button
|
||||
|
||||
def create_option(self, option: typing.Type[Option], name: str, world: typing.Type[World]) -> Widget:
|
||||
|
||||
@@ -32,6 +32,8 @@ If the APWorld is a folder, the only required field is "game":
|
||||
There are also the following optional fields:
|
||||
* `minimum_ap_version` and `maximum_ap_version` - which if present will each be compared against the current
|
||||
Archipelago version respectively to filter those files from being loaded.
|
||||
* `platforms` - a list of strings indicating the `sys.platform`(s) the world can run on.
|
||||
If empty or not set, it is assumed to be any that python itself can run on.
|
||||
* `world_version` - an arbitrary version for that world in order to only load the newest valid world.
|
||||
An APWorld without a world_version is always treated as older than one with a version
|
||||
(**Must** use exactly the format `"major.minor.build"`, e.g. `1.0.0`)
|
||||
|
||||
1
setup.py
1
setup.py
@@ -409,6 +409,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
|
||||
apworld = APWorldContainer(str(zip_path))
|
||||
apworld.minimum_ap_version = version_tuple
|
||||
apworld.maximum_ap_version = version_tuple
|
||||
apworld.platforms = [sys.platform]
|
||||
apworld.game = worldtype.game
|
||||
manifest.update(apworld.get_manifest())
|
||||
apworld.manifest_path = f"{file_name}/archipelago.json"
|
||||
|
||||
@@ -353,6 +353,8 @@ class World(metaclass=AutoWorldRegister):
|
||||
"""path it was loaded from"""
|
||||
world_version: ClassVar[Version] = Version(0, 0, 0)
|
||||
"""Optional world version loaded from archipelago.json"""
|
||||
platforms: ClassVar[Optional[List[str]]] = None
|
||||
"""Optional platforms loaded from archipelago.json"""
|
||||
|
||||
def __init__(self, multiworld: "MultiWorld", player: int):
|
||||
assert multiworld is not None
|
||||
|
||||
@@ -197,6 +197,7 @@ class APWorldContainer(APContainer):
|
||||
world_version: "Version | None" = None
|
||||
minimum_ap_version: "Version | None" = None
|
||||
maximum_ap_version: "Version | None" = None
|
||||
platforms: Optional[List[str]] = None
|
||||
|
||||
def read_contents(self, opened_zipfile: zipfile.ZipFile) -> Dict[str, Any]:
|
||||
from Utils import tuplize_version
|
||||
@@ -205,6 +206,7 @@ class APWorldContainer(APContainer):
|
||||
for version_key in ("world_version", "minimum_ap_version", "maximum_ap_version"):
|
||||
if version_key in manifest:
|
||||
setattr(self, version_key, tuplize_version(manifest[version_key]))
|
||||
self.platforms = manifest.get("platforms")
|
||||
return manifest
|
||||
|
||||
def get_manifest(self) -> Dict[str, Any]:
|
||||
@@ -215,6 +217,8 @@ class APWorldContainer(APContainer):
|
||||
version = getattr(self, version_key)
|
||||
if version:
|
||||
manifest[version_key] = version.as_simple_string()
|
||||
if self.platforms:
|
||||
manifest["platforms"] = self.platforms
|
||||
return manifest
|
||||
|
||||
|
||||
|
||||
@@ -289,6 +289,12 @@ if not is_frozen():
|
||||
if not worldtype:
|
||||
logging.error(f"Requested APWorld \"{worldname}\" does not exist.")
|
||||
continue
|
||||
|
||||
assert worldtype.platforms != [], (
|
||||
f"World {worldname} has an empty list for platforms. "
|
||||
"Use None or omit the attribute for 'any platform'."
|
||||
)
|
||||
|
||||
file_name = os.path.split(os.path.dirname(worldtype.__file__))[1]
|
||||
world_directory = os.path.join("worlds", file_name)
|
||||
if os.path.isfile(os.path.join(world_directory, "archipelago.json")):
|
||||
|
||||
@@ -118,6 +118,7 @@ for world_source in world_sources:
|
||||
game = manifest.get("game")
|
||||
if game in AutoWorldRegister.world_types:
|
||||
AutoWorldRegister.world_types[game].world_version = tuplize_version(manifest.get("world_version", "0.0.0"))
|
||||
AutoWorldRegister.world_types[game].platforms = manifest.get("platforms")
|
||||
|
||||
if apworlds:
|
||||
# encapsulation for namespace / gc purposes
|
||||
@@ -165,6 +166,11 @@ if apworlds:
|
||||
f"Did not load {apworld_source.path} "
|
||||
f"as its maximum core version {apworld.maximum_ap_version} "
|
||||
f"is lower than current core version {version_tuple}.")
|
||||
elif apworld.platforms and sys.platform not in apworld.platforms:
|
||||
fail_world(apworld.game,
|
||||
f"Did not load {apworld_source.path} "
|
||||
f"as it is not compatible with current platform {sys.platform}. "
|
||||
f"Supported platforms: {', '.join(apworld.platforms)}")
|
||||
else:
|
||||
core_compatible.append((apworld_source, apworld))
|
||||
# load highest version first
|
||||
@@ -199,6 +205,8 @@ if apworlds:
|
||||
# world could fail to load at this point
|
||||
if apworld.world_version:
|
||||
AutoWorldRegister.world_types[apworld.game].world_version = apworld.world_version
|
||||
if apworld.platforms:
|
||||
AutoWorldRegister.world_types[apworld.game].platforms = apworld.platforms
|
||||
load_apworlds()
|
||||
del load_apworlds
|
||||
|
||||
|
||||
Reference in New Issue
Block a user