Skip to content

Commit f003c71

Browse files
authored
[WebHost] Add Super Metroid support to Web Tracker (#153)
* [WebHost]: Added Super Metroid tracker, based on TimeSpinner & OOT
1 parent 0558351 commit f003c71

File tree

4 files changed

+340
-1
lines changed

4 files changed

+340
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
window.addEventListener('load', () => {
2+
// Reload tracker every 15 seconds
3+
const url = window.location;
4+
setInterval(() => {
5+
const ajax = new XMLHttpRequest();
6+
ajax.onreadystatechange = () => {
7+
if (ajax.readyState !== 4) { return; }
8+
9+
// Create a fake DOM using the returned HTML
10+
const domParser = new DOMParser();
11+
const fakeDOM = domParser.parseFromString(ajax.responseText, 'text/html');
12+
13+
// Update item tracker
14+
document.getElementById('inventory-table').innerHTML = fakeDOM.getElementById('inventory-table').innerHTML;
15+
// Update only counters in the location-table
16+
let counters = document.getElementsByClassName('counter');
17+
const fakeCounters = fakeDOM.getElementsByClassName('counter');
18+
for (let i = 0; i < counters.length; i++) {
19+
counters[i].innerHTML = fakeCounters[i].innerHTML;
20+
}
21+
};
22+
ajax.open('GET', url);
23+
ajax.send();
24+
}, 15000)
25+
26+
// Collapsible advancement sections
27+
const categories = document.getElementsByClassName("location-category");
28+
for (let i = 0; i < categories.length; i++) {
29+
let hide_id = categories[i].id.split('-')[0];
30+
if (hide_id == 'Total') {
31+
continue;
32+
}
33+
categories[i].addEventListener('click', function() {
34+
// Toggle the advancement list
35+
document.getElementById(hide_id).classList.toggle("hide");
36+
// Change text of the header
37+
const tab_header = document.getElementById(hide_id+'-header').children[0];
38+
const orig_text = tab_header.innerHTML;
39+
let new_text;
40+
if (orig_text.includes("▼")) {
41+
new_text = orig_text.replace("▼", "▲");
42+
}
43+
else {
44+
new_text = orig_text.replace("▲", "▼");
45+
}
46+
tab_header.innerHTML = new_text;
47+
});
48+
}
49+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#player-tracker-wrapper{
2+
margin: 0;
3+
}
4+
5+
#inventory-table{
6+
border-top: 2px solid #000000;
7+
border-left: 2px solid #000000;
8+
border-right: 2px solid #000000;
9+
border-top-left-radius: 4px;
10+
border-top-right-radius: 4px;
11+
padding: 3px 3px 10px;
12+
width: 384px;
13+
background-color: #546E7A;
14+
}
15+
16+
#inventory-table td{
17+
width: 45px;
18+
height: 45px;
19+
text-align: center;
20+
vertical-align: middle;
21+
}
22+
23+
#inventory-table img{
24+
height: 100%;
25+
max-width: 40px;
26+
max-height: 40px;
27+
min-width: 40px;
28+
min-height: 40px;
29+
filter: grayscale(100%) contrast(75%) brightness(30%);
30+
}
31+
32+
#inventory-table img.acquired{
33+
filter: none;
34+
}
35+
36+
#inventory-table div.counted-item {
37+
position: relative;
38+
}
39+
40+
#inventory-table div.item-count {
41+
position: absolute;
42+
color: white;
43+
font-family: "Minecraftia", monospace;
44+
font-weight: bold;
45+
bottom: 0px;
46+
right: 0px;
47+
}
48+
49+
#location-table{
50+
width: 384px;
51+
border-left: 2px solid #000000;
52+
border-right: 2px solid #000000;
53+
border-bottom: 2px solid #000000;
54+
border-bottom-left-radius: 4px;
55+
border-bottom-right-radius: 4px;
56+
background-color: #546E7A;
57+
color: #000000;
58+
padding: 0 3px 3px;
59+
font-size: 14px;
60+
cursor: default;
61+
}
62+
63+
#location-table th{
64+
vertical-align: middle;
65+
text-align: left;
66+
padding-right: 10px;
67+
}
68+
69+
#location-table td{
70+
padding-top: 2px;
71+
padding-bottom: 2px;
72+
line-height: 20px;
73+
}
74+
75+
#location-table td.counter {
76+
text-align: right;
77+
font-size: 14px;
78+
}
79+
80+
#location-table td.toggle-arrow {
81+
text-align: right;
82+
}
83+
84+
#location-table tr#Total-header {
85+
font-weight: bold;
86+
}
87+
88+
#location-table img{
89+
height: 100%;
90+
max-width: 30px;
91+
max-height: 30px;
92+
}
93+
94+
#location-table tbody.locations {
95+
font-size: 12px;
96+
}
97+
98+
#location-table td.location-name {
99+
padding-left: 16px;
100+
}
101+
102+
.hide {
103+
display: none;
104+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>{{ player_name }}&apos;s Tracker</title>
5+
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles/supermetroidTracker.css') }}"/>
6+
<script type="application/ecmascript" src="{{ url_for('static', filename='assets/supermetroidTracker.js') }}"></script>
7+
</head>
8+
9+
<body>
10+
<div id="player-tracker-wrapper" data-tracker="{{ room.tracker|suuid }}">
11+
<table id="inventory-table">
12+
<tr>
13+
<td><img src="{{ icons['Charge Beam'] }}" class="{{ 'acquired' if 'Charge Beam' in acquired_items }}" title="Charge Beam" /></td>
14+
<td><img src="{{ icons['Ice Beam'] }}" class="{{ 'acquired' if 'Ice Beam' in acquired_items }}" title="Ice Beam" /></td>
15+
<td><img src="{{ icons['Wave Beam'] }}" class="{{ 'acquired' if 'Wave Beam' in acquired_items }}" title="Wave Beam" /></td>
16+
<td><img src="{{ icons['Spazer'] }}" class="{{ 'acquired' if 'Spazer' in acquired_items }}" title="S p a z e r" /></td>
17+
<td><img src="{{ icons['Plasma Beam'] }}" class="{{ 'acquired' if 'Plasma Beam' in acquired_items }}" title="Plasma Beam" /></td>
18+
<td><img src="{{ icons['Varia Suit'] }}" class="{{ 'acquired' if 'Varia Suit' in acquired_items }}" title="Varia Suit" /></td>
19+
<td><img src="{{ icons['Gravity Suit'] }}" class="{{ 'acquired' if 'Gravity Suit' in acquired_items }}" title="Gravity Suit" /></td>
20+
</tr>
21+
<tr>
22+
<td><img src="{{ icons['Morph Ball'] }}" class="{{ 'acquired' if 'Morph Ball' in acquired_items }}" title="Morph Ball" /></td>
23+
<td><img src="{{ icons['Bomb'] }}" class="{{ 'acquired' if 'Bomb' in acquired_items }}" title="Bomb" /></td>
24+
<td><img src="{{ icons['Spring Ball'] }}" class="{{ 'acquired' if 'Spring Ball' in acquired_items }}" title="Spring Ball" /></td>
25+
<td><img src="{{ icons['Screw Attack'] }}" class="{{ 'acquired' if 'Screw Attack' in acquired_items }}" title="Screw Attack" /></td>
26+
<td><img src="{{ icons['Hi-Jump Boots'] }}" class="{{ 'acquired' if 'Hi-Jump Boots' in acquired_items }}" title="Hi-Jump Boots" /></td>
27+
<td><img src="{{ icons['Space Jump'] }}" class="{{ 'acquired' if 'Space Jump' in acquired_items }}" title="Space Jump" /></td>
28+
<td><img src="{{ icons['Speed Booster'] }}" class="{{ 'acquired' if 'Speed Booster' in acquired_items }}" title="Speed Booster" /></td>
29+
30+
31+
</tr>
32+
<tr>
33+
<td>
34+
<div class="counted-item">
35+
<img src="{{ icons['Energy Tank'] }}" class="{{ 'acquired' if energy_count > 0 }}" title="Energy Tank" />
36+
<div class="item-count">{{ energy_count }}</div>
37+
</div>
38+
</td>
39+
<td>
40+
<div class="counted-item">
41+
<img src="{{ icons['Reserve Tank'] }}" class="{{ 'acquired' if reserve_count > 0 }}" title="Reserve Tank" />
42+
<div class="item-count">{{ reserve_count }}</div>
43+
</div>
44+
</td>
45+
<td>
46+
<div class="counted-item">
47+
<img src="{{ icons['Missile'] }}" class="{{ 'acquired' if missile_count > 0 }}" title="Missile ({{missile_count * 5}})" />
48+
<div class="item-count">{{ missile_count }}</div>
49+
</div>
50+
</td>
51+
<td>
52+
<div class="counted-item">
53+
<img src="{{ icons['Super Missile'] }}" class="{{ 'acquired' if super_count > 0 }}" title="Super Missile ({{super_count * 5}})" />
54+
<div class="item-count">{{ super_count }}</div>
55+
</div>
56+
</td>
57+
<td>
58+
<div class="counted-item">
59+
<img src="{{ icons['Power Bomb'] }}" class="{{ 'acquired' if power_count > 0 }}" title="Power Bomb ({{power_count * 5}})" />
60+
<div class="item-count">{{ power_count }}</div>
61+
</div>
62+
</td>
63+
<td><img src="{{ icons['Grappling Beam'] }}" class="{{ 'acquired' if 'Grappling Beam' in acquired_items }}" title="Grappling Beam" /></td>
64+
<td><img src="{{ icons['X-Ray Scope'] }}" class="{{ 'acquired' if 'X-Ray Scope' in acquired_items }}" title="X-Ray Scope" /></td>
65+
</tr>
66+
</table>
67+
<table id="location-table">
68+
{% for area in checks_done %}
69+
<tr class="location-category" id="{{area}}-header">
70+
<td>{{ area }} {{'▼' if area != 'Total'}}</td>
71+
<td class="counter">{{ checks_done[area] }} / {{ checks_in_area[area] }}</td>
72+
</tr>
73+
<tbody class="locations hide" id="{{area}}">
74+
{% for location in location_info[area] %}
75+
<tr>
76+
<td class="location-name">{{ location }}</td>
77+
<td class="counter">{{ '✔' if location_info[area][location] else '' }}</td>
78+
</tr>
79+
{% endfor %}
80+
</tbody>
81+
{% endfor %}
82+
</table>
83+
</div>
84+
</body>
85+
</html>

WebHostLib/tracker.py

+102-1
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,106 @@ def __renderTimespinnerTracker(multisave: Dict[str, Any], room: Room, locations:
774774
checks_done=checks_done, checks_in_area=checks_in_area, location_info=location_info,
775775
**display_data)
776776

777+
def __renderSuperMetroidTracker(multisave: Dict[str, Any], room: Room, locations: Dict[int, Dict[int, Tuple[int, int]]],
778+
inventory: Counter, team: int, player: int, playerName: str,
779+
seed_checks_in_area: Dict[int, Dict[str, int]], checks_done: Dict[str, int]) -> str:
780+
781+
icons = {
782+
"Energy Tank": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/ETank.png",
783+
"Missile": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Missile.png",
784+
"Super Missile": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Super.png",
785+
"Power Bomb": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/PowerBomb.png",
786+
"Bomb": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Bomb.png",
787+
"Charge Beam": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Charge.png",
788+
"Ice Beam": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Ice.png",
789+
"Hi-Jump Boots": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/HiJump.png",
790+
"Speed Booster": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/SpeedBooster.png",
791+
"Wave Beam": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Wave.png",
792+
"Spazer": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Spazer.png",
793+
"Spring Ball": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/SpringBall.png",
794+
"Varia Suit": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Varia.png",
795+
"Plasma Beam": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Plasma.png",
796+
"Grappling Beam": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Grapple.png",
797+
"Morph Ball": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Morph.png",
798+
"Reserve Tank": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Reserve.png",
799+
"Gravity Suit": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Gravity.png",
800+
"X-Ray Scope": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/XRayScope.png",
801+
"Space Jump": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/SpaceJump.png",
802+
"Screw Attack": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/ScrewAttack.png",
803+
"Nothing": "",
804+
"No Energy": "",
805+
"Kraid": "",
806+
"Phantoon": "",
807+
"Draygon": "",
808+
"Ridley": "",
809+
"Mother Brain": "",
810+
}
811+
812+
multi_items = {
813+
"Energy Tank": 83000,
814+
"Missile": 83001,
815+
"Super Missile": 83002,
816+
"Power Bomb": 83003,
817+
"Reserve Tank": 83020,
818+
}
819+
820+
supermetroid_location_ids = {
821+
'Crateria/Blue Brinstar': [82005, 82007, 82008, 82026, 82029,
822+
82000, 82004, 82006, 82009, 82010,
823+
82011, 82012, 82027, 82028, 82034,
824+
82036, 82037],
825+
'Green/Pink Brinstar': [82017, 82023, 82030, 82033, 82035,
826+
82013, 82014, 82015, 82016, 82018,
827+
82019, 82021, 82022, 82024, 82025,
828+
82031],
829+
'Red Brinstar': [82038, 82042, 82039, 82040, 82041],
830+
'Kraid': [82043, 82048, 82044],
831+
'Norfair': [82050, 82053, 82061, 82066, 82068,
832+
82049, 82051, 82054, 82055, 82056,
833+
82062, 82063, 82064, 82065, 82067],
834+
'Lower Norfair': [82078, 82079, 82080, 82070, 82071,
835+
82073, 82074, 82075, 82076, 82077],
836+
'Crocomire': [82052, 82060, 82057, 82058, 82059],
837+
'Wrecked Ship': [82129, 82132, 82134, 82135, 82001,
838+
82002, 82003, 82128, 82130, 82131,
839+
82133],
840+
'West Maridia': [82138, 82136, 82137, 82139, 82140,
841+
82141, 82142],
842+
'East Maridia': [82143, 82145, 82150, 82152, 82154,
843+
82144, 82146, 82147, 82148, 82149,
844+
82151],
845+
}
846+
847+
display_data = {}
848+
849+
850+
for item_name, item_id in multi_items.items():
851+
base_name = item_name.split()[0].lower()
852+
count = inventory[item_id]
853+
display_data[base_name+"_count"] = inventory[item_id]
854+
855+
# Victory condition
856+
game_state = multisave.get("client_game_state", {}).get((team, player), 0)
857+
display_data['game_finished'] = game_state == 30
858+
859+
# Turn location IDs into advancement tab counts
860+
checked_locations = multisave.get("location_checks", {}).get((team, player), set())
861+
lookup_name = lambda id: lookup_any_location_id_to_name[id]
862+
location_info = {tab_name: {lookup_name(id): (id in checked_locations) for id in tab_locations}
863+
for tab_name, tab_locations in supermetroid_location_ids.items()}
864+
checks_done = {tab_name: len([id for id in tab_locations if id in checked_locations])
865+
for tab_name, tab_locations in supermetroid_location_ids.items()}
866+
checks_done['Total'] = len(checked_locations)
867+
checks_in_area = {tab_name: len(tab_locations) for tab_name, tab_locations in supermetroid_location_ids.items()}
868+
checks_in_area['Total'] = sum(checks_in_area.values())
869+
870+
return render_template("supermetroidTracker.html",
871+
inventory=inventory, icons=icons,
872+
acquired_items={lookup_any_item_id_to_name[id] for id in inventory if
873+
id in lookup_any_item_id_to_name},
874+
player=player, team=team, room=room, player_name=playerName,
875+
checks_done=checks_done, checks_in_area=checks_in_area, location_info=location_info,
876+
**display_data)
777877

778878
def __renderGenericTracker(multisave: Dict[str, Any], room: Room, locations: Dict[int, Dict[int, Tuple[int, int]]],
779879
inventory: Counter, team: int, player: int, playerName: str,
@@ -887,5 +987,6 @@ def getTracker(tracker: UUID):
887987
"Minecraft": __renderMinecraftTracker,
888988
"Ocarina of Time": __renderOoTTracker,
889989
"Timespinner": __renderTimespinnerTracker,
890-
"A Link to the Past": __renderAlttpTracker
990+
"A Link to the Past": __renderAlttpTracker,
991+
"Super Metroid": __renderSuperMetroidTracker
891992
}

0 commit comments

Comments
 (0)