// ==UserScript==
// @name SB-AUI
// @namespace http://tampermonkey.net/
// @version 1.2.1
// @description Advanced UI for Starblast with extra features
// @author Halcyon
// @license All rights reserved, this code may not be reproduced or used in any way without the express written consent of the author.
// @match https://starblast.io/
// @icon https://www.google.com/s2/favicons?sz=64&domain=starblast.io
// @grant none
// ==/UserScript==
/**
* CHANGELOG
* 1.0.0 - Initial creation. Core of AUI
* 1.1.0 - Added team evaluation (PBS, PPBS, DPS)
* 1.2.0 - Optimizations, code cleanup and transition to a component-based system
* 1.2.1 - More optimizations and code cleanup. Added hardElementRefresh
*/
'use strict';
const API_LINK = "https://starblast.dankdmitron.dev/api/simstatus.json";
const CURRENT_RUNNING_VERSION = "1.2.1"
/********* STYLING ************ */
const applyCSS = (styles, element) => {
if (!element) {
return;
}
for (let key of Object.keys(styles)) {
try {
element.style[key] = styles[key]
} catch (ex) {console.error(`Object.prototype.applyStyles: Cannot apply style '${key}' to ${element}`)}
}
return;
}
const applyBaseStyles = (element, includeFont = true) => {
element.style.boxShadow = "black 0px 0px 0px";
element.style.textShadow = "black 0px 0px 0px";
if (includeFont) {
element.style.fontFamily = `"DM Sans", sans-serif`;
}
element.style.fontWeight = "400";
element.style.background = "#0b0b0b";
element.style.border = "1px solid #1a1a1a"
element.style.color = "#FFF";
element.style.borderRadius = "10px";
element.classList.add("hover-class");
}
//Remove unneeded UI elements (spotify and socials columns)
let removalQueries = ['[data-translate-base="music"]','[data-translate-base="community"]'];
for (let query of removalQueries) {
for (let el of document.querySelectorAll(query)) {
el.style.display = "none"
}
}
//Setting the AUI logo
try {
document.querySelector("#logo > img").src = "https://i.ibb.co/t25sFmR/SBAUI.png";
} catch (ex) {
try {
setTimeout(() => {
document.querySelector("#logo > img").src = "https://i.ibb.co/t25sFmR/SBAUI.png";
}, 500)
} catch (ex) {
setTimeout(() => {
document.querySelector("#logo > img").src = "https://i.ibb.co/t25sFmR/SBAUI.png";
}, 1000)
}
}
//Importing DM Sans and Abel
try {
var styleElement = document.createElement('style');
var importRule = `
@import url('https://fonts.googleapis.com/css2?family=Abel&family=DM+Sans:wght@400;500;700&display=swap');
`;
styleElement.textContent = importRule;
document.head.appendChild(styleElement);
document.addEventListener("DOMContentLoaded", function() {
var elementsToStyle = document.querySelectorAll("body");
elementsToStyle.forEach(function(element) {
element.style.fontFamily = '"DM Sans", sans-serif';
});
});
} catch (ex) {}
//All buttons get base styles
for (let el of document.querySelectorAll('button')) {
applyBaseStyles(el)
}
//Scrollbar code
document.documentElement.style.scrollbarWidth = 'thin';
document.documentElement.style.msOverflowStyle = 'none';
var style = document.createElement('style');
var css = `
/* Webkit and Blink */
::-webkit-scrollbar {
width: 0.2em;
}
::-webkit-scrollbar-thumb {
background-color: #1a1a1a;
border: none;
border-radius: 0.1em;
}
::-webkit-scrollbar-track {
background-color: transparent;
border: none;
}
/* Firefox */
scrollbar-width: thin;
scrollbar-color: transparent transparent;
`;
style.appendChild(document.createTextNode(css));
document.head.appendChild(style);
document.querySelector("body").style.backgroundColor = "#0b0b0b";
//Overlay styles
applyCSS({
backgroundColor: "#1a1a1a",
background: "repeating-linear-gradient(45deg, #1a1a1a 0, #131313 1px, #0b0b0b 0, #0b0b0b 50%)",
backgroundSize: "10px 10px",
maxWidth: "calc(100% - 60px)",
maxHeight: "calc(100% - 60px)",
margin: "auto auto",
boxSizing: "content-box",
boxShadow: "black 0px 0px 0px",
border: "6px solid #131313",
outline: "54px solid #0b0b0b",
}, document.querySelector("#overlay"));
//Body styles
document.querySelector("body").style.height = "100dvh";
document.querySelector("body").style.width = "100vw";
//Play button styles
applyCSS({
fontFamily: `"DM Sans", sans-serif`,
letterSpacing: "4px",
fontSize: "2.2rem",
fontWeight: "600"
}, document.querySelector("#play"))
//Styles
applyCSS({
background: `transparent`,
textShadow: `black 0px 0px 0px`,
fontFamily: `'Abel', sans-serif`,
fontSize: `1rem`,
letterSpacing: `0px`,
marginTop: `5px`,
marginLeft: `auto`,
marginRight: `auto`,
width: `80%`,
borderTop: `1px solid #1a1a1a`,
color: `gray`
},document.querySelector("#game_modes"))
//Changelog styles
document.querySelector(".changelog-new").style.fontFamily = `"DM Sans", sans-serif`;
//Tools like "modding" styles (4 buttons)
document.querySelector('.followtools').style.left = '0';
document.querySelector('.followtools').style.width = 'max-content';
document.querySelector('.followtools').style.zIndex = '500';
//Changelog to top
document.querySelector('.bottom-left').style.top = '0';
document.querySelector('.bottom-left').style.height = 'max-content';
//Name input styles
applyCSS({
background: '#0b0b0b',
border: '1px solid #1a1a1a',
fontFamily: 'DM Sans',
boxShadow: 'black 0px 0px 0px',
borderRadius: '10px',
},document.querySelector('.inputwrapper'))
//Buttons for switching modes styles
const leftRight = [document.querySelector('#prevMode'),document.querySelector('#nextMode')];
for (let el of leftRight) {
el.style.color = '#FFFFFF';
el.style.textShadow = 'black 0px 0px 0px';
}
//Elements to apply baseStyles to
const baseStyleQueries = ['.changelog-new', '#moddingspace', "#donate", "#rankings", "#training"];
for (let query of baseStyleQueries) {
applyBaseStyles(document.querySelector(query));
}
//Styles for button elements
const ml = ['#moddingspace', "#donate", "#rankings", "#training"]
for (let query of ml) {
let item = document.querySelector(query);
item.style.paddingBottom = "0.5rem";
let icon = document.querySelector(`${query} > i`);
icon.style.margin = "0.5rem auto 0.5rem auto";
icon.style.paddingBottom = '0.5rem';
icon.style.width = '80%';
icon.style.borderBottom = '1px solid #1a1a1a';
let span = document.querySelector(`${query} > span`);
span.style.color = "#FFF";
span.style.letterSpacing = '1px';
span.style.textShadow = 'black 0px 0px 0px';
span.style.fontWeight = '500';
}
for (let el of document.querySelectorAll('.modal')) {
applyBaseStyles(el);
}
for (let el of document.querySelectorAll('.social i')) {
applyBaseStyles(el, false);
}
/***********=/STYLING********** */
//Create SL INTEGRATION
var J = document.createElement("div");
J.id = "SL_INTEGRATION";
document.querySelector('#overlay').appendChild(J);
const SL_INTEGRATION = document.querySelector('#SL_INTEGRATION');
applyCSS({
position: 'absolute',
height: '100%',
width: '25%',
top: '0',
right: '0',
display: 'flex',
flexDirection: 'column'
}, SL_INTEGRATION)
const templateStatusData = () => ({name: "",id: "",team_1: {hue: null,gems: 0,level: 0,potentialOutput: 0,PBS: 0,PPBS: 0,players: []},team_2: {hue: null,gems: 0,level: 0,potentialOutput: 0,PBS: 0,PPBS: 0,players: []},team_3: {hue: null,gems: 0,level: 0,potentialOutput: 0,PBS: 0,PPBS: 0,players: []}})
//All variables used in the components should be declared here
window["COMPONENT_STATE_VALUES"] = {
options: {
activePanel: "listing",
activeRegion: "europe",
modes: {
team: true,
survival: false,
deathmatch: false,
modding: false,
invasion: false
}
},
filteredSystems: [],
statusReportActive: false,
statusReportData: {
name: "",
id: "",
team_1: {
hue: null,
gems: 0,
level: 0,
potentialOutput: 0,
PBS: 0,
PPBS: 0,
players: []
},
team_2: {
hue: null,
gems: 0,
level: 0,
potentialOutput: 0,
PBS: 0,
PPBS: 0,
players: []
},
team_3: {
hue: null,
gems: 0,
level: 0,
potentialOutput: 0,
PBS: 0,
PPBS: 0,
players: []
}
}
}
//Component class for easier maintaining. NOTE: All components must have only 1 parent element and components should be named using PascalCase
class Component {
/**
* AUI HTML component. Make sure there is a wrapping parent element.
* @param {String} ID - ID of the element
* @param {Function} HTML - Function that returns a template string representing innerHTML. Note that any conditions put on the wrapper element itself will never reflect upon using .refreshElement(), to reflect those changes use .hardRefreshElement()
*/
constructor(ID, HTML) {
this.ID = ID
this.HTML = HTML
}
evaluate() {
if (typeof this.HTML === 'function') {
return this.HTML();
} else {throw new Error(`Component class error: Second argument in Component instantiation is not a function ('${typeof this.HTML}'). ${typeof this.HTML === 'string' && "Hint: Put '() =>' before your template literal"}`)}
}
getElement() {
const tempContainer = document.createElement("span");
tempContainer.innerHTML = this.evaluate();
tempContainer.children[0].setAttribute("id", `${this.ID}`)
if (tempContainer.children.length > 1) {
throw new Error(`Component class error: Components must have a parent element (Component ID: ${this.ID})`)
}
return tempContainer.innerHTML;
}
refreshElement() {
//console.log(`Component refreshed: ${this.ID}`)
try {
let tempElement = document.createElement("span");
tempElement.innerHTML = this.evaluate();
document.querySelector(`#${this.ID}`).innerHTML = tempElement.children[0].innerHTML;
} catch (ex) {console.error(`Couldn't refresh element with the ID of '${this.ID}': ` + ex)}
}
hardRefreshElement() {
try {
let tempElement = document.createElement("span");
tempElement.innerHTML = this.evaluate();
tempElement.children[0].setAttribute("id", `${this.ID}`);
document.querySelector(`#${this.ID}`).outerHTML = tempElement.innerHTML;
} catch (ex) {console.error(`Couldn't hardRefresh element with the ID of '${this.ID}': ` + ex)}
}
}
let API_TIMER = setInterval(async () => {
if (COMPONENT_STATE_VALUES.options.activePanel !== 'listing') {
return;
}
let raw = await(await fetch(API_LINK)).json();
let allSystems = [];
for (let item of raw) {
if (item.modding) {
if (!COMPONENT_STATE_VALUES.options.modes.modding) {
continue;
}
}
if (item.location.toLowerCase() !== COMPONENT_STATE_VALUES.options.activeRegion) {
continue;
}
for (let system of item.systems) {
if (COMPONENT_STATE_VALUES.options.modes[system.mode]) {
allSystems.push({
...system,
IP_ADDR: item.address
});
}
}
}
COMPONENT_STATE_VALUES.filteredSystems = allSystems.sort((a, b) => a.time - b.time);
Listing.refreshElement();
}, 3200)
let STATUS_TIMER = null;
window.statusReport = async (query) => {
if (STATUS_TIMER) {return};
COMPONENT_STATE_VALUES.statusReportActive = true;
StatusReportModal.hardRefreshElement();
STATUS_TIMER = setInterval(async () => {
let raw = await (await fetch(`https://starblast.dankdmitron.dev/api/status/${query}`)).json();
COMPONENT_STATE_VALUES.statusReportData = templateStatusData();
COMPONENT_STATE_VALUES.statusReportData.name = raw.name;
COMPONENT_STATE_VALUES.statusReportData.id = query.split('@')[0];
for (let key of Object.keys(raw.players)) {
let player = raw.players[key];
COMPONENT_STATE_VALUES.statusReportData[`team_${player.friendly + 1}`].players.push({
name: player.player_name,
ecp: !!player.custom,
score: player.score,
type: player.type,
PBS: calculatePlayerScore(player.type, !!player.custom)
})
COMPONENT_STATE_VALUES.statusReportData[`team_${player.friendly + 1}`].hue = player.hue;
}
for (let team of raw.mode.teams) {
for (let num of ["team_1", "team_2", "team_3"]) {
if (team.hue === COMPONENT_STATE_VALUES.statusReportData[num].hue) {
COMPONENT_STATE_VALUES.statusReportData[num].gems = team.crystals
COMPONENT_STATE_VALUES.statusReportData[num].level = team.level
break
}
}
}
for (let team of ["team_1", "team_2", "team_3"]) {
let sPBS = 0, sPPBS = 0, potentialOutput = 0;
for (let i = 0; i < COMPONENT_STATE_VALUES.statusReportData[team].players.length; i++) {
sPBS += Number(COMPONENT_STATE_VALUES.statusReportData[team].players[i].PBS.currentScore);
sPPBS += Number(COMPONENT_STATE_VALUES.statusReportData[team].players[i].PBS.potentialScore);
potentialOutput += COMPONENT_STATE_VALUES.statusReportData[team].players[i].PBS.energyOutput;
}
COMPONENT_STATE_VALUES.statusReportData[team].PBS = sPBS.toFixed(2);
COMPONENT_STATE_VALUES.statusReportData[team].PPBS = sPPBS.toFixed(2);
COMPONENT_STATE_VALUES.statusReportData[team].potentialOutput = potentialOutput;
COMPONENT_STATE_VALUES.statusReportData[team].players = COMPONENT_STATE_VALUES.statusReportData[team].players.sort((a, b) => a.score - b.score).reverse();
}
StatusReportModal.refreshElement();
}, 2500)
}
//These functions are attached to the window so listeners have access to them (Tampermonkey quirk)
window.switchActivePanel = (panel) => {
COMPONENT_STATE_VALUES.options.activePanel = panel;
ListingOrSettings.refreshElement();
Settings.hardRefreshElement();
Listing.hardRefreshElement();
}
window.switchActiveRegion = (region) => {
COMPONENT_STATE_VALUES.options.activeRegion = region;
Settings.refreshElement();
}
window.toggleMode = (mode) => {
COMPONENT_STATE_VALUES.options.modes[mode] = !COMPONENT_STATE_VALUES.options.modes[mode];
console.log(COMPONENT_STATE_VALUES.options)
Settings.refreshElement();
}
window.closeStatusReport = () => {
COMPONENT_STATE_VALUES.statusReportActive = false;
clearInterval(STATUS_TIMER);
STATUS_TIMER = null;
StatusReportModal.hardRefreshElement();
}
document.querySelector('#play').addEventListener('click', () => {
clearInterval(API_TIMER);
clearInterval(STATUS_TIMER);
SL_INTEGRATION.style.display = 'none';
});
//WARNING: refreshSL is a SLOW function. Use it only when absolutely neccessary.
const refreshSL = () => {
SL_INTEGRATION.innerHTML = `
${StatusReportModal.getElement()}
${TitleAndCredits.getElement()}
${ListingOrSettings.getElement()}
${Listing.getElement()}
${Settings.getElement()}
`
}
//COMPONENTS GO BELOW
let ListingOrSettings = new Component("SL_OPTIONS_WRAPPER", () => `<div id="SL_OPTIONS_WRAPPER" style="height:4%; width:100%;display: flex;">
<div id="SL_LISTING_REF" onclick="window.switchActivePanel('listing')"
style="${COMPONENT_STATE_VALUES.options.activePanel == 'listing' ? "background-color: #FFFFFF; color: #0b0b0b; fill: #0b0b0b;" : "background-color: #1a1a1a; color: #FFFFFF; fill: #FFFFFF;"}border-top-left-radius: 0.25vw; border-top-right-radius: 0.25vw;display: grid; place-items: center; font-size: 0.8vw; font-family: 'DM Sans',sans-serif;width:50%;font-weight: 500;cursor:pointer;">
LISTING
</div>
<div id="SL_SETTINGS_REF" onclick="window.switchActivePanel('settings')"
style="${COMPONENT_STATE_VALUES.options.activePanel == 'settings' ? "background-color: #FFFFFF; color: #0b0b0b; fill: #0b0b0b;" : "background-color: #1a1a1a; color: #FFFFFF; fill: #FFFFFF;"}border-top-left-radius: 0.25vw; border-top-right-radius: 0.25vw;display: grid; place-items: center; font-size: 0.8vw; font-family: 'DM Sans',sans-serif;width:50%;font-weight: 500;cursor:pointer;">
SETTINGS
</div>
</div>`)
let TitleAndCredits = new Component("TitleAndCredits", () => `<div style="height:10%;width:100%;">
<div style="font-family: 'DM Sans', sans-serif;font-weight: 600;font-size: 1.3vw;text-align:right;width:100%;color:white">
Starblast AUI v${CURRENT_RUNNING_VERSION}
</div>
<div style="font-family:'Abel', sans-serif; font-weight: 300; font-size: 0.9vw; text-align: right; width: 100%; color: gray;">
API (<a style="color:rgb(191,191,191)" href="https://starblast.dankdmitron.dev/" target="_blank"><u>Serverlist+</u></a>): <span style="color: white; font-weight: bold">dankdmitron</span>
</div>
<div style="font-family:'Abel', sans-serif; font-weight: 300; font-size: 0.9vw; text-align: right; width: 100%; color: gray;">
Design and integration: <span style="color: white; font-weight: bold">Halcyon</span>
</div>
</div>`)
let Settings = new Component("SLSettings", () => `<div id="SL_SETTINGS" style="box-sizing:border-box;padding:0.6vw;height:86%;width:100%;display: ${COMPONENT_STATE_VALUES.options.activePanel == "settings" ? "flex" : "none"}; flex-direction: column; gap: 0.2rem; overflow-y: auto;background-color:#0b0b0b;border:1px solid #1a1a1a;justify-content: flex-start;align-items:flex-end;">
<div style="text-align:right;color:white;">
<div style="font-family:'DM Sans',sans-serif;font-size:1.5vw;margin-bottom:0.5vh;">
REGION:
</div>
<div style="color:gray;font-family: 'Abel',sans-serif;font-size:1vw;display:flex;gap:0.5vw;justify-content:end;align-items:center;fill:#FFFFFF;">
<div>Europe</div>
${
COMPONENT_STATE_VALUES.options.activeRegion == "europe"
?
`<svg xmlns="http://www.w3.org/2000/svg" height="1vw" viewBox="0 -960 960 960"><path d="M480-294q78 0 132-54t54-132q0-78-54-132t-132-54q-78 0-132 54t-54 132q0 78 54 132t132 54Zm0 214q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z"/></svg>`
:
`<svg xmlns="http://www.w3.org/2000/svg" onclick="window.switchActiveRegion('europe')" height="1vw" viewBox="0 -960 960 960"><path d="M480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z"/></svg>`
}
</div>
<div style="color:gray;font-family: 'Abel',sans-serif;font-size:1vw;display:flex;gap:0.5vw;justify-content:end;align-items:center;fill:#FFFFFF;">
<div>America</div>
${
COMPONENT_STATE_VALUES.options.activeRegion == "america"
?
`<svg xmlns="http://www.w3.org/2000/svg" height="1vw" viewBox="0 -960 960 960"><path d="M480-294q78 0 132-54t54-132q0-78-54-132t-132-54q-78 0-132 54t-54 132q0 78 54 132t132 54Zm0 214q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z"/></svg>`
:
`<svg xmlns="http://www.w3.org/2000/svg" onclick="window.switchActiveRegion('america')" height="1vw" viewBox="0 -960 960 960"><path d="M480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z"/></svg>`
}
</div>
<div style="color:gray;font-family: 'Abel',sans-serif;font-size:1vw;display:flex;gap:0.5vw;justify-content:end;align-items:center;fill:#FFFFFF;">
<div>Asia</div>
${
COMPONENT_STATE_VALUES.options.activeRegion == "asia"
?
`<svg xmlns="http://www.w3.org/2000/svg" height="1vw" viewBox="0 -960 960 960"><path d="M480-294q78 0 132-54t54-132q0-78-54-132t-132-54q-78 0-132 54t-54 132q0 78 54 132t132 54Zm0 214q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z"/></svg>`
:
`<svg xmlns="http://www.w3.org/2000/svg" onclick="window.switchActiveRegion('asia')" height="1vw" viewBox="0 -960 960 960"><path d="M480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z"/></svg>`
}
</div>
</div>
<div style="text-align:right;color:white;">
<div style="font-family:'DM Sans',sans-serif;font-size:1.5vw;margin-bottom:0.5vh;margin-top:2vh;">
MODE:
</div>
<div style="color:gray;font-family: 'Abel',sans-serif;font-size:1vw;display:flex;gap:0.5vw;justify-content:end;align-items:center;fill:#FFFFFF;">
<div>Team Mode</div>
${
COMPONENT_STATE_VALUES.options.modes.team
?
`<svg onclick="window.toggleMode('team')" xmlns="http://www.w3.org/2000/svg" height="1vw" viewBox="0 -960 960 960"><path d="m419-321 289-289-43-43-246 246-119-119-43 43 162 162ZM180-120q-24 0-42-18t-18-42v-600q0-24 18-42t42-18h600q24 0 42 18t18 42v600q0 24-18 42t-42 18H180Zm0-60h600v-600H180v600Zm0-600v600-600Z"/></svg>`
:
`<svg onclick="window.toggleMode('team')" xmlns="http://www.w3.org/2000/svg" height="1vw" viewBox="0 -960 960 960"><path d="M180-120q-24 0-42-18t-18-42v-600q0-24 18-42t42-18h600q24 0 42 18t18 42v600q0 24-18 42t-42 18H180Zm0-60h600v-600H180v600Z"/></svg>`
}
</div>
<div style="color:gray;font-family: 'Abel',sans-serif;font-size:1vw;display:flex;gap:0.5vw;justify-content:end;align-items:center;fill:#FFFFFF;">
<div>Survival</div>
${
COMPONENT_STATE_VALUES.options.modes.survival
?
`<svg onclick="window.toggleMode('survival')" xmlns="http://www.w3.org/2000/svg" height="1vw" viewBox="0 -960 960 960"><path d="m419-321 289-289-43-43-246 246-119-119-43 43 162 162ZM180-120q-24 0-42-18t-18-42v-600q0-24 18-42t42-18h600q24 0 42 18t18 42v600q0 24-18 42t-42 18H180Zm0-60h600v-600H180v600Zm0-600v600-600Z"/></svg>`
:
`<svg onclick="window.toggleMode('survival')" xmlns="http://www.w3.org/2000/svg" height="1vw" viewBox="0 -960 960 960"><path d="M180-120q-24 0-42-18t-18-42v-600q0-24 18-42t42-18h600q24 0 42 18t18 42v600q0 24-18 42t-42 18H180Zm0-60h600v-600H180v600Z"/></svg>`
}
</div>
<div style="color:gray;font-family: 'Abel',sans-serif;font-size:1vw;display:flex;gap:0.5vw;justify-content:end;align-items:center;fill:#FFFFFF;">
<div>Deathmatch</div>
${
COMPONENT_STATE_VALUES.options.modes.deathmatch
?
`<svg onclick="window.toggleMode('deathmatch')" xmlns="http://www.w3.org/2000/svg" height="1vw" viewBox="0 -960 960 960"><path d="m419-321 289-289-43-43-246 246-119-119-43 43 162 162ZM180-120q-24 0-42-18t-18-42v-600q0-24 18-42t42-18h600q24 0 42 18t18 42v600q0 24-18 42t-42 18H180Zm0-60h600v-600H180v600Zm0-600v600-600Z"/></svg>`
:
`<svg onclick="window.toggleMode('deathmatch')" xmlns="http://www.w3.org/2000/svg" height="1vw" viewBox="0 -960 960 960"><path d="M180-120q-24 0-42-18t-18-42v-600q0-24 18-42t42-18h600q24 0 42 18t18 42v600q0 24-18 42t-42 18H180Zm0-60h600v-600H180v600Z"/></svg>`
}
</div>
<div style="color:gray;font-family: 'Abel',sans-serif;font-size:1vw;display:flex;gap:0.5vw;justify-content:end;align-items:center;fill:#FFFFFF;">
<div>Modded</div>
${
COMPONENT_STATE_VALUES.options.modes.modding
?
`<svg onclick="window.toggleMode('modding')" xmlns="http://www.w3.org/2000/svg" height="1vw" viewBox="0 -960 960 960"><path d="m419-321 289-289-43-43-246 246-119-119-43 43 162 162ZM180-120q-24 0-42-18t-18-42v-600q0-24 18-42t42-18h600q24 0 42 18t18 42v600q0 24-18 42t-42 18H180Zm0-60h600v-600H180v600Zm0-600v600-600Z"/></svg>`
:
`<svg onclick="window.toggleMode('modding')" xmlns="http://www.w3.org/2000/svg" height="1vw" viewBox="0 -960 960 960"><path d="M180-120q-24 0-42-18t-18-42v-600q0-24 18-42t42-18h600q24 0 42 18t18 42v600q0 24-18 42t-42 18H180Zm0-60h600v-600H180v600Z"/></svg>`
}
</div>
<div style="color:gray;font-family: 'Abel',sans-serif;font-size:1vw;display:flex;gap:0.5vw;justify-content:end;align-items:center;fill:#FFFFFF;">
<div>Invasion</div>
${
COMPONENT_STATE_VALUES.options.modes.invasion
?
`<svg onclick="window.toggleMode('invasion')" xmlns="http://www.w3.org/2000/svg" height="1vw" viewBox="0 -960 960 960"><path d="m419-321 289-289-43-43-246 246-119-119-43 43 162 162ZM180-120q-24 0-42-18t-18-42v-600q0-24 18-42t42-18h600q24 0 42 18t18 42v600q0 24-18 42t-42 18H180Zm0-60h600v-600H180v600Zm0-600v600-600Z"/></svg>`
:
`<svg onclick="window.toggleMode('invasion')" xmlns="http://www.w3.org/2000/svg" height="1vw" viewBox="0 -960 960 960"><path d="M180-120q-24 0-42-18t-18-42v-600q0-24 18-42t42-18h600q24 0 42 18t18 42v600q0 24-18 42t-42 18H180Zm0-60h600v-600H180v600Z"/></svg>`
}
</div>
</div>
</div>`)
let Listing = new Component("ServerListing", () => `
<div id="SL_LISTING" style="box-sizing:border-box;padding:0.6vw;height:86%;width:100%;display: ${COMPONENT_STATE_VALUES.options.activePanel == "listing" ? "flex" : "none"}; flex-direction: column; overflow-y: auto;background-color:#0b0b0b;border:1px solid #1a1a1a">
${
COMPONENT_STATE_VALUES.filteredSystems.length === 0
?
""
:
COMPONENT_STATE_VALUES.filteredSystems.map(system => {
return `
<div onclick="window.statusReport('${system.id}@${system.IP_ADDR}')" style="width:100%; cursor: pointer; min-height:8.5vh; margin-bottom: 0.9vh; border-radius:12px; border: 1px solid #1a1a1a; display: flex; flex-direction: column; align-items: center; justify-content: space-evenly;box-sizing:border-box;padding:0.4vh">
<div style="font-family:'DM Sans',sans-serif;color:white;font-weight:600;font-size:1.4vw;">
${system.name}
</div>
<div style="width:82%;height:1px;background-color:#1a1a1a"></div>
<div style="width:92%;display:flex;align-items:center;justify-content:space-between;color:gray;font-family:'Abel',sans-serif;font-size:0.8vw;position:relative;">
<div>
${system.mode === 'modding' ? capitalize(system.mod_id) : capitalize(system.mode)}
</div>
<div style="position:absolute;top:0;left:0;width:100%;text-align:center;">
${~~(system.time / 60)} min
</div>
<div>
${system.players} players
</div>
</div>
</div>
`
}).join('')
}
</div>`)
let StatusReportModal = new Component("StatusReportModal", () => `
<div style="position:fixed; top:0; left:0; width: 100%; height: 100%; display: ${COMPONENT_STATE_VALUES.statusReportActive ? "grid" : "none"}; place-items:center; z-index: 999; background: rgba(0,0,0,0.4)">
<div style="padding:1vw;display:flex;flex-direction:column;align-items:center;background:#0b0b0b;border:1px solid #1a1a1a;border-radius:12px">
<div style="height:3vh; border-bottom: 1px solid #1a1a1a; width:50vw; color: white; font-family: 'DM Sans',sans-serif; font-size: 1.7vw; display: flex; justify-content: space-between; fill: white; align-items: center; padding-bottom: 2vh">
<div>${COMPONENT_STATE_VALUES.statusReportData.name}</div>
<div style="display:flex;gap:1vw;align-items:center;">
<a target="_blank" href="https://starblast.io/#${COMPONENT_STATE_VALUES.statusReportData.id}">
<div style="height:1.4vw;display:grid;place-items:center;font-weight:400;font-size:1vw;border:1px solid white;border-radius:0.2vw;padding:0.4vw 1vw 0.4vw 1vw;color:white;">
JOIN
</div>
</a>
<svg onclick="window.closeStatusReport()" style="cursor: pointer;" xmlns="http://www.w3.org/2000/svg" height="2.5vh" viewBox="0 -960 960 960"><path d="m249-207-42-42 231-231-231-231 42-42 231 231 231-231 42 42-231 231 231 231-42 42-231-231-231 231Z"/></svg>
</div>
</div>
<div style="display:flex;height:55vh;width: 100%; margin-top: 2vh;justify-content:space-between;">
${
(() => {
let arr = [];
for (let CUR_TEAM of ["team_1", "team_2", "team_3"]) {
arr.push(`
<div style="width:30%;height:100%;overflow-y: auto; display: flex; flex-direction: column; justify-content: flex-start; align-items: center">
<div style="margin-bottom: 1vh;border-radius: 0.25vw;font-size:1.9vw;border:1px solid hsla(${COMPONENT_STATE_VALUES.statusReportData[CUR_TEAM].hue}, 100%, 50%, 1); background-color: hsla(${COMPONENT_STATE_VALUES.statusReportData[CUR_TEAM].hue}, 100%, 50%, 0.25); color: hsla(${COMPONENT_STATE_VALUES.statusReportData[CUR_TEAM].hue}, 100%, 50%, 1); width:80%;padding: 1vh 0 1vh 0; font-family: 'DM Sans',sans-serif; font-weight: 600; text-align: center;">
${hueToColorName(COMPONENT_STATE_VALUES.statusReportData[CUR_TEAM].hue)}
</div>
<div style="width:100%;margin: 1vh 0 1vh 0; padding-bottom:1vh;border-bottom: 1px solid #1a1a1a;">
<div style="font-family:'Abel',sans-serif;font-size:0.8vw;display:flex;width:100%;justify-content:space-between">
<div style="color:gray">Player count</div>
<div style="color:white">${COMPONENT_STATE_VALUES.statusReportData[CUR_TEAM].players.length}</div>
</div>
<div style="font-family:'Abel',sans-serif;font-size:0.8vw;display:flex;width:100%;justify-content:space-between">
<div style="color:gray">Gems</div>
<div style="color:white">${COMPONENT_STATE_VALUES.statusReportData[CUR_TEAM].gems}</div>
</div>
<div style="font-family:'Abel',sans-serif;font-size:0.8vw;display:flex;width:100%;justify-content:space-between">
<div style="color:gray">Level</div>
<div style="color:white">${COMPONENT_STATE_VALUES.statusReportData[CUR_TEAM].level}</div>
</div>
<div style="font-family:'Abel',sans-serif;font-size:0.8vw;display:flex;width:100%;justify-content:space-between;font-weight:500;">
<div style="color:gray">Playerbase strength score</div>
<div style="color:#A4D8D8;font-weight:bold;">${COMPONENT_STATE_VALUES.statusReportData[CUR_TEAM].PBS}</div>
</div>
<div style="font-family:'Abel',sans-serif;font-size:0.8vw;display:flex;width:100%;justify-content:space-between;font-weight:500;">
<div style="color:gray">Potential playerbase strength score</div>
<div style="color:#F49AC2;font-weight:bold;">${COMPONENT_STATE_VALUES.statusReportData[CUR_TEAM].PPBS}</div>
</div>
<div style="font-family:'Abel',sans-serif;font-size:0.8vw;display:flex;width:100%;justify-content:space-between;font-weight:500;">
<div style="color:gray">Maximum damage output (DPS)</div>
<div style="color:#FDFD96;font-weight:bold;">${COMPONENT_STATE_VALUES.statusReportData[CUR_TEAM].potentialOutput}</div>
</div>
</div>
<div style="height:max-content;width:100%;display:flex;justify-content:space-between;color:gray;font-family:'Abel',sans-serif;font-size:0.8vw;padding-bottom:0.5vh;border-bottom:1px solid #1a1a1a;margin-bottom: 0.5vh">
<div>ECP | NAME</div>
<div>SCORE | SHIP</div>
</div>
${
COMPONENT_STATE_VALUES.statusReportData[CUR_TEAM].players.length === 0
?
""
:
COMPONENT_STATE_VALUES.statusReportData[CUR_TEAM].players.map(player => {
return `
<div style="height:max-content;margin-bottom:0.3vh;display:flex;width:100%;height:1vw">
<div style="width:10%;height:100%;display:grid;place-items:center;">
<div style="height:60%; aspect-ratio: 1 / 1; border-radius:9999px; background: ${player.ecp ? "#37dd37" : "#Ff3931"}"></div>
</div>
<div style="width:90%;height:100%;display:flex;justify-content:space-between;font-family:'Abel',sans-serif;font-size:0.8vw;color:white;">
<div>${player.name}</div>
<div style="display:flex">${player.score} <img style="height:0.8vw;aspect-ratio: 1 / 1;object-fit:contain;" src="${SHIP_LINKS.find(url => url.endsWith(`/${player.type}.png`))}"/></div>
</div>
</div>
`
}).join('')
}
</div>
${CUR_TEAM !== "team_3" ? `<div style="height:100%;width:1px;background-color:#1a1a1a"></div>` : ""}`)
}
return arr.join('')
})()
}
</div>
</div>
</div>`)
//FUNCTIONS NOT RELEVANT TO MANIPULATING THE DOM GO BELOW
const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1);
const hueToColorName = (hue) => {
const colorMap = [
{ hueRange: [0, 15], colorName: 'Red' },
{ hueRange: [15, 45], colorName: 'Orange' },
{ hueRange: [45, 75], colorName: 'Yellow' },
{ hueRange: [75, 150], colorName: 'Green' },
{ hueRange: [150, 195], colorName: 'Cyan' },
{ hueRange: [195, 285], colorName: 'Blue' },
{ hueRange: [285, 330], colorName: 'Magenta' },
{ hueRange: [330, 360], colorName: 'Red' }
];
const matchedColor = colorMap.find(entry => hue >= entry.hueRange[0] && hue < entry.hueRange[1]);
return matchedColor ? matchedColor.colorName : 'Undefined';
}
const POTENTIAL = {
// ECP NON-ECP
odyssey: [2.5, 1.5],
x3: [1.7, 1 ],
bastion: [1.4, 0.4],
aries: [1 , 0.3],
barracuda: [1.75, 0.3],
}
const buildItem = (ecp, nonecp, eregen, potential) => ({ecp, nonecp, eregen, potential});
const SHIP_TABLE = {
// The numbers on this table need massive improvement
/**
* ECP - Points when player is ecp
* NONECP - Obvious
* POTENTIAL - Highest points the player can achieve with current path (e.g 2.5 for an ecp playing furystar)
* EREGEN - Obvious
*/
// ECP NON-ECP E-REGEN POTENTIAL
// Tier 7
"701": buildItem(2.5 , 1.5, 150, POTENTIAL.odyssey),
"702": buildItem(1.7 , 1 , 50 , POTENTIAL.x3),
"703": buildItem(1.4 , 0.4, 100, POTENTIAL.bastion),
"704": buildItem(1 , 0.3, 175, POTENTIAL.aries),
// Tier 6
"601": buildItem(1.2 , 0.5, 60 , POTENTIAL.odyssey),
"602": buildItem(1.2 , 0.45, 50 , POTENTIAL.odyssey),
"603": buildItem(0.9 , 0.25, 40 , POTENTIAL.x3),
"604": buildItem(0.5 , 0.25, 48 , POTENTIAL.x3),
"605": buildItem(0.9 , 0.25, 45 , POTENTIAL.x3),
"606": buildItem(0.9 , 0.2 , 45 , POTENTIAL.x3),
"607": buildItem(1.75, 0.3 , 0 , POTENTIAL.barracuda),
"608": buildItem(0.5 , 0.2 , 40 , POTENTIAL.bastion),
// Tier 5
"501": buildItem(1.05, 0.45, 60 , POTENTIAL.odyssey),
"502": buildItem(0.75, 0.3, 40 , POTENTIAL.odyssey),
"503": buildItem(0.2 , 0.1, 50 , POTENTIAL.x3),
"504": buildItem(0.3 , 0.15, 45 , POTENTIAL.x3),
"505": buildItem(0.1 , 0.05, 29 , POTENTIAL.x3),
"506": buildItem(0.9 , 0.5, 50 , POTENTIAL.barracuda),
"507": buildItem(0.15, 0.1, 35 , POTENTIAL.barracuda),
// Tier 4
"401": buildItem(0.3 , 0.05 , 35 , POTENTIAL.odyssey),
"402": buildItem(0.55, 0.25 , 50 , POTENTIAL.odyssey),
"403": buildItem(0.4 , 0.2 , 55 , POTENTIAL.x3),
"404": buildItem(0.3 , 0.15 , 40 , POTENTIAL.x3),
"405": buildItem(0.3 , 0.1 , 30 , POTENTIAL.x3),
"406": buildItem(0.05, 0 , 25 , POTENTIAL.barracuda),
// Tier 3
"301": buildItem(0.2 , 0.07 , 30 , POTENTIAL.odyssey),
"302": buildItem(0.17, 0.05 , 35 , POTENTIAL.x3),
"303": buildItem(0.05, 0 , 16 , POTENTIAL.x3),
"304": buildItem(0.15, 0.05 , 25 , POTENTIAL.barracuda),
// Tier 2
"201": buildItem(0.05, 0 , 25 , POTENTIAL.odyssey),
"202": buildItem(0.02, 0 , 20 , POTENTIAL.x3),
// Fly
"101": buildItem(0 , 0 , 10 , [0,0]),
}
const calculatePlayerScore = (type, ecp) => {
return {
currentScore: (Number(String(type).split('')[0]) / 15) + SHIP_TABLE[String(type)][ecp ? "ecp" : "nonecp"],
potentialScore: (7 / 15) + SHIP_TABLE[String(type)]["potential"][+!ecp], // +!ecp is: First ecp is converted into a boolean and then inverted using !, then turned into a number using +, resulting in index 0 for ecp=true
energyOutput: SHIP_TABLE[String(type)]["eregen"]
}
}
const SHIP_LINKS = [
"https://i.ibb.co/6gjB0Y9/504.png",
"https://i.ibb.co/h1BWddj/505.png",
"https://i.ibb.co/ZG2wQtk/506.png",
"https://i.ibb.co/ZxY43kc/507.png",
"https://i.ibb.co/f8zzwcS/601.png",
"https://i.ibb.co/hXgqvHQ/602.png",
"https://i.ibb.co/HxNmSPY/603.png",
"https://i.ibb.co/DVZrPT7/604.png",
"https://i.ibb.co/w6jZfmK/605.png",
"https://i.ibb.co/p4qBj2k/606.png",
"https://i.ibb.co/4fjJcBC/607.png",
"https://i.ibb.co/wYMGzCs/608.png",
"https://i.ibb.co/ZNmcHfC/701.png",
"https://i.ibb.co/JWZFqVv/702.png",
"https://i.ibb.co/X2w682R/703.png",
"https://i.ibb.co/RQrfMGW/704.png",
"https://i.ibb.co/s3YVpVW/101.png",
"https://i.ibb.co/w7GFPR5/201.png",
"https://i.ibb.co/4JsJz8G/202.png",
"https://i.ibb.co/Pz0xp1s/301.png",
"https://i.ibb.co/M7PWNz7/302.png",
"https://i.ibb.co/4ZKStWk/303.png",
"https://i.ibb.co/df72XT8/304.png",
"https://i.ibb.co/VM2kJgD/401.png",
"https://i.ibb.co/8g6qgBw/402.png",
"https://i.ibb.co/HnqK41P/403.png",
"https://i.ibb.co/s2grnKB/404.png",
"https://i.ibb.co/cvj9FWz/405.png",
"https://i.ibb.co/64fsKPt/406.png",
"https://i.ibb.co/27fLBPx/501.png",
"https://i.ibb.co/3SfYGZX/502.png",
"https://i.ibb.co/9pJt735/503.png"
]
//This runs the SL integration. Do not touch
refreshSL();