Skip to content

Commit 9c75b57

Browse files
authored
Merge branch 'ArchipelagoMW:main' into main
2 parents ec92831 + 064a7bf commit 9c75b57

46 files changed

Lines changed: 870 additions & 1076 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.

WebHostLib/templates/islandFooter.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{% block footer %}
22
<footer id="island-footer">
3-
<div id="copyright-notice">Copyright 2023 Archipelago</div>
3+
<div id="copyright-notice">Copyright 2024 Archipelago</div>
44
<div id="links">
55
<a href="/sitemap">Site Map</a>
66
-

WebHostLib/tracker.py

Lines changed: 52 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import datetime
2+
import collections
23
from dataclasses import dataclass
34
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
45
from uuid import UUID
@@ -8,7 +9,7 @@
89

910
from MultiServer import Context, get_saving_second
1011
from NetUtils import ClientStatus, Hint, NetworkItem, NetworkSlot, SlotType
11-
from Utils import restricted_loads
12+
from Utils import restricted_loads, KeyedDefaultDict
1213
from . import app, cache
1314
from .models import GameDataPackage, Room
1415

@@ -62,12 +63,18 @@ def __init__(self, room: Room):
6263
self.location_name_to_id: Dict[str, Dict[str, int]] = {}
6364

6465
# Generate inverse lookup tables from data package, useful for trackers.
65-
self.item_id_to_name: Dict[str, Dict[int, str]] = {}
66-
self.location_id_to_name: Dict[str, Dict[int, str]] = {}
66+
self.item_id_to_name: Dict[str, Dict[int, str]] = KeyedDefaultDict(lambda game_name: {
67+
game_name: KeyedDefaultDict(lambda code: f"Unknown Game {game_name} - Item (ID: {code})")
68+
})
69+
self.location_id_to_name: Dict[str, Dict[int, str]] = KeyedDefaultDict(lambda game_name: {
70+
game_name: KeyedDefaultDict(lambda code: f"Unknown Game {game_name} - Location (ID: {code})")
71+
})
6772
for game, game_package in self._multidata["datapackage"].items():
6873
game_package = restricted_loads(GameDataPackage.get(checksum=game_package["checksum"]).data)
69-
self.item_id_to_name[game] = {id: name for name, id in game_package["item_name_to_id"].items()}
70-
self.location_id_to_name[game] = {id: name for name, id in game_package["location_name_to_id"].items()}
74+
self.item_id_to_name[game] = KeyedDefaultDict(lambda code: f"Unknown Item (ID: {code})", {
75+
id: name for name, id in game_package["item_name_to_id"].items()})
76+
self.location_id_to_name[game] = KeyedDefaultDict(lambda code: f"Unknown Location (ID: {code})", {
77+
id: name for name, id in game_package["location_name_to_id"].items()})
7178

7279
# Normal lookup tables as well.
7380
self.item_name_to_id[game] = game_package["item_name_to_id"]
@@ -115,10 +122,10 @@ def get_player_received_items(self, team: int, player: int) -> List[NetworkItem]
115122
return self._multisave.get("received_items", {}).get((team, player, True), [])
116123

117124
@_cache_results
118-
def get_player_inventory_counts(self, team: int, player: int) -> Dict[int, int]:
125+
def get_player_inventory_counts(self, team: int, player: int) -> collections.Counter:
119126
"""Retrieves a dictionary of all items received by their id and their received count."""
120127
items = self.get_player_received_items(team, player)
121-
inventory = {item: 0 for item in self.item_id_to_name[self.get_player_game(team, player)]}
128+
inventory = collections.Counter()
122129
for item in items:
123130
inventory[item.item] += 1
124131

@@ -149,16 +156,15 @@ def get_team_completed_worlds_count(self) -> Dict[int, int]:
149156
"""Retrieves a dictionary of number of completed worlds per team."""
150157
return {
151158
team: sum(
152-
self.get_player_client_status(team, player) == ClientStatus.CLIENT_GOAL
153-
for player in players if self.get_slot_info(team, player).type == SlotType.player
154-
) for team, players in self.get_team_players().items()
159+
self.get_player_client_status(team, player) == ClientStatus.CLIENT_GOAL for player in players
160+
) for team, players in self.get_all_players().items()
155161
}
156162

