Skip to content

Commit 9be0920

Browse files
authored
Merge branch 'ArchipelagoMW:main' into CVLoD
2 parents 7f1885d + 74f41e3 commit 9be0920

440 files changed

Lines changed: 24349 additions & 8713 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
# This workflow will build a release-like distribution when manually dispatched
1+
# This workflow will build a release-like distribution when manually dispatched:
2+
# a Windows x64 7zip, a Windows x64 Installer, a Linux AppImage and a Linux binary .tar.gz.
23

34
name: Build
45

@@ -50,7 +51,7 @@ jobs:
5051
run: |
5152
Invoke-WebRequest -Uri https://github.com/Ijwu/Enemizer/releases/download/${Env:ENEMIZER_VERSION}/win-x64.zip -OutFile enemizer.zip
5253
Expand-Archive -Path enemizer.zip -DestinationPath EnemizerCLI -Force
53-
choco install innosetup --version=6.2.2 --allow-downgrade
54+
choco install innosetup --version=6.7.0 --allow-downgrade
5455
- name: Build
5556
run: |
5657
python -m pip install --upgrade pip

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ EnemizerCLI/
4545
/SNI/
4646
/sni-*/
4747
/appimagetool*
48+
/VC_redist.x64.exe
4849
/host.yaml
4950
/options.yaml
5051
/config.yaml

BaseClasses.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
if TYPE_CHECKING:
2424
from entrance_rando import ERPlacementState
25+
from rule_builder.rules import Rule
2526
from worlds import AutoWorld
2627

2728

