Skip to content

Commit

Permalink
Merge branch 'main' into mlss-bugfixes
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesbrq authored Sep 8, 2024
2 parents 1de8782 + e52ce01 commit 22dcf8a
Show file tree
Hide file tree
Showing 44 changed files with 2,783 additions and 3,274 deletions.
2 changes: 1 addition & 1 deletion BaseClasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -1207,7 +1207,7 @@ class ItemClassification(IntFlag):
filler = 0b0000 # aka trash, as in filler items like ammo, currency etc,
progression = 0b0001 # Item that is logically relevant
useful = 0b0010 # Item that is generally quite useful, but not required for anything logical
trap = 0b0100 # detrimental or entirely useless (nothing) item
trap = 0b0100 # detrimental item
skip_balancing = 0b1000 # should technically never occur on its own
# Item that is logically relevant, but progression balancing should not touch.
# Typically currency or other counted items.
Expand Down
33 changes: 19 additions & 14 deletions CommonClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -662,17 +662,19 @@ def handle_connection_loss(self, msg: str) -> None:
logger.exception(msg, exc_info=exc_info, extra={'compact_gui': True})
self._messagebox_connection_loss = self.gui_error(msg, exc_info[1])

def run_gui(self):
"""Import kivy UI system and start running it as self.ui_task."""
def make_gui(self) -> type:
"""To return the Kivy App class needed for run_gui so it can be overridden before being built"""
from kvui import GameManager

class TextManager(GameManager):
logging_pairs = [
("Client", "Archipelago")
]
base_title = "Archipelago Text Client"

self.ui = TextManager(self)
return TextManager

def run_gui(self):
"""Import kivy UI system from make_gui() and start running it as self.ui_task."""
ui_class = self.make_gui()
self.ui = ui_class(self)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")

def run_cli(self):
Expand Down Expand Up @@ -994,7 +996,7 @@ def get_base_parser(description: typing.Optional[str] = None):
return parser


def run_as_textclient():
def run_as_textclient(*args):
class TextContext(CommonContext):
# Text Mode to use !hint and such with games that have no text entry
tags = CommonContext.tags | {"TextOnly"}
Expand Down Expand Up @@ -1033,15 +1035,18 @@ async def main(args):
parser = get_base_parser(description="Gameless Archipelago Client, for text interfacing.")
parser.add_argument('--name', default=None, help="Slot Name to connect as.")
parser.add_argument("url", nargs="?", help="Archipelago connection url")
args = parser.parse_args()
args = parser.parse_args(args)

if args.url:
url = urllib.parse.urlparse(args.url)
args.connect = url.netloc
if url.username:
args.name = urllib.parse.unquote(url.username)
if url.password:
args.password = urllib.parse.unquote(url.password)
if url.scheme == "archipelago":
args.connect = url.netloc
if url.username:
args.name = urllib.parse.unquote(url.username)
if url.password:
args.password = urllib.parse.unquote(url.password)
else:
parser.error(f"bad url, found {args.url}, expected url in form of archipelago://archipelago.gg:38281")

colorama.init()

Expand All @@ -1051,4 +1056,4 @@ async def main(args):

if __name__ == '__main__':
logging.getLogger().setLevel(logging.INFO) # force log-level to work around log level resetting to WARNING
run_as_textclient()
run_as_textclient(*sys.argv[1:]) # default value for parse_args
3 changes: 2 additions & 1 deletion Generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ def main(args=None) -> Tuple[argparse.Namespace, int]:
erargs.outputpath = args.outputpath
erargs.skip_prog_balancing = args.skip_prog_balancing
erargs.skip_output = args.skip_output
erargs.name = {}

settings_cache: Dict[str, Tuple[argparse.Namespace, ...]] = \
{fname: (tuple(roll_settings(yaml, args.plando) for yaml in yamls) if args.sameoptions else None)
Expand Down Expand Up @@ -202,7 +203,7 @@ def main(args=None) -> Tuple[argparse.Namespace, int]:

if path == args.weights_file_path: # if name came from the weights file, just use base player name
erargs.name[player] = f"Player{player}"
elif not erargs.name[player]: # if name was not specified, generate it from filename
elif player not in erargs.name: # if name was not specified, generate it from filename
erargs.name[player] = os.path.splitext(os.path.split(path)[-1])[0]
erargs.name[player] = handle_name(erargs.name[player], player, name_counter)

Expand Down
103 changes: 93 additions & 10 deletions Launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
import shlex
import subprocess
import sys
import urllib.parse
import webbrowser
from os.path import isfile
from shutil import which
from typing import Callable, Sequence, Union, Optional
from typing import Callable, Optional, Sequence, Tuple, Union

import Utils
import settings
Expand Down Expand Up @@ -107,7 +108,81 @@ def update_settings():
])


