WebHost: Fix 500 Server errors relating to player/multi trackers. (#2664)

* WebHost: Fix player tracker issue with items missing from data package.

 Reported in https://discord.com/channels/731205301247803413/1192202112172576819

* WebHost: Fix multi-tracker error when item links are present.

 Reported in https://discord.com/channels/731205301247803413/1192104719959724062

* Use Utils.KeyedDefaultDict instead of checking for key

* formatted revert

* import tweak
This commit is contained in:
Zach Parks 2024-01-04 08:29:42 -06:00 committed by GitHub
parent 7406a1e512
commit c593a960f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 52 additions and 30 deletions

View File

@ -1,4 +1,5 @@
import datetime import datetime
import collections
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Callable, Dict, List, Optional, Set, Tuple from typing import Any, Callable, Dict, List, Optional, Set, Tuple
from uuid import UUID from uuid import UUID
@ -8,7 +9,7 @@ from werkzeug.exceptions import abort
from MultiServer import Context, get_saving_second from MultiServer import Context, get_saving_second
from NetUtils import ClientStatus, Hint, NetworkItem, NetworkSlot, SlotType from NetUtils import ClientStatus, Hint, NetworkItem, NetworkSlot, SlotType
from Utils import restricted_loads from Utils import restricted_loads, KeyedDefaultDict
from . import app, cache from . import app, cache
from .models import GameDataPackage, Room from .models import GameDataPackage, Room
@ -62,12 +63,18 @@ class TrackerData:
self.location_name_to_id: Dict[str, Dict[str, int]] = {} self.location_name_to_id: Dict[str, Dict[str, int]] = {}
# Generate inverse lookup tables from data package, useful for trackers. # Generate inverse lookup tables from data package, useful for trackers.
self.item_id_to_name: Dict[str, Dict[int, str]] = {} self.item_id_to_name: Dict[str, Dict[int, str]] = KeyedDefaultDict(lambda game_name: {
self.location_id_to_name: Dict[str, Dict[int, str]] = {} game_name: KeyedDefaultDict(lambda code: f"Unknown Game {game_name} - Item (ID: {code})")
})
self.location_id_to_name: Dict[str, Dict[int, str]] = KeyedDefaultDict(lambda game_name: {
game_name: KeyedDefaultDict(lambda code: f"Unknown Game {game_name} - Location (ID: {code})")
})
for game, game_package in self._multidata["datapackage"].items(): for game, game_package in self._multidata["datapackage"].items():
game_package = restricted_loads(GameDataPackage.get(checksum=game_package["checksum"]).data) game_package = restricted_loads(GameDataPackage.get(checksum=game_package["checksum"]).data)
self.item_id_to_name[game] = {id: name for name, id in game_package["item_name_to_id"].items()} self.item_id_to_name[game] = KeyedDefaultDict(lambda code: f"Unknown Item (ID: {code})", {
self.location_id_to_name[game] = {id: name for name, id in game_package["location_name_to_id"].items()} id: name for name, id in game_package["item_name_to_id"].items()})
self.location_id_to_name[game] = KeyedDefaultDict(lambda code: f"Unknown Location (ID: {code})", {
id: name for name, id in game_package["location_name_to_id"].items()})
# Normal lookup tables as well. # Normal lookup tables as well.
self.item_name_to_id[game] = game_package["item_name_to_id"] self.item_name_to_id[game] = game_package["item_name_to_id"]
@ -115,10 +122,10 @@ class TrackerData:
return self._multisave.get("received_items", {}).get((team, player, True), []) return self._multisave.get("received_items", {}).get((team, player, True), [])
@_cache_results @_cache_results
def get_player_inventory_counts(self, team: int, player: int) -> Dict[int, int]: def get_player_inventory_counts(self, team: int, player: int) -> collections.Counter:
"""Retrieves a dictionary of all items received by their id and their received count.""" """Retrieves a dictionary of all items received by their id and their received count."""
items = self.get_player_received_items(team, player) items = self.get_player_received_items(team, player)
inventory = {item: 0 for item in self.item_id_to_name[self.get_player_game(team, player)]} inventory = collections.Counter()
for item in items: for item in items:
inventory[item.item] += 1 inventory[item.item] += 1
@ -149,16 +156,15 @@ class TrackerData:
"""Retrieves a dictionary of number of completed worlds per team.""" """Retrieves a dictionary of number of completed worlds per team."""
return { return {
team: sum( team: sum(
self.get_player_client_status(team, player) == ClientStatus.CLIENT_GOAL self.get_player_client_status(team, player) == ClientStatus.CLIENT_GOAL for player in players
for player in players if self.get_slot_info(team, player).type == SlotType.player ) for team, players in self.get_all_players().items()
) for team, players in self.get_team_players().items()
} }
@_cache_results @_cache_results
def get_team_hints(self) -> Dict[int, Set[Hint]]: def get_team_hints(self) -> Dict[int, Set[Hint]]:
"""Retrieves a dictionary of all hints per team.""" """Retrieves a dictionary of all hints per team."""
hints = {} hints = {}
for team, players in self.get_team_players().items(): for team, players in self.get_all_slots().items():
hints[team] = set() hints[team] = set()
for player in players: for player in players:
hints[team] |= self.get_player_hints(team, player) hints[team] |= self.get_player_hints(team, player)
@ -170,7 +176,7 @@ class TrackerData:
"""Retrieves a dictionary of total player locations each team has.""" """Retrieves a dictionary of total player locations each team has."""
return { return {
team: sum(len(self.get_player_locations(team, player)) for player in players) team: sum(len(self.get_player_locations(team, player)) for player in players)
for team, players in self.get_team_players().items() for team, players in self.get_all_players().items()
} }
@_cache_results @_cache_results
@ -178,16 +184,30 @@ class TrackerData:
"""Retrieves a dictionary of checked player locations each team has.""" """Retrieves a dictionary of checked player locations each team has."""
return { return {
team: sum(len(self.get_player_checked_locations(team, player)) for player in players) team: sum(len(self.get_player_checked_locations(team, player)) for player in players)
for team, players in self.get_team_players().items() for team, players in self.get_all_players().items()
} }
# TODO: Change this method to properly build for each team once teams are properly implemented, as they don't # TODO: Change this method to properly build for each team once teams are properly implemented, as they don't
# currently exist in multidata to easily look up, so these are all assuming only 1 team: Team #0 # currently exist in multidata to easily look up, so these are all assuming only 1 team: Team #0
@_cache_results @_cache_results
def get_team_players(self) -> Dict[int, List[int]]: def get_all_slots(self) -> Dict[int, List[int]]:
"""Retrieves a dictionary of all players ids on each team.""" """Retrieves a dictionary of all players ids on each team."""
return { return {
0: [player for player, slot_info in self._multidata["slot_info"].items()] 0: [
player for player, slot_info in self._multidata["slot_info"].items()
]
}
# TODO: Change this method to properly build for each team once teams are properly implemented, as they don't
# currently exist in multidata to easily look up, so these are all assuming only 1 team: Team #0
@_cache_results
def get_all_players(self) -> Dict[int, List[int]]:
"""Retrieves a dictionary of all player slot-type players ids on each team."""
return {
0: [
player for player, slot_info in self._multidata["slot_info"].items()
if self.get_slot_info(0, player).type == SlotType.player
]
} }
@_cache_results @_cache_results
@ -203,7 +223,7 @@ class TrackerData:
"""Retrieves a dictionary of all locations and their associated item metadata per player.""" """Retrieves a dictionary of all locations and their associated item metadata per player."""
return { return {
(team, player): self.get_player_locations(team, player) (team, player): self.get_player_locations(team, player)
for team, players in self.get_team_players().items() for player in players for team, players in self.get_all_players().items() for player in players
} }
@_cache_results @_cache_results
@ -211,7 +231,7 @@ class TrackerData:
"""Retrieves a dictionary of games for each player.""" """Retrieves a dictionary of games for each player."""
return { return {
(team, player): self.get_player_game(team, player) (team, player): self.get_player_game(team, player)
for team, players in self.get_team_players().items() for player in players for team, players in self.get_all_slots().items() for player in players
} }
@_cache_results @_cache_results
@ -219,7 +239,7 @@ class TrackerData:
"""Retrieves a dictionary of all locations complete per player.""" """Retrieves a dictionary of all locations complete per player."""
return { return {
(team, player): len(self.get_player_checked_locations(team, player)) (team, player): len(self.get_player_checked_locations(team, player))
for team, players in self.get_team_players().items() for player in players for team, players in self.get_all_players().items() for player in players
} }
@_cache_results @_cache_results
@ -227,14 +247,14 @@ class TrackerData:
"""Retrieves a dictionary of all ClientStatus values per player.""" """Retrieves a dictionary of all ClientStatus values per player."""
return { return {
(team, player): self.get_player_client_status(team, player) (team, player): self.get_player_client_status(team, player)
for team, players in self.get_team_players().items() for player in players for team, players in self.get_all_players().items() for player in players
} }
@_cache_results @_cache_results
def get_room_long_player_names(self) -> Dict[TeamPlayer, str]: def get_room_long_player_names(self) -> Dict[TeamPlayer, str]:
"""Retrieves a dictionary of names with aliases for each player.""" """Retrieves a dictionary of names with aliases for each player."""
long_player_names = {} long_player_names = {}
for team, players in self.get_team_players().items(): for team, players in self.get_all_slots().items():
for player in players: for player in players:
alias = self.get_player_alias(team, player) alias = self.get_player_alias(team, player)
if alias: if alias:
@ -370,7 +390,8 @@ def render_generic_multiworld_tracker(tracker_data: TrackerData, enabled_tracker
enabled_trackers=enabled_trackers, enabled_trackers=enabled_trackers,
current_tracker="Generic", current_tracker="Generic",
room=tracker_data.room, room=tracker_data.room,
room_players=tracker_data.get_team_players(), all_slots=tracker_data.get_all_slots(),
room_players=tracker_data.get_all_players(),
locations=tracker_data.get_room_locations(), locations=tracker_data.get_room_locations(),
locations_complete=tracker_data.get_room_locations_complete(), locations_complete=tracker_data.get_room_locations_complete(),
total_team_locations=tracker_data.get_team_locations_total_count(), total_team_locations=tracker_data.get_team_locations_total_count(),
@ -389,7 +410,6 @@ def render_generic_multiworld_tracker(tracker_data: TrackerData, enabled_tracker
# TODO: This is a temporary solution until a proper Tracker API can be implemented for tracker templates and data to # TODO: This is a temporary solution until a proper Tracker API can be implemented for tracker templates and data to
# live in their respective world folders. # live in their respective world folders.
import collections
from worlds import network_data_package from worlds import network_data_package
@ -400,7 +420,7 @@ if "Factorio" in network_data_package["games"]:
(team, player): { (team, player): {
tracker_data.item_id_to_name["Factorio"][item_id]: count tracker_data.item_id_to_name["Factorio"][item_id]: count
for item_id, count in tracker_data.get_player_inventory_counts(team, player).items() for item_id, count in tracker_data.get_player_inventory_counts(team, player).items()
} for team, players in tracker_data.get_team_players().items() for player in players } for team, players in tracker_data.get_all_slots().items() for player in players
if tracker_data.get_player_game(team, player) == "Factorio" if tracker_data.get_player_game(team, player) == "Factorio"
} }
@ -409,7 +429,8 @@ if "Factorio" in network_data_package["games"]:
enabled_trackers=enabled_trackers, enabled_trackers=enabled_trackers,
current_tracker="Factorio", current_tracker="Factorio",
room=tracker_data.room, room=tracker_data.room,
room_players=tracker_data.get_team_players(), all_slots=tracker_data.get_all_slots(),
room_players=tracker_data.get_all_players(),
locations=tracker_data.get_room_locations(), locations=tracker_data.get_room_locations(),
locations_complete=tracker_data.get_room_locations_complete(), locations_complete=tracker_data.get_room_locations_complete(),
total_team_locations=tracker_data.get_team_locations_total_count(), total_team_locations=tracker_data.get_team_locations_total_count(),
@ -547,7 +568,7 @@ if "A Link to the Past" in network_data_package["games"]:
if area_name != "Total" else tracker_data._multidata["checks_in_area"][player]["Total"] if area_name != "Total" else tracker_data._multidata["checks_in_area"][player]["Total"]
for area_name in ordered_areas for area_name in ordered_areas
} }
for team, players in tracker_data.get_team_players().items() for team, players in tracker_data.get_all_slots().items()
for player in players for player in players
if tracker_data.get_slot_info(team, player).type != SlotType.group and if tracker_data.get_slot_info(team, player).type != SlotType.group and
tracker_data.get_slot_info(team, player).game == "A Link to the Past" tracker_data.get_slot_info(team, player).game == "A Link to the Past"
@ -585,7 +606,7 @@ if "A Link to the Past" in network_data_package["games"]:
player_location_to_area = { player_location_to_area = {
(team, player): _get_location_table(tracker_data._multidata["checks_in_area"][player]) (team, player): _get_location_table(tracker_data._multidata["checks_in_area"][player])
for team, players in tracker_data.get_team_players().items() for team, players in tracker_data.get_all_slots().items()
for player in players for player in players
if tracker_data.get_slot_info(team, player).type != SlotType.group and if tracker_data.get_slot_info(team, player).type != SlotType.group and
tracker_data.get_slot_info(team, player).game == "A Link to the Past" tracker_data.get_slot_info(team, player).game == "A Link to the Past"
@ -593,15 +614,15 @@ if "A Link to the Past" in network_data_package["games"]:
checks_done: Dict[TeamPlayer, Dict[str: int]] = { checks_done: Dict[TeamPlayer, Dict[str: int]] = {
(team, player): {location_name: 0 for location_name in default_locations} (team, player): {location_name: 0 for location_name in default_locations}
for team, players in tracker_data.get_team_players().items() for team, players in tracker_data.get_all_slots().items()
for player in players for player in players
if tracker_data.get_slot_info(team, player).type != SlotType.group and if tracker_data.get_slot_info(team, player).type != SlotType.group and
tracker_data.get_slot_info(team, player).game == "A Link to the Past" tracker_data.get_slot_info(team, player).game == "A Link to the Past"
} }
inventories: Dict[TeamPlayer, Dict[int, int]] = {} inventories: Dict[TeamPlayer, Dict[int, int]] = {}
player_big_key_locations = {(player): set() for player in tracker_data.get_team_players()[0]} player_big_key_locations = {(player): set() for player in tracker_data.get_all_slots()[0]}
player_small_key_locations = {player: set() for player in tracker_data.get_team_players()[0]} player_small_key_locations = {player: set() for player in tracker_data.get_all_slots()[0]}
group_big_key_locations = set() group_big_key_locations = set()
group_key_locations = set() group_key_locations = set()
@ -639,7 +660,8 @@ if "A Link to the Past" in network_data_package["games"]:
enabled_trackers=enabled_trackers, enabled_trackers=enabled_trackers,
current_tracker="A Link to the Past", current_tracker="A Link to the Past",
room=tracker_data.room, room=tracker_data.room,
room_players=tracker_data.get_team_players(), all_slots=tracker_data.get_all_slots(),
room_players=tracker_data.get_all_players(),
locations=tracker_data.get_room_locations(), locations=tracker_data.get_room_locations(),
locations_complete=tracker_data.get_room_locations_complete(), locations_complete=tracker_data.get_room_locations_complete(),
total_team_locations=tracker_data.get_team_locations_total_count(), total_team_locations=tracker_data.get_team_locations_total_count(),