@@ -726,6 +727,7 @@ class CollectionState():
726727
advancements: Set[Location]
727728
path: Dict[Union[Region, Entrance], PathValue]
728729
locations_checked: Set[Location]
730+
"""Internal cache for Advancement Locations already checked by this CollectionState. Not for use in logic."""
729731
stale: Dict[int, bool]
730732
allow_partial_entrances: bool
731733
additional_init_functions: List[Callable[[CollectionState, MultiWorld], None]] = []
@@ -787,9 +789,11 @@ def _update_reachable_regions_explicit_indirect_conditions(self, player: int, qu
787789
self.multiworld.worlds[player].reached_region(self, new_region)
788790

789791
# Retry connections if the new region can unblock them
790-
for new_entrance in self.multiworld.indirect_connections.get(new_region, set()):
791-
if new_entrance in blocked_connections and new_entrance not in queue:
792-
queue.append(new_entrance)
792+
entrances = self.multiworld.indirect_connections.get(new_region)
793+
if entrances is not None:
794+
relevant_entrances = entrances.intersection(blocked_connections)
795+
relevant_entrances.difference_update(queue)
796+
queue.extend(relevant_entrances)
793797

794798
def _update_reachable_regions_auto_indirect_conditions(self, player: int, queue: deque[Entrance]):
795799
reachable_regions = self.reachable_regions[player]
@@ -1368,7 +1372,7 @@ def add_event(
13681372
self,
13691373
location_name: str,
13701374
item_name: str | None = None,
1371-
rule: CollectionRule | None = None,
1375+
rule: CollectionRule | Rule[Any] | None = None,
13721376
location_type: type[Location] | None = None,
13731377
item_type: type[Item] | None = None,
13741378
show_in_spoiler: bool = True,
@@ -1396,7 +1400,7 @@ def add_event(
13961400
event_location = location_type(self.player, location_name, None, self)
13971401
event_location.show_in_spoiler = show_in_spoiler
13981402
if rule is not None:
1399-
event_location.access_rule = rule
1403+
self.multiworld.worlds[self.player].set_rule(event_location, rule)
14001404

14011405
event_item = item_type(item_name, ItemClassification.progression, None, self.player)
14021406

@@ -1407,16 +1411,16 @@ def add_event(
14071411
return event_item
14081412

14091413
def connect(self, connecting_region: Region, name: Optional[str] = None,
1410-
rule: Optional[CollectionRule] = None) -> Entrance:
1414+
rule: Optional[CollectionRule | Rule[Any]] = None) -> Entrance:
14111415
"""
14121416
Connects this Region to another Region, placing the provided rule on the connection.
14131417
14141418
:param connecting_region: Region object to connect to path is `self -> exiting_region`
14151419
:param name: name of the connection being created
14161420
:param rule: callable to determine access of this connection to go from self to the exiting_region"""
14171421
exit_ = self.create_exit(name if name else f"{self.name} -> {connecting_region.name}")
1418-
if rule:
1419-
exit_.access_rule = rule
1422+
if rule is not None:
1423+
self.multiworld.worlds[self.player].set_rule(exit_, rule)
14201424
exit_.connect(connecting_region)
14211425
return exit_
14221426

@@ -1441,7 +1445,7 @@ def create_er_target(self, name: str) -> Entrance:
14411445
return entrance
14421446

14431447
def add_exits(self, exits: Iterable[str] | Mapping[str, str | None],
1444-
rules: Mapping[str, CollectionRule] | None = None) -> List[Entrance]:
1448+
rules: Mapping[str, CollectionRule | Rule[Any]] | None = None) -> List[Entrance]:
14451449
"""
14461450
Connects current region to regions in exit dictionary. Passed region names must exist first.
14471451

CommonClient.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from MultiServer import CommandProcessor, mark_raw
2525
from NetUtils import (Endpoint, decode, NetworkItem, encode, JSONtoTextParser, ClientStatus, Permission, NetworkSlot,
2626
RawJSONtoTextParser, add_json_text, add_json_location, add_json_item, JSONTypes, HintStatus, SlotType)
27-
from Utils import Version, stream_input, async_start
27+
from Utils import gui_enabled, Version, stream_input, async_start
2828
from worlds import network_data_package, AutoWorldRegister
2929
import os
3030
import ssl
@@ -35,9 +35,6 @@
3535

3636
logger = logging.getLogger("Client")
3737

38-
# without terminal, we have to use gui mode
39-
gui_enabled = not sys.stdout or "--nogui" not in sys.argv
40-
4138

4239
@Utils.cache_argsless
4340
def get_ssl_context():
@@ -65,6 +62,8 @@ def output(self, text: str):
6562

6663
def _cmd_exit(self) -> bool:
6764
"""Close connections and client"""
65+
if self.ctx.ui:
66+
self.ctx.ui.stop()
6867
self.ctx.exit_event.set()
6968
return True
7069

@@ -774,7 +773,7 @@ def gui_error(self, title: str, text: typing.Union[Exception, str]) -> typing.Op
774773
if len(parts) == 1:
775774
parts = title.split(', ', 1)
776775
if len(parts) > 1:
777-
text = parts[1] + '\n\n' + text
776+
text = f"{parts[1]}\n\n{text}" if text else parts[1]
778777
title = parts[0]
779778
# display error
780779
self._messagebox = MessageBox(title, text, error=True)
@@ -897,6 +896,8 @@ def reconnect_hint() -> str:
897896
"May not be running Archipelago on that address or port.")
898897
except websockets.InvalidURI:
899898
ctx.handle_connection_loss("Failed to connect to the multiworld server (invalid URI)")
899+
except asyncio.TimeoutError:
900+
ctx.handle_connection_loss("Failed to connect to the multiworld server. Connection timed out.")
900901
except OSError:
901902
ctx.handle_connection_loss("Failed to connect to the multiworld server")
902903
except Exception:

Fill.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ def location_can_fill_item(location_to_fill: Location, item_to_fill: Item):
280280
item_to_place = itempool.pop()
281281
spot_to_fill: typing.Optional[Location] = None
282282

283+
# going through locations in the same order as the provided `locations` argument
283284
for i, location in enumerate(locations):
284285
if location_can_fill_item(location, item_to_place):
285286
# popping by index is faster than removing by content,

Generate.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
8787

8888
seed = get_seed(args.seed)
8989

90-
Utils.init_logging(f"Generate_{seed}", loglevel=args.log_level, add_timestamp=args.log_time)
90+
if __name__ == "__main__":
91+
Utils.init_logging(f"Generate_{seed}", loglevel=args.log_level, add_timestamp=args.log_time)
9192
random.seed(seed)
9293
seed_name = get_seed_name(random)
9394

Launcher.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
import Utils
3232
from Utils import (init_logging, is_frozen, is_linux, is_macos, is_windows, local_path, messagebox, open_filename,
3333
user_path)
34+
35+
if __name__ == "__main__":
36+
init_logging('Launcher')
37+
3438
from worlds.LauncherComponents import Component, components, icon_paths, SuffixIdentifier, Type
3539

3640

@@ -493,7 +497,6 @@ def main(args: argparse.Namespace | dict | None = None):
493497

494498

495499
if __name__ == '__main__':
496-
init_logging('Launcher')
497500
multiprocessing.freeze_support()
498501
multiprocessing.set_start_method("spawn") # if launched process uses kivy, fork won't work
499502
parser = argparse.ArgumentParser(

Main.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,9 @@ def main(args, seed=None, baked_server_options: dict[str, object] | None = None)
207207
else:
208208
logger.info("Progression balancing skipped.")
209209

210+
AutoWorld.call_all(multiworld, "finalize_multiworld")
211+
AutoWorld.call_all(multiworld, "pre_output")
212+
210213
# we're about to output using multithreading, so we're removing the global random state to prevent accidental use
211214
multiworld.random.passthrough = False
212215

MultiServer.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import typing
2222
import weakref
2323
import zlib
24+
from signal import SIGINT, SIGTERM, signal
2425

2526
import ModuleUpdate
2627

@@ -496,7 +497,8 @@ def _load(self, decoded_obj: MultiData, game_data_packages: typing.Dict[str, typ
496497

497498
self.read_data = {}
498499
# there might be a better place to put this.
499-
self.read_data["race_mode"] = lambda: decoded_obj.get("race_mode", 0)
500+
race_mode = decoded_obj.get("race_mode", 0)
501+
self.read_data["race_mode"] = lambda: race_mode
500502
mdata_ver = decoded_obj["minimum_versions"]["server"]
501503
if mdata_ver > version_tuple:
502504
raise RuntimeError(f"Supplied Multidata (.archipelago) requires a server of at least version {mdata_ver}, "
@@ -1301,6 +1303,13 @@ def __new__(cls, name, bases, attrs):
13011303
commands.update(base.commands)
13021304
commands.update({command_name[5:]: method for command_name, method in attrs.items() if
13031305
command_name.startswith("_cmd_")})
1306+
for command_name, method in commands.items():
1307+
# wrap async def functions so they run on default asyncio loop
1308+
if inspect.iscoroutinefunction(method):
1309+
def _wrapper(self, *args, _method=method, **kwargs):
1310+
return async_start(_method(self, *args, **kwargs))
1311+
functools.update_wrapper(_wrapper, method)
1312+
commands[command_name] = _wrapper
13041313
return super(CommandMeta, cls).__new__(cls, name, bases, attrs)
13051314

13061315

@@ -2563,6 +2572,8 @@ async def console(ctx: Context):
25632572
input_text = await queue.get()
25642573
queue.task_done()
25652574
ctx.commandprocessor(input_text)
2575+
except asyncio.exceptions.CancelledError:
2576+
ctx.logger.info("ConsoleTask cancelled")
25662577
except:
25672578
import traceback
25682579
traceback.print_exc()
@@ -2729,6 +2740,26 @@ async def main(args: argparse.Namespace):
27292740
console_task = asyncio.create_task(console(ctx))
27302741
if ctx.auto_shutdown:
27312742
ctx.shutdown_task = asyncio.create_task(auto_shutdown(ctx, [console_task]))
2743+
2744+
def stop():
2745+
try:
2746+
for remove_signal in [SIGINT, SIGTERM]:
2747+
asyncio.get_event_loop().remove_signal_handler(remove_signal)
2748+
except NotImplementedError:
2749+
pass
2750+
ctx.commandprocessor._cmd_exit()
2751+
2752+
def shutdown(signum, frame):
2753+
stop()
2754+
2755+
try:
2756+
for sig in [SIGINT, SIGTERM]:
2757+
asyncio.get_event_loop().add_signal_handler(sig, stop)
2758+
except NotImplementedError:
2759+
# add_signal_handler is only implemented for UNIX platforms
2760+
for sig in [SIGINT, SIGTERM]:
2761+
signal(sig, shutdown)
2762+
27322763
await ctx.exit_event.wait()
27332764
console_task.cancel()
27342765
if ctx.shutdown_task:

0 commit comments

Comments
 (0)