157163
@_cache_results
158164
def get_team_hints(self) -> Dict[int, Set[Hint]]:
159165
"""Retrieves a dictionary of all hints per team."""
160166
hints = {}
161-
for team, players in self.get_team_players().items():
167+
for team, players in self.get_all_slots().items():
162168
hints[team] = set()
163169
for player in players:
164170
hints[team] |= self.get_player_hints(team, player)
@@ -170,24 +176,38 @@ def get_team_locations_total_count(self) -> Dict[int, int]:
170176
"""Retrieves a dictionary of total player locations each team has."""
171177
return {
172178
team: sum(len(self.get_player_locations(team, player)) for player in players)
173-
for team, players in self.get_team_players().items()
179+
for team, players in self.get_all_players().items()
174180
}
175181

176182
@_cache_results
177183
def get_team_locations_checked_count(self) -> Dict[int, int]:
178184
"""Retrieves a dictionary of checked player locations each team has."""
179185
return {
180186
team: sum(len(self.get_player_checked_locations(team, player)) for player in players)
181-
for team, players in self.get_team_players().items()
187+
for team, players in self.get_all_players().items()
182188
}
183189

184190
# TODO: Change this method to properly build for each team once teams are properly implemented, as they don't
185191
# currently exist in multidata to easily look up, so these are all assuming only 1 team: Team #0
186192
@_cache_results
187-
def get_team_players(self) -> Dict[int, List[int]]:
193+
def get_all_slots(self) -> Dict[int, List[int]]:
188194
"""Retrieves a dictionary of all players ids on each team."""
189195
return {
190-
0: [player for player, slot_info in self._multidata["slot_info"].items()]
196+
0: [
197+
player for player, slot_info in self._multidata["slot_info"].items()
198+
]
199+
}
200+
201+
# TODO: Change this method to properly build for each team once teams are properly implemented, as they don't
202+
# currently exist in multidata to easily look up, so these are all assuming only 1 team: Team #0
203+
@_cache_results
204+
def get_all_players(self) -> Dict[int, List[int]]:
205+
"""Retrieves a dictionary of all player slot-type players ids on each team."""
206+
return {
207+
0: [
208+
player for player, slot_info in self._multidata["slot_info"].items()
209+
if self.get_slot_info(0, player).type == SlotType.player
210+
]
191211
}
192212

193213
@_cache_results
@@ -203,38 +223,38 @@ def get_room_locations(self) -> Dict[TeamPlayer, Dict[int, ItemMetadata]]:
203223
"""Retrieves a dictionary of all locations and their associated item metadata per player."""
204224
return {
205225
(team, player): self.get_player_locations(team, player)
206-
for team, players in self.get_team_players().items() for player in players
226+
for team, players in self.get_all_players().items() for player in players
207227
}
208228

209229
@_cache_results
210230
def get_room_games(self) -> Dict[TeamPlayer, str]:
211231
"""Retrieves a dictionary of games for each player."""
212232
return {
213233
(team, player): self.get_player_game(team, player)
214-
for team, players in self.get_team_players().items() for player in players
234+
for team, players in self.get_all_slots().items() for player in players
215235
}
216236

217237
@_cache_results
218238
def get_room_locations_complete(self) -> Dict[TeamPlayer, int]:
219239
"""Retrieves a dictionary of all locations complete per player."""
220240
return {
221241
(team, player): len(self.get_player_checked_locations(team, player))
222-
for team, players in self.get_team_players().items() for player in players
242+
for team, players in self.get_all_players().items() for player in players
223243
}
224244

225245
@_cache_results
226246
def get_room_client_statuses(self) -> Dict[TeamPlayer, ClientStatus]:
227247
"""Retrieves a dictionary of all ClientStatus values per player."""
228248
return {
229249
(team, player): self.get_player_client_status(team, player)
230-
for team, players in self.get_team_players().items() for player in players
250+
for team, players in self.get_all_players().items() for player in players
231251
}
232252