def identify(path: Union[None, str]):
def handle_uri(path: str, launch_args: Tuple[str, ...]) -> None:
url = urllib.parse.urlparse(path)
queries = urllib.parse.parse_qs(url.query)
launch_args = (path, *launch_args)
client_component = None
text_client_component = None
if "game" in queries:
game = queries["game"][0]
else: # TODO around 0.6.0 - this is for pre this change webhost uri's
game = "Archipelago"
for component in components:
if component.supports_uri and component.game_name == game:
client_component = component
elif component.display_name == "Text Client":
text_client_component = component

from kvui import App, Button, BoxLayout, Label, Clock, Window

class Popup(App):
timer_label: Label
remaining_time: Optional[int]

def __init__(self):
self.title = "Connect to Multiworld"
self.icon = r"data/icon.png"
super().__init__()

def build(self):
layout = BoxLayout(orientation="vertical")

if client_component is None:
self.remaining_time = 7
label_text = (f"A game client able to parse URIs was not detected for {game}.\n"
f"Launching Text Client in 7 seconds...")
self.timer_label = Label(text=label_text)
layout.add_widget(self.timer_label)
Clock.schedule_interval(self.update_label, 1)
else:
layout.add_widget(Label(text="Select client to open and connect with."))
button_row = BoxLayout(orientation="horizontal", size_hint=(1, 0.4))

text_client_button = Button(
text=text_client_component.display_name,
on_release=lambda *args: run_component(text_client_component, *launch_args)
)
button_row.add_widget(text_client_button)

game_client_button = Button(
text=client_component.display_name,
on_release=lambda *args: run_component(client_component, *launch_args)
)
button_row.add_widget(game_client_button)

layout.add_widget(button_row)

return layout

def update_label(self, dt):
if self.remaining_time > 1:
# countdown the timer and string replace the number
self.remaining_time -= 1
self.timer_label.text = self.timer_label.text.replace(
str(self.remaining_time + 1), str(self.remaining_time)
)
else:
# our timer is finished so launch text client and close down
run_component(text_client_component, *launch_args)
Clock.unschedule(self.update_label)
App.get_running_app().stop()
Window.close()

Popup().run()


def identify(path: Union[None, str]) -> Tuple[Union[None, str], Union[None, Component]]:
if path is None:
return None, None
for component in components:
Expand Down Expand Up @@ -299,20 +374,24 @@ def main(args: Optional[Union[argparse.Namespace, dict]] = None):
elif not args:
args = {}

if args.get("Patch|Game|Component", None) is not None:
file, component = identify(args["Patch|Game|Component"])
path = args.get("Patch|Game|Component|url", None)
if path is not None:
if path.startswith("archipelago://"):
handle_uri(path, args.get("args", ()))
return
file, component = identify(path)
if file:
args['file'] = file
if component:
args['component'] = component
if not component:
logging.warning(f"Could not identify Component responsible for {args['Patch|Game|Component']}")
logging.warning(f"Could not identify Component responsible for {path}")

if args["update_settings"]:
update_settings()
if 'file' in args:
if "file" in args:
run_component(args["component"], args["file"], *args["args"])
elif 'component' in args:
elif "component" in args:
run_component(args["component"], *args["args"])
elif not args["update_settings"]:
run_gui()
Expand All @@ -322,12 +401,16 @@ def main(args: Optional[Union[argparse.Namespace, dict]] = None):
init_logging('Launcher')
Utils.freeze_support()
multiprocessing.set_start_method("spawn") # if launched process uses kivy, fork won't work
parser = argparse.ArgumentParser(description='Archipelago Launcher')
parser = argparse.ArgumentParser(
description='Archipelago Launcher',
usage="[-h] [--update_settings] [Patch|Game|Component] [-- component args here]"
)
run_group = parser.add_argument_group("Run")
run_group.add_argument("--update_settings", action="store_true",
help="Update host.yaml and exit.")
run_group.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.")
run_group.add_argument("Patch|Game|Component|url", type=str, nargs="?",
help="Pass either a patch file, a generated game, the component name to run, or a url to "
"connect with.")
run_group.add_argument("args", nargs="*",
help="Arguments to pass to component.")
main(parser.parse_args())
Expand Down
2 changes: 1 addition & 1 deletion WebHostLib/templates/macros.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
{% for patch in room.seed.slots|list|sort(attribute="player_id") %}
<tr>
<td>{{ patch.player_id }}</td>
<td data-tooltip="Connect via TextClient"><a href="archipelago://{{ patch.player_name | e}}:None@{{ config['HOST_ADDRESS'] }}:{{ room.last_port }}">{{ patch.player_name }}</a></td>
<td data-tooltip="Connect via Game Client"><a href="archipelago://{{ patch.player_name | e}}:None@{{ config['HOST_ADDRESS'] }}:{{ room.last_port }}?game={{ patch.game }}&room={{ room.id | suuid }}">{{ patch.player_name }}</a></td>
<td>{{ patch.game }}</td>
<td>
{% if patch.data %}
Expand Down
2 changes: 1 addition & 1 deletion WebHostLib/templates/userContent.html
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ <h2>Your Seeds</h2>
</tbody>
</table>
{% else %}
You have no generated any seeds yet!
You have not generated any seeds yet!
{% endif %}
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions docs/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,6 @@
# Noita
/worlds/noita/ @ScipioWright @heinermann

