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] %}
+
+ {{ location }} |
+ {{ '✔' if location_info[area][location] else '' }} |
+
+ {% endfor %}
+
+ {% endfor %}
+
+
+
+
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