diff --git a/WebHostLib/static/styles/tracker__Celeste64.css b/WebHostLib/static/styles/tracker__Celeste64.css new file mode 100644 index 000000000000..8901adbc9f9c --- /dev/null +++ b/WebHostLib/static/styles/tracker__Celeste64.css @@ -0,0 +1,143 @@ +@import url('https://fonts.googleapis.com/css2?family=Lexend+Deca:wght@100..900&display=swap'); + +.tracker-container { + width: 440px; + box-sizing: border-box; + font-family: "Lexend Deca", Arial, Helvetica, sans-serif; + border-radius: 4px; + resize: both; + + color: white; +} + +/** Inventory Grid ****************************************************************************************************/ +.inventory-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + padding: 1rem; + gap: 1rem; + + margin-left: 0.5rem; + box-shadow: 2px 2px 4px rgba(0, 0, 0); +} + +.inventory-grid .item { + position: relative; + display: flex; + justify-content: center; + height: 48px; +} + +.inventory-grid .dual-item { + display: flex; + justify-content: center; +} + +.inventory-grid .missing { + /* Missing items will be in full grayscale to signify "uncollected". */ + filter: grayscale(100%) contrast(75%) brightness(75%); +} + +.inventory-grid .item img, +.inventory-grid .dual-item img { + display: flex; + align-items: center; + text-align: center; + font-size: 0.8rem; + text-shadow: 0 1px 2px black; + font-weight: bold; + image-rendering: crisp-edges; + background-size: contain; + background-repeat: no-repeat; +} + +.inventory-grid .dual-item img { + height: 48px; + margin: 0 -4px; +} + +.inventory-grid .dual-item img:first-child { + align-self: flex-end; +} + +.inventory-grid .item .quantity { + position: absolute; + bottom: 0; + right: 0; + text-align: right; + font-weight: 600; + font-size: 1.75rem; + line-height: 1.75rem; + text-shadow: + -1px -1px 0 #000, + 1px -1px 0 #000, + -1px 1px 0 #000, + 1px 1px 0 #000; + user-select: none; +} + +/** Regions List ******************************************************************************************************/ +.regions-list { + padding: 1rem; + + margin-top: 1rem; + margin-left: 0.5rem; + box-shadow: 2px 2px 4px rgba(0, 0, 0); +} + +.regions-list summary { + list-style: none; + display: flex; + gap: 0.5rem; + cursor: pointer; +} + +.regions-list summary::before { + content: "⯈"; + width: 1em; + flex-shrink: 0; +} + +.regions-list details { + font-weight: 300; +} + +.regions-list details[open] > summary::before { + content: "⯆"; +} + +.regions-list .region { + width: 100%; + display: grid; + grid-template-columns: 20fr 8fr 2fr 2fr; + align-items: center; + gap: 4px; + text-align: center; + font-weight: 300; + box-sizing: border-box; +} + +.regions-list .region :first-child { + text-align: left; + font-weight: 500; +} + +.regions-list .region.region-header { + margin-left: 24px; + width: calc(100% - 24px); + padding: 2px; +} + +.regions-list .location-rows { + border-top: 1px solid white; + display: grid; + grid-template-columns: auto 32px; + font-weight: 300; + padding: 2px 8px; + margin-top: 4px; + font-size: 0.8rem; +} + +.regions-list .location-rows :nth-child(even) { + text-align: right; +} diff --git a/WebHostLib/templates/multitracker__Celeste64.html b/WebHostLib/templates/multitracker__Celeste64.html new file mode 100644 index 000000000000..5da7793b9036 --- /dev/null +++ b/WebHostLib/templates/multitracker__Celeste64.html @@ -0,0 +1,91 @@ +{% extends "multitracker.html" %} +{% block head %} + {{ super() }} + +{% endblock %} + +{# Original icons located at https://github.com/PoryGone/Celeste-64-AP-Tracker/tree/main/images #} +{# List all tracker-relevant icons. Format: (Name, Image URL) #} +{% set icons = { + "Strawberry" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/strawberry.png?raw=true", + + "Air Dash" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/AirDash.png?raw=true", + "Breakable Blocks" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/Breakables_Filled.PNG?raw=true", + "Cassettes" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/Cassettes_Filled.PNG?raw=true", + "Climb" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/Climb.png?raw=true", + "Coins" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/Coins_Filled.PNG?raw=true", + "Dash Refills" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/Dash_Filled.png?raw=true", + "Double Dash Refills" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/DoubleDash_Filled.png?raw=true", + "Feathers" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/Feather_Filled.png?raw=true", + "Ground Dash" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/GroundDash.png?raw=true", + "Skid Jump" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/SkidJump.png?raw=true", + "Springs" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/Springs_Filled.png?raw=true", + "Traffic Blocks" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/Traffic_Filled.png?raw=true", + + "IsCompleted" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/Baddy.png?raw=true" +} %} + +{% set inventory_order = [ + "Strawberry", + + "Climb", + "Skid Jump", + "Air Dash", + "Ground Dash", + + "Cassettes", + "Dash Refills", + "Double Dash Refills", + "Feathers", + + "Coins", + "Breakable Blocks", + "Springs", + "Traffic Blocks", + + "IsCompleted" +] %} + +{% set multi_items = [ + "Strawberry" +] %} + +{# Most have a duplicated 0th entry for when we have none of that item to still load the correct icon/name. #} +{% set progressive_order = { + +} %} + +{%- block custom_table_headers %} + {#- macro that creates a table header with display name and image -#} + {%- macro make_header(name, img_src) %} + + {{ name }} + + {% endmacro -%} + + {#- call the macro to build the table header -#} + {%- for item in inventory_order %} + {%- if item in icons -%} + + {{ item | e }} + + {%- endif %} + {% endfor -%} +{% endblock %} + +{# build each row of custom entries #} +{% block custom_table_row scoped %} + {%- for item in inventory_order -%} + {%- if inventories[(team, player)][item] -%} + + {% if item in multi_items %} + {{ inventories[(team, player)][item] }} + {% else %} + ✔️ + {% endif %} + + {%- else -%} + + {%- endif -%} + {% endfor %} +{% endblock %} \ No newline at end of file diff --git a/WebHostLib/templates/tracker__Celeste64.html b/WebHostLib/templates/tracker__Celeste64.html new file mode 100644 index 000000000000..7e819ef65503 --- /dev/null +++ b/WebHostLib/templates/tracker__Celeste64.html @@ -0,0 +1,170 @@ +{# Original icons located at https://github.com/PoryGone/Celeste-64-AP-Tracker/tree/main/images #} +{% set icons = { + "Strawberry" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/strawberry.png?raw=true", + + "Air Dash" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/AirDash.png?raw=true", + "Breakable Blocks" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/Breakables_Filled.PNG?raw=true", + "Cassettes" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/Cassettes_Filled.PNG?raw=true", + "Climb" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/Climb.png?raw=true", + "Coins" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/Coins_Filled.PNG?raw=true", + "Dash Refills" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/Dash_Filled.png?raw=true", + "Double Dash Refills" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/DoubleDash_Filled.png?raw=true", + "Feathers" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/Feather_Filled.png?raw=true", + "Ground Dash" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/GroundDash.png?raw=true", + "Skid Jump" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/SkidJump.png?raw=true", + "Springs" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/Springs_Filled.png?raw=true", + "Traffic Blocks" : "https://github.com/mrkssr/ap-icons/blob/main/Celeste%2064%20Tracker/items/Traffic_Filled.png?raw=true" +} %} + +{% set inventory_order = [ + "Strawberry", + "Empty", + "Empty", + "Empty", + + "Climb", + "Skid Jump", + "Air Dash", + "Ground Dash", + + "Cassettes", + "Dash Refills", + "Double Dash Refills", + "Feathers", + + "Coins", + "Breakable Blocks", + "Springs", + "Traffic Blocks" +] %} + +{% set multi_items = [ + "Strawberry" +] %} + +{# Most have a duplicated 0th entry for when we have none of that item to still load the correct icon/name. #} +{% set progressive_order = { + +} %} + + + + + + + {{ player_name }}'s Tracker + + + + + + + + +
+
+ {{ player_name }}'s Tracker +
+
+ +
+
+ 🡸 Switch To Generic Tracker +
+
+ +
+ {# Inventory Grid #} +
+ {% for item in inventory_order %} + {% if item in progressive_order %} + {% set non_prog_item = progressive_order[item][inventory[item]] %} +
+ {{ non_prog_item }} +
+ {% else %} +
+ {% if item != "Empty" %} + {{ item }} + + {% if item in multi_items %} +
{{ inventory[item] }}
+ {% endif %} + {% endif %} +
+ {% endif %} + {% endfor %} +
+ +
+ {% for region_name in known_regions %} + {% set region_data = regions[region_name] %} + {% if region_data["locations"] | length > 0 %} +
+ +
+ {{ region_name }} + {{ region_data["checked"] }} / {{ region_data["locations"] | length }} + + {% if region_data["checked"] == (region_data["locations"] | length) %} + ✔ + {% else %} + — + {% endif %} + +
+
+ +
+ {% for location, checked in region_data["locations"] %} +
{{ location }}
+
{% if checked %}✔{% endif %}
+ {% endfor %} +
+
+ {% endif %} + {% endfor %} +
+
+ + + + diff --git a/WebHostLib/tracker.py b/WebHostLib/tracker.py index 75b5fb0202d9..68cff0d1a892 100644 --- a/WebHostLib/tracker.py +++ b/WebHostLib/tracker.py @@ -2459,3 +2459,181 @@ def render_Starcraft2_tracker(tracker_data: TrackerData, team: int, player: int) ) _player_trackers["Starcraft 2"] = render_Starcraft2_tracker + +if "Celeste 64" in network_data_package["games"]: + # Mapping from non-progressive item to progressive name and max level. + non_progressive_items = { + + } + + progressive_item_max = { + + } + + REGION_OVERWORLD = "Overworld" + REGION_CASSETTES = "Cassettes" + REGION_FRIENDS = "Friends" + REGION_SIGNS = "Signs" + REGION_CARS = "Cars" + + known_regions = [REGION_OVERWORLD, REGION_CASSETTES, REGION_FRIENDS, REGION_SIGNS, REGION_CARS] + + locations_table = { + 13238272: REGION_OVERWORLD, + 13238273: REGION_OVERWORLD, + 13238274: REGION_OVERWORLD, + 13238275: REGION_OVERWORLD, + 13238276: REGION_OVERWORLD, + 13238277: REGION_OVERWORLD, + 13238278: REGION_OVERWORLD, + 13238279: REGION_OVERWORLD, + 13238280: REGION_OVERWORLD, + 13238281: REGION_OVERWORLD, + 13238282: REGION_OVERWORLD, + 13238283: REGION_OVERWORLD, + 13238284: REGION_OVERWORLD, + 13238285: REGION_OVERWORLD, + 13238286: REGION_OVERWORLD, + 13238287: REGION_OVERWORLD, + 13238288: REGION_OVERWORLD, + 13238289: REGION_OVERWORLD, + 13238290: REGION_OVERWORLD, + 13238291: REGION_OVERWORLD, + + 13238292: REGION_CASSETTES, + 13238293: REGION_CASSETTES, + 13238294: REGION_CASSETTES, + 13238295: REGION_CASSETTES, + 13238296: REGION_CASSETTES, + 13238297: REGION_CASSETTES, + 13238298: REGION_CASSETTES, + 13238299: REGION_CASSETTES, + 13238300: REGION_CASSETTES, + 13238301: REGION_CASSETTES, + + 13238531: REGION_FRIENDS, + 13238532: REGION_FRIENDS, + 13238533: REGION_FRIENDS, + 13238534: REGION_FRIENDS, + 13238535: REGION_FRIENDS, + 13238536: REGION_FRIENDS, + 13238528: REGION_FRIENDS, + 13238529: REGION_FRIENDS, + 13238530: REGION_FRIENDS, + + 13238786: REGION_SIGNS, + 13238787: REGION_SIGNS, + 13238788: REGION_SIGNS, + 13238784: REGION_SIGNS, + 13238785: REGION_SIGNS, + + 13239040: REGION_CARS, + 13239041: REGION_CARS + } + + def prepare_inventories(team: int, player: int, inventory: Counter[str], tracker_data: TrackerData): + # These settings were added in v1.2. + # That this tracker also work for generated seeds on earlier versions is a check if the option is even available in the slot data. + if not ("move_shuffle" in tracker_data.get_slot_data(team, player) and bool(tracker_data.get_slot_data(team, player)["move_shuffle"])): + # If moves are not shuffled add these abilities to the inventory to avoid extra handling for the collected items. + inventory["Climb"] = 1 + inventory["Skid Jump"] = 1 + inventory["Air Dash"] = 1 + inventory["Ground Dash"] = 1 + + for item, (prog_item, level) in non_progressive_items.items(): + if item in inventory: + inventory[prog_item] = min(max(inventory[prog_item], level), progressive_item_max[prog_item]) + + # Completed item if we meet goal. + if tracker_data.get_room_client_statuses()[team, player] == ClientStatus.CLIENT_GOAL: + inventory["IsCompleted"] = 1 + + def render_Celeste64_multiworld_tracker(tracker_data: TrackerData, enabled_trackers: List[str]): + inventories: Dict[Tuple[int, int], Counter[str]] = { + (team, player): collections.Counter({ + tracker_data.item_id_to_name["Celeste 64"][code]: count + for code, count in tracker_data.get_player_inventory_counts(team, player).items() + }) + for team, players in tracker_data.get_all_players().items() + for player in players if tracker_data.get_slot_info(team, player).game == "Celeste 64" + } + + # Translate non-progression items to progression items for tracker simplicity. + for (team, player), inventory in inventories.items(): + prepare_inventories(team, player, inventory, tracker_data) + + return render_template( + "multitracker__Celeste64.html", + enabled_trackers=enabled_trackers, + current_tracker="Celeste 64", + room=tracker_data.room, + all_slots=tracker_data.get_all_slots(), + room_players=tracker_data.get_all_players(), + locations=tracker_data.get_room_locations(), + locations_complete=tracker_data.get_room_locations_complete(), + total_team_locations=tracker_data.get_team_locations_total_count(), + total_team_locations_complete=tracker_data.get_team_locations_checked_count(), + player_names_with_alias=tracker_data.get_room_long_player_names(), + completed_worlds=tracker_data.get_team_completed_worlds_count(), + games=tracker_data.get_room_games(), + states=tracker_data.get_room_client_statuses(), + hints=tracker_data.get_team_hints(), + activity_timers=tracker_data.get_room_last_activity(), + videos=tracker_data.get_room_videos(), + item_id_to_name=tracker_data.item_id_to_name, + location_id_to_name=tracker_data.location_id_to_name, + inventories=inventories + ) + + def render_Celeste64_tracker(tracker_data: TrackerData, team: int, player: int) -> str: + inventory = collections.Counter({ + tracker_data.item_id_to_name["Celeste 64"][code]: count + for code, count in tracker_data.get_player_inventory_counts(team, player).items() + }) + + # Translate non-progression items to progression items for tracker simplicity. + prepare_inventories(team, player, inventory, tracker_data) + + # These settings were added in v1.2. + # That this tracker also work for generated seeds on earlier versions is a check if the option is even available in the slot data. + friendSanity = "friendsanity" in tracker_data.get_slot_data(team, player) and bool(tracker_data.get_slot_data(team, player)["friendsanity"]) + signSanity = "signsanity" in tracker_data.get_slot_data(team, player) and bool(tracker_data.get_slot_data(team, player)["signsanity"]) + carSanity = "carsanity" in tracker_data.get_slot_data(team, player) and bool(tracker_data.get_slot_data(team, player)["carsanity"]) + knowRegionsCopy = known_regions.copy() # lifecycle thing + + if not friendSanity : knowRegionsCopy.remove(REGION_FRIENDS) + if not signSanity : knowRegionsCopy.remove(REGION_SIGNS) + if not carSanity : knowRegionsCopy.remove(REGION_CARS) + + regions = { + region_name: { + "checked": sum( + 1 for id, region in locations_table.items() + if region == region_name and id in tracker_data.get_player_checked_locations(team, player) + ), + "locations": [ + ( + tracker_data.location_id_to_name["Celeste 64"][id], + id in tracker_data.get_player_checked_locations(team, player) + ) + for id, region in locations_table.items() + if region == region_name + ], + } + for region_name in knowRegionsCopy + } + + return render_template( + template_name_or_list="tracker__Celeste64.html", + room=tracker_data.room, + team=team, + player=player, + inventory=inventory, + player_name=tracker_data.get_player_name(team, player), + regions=regions, + known_regions=knowRegionsCopy + ) + + _multiworld_trackers["Celeste 64"] = render_Celeste64_multiworld_tracker + _player_trackers["Celeste 64"] = render_Celeste64_tracker \ No newline at end of file