Files
garrysmod-coop/loading-screen/app.js
T

359 lines
15 KiB
JavaScript

(function () {
"use strict";
var screenshots = [
{ file: "evac_1.jpg", title: "The road ahead" },
{ file: "evac_2.jpg", title: "Make every round count" },
{ file: "evac_3.jpg", title: "Hold the perimeter" },
{ file: "evac_4.jpg", title: "Extraction route" },
{ file: "abducted_1.jpg", title: "Into the unknown" },
{ file: "abducted_2.jpg", title: "No one fights alone" },
{ file: "abducted_3.jpg", title: "Containment breached" },
{ file: "horror_raid.jpg", title: "After the sirens" },
{ file: "scream_1.jpg", title: "Keep moving" },
{ file: "scream_2.jpg", title: "Lights out" },
{ file: "scream_3.jpg", title: "Something is watching" },
{ file: "southtown.jpg", title: "The last safe block" }
];
var tips = [
"Stay close. Co-op maps are at their best when the squad moves together.",
"Press F3 or type !shop to spend Credits on gear, abilities and companions.",
"Harder enemies award more Score and Credits. Lifetime Score determines your rank.",
"Type !nextmap to vote for the next mission, or !skip if the current map is stuck.",
"Dying can cost Credits, but it never reduces your lifetime Score or rank.",
"Hold TAB in game to compare lifetime scores with the rest of the squad."
];
var state = {
filesTotal: 0,
filesNeeded: 0,
maxNeeded: 0,
filesStarted: {},
startedCount: 0,
stage: 0,
progress: 4,
steamID: "",
profileRequested: "",
leaderboardRequested: false,
currentSlide: 0,
activeLayer: 0
};
function byId(id) {
return document.getElementById(id);
}
function queryValue(name) {
var match = new RegExp("(?:^|&)" + name + "=([^&]*)", "i").exec(window.location.search.substring(1));
return match ? decodeURIComponent(match[1].replace(/\+/g, " ")) : "";
}
function cleanInteger(value) {
value = parseInt(value, 10);
return isFinite(value) && value > 0 ? value : 0;
}
function formatNumber(value) {
var text = String(Math.max(parseInt(value, 10) || 0, 0));
return text.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function formatPlaytime(seconds) {
var minutes = Math.max(Math.floor((parseInt(seconds, 10) || 0) / 60), 0);
var days = Math.floor(minutes / 1440);
var hours = Math.floor((minutes % 1440) / 60);
var mins = minutes % 60;
if (days > 0) return days + "d " + hours + "h";
if (hours > 0) return hours + "h " + mins + "m";
return mins + "m";
}
function titleCaseMap(mapName) {
if (!mapName) return "Preparing mission";
var words = mapName.replace(/\.(bsp|ain)$/i, "").replace(/[_\-]+/g, " ").replace(/\s+/g, " ").trim();
return words.replace(/\b([a-z])/g, function (_, letter) { return letter.toUpperCase(); });
}
function setStage(stage) {
var items = byId("stages").getElementsByTagName("li");
var index;
state.stage = Math.max(state.stage, Math.min(stage, items.length - 1));
for (index = 0; index < items.length; index += 1) {
items[index].className = index < state.stage ? "done" : (index === state.stage ? "active" : "");
}
}
function setProgress(value) {
value = Math.max(state.progress, Math.min(Math.round(value), 99));
state.progress = value;
byId("progress-bar").style.width = value + "%";
byId("percent").textContent = value;
}
function updateFileProgress() {
var needed = state.maxNeeded || state.filesNeeded;
var completedFromNeeded = needed && state.filesNeeded <= needed ? needed - state.filesNeeded : 0;
var completedFromStarts = Math.max(state.startedCount - 1, 0);
var completed = Math.max(completedFromNeeded, completedFromStarts);
var receiving = Math.min(Math.max(completed + (state.startedCount ? 1 : 0), 0), needed || state.filesTotal || 0);
var ratio;
if (needed > 0) {
completed = Math.min(completed, needed);
ratio = Math.min((completed + (state.startedCount ? 0.35 : 0)) / needed, 0.98);
setProgress(12 + ratio * 74);
byId("file-count").textContent = "FILE " + receiving + " OF " + needed + " REQUIRED";
} else if (state.filesTotal > 0) {
byId("file-count").textContent = state.filesTotal + " FILES ON MANIFEST";
}
}
function sourceForFile(fileName) {
if (/workshop|\.gma$|^\s*\d{7,}\s*$/i.test(fileName)) return "WORKSHOP CONTENT";
return "SERVER CONTENT";
}
function stageForStatus(status) {
if (/lua|client info|spawn|precach|ready|starting/i.test(status)) return 3;
if (/download|resource|materials|models|sound|fastdl/i.test(status)) return 2;
if (/workshop|addon|mount/i.test(status)) return 1;
return state.stage;
}
function progressForStatus(status, stage) {
if (stage === 1) return 10;
if (stage === 2) return 14;
if (/client info sent|ready|fully connected/i.test(status)) return 98;
if (/starting lua|lua started/i.test(status)) return 95;
if (/sending client info|precach|creating/i.test(status)) return 90;
if (stage === 3) return 88;
return 6;
}
function requestProfile(steamID) {
var request;
if (!/^7656119\d{10}$/.test(steamID) || state.profileRequested === steamID) return;
state.profileRequested = steamID;
request = new XMLHttpRequest();
request.open("GET", "/api/player?steamid=" + encodeURIComponent(steamID), true);
request.timeout = 3000;
request.onreadystatechange = function () {
var profile;
if (request.readyState !== 4) return;
if (request.status >= 200 && request.status < 300) {
try {
profile = JSON.parse(request.responseText);
showProfile(profile);
return;
} catch (ignore) {}
}
showProfileUnavailable();
};
request.ontimeout = showProfileUnavailable;
request.onerror = showProfileUnavailable;
request.send(null);
}
function monogram(name) {
var words = String(name || "?").replace(/[^a-z0-9 ]/gi, " ").trim().split(/\s+/);
var result = words.length > 1 ? words[0].charAt(0) + words[words.length - 1].charAt(0) : words[0].slice(0, 2);
return result.toUpperCase() || "?";
}
function showProfile(profile) {
var name = profile.playerName || "New operative";
byId("player-name").textContent = name;
byId("profile-monogram").textContent = monogram(name);
byId("profile-loading").className = "profile-loading hidden";
byId("profile-stats").className = "stats";
byId("player-score").textContent = formatNumber(profile.score);
byId("player-credits").textContent = formatNumber(profile.credits);
byId("player-time").textContent = formatPlaytime(profile.playtimeSeconds);
byId("player-rank").textContent = profile.rank || "Newbie";
byId("profile-kicker").textContent = profile.found ? "WELCOME BACK, OPERATIVE" : "NEW OPERATIVE RECORD";
}
function showProfileUnavailable() {
byId("profile-loading").innerHTML = "<span>Progression record available after deployment.</span>";
}
function showLeaderboard(players) {
var list = byId("leaderboard-list");
var index;
var player;
var row;
var position;
var identity;
var name;
var rank;
var score;
while (list.firstChild) list.removeChild(list.firstChild);
if (!players || !players.length) {
row = document.createElement("li");
row.className = "leaderboard-loading";
row.textContent = "No campaign records yet.";
list.appendChild(row);
return;
}
for (index = 0; index < players.length; index += 1) {
player = players[index];
row = document.createElement("li");
position = document.createElement("span");
position.className = "leaderboard-position";
position.textContent = String(player.position || index + 1).replace(/^(\d)$/, "0$1");
identity = document.createElement("div");
name = document.createElement("strong");
name.textContent = player.playerName || "Unknown operative";
rank = document.createElement("small");
rank.textContent = (player.rank || "Newbie") + " · " + formatPlaytime(player.playtimeSeconds);
identity.appendChild(name);
identity.appendChild(rank);
score = document.createElement("span");
score.className = "leaderboard-score";
score.textContent = formatNumber(player.score);
row.appendChild(position);
row.appendChild(identity);
row.appendChild(score);
list.appendChild(row);
}
}
function showLeaderboardUnavailable() {
var list = byId("leaderboard-list");
list.innerHTML = '<li class="leaderboard-loading">Leaderboard temporarily unavailable.</li>';
}
function requestLeaderboard() {
var request;
if (state.leaderboardRequested) return;
state.leaderboardRequested = true;
request = new XMLHttpRequest();
request.open("GET", "/api/leaderboard", true);
request.timeout = 3000;
request.onreadystatechange = function () {
var payload;
if (request.readyState !== 4) return;
if (request.status >= 200 && request.status < 300) {
try {
payload = JSON.parse(request.responseText);
showLeaderboard(payload.players || []);
return;
} catch (ignore) {}
}
showLeaderboardUnavailable();
};
request.ontimeout = showLeaderboardUnavailable;
request.onerror = showLeaderboardUnavailable;
request.send(null);
}
function showSlide(index, immediate) {
var next = screenshots[index % screenshots.length];
var nextLayer = state.activeLayer ? byId("slide-a") : byId("slide-b");
var oldLayer = state.activeLayer ? byId("slide-b") : byId("slide-a");
var image = new Image();
var url = "/assets/screenshots/" + encodeURIComponent(next.file);
image.onload = function () {
nextLayer.style.backgroundImage = "url('" + url + "')";
if (immediate) {
nextLayer.style.transition = "none";
}
nextLayer.className = "slide slide-visible";
oldLayer.className = "slide";
if (immediate) {
window.setTimeout(function () { nextLayer.style.transition = ""; }, 50);
}
state.activeLayer = state.activeLayer ? 0 : 1;
state.currentSlide = index % screenshots.length;
byId("photo-index").textContent = (state.currentSlide + 1 < 10 ? "0" : "") + (state.currentSlide + 1) + " / " + screenshots.length;
byId("photo-title").textContent = next.title;
};
image.src = url;
}
window.GameDetails = function (serverName, serverURL, mapName, maxPlayers, steamID, gamemode) {
var players = cleanInteger(maxPlayers);
byId("server-name").textContent = serverName || "brunner.ninja";
byId("map-title").textContent = titleCaseMap(mapName);
byId("gamemode").textContent = titleCaseMap(gamemode || "feedc0de_coop").toUpperCase();
byId("slots").textContent = players ? players + " PLAYER SLOTS" : "CO-OPERATIVE CAMPAIGNS";
byId("server-state").textContent = "SERVER CONTACT ESTABLISHED";
state.steamID = String(steamID || state.steamID);
setProgress(6);
requestProfile(state.steamID);
};
window.SetFilesTotal = function (total) {
state.filesTotal = Math.max(state.filesTotal, cleanInteger(total));
updateFileProgress();
};
window.SetFilesNeeded = function (needed) {
state.filesNeeded = cleanInteger(needed);
state.maxNeeded = Math.max(state.maxNeeded, state.filesNeeded);
if (state.filesNeeded > 0) setStage(1);
updateFileProgress();
};
window.DownloadingFile = function (fileName) {
fileName = String(fileName || "Preparing content").replace(/\\/g, "/");
if (!state.filesStarted[fileName]) {
state.filesStarted[fileName] = true;
state.startedCount += 1;
}
byId("phase-label").textContent = sourceForFile(fileName);
byId("status-text").textContent = "Receiving required content";
byId("file-name").textContent = fileName;
setStage(sourceForFile(fileName) === "WORKSHOP CONTENT" ? 1 : 2);
updateFileProgress();
};
window.SetStatusChanged = function (status) {
var newStage;
status = String(status || "Working…").replace(/^\s+|\s+$/g, "");
newStage = stageForStatus(status);
setStage(newStage);
byId("status-text").textContent = status;
byId("phase-label").textContent = newStage === 3 ? "PREPARING YOUR SESSION" : (newStage === 2 ? "SERVER CONTENT" : (newStage === 1 ? "WORKSHOP CONTENT" : "JOINING SERVER"));
if (newStage === 3) byId("file-name").textContent = "Downloads complete — initializing game code";
setProgress(progressForStatus(status, newStage));
};
function initialize() {
var map = queryValue("map");
var steamID = queryValue("steamid");
var seed = 0;
var i;
for (i = 0; i < (map + steamID).length; i += 1) seed += (map + steamID).charCodeAt(i);
state.currentSlide = seed % screenshots.length;
byId("tip").textContent = tips[seed % tips.length];
if (map) byId("map-title").textContent = titleCaseMap(map);
if (steamID) {
state.steamID = steamID;
requestProfile(steamID);
}
requestLeaderboard();
showSlide(state.currentSlide, true);
window.setInterval(function () { showSlide(state.currentSlide + 1, false); }, 8000);
if (queryValue("demo") === "1") {
window.GameDetails("brunner.ninja", window.location.href, map || "tbr_coop_thousand_antlions_v3b", 16, steamID || "76561198171048231", "feedc0de_coop");
window.SetFilesTotal(148);
window.SetFilesNeeded(37);
window.SetStatusChanged("Downloading Workshop content");
window.setTimeout(function () { window.DownloadingFile("workshop/706104732/campaign_content.gma"); }, 500);
}
}
initialize();
}());