From f003c7130f93bb0940e9b3b302f791328ec0d9b7 Mon Sep 17 00:00:00 2001 From: TauAkiou Date: Tue, 14 Dec 2021 11:04:24 -0500 Subject: [PATCH] [WebHost] Add Super Metroid support to Web Tracker (#153) * [WebHost]: Added Super Metroid tracker, based on TimeSpinner & OOT --- .../static/assets/supermetroidTracker.js | 49 +++++++++ .../static/styles/supermetroidTracker.css | 104 ++++++++++++++++++ WebHostLib/templates/supermetroidTracker.html | 85 ++++++++++++++ WebHostLib/tracker.py | 103 ++++++++++++++++- 4 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 WebHostLib/static/assets/supermetroidTracker.js create mode 100644 WebHostLib/static/styles/supermetroidTracker.css create mode 100644 WebHostLib/templates/supermetroidTracker.html diff --git a/WebHostLib/static/assets/supermetroidTracker.js b/WebHostLib/static/assets/supermetroidTracker.js new file mode 100644 index 00000000..a698214b --- /dev/null +++ b/WebHostLib/static/assets/supermetroidTracker.js @@ -0,0 +1,49 @@ +window.addEventListener('load', () => { + // Reload tracker every 15 seconds + const url = window.location; + setInterval(() => { + const ajax = new XMLHttpRequest(); + ajax.onreadystatechange = () => { + if (ajax.readyState !== 4) { return; } + + // Create a fake DOM using the returned HTML + const domParser = new DOMParser(); + const fakeDOM = domParser.parseFromString(ajax.responseText, 'text/html'); + + // Update item tracker + document.getElementById('inventory-table').innerHTML = fakeDOM.getElementById('inventory-table').innerHTML; + // Update only counters in the location-table + let counters = document.getElementsByClassName('counter'); + const fakeCounters = fakeDOM.getElementsByClassName('counter'); + for (let i = 0; i < counters.length; i++) { + counters[i].innerHTML = fakeCounters[i].innerHTML; + } + }; + ajax.open('GET', url); + ajax.send(); + }, 15000) + + // Collapsible advancement sections + const categories = document.getElementsByClassName("location-category"); + for (let i = 0; i < categories.length; i++) { + let hide_id = categories[i].id.split('-')[0]; + if (hide_id == 'Total') { + continue; + } + categories[i].addEventListener('click', function() { + // Toggle the advancement list + document.getElementById(hide_id).classList.toggle("hide"); + // Change text of the header + const tab_header = document.getElementById(hide_id+'-header').children[0]; + const orig_text = tab_header.innerHTML; + let new_text; + if (orig_text.includes("▼")) { + new_text = orig_text.replace("▼", "▲"); + } + else { + new_text = orig_text.replace("▲", "▼"); + } + tab_header.innerHTML = new_text; + }); + } +}); diff --git a/WebHostLib/static/styles/supermetroidTracker.css b/WebHostLib/static/styles/supermetroidTracker.css new file mode 100644 index 00000000..34dbeaf2 --- /dev/null +++ b/WebHostLib/static/styles/supermetroidTracker.css @@ -0,0 +1,104 @@ +#player-tracker-wrapper{ + margin: 0; +} + +#inventory-table{ + border-top: 2px solid #000000; + border-left: 2px solid #000000; + border-right: 2px solid #000000; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + padding: 3px 3px 10px; + width: 384px; + background-color: #546E7A; +} + +#inventory-table td{ + width: 45px; + height: 45px; + text-align: center; + vertical-align: middle; +} + +#inventory-table img{ + height: 100%; + max-width: 40px; + max-height: 40px; + min-width: 40px; + min-height: 40px; + filter: grayscale(100%) contrast(75%) brightness(30%); +} + +#inventory-table img.acquired{ + filter: none; +} + +#inventory-table div.counted-item { + position: relative; +} + +#inventory-table div.item-count { + position: absolute; + color: white; + font-family: "Minecraftia", monospace; + font-weight: bold; + bottom: 0px; + right: 0px; +} + +#location-table{ + width: 384px; + border-left: 2px solid #000000; + border-right: 2px solid #000000; + border-bottom: 2px solid #000000; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + background-color: #546E7A; + color: #000000; + padding: 0 3px 3px; + font-size: 14px; + cursor: default; +} + +#location-table th{ + vertical-align: middle; + text-align: left; + padding-right: 10px; +} + +#location-table td{ + padding-top: 2px; + padding-bottom: 2px; + line-height: 20px; +} + +#location-table td.counter { + text-align: right; + font-size: 14px; +} + +#location-table td.toggle-arrow { + text-align: right; +} + +#location-table tr#Total-header { + font-weight: bold; +} + +#location-table img{ + height: 100%; + max-width: 30px; + max-height: 30px; +} + +#location-table tbody.locations { + font-size: 12px; +} + +#location-table td.location-name { + padding-left: 16px; +} + +.hide { + display: none; +} diff --git a/WebHostLib/templates/supermetroidTracker.html b/WebHostLib/templates/supermetroidTracker.html new file mode 100644 index 00000000..342f7564 --- /dev/null +++ b/WebHostLib/templates/supermetroidTracker.html @@ -0,0 +1,85 @@ + + + + {{ player_name }}'s Tracker + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
{{ energy_count }}
+
+
+
+ +
{{ reserve_count }}
+
+
+
+ +
{{ missile_count }}
+
+
+
+ +
{{ super_count }}
+
+
+
+ +
{{ power_count }}
+
+
+ + {% for area in checks_done %} + + + + + + {% for location in location_info[area] %} + + + + + {% endfor %} + + {% endfor %} +
{{ area }} {{'▼' if area != 'Total'}}{{ checks_done[area] }} / {{ checks_in_area[area] }}
{{ location }}{{ '✔' if location_info[area][location] else '' }}
+
+ + diff --git a/WebHostLib/tracker.py b/WebHostLib/tracker.py index c91c2e8a..228bf375 100644 --- a/WebHostLib/tracker.py +++ b/WebHostLib/tracker.py @@ -774,6 +774,106 @@ def __renderTimespinnerTracker(multisave: Dict[str, Any], room: Room, locations: checks_done=checks_done, checks_in_area=checks_in_area, location_info=location_info, **display_data) +def __renderSuperMetroidTracker(multisave: Dict[str, Any], room: Room, locations: Dict[int, Dict[int, Tuple[int, int]]], + inventory: Counter, team: int, player: int, playerName: str, + seed_checks_in_area: Dict[int, Dict[str, int]], checks_done: Dict[str, int]) -> str: + + icons = { + "Energy Tank": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/ETank.png", + "Missile": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Missile.png", + "Super Missile": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Super.png", + "Power Bomb": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/PowerBomb.png", + "Bomb": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Bomb.png", + "Charge Beam": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Charge.png", + "Ice Beam": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Ice.png", + "Hi-Jump Boots": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/HiJump.png", + "Speed Booster": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/SpeedBooster.png", + "Wave Beam": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Wave.png", + "Spazer": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Spazer.png", + "Spring Ball": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/SpringBall.png", + "Varia Suit": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Varia.png", + "Plasma Beam": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Plasma.png", + "Grappling Beam": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Grapple.png", + "Morph Ball": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Morph.png", + "Reserve Tank": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Reserve.png", + "Gravity Suit": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Gravity.png", + "X-Ray Scope": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/XRayScope.png", + "Space Jump": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/SpaceJump.png", + "Screw Attack": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/ScrewAttack.png", + "Nothing": "", + "No Energy": "", + "Kraid": "", + "Phantoon": "", + "Draygon": "", + "Ridley": "", + "Mother Brain": "", + } + + multi_items = { + "Energy Tank": 83000, + "Missile": 83001, + "Super Missile": 83002, + "Power Bomb": 83003, + "Reserve Tank": 83020, + } + + supermetroid_location_ids = { + 'Crateria/Blue Brinstar': [82005, 82007, 82008, 82026, 82029, + 82000, 82004, 82006, 82009, 82010, + 82011, 82012, 82027, 82028, 82034, + 82036, 82037], + 'Green/Pink Brinstar': [82017, 82023, 82030, 82033, 82035, + 82013, 82014, 82015, 82016, 82018, + 82019, 82021, 82022, 82024, 82025, + 82031], + 'Red Brinstar': [82038, 82042, 82039, 82040, 82041], + 'Kraid': [82043, 82048, 82044], + 'Norfair': [82050, 82053, 82061, 82066, 82068, + 82049, 82051, 82054, 82055, 82056, + 82062, 82063, 82064, 82065, 82067], + 'Lower Norfair': [82078, 82079, 82080, 82070, 82071, + 82073, 82074, 82075, 82076, 82077], + 'Crocomire': [82052, 82060, 82057, 82058, 82059], + 'Wrecked Ship': [82129, 82132, 82134, 82135, 82001, + 82002, 82003, 82128, 82130, 82131, + 82133], + 'West Maridia': [82138, 82136, 82137, 82139, 82140, + 82141, 82142], + 'East Maridia': [82143, 82145, 82150, 82152, 82154, + 82144, 82146, 82147, 82148, 82149, + 82151], + } + + display_data = {} + + + for item_name, item_id in multi_items.items(): + base_name = item_name.split()[0].lower() + count = inventory[item_id] + display_data[base_name+"_count"] = inventory[item_id] + + # Victory condition + game_state = multisave.get("client_game_state", {}).get((team, player), 0) + display_data['game_finished'] = game_state == 30 + + # Turn location IDs into advancement tab counts + checked_locations = multisave.get("location_checks", {}).get((team, player), set()) + lookup_name = lambda id: lookup_any_location_id_to_name[id] + location_info = {tab_name: {lookup_name(id): (id in checked_locations) for id in tab_locations} + for tab_name, tab_locations in supermetroid_location_ids.items()} + checks_done = {tab_name: len([id for id in tab_locations if id in checked_locations]) + for tab_name, tab_locations in supermetroid_location_ids.items()} + checks_done['Total'] = len(checked_locations) + checks_in_area = {tab_name: len(tab_locations) for tab_name, tab_locations in supermetroid_location_ids.items()} + checks_in_area['Total'] = sum(checks_in_area.values()) + + return render_template("supermetroidTracker.html", + inventory=inventory, icons=icons, + acquired_items={lookup_any_item_id_to_name[id] for id in inventory if + id in lookup_any_item_id_to_name}, + player=player, team=team, room=room, player_name=playerName, + checks_done=checks_done, checks_in_area=checks_in_area, location_info=location_info, + **display_data) def __renderGenericTracker(multisave: Dict[str, Any], room: Room, locations: Dict[int, Dict[int, Tuple[int, int]]], inventory: Counter, team: int, player: int, playerName: str, @@ -887,5 +987,6 @@ game_specific_trackers: typing.Dict[str, typing.Callable] = { "Minecraft": __renderMinecraftTracker, "Ocarina of Time": __renderOoTTracker, "Timespinner": __renderTimespinnerTracker, - "A Link to the Past": __renderAlttpTracker + "A Link to the Past": __renderAlttpTracker, + "Super Metroid": __renderSuperMetroidTracker } \ No newline at end of file