Skip to content

Commit ea7eca0

Browse files
Merge branch 'ArchipelagoMW:main' into main
2 parents f34b87c + d65fcf2 commit ea7eca0

51 files changed

Lines changed: 965 additions & 502 deletions

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: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ env:
2424
# NOTE: since appimage/appimagetool and appimage/type2-runtime does not have tags anymore,
2525
# we check the sha256 and require manual intervention if it was updated.
2626
APPIMAGE_FORK: 'PopTracker'
27-
APPIMAGETOOL_VERSION: 'r-2025-10-19'
28-
APPIMAGETOOL_X86_64_HASH: '9493a6b253a01f84acb9c624c38810ecfa11d99daa829b952b0bff43113080f9'
29-
APPIMAGE_RUNTIME_VERSION: 'r-2025-08-11'
30-
APPIMAGE_RUNTIME_X86_64_HASH: 'e70ffa9b69b211574d0917adc482dd66f25a0083427b5945783965d55b0b0a8b'
27+
APPIMAGETOOL_VERSION: 'r-2025-11-18'
28+
APPIMAGETOOL_X86_64_HASH: '4577a452b30af2337123fbb383aea154b618e51ad5448c3b62085cbbbfbfd9a2'
29+
APPIMAGE_RUNTIME_VERSION: 'r-2025-11-07'
30+
APPIMAGE_RUNTIME_X86_64_HASH: '27ddd3f78e483fc5f7856e413d7c17092917f8c35bfe3318a0d378aa9435ad17'
3131

3232
permissions: # permissions required for attestation
3333
id-token: 'write'

.github/workflows/release.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ env:
1212
# NOTE: since appimage/appimagetool and appimage/type2-runtime does not have tags anymore,
1313
# we check the sha256 and require manual intervention if it was updated.
1414
APPIMAGE_FORK: 'PopTracker'
15-
APPIMAGETOOL_VERSION: 'r-2025-10-19'
16-
APPIMAGETOOL_X86_64_HASH: '9493a6b253a01f84acb9c624c38810ecfa11d99daa829b952b0bff43113080f9'
17-
APPIMAGE_RUNTIME_VERSION: 'r-2025-08-11'
18-
APPIMAGE_RUNTIME_X86_64_HASH: 'e70ffa9b69b211574d0917adc482dd66f25a0083427b5945783965d55b0b0a8b'
15+
APPIMAGETOOL_VERSION: 'r-2025-11-18'
16+
APPIMAGETOOL_X86_64_HASH: '4577a452b30af2337123fbb383aea154b618e51ad5448c3b62085cbbbfbfd9a2'
17+
APPIMAGE_RUNTIME_VERSION: 'r-2025-11-07'
18+
APPIMAGE_RUNTIME_X86_64_HASH: '27ddd3f78e483fc5f7856e413d7c17092917f8c35bfe3318a0d378aa9435ad17'
1919

2020
permissions: # permissions required for attestation
2121
id-token: 'write'