233253
@_cache_results
234254
def get_room_long_player_names(self) -> Dict[TeamPlayer, str]:
235255
"""Retrieves a dictionary of names with aliases for each player."""
236256
long_player_names = {}
237-
for team, players in self.get_team_players().items():
257+
for team, players in self.get_all_slots().items():
238258
for player in players:
239259
alias = self.get_player_alias(team, player)
240260
if alias:
@@ -370,7 +390,8 @@ def render_generic_multiworld_tracker(tracker_data: TrackerData, enabled_tracker
370390
enabled_trackers=enabled_trackers,
371391
current_tracker="Generic",
372392
room=tracker_data.room,
373-
room_players=tracker_data.get_team_players(),
393+
all_slots=tracker_data.get_all_slots(),
394+
room_players=tracker_data.get_all_players(),
374395
locations=tracker_data.get_room_locations(),
375396
locations_complete=tracker_data.get_room_locations_complete(),
376397
total_team_locations=tracker_data.get_team_locations_total_count(),
@@ -389,7 +410,6 @@ def render_generic_multiworld_tracker(tracker_data: TrackerData, enabled_tracker
389410

390411
# TODO: This is a temporary solution until a proper Tracker API can be implemented for tracker templates and data to
391412
# live in their respective world folders.
392-
import collections
393413

394414
from worlds import network_data_package
395415

@@ -400,7 +420,7 @@ def render_Factorio_multiworld_tracker(tracker_data: TrackerData, enabled_tracke
400420
(team, player): {
401421
tracker_data.item_id_to_name["Factorio"][item_id]: count
402422
for item_id, count in tracker_data.get_player_inventory_counts(team, player).items()
403-
} for team, players in tracker_data.get_team_players().items() for player in players
423+
} for team, players in tracker_data.get_all_slots().items() for player in players
404424
if tracker_data.get_player_game(team, player) == "Factorio"
405425
}
406426

@@ -409,7 +429,8 @@ def render_Factorio_multiworld_tracker(tracker_data: TrackerData, enabled_tracke
409429
enabled_trackers=enabled_trackers,
410430
current_tracker="Factorio",
411431
room=tracker_data.room,
412-
room_players=tracker_data.get_team_players(),
432+
all_slots=tracker_data.get_all_slots(),
433+
room_players=tracker_data.get_all_players(),
413434
locations=tracker_data.get_room_locations(),
414435
locations_complete=tracker_data.get_room_locations_complete(),
415436
total_team_locations=tracker_data.get_team_locations_total_count(),
@@ -547,7 +568,7 @@ def render_ALinkToThePast_multiworld_tracker(tracker_data: TrackerData, enabled_
547568
if area_name != "Total" else tracker_data._multidata["checks_in_area"][player]["Total"]
548569
for area_name in ordered_areas
549570
}
550-
for team, players in tracker_data.get_team_players().items()
571+
for team, players in tracker_data.get_all_slots().items()
551572
for player in players
552573
if tracker_data.get_slot_info(team, player).type != SlotType.group and
553574
tracker_data.get_slot_info(team, player).game == "A Link to the Past"
@@ -585,23 +606,23 @@ def _get_location_table(checks_table: dict) -> dict:
585606

586607
player_location_to_area = {
587608
(team, player): _get_location_table(tracker_data._multidata["checks_in_area"][player])
588-
for team, players in tracker_data.get_team_players().items()
609+
for team, players in tracker_data.get_all_slots().items()
589610
for player in players
590611
if tracker_data.get_slot_info(team, player).type != SlotType.group and
591612
tracker_data.get_slot_info(team, player).game == "A Link to the Past"
592613
}
593614

594615
checks_done: Dict[TeamPlayer, Dict[str: int]] = {
595616
(team, player): {location_name: 0 for location_name in default_locations}
596-
for team, players in tracker_data.get_team_players().items()
617+
for team, players in tracker_data.get_all_slots().items()
597618
for player in players
598619
if tracker_data.get_slot_info(team, player).type != SlotType.group and
599620
tracker_data.get_slot_info(team, player).game == "A Link to the Past"
600621
}
601622

602623
inventories: Dict[TeamPlayer, Dict[int, int]] = {}
603-
player_big_key_locations = {(player): set() for player in tracker_data.get_team_players()[0]}
604-
player_small_key_locations = {player: set() for player in tracker_data.get_team_players()[0]}
624+
player_big_key_locations = {(player): set() for player in tracker_data.get_all_slots()[0]}
625+
player_small_key_locations = {player: set() for player in tracker_data.get_all_slots()[0]}
605626
group_big_key_locations = set()
606627
group_key_locations = set()
607628

@@ -639,7 +660,8 @@ def _get_location_table(checks_table: dict) -> dict:
639660
enabled_trackers=enabled_trackers,
640661
current_tracker="A Link to the Past",
641662
room=tracker_data.room,
642-
room_players=tracker_data.get_team_players(),
663+
all_slots=tracker_data.get_all_slots(),
664+
room_players=tracker_data.get_all_players(),
643665
locations=tracker_data.get_room_locations(),
644666
locations_complete=tracker_data.get_room_locations_complete(),
645667
total_team_locations=tracker_data.get_team_locations_total_count(),

WebHostLib/upload.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,30 @@
1111
from markupsafe import Markup
1212
from pony.orm import commit, flush, select, rollback
1313
from pony.orm.core import TransactionIntegrityError
14+
import schema
1415

1516
import MultiServer
1617
from NetUtils import SlotType
1718
from Utils import VersionException, __version__
19+
from worlds import GamesPackage
1820
from worlds.Files import AutoPatchRegister
21+
from worlds.AutoWorld import data_package_checksum
1922
from . import app
2023
from .models import Seed, Room, Slot, GameDataPackage
2124

2225
banned_extensions = (".sfc", ".z64", ".n64", ".nes", ".smc", ".sms", ".gb", ".gbc", ".gba")
2326
allowed_options_extensions = (".yaml", ".json", ".yml", ".txt", ".zip")
2427
allowed_generation_extensions = (".archipelago", ".zip")
2528

29+
games_package_schema = schema.Schema({
30+
"item_name_groups": {str: [str]},
31+
"item_name_to_id": {str: int},
32+
"location_name_groups": {str: [str]},
33+
"location_name_to_id": {str: int},
34+
schema.Optional("checksum"): str,
35+
schema.Optional("version"): int,
36+
})
37+
2638

2739
def allowed_options(filename: str) -> bool:
2840
return filename.endswith(allowed_options_extensions)
@@ -37,6 +49,8 @@ def banned_file(filename: str) -> bool:
3749

3850

3951
def process_multidata(compressed_multidata, files={}):
52+
game_data: GamesPackage
53+
4054
decompressed_multidata = MultiServer.Context.decompress(compressed_multidata)
4155

4256
slots: typing.Set[Slot] = set()
@@ -45,11 +59,19 @@ def process_multidata(compressed_multidata, files={}):
4559
game_data_packages: typing.List[GameDataPackage] = []
4660
for game, game_data in decompressed_multidata["datapackage"].items():
4761
if game_data.get("checksum"):
62+
original_checksum = game_data.pop("checksum")
63+
game_data = games_package_schema.validate(game_data)
64+
game_data = {key: value for key, value in sorted(game_data.items())}
65+
game_data["checksum"] = data_package_checksum(game_data)
4866
game_data_package = GameDataPackage(checksum=game_data["checksum"],
4967
data=pickle.dumps(game_data))
68+
if original_checksum != game_data["checksum"]:
69+
raise Exception(f"Original checksum {original_checksum} != "
70+
f"calculated checksum {game_data['checksum']} "
71+
f"for game {game}.")
5072
decompressed_multidata["datapackage"][game] = {
5173
"version": game_data.get("version", 0),
52-
"checksum": game_data["checksum"]
74+
"checksum": game_data["checksum"],
5375
}
5476
try:
5577
commit() # commit game data package
@@ -64,14 +86,15 @@ def process_multidata(compressed_multidata, files={}):
6486
if slot_info.type == SlotType.group:
6587
continue
6688
slots.add(Slot(data=files.get(slot, None),
67-
player_name=slot_info.name,
68-
player_id=slot,
69-
game=slot_info.game))
89+
player_name=slot_info.name,
90+
player_id=slot,
91+
game=slot_info.game))
7092
flush() # commit slots
7193

7294
compressed_multidata = compressed_multidata[0:1] + zlib.compress(pickle.dumps(decompressed_multidata), 9)
7395
return slots, compressed_multidata
7496

97+
7598
def upload_zip_to_db(zfile: zipfile.ZipFile, owner=None, meta={"race": False}, sid=None):
7699
if not owner:
77100
owner = session["_id"]

0 commit comments

Comments
 (0)