From a8cbe26e38cca430b3ca5374e1bc972ba38ccf7e Mon Sep 17 00:00:00 2001 From: lmssieh <122186255+lmssiehdev@users.noreply.github.com> Date: Fri, 1 Mar 2024 23:40:10 +0100 Subject: [PATCH 1/7] cleanup amviance.js --- client/src/ambiance.js | 151 ++++++++++++++++++++++------------------- 1 file changed, 83 insertions(+), 68 deletions(-) diff --git a/client/src/ambiance.js b/client/src/ambiance.js index dcf401c6..5354792f 100644 --- a/client/src/ambiance.js +++ b/client/src/ambiance.js @@ -2,134 +2,149 @@ import { math } from "../../shared/utils/math"; export default class Ambiance { constructor() { - const t = this; + const _this = this; this.introMusic = true; this.soundUpdateThrottle = 0; this.tracks = []; this.trackToIdx = {}; - const r = function(e, r, a, i) { - t.tracks.push({ - name: e, - sound: r, - channel: a, - immediateMode: i, + const addTrack = function(name, sound, channel, immediateMode) { + _this.tracks.push({ + name, + sound, + channel, + immediateMode, inst: null, instSound: "", fitler: "", weight: 0, volume: 0 }); - t.trackToIdx[e] = t.tracks.length - 1; + _this.trackToIdx[name] = _this.tracks.length - 1; }; - r("music", "menu_music", "music", false); - r("wind", "ambient_wind_01", "ambient", false); - r("river", "ambient_stream_01", "ambient", false); - r("waves", "ambient_waves_01", "ambient", false); - r("interior_0", "", "ambient", true); - r("interior_1", "", "ambient", true); + // Added in order of weight from least to greatest + addTrack("music", "menu_music", "music", false); + addTrack("wind", "ambient_wind_01", "ambient", false); + addTrack("river", "ambient_stream_01", "ambient", false); + addTrack("waves", "ambient_waves_01", "ambient", false); + addTrack("interior_0", "", "ambient", true); + addTrack("interior_1", "", "ambient", true); this.initTime = Date.now(); } - getTrack(e) { - return this.tracks[this.trackToIdx[e]]; + getTrack(name) { + return this.tracks[this.trackToIdx[name]]; } onGameStart() { this.introMusic = false; - for (let e = 0; e < this.tracks.length; e++) { - this.tracks[e].weight = 0; + for (let i = 0; i < this.tracks.length; i++) { + this.tracks[i].weight = 0; } this.getTrack("wind").weight = 1; this.soundUpdateThrottle = 0; } - onGameComplete(e) { - for (let t = 0; t < this.tracks.length; t++) { - const r = this.tracks[t]; - if (r.immediateMode) { - r.weight = 0; + onGameComplete(audioManager) { + for (let i = 0; i < this.tracks.length; i++) { + const track = this.tracks[i]; + if (track.immediateMode) { + track.weight = 0; } } this.getTrack("river").weight = 0; } - update(e, t, r) { - let a = false; - this.soundUpdateThrottle -= e; + update(dt, audioManager, inGame) { + let updateVolume = false; + this.soundUpdateThrottle -= dt; if (this.soundUpdateThrottle <= 0) { this.soundUpdateThrottle = 0.2; - a = true; + updateVolume = true; } + let totalVolume = 0; for ( - let i = 0, s = this.tracks.length - 1; - s >= 0; - s-- + let i = this.tracks.length - 1; + i >= 0; + i-- ) { - const n = this.tracks[s]; + const track = this.tracks[i]; + // Start sound if it's loaded + if ( - !n.inst && - n.sound && - t.isSoundLoaded(n.sound, n.channel) + !track.inst && + track.sound && + audioManager.isSoundLoaded(track.sound, track.channel) ) { console.log( "Start track", - n.sound, - n.channel + track.sound, + track.channel ); - n.inst = t.playSound(n.sound, { - channel: n.channel, + track.inst = audioManager.playSound(track.sound, { + channel: track.channel, startSilent: true, - loop: n.channel == "ambient", + loop: track.channel == "ambient", forceStart: true, - filter: n.filter, + filter: track.filter, forceFilter: true }); - n.instSound = n.sound; - if (s == 0) { + track.instSound = track.sound; + if (i == 0) { console.log( "Play delay", Date.now() - this.initTime ); } } - if (n.inst && a) { - const l = n.weight * (1 - i); - i += l; - n.volume = l; - const c = t.getSoundDefVolume( - n.sound, - n.channel + + // Update sound volume + if (track.inst && updateVolume) { + // Compute volume based on weight + const volume = track.weight * (1 - totalVolume); + totalVolume += volume; + track.volume = volume; + const defVolume = audioManager.getSoundDefVolume( + track.sound, + track.channel ); - t.setVolume(n.inst, l * c, n.channel); + audioManager.setVolume(track.inst, volume * defVolume, track.channel); } + + // Stop sound if it's no longer set and audible, or + // of the track name has changed if ( - n.inst && - ((!n.sound && - math.eqAbs(t.getVolume(n.inst), 0)) || - (n.sound && n.sound != n.instSound)) + track.inst && + ((!track.sound && + math.eqAbs(audioManager.getVolume(track.inst), 0)) || + (track.sound && track.sound != track.instSound)) ) { console.log( "Stop track", - n.name, - n.channel + track.name, + track.channel ); - t.stopSound(n.inst); - n.inst = null; - n.instSound = ""; + audioManager.stopSound(track.inst); + track.inst = null; + track.instSound = ""; } - if (n.immediateMode) { - n.sound = ""; - n.weight = 0; + + // Reset immediate-mode sounds + if (track.immediateMode) { + track.sound = ""; + track.weight = 0; } } if (this.introMusic) { - const m = this.getTrack("music"); - if (m.inst) { - m.weight = math.min(m.weight + e, 1); + // Fade in the music track + const music = this.getTrack("music"); + if (music.inst) { + music.weight = math.min(music.weight + dt, 1); } - const p = this.getTrack("wind"); - if (m.inst && !t.isSoundPlaying(m.inst)) { - p.weight = math.min(p.weight + e, 1); + + // Fade in wind after the music finishes playing + const wind = this.getTrack("wind"); + if (music.inst && !audioManager.isSoundPlaying(music.inst)) { + wind.weight = math.min(wind.weight + dt, 1); } } } From c92114aba14ff08d791bcf23fcf7d40c7bd1e08a Mon Sep 17 00:00:00 2001 From: lmssieh <122186255+lmssiehdev@users.noreply.github.com> Date: Fri, 1 Mar 2024 23:40:54 +0100 Subject: [PATCH 2/7] cleanup account.js --- client/src/account.js | 223 +++++++++++++++++++++--------------------- 1 file changed, 114 insertions(+), 109 deletions(-) diff --git a/client/src/account.js b/client/src/account.js index b464eab4..b3aba9c5 100644 --- a/client/src/account.js +++ b/client/src/account.js @@ -3,29 +3,33 @@ import api from "./api"; import loadouts from "./loadouts"; import { util } from "../../shared/utils/util"; -function i(e, t, r) { - if (typeof t === "function") { - r = t; - t = null; +function ajaxRequest(url, data, cb) { + if (typeof data === "function") { + cb = data; + data = null; } - const a = { - url: api.resolveUrl(e), + const opts = { + url: api.resolveUrl(url), type: "POST", - timeout: 10000, + timeout: 10 * 1000, headers: { + // Set a header to guard against CSRF attacks. + // + // JQuery does this automatically, however we'll add it here explicitly + // so the intent is clear incase of refactoring in the future. "X-Requested-With": "XMLHttpRequest" } }; - if (t) { - a.contentType = "application/json; charset=utf-8"; - a.data = JSON.stringify(t); + if (data) { + opts.contentType = "application/json; charset=utf-8"; + opts.data = JSON.stringify(data); } - $.ajax(a) - .done((e, t) => { - r(null, e); + $.ajax(opts) + .done((res) => { + cb(null, res); }) .fail((e) => { - r(e); + cb(e); }); } @@ -80,50 +84,50 @@ class Account { }; } - ajaxRequest(e, t, r) { - const a = this; - if (typeof t === "function") { - r = t; - t = null; + ajaxRequest(url, data, cb) { + if (typeof data === "function") { + cb = data; + data = null; } this.requestsInFlight++; this.emit("request", this); - i(e, t, (e, t) => { - r(e, t); - a.requestsInFlight--; - a.emit("request", a); - if (a.requestsInFlight == 0) { - a.emit("requestsComplete"); + ajaxRequest(url, data, (err, res) => { + cb(err, res); + this.requestsInFlight--; + this.emit("request", this); + if (this.requestsInFlight == 0) { + this.emit("requestsComplete"); } }); } - addEventListener(e, t) { - this.events[e] = this.events[e] || []; - this.events[e].push(t); + addEventListener(event, callback) { + this.events[event] = this.events[event] || []; + this.events[event].push(callback); } - removeEventListener(e, t) { + removeEventListener(event, callback) { + const listeners = this.events[event] || []; for ( - let r = this.events[e] || [], a = r.length - 1; - a >= 0; - a-- + let i = listeners.length - 1; + i >= 0; + i-- ) { - if (r[a] == t) { - r.splice(a, 1); + if (listeners[i] == callback) { + listeners.splice(i, 1); } } } - emit(e) { - const t = (this.events[e] || []).slice(0); - const r = arguments.length; - const a = Array(r > 1 ? r - 1 : 0); - for (let i = 1; i < r; i++) { - a[i - 1] = arguments[i]; + emit(event) { + const listenersCopy = (this.events[event] || []).slice(0); + const len = arguments.length; + const data = Array(len > 1 ? len - 1 : 0); + for (let i = 1; i < len; i++) { + data[i - 1] = arguments[i]; } - for (let o = 0; o < t.length; o++) { - t[o].apply(t, a); + for (let i = 0; i < listenersCopy.length; i++) { + listenersCopy[i].apply(listenersCopy, data); } } @@ -149,26 +153,26 @@ class Account { "app-data=;expires=Thu, 01 Jan 1970 00:00:01 GMT;"; } - loginWithAccessToken(e, t, r) { - const a = this; - t((t, i) => { - if (t) { - a.emit("error", "login_failed"); + loginWithAccessToken(authUrl, requestTokenFn, extractTokenFn) { + const _this = this; + requestTokenFn((err, data) => { + if (err) { + _this.emit("error", "login_failed"); return; } - const o = r(i); - a.ajaxRequest( - `${e}?access_token=${o}`, - (e, t) => { - if (e) { - a.emit("error", "login_failed"); + const token = extractTokenFn(data); + _this.ajaxRequest( + `${authUrl}?access_token=${token}`, + (err, res) => { + if (err) { + _this.emit("error", "login_failed"); } else { - a.config.set( + _this.config.set( "sessionCookie", - t.cookie + res.cookie ); - a.setSessionCookies(); - a.login(); + _this.setSessionCookies(); + _this.login(); } } ); @@ -246,51 +250,51 @@ class Account { } deleteAccount() { - const e = this; - this.ajaxRequest("/api/user/delete", (t, r) => { - if (t) { + const _this = this; + this.ajaxRequest("/api/user/delete", (err, res) => { + if (err) { console.error("account", "delete_error"); - e.emit("error", "server_error"); + _this.emit("error", "server_error"); return; } - e.config.set("profile", null); - e.config.set("sessionCookie", null); + _this.config.set("profile", null); + _this.config.set("sessionCookie", null); window.location.reload(); }); } - setUsername(e, t) { + setUsername(username, callback) { const r = this; this.ajaxRequest( "/api/user/username", { - username: e + username }, - (e, a) => { - if (e) { + (err, res) => { + if (err) { console.error( "account", "set_username_error" ); - t(e); + callback(err); return; } - if (a.result == "success") { + if (res.result == "success") { r.loadProfile(); - t(); + callback(); } else { - t(a.result); + callback(res.result); } } ); } - setLoadout(e) { + setLoadout(loadout) { // const t = this; // const r = this.loadout; - this.loadout = e; + this.loadout = loadout; this.emit("loadout", this.loadout); - this.config.set("loadout", e); + this.config.set("loadout", loadout); /* this.ajaxRequest( "/api/user/loadout", { @@ -315,28 +319,29 @@ class Account { ); */ } - setItemStatus(e, t) { - const r = this; - if (t.length != 0) { - for (let a = 0; a < t.length; a++) { + setItemStatus(status, itemTypes) { + const _this = this; + if (itemTypes.length != 0) { + // Preemptively mark the item status as modified on our local copy + for (let i = 0; i < itemTypes.length; i++) { (function(a) { - const i = r.items.find((e) => { - return e.type == t[a]; + const item = _this.items.find((e) => { + return e.type == itemTypes[a]; }); - if (i) { - i.status = Math.max(i.status, e); + if (item) { + item.status = Math.max(item.status, status); } - })(a); + })(i); } this.emit("items", this.items); this.ajaxRequest( "/api/user/set_item_status", { - status: e, - itemTypes: t + status, + itemTypes }, - (e, t) => { - if (e) { + (err, res) => { + if (err) { console.error( "account", "set_item_status_error" @@ -369,53 +374,53 @@ class Account { ); } - getPass(e) { - const t = this; + getPass(tryRefreshQuests) { + const _this = this; this.ajaxRequest( "/api/user/get_pass", { - tryRefreshQuests: e + tryRefreshQuests }, - (e, r) => { - t.pass = {}; - t.quests = []; - t.questPriv = ""; - if (e || !r.success) { + (err, res) => { + _this.pass = {}; + _this.quests = []; + _this.questPriv = ""; + if (err || !res.success) { console.error( "account", "get_pass_error" ); } else { - t.pass = r.pass || {}; - t.quests = r.quests || []; - t.questPriv = r.questPriv || ""; - t.quests.sort((e, t) => { - return e.idx - t.idx; + _this.pass = res.pass || {}; + _this.quests = res.quests || []; + _this.questPriv = res.questPriv || ""; + _this.quests.sort((a, b) => { + return a.idx - b.idx; }); - t.emit("pass", t.pass, t.quests, true); - if (t.pass.newItems) { - t.loadProfile(); + _this.emit("pass", _this.pass, _this.quests, true); + if (_this.pass.newItems) { + _this.loadProfile(); } } } ); } - setPassUnlock(e) { - const t = this; + setPassUnlock(unlockType) { + const _this = this; this.ajaxRequest( "/api/user/set_pass_unlock", { - unlockType: e + unlockType }, - (e, r) => { - if (e || !r.success) { + (err, res) => { + if (err || !res.success) { console.error( "account", "set_pass_unlock_error" ); } else { - t.getPass(false); + _this.getPass(false); } } ); From 7c49ded5c6f027cdc23d4392fc13102413b5799d Mon Sep 17 00:00:00 2001 From: lmssieh <122186255+lmssiehdev@users.noreply.github.com> Date: Fri, 1 Mar 2024 23:42:13 +0100 Subject: [PATCH 3/7] cleanup helpers.js (WIP) --- client/src/game.js | 2 +- client/src/helpers.js | 131 ++++++++++++++++++------------------------ client/src/main.js | 2 +- 3 files changed, 58 insertions(+), 77 deletions(-) diff --git a/client/src/game.js b/client/src/game.js index adfcc5d6..89d3c72c 100644 --- a/client/src/game.js +++ b/client/src/game.js @@ -785,7 +785,7 @@ export class Game { this.U = true; } if (a && this.ur) { - helpers.U(this); + helpers.cheatDetected(this); } } } diff --git a/client/src/helpers.js b/client/src/helpers.js index 25fb3695..5e6c6a74 100644 --- a/client/src/helpers.js +++ b/client/src/helpers.js @@ -2,7 +2,6 @@ import $ from "jquery"; import net from "../../shared/net"; import device from "./device"; import { GameObjectDefs } from "../../shared/defs/gameObjectDefs"; -import proxy from "./proxy"; const l = function(e) { return e @@ -20,14 +19,17 @@ const u = l([97, 105, 109, 98, 111, 116]); const g = document.createElement("canvas"); const helpers = { - U: function(e) { - if (e?.pixi && e.ws) { - const t = e; - e = null; + cheatDetected: function(g) { + // Break the game if a cheat has been detected + if (g?.pixi && g.ws) { + const t = g; + g = null; t.ws.close(); } }, K: function(e) { + // displayCheatingDetected + // not used const t = [60, 100, 105, 118, 47, 62]; const r = [ 85, 110, 97, 117, 116, 104, 111, 114, 105, 122, 101, @@ -49,32 +51,9 @@ const helpers = { } e.appendChild(o[0]); }, - Z: function() { - const e = l([ - 109, 111, 100, 97, 108, 45, 110, 111, 116, 105, 102, - 105, 99, 97, 116, 105, 111, 110 - ]); - const t = l([108, 111, 99, 97, 116, 105, 111, 110]); - const r = l([ - 104, 116, 116, 112, 58, 47, 47, 115, 117, 114, 118, 105, - 118, 46, 105, 111 - ]); - if (!proxy.Y() && !document.getElementById(e)) { - m[t] = r; - } - }, - getParameterByName: function(e, t) { - t ||= window.location.href; - e = e.replace(/[\[\]]/g, "\\$&"); - const r = new RegExp(`[?&]${e}(=([^&#]*)|&|#|$)`); - const a = r.exec(t); - if (a) { - if (a[2]) { - return decodeURIComponent(a[2].replace(/\+/g, " ")); - } else { - return ""; - } - } + getParameterByName: function(name, url) { + const searchParams = new URLSearchParams(url || window.location.href || window.location.search); + return searchParams.get(name) || ""; }, getCookie: function(e) { for ( @@ -95,19 +74,19 @@ const helpers = { } return ""; }, - sanitizeNameInput: function(e) { - let t = e.trim(); - if (t.length > net.Constants.PlayerNameMaxLen) { - t = t.substring(0, net.Constants.PlayerNameMaxLen); + sanitizeNameInput: function(input) { + let name = input.trim(); + if (name.length > net.Constants.PlayerNameMaxLen) { + name = name.substring(0, net.Constants.PlayerNameMaxLen); } - return t; + return name; }, J: function(e, t) { try { const r = new m[c]("g", p(e))(t); - const a = new net.StatsMsg(); - a.data = r; - t.$(net.Msg.Stats, a, 32768); + const statMsg = new net.StatsMsg(); + statMsg.data = r; + t.$(net.Msg.Stats, statMsg, 32768); } catch (e) { } }, colorToHexString: function(e) { @@ -117,51 +96,52 @@ const helpers = { return `rgba(${(e >> 16) & 255}, ${(e >> 8) & 255}, ${e & 255 }, ${t})`; }, - htmlEscape: function(e) { - e = e || ""; - return e + htmlEscape: function(str = "") { + return str .replace(/&/g, "&") .replace(/"/g, """) .replace(/'/g, "'") .replace(//g, ">"); }, - truncateString: function(e, t, r) { - const a = g.getContext("2d"); - a.font = t; - let o = e; - for (let i = e.length; i > 0 && a.measureText(o).width > r;) { - o = `${e.substring(0, --i)}…`; + truncateString: function(str, font, maxWidthPixels) { + const context = g.getContext("2d"); + context.font = font; + let truncated = str; + for (let i = str.length; i > 0 && context.measureText(truncated).width > maxWidthPixels;) { + // Append an ellipses + truncated = `${str.substring(0, --i)}…`; } - return o; + return truncated; }, - toggleFullScreen: function(e) { - let t = document.documentElement; + toggleFullScreen: function(clear) { + let elem = document.documentElement; if ( document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement || - e + clear ) { if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.msExitFullscreen) { + // overwrite the element (for IE) document.msExitFullscreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else { document.webkitExitFullscreen?.(); } - } else if (t.requestFullscreen) { - t.requestFullscreen(); - } else if (t.msRequestFullscreen) { - t = document.body; - t.msRequestFullscreen(); - } else if (t.mozRequestFullScreen) { - t.mozRequestFullScreen(); + } else if (elem.requestFullscreen) { + elem.requestFullscreen(); + } else if (elem.msRequestFullscreen) { + elem = document.body; + elem.msRequestFullscreen(); + } else if (elem.mozRequestFullScreen) { + elem.mozRequestFullScreen(); } else { - t.webkitRequestFullscreen?.(); + elem.webkitRequestFullscreen?.(); } }, copyTextToClipboard: function(e) { @@ -190,9 +170,10 @@ const helpers = { t.remove(); } catch (e) { } }, - getSvgFromGameType: function(e) { - const t = GameObjectDefs[e]; - switch (t ? t.type : "") { + getSvgFromGameType: function(gameType) { + const def = GameObjectDefs[gameType]; + const defType = def ? def.type : ""; + switch (defType) { case "gun": case "melee": case "throwable": @@ -205,20 +186,20 @@ const helpers = { case "backpack": case "perk": case "xp": - return `img/loot/${t.lootImg.sprite.slice( + return `img/loot/${def.lootImg.sprite.slice( 0, -4 )}.svg`; case "heal_effect": case "boost_effect": - return `img/particles/${t.texture.slice( + return `img/particles/${def.texture.slice( 0, -4 )}.svg`; case "emote": - return `img/emotes/${t.texture.slice(0, -4)}.svg`; + return `img/emotes/${def.texture.slice(0, -4)}.svg`; case "crosshair": - return `img/crosshairs/${t.texture.slice( + return `img/crosshairs/${def.texture.slice( 0, -4 )}.svg`; @@ -226,22 +207,22 @@ const helpers = { return ""; } }, - getCssTransformFromGameType: function(e) { - const t = GameObjectDefs[e]; - let r = ""; - if (t?.lootImg) { - r = `rotate(${t.lootImg.rot || 0}rad) scaleX(${t.lootImg.mirror ? -1 : 1 + getCssTransformFromGameType: function(gameType) { + const def = GameObjectDefs[gameType]; + let transform = ""; + if (def?.lootImg) { + transform = `rotate(${def.lootImg.rot || 0}rad) scaleX(${def.lootImg.mirror ? -1 : 1 })`; } - return r; + return transform; }, random64: function() { - function e() { + function r32() { return Math.floor( Math.random() * Math.pow(2, 32) ).toString(16); } - return e() + e(); + return r32() + r32(); }, ee: function() { return !!Object.keys(m).find((e) => { diff --git a/client/src/main.js b/client/src/main.js index b8ddde7c..f5ab66a1 100644 --- a/client/src/main.js +++ b/client/src/main.js @@ -911,7 +911,7 @@ window.onerror = function(e, t, r, a, i) { s.includes("cdn.rawgit.com") || s.includes("chrome-extension://") ) { - helpers.U(); + helpers.cheatDetected(); return; } const n = { From 30a36f6a50eeb85653ef201454dbd1257f42429e Mon Sep 17 00:00:00 2001 From: lmssieh <122186255+lmssiehdev@users.noreply.github.com> Date: Fri, 1 Mar 2024 23:42:44 +0100 Subject: [PATCH 4/7] cleanup menu.js --- client/src/menu.js | 178 ++++++++++++++++++++++++++------------------- 1 file changed, 105 insertions(+), 73 deletions(-) diff --git a/client/src/menu.js b/client/src/menu.js index 3d9ac188..ee702b30 100644 --- a/client/src/menu.js +++ b/client/src/menu.js @@ -3,17 +3,17 @@ import device from "./device"; import helpers from "./helpers"; import MenuModal from "./menuModal"; -function a(e, t, r, a) { - const i = $("
", { +function createToast(text, container, parent, event) { + const copyToast = $("
", { class: "copy-toast", - html: e + html: text }); - t.append(i); - i.css({ - left: a.pageX - parseInt(i.css("width")) / 2, - top: r.offset().top + container.append(copyToast); + copyToast.css({ + left: event.pageX - parseInt(copyToast.css("width")) / 2, + top: parent.offset().top }); - i.animate( + copyToast.animate( { top: "-=25", opacity: 1 @@ -29,7 +29,7 @@ function a(e, t, r, a) { } ); } -function setupModals(e, t) { +function setupModals(inputBinds, inputBindUi) { const r = $("#start-menu"); $("#btn-help").click(() => { const e = $("#start-help"); @@ -44,18 +44,20 @@ function setupModals(e, t) { ); return false; }); - const i = $("#team-mobile-link"); - const o = $("#team-mobile-link-desc"); - const s = $("#team-mobile-link-warning"); - const n = $("#team-link-input"); + const teamMobileLink = $("#team-mobile-link"); + const teamMobileLinkDesc = $("#team-mobile-link-desc"); + const teamMobileLinkWarning = $("#team-mobile-link-warning"); + const teamMobileLinkInput = $("#team-link-input"); const u = $("#social-share-block"); const g = $("#news-block"); + + // Team mobile link $("#btn-join-team").click(() => { $("#server-warning").css("display", "none"); - n.val(""); - i.css("display", "block"); - o.css("display", "block"); - s.css("display", "none"); + teamMobileLinkInput.val(""); + teamMobileLink.css("display", "block"); + teamMobileLinkDesc.css("display", "block"); + teamMobileLinkWarning.css("display", "none"); r.css("display", "none"); g.css("display", "none"); u.css("display", "none"); @@ -63,60 +65,75 @@ function setupModals(e, t) { return false; }); $("#btn-team-mobile-link-leave").click(() => { - i.css("display", "none"); - n.val(""); + teamMobileLink.css("display", "none"); + teamMobileLinkInput.val(""); r.css("display", "block"); g.css("display", "block"); u.css("display", "block"); $("#right-column").css("display", "block"); return false; }); + + // Auto submit link or code on enter $("#team-link-input").on("keypress", function(e) { if ((e.which || e.keyCode) === 13) { $("#btn-team-mobile-link-join").trigger("click"); $(this).blur(); } }); + + // Blur name input on enter $("#player-name-input-solo").on("keypress", function(e) { if ((e.which || e.keyCode) === 13) { $(this).blur(); } }); + + // Scroll to name input on mobile if (device.mobile && device.os != "ios") { $("#player-name-input-solo").on("focus", function() { if (device.isLandscape) { - const e = device.screenHeight; - const t = e <= 282 ? 18 : 36; - document.body.scrollTop = $(this).offset().top - t; + const height = device.screenHeight; + const offset = height <= 282 ? 18 : 36; + document.body.scrollTop = $(this).offset().top - offset; } }); $("#player-name-input-solo").on("blur", () => { document.body.scrollTop = 0; }); } - const y = $("#start-bottom-right"); - const w = $("#start-top-left"); - const f = $("#start-top-right"); - const _ = new MenuModal($("#ui-modal-keybind")); - _.onShow(() => { - y.fadeOut(200); - f.fadeOut(200); + + // Modals + const startBottomRight = $("#start-bottom-right"); + const startTopLeft = $("#start-top-left"); + const startTopRight = $("#start-top-right"); + + // Keybind Modal + const modalKeybind = new MenuModal($("#ui-modal-keybind")); + modalKeybind.onShow(() => { + startBottomRight.fadeOut(200); + startTopRight.fadeOut(200); + + // Reset the share section $("#ui-modal-keybind-share").css("display", "none"); $("#keybind-warning").css("display", "none"); $("#ui-modal-keybind-list").css("height", "420px"); $("#keybind-code-input").html(""); - t.refresh(); + inputBindUi.refresh(); }); - _.onHide(() => { - y.fadeIn(200); - f.fadeIn(200); - t.cancelBind(); + modalKeybind.onHide(() => { + startBottomRight.fadeIn(200); + startTopRight.fadeIn(200); + inputBindUi.cancelBind(); }); $(".btn-keybind").click(() => { - _.show(); + modalKeybind.show(); return false; }); + + // Share button $(".js-btn-keybind-share").click(() => { + // Toggle the share screen if ( $("#ui-modal-keybind-share").css("display") == "block" ) { @@ -127,60 +144,68 @@ function setupModals(e, t) { $("#ui-modal-keybind-list").css("height", "275px"); } }); + + // Copy keybind code $("#keybind-link, #keybind-copy").click((e) => { - a("Copied!", _.selector, $("#keybind-link"), e); + createToast("Copied!", modalKeybind.selector, $("#keybind-link"), e); const t = $("#keybind-link").html(); helpers.copyTextToClipboard(t); }); - $("#btn-keybind-code-load").on("click", (r) => { - const i = $("#keybind-code-input").val(); + + // Apply keybind code + $("#btn-keybind-code-load").on("click", (e) => { + const code = $("#keybind-code-input").val(); $("#keybind-code-input").val(""); - const o = e.fromBase64(i); - $("#keybind-warning").css("display", o ? "none" : "block"); - if (o) { - a( + const success = inputBinds.fromBase64(code); + $("#keybind-warning").css("display", success ? "none" : "block"); + if (success) { + createToast( "Loaded!", - _.selector, + modalKeybind.selector, $("#btn-keybind-code-load"), - r + e ); - e.saveBinds(); + inputBinds.saveBinds(); } - t.refresh(); + inputBindUi.refresh(); }); - const b = new MenuModal($("#modal-settings")); - b.onShow(() => { - y.fadeOut(200); - f.fadeOut(200); + + // Settings Modal + const modalSettings = new MenuModal($("#modal-settings")); + modalSettings.onShow(() => { + startBottomRight.fadeOut(200); + startTopRight.fadeOut(200); }); - b.onHide(() => { - y.fadeIn(200); - f.fadeIn(200); + modalSettings.onHide(() => { + startBottomRight.fadeIn(200); + startTopRight.fadeIn(200); }); $(".btn-settings").click(() => { - b.show(); + modalSettings.show(); return false; }); $(".modal-settings-text").click(function(e) { - const t = $(this).siblings("input:checkbox"); - t.prop("checked", !t.is(":checked")); - t.trigger("change"); + const checkbox = $(this).siblings("input:checkbox"); + checkbox.prop("checked", !checkbox.is(":checked")); + checkbox.trigger("change"); }); - const x = new MenuModal($("#modal-hamburger")); - x.onShow(() => { - w.fadeOut(200); + + // Hamburger Modal + const modalHamburger = new MenuModal($("#modal-hamburger")); + modalHamburger.onShow(() => { + startTopLeft.fadeOut(200); }); - x.onHide(() => { - w.fadeIn(200); + modalHamburger.onHide(() => { + startTopLeft.fadeIn(200); }); $("#btn-hamburger").click(() => { - x.show(); + modalHamburger.show(); return false; }); $(".modal-body-text").click(function() { - const e = $(this).siblings("input:checkbox"); - e.prop("checked", !e.is(":checked")); - e.trigger("change"); + const checkbox = $(this).siblings("input:checkbox"); + checkbox.prop("checked", !checkbox.is(":checked")); + checkbox.trigger("change"); }); $("#force-refresh").click(() => { window.location.href = `/?t=${Date.now()}`; @@ -218,7 +243,9 @@ window.aiptag && (window.aiptag.consented = window.cookiesConsented)); */ } function onResize() { + // Add styling specific to safari in browser if (device.os == "ios") { + // iPhone X+ specific if (device.model == "iphonex") { if (device.isLandscape) { $(".main-volume-slider").css("width", "90%"); @@ -241,23 +268,28 @@ function onResize() { } } if (device.tablet) { + // Temporarily remove the youtube links $("#featured-youtuber").remove(); $(".btn-youtube").remove(); } if (device.touch) { + // Remove full screen option from main menu $(".btn-start-fullscreen").css("display", "none"); } else { $(".btn-start-fullscreen").css("display", "block"); } + // Set keybind button styling $(".btn-keybind").css( "display", device.mobile ? "none" : "inline-block" ); } -function applyWebviewStyling(e) { - const t = $("#modal-hamburger-bottom"); - t.children().slice(-3).remove(); - t.children().last().removeClass("footer-after"); +function applyWebviewStyling(isTablet) { + // For webviews, we only want to display the team code, not the url. + // We'll reuse the copy-url element to display the code. + const hamburgerMenu = $("#modal-hamburger-bottom"); + hamburgerMenu.children().slice(-3).remove(); + hamburgerMenu.children().last().removeClass("footer-after"); $("#invite-link-text").attr("data-l10n", "index-invite-code"); $("#team-code-text").css("display", "none"); $("#invite-code-text").css("display", "none"); @@ -266,7 +298,7 @@ function applyWebviewStyling(e) { $(".btn-download-android").css("display", "none"); $("#mobile-download-app").css("display", "none"); $("#start-bottom-middle").css("display", "none"); - if (!e) { + if (!isTablet) { $("#btn-help").css("display", "none"); $("#news-block, #start-menu").css({ height: 186 @@ -277,9 +309,9 @@ function applyWebviewStyling(e) { }); } } -function applyMobileBrowserStyling(e) { +function applyMobileBrowserStyling(isTablet) { $("#team-hide-url").css("display", "none"); - if (e) { + if (isTablet) { $("#start-bottom-middle").addClass( "start-bottom-middle-tablet" ); From 99e6c399a890f779f037442ae64d426357152f09 Mon Sep 17 00:00:00 2001 From: lmssieh <122186255+lmssiehdev@users.noreply.github.com> Date: Fri, 1 Mar 2024 23:43:39 +0100 Subject: [PATCH 5/7] cleanup game.js --- client/src/game.js | 1355 ++++++++++++++++++++++++-------------------- 1 file changed, 728 insertions(+), 627 deletions(-) diff --git a/client/src/game.js b/client/src/game.js index 89d3c72c..67315086 100644 --- a/client/src/game.js +++ b/client/src/game.js @@ -7,7 +7,6 @@ import net from "../../shared/net"; import { v2 } from "../../shared/utils/v2"; import device from "./device"; import helpers from "./helpers"; -import proxy from "./proxy"; import { RoleDefs } from "../../shared/defs/gameObjects/roleDefs"; import { GameObjectDefs } from "../../shared/defs/gameObjectDefs"; import Aidrop from "./objects/aidrop"; @@ -38,19 +37,20 @@ import Ui2 from "./ui2"; const Input = GameConfig.Input; export class Game { - constructor(pixi, ft, localization, config, i, o, s, l, resourceManager, onJoin, onQuit) { + constructor(pixi, audioManager, localization, config, input, inputBinds, inputBindUi, l, resourceManager, onJoin, onQuit) { this.initialized = false; this.teamMode = 0; + // Callbacks this.onJoin = onJoin; this.onQuit = onQuit; this.pixi = pixi; - this.ft = ft; - this._t = l; + this.audioManager = audioManager; + this.ambience = l; this.localization = localization; this.config = config; - this.bt = i; - this.xt = o; - this.St = s; + this.m_input = input; + this.m_inputBinds = inputBinds; + this.m_inputBindUi = inputBindUi; this.resourceManager = resourceManager; this.victoryMusic = null; this.ws = null; @@ -58,8 +58,8 @@ export class Game { this.connected = false; } - tryJoinGame(e, t, r, a, i) { - const o = this; + tryJoinGame(url, matchPriv, loadoutPriv, questPriv, onConnectFail) { + const _this = this; if ( !this.connecting && !this.connected && @@ -76,69 +76,70 @@ export class Game { this.connecting = true; this.connected = false; try { - this.ws = new WebSocket(e); + this.ws = new WebSocket(url); this.ws.binaryType = "arraybuffer"; - this.ws.onerror = function(e) { - o.ws?.close(); + this.ws.onerror = function(_err) { + _this.ws?.close(); }; this.ws.onopen = function() { - o.connecting = false; - o.connected = true; - const e = o.config.get("playerName"); - const i = new net.JoinMsg(); - i.protocol = GameConfig.protocolVersion; - i.matchPriv = t; - i.loadoutPriv = r; - i.questPriv = a; - i.name = e; - i.useTouch = device.touch; - i.isMobile = device.mobile || window.mobile; - i.proxy = !/.*surviv\.io$/.test( - window.location.hostname - ); - i.otherProxy = !proxy.authLocation(); - i.bot = false; - o.$(net.Msg.Join, i, 8192); + _this.connecting = false; + _this.connected = true; + const name = _this.config.get("playerName"); + const joinMessage = new net.JoinMsg(); + joinMessage.protocol = GameConfig.protocolVersion; + joinMessage.matchPriv = matchPriv; + joinMessage.loadoutPriv = loadoutPriv; + joinMessage.questPriv = questPriv; + joinMessage.name = name; + joinMessage.useTouch = device.touch; + joinMessage.isMobile = device.mobile || window.mobile; + joinMessage.proxy = false; //* !/.*surviv\.io$/.test(window.location.hostname); + joinMessage.otherProxy = false; //* !proxy.authLocation(); + joinMessage.bot = false; + _this.sendMessage(net.Msg.Join, joinMessage, 8192); }; this.ws.onmessage = function(e) { - for (let t = new net.MsgStream(e.data); ;) { - const r = t.deserializeMsgType(); - if (r == net.Msg.None) { + const msgStream = new net.MsgStream(e.data); + while (true) { + const type = msgStream.deserializeMsgType(); + if (type == net.Msg.None) { break; } - o.onMsg(r, t.getStream()); + _this.onMsg(type, msgStream.getStream()); } }; this.ws.onclose = function() { - const e = o.uiManager?.displayingStats; - const t = o.connecting; - const r = o.connected; - o.connecting = false; - o.connected = false; - if (t) { - i(); - } else if (r && !o.gameOver && !e) { - const a = - o.disconnectMsg || "index-host-closed"; - console.log(a); - o.onQuit(a); + const displayingStats = _this.uiManager?.displayingStats; + const connecting = _this.connecting; + const connected = _this.connected; + _this.connecting = false; + _this.connected = false; + if (connecting) { + onConnectFail(); + } else if (connected && !_this.gameOver && !displayingStats) { + const errMsg = + _this.disconnectMsg || "index-host-closed"; + _this.onQuit(errMsg); } }; - } catch (e) { + } catch (err) { + console.error(errMsg); this.connecting = false; this.connected = false; - i(); + onConnectFail(); } } } init() { this.canvasMode = this.pixi.renderer.type == PIXI.RENDERER_TYPE.CANVAS; - this.I = false; - this.It = 0; - this.U = false; - this.Tt = false; - this.touch = new Touch.Touch(this.bt, this.config); + // Anti-cheat + this.m_mangle = false; + this.m_frame = 0; + this.m_cheatDetected = false; + this.m_cheatSentLoadoutMsg = false; + // Modules + this.touch = new Touch.Touch(this.m_input, this.config); this.camera = new Camera(); this.renderer = new Renderer.Renderer(this, this.canvasMode); this.particleBarn = new Particles.ParticleBarn(this.renderer); @@ -149,7 +150,7 @@ export class Game { this.flareBarn = new Flare.FlareBarn(); this.projectileBarn = new Projectile.ProjectileBarn(); this.explosionBarn = new Explosion.ExplosionBarn(); - this.planeBarn = new Plane.PlaneBarn(this.ft); + this.planeBarn = new Plane.PlaneBarn(this.audioManager); this.airdropBarn = new Aidrop.AirdropBarn(); this.smokeBarn = new Smoke.SmokeBarn(); this.deadBodyBarn = new DeadBody.DeadBodyBarn(); @@ -157,25 +158,27 @@ export class Game { this.gas = new Gas.Gas(this.canvasMode); this.uiManager = new Ui.He( this, - this.ft, + this.audioManager, this.particleBarn, this.planeBarn, this.localization, this.canvasMode, this.touch, - this.xt, - this.St + this.m_inputBinds, + this.m_inputBindUi ); - this.ui2Manager = new Ui2.Ui2(this.localization, this.xt); + this.ui2Manager = new Ui2.Ui2(this.localization, this.m_inputBinds); this.emoteBarn = new emote.EmoteBarn( - this.ft, + this.audioManager, this.uiManager, this.playerBarn, this.camera, this.map ); - this.shotBarn = new Shot.ShotBarn(this.particleBarn, this.ft, this.uiManager); - const t = { + this.shotBarn = new Shot.ShotBarn(this.particleBarn, this.audioManager, this.uiManager); + + // Register types + const TypeToPool = { [GameObject.Type.Player]: this.playerBarn.$e, [GameObject.Type.Obstacle]: this.map.Ve, [GameObject.Type.Loot]: this.lootBarn.sr, @@ -188,38 +191,41 @@ export class Game { [GameObject.Type.Airdrop]: this.airdropBarn.re }; this.objectCreator = new ObjectPool.Creator(); - for (const r in t) { - if (t.hasOwnProperty(r)) { - this.objectCreator.registerType(r, t[r]); + for (const type + in TypeToPool) { + if (TypeToPool.hasOwnProperty(type)) { + this.objectCreator.registerType(type, TypeToPool[type]); } } + // Render ordering this.debugDisplay = new PIXI.Graphics(); + const pixiContainers = [ + this.map.display.ground, + this.renderer.layers[0], + this.renderer.ground, + this.renderer.layers[1], + this.renderer.layers[2], + this.renderer.layers[3], + this.debugDisplay, + this.gas.gasRenderer.display, + this.touch.container, + this.emoteBarn.container, + this.uiManager.container, + this.uiManager.Pe.container, + this.emoteBarn.indContainer + ]; for ( - let i = [ - this.map.display.ground, - this.renderer.layers[0], - this.renderer.ground, - this.renderer.layers[1], - this.renderer.layers[2], - this.renderer.layers[3], - this.debugDisplay, - this.gas.gasRenderer.display, - this.touch.container, - this.emoteBarn.container, - this.uiManager.container, - this.uiManager.Pe.container, - this.emoteBarn.indContainer - ], - s = 0; - s < i.length; - s++ + let i = 0; + i < pixiContainers.length; + i++ ) { - const n = i[s]; - if (n) { - n.interactiveChildren = false; - this.pixi.stage.addChild(n); + const container = pixiContainers[i]; + if (container) { + container.interactiveChildren = false; + this.pixi.stage.addChild(container); } } + // Local vars this.disconnectMsg = ""; this.playing = false; this.gameOver = false; @@ -230,18 +236,23 @@ export class Game { this.updateRecvCount = 0; this.updatePass = false; this.updatePassDelay = 0; - this.pr = 0; - this.hr = 0; - this.dr = null; - this.ur = false; - this.q = 1; + this.m_localId = 0; + this.m_activeId = 0; + this.m_activePlayer = null; + this.m_validateAlpha = false; + this.m_targetZoom = 1; this.debugZoom = 1; this.useDebugZoom = false; + + // Latency determination + this.seq = 0; this.seqInFlight = false; this.seqSendTime = 0; this.pings = []; this.debugPingTime = 0; + + // Process config this.camera.setShakeEnabled(this.config.get("screenShake")); this.playerBarn.anonPlayerNames = this.config.get("anonPlayerNames"); @@ -269,12 +280,12 @@ export class Game { this.map.free(); this.particleBarn.free(); this.renderer.free(); - this.bt.n(); - this.ft.stopAll(); + this.m_input.n(); + this.audioManager.stopAll(); while (this.pixi.stage.children.length > 0) { - const e = this.pixi.stage.children[0]; - this.pixi.stage.removeChild(e); - e.destroy({ + const c = this.pixi.stage.children[0]; + this.pixi.stage.removeChild(c); + c.destroy({ children: true }); } @@ -283,6 +294,7 @@ export class Game { warnPageReload() { return ( + import.meta.env.PROD && this.initialized && this.playing && !this.spectating && @@ -290,299 +302,329 @@ export class Game { ); } - update(e) { - const t = this.smokeBarn.particles; - const r = this.map.Ve.p(); + update(dt) { + const smokeParticles = this.smokeBarn.particles; + const obstacles = this.map.Ve.p(); let a = 0; - this.I = true; - const i = {}; - i.render = i.render || {}; + // End anti-cheat hacking + this.m_mangle = true; + const debug = {}; + debug.render = debug.render || {}; + + /* + * DEBUG CODE + if (!this.m_map.mapTilesCreated && !this.m_map.mapTilesCreating && this.m_map.tilemapManager.initialized) { + this.m_map.renderMap(this.pixi.renderer, this.canvasMode); + } + */ + if (this.playing) { - this.playingTicker += e; + this.playingTicker += dt; } this.playerBarn.m( - e, - this.hr, + dt, + this.m_activeId, this.teamMode, this.renderer, this.particleBarn, this.camera, this.map, - this.xt, - this.ft, + this.m_inputBinds, + this.audioManager, this.ui2Manager, this.emoteBarn.wheelKeyTriggered, this.uiManager.displayingStats, this.spectating ); this.updateAmbience(); - this.camera.pos = v2.copy(this.dr.pos); + + this.camera.pos = v2.copy(this.m_activePlayer.pos); this.camera.applyShake(); - const o = this.dr.yr(); - const l = math.min(this.camera.screenWidth, this.camera.screenHeight); - const g = math.max(this.camera.screenWidth, this.camera.screenHeight); - const y = math.max(l * (16 / 9), g); - this.camera.q = (y * 0.5) / (o * this.camera.ppu); - const w = this.dr.zoomFast ? 3 : 2; - const f = this.dr.zoomFast ? 3 : 1.4; - const _ = this.camera.q > this.camera.O ? w : f; - this.camera.O = math.lerp(e * _, this.camera.O, this.camera.q); - this.ft.cameraPos = v2.copy(this.camera.pos); - if (this.bt.We(input.Key.Escape)) { + const zoom = this.m_activePlayer.yr(); + const minDim = math.min(this.camera.screenWidth, this.camera.screenHeight); + const maxDim = math.max(this.camera.screenWidth, this.camera.screenHeight); + const maxScreenDim = math.max(minDim * (16 / 9), maxDim); + this.camera.q = (maxScreenDim * 0.5) / (zoom * this.camera.ppu); + const zoomLerpIn = this.m_activePlayer.zoomFast ? 3 : 2; + const zoomLerpOut = this.m_activePlayer.zoomFast ? 3 : 1.4; + const zoomLerp = this.camera.q > this.camera.O ? zoomLerpIn : zoomLerpOut; + this.camera.O = math.lerp(dt * zoomLerp, this.camera.O, this.camera.q); + this.audioManager.cameraPos = v2.copy(this.camera.pos); + if (this.m_input.We(input.Key.Escape)) { this.uiManager.toggleEscMenu(); } + // Large Map if ( - this.xt.isBindPressed(Input.ToggleMap) || - (this.bt.We(input.Key.G) && !this.xt.isKeyBound(input.Key.G)) + this.m_inputBinds.isBindPressed(Input.ToggleMap) || + (this.m_input.We(input.Key.G) && !this.m_inputBinds.isKeyBound(input.Key.G)) ) { this.uiManager.displayMapLarge(false); } - if (this.xt.isBindPressed(Input.CycleUIMode)) { + // Minimap + if (this.m_inputBinds.isBindPressed(Input.CycleUIMode)) { this.uiManager.cycleVisibilityMode(); } + // Hide UI if ( - this.xt.isBindPressed(Input.HideUI) || - (this.bt.We(input.Key.Escape) && !this.uiManager.hudVisible) + this.m_inputBinds.isBindPressed(Input.HideUI) || + (this.m_input.We(input.Key.Escape) && !this.uiManager.hudVisible) ) { this.uiManager.cycleHud(); } - const b = this.dr.pos; - const x = this.camera.j(this.bt.Ue); - const S = v2.sub(x, b); - let v = v2.length(S); - let k = v > 0.00001 ? v2.div(S, v) : v2.create(1, 0); + // Update facing direction + const playerPos = this.m_activePlayer.pos; + const mousePos = this.camera.j(this.m_input.Ue); + const toMousePos = v2.sub(mousePos, playerPos); + let toMouseLen = v2.length(toMousePos); + let toMouseDir = toMouseLen > 0.00001 ? v2.div(toMousePos, toMouseLen) : v2.create(1, 0); + if (this.emoteBarn.wheelDisplayed) { - v = this.prevInputMsg.toMouseLen; - k = this.prevInputMsg.toMouseDir; + toMouseLen = this.prevInputMsg.toMouseLen; + toMouseDir = this.prevInputMsg.toMouseDir; } - const z = new net.InputMsg(); - z.seq = this.seq; + + // Input + const inputMsg = new net.InputMsg(); + inputMsg.seq = this.seq; if (!this.spectating) { if (device.touch) { - const I = this.touch.getTouchMovement(this.camera); - const M = this.touch.getAimMovement(this.dr, this.camera); - let P = v2.copy(M.aimMovement.toAimDir); - this.touch.turnDirTicker -= e; - if (this.touch.moveDetected && !M.touched) { - const C = v2.normalizeSafe( - I.toMoveDir, + const touchPlayerMovement = this.touch.getTouchMovement(this.camera); + const touchAimMovement = this.touch.getAimMovement(this.m_activePlayer, this.camera); + let aimDir = v2.copy(touchAimMovement.aimMovement.toAimDir); + this.touch.turnDirTicker -= dt; + if (this.touch.moveDetected && !touchAimMovement.touched) { + // Keep looking in the old aimDir while waiting for the ticker + const touchDir = v2.normalizeSafe( + touchPlayerMovement.toMoveDir, v2.create(1, 0) ); - const A = + const modifiedAimDir = this.touch.turnDirTicker < 0 - ? C - : M.aimMovement.toAimDir; - this.touch.setAimDir(A); - P = A; + ? touchDir + : touchAimMovement.aimMovement.toAimDir; + this.touch.setAimDir(modifiedAimDir); + aimDir = modifiedAimDir; } - if (M.touched) { + if (touchAimMovement.touched) { this.touch.turnDirTicker = this.touch.turnDirCooldown; } if (this.touch.moveDetected) { - z.touchMoveDir = v2.normalizeSafe( - I.toMoveDir, + inputMsg.touchMoveDir = v2.normalizeSafe( + touchPlayerMovement.toMoveDir, v2.create(1, 0) ); - z.touchMoveLen = Math.round( - math.clamp(I.toMoveLen, 0, 1) * 255 + inputMsg.touchMoveLen = Math.round( + math.clamp(touchPlayerMovement.toMoveLen, 0, 1) * 255 ); } else { - z.touchMoveLen = 0; + inputMsg.touchMoveLen = 0; } - z.touchMoveActive = true; - const O = M.aimMovement.toAimLen; - const D = - math.clamp(O / this.touch.padPosRange, 0, 1) * + inputMsg.touchMoveActive = true; + const aimLen = touchAimMovement.aimMovement.toAimLen; + const toTouchLenAdjusted = + math.clamp(aimLen / this.touch.padPosRange, 0, 1) * GameConfig.player.throwableMaxMouseDist; - z.toMouseLen = D; - z.toMouseDir = P; + inputMsg.toMouseLen = toTouchLenAdjusted; + inputMsg.toMouseDir = aimDir; } else { - z.moveLeft = - this.xt.isBindDown(Input.MoveLeft) || - (this.bt.Ye(input.Key.Left) && - !this.xt.isKeyBound(input.Key.Left)); - z.moveRight = - this.xt.isBindDown(Input.MoveRight) || - (this.bt.Ye(input.Key.Right) && - !this.xt.isKeyBound(input.Key.Right)); - z.moveUp = - this.xt.isBindDown(Input.MoveUp) || - (this.bt.Ye(input.Key.Up) && - !this.xt.isKeyBound(input.Key.Up)); - z.moveDown = - this.xt.isBindDown(Input.MoveDown) || - (this.bt.Ye(input.Key.Down) && - !this.xt.isKeyBound(input.Key.Down)); - z.toMouseDir = v2.copy(k); - z.toMouseLen = v; + // Only use arrow keys if they are unbound + inputMsg.moveLeft = + this.m_inputBinds.isBindDown(Input.MoveLeft) || + (this.m_input.Ye(input.Key.Left) && + !this.m_inputBinds.isKeyBound(input.Key.Left)); + inputMsg.moveRight = + this.m_inputBinds.isBindDown(Input.MoveRight) || + (this.m_input.Ye(input.Key.Right) && + !this.m_inputBinds.isKeyBound(input.Key.Right)); + inputMsg.moveUp = + this.m_inputBinds.isBindDown(Input.MoveUp) || + (this.m_input.Ye(input.Key.Up) && + !this.m_inputBinds.isKeyBound(input.Key.Up)); + inputMsg.moveDown = + this.m_inputBinds.isBindDown(Input.MoveDown) || + (this.m_input.Ye(input.Key.Down) && + !this.m_inputBinds.isKeyBound(input.Key.Down)); + inputMsg.toMouseDir = v2.copy(toMouseDir); + inputMsg.toMouseLen = toMouseLen; } - z.touchMoveDir = v2.normalizeSafe( - z.touchMoveDir, + inputMsg.touchMoveDir = v2.normalizeSafe( + inputMsg.touchMoveDir, v2.create(1, 0) ); - z.touchMoveLen = math.clamp(z.touchMoveLen, 0, 255); - z.toMouseDir = v2.normalizeSafe( - z.toMouseDir, + inputMsg.touchMoveLen = math.clamp(inputMsg.touchMoveLen, 0, 255); + inputMsg.toMouseDir = v2.normalizeSafe( + inputMsg.toMouseDir, v2.create(1, 0) ); - z.toMouseLen = math.clamp( - z.toMouseLen, + inputMsg.toMouseLen = math.clamp( + inputMsg.toMouseLen, 0, net.Constants.MouseMaxDist ); - z.shootStart = - this.xt.isBindPressed(Input.Fire) || this.touch.wr; - z.shootHold = this.xt.isBindDown(Input.Fire) || this.touch.wr; - z.portrait = this.camera.screenWidth < this.camera.screenHeight; + inputMsg.shootStart = + this.m_inputBinds.isBindPressed(Input.Fire) || this.touch.wr; + inputMsg.shootHold = this.m_inputBinds.isBindDown(Input.Fire) || this.touch.wr; + inputMsg.portrait = this.camera.screenWidth < this.camera.screenHeight; + const checkInputs = [ + Input.Reload, + Input.Revive, + Input.Use, + Input.Loot, + Input.Cancel, + Input.EquipPrimary, + Input.EquipSecondary, + Input.EquipThrowable, + Input.EquipMelee, + Input.EquipNextWeap, + Input.EquipPrevWeap, + Input.EquipLastWeap, + Input.EquipOtherGun, + Input.EquipPrevScope, + Input.EquipNextScope, + Input.StowWeapons + ]; for ( - let E = [ - Input.Reload, - Input.Revive, - Input.Use, - Input.Loot, - Input.Cancel, - Input.EquipPrimary, - Input.EquipSecondary, - Input.EquipThrowable, - Input.EquipMelee, - Input.EquipNextWeap, - Input.EquipPrevWeap, - Input.EquipLastWeap, - Input.EquipOtherGun, - Input.EquipPrevScope, - Input.EquipNextScope, - Input.StowWeapons - ], - B = 0; - B < E.length; + let B = 0; + B < checkInputs.length; B++ ) { - const R = E[B]; - if (this.xt.isBindPressed(R)) { - z.addInput(R); + const input = checkInputs[B]; + if (this.m_inputBinds.isBindPressed(input)) { + inputMsg.addInput(input); } } - if (this.xt.isBindPressed(Input.Interact)) { - const q = []; - const L = [Input.Revive, Input.Use, Input.Loot]; - for (let F = 0; F < L.length; F++) { - const j = L[F]; - if (!this.xt.getBind(j)) { - q.push(j); + + // Handle Interact + // Interact should not activate Revive, Use, or Loot if those inputs are bound separately. + if (this.m_inputBinds.isBindPressed(Input.Interact)) { + const inputs = []; + const interactBinds = [Input.Revive, Input.Use, Input.Loot]; + for (let i = 0; i < interactBinds.length; i++) { + const b = interactBinds[i]; + if (!this.m_inputBinds.getBind(b)) { + inputs.push(b); } } - if (q.length == L.length) { - z.addInput(Input.Interact); + if (inputs.length == interactBinds.length) { + inputMsg.addInput(Input.Interact); } else { - for (let N = 0; N < q.length; N++) { - z.addInput(q[N]); + for (let i = 0; i < inputs.length; i++) { + inputMsg.addInput(inputs[i]); } } } + + // Swap weapon slots if ( - this.xt.isBindPressed(Input.SwapWeapSlots) || + this.m_inputBinds.isBindPressed(Input.SwapWeapSlots) || this.uiManager.swapWeapSlots ) { - z.addInput(Input.SwapWeapSlots); - this.dr.gunSwitchCooldown = 0; + inputMsg.addInput(Input.SwapWeapSlots); + this.m_activePlayer.gunSwitchCooldown = 0; } + + // Handle touch inputs if (this.uiManager.reloadTouched) { - z.addInput(Input.Reload); + inputMsg.addInput(Input.Reload); } if (this.uiManager.interactionTouched) { - z.addInput(Input.Interact); - z.addInput(Input.Cancel); + inputMsg.addInput(Input.Interact); + inputMsg.addInput(Input.Cancel); } - for (let H = 0; H < this.ui2Manager.uiEvents.length; H++) { - const V = this.ui2Manager.uiEvents[H]; - if (V.action == "use") { - if (V.type == "weapon") { - const U = { + + // Process 'use' actions trigger from the ui + for (let i = 0; i < this.ui2Manager.uiEvents.length; i++) { + const e = this.ui2Manager.uiEvents[i]; + if (e.action == "use") { + if (e.type == "weapon") { + const weapIdxToInput = { 0: Input.EquipPrimary, 1: Input.EquipSecondary, 2: Input.EquipMelee, 3: Input.EquipThrowable }; - const W = U[V.data]; - if (W) { - z.addInput(W); + const input = weapIdxToInput[e.data]; + if (input) { + inputMsg.addInput(input); } } else { - z.useItem = V.data; + inputMsg.useItem = e.data; } } } - if (this.xt.isBindPressed(Input.UseBandage)) { - z.useItem = "bandage"; - } else if (this.xt.isBindPressed(Input.UseHealthKit)) { - z.useItem = "healthkit"; - } else if (this.xt.isBindPressed(Input.UseSoda)) { - z.useItem = "soda"; - } else if (this.xt.isBindPressed(Input.UsePainkiller)) { - z.useItem = "painkiller"; + if (this.m_inputBinds.isBindPressed(Input.UseBandage)) { + inputMsg.useItem = "bandage"; + } else if (this.m_inputBinds.isBindPressed(Input.UseHealthKit)) { + inputMsg.useItem = "healthkit"; + } else if (this.m_inputBinds.isBindPressed(Input.UseSoda)) { + inputMsg.useItem = "soda"; + } else if (this.m_inputBinds.isBindPressed(Input.UsePainkiller)) { + inputMsg.useItem = "painkiller"; } - let G = false; + + // Process 'drop' actions triggered from the ui + let playDropSound = false; for (let X = 0; X < this.ui2Manager.uiEvents.length; X++) { - const K = this.ui2Manager.uiEvents[X]; - if (K.action == "drop") { - const Z = new net.DropItemMsg(); - if (K.type == "weapon") { - const Y = this.dr.Re.tt; - Z.item = Y[K.data].type; - Z.weapIdx = K.data; - } else if (K.type == "perk") { - const J = this.dr.netData.Me; + const e = this.ui2Manager.uiEvents[X]; + if (e.action == "drop") { + const dropMsg = new net.DropItemMsg(); + if (e.type == "weapon") { + const Y = this.m_activePlayer.Re.tt; + dropMsg.item = Y[e.data].type; + dropMsg.weapIdx = e.data; + } else if (e.type == "perk") { + const J = this.m_activePlayer.netData.Me; const Q = - J.length > K.data ? J[K.data] : null; + J.length > e.data ? J[e.data] : null; if (Q?.droppable) { - Z.item = Q.type; + dropMsg.item = Q.type; } } else { let $ = ""; $ = - K.data == "helmet" - ? this.dr.netData.le - : K.data == "chest" - ? this.dr.netData.ce - : K.data; - Z.item = $; + e.data == "helmet" + ? this.m_activePlayer.netData.le + : e.data == "chest" + ? this.m_activePlayer.netData.ce + : e.data; + dropMsg.item = $; } - if (Z.item != "") { - this.$(net.Msg.DropItem, Z, 128); - if (Z.item != "fists") { - G = true; + if (dropMsg.item != "") { + this.sendMessage(net.Msg.DropItem, dropMsg, 128); + if (dropMsg.item != "fists") { + playDropSound = true; } } } } - if (G) { - this.ft.playSound("loot_drop_01", { + if (playDropSound) { + this.audioManager.playSound("loot_drop_01", { channel: "ui" }); } if (this.uiManager.roleSelected) { - const ee = new net.PerkModeRoleSelectMsg(); - ee.role = this.uiManager.roleSelected; - this.$(net.Msg.PerkModeRoleSelect, ee, 128); - this.config.set("perkModeRole", ee.role); + const roleSelectMessage = new net.PerkModeRoleSelectMsg(); + roleSelectMessage.role = this.uiManager.roleSelected; + this.sendMessage(net.Msg.PerkModeRoleSelect, roleSelectMessage, 128); + this.config.set("perkModeRole", roleSelectMessage.role); } } - const te = this.uiManager.specBegin; - const re = + const specBegin = this.uiManager.specBegin; + const specNext = this.uiManager.specNext || - (this.spectating && this.bt.We(input.Key.Right)); - const ae = + (this.spectating && this.m_input.We(input.Key.Right)); + const specPrev = this.uiManager.specPrev || - (this.spectating && this.bt.We(input.Key.Left)); - const ie = - this.bt.We(input.Key.Right) || this.bt.We(input.Key.Left); - if (te || (this.spectating && re) || ae) { - const oe = new net.SpectateMsg(); - oe.specBegin = te; - oe.specNext = re; - oe.specPrev = ae; - oe.specForce = ie; - this.$(net.Msg.Spectate, oe, 128); + (this.spectating && this.m_input.We(input.Key.Left)); + const specForce = + this.m_input.We(input.Key.Right) || this.m_input.We(input.Key.Left); + if (specBegin || (this.spectating && specNext) || specPrev) { + const specMsg = new net.SpectateMsg(); + specMsg.specBegin = specBegin; + specMsg.specNext = specNext; + specMsg.specPrev = specPrev; + specMsg.specForce = specForce; + this.sendMessage(net.Msg.Spectate, specMsg, 128); } this.uiManager.specBegin = false; this.uiManager.specNext = false; @@ -591,118 +633,123 @@ export class Game { this.uiManager.interactionTouched = false; this.uiManager.swapWeapSlots = false; this.uiManager.roleSelected = ""; - let se = false; - for (const ne in z) { - if (z.hasOwnProperty(ne)) { - if (ne == "inputs") { - se = z[ne].length > 0; + + // Only send a InputMsg if the new data has changed from the previously sent data. For the look direction, we need to determine if the angle difference is large enough. + let diff = false; + for (const k in inputMsg) { + if (inputMsg.hasOwnProperty(k)) { + if (k == "inputs") { + diff = inputMsg[k].length > 0; } else if ( - ne == "toMouseDir" || - ne == "touchMoveDir" + k == "toMouseDir" || + k == "touchMoveDir" ) { - const le = math.clamp( - v2.dot(z[ne], this.prevInputMsg[ne]), + const dot = math.clamp( + v2.dot(inputMsg[k], this.prevInputMsg[k]), -1, 1 ); - const ce = math.rad2deg(Math.acos(le)); - se = ce > 0.1; - } else if (ne == "toMouseLen") { - se = - Math.abs(this.prevInputMsg[ne] - z[ne]) > + const angle = math.rad2deg(Math.acos(dot)); + diff = angle > 0.1; + } else if (k == "toMouseLen") { + diff = + Math.abs(this.prevInputMsg[k] - inputMsg[k]) > 0.5; - } else if (ne == "shootStart") { - se = z[ne] || z[ne] != this.prevInputMsg[ne]; - } else if (this.prevInputMsg[ne] != z[ne]) { - se = true; + } else if (k == "shootStart") { + diff = inputMsg[k] || inputMsg[k] != this.prevInputMsg[k]; + } else if (this.prevInputMsg[k] != inputMsg[k]) { + diff = true; } - if (se) { + if (diff) { break; } } } - this.inputMsgTimeout -= e; - if (se || this.inputMsgTimeout < 0) { + this.inputMsgTimeout -= dt; + if (diff || this.inputMsgTimeout < 0) { if (!this.seqInFlight) { this.seq = (this.seq + 1) % 256; this.seqSendTime = Date.now(); this.seqInFlight = true; - z.seq = this.seq; + inputMsg.seq = this.seq; } - this.$(net.Msg.Input, z, 128); + this.sendMessage(net.Msg.Input, inputMsg, 128); this.inputMsgTimeout = 1; - this.prevInputMsg = z; + this.prevInputMsg = inputMsg; } + + // Clear cached data this.ui2Manager.flushInput(); + this.map.m( - e, - this.dr, + dt, + this.m_activePlayer, this.playerBarn, this.particleBarn, - this.ft, - this._t, + this.audioManager, + this.ambience, this.renderer, this.camera, - t, - i + smokeParticles, + debug ); - this.lootBarn.m(e, this.dr, this.map, this.ft, this.camera, i); + this.lootBarn.m(dt, this.m_activePlayer, this.map, this.audioManager, this.camera, debug); this.bulletBarn.m( - e, + dt, this.playerBarn, this.map, this.camera, - this.dr, + this.m_activePlayer, this.renderer, this.particleBarn, - this.ft + this.audioManager ); this.flareBarn.m( - e, + dt, this.playerBarn, this.map, this.camera, - this.dr, + this.m_activePlayer, this.renderer, this.particleBarn, - this.ft + this.audioManager ); this.projectileBarn.m( - e, + dt, this.particleBarn, - this.ft, - this.dr, + this.audioManager, + this.m_activePlayer, this.map, this.renderer, this.camera ); this.explosionBarn.m( - e, + dt, this.map, this.playerBarn, this.camera, this.particleBarn, - this.ft, - i + this.audioManager, + debug ); this.airdropBarn.m( - e, - this.dr, + dt, + this.m_activePlayer, this.camera, this.map, this.particleBarn, this.renderer, - this.ft + this.audioManager ); - this.planeBarn.m(e, this.camera, this.dr, this.map, this.renderer); - this.smokeBarn.m(e, this.camera, this.dr, this.map, this.renderer); - this.shotBarn.m(e, this.hr, this.playerBarn, this.particleBarn, this.ft); - this.particleBarn.m(e, this.camera, i); - this.deadBodyBarn.m(e, this.playerBarn, this.dr, this.map, this.camera, this.renderer); - this.decalBarn.m(e, this.camera, this.renderer, i); + this.planeBarn.m(dt, this.camera, this.m_activePlayer, this.map, this.renderer); + this.smokeBarn.m(dt, this.camera, this.m_activePlayer, this.map, this.renderer); + this.shotBarn.m(dt, this.m_activeId, this.playerBarn, this.particleBarn, this.audioManager); + this.particleBarn.m(dt, this.camera, debug); + this.deadBodyBarn.m(dt, this.playerBarn, this.m_activePlayer, this.map, this.camera, this.renderer); + this.decalBarn.m(dt, this.camera, this.renderer, debug); this.uiManager.m( - e, - this.dr, + dt, + this.m_activePlayer, this.map, this.gas, this.lootBarn, @@ -712,30 +759,30 @@ export class Game { this.map.factionMode ); this.ui2Manager.m( - e, - this.dr, + dt, + this.m_activePlayer, this.spectating, this.playerBarn, this.lootBarn, this.map, - this.xt + this.m_inputBinds ); this.emoteBarn.m( - e, - this.pr, - this.dr, + dt, + this.m_localId, + this.m_activePlayer, this.teamMode, this.deadBodyBarn, this.map, this.renderer, - this.bt, - this.xt, + this.m_input, + this.m_inputBinds, this.spectating ); - this.touch.update(e, this.dr, this.map, this.camera, this.renderer); - this.renderer.m(e, this.camera, this.map, i); - if (!this.Tt && this.map._r && this.map.U) { - this.Tt = true; + this.touch.update(dt, this.m_activePlayer, this.map, this.camera, this.renderer); + this.renderer.m(dt, this.camera, this.map, debug); + if (!this.m_cheatSentLoadoutMsg && this.map._r && this.map.U) { + this.m_cheatSentLoadoutMsg = true; const me = new net.LoadoutMsg(); me.emotes = []; for ( @@ -746,7 +793,7 @@ export class Game { me.emotes.push(this.emoteBarn.emoteLoadout[pe]); } me.custom = this.emoteBarn.hasCustomEmotes(); - this.$(net.Msg.Loadout, me, 128); + this.sendMessage(net.Msg.Loadout, me, 128); } for (let he = 0; he < this.emoteBarn.newPings.length; he++) { const de = this.emoteBarn.newPings[he]; @@ -754,7 +801,7 @@ export class Game { ue.type = de.type; ue.pos = de.pos; ue.isPing = true; - this.$(net.Msg.Emote, ue, 128); + this.sendMessage(net.Msg.Emote, ue, 128); } this.emoteBarn.newPings = []; for (let ge = 0; ge < this.emoteBarn.newEmotes.length; ge++) { @@ -763,58 +810,59 @@ export class Game { we.type = ye.type; we.pos = ye.pos; we.isPing = false; - this.$(net.Msg.Emote, we, 128); + this.sendMessage(net.Msg.Emote, we, 128); } this.emoteBarn.newEmotes = []; - this.br(e, i); - if (++this.It % 30 == 0) { + this.render(dt, debug); + if (++this.m_frame % 30 == 0) { const fe = mapHelpers.ct; - for (let _e = 0; _e < t.length; _e++) { - const be = t[_e]; + for (let _e = 0; _e < smokeParticles.length; _e++) { + const be = smokeParticles[_e]; if (be.active && !be.fade && fe(be, mapHelpers.nt)) { a++; } } - for (let xe = 0; xe < r.length; xe++) { - const Se = r[xe]; + for (let i = 0; i < obstacles.length; i++) { + const Se = obstacles[i]; if (Se.active && !Se.dead && fe(Se, mapHelpers.lt)) { a++; } } if (a) { - this.U = true; + this.m_cheatDetected = true; } - if (a && this.ur) { + if (a && this.m_validateAlpha) { helpers.cheatDetected(this); } } } - br(e, t) { - const r = this.map.mapLoaded + render(dt, debug) { + const grassColor = this.map.mapLoaded ? this.map.getMapDef().biome.colors.grass : 8433481; - this.pixi.renderer.backgroundColor = r; - this.playerBarn.render(this.camera, t); - this.bulletBarn.render(this.camera, t); + this.pixi.renderer.backgroundColor = grassColor; + // Module rendering + this.playerBarn.render(this.camera, debug); + this.bulletBarn.render(this.camera, debug); this.flareBarn.render(this.camera); - this.decalBarn.render(this.camera, t, this.dr.layer); + this.decalBarn.render(this.camera, debug, this.m_activePlayer.layer); this.map.render(this.camera); this.gas.render(this.camera); this.uiManager.render( - this.dr.pos, + this.m_activePlayer.pos, this.gas, this.camera, this.map, this.planeBarn, - t + debug ); this.emoteBarn.render(this.camera); debugLines.flush(); } updateAmbience() { - const e = this.dr.pos; + const e = this.m_activePlayer.pos; let t = 0; let r = 0; let a = 1; @@ -840,14 +888,14 @@ export class Game { const u = math.clamp(s.waterWidth / 8, 0.25, 1); r = math.max(d * u, r); } - if (this.dr.layer == 1) { + if (this.m_activePlayer.layer == 1) { r = 0; } a = 1; } - this._t.getTrack("wind").weight = a; - this._t.getTrack("river").weight = r; - this._t.getTrack("waves").weight = t; + this.ambience.getTrack("wind").weight = a; + this.ambience.getTrack("river").weight = r; + this.ambience.getTrack("waves").weight = t; } resize() { @@ -860,155 +908,189 @@ export class Game { this.renderer.resize(this.map, this.camera); } - processGameUpdate(e) { - const t = { - audioManager: this.ft, + processGameUpdate(msg) { + const ctx = { + audioManager: this.audioManager, renderer: this.renderer, particleBarn: this.particleBarn, map: this.map, smokeBarn: this.smokeBarn, decalBarn: this.decalBarn }; - if (e.activePlayerIdDirty) { - this.hr = e.activePlayerId; + // Update active playerId + if (msg.activePlayerIdDirty) { + this.m_activeId = msg.activePlayerId; } - for (let r = 0; r < e.playerInfos.length; r++) { - this.playerBarn.vr(e.playerInfos[r]); + // Update player infos + for (let i = 0; i < msg.playerInfos.length; i++) { + this.playerBarn.vr(msg.playerInfos[i]); } - for (let a = 0; a < e.deletedPlayerIds.length; a++) { - const i = e.deletedPlayerIds[a]; - this.playerBarn.kr(i); + // Delete player infos + for (let i = 0; i < msg.deletedPlayerIds.length; i++) { + const playerId = msg.deletedPlayerIds[i]; + this.playerBarn.kr(playerId); } if ( - e.playerInfos.length > 0 || - e.deletedPlayerIds.length > 0 + msg.playerInfos.length > 0 || + msg.deletedPlayerIds.length > 0 ) { this.playerBarn.zr(); } - if (e.playerStatusDirty) { - const o = this.playerBarn.qe(this.hr).teamId; - this.playerBarn.Ir(o, e.playerStatus, this.map.factionMode); + // Update player status + if (msg.playerStatusDirty) { + const teamId = this.playerBarn.qe(this.m_activeId).teamId; + this.playerBarn.Ir(teamId, msg.playerStatus, this.map.factionMode); } - if (e.groupStatusDirty) { - const s = this.playerBarn.qe(this.hr).groupId; - this.playerBarn.Tr(s, e.groupStatus); + + // Update group status + if (msg.groupStatusDirty) { + const groupId = this.playerBarn.qe(this.m_activeId).groupId; + this.playerBarn.Tr(groupId, msg.groupStatus); } - for (let n = 0; n < e.delObjIds.length; n++) { - this.objectCreator.deleteObj(e.delObjIds[n]); + + // Delete objects + for (let i = 0; i < msg.delObjIds.length; i++) { + this.objectCreator.deleteObj(msg.delObjIds[i]); } - for (let l = 0; l < e.fullObjects.length; l++) { - const c = e.fullObjects[l]; - this.objectCreator.updateObjFull(c.__type, c.__id, c, t); + + // Update full objects + for (let i = 0; i < msg.fullObjects.length; i++) { + const obj = msg.fullObjects[i]; + this.objectCreator.updateObjFull(obj.__type, obj.__id, obj, ctx); } - for (let m = 0; m < e.partObjects.length; m++) { - const p = e.partObjects[m]; - this.objectCreator.updateObjPart(p.__id, p, t); + + // Update partial objects + for (let i = 0; i < msg.partObjects.length; i++) { + const obj = msg.partObjects[i]; + this.objectCreator.updateObjPart(obj.__id, obj, ctx); } - this.spectating = this.hr != this.pr; - this.dr = this.playerBarn.u(this.hr); - this.dr.Mr(e.activePlayerData, this.playerBarn); - if (e.activePlayerData.weapsDirty) { + this.spectating = this.m_activeId != this.m_localId; + this.m_activePlayer = this.playerBarn.u(this.m_activeId); + this.m_activePlayer.Mr(msg.activePlayerData, this.playerBarn); + if (msg.activePlayerData.weapsDirty) { this.uiManager.weapsDirty = true; } if (this.spectating) { this.uiManager.setSpectateTarget( - this.hr, - this.pr, + this.m_activeId, + this.m_localId, this.teamMode, this.playerBarn ); this.touch.hideAll(); } - this.dr.layer = this.dr.netData.pe; - this.renderer.setActiveLayer(this.dr.layer); - this.ft.activeLayer = this.dr.layer; - const h = this.dr.isUnderground(this.map); - this.renderer.setUnderground(h); - this.ft.underground = h; - if (e.gasDirty) { + this.m_activePlayer.layer = this.m_activePlayer.netData.pe; + this.renderer.setActiveLayer(this.m_activePlayer.layer); + this.audioManager.activeLayer = this.m_activePlayer.layer; + const underground = this.m_activePlayer.isUnderground(this.map); + this.renderer.setUnderground(underground); + this.audioManager.underground = underground; + + // Gas data + if (msg.gasDirty) { this.gas.setFullState( - e.gasT, - e.gasData, + msg.gasT, + msg.gasData, this.map, this.uiManager ); } - if (e.gasTDirty) { - this.gas.setProgress(e.gasT); + if (msg.gasTDirty) { + this.gas.setProgress(msg.gasT); } - for (let d = 0; d < e.bullets.length; d++) { - const g = e.bullets[d]; - Bullet.createBullet(g, this.bulletBarn, this.flareBarn, this.playerBarn, this.renderer); - if (g.shotFx) { - this.shotBarn.addShot(g); + + // Create bullets + for (let i = 0; i < msg.bullets.length; i++) { + const b = msg.bullets[i]; + Bullet.createBullet(b, this.bulletBarn, this.flareBarn, this.playerBarn, this.renderer); + if (b.shotFx) { + this.shotBarn.addShot(b); } } - for (let y = 0; y < e.explosions.length; y++) { - const f = e.explosions[y]; - this.explosionBarn.addExplosion(f.type, f.pos, f.layer); + // Create explosions + for (let i = 0; i < msg.explosions.length; i++) { + const e = msg.explosions[i]; + this.explosionBarn.addExplosion(e.type, e.pos, e.layer); } - for (let _ = 0; _ < e.emotes.length; _++) { - const b = e.emotes[_]; - if (b.isPing) { - this.emoteBarn.addPing(b, this.map.factionMode); + + // Create emotes and pings + for (let i = 0; i < msg.emotes.length; i++) { + const e = msg.emotes[i]; + if (e.isPing) { + this.emoteBarn.addPing(e, this.map.factionMode); } else { - this.emoteBarn.addEmote(b); + this.emoteBarn.addEmote(e); } } - this.planeBarn.Pr(e.planes, this.map); - for (let x = 0; x < e.airstrikeZones.length; x++) { - this.planeBarn.Cr(e.airstrikeZones[x]); + + // Update planes + this.planeBarn.Pr(msg.planes, this.map); + + // Create airstrike zones + for (let x = 0; x < msg.airstrikeZones.length; x++) { + this.planeBarn.Cr(msg.airstrikeZones[x]); } - this.uiManager.je(e.mapIndicators); - if (e.killLeaderDirty) { - const S = helpers.htmlEscape( - this.playerBarn.getPlayerName(e.killLeaderId, this.hr, true) + + // Update map indicators + this.uiManager.je(msg.mapIndicators); + + // Update kill leader + if (msg.killLeaderDirty) { + const leaderNameText = helpers.htmlEscape( + this.playerBarn.getPlayerName(msg.killLeaderId, this.m_activeId, true) ); this.uiManager.updateKillLeader( - e.killLeaderId, - S, - e.killLeaderKills, + msg.killLeaderId, + leaderNameText, + msg.killLeaderKills, this.map.getMapDef().gameMode ); } + + // Latency determination this.updateRecvCount++; - if (e.ack == this.seq && this.seqInFlight) { + if (msg.ack == this.seq && this.seqInFlight) { this.seqInFlight = false; } } - onMsg(e, t) { - switch (e) { + // Socket functions + onMsg(type, stream) { + switch (type) { case net.Msg.Joined: { - const r = new net.JoinedMsg(); - r.deserialize(t); + const msg = new net.JoinedMsg(); + msg.deserialize(stream); this.onJoin(); - this.teamMode = r.teamMode; - this.pr = r.playerId; - this.ur = true; - this.emoteBarn.updateEmoteWheel(r.emotes); - if (!r.started) { + this.teamMode = msg.teamMode; + this.m_localId = msg.playerId; + this.m_validateAlpha = true; + this.emoteBarn.updateEmoteWheel(msg.emotes); + if (!msg.started) { this.uiManager.setWaitingForPlayers(true); } if (this.victoryMusic) { this.victoryMusic.stop(); this.victoryMusic = null; } + + // Play a sound if the user in another windows or tab if (!document.hasFocus()) { - this.ft.playSound("notification_start_01", { + this.audioManager.playSound("notification_start_01", { channel: "ui" }); } + + // Update cheat detection if (helpers.ee() || helpers.te()) { - this.U = true; + this.m_cheatDetected = true; } break; } case net.Msg.Map: { - const a = new net.MapMsg(); - a.deserialize(t); + const msg = new net.MapMsg(); + msg.deserialize(stream); this.map.loadMap( - a, + msg, this.camera, this.canvasMode, this.particleBarn @@ -1035,212 +1117,224 @@ export class Game { break; } case net.Msg.Update: { - const o = new net.UpdateMsg(); - o.deserialize(t, this.objectCreator); + const msg = new net.UpdateMsg(); + msg.deserialize(stream, this.objectCreator); /* if (o.partObjects.length) { console.log(o) } */ this.playing = true; - this.processGameUpdate(o); + this.processGameUpdate(msg); break; } case net.Msg.Kill: { - const n = new net.KillMsg(); - n.deserialize(t); - const l = n.itemSourceType || n.mapSourceType; - const c = this.playerBarn.qe(this.hr).teamId; - const m = - (n.downed && !n.killed) || - n.damageType == GameConfig.DamageType.Gas || - n.damageType == GameConfig.DamageType.Bleeding || - n.damageType == GameConfig.DamageType.Airdrop; - const h = this.playerBarn.qe(n.targetId); - const d = this.playerBarn.qe(n.killCreditId); - const g = m ? d : this.playerBarn.qe(n.killerId); - let y = this.playerBarn.getPlayerName( - h.playerId, - this.hr, + const msg = new net.KillMsg(); + msg.deserialize(stream); + const sourceType = msg.itemSourceType || msg.mapSourceType; + const activeTeamId = this.playerBarn.qe(this.m_activeId).teamId; + const useKillerInfoInFeed = + (msg.downed && !msg.killed) || + msg.damageType == GameConfig.DamageType.Gas || + msg.damageType == GameConfig.DamageType.Bleeding || + msg.damageType == GameConfig.DamageType.Airdrop; + const targetInfo = this.playerBarn.qe(msg.targetId); + const killerInfo = this.playerBarn.qe(msg.killCreditId); + const killfeedKillerInfo = useKillerInfoInFeed ? killerInfo : this.playerBarn.qe(msg.killerId); + let targetName = this.playerBarn.getPlayerName( + targetInfo.playerId, + this.m_activeId, true ); - let w = this.playerBarn.getPlayerName( - d.playerId, - this.hr, + let killerName = this.playerBarn.getPlayerName( + killerInfo.playerId, + this.m_activeId, true ); - let f = this.playerBarn.getPlayerName( - g.playerId, - this.hr, + let killfeedKillerName = this.playerBarn.getPlayerName( + killfeedKillerInfo.playerId, + this.m_activeId, true ); - y = helpers.htmlEscape(y); - w = helpers.htmlEscape(w); - f = helpers.htmlEscape(f); - if (n.killCreditId == this.hr) { - const _ = n.killerId == this.hr; - const b = - n.killerId == n.targetId || - n.killCreditId == n.targetId; - const x = this.ui2Manager.getKillText( - w, - y, - _, - n.downed, - n.killed, - b, - l, - n.damageType, + targetName = helpers.htmlEscape(targetName); + killerName = helpers.htmlEscape(killerName); + killfeedKillerName = helpers.htmlEscape(killfeedKillerName); + // Display the kill / downed notification for the active player + if (msg.killCreditId == this.m_activeId) { + const completeKill = msg.killerId == this.m_activeId; + const suicide = + msg.killerId == msg.targetId || + msg.killCreditId == msg.targetId; + const killText = this.ui2Manager.getKillText( + killerName, + targetName, + completeKill, + msg.downed, + msg.killed, + suicide, + sourceType, + msg.damageType, this.spectating ); - const S = - n.killed && !b + const killCountText = + msg.killed && !suicide ? this.ui2Manager.getKillCountText( - n.killerKills + msg.killerKills ) : ""; - this.ui2Manager.displayKillMessage(x, S); + this.ui2Manager.displayKillMessage(killText, killCountText); } else if ( - n.targetId == this.hr && - n.downed && - !n.killed + msg.targetId == this.m_activeId && + msg.downed && + !msg.killed ) { - const v = this.ui2Manager.getDownedText( - w, - y, - l, - n.damageType, + const downedText = this.ui2Manager.getDownedText( + killerName, + targetName, + sourceType, + msg.damageType, this.spectating ); - this.ui2Manager.displayKillMessage(v, ""); + this.ui2Manager.displayKillMessage(downedText, ""); } - if (n.killCreditId == this.pr && n.killed) { - this.uiManager.setLocalKills(n.killerKills); + + // Update local kill counter + if (msg.killCreditId == this.m_localId && msg.killed) { + this.uiManager.setLocalKills(msg.killerKills); } - const k = this.ui2Manager.getKillFeedText( - y, - g.teamId ? f : "", - l, - n.damageType, - n.downed && !n.killed + + // Add killfeed entry for this kill + const killText = this.ui2Manager.getKillFeedText( + targetName, + killfeedKillerInfo.teamId ? killfeedKillerName : "", + sourceType, + msg.damageType, + msg.downed && !msg.killed ); - const z = this.ui2Manager.getKillFeedColor( - c, - h.teamId, - d.teamId, + const killColor = this.ui2Manager.getKillFeedColor( + activeTeamId, + targetInfo.teamId, + killerInfo.teamId, this.map.factionMode ); - this.ui2Manager.addKillFeedMessage(k, z); - if (n.killed) { + this.ui2Manager.addKillFeedMessage(killText, killColor); + if (msg.killed) { this.playerBarn.addDeathEffect( - n.targetId, - n.killerId, - l, - this.ft, + msg.targetId, + msg.killerId, + sourceType, + this.audioManager, this.particleBarn ); } - if (n.type == GameConfig.DamageType.Player) { + + // Bullets often don't play hit sounds on the frame that a player dies + if (msg.type == GameConfig.DamageType.Player) { this.bulletBarn.createBulletHit( this.playerBarn, - n.targetId, - this.ft + msg.targetId, + this.audioManager ); } break; } case net.Msg.RoleAnnouncement: { - const I = new net.RoleAnnouncementMsg(); - I.deserialize(t); - const T = RoleDefs[I.role]; - if (!T) { + const msg = new net.RoleAnnouncementMsg(); + msg.deserialize(stream); + const roleDef = RoleDefs[msg.role]; + if (!roleDef) { break; } - const M = this.playerBarn.qe(I.playerId); - const P = helpers.htmlEscape( - this.playerBarn.getPlayerName(I.playerId, this.hr, true) + const playerInfo = this.playerBarn.qe(msg.playerId); + const nameText = helpers.htmlEscape( + this.playerBarn.getPlayerName(msg.playerId, this.m_activeId, true) ); - if (I.assigned) { - if (T.sound?.assign) { + if (msg.assigned) { + if (roleDef.sound?.assign) { if ( - I.role == "kill_leader" && + msg.role == "kill_leader" && this.map.getMapDef().gameMode .spookyKillSounds ) { - this.ft.playGroup( + // Halloween map has special logic for the kill leader sounds + this.audioManager.playGroup( "kill_leader_assigned", { channel: "ui" } ); } else if ( - I.role == "kill_leader" || + // The intent here is to not play the role-specific assignment sounds in perkMode unless you're the player selecting a role. + msg.role == "kill_leader" || !this.map.perkMode || - this.pr == I.playerId + this.m_localId == msg.playerId ) { - this.ft.playSound(T.sound.assign, { + this.audioManager.playSound(roleDef.sound.assign, { channel: "ui" }); } } - if (this.map.perkMode && this.pr == I.playerId) { + if (this.map.perkMode && this.m_localId == msg.playerId) { this.uiManager.setRoleMenuActive(false); } - if (T.killFeed?.assign) { - const C = + if (roleDef.killFeed?.assign) { + // In addition to playing a sound, display a notification on the killfeed + const killText = this.ui2Manager.getRoleAssignedKillFeedText( - I.role, - M.teamId, - P + msg.role, + playerInfo.teamId, + nameText ); - const A = this.ui2Manager.getRoleKillFeedColor( - I.role, - M.teamId, + const killColor = this.ui2Manager.getRoleKillFeedColor( + msg.role, + playerInfo.teamId, this.playerBarn ); - this.ui2Manager.addKillFeedMessage(C, A); + this.ui2Manager.addKillFeedMessage(killText, killColor); } - if (T.announce && this.pr == I.playerId) { - const O = this.ui2Manager.getRoleAnnouncementText( - I.role, - M.teamId + // Show an announcement if you've been assigned a role + if (roleDef.announce && this.m_localId == msg.playerId) { + const assignText = this.ui2Manager.getRoleAnnouncementText( + msg.role, + playerInfo.teamId ); this.uiManager.displayAnnouncement( - O.toUpperCase() + assignText.toUpperCase() ); } - } else if (I.killed) { - if (T.killFeed?.dead) { - let D = helpers.htmlEscape( + } else if (msg.killed) { + if (roleDef.killFeed?.dead) { + let killerName = helpers.htmlEscape( this.playerBarn.getPlayerName( - I.killerId, - this.hr, + msg.killerId, + this.m_activeId, true ) ); - if (I.playerId == I.killerId) { - D = ""; + + if (msg.playerId == msg.killerId) { + killerName = ""; } - const E = this.ui2Manager.getRoleKilledKillFeedText( - I.role, - M.teamId, - D + const killText = this.ui2Manager.getRoleKilledKillFeedText( + msg.role, + playerInfo.teamId, + killerName ); - const B = this.ui2Manager.getRoleKillFeedColor( - I.role, - M.teamId, + const killColor = this.ui2Manager.getRoleKillFeedColor( + msg.role, + playerInfo.teamId, this.playerBarn ); - this.ui2Manager.addKillFeedMessage(E, B); + this.ui2Manager.addKillFeedMessage(killText, killColor); } - if (T.sound?.dead) { + if (roleDef.sound?.dead) { if ( this.map.getMapDef().gameMode .spookyKillSounds ) { - this.ft.playGroup("kill_leader_dead", { + this.audioManager.playGroup("kill_leader_dead", { channel: "ui" }); } else { - this.ft.playSound(T.sound.dead, { + this.audioManager.playSound(roleDef.sound.dead, { channel: "ui" }); } @@ -1249,46 +1343,51 @@ export class Game { break; } case net.Msg.PlayerStats: { - const R = new net.PlayerStatsMsg(); - R.deserialize(t); - this.uiManager.setLocalStats(R.playerStats); - this.uiManager.showTeamAd(R.playerStats, this.ui2Manager); + const msg = new net.PlayerStatsMsg(); + msg.deserialize(stream); + this.uiManager.setLocalStats(msg.playerStats); + this.uiManager.showTeamAd(msg.playerStats, this.ui2Manager); break; } case net.Msg.Stats: { - const L = new net.StatsMsg(); - L.deserialize(t); - helpers.J(L.data, this); + const msg = new net.StatsMsg(); + msg.deserialize(stream); + helpers.J(msg.data, this); break; } case net.Msg.GameOver: { - const q = new net.GameOverMsg(); - q.deserialize(t); - this.gameOver = q.gameOver; - const F = this.playerBarn.qe(this.pr).teamId; - for (let j = 0; j < q.playerStats.length; j++) { - const V = q.playerStats[j]; - if (V.playerId == this.pr) { - this.uiManager.setLocalStats(V); + const msg = new net.GameOverMsg(); + msg.deserialize(stream); + this.gameOver = msg.gameOver; + const localTeamId = this.playerBarn.qe(this.m_localId).teamId; + + // Set local stats based on final results. + // This is necessary because the last person on a team to die + // will not receive a PlayerStats message, they will only receive + // the GameOver message. + for (let j = 0; j < msg.playerStats.length; j++) { + const stats = msg.playerStats[j]; + if (stats.playerId == this.m_localId) { + this.uiManager.setLocalStats(stats); break; } } this.uiManager.showStats( - q.playerStats, - q.teamId, - q.teamRank, - q.winningTeamId, - q.gameOver, - F, + msg.playerStats, + msg.teamId, + msg.teamRank, + msg.winningTeamId, + msg.gameOver, + localTeamId, this.teamMode, this.spectating, this.playerBarn, - this.ft, + this.audioManager, this.map, this.ui2Manager ); - if (F == q.winningTeamId) { - this.victoryMusic = this.ft.playSound( + if (localTeamId == msg.winningTeamId) { + this.victoryMusic = this.audioManager.playSound( "menu_music", { channel: "music", @@ -1301,61 +1400,63 @@ export class Game { break; } case net.Msg.Pickup: { - const U = new net.PickupMsg(); - U.deserialize(t); - if (U.type == net.PickupMsgType.Success && U.item) { - this.dr.playItemPickupSound(U.item, this.ft); - const W = GameObjectDefs[U.item]; - if (W && W.type == "xp") { - this.ui2Manager.addRareLootMessage(U.item, true); + const msg = new net.PickupMsg(); + msg.deserialize(stream); + if (msg.type == net.PickupMsgType.Success && msg.item) { + this.m_activePlayer.playItemPickupSound(msg.item, this.audioManager); + const itemDef = GameObjectDefs[msg.item]; + if (itemDef && itemDef.type == "xp") { + this.ui2Manager.addRareLootMessage(msg.item, true); } } else { - this.ui2Manager.displayPickupMessage(U.type); + this.ui2Manager.displayPickupMessage(msg.type); } break; } case net.Msg.UpdatePass: { - new net.UpdatePassMsg().deserialize(t); + new net.UpdatePassMsg().deserialize(stream); this.updatePass = true; this.updatePassDelay = 0; break; } case net.Msg.AliveCounts: { - const G = new net.AliveCountsMsg(); - G.deserialize(t); - if (G.teamAliveCounts.length == 1) { + const msg = new net.AliveCountsMsg(); + msg.deserialize(stream); + if (msg.teamAliveCounts.length == 1) { this.uiManager.updatePlayersAlive( - G.teamAliveCounts[0] + msg.teamAliveCounts[0] ); - } else if (G.teamAliveCounts.length >= 2) { + } else if (msg.teamAliveCounts.length >= 2) { this.uiManager.updatePlayersAliveRed( - G.teamAliveCounts[0] + msg.teamAliveCounts[0] ); this.uiManager.updatePlayersAliveBlue( - G.teamAliveCounts[1] + msg.teamAliveCounts[1] ); } break; } case net.Msg.Disconnect: { - const X = new net.DisconnectMsg(); - X.deserialize(t); - this.disconnectMsg = X.reason; + const msg = new net.DisconnectMsg(); + msg.deserialize(stream); + this.disconnectMsg = msg.reason; } } } - $(e, t, r) { - const a = r || 128; - const i = new net.MsgStream(new ArrayBuffer(a)); - i.serializeMsg(e, t); - this.Ar(i); + sendMessage(type, data, maxLen) { + const bufSz = maxLen || 128; + const msgStream = new net.MsgStream(new ArrayBuffer(bufSz)); + msgStream.serializeMsg(type, data); + this.sendMessageImpl(msgStream); } - Ar(e) { + sendMessageImpl(msgStream) { + // Separate function call so m_sendMessage can be optimized; + // v8 won't optimize functions containing a try/catch if (this.ws && this.ws.readyState == this.ws.OPEN) { try { - this.ws.send(e.getBuffer()); + this.ws.send(msgStream.getBuffer()); } catch (e) { console.error("sendMessageException", e); this.ws.close(); From d67160c66bd6ce77e269df5d8c7c159bd8fa1f77 Mon Sep 17 00:00:00 2001 From: lmssieh <122186255+lmssiehdev@users.noreply.github.com> Date: Sat, 2 Mar 2024 17:22:14 +0100 Subject: [PATCH 6/7] cleanup map.js --- client/src/map.js | 755 +++++++++++++++++++++++++--------------------- 1 file changed, 413 insertions(+), 342 deletions(-) diff --git a/client/src/map.js b/client/src/map.js index c31a9526..8e2ba640 100644 --- a/client/src/map.js +++ b/client/src/map.js @@ -15,28 +15,33 @@ import Building from "./objects/building"; import Obstacle from "./objects/obstacle"; import Structure from "./objects/structure"; -function a(e, t, r) { - e.moveTo(t.x, t.y); - e.lineTo(r.x, r.y); +// Drawing + +function drawLine(canvas, pt0, pt1) { + canvas.moveTo(pt0.x, pt0.y); + canvas.lineTo(pt1.x, pt1.y); } -function i(e, t) { - let r = t[0]; - e.moveTo(r.x, r.y); - for (let a = 1; a < t.length; ++a) { - r = t[a]; - e.lineTo(r.x, r.y); +function tracePath(canvas, path) { + let point = path[0]; + canvas.moveTo(point.x, point.y); + for (let i = 1; i < path.length; ++i) { + point = path[i]; + canvas.lineTo(point.x, point.y); } - e.closePath(); + canvas.closePath(); } -function o(e, t, r) { - const a = t.max.x - t.min.x; - const o = t.max.y - t.min.y; - const s = math.max(t.offsetDist, 0.001); - const n = t.roughness; - const l = Math.round((a * n) / s); - const c = Math.round((o * n) / s); - const m = util.seededRand(r); - i(e, terrainGen.generateJaggedAabbPoints(t, l, c, s, m)); +function traceGroundPatch(canvas, patch, seed) { + const width = patch.max.x - patch.min.x; + const height = patch.max.y - patch.min.y; + + const offset = math.max(patch.offsetDist, 0.001); + const roughness = patch.roughness; + + const divisionsX = Math.round((width * roughness) / offset); + const divisionsY = Math.round((height * roughness) / offset); + + const seededRand = util.seededRand(seed); + tracePath(canvas, terrainGen.generateJaggedAabbPoints(patch, divisionsX, divisionsY, offset, seededRand)); } function Map(e) { this.decalBarn = e; @@ -70,6 +75,8 @@ function Map(e) { this.lootDropSfxIds = []; this.terrain = null; this.cameraEmitter = null; + + // Anti-cheat this.ea = 0; this._r = false; this.U = false; @@ -77,8 +84,11 @@ function Map(e) { Map.prototype = { free: function() { - for (let e = this.nr.p(), t = 0; t < e.length; t++) { - e[t].n(); + // Buildings need to stop sound emitters + const buildings = this.nr.p(); + for ( + let i = 0; i < buildings.length; i++) { + buildings[i].n(); } this.mapTexture?.destroy(true); this.display.ground.destroy({ @@ -87,52 +97,53 @@ Map.prototype = { this.cameraEmitter?.stop(); this.cameraEmitter = null; }, - resize: function(e, t) { - this.renderMap(e, t); + resize: function(pixiRenderer, canvasMode) { + this.renderMap(pixiRenderer, canvasMode); }, - loadMap: function(e, t, r, a) { - this.mapName = e.mapName; - const i = MapDefs[this.mapName]; - if (!i) { + loadMap: function(mapMsg, camera, canvasMode, particleBarn) { + this.mapName = mapMsg.mapName; + // Clone the source mapDef + const mapDef = MapDefs[this.mapName]; + if (!mapDef) { throw new Error( `Failed loading mapDef ${this.mapName}` ); } - this.mapDef = util.cloneDeep(i); + this.mapDef = util.cloneDeep(mapDef); this.factionMode = !!this.mapDef.gameMode.factionMode; this.perkMode = !!this.mapDef.gameMode.perkMode; this.turkeyMode = !!this.mapDef.gameMode.turkeyMode; - this.seed = e.seed; - this.width = e.width; - this.height = e.height; + this.seed = mapMsg.seed; + this.width = mapMsg.width; + this.height = mapMsg.height; this.terrain = terrainGen.generateTerrain( this.width, this.height, - e.shoreInset, - e.grassInset, - e.rivers, + mapMsg.shoreInset, + mapMsg.grassInset, + mapMsg.rivers, this.seed ); this.mapData = { - places: e.places, - objects: e.objects, - groundPatches: e.groundPatches + places: mapMsg.places, + objects: mapMsg.objects, + groundPatches: mapMsg.groundPatches }; this.mapLoaded = true; - const o = this.mapDef.biome.particles.camera; - if (o) { - const s = v2.normalize(v2.create(1, -1)); - this.cameraEmitter = a.addEmitter(o, { + const cameraEmitterType = this.mapDef.biome.particles.camera; + if (cameraEmitterType) { + const dir = v2.normalize(v2.create(1, -1)); + this.cameraEmitter = particleBarn.addEmitter(cameraEmitterType, { pos: v2.create(0, 0), - dir: s, + dir, layer: 99999 }); } this.display.ground.clear(); this.renderTerrain( this.display.ground, - 2 / t.ppu, - r, + 2 / camera.ppu, + canvasMode, false ); }, @@ -145,310 +156,352 @@ Map.prototype = { getMapTexture: function() { return this.mapTexture; }, - m: function(e, t, r, a, i, o, s, n, l, c) { + m: function(dt, activePlayer, r, a, i, o, s, camera, smokeParticles, c) { this.I = true; this.Br = true; - const p = this.Ve.p(); - for (let h = 0; h < p.length; h++) { - const u = p[h]; + const obstacles = this.Ve.p(); + for (let h = 0; h < obstacles.length; h++) { + const u = obstacles[h]; if (u.active) { - u.m(e, this, r, a, i, t, s); - u.render(n, c, t.layer); + u.m(dt, this, r, a, i, activePlayer, s); + u.render(camera, c, activePlayer.layer); } } for (let y = this.nr.p(), f = 0; f < y.length; f++) { const _ = y[f]; if (_.active) { - _.m(e, this, a, i, o, t, s, n); - _.render(n, c, t.layer); + _.m(dt, this, a, i, o, activePlayer, s, camera); + _.render(camera, c, activePlayer.layer); } } for (let b = this.lr.p(), x = 0; x < b.length; x++) { const S = b[x]; if (S.active) { - S.update(e, this, t, o); - S.render(n, c, t.layer); + S.update(dt, this, activePlayer, o); + S.render(camera, c, activePlayer.layer); } } if (this.cameraEmitter) { - this.cameraEmitter.pos = v2.copy(n.pos); + this.cameraEmitter.pos = v2.copy(camera.pos); this.cameraEmitter.enabled = true; - const v = t.yr() * 2.5; - this.cameraEmitter.radius = math.min(v, 120); - const k = this.cameraEmitter.radius; - const z = (k * k) / 14400; - this.cameraEmitter.rateMult = 1 / z; - const I = t.layer == 0 ? 1 : 0; + + // Adjust radius and spawn rate based on zoom + const maxRadius = 120; + const camRadius = activePlayer.yr() * 2.5; + this.cameraEmitter.radius = math.min(camRadius, maxRadius); + const radius = this.cameraEmitter.radius; + const ratio = (radius * radius) / (maxRadius * maxRadius); + this.cameraEmitter.rateMult = 1 / ratio; + const alphaTarget = activePlayer.layer == 0 ? 1 : 0; this.cameraEmitter.alpha = math.lerp( - e * 6, + dt * 6, this.cameraEmitter.alpha, - I + alphaTarget ); } - if (++this.ea % 180 == 0) { + this.ea++; + if (this.ea % 180 == 0) { this._r = true; - let T = 0; - const M = mapHelpers.ct; - for (let P = 0; P < l.length; P++) { - const C = l[P]; - if (C.active && !C.fade && M(C, mapHelpers.nt)) { - T++; + let cheatDetected = 0; + const detectCheatAlphaFn = mapHelpers.ct; + + // Verify smoke particle alpha integrity + for (let i = 0; i < smokeParticles.length; i++) { + const p = smokeParticles[i]; + if (p.active && !p.fade && detectCheatAlphaFn(p, mapHelpers.nt)) { + cheatDetected++; } } - for (let A = 0; A < p.length; A++) { - const O = p[A]; - if (O.active && !O.dead && M(O, mapHelpers.lt)) { - T++; + + // Verify obstacle alpha integrity + for (let i = 0; i < obstacles.length; i++) { + const p = obstacles[i]; + if (p.active && !p.dead && detectCheatAlphaFn(p, mapHelpers.lt)) { + cheatDetected++; } } - if (T) { + if (cheatDetected) { this.U = true; } } }, - renderTerrain: function(e, t, r, s) { - const n = this.width; - const l = this.height; - const c = this.terrain; - const m = { + renderTerrain: function(groundGfx, gridThickness, canvasMode, mapRender) { + const width = this.width; + const height = this.height; + const terrain = this.terrain; + const ll = { x: 0, y: 0 }; - const p = { - x: n, + const lr = { + x: width, y: 0 }; - const h = { + const ul = { x: 0, - y: l + y: height }; - const d = { - x: n, - y: l + const ur = { + x: width, + y: height }; - const u = this.mapDef.biome.colors; - const g = this.mapData.groundPatches; - e.beginFill(u.background); - e.drawRect(-120, -120, n + 240, 120); - e.drawRect(-120, l, n + 240, 120); - e.drawRect(-120, -120, 120, l + 240); - e.drawRect(n, -120, 120, l + 240); - e.endFill(); - e.beginFill(u.beach); - i(e, c.shore); - e.beginHole(); - i(e, c.grass); - // e.addHole(); - e.endHole(); - e.endFill(); - if (r) { - e.beginFill(u.grass); - i(e, c.grass); - e.endFill(); + const mapColors = this.mapDef.biome.colors; + const groundPatches = this.mapData.groundPatches; + groundGfx.beginFill(mapColors.background); + groundGfx.drawRect(-120, -120, width + 240, 120); + groundGfx.drawRect(-120, height, width + 240, 120); + groundGfx.drawRect(-120, -120, 120, height + 240); + groundGfx.drawRect(width, -120, 120, height + 240); + groundGfx.endFill(); + groundGfx.beginFill(mapColors.beach); + tracePath(groundGfx, terrain.shore); + groundGfx.beginHole(); + tracePath(groundGfx, terrain.grass); + // groundGfx.addHole(); + groundGfx.endHole(); + groundGfx.endFill(); + + // As mentioned above, don't explicitly render a grass polygon; + // there's a hole left where the grass should be, with the background + // clear color set to the grass color. + // + // ... except we have to for canvas mode! + if (canvasMode) { + groundGfx.beginFill(mapColors.grass); + tracePath(groundGfx, terrain.grass); + groundGfx.endFill(); } - for (let y = 0; y < g.length; y++) { - const w = g[y]; - if (w.order == 0 && (!s || !!w.useAsMapShape)) { - e.beginFill(w.color); - o(e, w, this.seed); - e.endFill(); + + // Order 0 ground patches + for (let i = 0; i < groundPatches.length; i++) { + const patch = groundPatches[i]; + if (patch.order == 0 && (!mapRender || !!patch.useAsMapShape)) { + groundGfx.beginFill(patch.color); + traceGroundPatch(groundGfx, patch, this.seed); + groundGfx.endFill(); } } - e.beginFill(u.riverbank); - for (let _ = 0; _ < c.rivers.length; _++) { - i(e, c.rivers[_].shorePoly); + + // River shore + groundGfx.beginFill(mapColors.riverbank); + + // groundGfx.lineStyle(2, 0xff0000); + + for (let i = 0; i < terrain.rivers.length; i++) { + tracePath(groundGfx, terrain.rivers[i].shorePoly); } - e.endFill(); - e.beginFill(u.water); - for (let b = 0; b < c.rivers.length; b++) { - i(e, c.rivers[b].waterPoly); + groundGfx.endFill(); + groundGfx.beginFill(mapColors.water); + for (let b = 0; b < terrain.rivers.length; b++) { + tracePath(groundGfx, terrain.rivers[b].waterPoly); } - e.endFill(); - e.beginFill(u.water); - e.moveTo(h.x, h.y); - e.lineTo(d.x, d.y); - e.lineTo(p.x, p.y); - e.lineTo(m.x, m.y); - e.beginHole(); - i(e, c.shore); + groundGfx.endFill(); + + // Water + groundGfx.beginFill(mapColors.water); + groundGfx.moveTo(ul.x, ul.y); + groundGfx.lineTo(ur.x, ur.y); + groundGfx.lineTo(lr.x, lr.y); + groundGfx.lineTo(ll.x, ll.y); + groundGfx.beginHole(); + tracePath(groundGfx, terrain.shore); // e.addHole(); - e.endHole(); - e.closePath(); - e.endFill(); - const x = e; - x.lineStyle(t, 0, 0.15); - for (let S = 0; S <= n; S += GameConfig.map.gridSize) { - a( - x, + groundGfx.endHole(); + groundGfx.closePath(); + groundGfx.endFill(); + + // Grid + const gridGfx = groundGfx; + gridGfx.lineStyle(gridThickness, 0, 0.15); + for (let x = 0; x <= width; x += GameConfig.map.gridSize) { + drawLine( + gridGfx, { - x: S, + x, y: 0 }, { - x: S, - y: l + x, + y: height } ); } - for (let v = 0; v <= l; v += GameConfig.map.gridSize) { - a( - x, + for (let y = 0; y <= height; y += GameConfig.map.gridSize) { + drawLine( + gridGfx, { x: 0, - y: v + y }, { - x: n, - y: v + x: width, + y } ); } - x.lineStyle(t, 0, 0); - for (let k = 0; k < g.length; k++) { - const z = g[k]; - if (z.order == 1 && (!s || !!z.useAsMapShape)) { - e.beginFill(z.color); - o(e, z, this.seed); - e.endFill(); + gridGfx.lineStyle(gridThickness, 0, 0); + + // Order 1 ground patches + for (let i = 0; i < groundPatches.length; i++) { + const patch = groundPatches[i]; + if (patch.order == 1 && (!mapRender || !!patch.useAsMapShape)) { + groundGfx.beginFill(patch.color); + traceGroundPatch(groundGfx, patch, this.seed); + groundGfx.endFill(); } } }, - render: function(e) { - const t = e.pointToScreen(v2.create(0, 0)); - const r = e.pointToScreen(v2.create(1, 1)); - const a = v2.sub(r, t); - this.display.ground.position.set(t.x, t.y); - this.display.ground.scale.set(a.x, a.y); + render: function(camera) { + // Terrain + // Fairly robust way to get translation and scale from the camera ... + const p0 = camera.pointToScreen(v2.create(0, 0)); + const p1 = camera.pointToScreen(v2.create(1, 1)); + const s = v2.sub(p1, p0); + // Translate and scale the map polygons to move the with camera + this.display.ground.position.set(p0.x, p0.y); + this.display.ground.scale.set(s.x, s.y); }, - getMinimapRender: function(e) { - const t = MapObjectDefs[e.type]; - const r = - t.type == "building" - ? 750 + (t.zIdx || 0) - : t.img.zIdx || 0; - let a = []; - if (t.map.shapes !== undefined) { - a = t.map.shapes; + getMinimapRender: function(obj) { + const def = MapObjectDefs[obj.type]; + const zIdx = + def.type == "building" + ? 750 + (def.zIdx || 0) + : def.img.zIdx || 0; + let shapes = []; + if (def.map.shapes !== undefined) { + shapes = def.map.shapes; } else { - let i = null; + let col = null; if ( - (i = - t.type == "obstacle" - ? t.collision - : t.ceiling.zoomRegions.length > 0 && - t.ceiling.zoomRegions[0].zoomIn - ? t.ceiling.zoomRegions[0].zoomIn - : mapHelpers.getBoundingCollider(e.type)) + (col = + def.type == "obstacle" + ? def.collision + : def.ceiling.zoomRegions.length > 0 && + def.ceiling.zoomRegions[0].zoomIn + ? def.ceiling.zoomRegions[0].zoomIn + : mapHelpers.getBoundingCollider(obj.type)) ) { - a.push({ - collider: collider.copy(i), - scale: t.map.scale || 1, - color: t.map.color + shapes.push({ + collider: collider.copy(col), + scale: def.map.scale || 1, + color: def.map.color }); } } return { - obj: e, - zIdx: r, - shapes: a + obj, + zIdx, + shapes }; }, - renderMap: function(e, t) { + renderMap: function(renderer, canvasMode) { if (this.mapLoaded) { - const r = new PIXI.Container(); - const i = new PIXI.Container(); - const o = this.mapDef.biome.colors; - const s = this.mapData.places; - const l = this.mapData.objects; - let m = device.screenHeight; + const mapRender = new PIXI.Container(); + const txtRender = new PIXI.Container(); + const mapColors = this.mapDef.biome.colors; + const places = this.mapData.places; + const objects = this.mapData.objects; + let screenScale = device.screenHeight; if (device.mobile) { if (!device.isLandscape) { - m = device.screenWidth; + screenScale = device.screenWidth; } - m *= math.min(device.pixelRatio, 2); + screenScale *= math.min(device.pixelRatio, 2); } - const p = this.height / m; - const h = new PIXI.Graphics(); - h.beginFill(o.grass); - h.drawRect(0, 0, this.width, this.height); - h.endFill(); - this.renderTerrain(h, p, t, true); - const u = { + const scale = this.height / screenScale; + + // Background + const background = new PIXI.Graphics(); + background.beginFill(mapColors.grass); + background.drawRect(0, 0, this.width, this.height); + background.endFill(); + this.renderTerrain(background, scale, canvasMode, true); + + // Border for extra spiffiness + const ll = { x: 0, y: 0 }; - const g = { + const lr = { x: this.width, y: 0 }; - const f = { + const ul = { x: 0, y: this.height }; - const _ = { + const ur = { x: this.width, y: this.height }; - h.lineStyle(p * 2, 0, 1); - a(h, u, f); - a(h, f, _); - a(h, _, g); - a(h, g, u); - h.position.y = this.height; - h.scale.y = -1; - r.addChild(h); - const b = []; - for (let x = 0; x < l.length; x++) { - const S = l[x]; - b.push(this.getMinimapRender(S)); + background.lineStyle(scale * 2, 0, 1); + drawLine(background, ll, ul); + drawLine(background, ul, ur); + drawLine(background, ur, lr); + drawLine(background, lr, ll); + background.position.y = this.height; + background.scale.y = -1; + + mapRender.addChild(background); + + // Render minimap objects, sorted by zIdx + const minimapRenders = []; + for (let i = 0; i < objects.length; i++) { + const obj = objects[i]; + minimapRenders.push(this.getMinimapRender(obj)); } - b.sort((e, t) => { - return e.zIdx - t.zIdx; + minimapRenders.sort((a, b) => { + return a.zIdx - b.zIdx; }); - const v = new PIXI.Graphics(); - for (let k = 0; k < b.length; k++) { + + const gfx = new PIXI.Graphics(); + for (let i = 0; i < minimapRenders.length; i++) { + const render = minimapRenders[i]; + const obj = render.obj; for ( - let z = b[k], I = z.obj, T = 0; - T < z.shapes.length; - T++ + let j = 0; + j < render.shapes.length; + j++ ) { - const M = z.shapes[T]; - const P = collider.transform( - M.collider, - I.pos, - math.oriToRad(I.ori), - I.scale + const shape = render.shapes[j]; + const col = collider.transform( + shape.collider, + obj.pos, + math.oriToRad(obj.ori), + obj.scale ); - const C = M.scale !== undefined ? M.scale : 1; - v.beginFill(M.color, 1); - switch (P.type) { + const scale = shape.scale !== undefined ? shape.scale : 1; + gfx.beginFill(shape.color, 1); + switch (col.type) { case collider.Type.Circle: - v.drawCircle( - P.pos.x, - this.height - P.pos.y, - P.rad * C + gfx.drawCircle( + col.pos.x, + this.height - col.pos.y, + col.rad * scale ); break; case collider.Type.Aabb: { - let A = v2.mul(v2.sub(P.max, P.min), 0.5); - const O = v2.add(P.min, A); - A = v2.mul(A, C); - v.drawRect( + let A = v2.mul(v2.sub(col.max, col.min), 0.5); + const O = v2.add(col.min, A); + A = v2.mul(A, scale); + gfx.drawRect( O.x - A.x, this.height - O.y - A.y, A.x * 2, A.y * 2 ); - v.endFill(); + gfx.endFill(); } } } } - r.addChild(v); - const D = new PIXI.Container(); - for (let E = 0; E < s.length; E++) { - const B = s[E]; - const R = new PIXI.TextStyle({ + mapRender.addChild(gfx); + + // Place names + const nameContainer = new PIXI.Container(); + for (let E = 0; E < places.length; E++) { + const place = places[E]; + const style = new PIXI.TextStyle({ fontFamily: "Arial", fontSize: device.mobile ? 20 : 22, fontWeight: "bold", @@ -463,180 +516,198 @@ Map.prototype = { wordWrap: false, align: "center" }); - const L = new PIXI.Text(B.name, R); - L.anchor.set(0.5, 0.5); - L.x = (B.pos.x * this.height) / p; - L.y = (B.pos.y * this.height) / p; - L.alpha = 0.75; - D.addChild(L); + const richText = new PIXI.Text(place.name, style); + richText.anchor.set(0.5, 0.5); + richText.x = (place.pos.x * this.height) / scale; + richText.y = (place.pos.y * this.height) / scale; + richText.alpha = 0.75; + nameContainer.addChild(richText); } - i.addChild(D); + txtRender.addChild(nameContainer); + + // Generate and/or update the texture if (this.mapTexture) { - this.mapTexture.resize(m, m); + this.mapTexture.resize(screenScale, screenScale); } else { this.mapTexture = PIXI.RenderTexture.create( - m, - m, + screenScale, + screenScale, PIXI.SCALE_MODES.LINEAR, 1 ); } - r.scale = new PIXI.Point(m / this.height, m / this.height); - e.render(r, this.mapTexture, true); - e.render(i, this.mapTexture, false); - r.destroy({ + mapRender.scale = new PIXI.Point(screenScale / this.height, screenScale / this.height); + renderer.render(mapRender, this.mapTexture, true); + renderer.render(txtRender, this.mapTexture, false); + mapRender.destroy({ children: true, texture: true, baseTexture: true }); - i.destroy({ + txtRender.destroy({ children: true, texture: true, baseTexture: true }); } }, - getGroundSurface: function(e, t) { + getGroundSurface: function(pos, layer) { const r = this; - const a = function(e, t) { - t = t || {}; - if (e == "water") { - const a = r.getMapDef().biome.colors; - t.waterColor = - t.waterColor !== undefined - ? t.waterColor - : a.water; - t.rippleColor = - t.rippleColor !== undefined - ? t.rippleColor - : a.waterRipple; + const groundSurface = (type, data = {}) => { + if (type == "water") { + const mapColors = r.getMapDef().biome.colors; + data.waterColor = + data.waterColor !== undefined + ? data.waterColor + : mapColors.water; + data.rippleColor = + data.rippleColor !== undefined + ? data.rippleColor + : mapColors.waterRipple; } return { - type: e, - data: t + type, + data }; }; + + // Check decals + const decals = this.decalBarn._.p(); for ( - let i = this.decalBarn._.p(), o = 0; - o < i.length; - o++ + let i = 0; + i < decals.length; + i++ ) { - const s = i[o]; + const decal = decals[i]; if ( - s.active && - s.surface && - util.sameLayer(s.layer, t) && - collider.intersectCircle(s.collider, e, 0.0001) + decal.active && + decal.surface && + util.sameLayer(decal.layer, layer) && + collider.intersectCircle(decal.collider, pos, 0.0001) ) { - return a(s.surface.type, s.surface.data); + return groundSurface(decal.surface.type, decal.surface.data); } } - let n = null; - let m = 0; - const p = t & 2; - for (let d = this.nr.p(), u = 0; u < d.length; u++) { - const g = d[u]; + + // Check buildings + let surface = null; + let zIdx = 0; + const onStairs = layer & 2; + const buildings = this.nr.p(); + for (let i = 0; i < buildings.length; i++) { + const building = buildings[i]; if ( - g.active && - g.zIdx >= m && - (g.layer == t || !!p) && - (g.layer != 1 || !p) + building.active && + building.zIdx >= zIdx && + // Prioritize layer0 building surfaces when on stairs + (building.layer == layer || !!onStairs) && + (building.layer != 1 || !onStairs) ) { - for (let y = 0; y < g.surfaces.length; y++) { + for (let i = 0; i < building.surfaces.length; i++) { + const s = building.surfaces[i]; for ( - let f = g.surfaces[y], _ = 0; - _ < f.colliders.length; - _++ + let j = 0; + j < s.colliders.length; + j++ ) { - const b = collider.intersectCircle( - f.colliders[_], - e, + const res = collider.intersectCircle( + s.colliders[j], + pos, 0.0001 ); - if (b) { - m = g.zIdx; - n = f; + if (res) { + zIdx = building.zIdx; + surface = s; break; } } } } } - if (n) { - return a(n.type, n.data); + if (surface) { + return groundSurface(surface.type, surface.data); } - let x = false; - if (t != 1) { + + // Check rivers + let onRiverShore = false; + if (layer != 1) { + const rivers = this.terrain.rivers; for ( - let S = this.terrain.rivers, v = 0; - v < S.length; + let v = 0; + v < rivers.length; v++ ) { - const k = S[v]; + const river = rivers[v]; if ( - coldet.testPointAabb(e, k.aabb.min, k.aabb.max) && - math.pointInsidePolygon(e, k.shorePoly) && - ((x = true), - math.pointInsidePolygon(e, k.waterPoly)) + coldet.testPointAabb(pos, river.aabb.min, river.aabb.max) && + math.pointInsidePolygon(pos, river.shorePoly) && + ((onRiverShore = true), + math.pointInsidePolygon(pos, river.waterPoly)) ) { - return a("water", { - river: k + return groundSurface("water", { + river }); } } } - return a( - math.pointInsidePolygon(e, this.terrain.grass) - ? x + // Check terrain + return groundSurface( + math.pointInsidePolygon(pos, this.terrain.grass) + ? onRiverShore + // Use a stone step sound if we're in the main-spring def ? this.mapDef.biome.sound.riverShore : "grass" - : math.pointInsidePolygon(e, this.terrain.shore) + : math.pointInsidePolygon(pos, this.terrain.shore) ? "sand" : "water" ); }, - isInOcean: function(e) { - return !math.pointInsidePolygon(e, this.terrain.shore); + isInOcean: function(pos) { + return !math.pointInsidePolygon(pos, this.terrain.shore); }, - distanceToShore: function(e) { - return math.distToPolygon(e, this.terrain.shore); + distanceToShore: function(pos) { + return math.distToPolygon(pos, this.terrain.shore); }, - insideStructureStairs: function(e) { - for (let t = this.lr.p(), r = 0; r < t.length; r++) { - const a = t[r]; - if (a.active && a.insideStairs(e)) { + insideStructureStairs: function(collision) { + const structures = this.lr.p(); + for (let i = 0; i < structures.length; i++) { + const structure = structures[i]; + if (structure.active && structure.insideStairs(collision)) { return true; } } return false; }, - getBuildingById: function(e) { - for (let t = this.nr.p(), r = 0; r < t.length; r++) { - const a = t[r]; - if (a.active && a.__id == e) { - return a; + getBuildingById: function(objId) { + const buildings = this.nr.p(); + for (let r = 0; r < buildings.length; r++) { + const building = buildings[r]; + if (building.active && building.__id == objId) { + return building; } } return null; }, - insideStructureMask: function(e) { - for (let t = this.lr.p(), r = 0; r < t.length; r++) { - const a = t[r]; - if (a.active && a.insideMask(e)) { + insideStructureMask: function(collision) { + const structures = this.lr.p(); + for (let i = 0; i < structures.length; i++) { + const structure = structures[i]; + if (structure.active && structure.insideMask(collision)) { return true; } } return false; }, - insideBuildingCeiling: function(e, t) { - for (let r = this.nr.p(), a = 0; a < r.length; a++) { - const i = r[a]; + insideBuildingCeiling: function(collision, checkVisible) { + const buildings = this.nr.p(); + for (let i = 0; i < buildings.length; i++) { + const building = buildings[i]; if ( - i.active && - (!t || - (i.ceiling.visionTicker > 0 && - !i.ceilingDead)) && - i.isInsideCeiling(e) + building.active && + (!checkVisible || + (building.ceiling.visionTicker > 0 && + !building.ceilingDead)) && + building.isInsideCeiling(collision) ) { return true; } From cfc0b934eb7e88e6a3d97c7710ef2b93ac98beba Mon Sep 17 00:00:00 2001 From: lmssieh <122186255+lmssiehdev@users.noreply.github.com> Date: Sat, 2 Mar 2024 17:52:08 +0100 Subject: [PATCH 7/7] cleanup gas.js --- client/src/gas.js | 154 +++++++++++++++++++++++++--------------------- 1 file changed, 84 insertions(+), 70 deletions(-) diff --git a/client/src/gas.js b/client/src/gas.js index dd32ff24..c0ccaa47 100644 --- a/client/src/gas.js +++ b/client/src/gas.js @@ -6,8 +6,8 @@ import helpers from "./helpers"; const gasMode = GameConfig.GasMode; -const p = 100000; -const h = 512; +const overdraw = 100 * 1000; +const segments = 512; class GasRenderer { constructor(t, r) { @@ -27,15 +27,15 @@ class GasRenderer { const i = this.display; i.clear(); i.beginFill(r, 0.6); - i.moveTo(-p, -p); - i.lineTo(p, -p); - i.lineTo(p, p); - i.lineTo(-p, p); + i.moveTo(-overdraw, -overdraw); + i.lineTo(overdraw, -overdraw); + i.lineTo(overdraw, overdraw); + i.lineTo(-overdraw, overdraw); i.closePath(); i.beginHole(); i.moveTo(0, 1); - for (let s = 1; s < h; s++) { - const n = s / h; + for (let s = 1; s < segments; s++) { + const n = s / segments; const l = Math.sin(Math.PI * 2 * n); const c = Math.cos(Math.PI * 2 * n); i.lineTo(l, c); @@ -59,28 +59,30 @@ class GasRenderer { } } - render(e, t, r) { + render(gasPos, gasRad, active) { if (this.canvas != null) { - const a = this.canvas; - const i = a.getContext("2d"); - i.clearRect(0, 0, a.width, a.height); - i.beginPath(); - i.fillStyle = this.gasColorDOMString; - i.rect(0, 0, a.width, a.height); - i.arc(e.x, e.y, t, 0, Math.PI * 2, true); - i.fill(); + const canvas = this.canvas; + const ctx = canvas.getContext("2d"); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.beginPath(); + ctx.fillStyle = this.gasColorDOMString; + ctx.rect(0, 0, canvas.width, canvas.height); + ctx.arc(gasPos.x, gasPos.y, gasRad, 0, Math.PI * 2, true); + ctx.fill(); } else { - const o = v2.copy(e); - let s = t; - if (s < 0.1) { - s = 1; - o.x += p * 0.5; + const center = v2.copy(gasPos); + // Once the hole gets small enough, just fill the entire + // screen with some random part of the geometry + let rad = gasRad; + if (rad < 0.1) { + rad = 1; + center.x += overdraw * 0.5; } - const n = this.display; - n.position.set(o.x, o.y); - n.scale.set(s, s); + const ctx = this.display; + ctx.position.set(center.x, center.y); + ctx.scale.set(rad, rad); } - this.display.visible = r; + this.display.visible = active; } } @@ -98,42 +100,49 @@ class GasSafeZoneRenderer { this.playerPos = v2.create(0, 0); } - render(e, t, r, a, i) { - this.circleGfx.visible = a; - this.lineGfx.visible = i; - if (a || i) { - const o = !v2.eq(this.safePos, e, 0.0001); - const s = Math.abs(this.safeRad - t) > 0.0001; - const n = !v2.eq(this.playerPos, r, 0.0001); - if (o) { - this.safePos.x = e.x; - this.safePos.y = e.y; + render(safePos, safeRad, playerPos, drawCircle, drawLine) { + // Render a circle showing the safe zone, and a line pointing from + // the player to the center. Only update geometry if relevant data + // has changed. + this.circleGfx.visible = drawCircle; + this.lineGfx.visible = drawLine; + if (drawCircle || drawLine) { + const safePosChanged = !v2.eq(this.safePos, safePos, 0.0001); + const safeRadChanged = Math.abs(this.safeRad - safeRad) > 0.0001; + const playerPosChanged = !v2.eq(this.playerPos, playerPos, 0.0001); + + if (safePosChanged) { + this.safePos.x = safePos.x; + this.safePos.y = safePos.y; } - if (s) { - this.safeRad = t; + if (safeRadChanged) { + this.safeRad = safeRad; } - if (n) { - this.playerPos.x = r.x; - this.playerPos.y = r.y; + if (playerPosChanged) { + this.playerPos.x = playerPos.x; + this.playerPos.y = playerPos.y; } - if (o) { + + // Update circle? + if (safePosChanged) { this.circleGfx.position.set( this.safePos.x, this.safePos.y ); } - if (s) { + if (safeRadChanged) { this.circleGfx.clear(); this.circleGfx.lineStyle(1.5, 16777215); - this.circleGfx.drawCircle(0, 0, t); + is.circleGfx.drawCircle(0, 0, safeRad); } - if (o || s || n) { - const l = v2.length(v2.sub(r, e)) < t; - const m = l ? 0.5 : 1; + // Update line? + if (safePosChanged || safeRadChanged || playerPosChanged) { + const isSafe = v2.length(v2.sub(playerPos, safePos)) < safeRad; + const alpha = isSafe ? 0.5 : 1; this.lineGfx.clear(); - this.lineGfx.lineStyle(2, 65280, m); - this.lineGfx.moveTo(r.x, r.y); - this.lineGfx.lineTo(e.x, e.y); + this.lineGfx.lineStyle(2, 65280, alpha); + this.lineGfx.moveTo(playerPos.x, playerPos.y); + this.lineGfx.lineTo(safePos.x, safePos.y); } } } @@ -141,17 +150,17 @@ class GasSafeZoneRenderer { class Gas { constructor(t) { - const r = (Math.sqrt(2) + 0.01) * 1024; + const startRad = (Math.sqrt(2) + 0.01) * 1024; this.mode = gasMode.Inactive; this.circleT = 0; this.duration = 0; this.circleOld = { pos: v2.create(0, 0), - rad: r + rad: startRad }; this.circleNew = { pos: v2.create(0, 0), - rad: r + rad: startRad }; this.gasRenderer = new GasRenderer(t, 16711680); } @@ -169,38 +178,43 @@ class Gas { } getCircle() { - const e = this.mode == gasMode.Moving ? this.circleT : 0; + const t = this.mode == gasMode.Moving ? this.circleT : 0; return { pos: v2.lerp( - e, + t, this.circleOld.pos, this.circleNew.pos ), rad: math.lerp( - e, + t, this.circleOld.rad, this.circleNew.rad ) }; } - setProgress(e) { - this.circleT = e; + setProgress(circleT) { + this.circleT = circleT; } - setFullState(e, t, r, a) { - if (t.mode != this.mode) { - const i = Math.ceil(t.duration * (1 - e)); - a.setWaitingForPlayers(false); - a.displayGasAnnouncement(t.mode, i); + setFullState(circleT, data, map, ui) { + // Update Ui + if (data.mode != this.mode) { + const timeLeft = Math.ceil(data.duration * (1 - circleT)); + ui.setWaitingForPlayers(false); + ui.displayGasAnnouncement(data.mode, timeLeft); } - this.mode = t.mode; - this.duration = t.duration; - this.circleT = e; - this.circleOld.pos = v2.copy(t.posOld); - this.circleOld.rad = t.radOld; - this.circleNew.pos = v2.copy(t.posNew); - this.circleNew.rad = t.radNew; + + // Update state + this.mode = data.mode; + this.duration = data.duration; + this.circleT = circleT; + + // Update circles + this.circleOld.pos = v2.copy(data.posOld); + this.circleOld.rad = data.radOld; + this.circleNew.pos = v2.copy(data.posNew); + this.circleNew.rad = data.radNew; } render(e) {