From 62b3fd4d377e488be7e02bed0f09df92384c893e Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Mon, 28 Aug 2023 17:18:13 +0200 Subject: [PATCH] WebHost: invert multitracker control back to webhost --- .../templates/multiTrackerNavigation.html | 2 +- WebHostLib/tracker.py | 237 ++++++++++-------- worlds/AutoWorld.py | 5 +- worlds/factorio/__init__.py | 30 +-- 4 files changed, 145 insertions(+), 129 deletions(-) diff --git a/WebHostLib/templates/multiTrackerNavigation.html b/WebHostLib/templates/multiTrackerNavigation.html index 7fc405b6fb..7eef9420e7 100644 --- a/WebHostLib/templates/multiTrackerNavigation.html +++ b/WebHostLib/templates/multiTrackerNavigation.html @@ -1,7 +1,7 @@ {%- if enabled_multiworld_trackers|length > 1 -%}
{% for enabled_tracker in enabled_multiworld_trackers %} - {% set tracker_url = url_for(enabled_tracker.endpoint, tracker=room.tracker) %} + {% set tracker_url = url_for(enabled_tracker.endpoint, tracker=room.tracker, game=enabled_tracker.name) %} {{ enabled_tracker.name }} {% endfor %} diff --git a/WebHostLib/tracker.py b/WebHostLib/tracker.py index ef6983f2ce..dba3299c55 100644 --- a/WebHostLib/tracker.py +++ b/WebHostLib/tracker.py @@ -1,17 +1,18 @@ import collections import datetime import typing +import pkgutil from typing import Counter, Optional, Dict, Any, Tuple, List from uuid import UUID from flask import render_template -from jinja2 import pass_context, runtime +from jinja2 import pass_context, runtime, Template from werkzeug.exceptions import abort from MultiServer import Context, get_saving_second from NetUtils import SlotType, NetworkSlot from Utils import restricted_loads -from worlds import lookup_any_item_id_to_name, lookup_any_location_id_to_name, network_data_package, games +from worlds import lookup_any_item_id_to_name, lookup_any_location_id_to_name, network_data_package, AutoWorldRegister from worlds.alttp import Items from . import app, cache from .models import GameDataPackage, Room @@ -1331,90 +1332,6 @@ def __renderGenericTracker(multisave: Dict[str, Any], room: Room, locations: Dic custom_items=custom_items, custom_locations=custom_locations) -def get_enabled_multiworld_trackers(room: Room, current: str): - enabled = [ - { - "name": "Generic", - "endpoint": "get_multiworld_tracker", - "current": current == "Generic" - } - ] - for game_name, endpoint in multi_trackers.items(): - if any(slot.game == game_name for slot in room.seed.slots) or current == game_name: - enabled.append({ - "name": game_name, - "endpoint": endpoint.__name__, - "current": current == game_name} - ) - return enabled - - -def _get_multiworld_tracker_data(tracker: UUID) -> typing.Optional[typing.Dict[str, typing.Any]]: - room: Room = Room.get(tracker=tracker) - if not room: - return None - - locations, names, use_door_tracker, checks_in_area, player_location_to_area, \ - precollected_items, games, slot_data, groups, saving_second, custom_locations, custom_items = \ - get_static_room_data(room) - - checks_done = {teamnumber: {playernumber: {loc_name: 0 for loc_name in default_locations} - for playernumber in range(1, len(team) + 1) if playernumber not in groups} - for teamnumber, team in enumerate(names)} - - percent_total_checks_done = {teamnumber: {playernumber: 0 - for playernumber in range(1, len(team) + 1) if playernumber not in groups} - for teamnumber, team in enumerate(names)} - - hints = {team: set() for team in range(len(names))} - if room.multisave: - multisave = restricted_loads(room.multisave) - else: - multisave = {} - if "hints" in multisave: - for (team, slot), slot_hints in multisave["hints"].items(): - hints[team] |= set(slot_hints) - - for (team, player), locations_checked in multisave.get("location_checks", {}).items(): - if player in groups: - continue - player_locations = locations[player] - checks_done[team][player]["Total"] = len(locations_checked) - percent_total_checks_done[team][player] = int(checks_done[team][player]["Total"] / - len(player_locations) * 100) \ - if player_locations else 100 - - activity_timers = {} - now = datetime.datetime.utcnow() - for (team, player), timestamp in multisave.get("client_activity_timers", []): - activity_timers[team, player] = now - datetime.datetime.utcfromtimestamp(timestamp) - - player_names = {} - states: typing.Dict[typing.Tuple[int, int], int] = {} - for team, names in enumerate(names): - for player, name in enumerate(names, 1): - player_names[team, player] = name - states[team, player] = multisave.get("client_game_state", {}).get((team, player), 0) - long_player_names = player_names.copy() - for (team, player), alias in multisave.get("name_aliases", {}).items(): - player_names[team, player] = alias - long_player_names[(team, player)] = f"{alias} ({long_player_names[team, player]})" - - video = {} - for (team, player), data in multisave.get("video", []): - video[team, player] = data - - return dict( - player_names=player_names, room=room, checks_done=checks_done, - percent_total_checks_done=percent_total_checks_done, checks_in_area=checks_in_area, - activity_timers=activity_timers, video=video, hints=hints, - long_player_names=long_player_names, - multisave=multisave, precollected_items=precollected_items, groups=groups, - locations=locations, games=games, states=states, - custom_locations=custom_locations, custom_items=custom_items, - ) - - def _get_inventory_data(data: typing.Dict[str, typing.Any]) -> typing.Dict[int, typing.Dict[int, int]]: inventory = {teamnumber: {playernumber: collections.Counter() for playernumber in team_data} for teamnumber, team_data in data["checks_done"].items()} @@ -1435,18 +1352,6 @@ def _get_inventory_data(data: typing.Dict[str, typing.Any]) -> typing.Dict[int, inventory[team][recipient][item_id] += 1 return inventory - -@app.route('/tracker/') -@cache.memoize(timeout=60) # multisave is currently created at most every minute -def get_multiworld_tracker(tracker: UUID): - data = _get_multiworld_tracker_data(tracker) - if not data: - abort(404) - - data["enabled_multiworld_trackers"] = get_enabled_multiworld_trackers(data["room"], "Generic") - - return render_template("multiTracker.html", **data) - @app.route('/tracker//A Link to the Past') @cache.memoize(timeout=60) # multisave is currently created at most every minute def get_LttP_multiworld_tracker(tracker: UUID): @@ -1572,6 +1477,142 @@ game_specific_trackers: typing.Dict[str, typing.Callable] = { "Starcraft 2 Wings of Liberty": __renderSC2WoLTracker } +# MultiTrackers + +@app.route('/tracker/') +@cache.memoize(timeout=60) # multisave is currently created at most every minute +def get_multiworld_tracker(tracker: UUID) -> str: + data = _get_multiworld_tracker_data(tracker) + if not data: + abort(404) + + data["enabled_multiworld_trackers"] = get_enabled_multiworld_trackers(data["room"], "Generic") + + return render_template("multiTracker.html", **data) + +def get_enabled_multiworld_trackers(room: Room, current: str) -> typing.List[typing.Dict[str, typing.Any]]: + enabled = [ + { + "name": "Generic", + "endpoint": "get_multiworld_tracker", + "current": current == "Generic" + } + ] + for game_name, endpoint in multi_trackers.items(): + if any(slot.game == game_name for slot in room.seed.slots) or current == game_name: + enabled.append({ + "name": game_name, + "endpoint": endpoint.__name__, + "current": current == game_name} + ) + return enabled + + +def _get_multiworld_tracker_data(tracker: UUID) -> typing.Optional[typing.Dict[str, typing.Any]]: + room: Room = Room.get(tracker=tracker) + if not room: + return None + + locations, names, use_door_tracker, checks_in_area, player_location_to_area, \ + precollected_items, games, slot_data, groups, saving_second, custom_locations, custom_items = \ + get_static_room_data(room) + + checks_done = {teamnumber: {playernumber: {loc_name: 0 for loc_name in default_locations} + for playernumber in range(1, len(team) + 1) if playernumber not in groups} + for teamnumber, team in enumerate(names)} + + percent_total_checks_done = {teamnumber: {playernumber: 0 + for playernumber in range(1, len(team) + 1) if playernumber not in groups} + for teamnumber, team in enumerate(names)} + + hints = {team: set() for team in range(len(names))} + if room.multisave: + multisave = restricted_loads(room.multisave) + else: + multisave = {} + if "hints" in multisave: + for (team, slot), slot_hints in multisave["hints"].items(): + hints[team] |= set(slot_hints) + + for (team, player), locations_checked in multisave.get("location_checks", {}).items(): + if player in groups: + continue + player_locations = locations[player] + checks_done[team][player]["Total"] = len(locations_checked) + percent_total_checks_done[team][player] = int(checks_done[team][player]["Total"] / + len(player_locations) * 100) \ + if player_locations else 100 + + activity_timers = {} + now = datetime.datetime.utcnow() + for (team, player), timestamp in multisave.get("client_activity_timers", []): + activity_timers[team, player] = now - datetime.datetime.utcfromtimestamp(timestamp) + + player_names = {} + states: typing.Dict[typing.Tuple[int, int], int] = {} + for team, names in enumerate(names): + for player, name in enumerate(names, 1): + player_names[team, player] = name + states[team, player] = multisave.get("client_game_state", {}).get((team, player), 0) + long_player_names = player_names.copy() + for (team, player), alias in multisave.get("name_aliases", {}).items(): + player_names[team, player] = alias + long_player_names[(team, player)] = f"{alias} ({long_player_names[team, player]})" + + video = {} + for (team, player), data in multisave.get("video", []): + video[team, player] = data + + return dict( + player_names=player_names, room=room, checks_done=checks_done, + percent_total_checks_done=percent_total_checks_done, checks_in_area=checks_in_area, + activity_timers=activity_timers, video=video, hints=hints, + long_player_names=long_player_names, + multisave=multisave, precollected_items=precollected_items, groups=groups, + locations=locations, games=games, states=states, + custom_locations=custom_locations, custom_items=custom_items, + ) + multi_trackers: typing.Dict[str, typing.Callable] = { "A Link to the Past": get_LttP_multiworld_tracker, } + +class MultiTrackerData(typing.NamedTuple): + template: Template + item_name_to_id: typing.Dict[str, int] + location_name_to_id: typing.Dict[str, int] + +multi_tracker_data: typing.Dict[str, MultiTrackerData] = {} + +@app.route("/tracker//") +@cache.memoize(timeout=60) # multisave is currently created up to every minute +def get_game_multiworld_tracker(tracker: UUID, game: str) -> str: + current_multi_tracker_data = multi_tracker_data.get(game, None) + if not current_multi_tracker_data: + abort(404) + data = _get_multiworld_tracker_data(tracker) + if not data: + abort(404) + + data["inventory"] = _get_inventory_data(data) + data["enabled_multiworld_trackers"] = get_enabled_multiworld_trackers(data["room"], game) + data["item_name_to_id"] = current_multi_tracker_data.item_name_to_id + data["location_name_to_id"] = current_multi_tracker_data.location_name_to_id + + return render_template(current_multi_tracker_data.template, **data) + +def register_multitrackers() -> None: + for world in AutoWorldRegister.world_types.values(): + multitracker = world.web.multitracker_template + if multitracker: + multitracker_template = pkgutil.get_data(world.__module__, multitracker).decode() + multitracker_template = app.jinja_env.from_string(multitracker_template) + + multi_trackers[world.game] = get_game_multiworld_tracker + multi_tracker_data[world.game] = MultiTrackerData( + multitracker_template, + world.item_name_to_id, + world.location_name_to_id, + ) + +register_multitrackers() diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index 99393417a4..2fcf0bc970 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -156,9 +156,12 @@ class WebWorld: """Choose a theme for you /game/* pages. Available: dirt, grass, grassFlowers, ice, jungle, ocean, partyTime, stone""" - bug_report_page: Optional[str] + bug_report_page: Optional[str] = None """display a link to a bug report page, most likely a link to a GitHub issue page.""" + multitracker_template: Optional[str] = None + """relative path with /-seperator to a MultiTracker Template file.""" + # allows modification of webhost during startup, this is run once @classmethod def run_webhost_setup(cls): diff --git a/worlds/factorio/__init__.py b/worlds/factorio/__init__.py index e348559098..e736f54d4a 100644 --- a/worlds/factorio/__init__.py +++ b/worlds/factorio/__init__.py @@ -61,36 +61,8 @@ class FactorioWeb(WebWorld): ["Berserker, Farrak Kilhn"] )] - @classmethod - def run_webhost_app_setup(cls, app): - from uuid import UUID - import pkgutil + multitracker_template = "data/web/templates/MultiTracker.html" - from werkzeug.exceptions import abort - from flask import render_template - - from WebHostLib import cache - from WebHostLib.tracker import (_get_multiworld_tracker_data, _get_inventory_data, - get_enabled_multiworld_trackers, multi_trackers) - - - multitracker_template = pkgutil.get_data(__name__, "data/web/templates/MultiTracker.html").decode() - multitracker_template = app.jinja_env.from_string(multitracker_template) - - @app.route('/tracker//Factorio') - @cache.memoize(timeout=60) # multisave is currently created up to every minute - def get_Factorio_multiworld_tracker(tracker: UUID): - data = _get_multiworld_tracker_data(tracker) - if not data: - abort(404) - - data["inventory"] = _get_inventory_data(data) - data["enabled_multiworld_trackers"] = get_enabled_multiworld_trackers(data["room"], "Factorio") - data["item_name_to_id"] = Factorio.item_name_to_id - - return render_template(multitracker_template, **data) - - multi_trackers[Factorio.game] = get_Factorio_multiworld_tracker class FactorioItem(Item): game = "Factorio"