Generate.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,9 @@ def update_weights(weights: dict, new_weights: dict, update_type: str, name: str
347347
elif isinstance(new_value, list):
348348
cleaned_value.extend(new_value)
349349
elif isinstance(new_value, dict):
350-
cleaned_value = dict(Counter(cleaned_value) + Counter(new_value))
350+
counter_value = Counter(cleaned_value)
351+
counter_value.update(new_value)
352+
cleaned_value = dict(counter_value)
351353
else:
352354
raise Exception(f"Cannot apply merge to non-dict, set, or list type {option_name},"
353355
f" received {type(new_value).__name__}.")
@@ -361,7 +363,9 @@ def update_weights(weights: dict, new_weights: dict, update_type: str, name: str
361363
for element in new_value:
362364
cleaned_value.remove(element)
363365
elif isinstance(new_value, dict):
364-
cleaned_value = dict(Counter(cleaned_value) - Counter(new_value))
366+
counter_value = Counter(cleaned_value)
367+
counter_value.subtract(new_value)
368+
cleaned_value = dict(counter_value)
365369
else:
366370
raise Exception(f"Cannot apply remove to non-dict, set, or list type {option_name},"
367371
f" received {type(new_value).__name__}.")

MultiServer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ def _load(self, decoded_obj: MultiData, game_data_packages: typing.Dict[str, typ
493493
self.read_data["race_mode"] = lambda: decoded_obj.get("race_mode", 0)
494494
mdata_ver = decoded_obj["minimum_versions"]["server"]
495495
if mdata_ver > version_tuple:
496-
raise RuntimeError(f"Supplied Multidata (.archipelago) requires a server of at least version {mdata_ver},"
496+
raise RuntimeError(f"Supplied Multidata (.archipelago) requires a server of at least version {mdata_ver}, "
497497
f"however this server is of version {version_tuple}")
498498
self.generator_version = Version(*decoded_obj["version"])
499499
clients_ver = decoded_obj["minimum_versions"].get("clients", {})

Options.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1545,6 +1545,7 @@ class PlandoItems(Option[typing.List[PlandoItem]]):
15451545
default = ()
15461546
supports_weighting = False
15471547
display_name = "Plando Items"
1548+
visibility = Visibility.template | Visibility.spoiler
15481549

15491550
def __init__(self, value: typing.Iterable[PlandoItem]) -> None:
15501551
self.value = list(deepcopy(value))

OptionsCreator.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
if __name__ == "__main__":
2+
import ModuleUpdate
3+
4+
ModuleUpdate.update()
5+
6+
17
from kvui import (ThemedApp, ScrollBox, MainLayout, ContainerLayout, dp, Widget, MDBoxLayout, TooltipLabel, MDLabel,
28
ToggleButton, MarkupDropdown, ResizableTextField)
39
from kivy.uix.behaviors.button import ButtonBehavior
@@ -330,6 +336,11 @@ def open_dropdown(button):
330336
box.range.slider.dropdown.open()
331337

332338
box = VisualNamedRange(option=option, name=name, range_widget=self.create_range(option, name))
339+
if option.default in option.special_range_names:
340+
# value can get mismatched in this case
341+
box.range.slider.value = min(max(option.special_range_names[option.default], option.range_start),
342+
option.range_end)
343+
box.range.tag.text = str(int(box.range.slider.value))
333344
box.range.slider.bind(on_touch_move=lambda _, _2: set_to_custom(box))
334345
items = [
335346
{
@@ -365,7 +376,7 @@ def open_dropdown(button):
365376
# for some reason this fixes an issue causing some to not open
366377
dropdown.open()
367378

368-
default_random = option.default == "random"
379+
default_string = isinstance(option.default, str)
369380
main_button = VisualChoice(option=option, name=name)
370381
main_button.bind(on_release=open_dropdown)
371382

@@ -377,7 +388,7 @@ def open_dropdown(button):
377388
for choice in option.name_lookup
378389
]
379390
dropdown = MDDropdownMenu(caller=main_button, items=items)
380-
self.options[name] = option.name_lookup[option.default] if not default_random else option.default
391+
self.options[name] = option.name_lookup[option.default] if not default_string else option.default
381392
return main_button
382393

383394
def create_text_choice(self, option: typing.Type[TextChoice], name: str):
@@ -560,8 +571,11 @@ def create_options_panel(self, world_button: WorldButton):
560571
groups[group].append((name, option))
561572

562573
for group, options in groups.items():
574+
options = [(name, option) for name, option in options
575+
if name and option.visibility & Visibility.simple_ui]
563576
if not options:
564577
continue # Game Options can be empty if every other option is in another group
578+
# Can also have an option group of options that should not render on simple ui
565579
group_item = MDExpansionPanel(size_hint_y=None)
566580
group_header = MDExpansionPanelHeader(MDListItem(MDListItemSupportingText(text=group),
567581
TrailingPressedIconButton(icon="chevron-right",
@@ -583,8 +597,7 @@ def create_options_panel(self, world_button: WorldButton):
583597
group_box.layout.orientation = "vertical"
584598
group_box.layout.spacing = dp(3)
585599
for name, option in options:
586-
if name and option is not Removed and option.visibility & Visibility.simple_ui:
587-
group_content.add_widget(self.create_option(option, name, cls))
600+
group_content.add_widget(self.create_option(option, name, cls))
588601
expansion_box.layout.add_widget(group_item)
589602
self.option_layout.add_widget(expansion_box)
590603
self.game_label.text = f"Game: {self.current_game}"

WebHostLib/api/tracker.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ class PlayerLocationsTotal(TypedDict):
5858
total_locations: int
5959

6060

61+
class PlayerGame(TypedDict):
62+
team: int
63+
player: int
64+
game: str
65+
66+
6167
@api_endpoints.route("/tracker/<suuid:tracker>")
6268
@cache.memoize(timeout=60)
6369
def tracker_data(tracker: UUID) -> dict[str, Any]:
@@ -80,7 +86,8 @@ def tracker_data(tracker: UUID) -> dict[str, Any]:
8086
"""Slot aliases of all players."""
8187
for team, players in all_players.items():
8288
for player in players:
83-
player_aliases.append({"team": team, "player": player, "alias": tracker_data.get_player_alias(team, player)})
89+
player_aliases.append(
90+
{"team": team, "player": player, "alias": tracker_data.get_player_alias(team, player)})
8491

8592
player_items_received: list[PlayerItemsReceived] = []
8693
"""Items received by each player."""
@@ -94,7 +101,8 @@ def tracker_data(tracker: UUID) -> dict[str, Any]:
94101
for team, players in all_players.items():
95102
for player in players:
96103
player_checks_done.append(
97-
{"team": team, "player": player, "locations": sorted(tracker_data.get_player_checked_locations(team, player))})
104+
{"team": team, "player": player,
105+
"locations": sorted(tracker_data.get_player_checked_locations(team, player))})
98106

99107
total_checks_done: list[TeamTotalChecks] = [
100108
{"team": team, "checks_done": checks_done}
@@ -144,7 +152,8 @@ def tracker_data(tracker: UUID) -> dict[str, Any]:
144152
"""The current client status for each player."""
145153
for team, players in all_players.items():
146154
for player in players:
147-
player_status.append({"team": team, "player": player, "status": tracker_data.get_player_client_status(team, player)})
155+
player_status.append(
156+
{"team": team, "player": player, "status": tracker_data.get_player_client_status(team, player)})
148157

149158
return {
150159
"aliases": player_aliases,
@@ -207,12 +216,20 @@ def static_tracker_data(tracker: UUID) -> dict[str, Any]:
207216
player_locations_total.append(
208217
{"team": team, "player": player, "total_locations": len(tracker_data.get_player_locations(player))})
209218

219+
player_game: list[PlayerGame] = []
220+
"""The played game per player slot."""
221+
for team, players in all_players.items():
222+
for player in players:
223+
player_game.append({"team": team, "player": player, "game": tracker_data.get_player_game(player)})
224+
210225
return {
211226
"groups": groups,
212227
"datapackage": tracker_data._multidata["datapackage"],
213228
"player_locations_total": player_locations_total,
229+
"player_game": player_game,
214230
}
215231

232+
216233
# It should be exceedingly rare that slot data is needed, so it's separated out.
217234
@api_endpoints.route("/slot_data_tracker/<suuid:tracker>")
218235
@cache.memoize(timeout=300)

WebHostLib/static/assets/faq/en.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ players to rely upon each other to complete their game.
2323
While a multiworld game traditionally requires all players to be playing the same game, a multi-game multiworld allows
2424
players to randomize any of the supported games, and send items between them. This allows players of different
2525
games to interact with one another in a single multiplayer environment. Archipelago supports multi-game multiworlds.
26-
Here is a list of our [Supported Games](https://archipelago.gg/games).
26+
Here is a list of our [Supported Games](/games).
2727

2828
## Can I generate a single-player game with Archipelago?
2929

@@ -33,7 +33,7 @@ play, open the Settings Page, pick your settings, and click Generate Game.
3333

3434
## How do I get started?
3535

36-
We have a [Getting Started](https://archipelago.gg/tutorial/Archipelago/setup/en) guide that will help you get the
36+
We have a [Getting Started](/tutorial/Archipelago/setup/en) guide that will help you get the
3737
software set up. You can use that guide to learn how to generate multiworlds. There are also basic instructions for
3838
including multiple games, and hosting multiworlds on the website for ease and convenience.
3939

@@ -57,7 +57,7 @@ their multiworld.
5757

5858
If a player must leave early, they can use Archipelago's release system. When a player releases their game, all items
5959
in that game belonging to other players are sent out automatically. This allows other players to continue to play
60-
uninterrupted. Here is a list of all of our [Server Commands](https://archipelago.gg/tutorial/Archipelago/commands/en).
60+
uninterrupted. Here is a list of all of our [Server Commands](/tutorial/Archipelago/commands/en).
6161

6262
## What happens if an item is placed somewhere it is impossible to get?
6363

WebHostLib/tracker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -959,7 +959,7 @@ def render_Timespinner_tracker(tracker_data: TrackerData, team: int, player: int
959959

960960
timespinner_location_ids = {
961961
"Present": list(range(1337000, 1337085)),
962-
"Past": list(range(1337086, 1337175)),
962+
"Past": list(range(1337086, 1337157)) + list(range(1337159, 1337175)),
963963
"Ancient Pyramid": [
964964
1337236,
965965
1337246, 1337247, 1337248, 1337249]

Zelda1Client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ async def nes_sync_task(ctx: ZeldaContext):
289289
if not ctx.auth:
290290
ctx.auth = ''.join([chr(i) for i in data_decoded['playerName'] if i != 0])
291291
if ctx.auth == '':
292-
logger.info("Invalid ROM detected. No player name built into the ROM. Please regenerate"
292+
logger.info("Invalid ROM detected. No player name built into the ROM. Please regenerate "
293293
"the ROM using the same link but adding your slot name")
294294
if ctx.awaiting_rom:
295295
await ctx.server_auth(False)

0 commit comments

Comments
 (0)