1
1
import datetime
2
+ import collections
2
3
from dataclasses import dataclass
3
4
from typing import Any , Callable , Dict , List , Optional , Set , Tuple
4
5
from uuid import UUID
8
9
9
10
from MultiServer import Context , get_saving_second
10
11
from NetUtils import ClientStatus , Hint , NetworkItem , NetworkSlot , SlotType
11
- from Utils import restricted_loads
12
+ from Utils import restricted_loads , KeyedDefaultDict
12
13
from . import app , cache
13
14
from .models import GameDataPackage , Room
14
15
@@ -62,12 +63,18 @@ def __init__(self, room: Room):
62
63
self .location_name_to_id : Dict [str , Dict [str , int ]] = {}
63
64
64
65
# 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
+ })
67
72
for game , game_package in self ._multidata ["datapackage" ].items ():
68
73
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 ()})
71
78
72
79
# Normal lookup tables as well.
73
80
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]
115
122
return self ._multisave .get ("received_items" , {}).get ((team , player , True ), [])
116
123
117
124
@_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 :
119
126
"""Retrieves a dictionary of all items received by their id and their received count."""
120
127
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 ()
122
129
for item in items :
123
130
inventory [item .item ] += 1
124
131
@@ -149,16 +156,15 @@ def get_team_completed_worlds_count(self) -> Dict[int, int]:
149
156
"""Retrieves a dictionary of number of completed worlds per team."""
150
157
return {
151
158
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 ()
155
161
}
156
162
157
163
@_cache_results
158
164
def get_team_hints (self ) -> Dict [int , Set [Hint ]]:
159
165
"""Retrieves a dictionary of all hints per team."""
160
166
hints = {}
161
- for team , players in self .get_team_players ().items ():
167
+ for team , players in self .get_all_slots ().items ():
162
168
hints [team ] = set ()
163
169
for player in players :
164
170
hints [team ] |= self .get_player_hints (team , player )
@@ -170,24 +176,38 @@ def get_team_locations_total_count(self) -> Dict[int, int]:
170
176
"""Retrieves a dictionary of total player locations each team has."""
171
177
return {
172
178
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 ()
174
180
}
175
181
176
182
@_cache_results
177
183
def get_team_locations_checked_count (self ) -> Dict [int , int ]:
178
184
"""Retrieves a dictionary of checked player locations each team has."""
179
185
return {
180
186
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 ()
182
188
}
183
189
184
190
# TODO: Change this method to properly build for each team once teams are properly implemented, as they don't
185
191
# currently exist in multidata to easily look up, so these are all assuming only 1 team: Team #0
186
192
@_cache_results
187
- def get_team_players (self ) -> Dict [int , List [int ]]:
193
+ def get_all_slots (self ) -> Dict [int , List [int ]]:
188
194
"""Retrieves a dictionary of all players ids on each team."""
189
195
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
+ ]
191
211
}
192
212
193
213
@_cache_results
@@ -203,38 +223,38 @@ def get_room_locations(self) -> Dict[TeamPlayer, Dict[int, ItemMetadata]]:
203
223
"""Retrieves a dictionary of all locations and their associated item metadata per player."""
204
224
return {
205
225
(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
207
227
}
208
228
209
229
@_cache_results
210
230
def get_room_games (self ) -> Dict [TeamPlayer , str ]:
211
231
"""Retrieves a dictionary of games for each player."""
212
232
return {
213
233
(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
215
235
}
216
236
217
237
@_cache_results
218
238
def get_room_locations_complete (self ) -> Dict [TeamPlayer , int ]:
219
239
"""Retrieves a dictionary of all locations complete per player."""
220
240
return {
221
241
(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
223
243
}
224
244
225
245
@_cache_results
226
246
def get_room_client_statuses (self ) -> Dict [TeamPlayer , ClientStatus ]:
227
247
"""Retrieves a dictionary of all ClientStatus values per player."""
228
248
return {
229
249
(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
231
251
}
232
252
233
253
@_cache_results
234
254
def get_room_long_player_names (self ) -> Dict [TeamPlayer , str ]:
235
255
"""Retrieves a dictionary of names with aliases for each player."""
236
256
long_player_names = {}
237
- for team , players in self .get_team_players ().items ():
257
+ for team , players in self .get_all_slots ().items ():
238
258
for player in players :
239
259
alias = self .get_player_alias (team , player )
240
260
if alias :
@@ -370,7 +390,8 @@ def render_generic_multiworld_tracker(tracker_data: TrackerData, enabled_tracker
370
390
enabled_trackers = enabled_trackers ,
371
391
current_tracker = "Generic" ,
372
392
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 (),
374
395
locations = tracker_data .get_room_locations (),
375
396
locations_complete = tracker_data .get_room_locations_complete (),
376
397
total_team_locations = tracker_data .get_team_locations_total_count (),
@@ -389,7 +410,6 @@ def render_generic_multiworld_tracker(tracker_data: TrackerData, enabled_tracker
389
410
390
411
# TODO: This is a temporary solution until a proper Tracker API can be implemented for tracker templates and data to
391
412
# live in their respective world folders.
392
- import collections
393
413
394
414
from worlds import network_data_package
395
415
@@ -400,7 +420,7 @@ def render_Factorio_multiworld_tracker(tracker_data: TrackerData, enabled_tracke
400
420
(team , player ): {
401
421
tracker_data .item_id_to_name ["Factorio" ][item_id ]: count
402
422
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
404
424
if tracker_data .get_player_game (team , player ) == "Factorio"
405
425
}
406
426
@@ -409,7 +429,8 @@ def render_Factorio_multiworld_tracker(tracker_data: TrackerData, enabled_tracke
409
429
enabled_trackers = enabled_trackers ,
410
430
current_tracker = "Factorio" ,
411
431
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 (),
413
434
locations = tracker_data .get_room_locations (),
414
435
locations_complete = tracker_data .get_room_locations_complete (),
415
436
total_team_locations = tracker_data .get_team_locations_total_count (),
@@ -547,7 +568,7 @@ def render_ALinkToThePast_multiworld_tracker(tracker_data: TrackerData, enabled_
547
568
if area_name != "Total" else tracker_data ._multidata ["checks_in_area" ][player ]["Total" ]
548
569
for area_name in ordered_areas
549
570
}
550
- for team , players in tracker_data .get_team_players ().items ()
571
+ for team , players in tracker_data .get_all_slots ().items ()
551
572
for player in players
552
573
if tracker_data .get_slot_info (team , player ).type != SlotType .group and
553
574
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:
585
606
586
607
player_location_to_area = {
587
608
(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 ()
589
610
for player in players
590
611
if tracker_data .get_slot_info (team , player ).type != SlotType .group and
591
612
tracker_data .get_slot_info (team , player ).game == "A Link to the Past"
592
613
}
593
614
594
615
checks_done : Dict [TeamPlayer , Dict [str : int ]] = {
595
616
(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 ()
597
618
for player in players
598
619
if tracker_data .get_slot_info (team , player ).type != SlotType .group and
599
620
tracker_data .get_slot_info (team , player ).game == "A Link to the Past"
600
621
}
601
622
602
623
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 ]}
605
626
group_big_key_locations = set ()
606
627
group_key_locations = set ()
607
628
@@ -639,7 +660,8 @@ def _get_location_table(checks_table: dict) -> dict:
639
660
enabled_trackers = enabled_trackers ,
640
661
current_tracker = "A Link to the Past" ,
641
662
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 (),
643
665
locations = tracker_data .get_room_locations (),
644
666
locations_complete = tracker_data .get_room_locations_complete (),
645
667
total_team_locations = tracker_data .get_team_locations_total_count (),
0 commit comments