# Ocarina of Time
/worlds/oot/ @espeon65536

# Old School Runescape
/worlds/osrs @digiholic

Expand Down Expand Up @@ -230,6 +227,9 @@
# Links Awakening DX
# /worlds/ladx/

# Ocarina of Time
# /worlds/oot/

## Disabled Unmaintained Worlds

# The following worlds in this repo are currently unmaintained and disabled as they do not work in core. If you are
Expand Down
4 changes: 2 additions & 2 deletions inno_setup.iss
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,8 @@ Root: HKCR; Subkey: "{#MyAppName}worlddata\shell\open\command"; ValueData: """{a

Root: HKCR; Subkey: "archipelago"; ValueType: "string"; ValueData: "Archipegalo Protocol"; Flags: uninsdeletekey;
Root: HKCR; Subkey: "archipelago"; ValueType: "string"; ValueName: "URL Protocol"; ValueData: "";
Root: HKCR; Subkey: "archipelago\DefaultIcon"; ValueType: "string"; ValueData: "{app}\ArchipelagoTextClient.exe,0";
Root: HKCR; Subkey: "archipelago\shell\open\command"; ValueType: "string"; ValueData: """{app}\ArchipelagoTextClient.exe"" ""%1""";
Root: HKCR; Subkey: "archipelago\DefaultIcon"; ValueType: "string"; ValueData: "{app}\ArchipelagoLauncher.exe,0";
Root: HKCR; Subkey: "archipelago\shell\open\command"; ValueType: "string"; ValueData: """{app}\ArchipelagoLauncher.exe"" ""%1""";

[Code]
// See: https://stackoverflow.com/a/51614652/2287576
Expand Down
15 changes: 10 additions & 5 deletions worlds/LauncherComponents.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ class Component:
cli: bool
func: Optional[Callable]
file_identifier: Optional[Callable[[str], bool]]
game_name: Optional[str]
supports_uri: Optional[bool]

def __init__(self, display_name: str, script_name: Optional[str] = None, frozen_name: Optional[str] = None,
cli: bool = False, icon: str = 'icon', component_type: Optional[Type] = None,
func: Optional[Callable] = None, file_identifier: Optional[Callable[[str], bool]] = None):
func: Optional[Callable] = None, file_identifier: Optional[Callable[[str], bool]] = None,
game_name: Optional[str] = None, supports_uri: Optional[bool] = False):
self.display_name = display_name
self.script_name = script_name
self.frozen_name = frozen_name or f'Archipelago{script_name}' if script_name else None
Expand All @@ -45,6 +48,8 @@ def __init__(self, display_name: str, script_name: Optional[str] = None, frozen_
Type.ADJUSTER if "Adjuster" in display_name else Type.MISC)
self.func = func
self.file_identifier = file_identifier
self.game_name = game_name
self.supports_uri = supports_uri

def handles_file(self, path: str):
return self.file_identifier(path) if self.file_identifier else False
Expand All @@ -56,10 +61,10 @@ def __repr__(self):
processes = weakref.WeakSet()


def launch_subprocess(func: Callable, name: str = None):
def launch_subprocess(func: Callable, name: str = None, args: Tuple[str, ...] = ()) -> None:
global processes
import multiprocessing
process = multiprocessing.Process(target=func, name=name)
process = multiprocessing.Process(target=func, name=name, args=args)
process.start()
processes.add(process)

Expand All @@ -78,9 +83,9 @@ def __call__(self, path: str) -> bool:
return False


def launch_textclient():
def launch_textclient(*args):
import CommonClient
launch_subprocess(CommonClient.run_as_textclient, name="TextClient")
launch_subprocess(CommonClient.run_as_textclient, name="TextClient", args=args)


def _install_apworld(apworld_src: str = "") -> Optional[Tuple[pathlib.Path, pathlib.Path]]:
Expand Down
3 changes: 2 additions & 1 deletion worlds/messenger/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@
from .subclasses import MessengerEntrance, MessengerItem, MessengerRegion, MessengerShopLocation

components.append(
Component("The Messenger", component_type=Type.CLIENT, func=launch_game)#, game_name="The Messenger", supports_uri=True)
Component("The Messenger", component_type=Type.CLIENT, func=launch_game, game_name="The Messenger", supports_uri=True)
)


class MessengerSettings(Group):
class GamePath(FilePath):
description = "The Messenger game executable"
is_exe = True
md5s = ["1b53534569060bc06179356cd968ed1d"]

game_path: GamePath = GamePath("TheMessenger.exe")

Expand Down
Loading

0 comments on commit 22dcf8a

Please sign in to comment.