11import datetime
2+ import collections
23from dataclasses import dataclass
34from typing import Any , Callable , Dict , List , Optional , Set , Tuple
45from uuid import UUID
89
910from MultiServer import Context , get_saving_second
1011from NetUtils import ClientStatus , Hint , NetworkItem , NetworkSlot , SlotType
11- from Utils import restricted_loads
12+ from Utils import restricted_loads , KeyedDefaultDict
1213from . import app , cache
1314from .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
394414from 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 (),
0 commit comments