From 25fe710c3b36b097502a5f3e0afb024312cbf7b9 Mon Sep 17 00:00:00 2001 From: Caleb Mabry <36182383+caleb-mabry@users.noreply.github.com> Date: Sun, 17 Jul 2022 00:56:15 -0400 Subject: Moved viewport out of client --- webAO/client.ts | 2944 +++++++++++++++++++--------------------------- webAO/client/setEmote.js | 40 - webAO/client/setEmote.ts | 51 + webAO/viewport.ts | 1053 +++++++++++++++++ 4 files changed, 2335 insertions(+), 1753 deletions(-) delete mode 100644 webAO/client/setEmote.js create mode 100644 webAO/client/setEmote.ts create mode 100644 webAO/viewport.ts (limited to 'webAO') diff --git a/webAO/client.ts b/webAO/client.ts index db4c776..64ffab9 100644 --- a/webAO/client.ts +++ b/webAO/client.ts @@ -2,62 +2,63 @@ * Glorious webAO * made by sD, refactored by oldmud0 and Qubrick * credits to aleks for original idea and source -*/ + */ -import FingerprintJS from '@fingerprintjs/fingerprintjs'; -import { EventEmitter } from 'events'; -import tryUrls from './utils/tryUrls' -import { - escapeChat, prepChat, safeTags, unescapeChat, -} from './encoding'; -import mlConfig from './utils/aoml'; +import FingerprintJS from "@fingerprintjs/fingerprintjs"; +import { EventEmitter } from "events"; +import tryUrls from "./utils/tryUrls"; +import { escapeChat, prepChat, safeTags, unescapeChat } from "./encoding"; +import mlConfig from "./utils/aoml"; // Load some defaults for the background and evidence dropdowns -import vanilla_character_arr from './constants/characters.js'; -import vanilla_music_arr from './constants/music.js'; -import vanilla_background_arr from './constants/backgrounds.js'; -import vanilla_evidence_arr from './constants/evidence.js'; - -import chatbox_arr from './styles/chatbox/chatboxes.js'; -import iniParse from './iniParse'; -import getCookie from './utils/getCookie.js'; -import setCookie from './utils/setCookie.js'; -import { request } from './services/request.js'; -import { changeShoutVolume, changeSFXVolume, changeTestimonyVolume } from './dom/changeVolume.js'; -import setEmote from './client/setEmote.js'; -import fileExists from './utils/fileExists.js'; -import queryParser from './utils/queryParser.js'; -import getAnimLength from './utils/getAnimLength.js'; -import getResources from './utils/getResources.js'; -import transparentPng from './constants/transparentPng'; -import downloadFile from './services/downloadFile' -import { getFilenameFromPath } from './utils/paths'; +import vanilla_character_arr from "./constants/characters.js"; +import vanilla_music_arr from "./constants/music.js"; +import vanilla_background_arr from "./constants/backgrounds.js"; +import vanilla_evidence_arr from "./constants/evidence.js"; + +import chatbox_arr from "./styles/chatbox/chatboxes.js"; +import iniParse from "./iniParse"; +import getCookie from "./utils/getCookie.js"; +import setCookie from "./utils/setCookie.js"; +import { request } from "./services/request.js"; +import { + changeShoutVolume, + changeSFXVolume, + changeTestimonyVolume, +} from "./dom/changeVolume.js"; +import setEmote from "./client/setEmote.js"; +import fileExists from "./utils/fileExists.js"; +import queryParser from "./utils/queryParser.js"; +import getAnimLength from "./utils/getAnimLength.js"; +import getResources from "./utils/getResources.js"; +import transparentPng from "./constants/transparentPng"; +import downloadFile from "./services/downloadFile"; +import { getFilenameFromPath } from "./utils/paths"; const version = process.env.npm_package_version; +import masterViewport, { Viewport } from "./viewport"; -let client: Client; -let viewport: Viewport; interface Testimony { - [key: number]: string + [key: number]: string; } // Get the arguments from the URL bar interface QueryParams { - ip: string - serverIP: string - mode: string - asset: string - theme: string + ip: string; + serverIP: string; + mode: string; + asset: string; + theme: string; } -let { - ip: serverIP, mode, asset, theme, -} = queryParser() as QueryParams; +let { ip: serverIP, mode, asset, theme } = queryParser() as QueryParams; // Unless there is an asset URL specified, use the wasabi one -const DEFAULT_HOST = 'http://attorneyoffline.de/base/'; +const DEFAULT_HOST = "http://attorneyoffline.de/base/"; let AO_HOST = asset || DEFAULT_HOST; -const THEME = theme || 'default'; +const THEME = theme || "default"; -const attorneyMarkdown = mlConfig(AO_HOST) +let client: Client; + +const attorneyMarkdown = mlConfig(AO_HOST); -const UPDATE_INTERVAL = 60; +export const UPDATE_INTERVAL = 60; /** * Toggles AO1-style loading using paginated music packets for mobile platforms. @@ -131,7 +132,11 @@ declare global { } function isLowMemory() { - if (/webOS|iPod|BlackBerry|BB|PlayBook|IEMobile|Windows Phone|Kindle|Silk|PlayStation|Nintendo|Opera Mini/i.test(navigator.userAgent)) { + if ( + /webOS|iPod|BlackBerry|BB|PlayBook|IEMobile|Windows Phone|Kindle|Silk|PlayStation|Nintendo|Opera Mini/i.test( + navigator.userAgent + ) + ) { oldLoading = true; } } @@ -141,12 +146,11 @@ fpPromise .then((result) => { hdid = result.visitorId; client = new Client(serverIP); - viewport = new Viewport(); isLowMemory(); client.loadResources(); }); -const delay = (ms: number) => new Promise(res => setTimeout(res, ms)); +export const delay = (ms: number) => new Promise((res) => setTimeout(res, ms)); let lastICMessageTime = new Date(0); @@ -172,24 +176,24 @@ class Client extends EventEmitter { selectedEvidence: number; checkUpdater: any; _lastTimeICReceived: any; - + viewport: Viewport; constructor(address: string) { super(); - if (mode !== 'replay') { + if (mode !== "replay") { this.serv = new WebSocket(`ws://${address}`); // Assign the websocket events - this.serv.addEventListener('open', this.emit.bind(this, 'open')); - this.serv.addEventListener('close', this.emit.bind(this, 'close')); - this.serv.addEventListener('message', this.emit.bind(this, 'message')); - this.serv.addEventListener('error', this.emit.bind(this, 'error')); + this.serv.addEventListener("open", this.emit.bind(this, "open")); + this.serv.addEventListener("close", this.emit.bind(this, "close")); + this.serv.addEventListener("message", this.emit.bind(this, "message")); + this.serv.addEventListener("error", this.emit.bind(this, "error")); } else { this.joinServer(); } - this.on('open', this.onOpen.bind(this)); - this.on('close', this.onClose.bind(this)); - this.on('message', this.onMessage.bind(this)); - this.on('error', this.onError.bind(this)); + this.on("open", this.onOpen.bind(this)); + this.on("close", this.onClose.bind(this)); + this.on("message", this.onMessage.bind(this)); + this.on("error", this.onError.bind(this)); // Preset some of the variables @@ -218,119 +222,128 @@ class Client extends EventEmitter { this.selectedEvidence = 0; this.checkUpdater = null; - + this.viewport = masterViewport(this, AO_HOST); /** - * Assign handlers for all commands - * If you implement a new command, you need to add it here - */ - this.on('MS', this.handleMS.bind(this)); - this.on('CT', this.handleCT.bind(this)); - this.on('MC', this.handleMC.bind(this)); - this.on('RMC', this.handleRMC.bind(this)); - this.on('CI', this.handleCI.bind(this)); - this.on('SC', this.handleSC.bind(this)); - this.on('EI', this.handleEI.bind(this)); - this.on('FL', this.handleFL.bind(this)); - this.on('LE', this.handleLE.bind(this)); - this.on('EM', this.handleEM.bind(this)); - this.on('FM', this.handleFM.bind(this)); - this.on('FA', this.handleFA.bind(this)); - this.on('SM', this.handleSM.bind(this)); - this.on('MM', this.handleMM.bind(this)); - this.on('BD', this.handleBD.bind(this)); - this.on('BB', this.handleBB.bind(this)); - this.on('KB', this.handleKB.bind(this)); - this.on('KK', this.handleKK.bind(this)); - this.on('DONE', this.handleDONE.bind(this)); - this.on('BN', this.handleBN.bind(this)); - this.on('HP', this.handleHP.bind(this)); - this.on('RT', this.handleRT.bind(this)); - this.on('TI', this.handleTI.bind(this)); - this.on('ZZ', this.handleZZ.bind(this)); - this.on('HI', this.handleHI.bind(this)); - this.on('ID', this.handleID.bind(this)); - this.on('PN', this.handlePN.bind(this)); - this.on('SI', this.handleSI.bind(this)); - this.on('ARUP', this.handleARUP.bind(this)); - this.on('askchaa', this.handleaskchaa.bind(this)); - this.on('CC', this.handleCC.bind(this)); - this.on('RC', this.handleRC.bind(this)); - this.on('RM', this.handleRM.bind(this)); - this.on('RD', this.handleRD.bind(this)); - this.on('CharsCheck', this.handleCharsCheck.bind(this)); - this.on('PV', this.handlePV.bind(this)); - this.on('ASS', this.handleASS.bind(this)); - this.on('CHECK', () => { }); - this.on('CH', () => { }); + * Assign handlers for all commands + * If you implement a new command, you need to add it here + */ + this.on("MS", this.handleMS.bind(this)); + this.on("CT", this.handleCT.bind(this)); + this.on("MC", this.handleMC.bind(this)); + this.on("RMC", this.handleRMC.bind(this)); + this.on("CI", this.handleCI.bind(this)); + this.on("SC", this.handleSC.bind(this)); + this.on("EI", this.handleEI.bind(this)); + this.on("FL", this.handleFL.bind(this)); + this.on("LE", this.handleLE.bind(this)); + this.on("EM", this.handleEM.bind(this)); + this.on("FM", this.handleFM.bind(this)); + this.on("FA", this.handleFA.bind(this)); + this.on("SM", this.handleSM.bind(this)); + this.on("MM", this.handleMM.bind(this)); + this.on("BD", this.handleBD.bind(this)); + this.on("BB", this.handleBB.bind(this)); + this.on("KB", this.handleKB.bind(this)); + this.on("KK", this.handleKK.bind(this)); + this.on("DONE", this.handleDONE.bind(this)); + this.on("BN", this.handleBN.bind(this)); + this.on("HP", this.handleHP.bind(this)); + this.on("RT", this.handleRT.bind(this)); + this.on("TI", this.handleTI.bind(this)); + this.on("ZZ", this.handleZZ.bind(this)); + this.on("HI", this.handleHI.bind(this)); + this.on("ID", this.handleID.bind(this)); + this.on("PN", this.handlePN.bind(this)); + this.on("SI", this.handleSI.bind(this)); + this.on("ARUP", this.handleARUP.bind(this)); + this.on("askchaa", this.handleaskchaa.bind(this)); + this.on("CC", this.handleCC.bind(this)); + this.on("RC", this.handleRC.bind(this)); + this.on("RM", this.handleRM.bind(this)); + this.on("RD", this.handleRD.bind(this)); + this.on("CharsCheck", this.handleCharsCheck.bind(this)); + this.on("PV", this.handlePV.bind(this)); + this.on("ASS", this.handleASS.bind(this)); + this.on("CHECK", () => {}); + this.on("CH", () => {}); this._lastTimeICReceived = new Date(0); } /** - * Gets the current player's character. - */ + * Gets the current player's character. + */ get character() { return this.chars[this.charID]; } /** - * Gets the player's currently selected emote. - */ + * Gets the player's currently selected emote. + */ get emote() { return this.emotes[this.selectedEmote]; } /** - * Gets the current evidence ID unless the player doesn't want to present any evidence - */ + * Gets the current evidence ID unless the player doesn't want to present any evidence + */ get evidence() { - return (document.getElementById('button_present').classList.contains('dark')) ? this.selectedEvidence : 0; + return document.getElementById("button_present").classList.contains("dark") + ? this.selectedEvidence + : 0; } /** - * Hook for sending messages to the server - * @param {string} message the message to send - */ + * Hook for sending messages to the server + * @param {string} message the message to send + */ sendServer(message: string) { //console.log("C: "+message); - mode === 'replay' ? this.sendSelf(message) : this.serv.send(message); + mode === "replay" ? this.sendSelf(message) : this.serv.send(message); } /** - * Hook for sending messages to the client - * @param {string} message the message to send - */ + * Hook for sending messages to the client + * @param {string} message the message to send + */ handleSelf(message: string) { - const message_event = new MessageEvent('websocket', { data: message }); + const message_event = new MessageEvent("websocket", { data: message }); setTimeout(() => this.onMessage(message_event), 1); } /** - * Hook for sending messages to the client - * @param {string} message the message to send - */ + * Hook for sending messages to the client + * @param {string} message the message to send + */ sendSelf(message: string) { - (document.getElementById('client_ooclog')).value += `${message}\r\n`; + (( + document.getElementById("client_ooclog") + )).value += `${message}\r\n`; this.handleSelf(message); } /** - * Sends an out-of-character chat message. - * @param {string} message the message to send - */ + * Sends an out-of-character chat message. + * @param {string} message the message to send + */ sendOOC(message: string) { - setCookie('OOC_name', (document.getElementById('OOC_name')).value); - const oocName = `${escapeChat((document.getElementById('OOC_name')).value)}`; + setCookie( + "OOC_name", + (document.getElementById("OOC_name")).value + ); + const oocName = `${escapeChat( + (document.getElementById("OOC_name")).value + )}`; const oocMessage = `${escapeChat(message)}`; const commands = { - '/save_chatlog': this.saveChatlogHandle - } - const commandsMap = new Map(Object.entries(commands)) + "/save_chatlog": this.saveChatlogHandle, + }; + const commandsMap = new Map(Object.entries(commands)); if (oocMessage && commandsMap.has(oocMessage.toLowerCase())) { try { - commandsMap.get(oocMessage.toLowerCase())() + commandsMap.get(oocMessage.toLowerCase())(); } catch (e) { // Command Not Recognized } @@ -340,26 +353,26 @@ class Client extends EventEmitter { } /** - * Sends an in-character chat message. - * @param {number} deskmod controls the desk - * @param {string} speaking who is speaking - * @param {string} name the name of the current character - * @param {string} silent whether or not it's silent - * @param {string} message the message to be sent - * @param {string} side the name of the side in the background - * @param {string} sfx_name the name of the sound effect - * @param {number} emote_modifier whether or not to zoom - * @param {number} sfx_delay the delay (in milliseconds) to play the sound effect - * @param {number} objection_modifier the number of the shout to play - * @param {string} evidence the filename of evidence to show - * @param {boolean} flip change to 1 to reverse sprite for position changes - * @param {boolean} realization screen flash effect - * @param {number} text_color text color - * @param {string} showname custom name to be displayed (optional) - * @param {number} other_charid paired character (optional) - * @param {number} self_offset offset to paired character (optional) - * @param {number} noninterrupting_preanim play the full preanim (optional) - */ + * Sends an in-character chat message. + * @param {number} deskmod controls the desk + * @param {string} speaking who is speaking + * @param {string} name the name of the current character + * @param {string} silent whether or not it's silent + * @param {string} message the message to be sent + * @param {string} side the name of the side in the background + * @param {string} sfx_name the name of the sound effect + * @param {number} emote_modifier whether or not to zoom + * @param {number} sfx_delay the delay (in milliseconds) to play the sound effect + * @param {number} objection_modifier the number of the shout to play + * @param {string} evidence the filename of evidence to show + * @param {boolean} flip change to 1 to reverse sprite for position changes + * @param {boolean} realization screen flash effect + * @param {number} text_color text color + * @param {string} showname custom name to be displayed (optional) + * @param {number} other_charid paired character (optional) + * @param {number} self_offset offset to paired character (optional) + * @param {number} noninterrupting_preanim play the full preanim (optional) + */ sendIC( deskmod: number, preanim: string, @@ -386,233 +399,283 @@ class Client extends EventEmitter { frame_realization: string, frame_sfx: string, additive: boolean, - effect: string, + effect: string ) { - let extra_cccc = ''; - let other_emote = ''; - let other_offset = ''; - let extra_27 = ''; - let extra_28 = ''; - - if (extrafeatures.includes('cccc_ic_support')) { - const self_offset = extrafeatures.includes('y_offset') ? `${self_hoffset}${self_yoffset}` : self_hoffset; // HACK: this should be an & but client fucked it up and all the servers adopted it - if (mode === 'replay') { - other_emote = '##'; - other_offset = '#0#0'; + let extra_cccc = ""; + let other_emote = ""; + let other_offset = ""; + let extra_27 = ""; + let extra_28 = ""; + + if (extrafeatures.includes("cccc_ic_support")) { + const self_offset = extrafeatures.includes("y_offset") + ? `${self_hoffset}${self_yoffset}` + : self_hoffset; // HACK: this should be an & but client fucked it up and all the servers adopted it + if (mode === "replay") { + other_emote = "##"; + other_offset = "#0#0"; } - extra_cccc = `${escapeChat(showname)}#${other_charid}${other_emote}#${self_offset}${other_offset}#${Number(noninterrupting_preanim)}#`; - - if (extrafeatures.includes('looping_sfx')) { - extra_27 = `${Number(looping_sfx)}#${Number(screenshake)}#${frame_screenshake}#${frame_realization}#${frame_sfx}#`; - if (extrafeatures.includes('effects')) { + extra_cccc = `${escapeChat( + showname + )}#${other_charid}${other_emote}#${self_offset}${other_offset}#${Number( + noninterrupting_preanim + )}#`; + + if (extrafeatures.includes("looping_sfx")) { + extra_27 = `${Number(looping_sfx)}#${Number( + screenshake + )}#${frame_screenshake}#${frame_realization}#${frame_sfx}#`; + if (extrafeatures.includes("effects")) { extra_28 = `${Number(additive)}#${escapeChat(effect)}#`; } } } - const serverMessage = `MS#${deskmod}#${escapeChat(preanim)}#${escapeChat(name)}#${escapeChat(emote)}` - + `#${escapeChat(message)}#${escapeChat(side)}#${escapeChat(sfx_name)}#${emote_modifier}` - + `#${this.charID}#${sfx_delay}#${Number(objection_modifier)}#${Number(evidence)}#${Number(flip)}#${Number(realization)}#${text_color}#${extra_cccc}${extra_27}${extra_28}%`; + const serverMessage = + `MS#${deskmod}#${escapeChat(preanim)}#${escapeChat(name)}#${escapeChat( + emote + )}` + + `#${escapeChat(message)}#${escapeChat(side)}#${escapeChat( + sfx_name + )}#${emote_modifier}` + + `#${this.charID}#${sfx_delay}#${Number(objection_modifier)}#${Number( + evidence + )}#${Number(flip)}#${Number( + realization + )}#${text_color}#${extra_cccc}${extra_27}${extra_28}%`; this.sendServer(serverMessage); - if (mode === 'replay') { - (document.getElementById('client_ooclog')).value += `wait#${(document.getElementById('client_replaytimer')).value}#%\r\n`; + if (mode === "replay") { + (( + document.getElementById("client_ooclog") + )).value += `wait#${ + (document.getElementById("client_replaytimer")).value + }#%\r\n`; } } /** - * Sends add evidence command. - * @param {string} evidence name - * @param {string} evidence description - * @param {string} evidence image filename - */ + * Sends add evidence command. + * @param {string} evidence name + * @param {string} evidence description + * @param {string} evidence image filename + */ sendPE(name: string, desc: string, img: string) { - this.sendServer(`PE#${escapeChat(name)}#${escapeChat(desc)}#${escapeChat(img)}#%`); + this.sendServer( + `PE#${escapeChat(name)}#${escapeChat(desc)}#${escapeChat(img)}#%` + ); } /** - * Sends edit evidence command. - * @param {number} evidence id - * @param {string} evidence name - * @param {string} evidence description - * @param {string} evidence image filename - */ + * Sends edit evidence command. + * @param {number} evidence id + * @param {string} evidence name + * @param {string} evidence description + * @param {string} evidence image filename + */ sendEE(id: number, name: string, desc: string, img: string) { - this.sendServer(`EE#${id}#${escapeChat(name)}#${escapeChat(desc)}#${escapeChat(img)}#%`); + this.sendServer( + `EE#${id}#${escapeChat(name)}#${escapeChat(desc)}#${escapeChat(img)}#%` + ); } /** - * Sends delete evidence command. - * @param {number} evidence id - */ + * Sends delete evidence command. + * @param {number} evidence id + */ sendDE(id: number) { this.sendServer(`DE#${id}#%`); } /** - * Sends health point command. - * @param {number} side the position - * @param {number} hp the health point - */ + * Sends health point command. + * @param {number} side the position + * @param {number} hp the health point + */ sendHP(side: number, hp: number) { this.sendServer(`HP#${side}#${hp}#%`); } /** - * Sends call mod command. - * @param {string} message to mod - */ + * Sends call mod command. + * @param {string} message to mod + */ sendZZ(msg: string) { - if (extrafeatures.includes('modcall_reason')) { + if (extrafeatures.includes("modcall_reason")) { this.sendServer(`ZZ#${msg}#%`); } else { - this.sendServer('ZZ#%'); + this.sendServer("ZZ#%"); } } /** - * Sends testimony command. - * @param {string} testimony type - */ + * Sends testimony command. + * @param {string} testimony type + */ sendRT(testimony: string) { - if (this.chars[this.charID].side === 'jud') { + if (this.chars[this.charID].side === "jud") { this.sendServer(`RT#${testimony}#%`); } } /** - * Requests to change the music to the specified track. - * @param {string} track the track ID - */ + * Requests to change the music to the specified track. + * @param {string} track the track ID + */ sendMusicChange(track: string) { this.sendServer(`MC#${track}#${this.charID}#%`); } /** - * Begins the handshake process by sending an identifier - * to the server. - */ + * Begins the handshake process by sending an identifier + * to the server. + */ joinServer() { this.sendServer(`HI#${hdid}#%`); - this.sendServer('ID#webAO#webAO#%'); - if (mode !== 'replay') { this.checkUpdater = setInterval(() => this.sendCheck(), 5000); } + this.sendServer("ID#webAO#webAO#%"); + if (mode !== "replay") { + this.checkUpdater = setInterval(() => this.sendCheck(), 5000); + } } /** - * Load game resources and stored settings. - */ + * Load game resources and stored settings. + */ loadResources() { - document.getElementById('client_version').innerText = `version ${version}`; + document.getElementById("client_version").innerText = `version ${version}`; // Load background array to select - const background_select = document.getElementById('bg_select'); - background_select.add(new Option('Custom', '0')); + const background_select = ( + document.getElementById("bg_select") + ); + background_select.add(new Option("Custom", "0")); vanilla_background_arr.forEach((background) => { background_select.add(new Option(background)); }); // Load evidence array to select - const evidence_select = document.getElementById('evi_select'); - evidence_select.add(new Option('Custom', '0')); + const evidence_select = ( + document.getElementById("evi_select") + ); + evidence_select.add(new Option("Custom", "0")); vanilla_evidence_arr.forEach((evidence) => { evidence_select.add(new Option(evidence)); }); // Read cookies and set the UI to its values - (document.getElementById('OOC_name')).value = getCookie('OOC_name') || `web${String(Math.round(Math.random() * 100 + 10))}`; + (document.getElementById("OOC_name")).value = + getCookie("OOC_name") || + `web${String(Math.round(Math.random() * 100 + 10))}`; // Read cookies and set the UI to its values - const cookietheme = getCookie('theme') || 'default'; + const cookietheme = getCookie("theme") || "default"; - (document.querySelector(`#client_themeselect [value="${cookietheme}"]`)).selected = true; - reloadTheme(); + (( + document.querySelector(`#client_themeselect [value="${cookietheme}"]`) + )).selected = true; + this.viewport.reloadTheme(); - const cookiechatbox = getCookie('chatbox') || 'dynamic'; + const cookiechatbox = getCookie("chatbox") || "dynamic"; - (document.querySelector(`#client_chatboxselect [value="${cookiechatbox}"]`)).selected = true; + (( + document.querySelector(`#client_chatboxselect [value="${cookiechatbox}"]`) + )).selected = true; setChatbox(cookiechatbox); - (document.getElementById('client_mvolume')).value = getCookie('musicVolume') || '1'; - changeMusicVolume(); - (document.getElementById('client_sfxaudio')).volume = Number(getCookie('sfxVolume')) || 1; + (document.getElementById("client_mvolume")).value = + getCookie("musicVolume") || "1"; + this.viewport.changeMusicVolume(); + (document.getElementById("client_sfxaudio")).volume = + Number(getCookie("sfxVolume")) || 1; changeSFXVolume(); - (document.getElementById('client_shoutaudio')).volume = Number(getCookie('shoutVolume')) || 1; + (document.getElementById("client_shoutaudio")).volume = + Number(getCookie("shoutVolume")) || 1; changeShoutVolume(); - (document.getElementById('client_testimonyaudio')).volume = Number(getCookie('testimonyVolume')) || 1; + (( + document.getElementById("client_testimonyaudio") + )).volume = Number(getCookie("testimonyVolume")) || 1; changeTestimonyVolume(); - (document.getElementById('client_bvolume')).value = getCookie('blipVolume') || '1'; - changeBlipVolume(); - - (document.getElementById('ic_chat_name')).value = getCookie('ic_chat_name'); - (document.getElementById('showname')).checked = Boolean(getCookie('showname')); + (document.getElementById("client_bvolume")).value = + getCookie("blipVolume") || "1"; + this.viewport.changeBlipVolume(); + + (document.getElementById("ic_chat_name")).value = + getCookie("ic_chat_name"); + (document.getElementById("showname")).checked = Boolean( + getCookie("showname") + ); showname_click(null); - (document.getElementById('client_callwords')).value = getCookie('callwords'); + (document.getElementById("client_callwords")).value = + getCookie("callwords"); } /** - * Requests to play as a specified character. - * @param {number} character the character ID - */ + * Requests to play as a specified character. + * @param {number} character the character ID + */ sendCharacter(character: number) { - console.log("sending "+character); + console.log("sending " + character); if (character === -1 || this.chars[character].name) { this.sendServer(`CC#${this.playerID}#${character}#web#%`); } } /** - * Requests to select a music track. - * @param {number?} song the song to be played - */ + * Requests to select a music track. + * @param {number?} song the song to be played + */ sendMusic(song: string) { this.sendServer(`MC#${song}#${this.charID}#%`); } /** - * Sends a keepalive packet. - */ + * Sends a keepalive packet. + */ sendCheck() { this.sendServer(`CH#${this.charID}#%`); } /** - * Triggered when a connection is established to the server. - */ + * Triggered when a connection is established to the server. + */ onOpen(_e: Event) { client.joinServer(); } /** - * Triggered when the connection to the server closes. - * @param {CloseEvent} e - */ + * Triggered when the connection to the server closes. + * @param {CloseEvent} e + */ onClose(e: CloseEvent) { console.error(`The connection was closed: ${e.reason} (${e.code})`); if (extrafeatures.length == 0 && banned === false) { - document.getElementById('client_errortext').textContent = 'Could not connect to the server'; + document.getElementById("client_errortext").textContent = + "Could not connect to the server"; } - document.getElementById('client_waiting').style.display = 'block'; - document.getElementById('client_error').style.display = 'flex'; - document.getElementById('client_loading').style.display = 'none'; - document.getElementById('error_id').textContent = String(e.code); + document.getElementById("client_waiting").style.display = "block"; + document.getElementById("client_error").style.display = "flex"; + document.getElementById("client_loading").style.display = "none"; + document.getElementById("error_id").textContent = String(e.code); this.cleanup(); } /** - * Triggered when a packet is received from the server. - * @param {MessageEvent} e - */ + * Triggered when a packet is received from the server. + * @param {MessageEvent} e + */ onMessage(e: MessageEvent) { const msg = e.data; console.debug(`S: ${msg}`); - const lines = msg.split('%'); + const lines = msg.split("%"); for (const msg of lines) { - if (msg === '') { break; } + if (msg === "") { + break; + } - const args = msg.split('#'); + const args = msg.split("#"); const header = args[0]; if (!this.emit(header, args)) { @@ -622,41 +685,43 @@ class Client extends EventEmitter { } /** - * Triggered when an network error occurs. - * @param {ErrorEvent} e - */ + * Triggered when an network error occurs. + * @param {ErrorEvent} e + */ onError(e: ErrorEvent) { console.error(`A network error occurred`); - document.getElementById('client_error').style.display = 'flex'; + document.getElementById("client_error").style.display = "flex"; this.cleanup(); } /** - * Stop sending keepalives to the server. - */ + * Stop sending keepalives to the server. + */ cleanup() { clearInterval(this.checkUpdater); - this.serv.close() + this.serv.close(); } /** - * Parse the lines in the OOC and play them - * @param {*} args packet arguments - */ + * Parse the lines in the OOC and play them + * @param {*} args packet arguments + */ handleReplay() { - const ooclog = document.getElementById('client_ooclog'); + const ooclog = document.getElementById("client_ooclog"); const rawLog = false; - let rtime: number = Number((document.getElementById('client_replaytimer')).value); + let rtime: number = Number( + (document.getElementById("client_replaytimer")).value + ); const clines = ooclog.value.split(/\r?\n/); if (clines[0]) { const currentLine = String(clines[0]); this.handleSelf(currentLine); - ooclog.value = clines.slice(1).join('\r\n'); - if (currentLine.substr(0, 4) === 'wait' && rawLog === false) { - rtime = Number(currentLine.split('#')[1]); - } else if (currentLine.substr(0, 2) !== 'MS') { + ooclog.value = clines.slice(1).join("\r\n"); + if (currentLine.substr(0, 4) === "wait" && rawLog === false) { + rtime = Number(currentLine.split("#")[1]); + } else if (currentLine.substr(0, 2) !== "MS") { rtime = 0; } @@ -665,70 +730,69 @@ class Client extends EventEmitter { } saveChatlogHandle = async () => { - const clientLog = document.getElementById('client_log') - const icMessageLogs = clientLog.getElementsByTagName('p') - const messages = [] + const clientLog = document.getElementById("client_log"); + const icMessageLogs = clientLog.getElementsByTagName("p"); + const messages = []; for (let i = 0; i < icMessageLogs.length; i++) { - const SHOWNAME_POSITION = 0 - const TEXT_POSITION = 2 - const showname = icMessageLogs[i].children[SHOWNAME_POSITION].innerHTML - const text = icMessageLogs[i].children[TEXT_POSITION].innerHTML - const message = `${showname}: ${text}` - messages.push(message) + const SHOWNAME_POSITION = 0; + const TEXT_POSITION = 2; + const showname = icMessageLogs[i].children[SHOWNAME_POSITION].innerHTML; + const text = icMessageLogs[i].children[TEXT_POSITION].innerHTML; + const message = `${showname}: ${text}`; + messages.push(message); } const d = new Date(); - let ye = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(d); - let mo = new Intl.DateTimeFormat('en', { month: 'short' }).format(d); - let da = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(d); + let ye = new Intl.DateTimeFormat("en", { year: "numeric" }).format(d); + let mo = new Intl.DateTimeFormat("en", { month: "short" }).format(d); + let da = new Intl.DateTimeFormat("en", { day: "2-digit" }).format(d); const filename = `chatlog-${da}-${mo}-${ye}`.toLowerCase(); - downloadFile(messages.join('\n'), filename); + downloadFile(messages.join("\n"), filename); // Reset Chatbox to Empty - (document.getElementById('client_inputbox')).value = ''; - } - + (document.getElementById("client_inputbox")).value = ""; + }; + /** - * Handles an in-character chat message. - * @param {*} args packet arguments - */ + * Handles an in-character chat message. + * @param {*} args packet arguments + */ handleMS(args: string[]) { - // TODO: this if-statement might be a bug. - if (args[4] !== viewport.chatmsg.content) { - document.getElementById('client_inner_chat').innerHTML = ''; + if (args[4] !== this.viewport.chatmsg.content) { + document.getElementById("client_inner_chat").innerHTML = ""; const char_id = Number(args[9]); const char_name = safeTags(args[3]); let msg_nameplate = args[3]; - let msg_blips = 'male'; - let char_chatbox = 'default'; + let msg_blips = "male"; + let char_chatbox = "default"; let char_muted = false; if (this.chars[char_id].name !== char_name) { - console.info(`${this.chars[char_id].name} is iniediting to ${char_name}`); - const chargs = (`${char_name}&` + 'iniediter').split('&'); + console.info( + `${this.chars[char_id].name} is iniediting to ${char_name}` + ); + const chargs = (`${char_name}&` + "iniediter").split("&"); this.handleCharacterInfo(chargs, char_id); } try { - msg_nameplate = this.chars[char_id].showname; + msg_nameplate = this.chars[char_id].showname; } catch (e) { msg_nameplate = args[3]; } try { msg_blips = this.chars[char_id].blips; - } catch (e) { - ; - } + } catch (e) {} try { char_chatbox = this.chars[char_id].chat; } catch (e) { - char_chatbox = 'default'; + char_chatbox = "default"; } try { @@ -758,23 +822,23 @@ class Client extends EventEmitter { flip: Number(args[13]), flash: Number(args[14]), color: Number(args[15]), - speed: UPDATE_INTERVAL + speed: UPDATE_INTERVAL, }; - if (extrafeatures.includes('cccc_ic_support')) { + if (extrafeatures.includes("cccc_ic_support")) { const extra_cccc = { showname: safeTags(args[16]), other_charid: Number(args[17]), other_name: safeTags(args[18]), other_emote: safeTags(args[19]), - self_offset: args[20].split(''), // HACK: here as well, client is fucked and uses this instead of & - other_offset: args[21].split(''), + self_offset: args[20].split(""), // HACK: here as well, client is fucked and uses this instead of & + other_offset: args[21].split(""), other_flip: Number(args[22]), noninterrupting_preanim: Number(args[23]), }; chatmsg = Object.assign(extra_cccc, chatmsg); - if (extrafeatures.includes('looping_sfx')) { + if (extrafeatures.includes("looping_sfx")) { const extra_27 = { looping_sfx: Number(args[24]), screenshake: Number(args[25]), @@ -784,16 +848,16 @@ class Client extends EventEmitter { }; chatmsg = Object.assign(extra_27, chatmsg); - if (extrafeatures.includes('effects')) { + if (extrafeatures.includes("effects")) { const extra_28 = { additive: Number(args[29]), - effects: args[30].split('|'), + effects: args[30].split("|"), }; chatmsg = Object.assign(extra_28, chatmsg); } else { const extra_28 = { additive: 0, - effects: ['', '', ''], + effects: ["", "", ""], }; chatmsg = Object.assign(extra_28, chatmsg); } @@ -801,23 +865,23 @@ class Client extends EventEmitter { const extra_27 = { looping_sfx: 0, screenshake: 0, - frame_screenshake: '', - frame_realization: '', - frame_sfx: '', + frame_screenshake: "", + frame_realization: "", + frame_sfx: "", }; chatmsg = Object.assign(extra_27, chatmsg); const extra_28 = { additive: 0, - effects: ['', '', ''], + effects: ["", "", ""], }; chatmsg = Object.assign(extra_28, chatmsg); } } else { const extra_cccc = { - showname: '', + showname: "", other_charid: 0, - other_name: '', - other_emote: '', + other_name: "", + other_emote: "", self_offset: [0, 0], other_offset: [0, 0], other_flip: 0, @@ -827,14 +891,14 @@ class Client extends EventEmitter { const extra_27 = { looping_sfx: 0, screenshake: 0, - frame_screenshake: '', - frame_realization: '', - frame_sfx: '', + frame_screenshake: "", + frame_realization: "", + frame_sfx: "", }; chatmsg = Object.assign(extra_27, chatmsg); const extra_28 = { additive: 0, - effects: ['', '', ''], + effects: ["", "", ""], }; chatmsg = Object.assign(extra_28, chatmsg); } @@ -843,42 +907,42 @@ class Client extends EventEmitter { if (chatmsg.charid === this.charID) { resetICParams(); } - viewport.handle_ic_speaking(chatmsg); // no await + console.log(chatmsg); + this.viewport.handle_ic_speaking(chatmsg); // no await } } } /** - * Handles an out-of-character chat message. - * @param {Array} args packet arguments - */ + * Handles an out-of-character chat message. + * @param {Array} args packet arguments + */ handleCT(args: string[]) { - if (mode !== 'replay') { - const oocLog = document.getElementById('client_ooclog'); + if (mode !== "replay") { + const oocLog = document.getElementById("client_ooclog"); oocLog.innerHTML += `${prepChat(args[1])}: ${prepChat(args[2])}\r\n`; if (oocLog.scrollTop > oocLog.scrollHeight - 600) { oocLog.scrollTop = oocLog.scrollHeight; } } - } /** - * Handles a music change to an arbitrary resource. - * @param {Array} args packet arguments - */ + * Handles a music change to an arbitrary resource. + * @param {Array} args packet arguments + */ handleMC(args: string[]) { const track = prepChat(args[1]); let charID = Number(args[2]); - const showname = args[3] || ''; + const showname = args[3] || ""; const looping = Boolean(args[4]); const channel = Number(args[5]) || 0; // const fading = Number(args[6]) || 0; // unused in web - const music = viewport.music[channel]; + const music = this.viewport.music[channel]; let musicname; music.pause(); - if (track.startsWith('http')) { + if (track.startsWith("http")) { music.src = track; } else { music.src = `${AO_HOST}sounds/music/${encodeURI(track.toLowerCase())}`; @@ -899,41 +963,48 @@ class Client extends EventEmitter { appendICLog(`The music was changed to ${track}`); } - document.getElementById('client_trackstatustext').innerText = track; + document.getElementById("client_trackstatustext").innerText = track; } + // TODO BUG: + // this.viewport.music is an array. Therefore you must access elements /** - * Handles a music change to an arbitrary resource, with an offset in seconds. - * @param {Array} args packet arguments - */ + * Handles a music change to an arbitrary resource, with an offset in seconds. + * @param {Array} args packet arguments + */ handleRMC(args: string[]) { - viewport.music.pause(); - const { music } = viewport; + this.viewport.music.pause(); + const { music } = this.viewport; // Music offset + drift from song loading music.totime = args[1]; music.offset = new Date().getTime() / 1000; - music.addEventListener('loadedmetadata', () => { - music.currentTime += parseFloat(music.totime + (new Date().getTime() / 1000 - music.offset)).toFixed(3); - music.play(); - }, false); + music.addEventListener( + "loadedmetadata", + () => { + music.currentTime += parseFloat( + music.totime + (new Date().getTime() / 1000 - music.offset) + ).toFixed(3); + music.play(); + }, + false + ); } /** - * Handles the incoming character information, and downloads the sprite + ini for it - * @param {Array} chargs packet arguments - * @param {Number} charid character ID - */ + * Handles the incoming character information, and downloads the sprite + ini for it + * @param {Array} chargs packet arguments + * @param {Number} charid character ID + */ async handleCharacterInfo(chargs: string[], charid: number) { const img = document.getElementById(`demo_${charid}`); if (chargs[0]) { let cini: any = {}; const getCharIcon = async () => { - const extensions = [ - '.png', - '.webp', - ]; + const extensions = [".png", ".webp"]; img.alt = chargs[0]; - const charIconBaseUrl = `${AO_HOST}characters/${encodeURI(chargs[0].toLowerCase())}/char_icon`; + const charIconBaseUrl = `${AO_HOST}characters/${encodeURI( + chargs[0].toLowerCase() + )}/char_icon`; for (let i = 0; i < extensions.length; i++) { const fileUrl = charIconBaseUrl + extensions[i]; const exists = await fileExists(fileUrl); @@ -948,28 +1019,34 @@ class Client extends EventEmitter { // If the ini doesn't exist on the server this will throw an error try { - const cinidata = await request(`${AO_HOST}characters/${encodeURI(chargs[0].toLowerCase())}/char.ini`); + const cinidata = await request( + `${AO_HOST}characters/${encodeURI(chargs[0].toLowerCase())}/char.ini` + ); cini = iniParse(cinidata); } catch (err) { cini = {}; - img.classList.add('noini'); + img.classList.add("noini"); console.warn(`character ${chargs[0]} is missing from webAO`); // If it does, give the user a visual indication that the character is unusable } - const mute_select = document.getElementById('mute_select'); + const mute_select = ( + document.getElementById("mute_select") + ); mute_select.add(new Option(safeTags(chargs[0]), String(charid))); - const pair_select = document.getElementById('pair_select'); + const pair_select = ( + document.getElementById("pair_select") + ); pair_select.add(new Option(safeTags(chargs[0]), String(charid))); // sometimes ini files lack important settings const default_options = { name: chargs[0], showname: chargs[0], - side: 'def', - blips: 'male', - chat: '', - category: '', + side: "def", + blips: "male", + chat: "", + category: "", }; cini.options = Object.assign(default_options, cini.options); @@ -986,107 +1063,133 @@ class Client extends EventEmitter { blips: safeTags(cini.options.blips).toLowerCase(), gender: safeTags(cini.options.gender).toLowerCase(), side: safeTags(cini.options.side).toLowerCase(), - chat: (cini.options.chat === '' ) ? safeTags(cini.options.category).toLowerCase() : safeTags(cini.options.chat).toLowerCase(), + chat: + cini.options.chat === "" + ? safeTags(cini.options.category).toLowerCase() + : safeTags(cini.options.chat).toLowerCase(), evidence: chargs[3], icon: img.src, inifile: cini, muted: false, }; - if (this.chars[charid].blips === 'male' && this.chars[charid].gender !== 'male' && this.chars[charid].gender !== '') { this.chars[charid].blips = this.chars[charid].gender; } + if ( + this.chars[charid].blips === "male" && + this.chars[charid].gender !== "male" && + this.chars[charid].gender !== "" + ) { + this.chars[charid].blips = this.chars[charid].gender; + } - const iniedit_select = document.getElementById('client_ininame'); + const iniedit_select = ( + document.getElementById("client_ininame") + ); iniedit_select.add(new Option(safeTags(chargs[0]))); } else { console.warn(`missing charid ${charid}`); - img.style.display = 'none'; + img.style.display = "none"; } } /** - * Handles incoming character information, bundling multiple characters - * per packet. - * CI#0#Phoenix&description&&&&#Miles ... - * @param {Array} args packet arguments - */ + * Handles incoming character information, bundling multiple characters + * per packet. + * CI#0#Phoenix&description&&&&#Miles ... + * @param {Array} args packet arguments + */ handleCI(args: string[]) { // Loop through the 10 characters that were sent for (let i = 2; i <= args.length - 2; i++) { if (i % 2 === 0) { - document.getElementById('client_loadingtext').innerHTML = `Loading Character ${args[1]}/${this.char_list_length}`; - const chargs = args[i].split('&'); + document.getElementById( + "client_loadingtext" + ).innerHTML = `Loading Character ${args[1]}/${this.char_list_length}`; + const chargs = args[i].split("&"); const charid = Number(args[i - 1]); - (document.getElementById('client_loadingbar')).value = charid; + (( + document.getElementById("client_loadingbar") + )).value = charid; setTimeout(() => this.handleCharacterInfo(chargs, charid), 500); } } // Request the next pack - this.sendServer(`AN#${(Number(args[1]) / 10) + 1}#%`); + this.sendServer(`AN#${Number(args[1]) / 10 + 1}#%`); } /** - * Handles incoming character information, containing all characters - * in one packet. - * @param {Array} args packet arguments - */ + * Handles incoming character information, containing all characters + * in one packet. + * @param {Array} args packet arguments + */ async handleSC(args: string[]) { const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); - if (mode === 'watch') { // Spectators don't need to pick a character - document.getElementById('client_charselect').style.display = 'none'; + if (mode === "watch") { + // Spectators don't need to pick a character + document.getElementById("client_charselect").style.display = "none"; } else { - document.getElementById('client_charselect').style.display = 'block'; + document.getElementById("client_charselect").style.display = "block"; } - document.getElementById('client_loadingtext').innerHTML = 'Loading Characters'; + document.getElementById("client_loadingtext").innerHTML = + "Loading Characters"; for (let i = 1; i < args.length; i++) { - document.getElementById('client_loadingtext').innerHTML = `Loading Character ${i}/${this.char_list_length}`; - const chargs = args[i].split('&'); + document.getElementById( + "client_loadingtext" + ).innerHTML = `Loading Character ${i}/${this.char_list_length}`; + const chargs = args[i].split("&"); const charid = i - 1; - (document.getElementById('client_loadingbar')).value = charid; + (( + document.getElementById("client_loadingbar") + )).value = charid; await sleep(0.1); // TODO: Too many network calls without this. net::ERR_INSUFFICIENT_RESOURCES this.handleCharacterInfo(chargs, charid); } // We're done with the characters, request the music - this.sendServer('RM#%'); + this.sendServer("RM#%"); } /** - * Handles incoming evidence information, containing only one evidence - * item per packet. - * - * EI#id#name&description&type&image&##% - * - * @param {Array} args packet arguments - */ + * Handles incoming evidence information, containing only one evidence + * item per packet. + * + * EI#id#name&description&type&image&##% + * + * @param {Array} args packet arguments + */ handleEI(args: string[]) { - document.getElementById('client_loadingtext').innerHTML = `Loading Evidence ${args[1]}/${this.evidence_list_length}`; + document.getElementById( + "client_loadingtext" + ).innerHTML = `Loading Evidence ${args[1]}/${this.evidence_list_length}`; const evidenceID = Number(args[1]); - (document.getElementById('client_loadingbar')).value = this.char_list_length + evidenceID; - - const arg = args[2].split('&'); - this.evidences[evidenceID] = { - name: prepChat(arg[0]), - desc: prepChat(arg[1]), - filename: safeTags(arg[3]), - icon: `${AO_HOST}evidence/${encodeURI(arg[3].toLowerCase())}`, - }; - - this.sendServer('AE'+(evidenceID+1)+'#%'); + (document.getElementById("client_loadingbar")).value = + this.char_list_length + evidenceID; + + const arg = args[2].split("&"); + this.evidences[evidenceID] = { + name: prepChat(arg[0]), + desc: prepChat(arg[1]), + filename: safeTags(arg[3]), + icon: `${AO_HOST}evidence/${encodeURI(arg[3].toLowerCase())}`, + }; + + this.sendServer("AE" + (evidenceID + 1) + "#%"); } /** - * Handles incoming evidence list, all evidences at once - * item per packet. - * - * @param {Array} args packet arguments - */ + * Handles incoming evidence list, all evidences at once + * item per packet. + * + * @param {Array} args packet arguments + */ handleLE(args: string[]) { this.evidences = []; for (let i = 1; i < args.length - 1; i++) { - (document.getElementById('client_loadingbar')).value = this.char_list_length + i; - const arg = args[i].split('&'); + (( + document.getElementById("client_loadingbar") + )).value = this.char_list_length + i; + const arg = args[i].split("&"); this.evidences[i - 1] = { name: prepChat(arg[0]), desc: prepChat(arg[1]), @@ -1095,8 +1198,8 @@ class Client extends EventEmitter { }; } - const evidence_box = document.getElementById('evidences'); - evidence_box.innerHTML = ''; + const evidence_box = document.getElementById("evidences"); + evidence_box.innerHTML = ""; for (let i = 1; i <= this.evidences.length; i++) { evidence_box.innerHTML += `document.getElementById('bg_select'); - bg_select.innerHTML = ''; + const bg_select = document.getElementById("bg_select"); + bg_select.innerHTML = ""; - bg_select.add(new Option('Custom', '0')); + bg_select.add(new Option("Custom", "0")); bg_array.forEach((background: string) => { bg_select.add(new Option(background)); }); } catch (err) { - console.warn('there was no backgrounds.json file'); + console.warn("there was no backgrounds.json file"); } } @@ -1143,14 +1246,16 @@ class Client extends EventEmitter { const char_array = JSON.parse(chardata); // the try catch will fail before here when there is no file - const char_select = document.getElementById('client_ininame'); - char_select.innerHTML = ''; + const char_select = ( + document.getElementById("client_ininame") + ); + char_select.innerHTML = ""; char_array.forEach((character: string) => { char_select.add(new Option(character)); }); } catch (err) { - console.warn('there was no characters.json file'); + console.warn("there was no characters.json file"); } } @@ -1160,29 +1265,35 @@ class Client extends EventEmitter { const evi_array = JSON.parse(evidata); // the try catch will fail before here when there is no file - const evi_select = document.getElementById('evi_select'); - evi_select.innerHTML = ''; + const evi_select = ( + document.getElementById("evi_select") + ); + evi_select.innerHTML = ""; evi_array.forEach((evi: string) => { evi_select.add(new Option(evi)); }); - evi_select.add(new Option('Custom', '0')); + evi_select.add(new Option("Custom", "0")); } catch (err) { - console.warn('there was no evidence.json file'); + console.warn("there was no evidence.json file"); } } isAudio(trackname: string) { - const audioEndings = ['.wav', '.mp3', '.ogg', '.opus']; - return audioEndings.filter((ending) => trackname.endsWith(ending)).length === 1; + const audioEndings = [".wav", ".mp3", ".ogg", ".opus"]; + return ( + audioEndings.filter((ending) => trackname.endsWith(ending)).length === 1 + ); } addTrack(trackname: string) { - const newentry = document.createElement('OPTION'); + const newentry = document.createElement("OPTION"); const songName = getFilenameFromPath(trackname); newentry.text = unescapeChat(songName); newentry.value = trackname; - (document.getElementById('client_musiclist')).options.add(newentry); + (( + document.getElementById("client_musiclist") + )).options.add(newentry); this.musics.push(trackname); } @@ -1190,49 +1301,50 @@ class Client extends EventEmitter { const thisarea = { name, players: 0, - status: 'IDLE', - cm: '', - locked: 'FREE', + status: "IDLE", + cm: "", + locked: "FREE", }; this.areas.push(thisarea); // Create area button - const newarea = document.createElement('SPAN'); - newarea.className = 'area-button area-default'; + const newarea = document.createElement("SPAN"); + newarea.className = "area-button area-default"; newarea.id = `area${id}`; newarea.innerText = thisarea.name; - newarea.title = `Players: ${thisarea.players}\n` - + `Status: ${thisarea.status}\n` - + `CM: ${thisarea.cm}\n` - + `Area lock: ${thisarea.locked}`; + newarea.title = + `Players: ${thisarea.players}\n` + + `Status: ${thisarea.status}\n` + + `CM: ${thisarea.cm}\n` + + `Area lock: ${thisarea.locked}`; newarea.onclick = function () { area_click(newarea); }; - document.getElementById('areas').appendChild(newarea); + document.getElementById("areas").appendChild(newarea); } /** - * Area list fuckery - */ + * Area list fuckery + */ fix_last_area() { if (this.areas.length > 0) { const malplaced = this.areas.pop().name; - const areas = document.getElementById('areas'); + const areas = document.getElementById("areas"); areas.removeChild(areas.lastChild); this.addTrack(malplaced); } } /** - * Handles incoming music information, containing multiple entries - * per packet. - * @param {Array} args packet arguments - */ + * Handles incoming music information, containing multiple entries + * per packet. + * @param {Array} args packet arguments + */ handleEM(args: string[]) { - document.getElementById('client_loadingtext').innerHTML = 'Loading Music'; - if (args[1] === '0') { + document.getElementById("client_loadingtext").innerHTML = "Loading Music"; + if (args[1] === "0") { this.resetMusicList(); this.resetAreaList(); this.musics_time = false; @@ -1242,29 +1354,32 @@ class Client extends EventEmitter { if (i % 2 === 0) { const trackname = safeTags(args[i]); const trackindex = Number(args[i - 1]); - (document.getElementById('client_loadingbar')).value = this.char_list_length + this.evidence_list_length + trackindex; + (( + document.getElementById("client_loadingbar") + )).value = + this.char_list_length + this.evidence_list_length + trackindex; if (this.musics_time) { - this.addTrack(trackname); - } else if (this.isAudio(trackname)) { - this.musics_time = true; - this.fix_last_area(); - this.addTrack(trackname); - } else { - this.createArea(trackindex, trackname); - } + this.addTrack(trackname); + } else if (this.isAudio(trackname)) { + this.musics_time = true; + this.fix_last_area(); + this.addTrack(trackname); + } else { + this.createArea(trackindex, trackname); + } } } // get the next batch of tracks - this.sendServer(`AM#${(Number(args[1]) / 10) + 1}#%`); + this.sendServer(`AM#${Number(args[1]) / 10 + 1}#%`); } /** - * Handles incoming music information, containing all music in one packet. - * @param {Array} args packet arguments - */ + * Handles incoming music information, containing all music in one packet. + * @param {Array} args packet arguments + */ handleSM(args: string[]) { - document.getElementById('client_loadingtext').innerHTML = 'Loading Music '; + document.getElementById("client_loadingtext").innerHTML = "Loading Music "; this.resetMusicList(); this.resetAreaList(); @@ -1274,8 +1389,12 @@ class Client extends EventEmitter { // Check when found the song for the first time const trackname = args[i]; const trackindex = i - 1; - document.getElementById('client_loadingtext').innerHTML = `Loading Music ${i}/${this.music_list_length}`; - (document.getElementById('client_loadingbar')).value = this.char_list_length + this.evidence_list_length + i; + document.getElementById( + "client_loadingtext" + ).innerHTML = `Loading Music ${i}/${this.music_list_length}`; + (( + document.getElementById("client_loadingbar") + )).value = this.char_list_length + this.evidence_list_length + i; if (this.musics_time) { this.addTrack(trackname); } else if (this.isAudio(trackname)) { @@ -1288,13 +1407,13 @@ class Client extends EventEmitter { } // Music done, carry on - this.sendServer('RD#%'); + this.sendServer("RD#%"); } /** - * Handles updated music list - * @param {Array} args packet arguments - */ + * Handles updated music list + * @param {Array} args packet arguments + */ handleFM(args: string[]) { this.resetMusicList(); @@ -1305,9 +1424,9 @@ class Client extends EventEmitter { } /** - * Handles updated area list - * @param {Array} args packet arguments - */ + * Handles updated area list + * @param {Array} args packet arguments + */ handleFA(args: string[]) { this.resetAreaList(); @@ -1317,249 +1436,306 @@ class Client extends EventEmitter { } /** - * Handles the "MusicMode" packet - * @param {Array} args packet arguments - */ + * Handles the "MusicMode" packet + * @param {Array} args packet arguments + */ handleMM(_args: string[]) { // It's unused nowadays, as preventing people from changing the music is now serverside } /** - * Handles the kicked packet - * @param {string} type is it a kick or a ban - * @param {string} reason why - */ + * Handles the kicked packet + * @param {string} type is it a kick or a ban + * @param {string} reason why + */ handleBans(type: string, reason: string) { - document.getElementById('client_error').style.display = 'flex'; - document.getElementById('client_errortext').innerHTML = `${type}:
${reason.replace(/\n/g, '
')}`; - (document.getElementsByClassName('client_reconnect')[0]).style.display = 'none'; - (document.getElementsByClassName('client_reconnect')[1]).style.display = 'none'; - } - - /** - * Handles the kicked packet - * @param {Array} args kick reason - */ + document.getElementById("client_error").style.display = "flex"; + document.getElementById( + "client_errortext" + ).innerHTML = `${type}:
${reason.replace(/\n/g, "
")}`; + (( + document.getElementsByClassName("client_reconnect")[0] + )).style.display = "none"; + (( + document.getElementsByClassName("client_reconnect")[1] + )).style.display = "none"; + } + + /** + * Handles the kicked packet + * @param {Array} args kick reason + */ handleKK(args: string[]) { - this.handleBans('Kicked', safeTags(args[1])); + this.handleBans("Kicked", safeTags(args[1])); } /** - * Handles the banned packet - * this one is sent when you are kicked off the server - * @param {Array} args ban reason - */ + * Handles the banned packet + * this one is sent when you are kicked off the server + * @param {Array} args ban reason + */ handleKB(args: string[]) { - this.handleBans('Banned', safeTags(args[1])); + this.handleBans("Banned", safeTags(args[1])); banned = true; } /** - * Handles the warning packet - * on client this spawns a message box you can't close for 2 seconds - * @param {Array} args ban reason - */ - handleBB(args: string[]) { + * Handles the warning packet + * on client this spawns a message box you can't close for 2 seconds + * @param {Array} args ban reason + */ + handleBB(args: string[]) { alert(safeTags(args[1])); } /** - * Handles the banned packet - * this one is sent when you try to reconnect but you're banned - * @param {Array} args ban reason - */ + * Handles the banned packet + * this one is sent when you try to reconnect but you're banned + * @param {Array} args ban reason + */ handleBD(args: string[]) { - this.handleBans('Banned', safeTags(args[1])); + this.handleBans("Banned", safeTags(args[1])); banned = true; } /** - * Handles the handshake completion packet, meaning the player - * is ready to select a character. - * - * @param {Array} args packet arguments - */ + * Handles the handshake completion packet, meaning the player + * is ready to select a character. + * + * @param {Array} args packet arguments + */ handleDONE(_args: string[]) { - document.getElementById('client_loading').style.display = 'none'; - if (mode === 'watch') { // Spectators don't need to pick a character - document.getElementById('client_waiting').style.display = 'none'; + document.getElementById("client_loading").style.display = "none"; + if (mode === "watch") { + // Spectators don't need to pick a character + document.getElementById("client_waiting").style.display = "none"; } } - + /** - * Handles a background change. - * @param {Array} args packet arguments - */ - + * Handles a background change. + * @param {Array} args packet arguments + */ + handleBN(args: string[]) { - viewport.bgname = safeTags(args[1]); - const bgfolder = viewport.bgFolder; - const bg_index = getIndexFromSelect('bg_select', viewport.bgname); - (document.getElementById('bg_select')).selectedIndex = bg_index; + this.viewport.bgname = safeTags(args[1]); + const bgfolder = this.viewport.bgFolder; + const bg_index = getIndexFromSelect("bg_select", this.viewport.bgname); + (document.getElementById("bg_select")).selectedIndex = + bg_index; updateBackgroundPreview(); if (bg_index === 0) { - (document.getElementById('bg_filename')).value = viewport.bgname; - } - - tryUrls(`${AO_HOST}background/${encodeURI(args[1].toLowerCase())}/defenseempty`).then(resp => {(document.getElementById('bg_preview')).src = resp}); - tryUrls(`${bgfolder}defensedesk`).then((resp) => {(document.getElementById('client_def_bench')).src = resp}); - tryUrls(`${bgfolder}stand`).then(resp => {(document.getElementById('client_wit_bench')).src = resp}); - tryUrls(`${bgfolder}prosecutiondesk`).then(resp => {(document.getElementById('client_pro_bench')).src = resp}); - tryUrls(`${bgfolder}full`).then(resp => {(document.getElementById('client_court')).src = resp}); - tryUrls(`${bgfolder}defenseempty`).then(resp => {(document.getElementById('client_court_def')).src = resp}); - tryUrls(`${bgfolder}transition_def`).then(resp => {(document.getElementById('client_court_deft')).src = resp}); - tryUrls(`${bgfolder}witnessempty`).then(resp => {(document.getElementById('client_court_wit')).src = resp}); - tryUrls(`${bgfolder}transition_pro`).then(resp => {(document.getElementById('client_court_prot')).src = resp}); - tryUrls(`${bgfolder}prosecutorempty`).then(resp => {(document.getElementById('client_court_pro')).src = resp}); + (document.getElementById("bg_filename")).value = + this.viewport.bgname; + } + + tryUrls( + `${AO_HOST}background/${encodeURI(args[1].toLowerCase())}/defenseempty` + ).then((resp) => { + (document.getElementById("bg_preview")).src = resp; + }); + tryUrls(`${bgfolder}defensedesk`).then((resp) => { + (document.getElementById("client_def_bench")).src = + resp; + }); + tryUrls(`${bgfolder}stand`).then((resp) => { + (document.getElementById("client_wit_bench")).src = + resp; + }); + tryUrls(`${bgfolder}prosecutiondesk`).then((resp) => { + (document.getElementById("client_pro_bench")).src = + resp; + }); + tryUrls(`${bgfolder}full`).then((resp) => { + (document.getElementById("client_court")).src = resp; + }); + tryUrls(`${bgfolder}defenseempty`).then((resp) => { + (document.getElementById("client_court_def")).src = + resp; + }); + tryUrls(`${bgfolder}transition_def`).then((resp) => { + (document.getElementById("client_court_deft")).src = + resp; + }); + tryUrls(`${bgfolder}witnessempty`).then((resp) => { + (document.getElementById("client_court_wit")).src = + resp; + }); + tryUrls(`${bgfolder}transition_pro`).then((resp) => { + (document.getElementById("client_court_prot")).src = + resp; + }); + tryUrls(`${bgfolder}prosecutorempty`).then((resp) => { + (document.getElementById("client_court_pro")).src = + resp; + }); if (this.charID === -1) { - viewport.set_side('jud',false,true); + this.viewport.set_side({ + position: "jud", + showSpeedLines: false, + showDesk: true, + }); } else { - viewport.set_side(this.chars[this.charID].side,false,true); + this.viewport.set_side({ + position: this.chars[this.charID].side, + showSpeedLines: false, + showDesk: true, + }); } } /** - * Handles a change in the health bars' states. - * @param {Array} args packet arguments - */ + * Handles a change in the health bars' states. + * @param {Array} args packet arguments + */ handleHP(args: string[]) { const percent_hp = Number(args[2]) * 10; let healthbox; - if (args[1] === '1') { + if (args[1] === "1") { // Def hp this.hp[0] = Number(args[2]); - healthbox = document.getElementById('client_defense_hp'); + healthbox = document.getElementById("client_defense_hp"); } else { // Pro hp this.hp[1] = Number(args[2]); - healthbox = document.getElementById('client_prosecutor_hp'); + healthbox = document.getElementById("client_prosecutor_hp"); } - (healthbox.getElementsByClassName('health-bar')[0]).style.width = `${percent_hp}%`; + (( + healthbox.getElementsByClassName("health-bar")[0] + )).style.width = `${percent_hp}%`; } /** - * Handles a testimony states. - * @param {Array} args packet arguments - */ + * Handles a testimony states. + * @param {Array} args packet arguments + */ handleRT(args: string[]) { const judgeid = Number(args[2]); switch (args[1]) { - case 'testimony1': + case "testimony1": this.testimonyID = 1; break; - case 'testimony2': + case "testimony2": // Cross Examination this.testimonyID = 2; break; - case 'judgeruling': + case "judgeruling": this.testimonyID = 3 + judgeid; break; default: - console.warn('Invalid testimony'); + console.warn("Invalid testimony"); } - viewport.initTestimonyUpdater(); + this.viewport.initTestimonyUpdater(); } /** - * Handles a timer update - * @param {Array} args packet arguments - */ + * Handles a timer update + * @param {Array} args packet arguments + */ handleTI(args: string[]) { const timerid = Number(args[1]); const type = Number(args[2]); const timer_value = args[3]; switch (type) { case 0: - // + // case 1: - document.getElementById(`client_timer${timerid}`).innerText = timer_value; + document.getElementById(`client_timer${timerid}`).innerText = + timer_value; case 2: - document.getElementById(`client_timer${timerid}`).style.display = ''; + document.getElementById(`client_timer${timerid}`).style.display = ""; case 3: - document.getElementById(`client_timer${timerid}`).style.display = 'none'; + document.getElementById(`client_timer${timerid}`).style.display = + "none"; } } /** - * Handles a modcall - * @param {Array} args packet arguments - */ + * Handles a modcall + * @param {Array} args packet arguments + */ handleZZ(args: string[]) { - const oocLog = document.getElementById('client_ooclog'); + const oocLog = document.getElementById("client_ooclog"); oocLog.innerHTML += `$Alert: ${prepChat(args[1])}\r\n`; if (oocLog.scrollTop > oocLog.scrollHeight - 60) { oocLog.scrollTop = oocLog.scrollHeight; } - viewport.sfxaudio.pause(); - const oldvolume = viewport.sfxaudio.volume; - viewport.sfxaudio.volume = 1; - viewport.sfxaudio.src = `${AO_HOST}sounds/general/sfx-gallery.opus`; - viewport.sfxaudio.play(); - viewport.sfxaudio.volume = oldvolume; + + this.viewport.sfxaudio.pause(); + const oldvolume = this.viewport.sfxaudio.volume; + this.viewport.sfxaudio.volume = 1; + this.viewport.sfxaudio.src = `${AO_HOST}sounds/general/sfx-gallery.opus`; + this.viewport.sfxaudio.play(); + this.viewport.sfxaudio.volume = oldvolume; } /** - * Handle the player - * @param {Array} args packet arguments - */ + * Handle the player + * @param {Array} args packet arguments + */ handleHI(_args: string[]) { this.sendSelf(`ID#1#webAO#${version}#%`); - this.sendSelf('FL#fastloading#yellowtext#cccc_ic_support#flipping#looping_sfx#effects#%'); + this.sendSelf( + "FL#fastloading#yellowtext#cccc_ic_support#flipping#looping_sfx#effects#%" + ); } /** - * Identifies the server and issues a playerID - * @param {Array} args packet arguments - */ + * Identifies the server and issues a playerID + * @param {Array} args packet arguments + */ handleID(args: string[]) { this.playerID = Number(args[1]); - const serverSoftware = args[2].split('&')[0]; + const serverSoftware = args[2].split("&")[0]; let serverVersion; - if (serverSoftware === 'serverD') { - serverVersion = args[2].split('&')[1]; - } else if (serverSoftware === 'webAO') { + if (serverSoftware === "serverD") { + serverVersion = args[2].split("&")[1]; + } else if (serverSoftware === "webAO") { oldLoading = false; - this.sendSelf('PN#0#1#%'); - } else { - serverVersion = args[3]; + this.sendSelf("PN#0#1#%"); + } else { + serverVersion = args[3]; } - if (serverSoftware === 'serverD' && serverVersion === '1377.152') { oldLoading = true; } // bugged version + if (serverSoftware === "serverD" && serverVersion === "1377.152") { + oldLoading = true; + } // bugged version } /** - * Indicates how many users are on this server - * @param {Array} args packet arguments - */ + * Indicates how many users are on this server + * @param {Array} args packet arguments + */ handlePN(_args: string[]) { - this.sendServer('askchaa#%'); + this.sendServer("askchaa#%"); } /** - * What? you want a character?? - * @param {Array} args packet arguments - */ + * What? you want a character?? + * @param {Array} args packet arguments + */ handleCC(args: string[]) { this.sendSelf(`PV#1#CID#${args[2]}#%`); } /** - * What? you want a character list from me?? - * @param {Array} args packet arguments - */ + * What? you want a character list from me?? + * @param {Array} args packet arguments + */ handleaskchaa(_args: string[]) { this.sendSelf(`SI#${vanilla_character_arr.length}#0#0#%`); } /** - * Handle the change of players in an area. - * @param {Array} args packet arguments - */ + * Handle the change of players in an area. + * @param {Array} args packet arguments + */ handleARUP(args: string[]) { args = args.slice(1); for (let i = 0; i < args.length - 2; i++) { - if (this.areas[i]) { // the server sends us ARUP before we even get the area list + if (this.areas[i]) { + // the server sends us ARUP before we even get the area list const thisarea = document.getElementById(`area${i}`); switch (Number(args[0])) { case 0: // playercount @@ -1576,143 +1752,158 @@ class Client extends EventEmitter { break; } - thisarea.className = `area-button area-${this.areas[i].status.toLowerCase()}`; + thisarea.className = `area-button area-${this.areas[ + i + ].status.toLowerCase()}`; thisarea.innerText = `${this.areas[i].name} (${this.areas[i].players}) [${this.areas[i].status}]`; - thisarea.title = `Players: ${this.areas[i].players}\n` - + `Status: ${this.areas[i].status}\n` - + `CM: ${this.areas[i].cm}\n` - + `Area lock: ${this.areas[i].locked}`; + thisarea.title = + `Players: ${this.areas[i].players}\n` + + `Status: ${this.areas[i].status}\n` + + `CM: ${this.areas[i].cm}\n` + + `Area lock: ${this.areas[i].locked}`; } } } /** - * With this the server tells us which features it supports - * @param {Array} args list of features - */ + * With this the server tells us which features it supports + * @param {Array} args list of features + */ handleFL(args: string[]) { - console.info('Server-supported features:'); + console.info("Server-supported features:"); console.info(args); extrafeatures = args; - if (args.includes('yellowtext')) { - const colorselect = document.getElementById('textcolor'); + if (args.includes("yellowtext")) { + const colorselect = ( + document.getElementById("textcolor") + ); - colorselect.options[colorselect.options.length] = new Option('Yellow', '5'); - colorselect.options[colorselect.options.length] = new Option('Grey', '6'); - colorselect.options[colorselect.options.length] = new Option('Pink', '7'); - colorselect.options[colorselect.options.length] = new Option('Cyan', '8'); + colorselect.options[colorselect.options.length] = new Option( + "Yellow", + "5" + ); + colorselect.options[colorselect.options.length] = new Option("Grey", "6"); + colorselect.options[colorselect.options.length] = new Option("Pink", "7"); + colorselect.options[colorselect.options.length] = new Option("Cyan", "8"); } - if (args.includes('cccc_ic_support')) { - document.getElementById('cccc').style.display = ''; - document.getElementById('pairing').style.display = ''; + if (args.includes("cccc_ic_support")) { + document.getElementById("cccc").style.display = ""; + document.getElementById("pairing").style.display = ""; } - if (args.includes('flipping')) { - document.getElementById('button_flip').style.display = ''; + if (args.includes("flipping")) { + document.getElementById("button_flip").style.display = ""; } - if (args.includes('looping_sfx')) { - document.getElementById('button_shake').style.display = ''; - document.getElementById('2.7').style.display = ''; + if (args.includes("looping_sfx")) { + document.getElementById("button_shake").style.display = ""; + document.getElementById("2.7").style.display = ""; } - if (args.includes('effects')) { - document.getElementById('2.8').style.display = ''; + if (args.includes("effects")) { + document.getElementById("2.8").style.display = ""; } - if (args.includes('y_offset')) { - document.getElementById('y_offset').style.display = ''; + if (args.includes("y_offset")) { + document.getElementById("y_offset").style.display = ""; } } /** - * Received when the server announces its server info, - * but we use it as a cue to begin retrieving characters. - * @param {Array} args packet arguments - */ + * Received when the server announces its server info, + * but we use it as a cue to begin retrieving characters. + * @param {Array} args packet arguments + */ handleSI(args: string[]) { this.char_list_length = Number(args[1]); this.char_list_length += 1; // some servers count starting from 0 some from 1... this.evidence_list_length = Number(args[2]); this.music_list_length = Number(args[3]); - - (document.getElementById('client_loadingbar')).max = this.char_list_length + this.evidence_list_length + this.music_list_length; + + (document.getElementById("client_loadingbar")).max = + this.char_list_length + + this.evidence_list_length + + this.music_list_length; // create the charselect grid, to be filled by the character loader - document.getElementById('client_chartable').innerHTML = ''; + document.getElementById("client_chartable").innerHTML = ""; for (let i = 0; i < this.char_list_length; i++) { - const demothing = document.createElement('img'); + const demothing = document.createElement("img"); - demothing.className = 'demothing'; + demothing.className = "demothing"; demothing.id = `demo_${i}`; - const demoonclick = document.createAttribute('onclick'); + const demoonclick = document.createAttribute("onclick"); demoonclick.value = `pickChar(${i})`; demothing.setAttributeNode(demoonclick); - document.getElementById('client_chartable').appendChild(demothing); + document.getElementById("client_chartable").appendChild(demothing); } // this is determined at the top of this file - if (!oldLoading && extrafeatures.includes('fastloading')) { - this.sendServer('RC#%'); + if (!oldLoading && extrafeatures.includes("fastloading")) { + this.sendServer("RC#%"); } else { - this.sendServer('askchar2#%'); + this.sendServer("askchar2#%"); } } /** - * Handles the list of all used and vacant characters. - * @param {Array} args list of all characters represented as a 0 for free or a -1 for taken - */ + * Handles the list of all used and vacant characters. + * @param {Array} args list of all characters represented as a 0 for free or a -1 for taken + */ handleCharsCheck(args: string[]) { for (let i = 0; i < this.char_list_length; i++) { const img = document.getElementById(`demo_${i}`); - if (args[i + 1] === '-1') { img.style.opacity = '0.25'; } else if (args[i + 1] === '0') { img.style.opacity = '1'; } + if (args[i + 1] === "-1") { + img.style.opacity = "0.25"; + } else if (args[i + 1] === "0") { + img.style.opacity = "1"; + } } } /** - * Handles the server's assignment of a character for the player to use. - * PV # playerID (unused) # CID # character ID - * @param {Array} args packet arguments - */ + * Handles the server's assignment of a character for the player to use. + * PV # playerID (unused) # CID # character ID + * @param {Array} args packet arguments + */ async handlePV(args: string[]) { this.charID = Number(args[3]); - document.getElementById('client_waiting').style.display = 'none'; - document.getElementById('client_charselect').style.display = 'none'; + document.getElementById("client_waiting").style.display = "none"; + document.getElementById("client_charselect").style.display = "none"; const me = this.chars[this.charID]; this.selectedEmote = -1; const { emotes } = this; - const emotesList = document.getElementById('client_emo'); - emotesList.style.display = ''; - emotesList.innerHTML = ''; // Clear emote box + const emotesList = document.getElementById("client_emo"); + emotesList.style.display = ""; + emotesList.innerHTML = ""; // Clear emote box const ini = me.inifile; me.side = ini.options.side; updateActionCommands(me.side); if (ini.emotions.number === 0) { - emotesList.innerHTML = `No emotes available`; } else { for (let i = 1; i <= ini.emotions.number; i++) { try { - const emoteinfo = ini.emotions[i].split('#'); + const emoteinfo = ini.emotions[i].split("#"); let esfx; let esfxd; try { - esfx = ini.soundn[i] || '0'; + esfx = ini.soundn[i] || "0"; esfxd = Number(ini.soundt[i]) || 0; } catch (e) { - console.warn('ini sound is completly missing'); - esfx = '0'; + console.warn("ini sound is completly missing"); + esfx = "0"; esfxd = 0; } // Make sure the asset server is case insensitive, or that everything on it is lowercase @@ -1725,13 +1916,14 @@ class Client extends EventEmitter { deskmod: Number(emoteinfo[4]) || 1, sfx: esfx.toLowerCase(), sfxdelay: esfxd, - frame_screenshake: '', - frame_realization: '', - frame_sfx: '', - button: `${AO_HOST}characters/${encodeURI(me.name.toLowerCase())}/emotions/button${i}_off.png`, + frame_screenshake: "", + frame_realization: "", + frame_sfx: "", + button: `${AO_HOST}characters/${encodeURI( + me.name.toLowerCase() + )}/emotions/button${i}_off.png`, }; - emotesList.innerHTML - += `${emotes[i].desc}document.getElementById('client_ininame'); + const iniedit_select = ( + document.getElementById("client_ininame") + ); // Load iniswaps if there are any try { - const cswapdata = await request(`${AO_HOST}characters/${encodeURI(me.name.toLowerCase())}/iniswaps.ini`); - const cswap = cswapdata.split('\n'); + const cswapdata = await request( + `${AO_HOST}characters/${encodeURI(me.name.toLowerCase())}/iniswaps.ini` + ); + const cswap = cswapdata.split("\n"); // most iniswaps don't list their original char if (cswap.length > 0) { - iniedit_select.innerHTML = ''; + iniedit_select.innerHTML = ""; iniedit_select.add(new Option(safeTags(me.name))); - cswap.forEach((inisw: string) => iniedit_select.add(new Option(safeTags(inisw)))); + cswap.forEach((inisw: string) => + iniedit_select.add(new Option(safeTags(inisw))) + ); } } catch (err) { console.info("character doesn't have iniswaps"); @@ -1767,837 +1973,43 @@ class Client extends EventEmitter { } /** - * new asset url!! - * @param {Array} args packet arguments - */ - handleASS(args: string[]) { + * new asset url!! + * @param {Array} args packet arguments + */ + handleASS(args: string[]) { AO_HOST = args[1]; } /** - * we are asking ourselves what characters there are - * @param {Array} args packet arguments - */ + * we are asking ourselves what characters there are + * @param {Array} args packet arguments + */ handleRC(_args: string[]) { - this.sendSelf(`SC#${vanilla_character_arr.join('#')}#%`); + this.sendSelf(`SC#${vanilla_character_arr.join("#")}#%`); } /** - * we are asking ourselves what characters there are - * @param {Array} args packet arguments - */ + * we are asking ourselves what characters there are + * @param {Array} args packet arguments + */ handleRM(_args: string[]) { - this.sendSelf(`SM#${vanilla_music_arr.join('#')}#%`); + this.sendSelf(`SM#${vanilla_music_arr.join("#")}#%`); } /** - * we are asking ourselves what characters there are - * @param {Array} args packet arguments - */ + * we are asking ourselves what characters there are + * @param {Array} args packet arguments + */ handleRD(_args: string[]) { - this.sendSelf('BN#gs4#%'); - this.sendSelf('DONE#%'); - const ooclog = document.getElementById('client_ooclog'); - ooclog.value = ''; + this.sendSelf("BN#gs4#%"); + this.sendSelf("DONE#%"); + const ooclog = document.getElementById("client_ooclog"); + ooclog.value = ""; ooclog.readOnly = false; - document.getElementById('client_oocinput').style.display = 'none'; - document.getElementById('client_replaycontrols').style.display = 'inline-block'; - } -} - -class Viewport { - textnow: string; - chatmsg: any; - shouts: string[]; - colors: string[]; - blipChannels: any; - currentBlipChannel: number; - sfxaudio: any; - sfxplayed: number; - shoutaudio: any; - testimonyAudio: any; - music: any; - updater: any; - testimonyUpdater: any; - bgname: string; - lastChar: string; - lastEvi: number; - testimonyTimer: number; - shoutTimer: number; - tickTimer: number; - _animating: boolean; - startFirstTickCheck: boolean; - startSecondTickCheck: boolean; - startThirdTickCheck: boolean; - theme: string; - - constructor() { - this.textnow = ''; - this.chatmsg = { - content: '', - objection: 0, - sound: '', - startpreanim: true, - startspeaking: false, - side: null, - color: 0, - snddelay: 0, - preanimdelay: 0, - speed: UPDATE_INTERVAL - }; - - this.shouts = [ - undefined, - 'holdit', - 'objection', - 'takethat', - 'custom', - ]; - - this.colors = [ - 'white', - 'green', - 'red', - 'orange', - 'blue', - 'yellow', - 'pink', - 'cyan', - 'grey', - ]; - - // Allocate multiple blip audio channels to make blips less jittery - const blipSelectors = document.getElementsByClassName('blipSound') - this.blipChannels = [...blipSelectors]; - this.blipChannels.forEach((channel: HTMLAudioElement) => channel.volume = 0.5); - this.blipChannels.forEach((channel: HTMLAudioElement) => channel.onerror = opusCheck(channel)); - this.currentBlipChannel = 0; - - this.sfxaudio = document.getElementById('client_sfxaudio'); - this.sfxaudio.src = `${AO_HOST}sounds/general/sfx-realization.opus`; - - this.sfxplayed = 0; - - this.shoutaudio = document.getElementById('client_shoutaudio'); - this.shoutaudio.src = `${AO_HOST}misc/default/objection.opus`; - - this.testimonyAudio = document.getElementById('client_testimonyaudio'); - this.testimonyAudio.src = `${AO_HOST}sounds/general/sfx-guilty.opus`; - - const audioChannels = document.getElementsByClassName('audioChannel') - this.music = [...audioChannels]; - this.music.forEach((channel: HTMLAudioElement) => channel.volume = 0.5); - this.music.forEach((channel: HTMLAudioElement) => channel.onerror = opusCheck(channel)); - - this.updater = null; - this.testimonyUpdater = null; - - this.bgname = 'gs4'; - - this.lastChar = ''; - this.lastEvi = 0; - - this.testimonyTimer = 0; - this.shoutTimer = 0; - this.tickTimer = 0; - - this._animating = false; - } - - /** - * Sets the volume of the music. - * @param {number} volume - */ - set musicVolume(volume: number) { - this.music.forEach((channel: HTMLAudioElement) => channel.volume = volume); - } - - /** - * Returns the path which the background is located in. - */ - get bgFolder() { - return `${AO_HOST}background/${encodeURI(this.bgname.toLowerCase())}/`; - } - - /** - * Play any SFX - * - * @param {string} sfxname - */ - async playSFX(sfxname: string, looping: boolean) { - this.sfxaudio.pause(); - this.sfxaudio.loop = looping; - this.sfxaudio.src = sfxname; - this.sfxaudio.play(); - } - - /** - * Changes the viewport background based on a given position. - * - * Valid positions: `def, pro, hld, hlp, wit, jud, jur, sea` - * @param {string} position the position to change into - */ - async set_side(position: string, showspeedlines: boolean, showdesk: boolean) { - const bgfolder = viewport.bgFolder; - - const view = document.getElementById('client_fullview'); - - let bench: HTMLImageElement; - if ('def,pro,wit'.includes(position)) { - bench = document.getElementById(`client_${position}_bench`); - } else { - bench = document.getElementById('client_bench_classic'); - } - - let court: HTMLImageElement; - if ('def,pro,wit'.includes(position)) { - court = document.getElementById(`client_court_${position}`); - } else { - court = document.getElementById('client_court_classic'); - } - - interface Desk { - ao2?: string - ao1?: string - } - interface Position { - bg?: string - desk?: Desk - speedLines: string - } - - interface Positions { - [key: string]: Position - } - - const positions: Positions = { - def: { - bg: 'defenseempty', - desk: { ao2: 'defensedesk.png', ao1: 'bancodefensa.png' } as Desk, - speedLines: 'defense_speedlines.gif', - }, - pro: { - bg: 'prosecutorempty', - desk: { ao2: 'prosecutiondesk.png', ao1: 'bancoacusacion.png' } as Desk, - speedLines: 'prosecution_speedlines.gif', - }, - hld: { - bg: 'helperstand', - desk: null as Desk, - speedLines: 'defense_speedlines.gif', - }, - hlp: { - bg: 'prohelperstand', - desk: null as Desk, - speedLines: 'prosecution_speedlines.gif', - }, - wit: { - bg: 'witnessempty', - desk: { ao2: 'stand.png', ao1: 'estrado.png' } as Desk, - speedLines: 'prosecution_speedlines.gif', - }, - jud: { - bg: 'judgestand', - desk: { ao2: 'judgedesk.png', ao1: 'judgedesk.gif' } as Desk, - speedLines: 'prosecution_speedlines.gif', - }, - jur: { - bg: 'jurystand', - desk: { ao2: 'jurydesk.png', ao1: 'estrado.png' } as Desk, - speedLines: 'defense_speedlines.gif', - }, - sea: { - bg: 'seancestand', - desk: { ao2: 'seancedesk.png', ao1: 'estrado.png' } as Desk, - speedLines: 'prosecution_speedlines.gif', - }, - }; - - let bg; - let desk; - let speedLines; - - if ('def,pro,hld,hlp,wit,jud,jur,sea'.includes(position)) { - bg = positions[position].bg; - desk = positions[position].desk; - speedLines = positions[position].speedLines; - } else { - bg = `${position}`; - desk = { ao2: `${position}_overlay.png`, ao1: '_overlay.png' }; - speedLines = 'defense_speedlines.gif'; - } - - if (showspeedlines === true) { - court.src = `${AO_HOST}themes/default/${encodeURI(speedLines)}`; - } else { - court.src = await tryUrls(bgfolder + bg) - } - - if (showdesk === true && desk) { - const deskFilename = await fileExists(bgfolder + desk.ao2) ? desk.ao2 : desk.ao1; - bench.src = bgfolder + deskFilename; - bench.style.opacity = '1'; - } else { - bench.style.opacity = '0'; - } - - if ('def,pro,wit'.includes(position)) { - view.style.display = ''; - document.getElementById('client_classicview').style.display = 'none'; - switch (position) { - case 'def': - view.style.left = '0'; - break; - case 'wit': - view.style.left = '-200%'; - break; - case 'pro': - view.style.left = '-400%'; - break; - } - } else { - view.style.display = 'none'; - document.getElementById('client_classicview').style.display = ''; - } - } - - /** - * Intialize testimony updater - */ - initTestimonyUpdater() { - - const testimonyFilenames: Testimony = { - 1: 'witnesstestimony', - 2: 'crossexamination', - 3: 'notguilty', - 4: 'guilty', - }; - - const testimony = testimonyFilenames[client.testimonyID]; - if (!testimony) { - console.warn(`Invalid testimony ID ${client.testimonyID}`); - return; - } - - this.testimonyAudio.src = client.resources[testimony].sfx; - this.testimonyAudio.play(); - - const testimonyOverlay = document.getElementById('client_testimony'); - testimonyOverlay.src = client.resources[testimony].src; - testimonyOverlay.style.opacity = '1'; - - this.testimonyTimer = 0; - this.testimonyUpdater = setTimeout(() => this.updateTestimony(), UPDATE_INTERVAL); - } - - /** - * Updates the testimony overaly - */ - updateTestimony() { - const testimonyFilenames: Testimony = { - 1: 'witnesstestimony', - 2: 'crossexamination', - 3: 'notguilty', - 4: 'guilty', - }; - - // Update timer - this.testimonyTimer += UPDATE_INTERVAL; - - const testimony = testimonyFilenames[client.testimonyID]; - const resource = client.resources[testimony]; - if (!resource) { - this.disposeTestimony(); - return; - } - - if (this.testimonyTimer >= resource.duration) { - this.disposeTestimony(); - } else { - this.testimonyUpdater = setTimeout(() => this.updateTestimony(), UPDATE_INTERVAL); - } - } - - /** - * Dispose the testimony overlay - */ - disposeTestimony() { - client.testimonyID = 0; - this.testimonyTimer = 0; - document.getElementById('client_testimony').style.opacity = '0'; - clearTimeout(this.testimonyUpdater); - } - - /** - * Sets a new emote. - * This sets up everything before the tick() loops starts - * a lot of things can probably be moved here, like starting the shout animation if there is one - * TODO: the preanim logic, on the other hand, should probably be moved to tick() - * @param {object} chatmsg the new chat message - */ - async handle_ic_speaking(chatmsg: any) { - - this.chatmsg = chatmsg; - this.textnow = ''; - this.sfxplayed = 0; - this.tickTimer = 0; - this._animating = true; - this.startFirstTickCheck = true - this.startSecondTickCheck = false - this.startThirdTickCheck = false - let charLayers = document.getElementById('client_char'); - let pairLayers = document.getElementById('client_pair_char'); - - // stop updater - clearTimeout(this.updater); - - // stop last sfx from looping any longer - this.sfxaudio.loop = false; - - const fg = document.getElementById('client_fg'); - const gamewindow = document.getElementById('client_gamewindow'); - const waitingBox = document.getElementById('client_chatwaiting'); - - // Reset CSS animation - gamewindow.style.animation = ''; - waitingBox.style.opacity = '0'; - - const eviBox = document.getElementById('client_evi'); - - if (this.lastEvi !== this.chatmsg.evidence) { - eviBox.style.opacity = '0'; - eviBox.style.height = '0%'; - } - this.lastEvi = this.chatmsg.evidence; - - const validSides: string[] = ['def', 'pro', 'wit']; // these are for the full view pan, the other positions use 'client_char' - if (validSides.includes(this.chatmsg.side)) { - charLayers = document.getElementById(`client_${this.chatmsg.side}_char`); - pairLayers = document.getElementById(`client_${this.chatmsg.side}_pair_char`); - } - - const chatContainerBox = document.getElementById('client_chatcontainer'); - const nameBoxInner = document.getElementById('client_inner_name'); - const chatBoxInner = document.getElementById('client_inner_chat'); - - const displayname = ((document.getElementById('showname')).checked && this.chatmsg.showname !== '') ? this.chatmsg.showname : this.chatmsg.nameplate; - - // Clear out the last message - chatBoxInner.innerText = this.textnow; - nameBoxInner.innerText = displayname; - - if (this.lastChar !== this.chatmsg.name) { - charLayers.style.opacity = '0'; - pairLayers.style.opacity = '0'; - } - this.lastChar = this.chatmsg.name; - - appendICLog(this.chatmsg.content, this.chatmsg.showname, this.chatmsg.nameplate); - - checkCallword(this.chatmsg.content); - - setEmote(AO_HOST, this, this.chatmsg.name.toLowerCase(), this.chatmsg.sprite, '(a)', false, this.chatmsg.side); - - if (this.chatmsg.other_name) { - setEmote(AO_HOST, this, this.chatmsg.other_name.toLowerCase(), this.chatmsg.other_emote, '(a)', false, this.chatmsg.side); - } - - // gets which shout shall played - const shoutSprite = document.getElementById('client_shout'); - const shout = this.shouts[this.chatmsg.objection]; - if (shout) { - // Hide message box - chatContainerBox.style.opacity = '0'; - if (this.chatmsg.objection === 4) { - shoutSprite.src = `${AO_HOST}characters/${encodeURI(this.chatmsg.name.toLowerCase())}/custom.gif`; - } else { - shoutSprite.src = client.resources[shout].src; - shoutSprite.style.animation = 'bubble 700ms steps(10, jump-both)'; - } - shoutSprite.style.opacity = '1'; - - this.shoutaudio.src = `${AO_HOST}characters/${encodeURI(this.chatmsg.name.toLowerCase())}/${shout}.opus`; - this.shoutaudio.play(); - this.shoutTimer = client.resources[shout].duration; - } else { - this.shoutTimer = 0; - } - - this.chatmsg.startpreanim = true; - let gifLength = 0; - - if (this.chatmsg.type === 1 && this.chatmsg.preanim !== '-') { //we have a preanim - chatContainerBox.style.opacity = '0'; - gifLength = await getAnimLength(`${AO_HOST}characters/${encodeURI(this.chatmsg.name.toLowerCase())}/${encodeURI(this.chatmsg.preanim)}`); - console.debug("preanim is "+gifLength+" long"); - this.chatmsg.startspeaking = false; - } else { - this.chatmsg.startspeaking = true; - if(this.chatmsg.content !== "") - chatContainerBox.style.opacity = '1'; - } - this.chatmsg.preanimdelay = gifLength; - - let skipoffset: boolean = false; - if (chatmsg.type === 5) { - this.set_side(chatmsg.side,true,false); - } else { - switch(Number(chatmsg.deskmod)) { - case 0: //desk is hidden - this.set_side(chatmsg.side,false,false); - break; - case 1: //desk is shown - this.set_side(chatmsg.side,false,true); - break; - case 2: //desk is hidden during preanim, but shown during idle/talk - this.set_side(chatmsg.side,false,false); - break; - case 3: //opposite of 2 - this.set_side(chatmsg.side,false,false); - break; - case 4: //desk is hidden, character offset is ignored, pair character is hidden during preanim, normal behavior during idle/talk - this.set_side(chatmsg.side,false,false); - skipoffset = true; - break; - case 5: //opposite of 4 - this.set_side(chatmsg.side,false,true); - break; - default: - this.set_side(chatmsg.side,false,true); - break; - } - } - - setChatbox(chatmsg.chatbox); - resizeChatbox(); - - if (!skipoffset) { - // Flip the character - charLayers.style.transform = this.chatmsg.flip === 1 ? 'scaleX(-1)' : 'scaleX(1)'; - pairLayers.style.transform = this.chatmsg.other_flip === 1 ? 'scaleX(-1)' : 'scaleX(1)'; - - // Shift by the horizontal offset - switch (this.chatmsg.side) { - case 'wit': - pairLayers.style.left = `${200 + Number(this.chatmsg.other_offset[0])}%`; - charLayers.style.left = `${200 + Number(this.chatmsg.self_offset[0])}%`; - break; - case 'pro': - pairLayers.style.left = `${400 + Number(this.chatmsg.other_offset[0])}%`; - charLayers.style.left = `${400 + Number(this.chatmsg.self_offset[0])}%`; - break; - default: - pairLayers.style.left = `${Number(this.chatmsg.other_offset[0])}%`; - charLayers.style.left = `${Number(this.chatmsg.self_offset[0])}%`; - break; - } - - // New vertical offsets - pairLayers.style.top = `${Number(this.chatmsg.other_offset[1])}%`; - charLayers.style.top = `${Number(this.chatmsg.self_offset[1])}%`; - - } - - this.blipChannels.forEach((channel: HTMLAudioElement) => channel.src = `${AO_HOST}sounds/general/sfx-blip${encodeURI(this.chatmsg.blips.toLowerCase())}.opus`); - - // process markup - if (this.chatmsg.content.startsWith('~~')) { - chatBoxInner.style.textAlign = 'center'; - this.chatmsg.content = this.chatmsg.content.substring(2, this.chatmsg.content.length); - } else { - chatBoxInner.style.textAlign = 'inherit'; - } - - // apply effects - fg.style.animation = ''; - const badEffects = ['', '-', 'none']; - if (this.chatmsg.effects[0] && !badEffects.includes(this.chatmsg.effects[0].toLowerCase())) { - const baseEffectUrl = `${AO_HOST}themes/default/effects/`; - fg.src = `${baseEffectUrl}${encodeURI(this.chatmsg.effects[0].toLowerCase())}.webp`; - } else { - - fg.src = transparentPng; - } - - charLayers.style.opacity = '1'; - - const soundChecks = ['0', '1', '', undefined]; - if (soundChecks.some((check) => this.chatmsg.sound === check)) { - this.chatmsg.sound = this.chatmsg.effects[2]; - } - this.chatmsg.parsed = await attorneyMarkdown.applyMarkdown(chatmsg.content, this.colors[this.chatmsg.color]) - this.chat_tick(); - } - - async handleTextTick(charLayers: HTMLImageElement) { - const chatBox = document.getElementById('client_chat'); - const waitingBox = document.getElementById('client_chatwaiting'); - const chatBoxInner = document.getElementById('client_inner_chat'); - const charName = this.chatmsg.name.toLowerCase(); - const charEmote = this.chatmsg.sprite.toLowerCase(); - - - if (this.chatmsg.content.charAt(this.textnow.length) !== ' ') { - this.blipChannels[this.currentBlipChannel].play(); - this.currentBlipChannel++; - this.currentBlipChannel %= this.blipChannels.length; - } - this.textnow = this.chatmsg.content.substring(0, this.textnow.length + 1); - const characterElement = this.chatmsg.parsed[this.textnow.length - 1] - if (characterElement) { - const COMMAND_IDENTIFIER = '\\' - - const nextCharacterElement = this.chatmsg.parsed[this.textnow.length] - const flash = async () => { - const effectlayer = document.getElementById('client_fg'); - this.playSFX(`${AO_HOST}sounds/general/sfx-realization.opus`, false); - effectlayer.style.animation = 'flash 0.4s 1'; - await delay(400) - effectlayer.style.removeProperty('animation') - } - - const shake = async () => { - const gamewindow = document.getElementById('client_gamewindow'); - this.playSFX(`${AO_HOST}sounds/general/sfx-stab.opus`, false); - gamewindow.style.animation = 'shake 0.2s 1'; - await delay(200) - gamewindow.style.removeProperty('animation') - } - - const commands = new Map(Object.entries({ - 's': shake, - 'f': flash - })) - const textSpeeds = new Set(['{', '}']) - - // Changing Text Speed - if (textSpeeds.has(characterElement.innerHTML)) { - // Grab them all in a row - const MAX_SLOW_CHATSPEED = 120 - for(let i = this.textnow.length; i < this.chatmsg.content.length; i++) { - const currentCharacter = this.chatmsg.parsed[i - 1].innerHTML - if (currentCharacter === '}') { - if (this.chatmsg.speed > 0) { - this.chatmsg.speed -= 20 - } - } else if(currentCharacter === '{') { - if(this.chatmsg.speed < MAX_SLOW_CHATSPEED) { - this.chatmsg.speed += 20 - } - } else { - // No longer at a speed character - this.textnow = this.chatmsg.content.substring(0, i); - break - } - } - } - - if (characterElement.innerHTML === COMMAND_IDENTIFIER && commands.has(nextCharacterElement?.innerHTML)) { - this.textnow = this.chatmsg.content.substring(0, this.textnow.length + 1); - await commands.get(nextCharacterElement.innerHTML)() - } else { - chatBoxInner.appendChild(this.chatmsg.parsed[this.textnow.length - 1]); - } - } - - // scroll to bottom - chatBox.scrollTop = chatBox.scrollHeight; - - if (this.textnow === this.chatmsg.content) { - this._animating = false; - setEmote(AO_HOST, this, charName, charEmote, '(a)', false, this.chatmsg.side); - charLayers.style.opacity = '1'; - waitingBox.style.opacity = '1'; - clearTimeout(this.updater); - } - } - /** - * Updates the chatbox based on the given text. - * - * OK, here's the documentation on how this works: - * - * 1 _animating - * If we're not done with this characters animation, i.e. his text isn't fully there, set a timeout for the next tick/step to happen - * - * 2 startpreanim - * If the shout timer is over it starts with the preanim - * The first thing it checks for is the shake effect (TODO on client this is handled by the @ symbol and not a flag ) - * Then is the flash/realization effect - * After that, the shout image set to be transparent - * and the main characters preanim gif is loaded - * If pairing is supported the paired character will just stand around with his idle sprite - * - * 3 preanimdelay over - * this animates the evidence popup and finally shows the character name and message box - * it sets the text color and the character speaking sprite - * - * 4 textnow != content - * this adds a character to the textbox and stops the animations if the entire message is present in the textbox - * - * 5 sfx - * independent of the stuff above, this will play any sound effects specified by the emote the character sent. - * happens after the shout delay + an sfx delay that comes with the message packet - * - * XXX: This relies on a global variable `this.chatmsg`! - */ - async chat_tick() { - // note: this is called fairly often - // do not perform heavy operations here - - await delay(this.chatmsg.speed) - - if (this.textnow === this.chatmsg.content) { - return - } - - const gamewindow = document.getElementById('client_gamewindow'); - const waitingBox = document.getElementById('client_chatwaiting'); - const eviBox = document.getElementById('client_evi'); - const shoutSprite = document.getElementById('client_shout'); - const effectlayer = document.getElementById('client_fg'); - const chatBoxInner = document.getElementById('client_inner_chat'); - let charLayers = document.getElementById('client_char'); - let pairLayers = document.getElementById('client_pair_char'); - - const validSides: string[] = ['def', 'pro', 'wit']; // these are for the full view pan, the other positions use 'client_char' - if (validSides.includes(this.chatmsg.side)) { - charLayers = document.getElementById(`client_${this.chatmsg.side}_char`); - pairLayers = document.getElementById(`client_${this.chatmsg.side}_pair_char`); - } - - const charName = this.chatmsg.name.toLowerCase(); - const charEmote = this.chatmsg.sprite.toLowerCase(); - - const pairName = this.chatmsg.other_name.toLowerCase(); - const pairEmote = this.chatmsg.other_emote.toLowerCase(); - - // TODO: preanims sometimes play when they're not supposed to - const isShoutOver = this.tickTimer >= this.shoutTimer - const isShoutAndPreanimOver = this.tickTimer >= this.shoutTimer + this.chatmsg.preanimdelay - if (isShoutOver && this.startFirstTickCheck) { - // Effect stuff - if (this.chatmsg.screenshake === 1) { - // Shake screen - this.playSFX(`${AO_HOST}sounds/general/sfx-stab.opus`, false); - gamewindow.style.animation = 'shake 0.2s 1'; - } - if (this.chatmsg.flash === 1) { - // Flash screen - this.playSFX(`${AO_HOST}sounds/general/sfx-realization.opus`, false); - effectlayer.style.animation = 'flash 0.4s 1'; - } - - // Pre-animation stuff - if (this.chatmsg.preanimdelay > 0) { - shoutSprite.style.opacity = '0'; - shoutSprite.style.animation = ''; - const preanim = this.chatmsg.preanim.toLowerCase(); - setEmote(AO_HOST, this, charName, preanim, '', false, this.chatmsg.side); - } - - if (this.chatmsg.other_name) { - pairLayers.style.opacity = '1'; - } else { - pairLayers.style.opacity = '0'; - } - // Done with first check, move to second - this.startFirstTickCheck = false - this.startSecondTickCheck = true - - this.chatmsg.startpreanim = false; - this.chatmsg.startspeaking = true; - } - - const hasNonInterruptingPreAnim = this.chatmsg.noninterrupting_preanim === 1 - if (this.textnow !== this.chatmsg.content && hasNonInterruptingPreAnim) { - const chatContainerBox = document.getElementById('client_chatcontainer'); - chatContainerBox.style.opacity = '1'; - await this.handleTextTick(charLayers) - - }else if (isShoutAndPreanimOver && this.startSecondTickCheck) { - if (this.chatmsg.startspeaking) { - this.chatmsg.startspeaking = false; - - // Evidence Bullshit - if (this.chatmsg.evidence > 0) { - // Prepare evidence - eviBox.src = safeTags(client.evidences[this.chatmsg.evidence - 1].icon); - - eviBox.style.width = 'auto'; - eviBox.style.height = '36.5%'; - eviBox.style.opacity = '1'; - - this.testimonyAudio.src = `${AO_HOST}sounds/general/sfx-evidenceshoop.opus`; - this.testimonyAudio.play(); - - if (this.chatmsg.side === 'def') { - // Only def show evidence on right - eviBox.style.right = '1em'; - eviBox.style.left = 'initial'; - } else { - eviBox.style.right = 'initial'; - eviBox.style.left = '1em'; - } - } - chatBoxInner.className = `text_${this.colors[this.chatmsg.color]}`; - - - if (this.chatmsg.preanimdelay === 0) { - shoutSprite.style.opacity = '0'; - shoutSprite.style.animation = ''; - } - - switch(Number(this.chatmsg.deskmod)) { - case 2: - this.set_side(this.chatmsg.side,false,true); - break; - case 3: - this.set_side(this.chatmsg.side,false,false); - break; - case 4: - this.set_side(this.chatmsg.side,false,true); - break; - case 5: - this.set_side(this.chatmsg.side,false,false); - break; - } - - if (this.chatmsg.other_name) { - setEmote(AO_HOST, this, pairName, pairEmote, '(a)', true, this.chatmsg.side); - pairLayers.style.opacity = '1'; - } else { - pairLayers.style.opacity = '0'; - } - - setEmote(AO_HOST, this, charName, charEmote, '(b)', false, this.chatmsg.side); - charLayers.style.opacity = '1'; - - if (this.textnow === this.chatmsg.content) { - setEmote(AO_HOST, this, charName, charEmote, '(a)', false, this.chatmsg.side); - charLayers.style.opacity = '1'; - waitingBox.style.opacity = '1'; - this._animating = false; - clearTimeout(this.updater); - return - } - } else if (this.textnow !== this.chatmsg.content) { - const chatContainerBox = document.getElementById('client_chatcontainer'); - chatContainerBox.style.opacity = '1'; - await this.handleTextTick(charLayers) - } - } - - if (!this.sfxplayed && this.chatmsg.snddelay + this.shoutTimer >= this.tickTimer) { - this.sfxplayed = 1; - if (this.chatmsg.sound !== '0' && this.chatmsg.sound !== '1' && this.chatmsg.sound !== '' && this.chatmsg.sound !== undefined && (this.chatmsg.type == 1 || this.chatmsg.type == 2 || this.chatmsg.type == 6)) { - this.playSFX(`${AO_HOST}sounds/general/${encodeURI(this.chatmsg.sound.toLowerCase())}.opus`, this.chatmsg.looping_sfx); - } - } - if (this._animating) { - this.chat_tick() - } - this.tickTimer += UPDATE_INTERVAL; + document.getElementById("client_oocinput").style.display = "none"; + document.getElementById("client_replaycontrols").style.display = + "inline-block"; } } @@ -2607,8 +2019,11 @@ class Viewport { */ export function onOOCEnter(event: KeyboardEvent) { if (event.keyCode === 13) { - client.sendOOC((document.getElementById('client_oocinputbox')).value); - (document.getElementById('client_oocinputbox')).value = ''; + client.sendOOC( + (document.getElementById("client_oocinputbox")).value + ); + (document.getElementById("client_oocinputbox")).value = + ""; } } window.onOOCEnter = onOOCEnter; @@ -2631,34 +2046,63 @@ export function onEnter(event: KeyboardEvent) { const mychar = client.character; const myemo = client.emote; const evi = client.evidence; - const flip = Boolean((document.getElementById('button_flip').classList.contains('dark'))); - const flash = Boolean((document.getElementById('button_flash').classList.contains('dark'))); - const screenshake = Boolean((document.getElementById('button_shake').classList.contains('dark'))); - const noninterrupting_preanim = Boolean(((document.getElementById('check_nonint')).checked)); - const looping_sfx = Boolean(((document.getElementById('check_loopsfx')).checked)); - const color = Number((document.getElementById('textcolor')).value); - const showname = escapeChat((document.getElementById('ic_chat_name')).value); - const text = (document.getElementById('client_inputbox')).value; - const pairchar = (document.getElementById('pair_select')).value; - const pairoffset = Number((document.getElementById('pair_offset')).value); - const pairyoffset = Number((document.getElementById('pair_y_offset')).value); - const myrole = (document.getElementById('role_select')).value ? (document.getElementById('role_select')).value : mychar.side; - const additive = Boolean(((document.getElementById('check_additive')).checked)); - const effect = (document.getElementById('effect_select')).value; - - let sfxname = '0'; + const flip = Boolean( + document.getElementById("button_flip").classList.contains("dark") + ); + const flash = Boolean( + document.getElementById("button_flash").classList.contains("dark") + ); + const screenshake = Boolean( + document.getElementById("button_shake").classList.contains("dark") + ); + const noninterrupting_preanim = Boolean( + (document.getElementById("check_nonint")).checked + ); + const looping_sfx = Boolean( + (document.getElementById("check_loopsfx")).checked + ); + const color = Number( + (document.getElementById("textcolor")).value + ); + const showname = escapeChat( + (document.getElementById("ic_chat_name")).value + ); + const text = (document.getElementById("client_inputbox")) + .value; + const pairchar = (document.getElementById("pair_select")) + .value; + const pairoffset = Number( + (document.getElementById("pair_offset")).value + ); + const pairyoffset = Number( + (document.getElementById("pair_y_offset")).value + ); + const myrole = (document.getElementById("role_select")) + .value + ? (document.getElementById("role_select")).value + : mychar.side; + const additive = Boolean( + (document.getElementById("check_additive")).checked + ); + const effect = (document.getElementById("effect_select")) + .value; + + let sfxname = "0"; let sfxdelay = 0; let emote_mod = myemo.zoom; - if ((document.getElementById('sendsfx')).checked) { + if ((document.getElementById("sendsfx")).checked) { sfxname = myemo.sfx; sfxdelay = myemo.sfxdelay; } // not to overwrite a 5 from the ini or anything else - if ((document.getElementById('sendpreanim')).checked) { - if (emote_mod === 0) { emote_mod = 1; } - } else if (emote_mod === 1) { emote_mod = 0; } - + if ((document.getElementById("sendpreanim")).checked) { + if (emote_mod === 0) { + emote_mod = 1; + } + } else if (emote_mod === 1) { + emote_mod = 0; + } client.sendIC( myemo.deskmod, @@ -2682,11 +2126,11 @@ export function onEnter(event: KeyboardEvent) { noninterrupting_preanim, looping_sfx, screenshake, - '-', - '-', - '-', + "-", + "-", + "-", additive, - effect, + effect ); } return false; @@ -2699,22 +2143,23 @@ window.onEnter = onEnter; * was successfully sent/presented. */ function resetICParams() { - (document.getElementById('client_inputbox')).value = ''; - document.getElementById('button_flash').className = 'client_button'; - document.getElementById('button_shake').className = 'client_button'; + (document.getElementById("client_inputbox")).value = ""; + document.getElementById("button_flash").className = "client_button"; + document.getElementById("button_shake").className = "client_button"; - (document.getElementById('sendpreanim')).checked = false; - (document.getElementById('sendsfx')).checked = false; + (document.getElementById("sendpreanim")).checked = false; + (document.getElementById("sendsfx")).checked = false; if (selectedShout) { - document.getElementById(`button_${selectedShout}`).className = 'client_button'; + document.getElementById(`button_${selectedShout}`).className = + "client_button"; selectedShout = 0; } } export function resetOffset(_event: Event) { - (document.getElementById('pair_offset')).value = '0'; - (document.getElementById('pair_y_offset')).value = '0'; + (document.getElementById("pair_offset")).value = "0"; + (document.getElementById("pair_y_offset")).value = "0"; } window.resetOffset = resetOffset; @@ -2723,14 +2168,18 @@ window.resetOffset = resetOffset; * @param {MouseEvent} event */ export function musiclist_filter(_event: Event) { - const musiclist_element = document.getElementById('client_musiclist'); - const searchname = (document.getElementById('client_musicsearch')).value; + const musiclist_element = ( + document.getElementById("client_musiclist") + ); + const searchname = (( + document.getElementById("client_musicsearch") + )).value; - musiclist_element.innerHTML = ''; + musiclist_element.innerHTML = ""; for (const trackname of client.musics) { if (trackname.toLowerCase().indexOf(searchname.toLowerCase()) !== -1) { - const newentry = document.createElement('OPTION'); + const newentry = document.createElement("OPTION"); newentry.text = trackname; musiclist_element.options.add(newentry); } @@ -2743,12 +2192,16 @@ window.musiclist_filter = musiclist_filter; * @param {MouseEvent} event */ export function musiclist_click(_event: Event) { - const playtrack = (document.getElementById('client_musiclist')).value; + const playtrack = (( + document.getElementById("client_musiclist") + )).value; client.sendMusicChange(playtrack); // This is here so you can't actually select multiple tracks, // even though the select tag has the multiple option to render differently - const musiclist_elements = (document.getElementById('client_musiclist')).selectedOptions; + const musiclist_elements = (( + document.getElementById("client_musiclist") + )).selectedOptions; for (let i = 0; i < musiclist_elements.length; i++) { musiclist_elements[i].selected = false; } @@ -2760,12 +2213,14 @@ window.musiclist_click = musiclist_click; * @param {MouseEvent} event */ export function mutelist_click(_event: Event) { - const mutelist = document.getElementById('mute_select'); + const mutelist = document.getElementById("mute_select"); const selected_character = mutelist.options[mutelist.selectedIndex]; if (client.chars[selected_character.value].muted === false) { client.chars[selected_character.value].muted = true; - selected_character.text = `${client.chars[selected_character.value].name} (muted)`; + selected_character.text = `${ + client.chars[selected_character.value].name + } (muted)`; console.info(`muted ${client.chars[selected_character.value].name}`); } else { client.chars[selected_character.value].muted = false; @@ -2779,12 +2234,22 @@ window.mutelist_click = mutelist_click; * @param {MouseEvent} event */ export function showname_click(_event: Event) { - setCookie('showname', String((document.getElementById('showname')).checked)); - setCookie('ic_chat_name', (document.getElementById('ic_chat_name')).value); + setCookie( + "showname", + String((document.getElementById("showname")).checked) + ); + setCookie( + "ic_chat_name", + (document.getElementById("ic_chat_name")).value + ); - const css_s = document.getElementById('nameplate_setting'); + const css_s = document.getElementById("nameplate_setting"); - if ((document.getElementById('showname')).checked) { css_s.href = 'styles/shownames.css'; } else { css_s.href = 'styles/nameplates.css'; } + if ((document.getElementById("showname")).checked) { + css_s.href = "styles/shownames.css"; + } else { + css_s.href = "styles/nameplates.css"; + } } window.showname_click = showname_click; @@ -2796,48 +2261,21 @@ export function area_click(el: HTMLElement) { const area = client.areas[el.id.substr(4)].name; client.sendMusicChange(area); - const areaHr = document.createElement('div'); - areaHr.className = 'hrtext'; + const areaHr = document.createElement("div"); + areaHr.className = "hrtext"; areaHr.textContent = `switched to ${el.textContent}`; - document.getElementById('client_log').appendChild(areaHr); + document.getElementById("client_log").appendChild(areaHr); } window.area_click = area_click; -/** - * Triggered by the music volume slider. - */ -export function changeMusicVolume() { - viewport.musicVolume = Number((document.getElementById('client_mvolume')).value); - setCookie('musicVolume', String(viewport.musicVolume)); -} -window.changeMusicVolume = changeMusicVolume; - -/** - * Triggered by the blip volume slider. - */ -export function changeBlipVolume() { - const blipVolume = (document.getElementById('client_bvolume')).value; - viewport.blipChannels.forEach((channel: HTMLAudioElement) => channel.volume = Number(blipVolume)); - setCookie('blipVolume', blipVolume); -} -window.changeBlipVolume = changeBlipVolume; - -/** - * Triggered by the theme selector. - */ -export function reloadTheme() { - viewport.theme = (document.getElementById('client_themeselect')).value; - setCookie('theme', viewport.theme); - (document.getElementById('client_theme')).href = `styles/${viewport.theme}.css`; -} -window.reloadTheme = reloadTheme; - /** * Triggered by a changed callword list */ export function changeCallwords() { - client.callwords = (document.getElementById('client_callwords')).value.split('\n'); - setCookie('callwords', client.callwords.join('\n')); + client.callwords = (( + document.getElementById("client_callwords") + )).value.split("\n"); + setCookie("callwords", client.callwords.join("\n")); } window.changeCallwords = changeCallwords; @@ -2845,7 +2283,7 @@ window.changeCallwords = changeCallwords; * Triggered by the modcall sfx dropdown */ export function modcall_test() { - client.handleZZ('test#test'.split('#')); + client.handleZZ("test#test".split("#")); } window.modcall_test = modcall_test; @@ -2853,10 +2291,11 @@ window.modcall_test = modcall_test; * Triggered by the ini button. */ export async function iniedit() { - const ininame = (document.getElementById('client_ininame')).value; + const ininame = (document.getElementById("client_ininame")) + .value; const inicharID = client.charID; - await client.handleCharacterInfo(ininame.split('&'), inicharID); - client.handlePV((`PV#0#CID#${inicharID}`).split('#')); + await client.handleCharacterInfo(ininame.split("&"), inicharID); + client.handlePV(`PV#0#CID#${inicharID}`.split("#")); } window.iniedit = iniedit; @@ -2864,16 +2303,16 @@ window.iniedit = iniedit; * Triggered by the pantilt checkbox */ export async function switchPanTilt() { - const fullview = document.getElementById('client_fullview'); - const checkbox = document.getElementById('client_pantilt'); + const fullview = document.getElementById("client_fullview"); + const checkbox = document.getElementById("client_pantilt"); if (checkbox.checked) { - fullview.style.transition = '0.5s ease-in-out'; + fullview.style.transition = "0.5s ease-in-out"; } else { - fullview.style.transition = 'none'; + fullview.style.transition = "none"; } - return + return; } window.switchPanTilt = switchPanTilt; @@ -2881,13 +2320,17 @@ window.switchPanTilt = switchPanTilt; * Triggered by the change aspect ratio checkbox */ export async function switchAspectRatio() { - const background = document.getElementById('client_gamewindow'); - const offsetCheck = document.getElementById('client_hdviewport_offset'); - if ((document.getElementById('client_hdviewport')).checked) { - background.style.paddingBottom = '56.25%'; + const background = document.getElementById("client_gamewindow"); + const offsetCheck = ( + document.getElementById("client_hdviewport_offset") + ); + if ( + (document.getElementById("client_hdviewport")).checked + ) { + background.style.paddingBottom = "56.25%"; offsetCheck.disabled = false; } else { - background.style.paddingBottom = '75%'; + background.style.paddingBottom = "75%"; offsetCheck.disabled = true; } } @@ -2897,13 +2340,16 @@ window.switchAspectRatio = switchAspectRatio; * Triggered by the change aspect ratio checkbox */ export async function switchChatOffset() { - const container = document.getElementById('client_chatcontainer'); - if ((document.getElementById('client_hdviewport_offset')).checked) { - container.style.width = '80%'; - container.style.left = '10%'; + const container = document.getElementById("client_chatcontainer"); + if ( + (document.getElementById("client_hdviewport_offset")) + .checked + ) { + container.style.width = "80%"; + container.style.left = "10%"; } else { - container.style.width = '100%'; - container.style.left = '0'; + container.style.width = "100%"; + container.style.left = "0"; } } window.switchChatOffset = switchChatOffset; @@ -2913,9 +2359,9 @@ window.switchChatOffset = switchChatOffset; * @param {MouseEvent} event */ export function changeCharacter(_event: Event) { - document.getElementById('client_waiting').style.display = 'block'; - document.getElementById('client_charselect').style.display = 'block'; - document.getElementById('client_emo').innerHTML = ''; + document.getElementById("client_waiting").style.display = "block"; + document.getElementById("client_charselect").style.display = "block"; + document.getElementById("client_emo").innerHTML = ""; } window.changeCharacter = changeCharacter; @@ -2936,7 +2382,7 @@ window.charError = charError; */ export function imgError(image: HTMLImageElement) { image.onerror = null; - image.src = ''; // unload so the old sprite doesn't persist + image.src = ""; // unload so the old sprite doesn't persist return true; } window.imgError = imgError; @@ -2945,18 +2391,20 @@ window.imgError = imgError; * Triggered when there was an error loading a sound * @param {HTMLAudioElement} image the element containing the missing sound */ -export function opusCheck(channel: HTMLAudioElement): OnErrorEventHandlerNonNull{ - const audio = channel.src - if (audio === '') { - return +export function opusCheck( + channel: HTMLAudioElement +): OnErrorEventHandlerNonNull { + const audio = channel.src; + if (audio === "") { + return; } console.info(`failed to load sound ${channel.src}`); - let oldsrc = ''; - let newsrc = ''; + let oldsrc = ""; + let newsrc = ""; oldsrc = channel.src; - if (!oldsrc.endsWith('.opus')) { - newsrc = oldsrc.replace('.mp3', '.opus'); - newsrc = newsrc.replace('.wav', '.opus'); + if (!oldsrc.endsWith(".opus")) { + newsrc = oldsrc.replace(".mp3", ".opus"); + newsrc = newsrc.replace(".wav", ".opus"); channel.src = newsrc; // unload so the old sprite doesn't persist } } @@ -2968,9 +2416,9 @@ window.opusCheck = opusCheck; export function ReconnectButton() { client.cleanup(); client = new Client(serverIP); - + if (client) { - document.getElementById('client_error').style.display = 'none'; + document.getElementById("client_error").style.display = "none"; } } window.ReconnectButton = ReconnectButton; @@ -2980,18 +2428,27 @@ window.ReconnectButton = ReconnectButton; * @param {string} msg the string to be added * @param {string} name the name of the sender */ -function appendICLog(msg: string, showname = '', nameplate = '', time = new Date()) { - const entry = document.createElement('p'); - const shownameField = document.createElement('span'); - const nameplateField = document.createElement('span'); - const textField = document.createElement('span'); - nameplateField.className = 'iclog_name iclog_nameplate'; +export function appendICLog( + msg: string, + showname = "", + nameplate = "", + time = new Date() +) { + const entry = document.createElement("p"); + const shownameField = document.createElement("span"); + const nameplateField = document.createElement("span"); + const textField = document.createElement("span"); + nameplateField.className = "iclog_name iclog_nameplate"; nameplateField.appendChild(document.createTextNode(nameplate)); - shownameField.className = 'iclog_name iclog_showname'; - if (showname === '' || !showname) { shownameField.appendChild(document.createTextNode(nameplate)); } else { shownameField.appendChild(document.createTextNode(showname)); } + shownameField.className = "iclog_name iclog_showname"; + if (showname === "" || !showname) { + shownameField.appendChild(document.createTextNode(nameplate)); + } else { + shownameField.appendChild(document.createTextNode(showname)); + } - textField.className = 'iclog_text'; + textField.className = "iclog_text"; textField.appendChild(document.createTextNode(msg)); entry.appendChild(shownameField); @@ -3000,16 +2457,16 @@ function appendICLog(msg: string, showname = '', nameplate = '', time = new Date // Only put a timestamp if the minute has changed. if (lastICMessageTime.getMinutes() !== time.getMinutes()) { - const timeStamp = document.createElement('span'); - timeStamp.className = 'iclog_time'; + const timeStamp = document.createElement("span"); + timeStamp.className = "iclog_time"; timeStamp.innerText = time.toLocaleTimeString(undefined, { - hour: 'numeric', - minute: '2-digit', + hour: "numeric", + minute: "2-digit", }); entry.appendChild(timeStamp); } - const clientLog = document.getElementById('client_log'); + const clientLog = document.getElementById("client_log"); clientLog.appendChild(entry); /* This is a little buggy - some troubleshooting might be desirable */ @@ -3024,14 +2481,13 @@ function appendICLog(msg: string, showname = '', nameplate = '', time = new Date * check if the message contains an entry on our callword list * @param {string} message */ -export function checkCallword(message: string) { +export function checkCallword(message: string, sfxAudio: HTMLAudioElement) { client.callwords.forEach(testCallword); - function testCallword(item: string) { - if (item !== '' && message.toLowerCase().includes(item.toLowerCase())) { - viewport.sfxaudio.pause(); - viewport.sfxaudio.src = `${AO_HOST}sounds/general/sfx-gallery.opus`; - viewport.sfxaudio.play(); + if (item !== "" && message.toLowerCase().includes(item.toLowerCase())) { + sfxAudio.pause(); + sfxAudio.src = `${AO_HOST}sounds/general/sfx-gallery.opus`; + sfxAudio.play(); } } } @@ -3041,14 +2497,16 @@ export function checkCallword(message: string) { * @param {MouseEvent} event */ export function chartable_filter(_event: Event) { - const searchname = (document.getElementById('client_charactersearch')).value; + const searchname = (( + document.getElementById("client_charactersearch") + )).value; client.chars.forEach((character: any, charid: number) => { const demothing = document.getElementById(`demo_${charid}`); if (character.name.toLowerCase().indexOf(searchname.toLowerCase()) === -1) { - demothing.style.display = 'none'; + demothing.style.display = "none"; } else { - demothing.style.display = 'inline-block'; + demothing.style.display = "inline-block"; } }); } @@ -3062,8 +2520,8 @@ window.chartable_filter = chartable_filter; export function pickChar(ccharacter: number) { if (ccharacter === -1) { // Spectator - document.getElementById('client_waiting').style.display = 'none'; - document.getElementById('client_charselect').style.display = 'none'; + document.getElementById("client_waiting").style.display = "none"; + document.getElementById("client_charselect").style.display = "none"; } client.sendCharacter(ccharacter); } @@ -3076,17 +2534,20 @@ window.pickChar = pickChar; export function pickEmotion(emo: number) { try { if (client.selectedEmote !== -1) { - document.getElementById(`emo_${client.selectedEmote}`).className = 'emote_button'; + document.getElementById(`emo_${client.selectedEmote}`).className = + "emote_button"; } } catch (err) { - // do nothing + // do nothing } client.selectedEmote = emo; - document.getElementById(`emo_${emo}`).className = 'emote_button dark'; + document.getElementById(`emo_${emo}`).className = "emote_button dark"; - (document.getElementById('sendsfx')).checked = (client.emote.sfx.length > 1); + (document.getElementById("sendsfx")).checked = + client.emote.sfx.length > 1; - (document.getElementById('sendpreanim')).checked = (client.emote.zoom == 1); + (document.getElementById("sendpreanim")).checked = + client.emote.zoom == 1; } window.pickEmotion = pickEmotion; @@ -3098,28 +2559,39 @@ export function pickEvidence(evidence: number) { if (client.selectedEvidence !== evidence) { // Update selected evidence if (client.selectedEvidence > 0) { - document.getElementById(`evi_${client.selectedEvidence}`).className = 'evi_icon'; + document.getElementById(`evi_${client.selectedEvidence}`).className = + "evi_icon"; } - document.getElementById(`evi_${evidence}`).className = 'evi_icon dark'; + document.getElementById(`evi_${evidence}`).className = "evi_icon dark"; client.selectedEvidence = evidence; // Show evidence on information window - (document.getElementById('evi_name')).value = client.evidences[evidence - 1].name; - (document.getElementById('evi_desc')).value = client.evidences[evidence - 1].desc; + (document.getElementById("evi_name")).value = + client.evidences[evidence - 1].name; + (document.getElementById("evi_desc")).value = + client.evidences[evidence - 1].desc; // Update icon - const icon_id = getIndexFromSelect('evi_select', client.evidences[evidence - 1].filename); - (document.getElementById('evi_select')).selectedIndex = icon_id; + const icon_id = getIndexFromSelect( + "evi_select", + client.evidences[evidence - 1].filename + ); + (document.getElementById("evi_select")).selectedIndex = + icon_id; if (icon_id === 0) { - (document.getElementById('evi_filename')).value = client.evidences[evidence - 1].filename; + (document.getElementById("evi_filename")).value = + client.evidences[evidence - 1].filename; } updateEvidenceIcon(); // Update button - document.getElementById('evi_add').className = 'client_button hover_button inactive'; - document.getElementById('evi_edit').className = 'client_button hover_button'; - document.getElementById('evi_cancel').className = 'client_button hover_button'; - document.getElementById('evi_del').className = 'client_button hover_button'; + document.getElementById("evi_add").className = + "client_button hover_button inactive"; + document.getElementById("evi_edit").className = + "client_button hover_button"; + document.getElementById("evi_cancel").className = + "client_button hover_button"; + document.getElementById("evi_del").className = "client_button hover_button"; } else { cancelEvidence(); } @@ -3130,13 +2602,15 @@ window.pickEvidence = pickEvidence; * Add evidence. */ export function addEvidence() { - const evidence_select = document.getElementById('evi_select'); + const evidence_select = ( + document.getElementById("evi_select") + ); client.sendPE( - (document.getElementById('evi_name')).value, - (document.getElementById('evi_desc')).value, + (document.getElementById("evi_name")).value, + (document.getElementById("evi_desc")).value, evidence_select.selectedIndex === 0 - ? (document.getElementById('evi_filename')).value - : evidence_select.options[evidence_select.selectedIndex].text, + ? (document.getElementById("evi_filename")).value + : evidence_select.options[evidence_select.selectedIndex].text ); cancelEvidence(); } @@ -3146,15 +2620,17 @@ window.addEvidence = addEvidence; * Edit selected evidence. */ export function editEvidence() { - const evidence_select = document.getElementById('evi_select'); + const evidence_select = ( + document.getElementById("evi_select") + ); const id = client.selectedEvidence - 1; client.sendEE( id, - (document.getElementById('evi_name')).value, - (document.getElementById('evi_desc')).value, + (document.getElementById("evi_name")).value, + (document.getElementById("evi_desc")).value, evidence_select.selectedIndex === 0 - ? (document.getElementById('evi_filename')).value - : evidence_select.options[evidence_select.selectedIndex].text, + ? (document.getElementById("evi_filename")).value + : evidence_select.options[evidence_select.selectedIndex].text ); cancelEvidence(); } @@ -3176,23 +2652,29 @@ window.deleteEvidence = deleteEvidence; export function cancelEvidence() { // Clear evidence data if (client.selectedEvidence > 0) { - document.getElementById(`evi_${client.selectedEvidence}`).className = 'evi_icon'; + document.getElementById(`evi_${client.selectedEvidence}`).className = + "evi_icon"; } client.selectedEvidence = 0; // Clear evidence on information window - (document.getElementById('evi_select')).selectedIndex = 0; + (document.getElementById("evi_select")).selectedIndex = 0; updateEvidenceIcon(); // Update icon widget - (document.getElementById('evi_filename')).value = ''; - (document.getElementById('evi_name')).value = ''; - (document.getElementById('evi_desc')).value = ''; - (document.getElementById('evi_preview')).src = `${AO_HOST}misc/empty.png`; // Clear icon + (document.getElementById("evi_filename")).value = ""; + (document.getElementById("evi_name")).value = ""; + (document.getElementById("evi_desc")).value = ""; + (( + document.getElementById("evi_preview") + )).src = `${AO_HOST}misc/empty.png`; // Clear icon // Update button - document.getElementById('evi_add').className = 'client_button hover_button'; - document.getElementById('evi_edit').className = 'client_button hover_button inactive'; - document.getElementById('evi_cancel').className = 'client_button hover_button inactive'; - document.getElementById('evi_del').className = 'client_button hover_button inactive'; + document.getElementById("evi_add").className = "client_button hover_button"; + document.getElementById("evi_edit").className = + "client_button hover_button inactive"; + document.getElementById("evi_cancel").className = + "client_button hover_button inactive"; + document.getElementById("evi_del").className = + "client_button hover_button inactive"; } window.cancelEvidence = cancelEvidence; @@ -3217,16 +2699,20 @@ window.getIndexFromSelect = getIndexFromSelect; * Set the style of the chatbox */ export function setChatbox(style: string) { - const chatbox_theme = document.getElementById('chatbox_theme'); - const themeselect = document.getElementById('client_chatboxselect'); + const chatbox_theme = ( + document.getElementById("chatbox_theme") + ); + const themeselect = ( + document.getElementById("client_chatboxselect") + ); const selected_theme = themeselect.value; - setCookie('chatbox', selected_theme); - if (selected_theme === 'dynamic') { + setCookie("chatbox", selected_theme); + if (selected_theme === "dynamic") { if (chatbox_arr.includes(style)) { chatbox_theme.href = `styles/chatbox/${style}.css`; } else { - chatbox_theme.href = 'styles/chatbox/aa.css'; + chatbox_theme.href = "styles/chatbox/aa.css"; } } else { chatbox_theme.href = `styles/chatbox/${selected_theme}.css`; @@ -3238,8 +2724,8 @@ window.setChatbox = setChatbox; * Set the font size for the chatbox */ export function resizeChatbox() { - const chatContainerBox = document.getElementById('client_chatcontainer'); - const gameHeight = document.getElementById('client_background').offsetHeight; + const chatContainerBox = document.getElementById("client_chatcontainer"); + const gameHeight = document.getElementById("client_background").offsetHeight; chatContainerBox.style.fontSize = `${(gameHeight * 0.0521).toFixed(1)}px`; } @@ -3249,16 +2735,26 @@ window.resizeChatbox = resizeChatbox; * Update evidence icon. */ export function updateEvidenceIcon() { - const evidence_select = document.getElementById('evi_select'); - const evidence_filename = document.getElementById('evi_filename'); - const evidence_iconbox = document.getElementById('evi_preview'); + const evidence_select = ( + document.getElementById("evi_select") + ); + const evidence_filename = ( + document.getElementById("evi_filename") + ); + const evidence_iconbox = ( + document.getElementById("evi_preview") + ); if (evidence_select.selectedIndex === 0) { - evidence_filename.style.display = 'initial'; - evidence_iconbox.src = `${AO_HOST}evidence/${encodeURI(evidence_filename.value.toLowerCase())}`; + evidence_filename.style.display = "initial"; + evidence_iconbox.src = `${AO_HOST}evidence/${encodeURI( + evidence_filename.value.toLowerCase() + )}`; } else { - evidence_filename.style.display = 'none'; - evidence_iconbox.src = `${AO_HOST}evidence/${encodeURI(evidence_select.value.toLowerCase())}`; + evidence_filename.style.display = "none"; + evidence_iconbox.src = `${AO_HOST}evidence/${encodeURI( + evidence_select.value.toLowerCase() + )}`; } } window.updateEvidenceIcon = updateEvidenceIcon; @@ -3267,16 +2763,21 @@ window.updateEvidenceIcon = updateEvidenceIcon; * Update evidence icon. */ export function updateActionCommands(side: string) { - if (side === 'jud') { - document.getElementById('judge_action').style.display = 'inline-table'; - document.getElementById('no_action').style.display = 'none'; + if (side === "jud") { + document.getElementById("judge_action").style.display = "inline-table"; + document.getElementById("no_action").style.display = "none"; } else { - document.getElementById('judge_action').style.display = 'none'; - document.getElementById('no_action').style.display = 'inline-table'; + document.getElementById("judge_action").style.display = "none"; + document.getElementById("no_action").style.display = "inline-table"; } // Update role selector - for (let i = 0, role_select = document.getElementById('role_select'); i < role_select.options.length; i++) { + for ( + let i = 0, + role_select = document.getElementById("role_select"); + i < role_select.options.length; + i++ + ) { if (side === role_select.options[i].value) { role_select.options.selectedIndex = i; return; @@ -3289,18 +2790,22 @@ window.updateActionCommands = updateActionCommands; * Change background via OOC. */ export function changeBackgroundOOC() { - const selectedBG = document.getElementById('bg_select'); + const selectedBG = document.getElementById("bg_select"); const changeBGCommand = "bg $1"; - const bgFilename = document.getElementById('bg_filename'); + const bgFilename = document.getElementById("bg_filename"); - let filename = ''; + let filename = ""; if (selectedBG.selectedIndex === 0) { filename = bgFilename.value; } else { filename = selectedBG.value; } - if (mode === 'join') { client.sendOOC(`/${changeBGCommand.replace('$1', filename)}`); } else if (mode === 'replay') { client.sendSelf(`BN#${filename}#%`); } + if (mode === "join") { + client.sendOOC(`/${changeBGCommand.replace("$1", filename)}`); + } else if (mode === "replay") { + client.sendSelf(`BN#${filename}#%`); + } } window.changeBackgroundOOC = changeBackgroundOOC; @@ -3308,7 +2813,7 @@ window.changeBackgroundOOC = changeBackgroundOOC; * Change role via OOC. */ export function changeRoleOOC() { - const roleselect = document.getElementById('role_select'); + const roleselect = document.getElementById("role_select"); client.sendOOC(`/pos ${roleselect.value}`); client.sendServer(`SP#${roleselect.value}#%`); @@ -3329,10 +2834,10 @@ window.randomCharacterOOC = randomCharacterOOC; */ export function callMod() { let modcall; - if (extrafeatures.includes('modcall_reason')) { - modcall = prompt('Please enter the reason for the modcall', ''); + if (extrafeatures.includes("modcall_reason")) { + modcall = prompt("Please enter the reason for the modcall", ""); } - if (modcall == null || modcall === '') { + if (modcall == null || modcall === "") { // cancel } else { client.sendZZ(modcall); @@ -3344,7 +2849,7 @@ window.callMod = callMod; * Declare witness testimony. */ export function initWT() { - client.sendRT('testimony1'); + client.sendRT("testimony1"); } window.initWT = initWT; @@ -3352,7 +2857,7 @@ window.initWT = initWT; * Declare cross examination. */ export function initCE() { - client.sendRT('testimony2'); + client.sendRT("testimony2"); } window.initCE = initCE; @@ -3360,7 +2865,7 @@ window.initCE = initCE; * Declare the defendant not guilty */ export function notguilty() { - client.sendRT('judgeruling#0'); + client.sendRT("judgeruling#0"); } window.notguilty = notguilty; @@ -3368,7 +2873,7 @@ window.notguilty = notguilty; * Declare the defendant not guilty */ export function guilty() { - client.sendRT('judgeruling#1'); + client.sendRT("judgeruling#1"); } window.guilty = guilty; @@ -3376,7 +2881,7 @@ window.guilty = guilty; * Increment defense health point. */ export function addHPD() { - client.sendHP(1, (client.hp[0] + 1)); + client.sendHP(1, client.hp[0] + 1); } window.addHPD = addHPD; @@ -3384,7 +2889,7 @@ window.addHPD = addHPD; * Decrement defense health point. */ export function redHPD() { - client.sendHP(1, (client.hp[0] - 1)); + client.sendHP(1, client.hp[0] - 1); } window.redHPD = redHPD; @@ -3392,7 +2897,7 @@ window.redHPD = redHPD; * Increment prosecution health point. */ export function addHPP() { - client.sendHP(2, (client.hp[1] + 1)); + client.sendHP(2, client.hp[1] + 1); } window.addHPP = addHPP; @@ -3400,7 +2905,7 @@ window.addHPP = addHPP; * Decrement prosecution health point. */ export function redHPP() { - client.sendHP(2, (client.hp[1] - 1)); + client.sendHP(2, client.hp[1] - 1); } window.redHPP = redHPP; @@ -3408,16 +2913,26 @@ window.redHPP = redHPP; * Update background preview. */ export function updateBackgroundPreview() { - const background_select = document.getElementById('bg_select'); - const background_filename = document.getElementById('bg_filename'); - const background_preview = document.getElementById('bg_preview'); + const background_select = ( + document.getElementById("bg_select") + ); + const background_filename = ( + document.getElementById("bg_filename") + ); + const background_preview = ( + document.getElementById("bg_preview") + ); if (background_select.selectedIndex === 0) { - background_filename.style.display = 'initial'; - background_preview.src = `${AO_HOST}background/${encodeURI(background_filename.value.toLowerCase())}/defenseempty.png`; + background_filename.style.display = "initial"; + background_preview.src = `${AO_HOST}background/${encodeURI( + background_filename.value.toLowerCase() + )}/defenseempty.png`; } else { - background_filename.style.display = 'none'; - background_preview.src = `${AO_HOST}background/${encodeURI(background_select.value.toLowerCase())}/defenseempty.png`; + background_filename.style.display = "none"; + background_preview.src = `${AO_HOST}background/${encodeURI( + background_select.value.toLowerCase() + )}/defenseempty.png`; } } window.updateBackgroundPreview = updateBackgroundPreview; @@ -3428,10 +2943,12 @@ window.updateBackgroundPreview = updateBackgroundPreview; */ export function toggleMenu(menu: number) { if (menu !== selectedMenu) { - document.getElementById(`menu_${menu}`).className = 'menu_button active'; - document.getElementById(`content_${menu}`).className = 'menu_content active'; - document.getElementById(`menu_${selectedMenu}`).className = 'menu_button'; - document.getElementById(`content_${selectedMenu}`).className = 'menu_content'; + document.getElementById(`menu_${menu}`).className = "menu_button active"; + document.getElementById(`content_${menu}`).className = + "menu_content active"; + document.getElementById(`menu_${selectedMenu}`).className = "menu_button"; + document.getElementById(`content_${selectedMenu}`).className = + "menu_content"; selectedMenu = menu; } } @@ -3444,15 +2961,16 @@ window.toggleMenu = toggleMenu; */ export function toggleShout(shout: number) { if (shout === selectedShout) { - document.getElementById(`button_${shout}`).className = 'client_button'; + document.getElementById(`button_${shout}`).className = "client_button"; selectedShout = 0; } else { - document.getElementById(`button_${shout}`).className = 'client_button dark'; + document.getElementById(`button_${shout}`).className = "client_button dark"; if (selectedShout) { - document.getElementById(`button_${selectedShout}`).className = 'client_button'; + document.getElementById(`button_${selectedShout}`).className = + "client_button"; } selectedShout = shout; } } window.toggleShout = toggleShout; -export default Client +export default Client; diff --git a/webAO/client/setEmote.js b/webAO/client/setEmote.js deleted file mode 100644 index f682fe5..0000000 --- a/webAO/client/setEmote.js +++ /dev/null @@ -1,40 +0,0 @@ -import transparentPng from '../constants/transparentPng'; -import fileExists from '../utils/fileExists'; - -/** - * Sets all the img tags to the right sources - * @param {*} chatmsg - */ - -const setEmote = async (AO_HOST, client, charactername, emotename, prefix, pair, side) => { - const pairID = pair ? 'pair' : 'char'; - const characterFolder = `${AO_HOST}characters/`; - const acceptedPositions = ['def', 'pro', 'wit']; - const position = acceptedPositions.includes(side) ? `${side}_` : ''; - const emoteSelector = document.getElementById(`client_${position}${pairID}_img`) - const extensionsMap = [ - '.gif', - '.png', - '.apng', - '.webp' - ]; - - for (const extension of extensionsMap) { - // Hides all sprites before creating a new sprite - if (client.lastChar !== client.chatmsg.name) { - emoteSelector.src = transparentPng; - } - let url; - if (extension === '.png') { - url = `${characterFolder}${encodeURI(charactername)}/${encodeURI(emotename)}${extension}`; - } else { - url = `${characterFolder}${encodeURI(charactername)}/${encodeURI(prefix)}${encodeURI(emotename)}${extension}`; - } - const exists = await fileExists(url); - if (exists) { - emoteSelector.src = url; - break; - } - } -}; -export default setEmote; diff --git a/webAO/client/setEmote.ts b/webAO/client/setEmote.ts new file mode 100644 index 0000000..1f0de07 --- /dev/null +++ b/webAO/client/setEmote.ts @@ -0,0 +1,51 @@ +import Client from "../client"; +import transparentPng from "../constants/transparentPng"; +import fileExists from "../utils/fileExists"; + +/** + * Sets all the img tags to the right sources + * @param {*} chatmsg + */ + +const setEmote = async ( + AO_HOST: string, + client: Client, + charactername: string, + emotename: string, + prefix: string, + pair: boolean, + side: string +) => { + const pairID = pair ? "pair" : "char"; + const characterFolder = `${AO_HOST}characters/`; + const acceptedPositions = ["def", "pro", "wit"]; + const position = acceptedPositions.includes(side) ? `${side}_` : ""; + const emoteSelector = document.getElementById( + `client_${position}${pairID}_img` + ) as HTMLImageElement; + const extensionsMap = [".gif", ".png", ".apng", ".webp"]; + + for (const extension of extensionsMap) { + // Hides all sprites before creating a new sprite + + if (client.viewport.lastChar !== client.viewport.chatmsg.name) { + emoteSelector.src = transparentPng; + } + let url; + if (extension === ".png") { + url = `${characterFolder}${encodeURI(charactername)}/${encodeURI( + emotename + )}${extension}`; + } else { + url = `${characterFolder}${encodeURI(charactername)}/${encodeURI( + prefix + )}${encodeURI(emotename)}${extension}`; + } + const exists = await fileExists(url); + if (exists) { + emoteSelector.src = url; + break; + } + } +}; +export default setEmote; diff --git a/webAO/viewport.ts b/webAO/viewport.ts new file mode 100644 index 0000000..f680162 --- /dev/null +++ b/webAO/viewport.ts @@ -0,0 +1,1053 @@ +import tryUrls from "./utils/tryUrls"; +import fileExists from "./utils/fileExists"; +import Client, { opusCheck } from "./client"; +import { delay } from "./client"; +import { UPDATE_INTERVAL } from "./client"; +import { setChatbox } from "./client"; +import { resizeChatbox } from "./client"; +import transparentPng from "./constants/transparentPng"; +import mlConfig from "./utils/aoml"; +import { appendICLog } from "./client"; +import { checkCallword } from "./client"; +import setEmote from "./client/setEmote"; +import getAnimLength from "./utils/getAnimLength"; +import { safeTags } from "./encoding"; +import setCookie from "./utils/setCookie"; +interface ChatMsg { + content: string; + objection: number; + sound: string; + startpreanim: boolean; + startspeaking: boolean; + side: any; + color: number; + snddelay: number; + preanimdelay: number; + speed: number; + blips: string; + self_offset?: number[]; + other_offset?: number[]; + showname?: string; + nameplate?: string; + flip?: number; + other_flip?: number; + effects?: string[]; + deskmod?: number; + preanim?: string; + other_name?: string; + sprite?: string; + name?: string; + chatbox?: string; + other_emote?: string; + parsed?: HTMLSpanElement[]; + screenshake?: number; + flash?: number; + type?: number; + evidence?: number; + looping_sfx?: boolean; + noninterrupting_preanim?: number; +} +interface Testimony { + [key: number]: string; +} +export interface Viewport { + chat_tick: Function; + changeMusicVolume: Function; + changeBlipVolume: Function; + reloadTheme: Function; + playSFX: Function; + set_side: Function; + initTestimonyUpdater: Function; + updateTestimony: Function; + disposeTestimony: Function; + handle_ic_speaking: Function; + handleTextTick: Function; + theme: string; + chatmsg: ChatMsg; + sfxaudio: HTMLAudioElement; + blipChannels: HTMLAudioElement[]; + music: any; + musicVolume: number; + bgFolder: string; + bgname: string; + lastChar: string; +} +const viewport = (masterClient: Client, AO_HOST: string): Viewport => { + const attorneyMarkdown = mlConfig(AO_HOST); + let client = masterClient; + let textnow = ""; + let chatmsg = { + content: "", + objection: 0, + sound: "", + startpreanim: true, + startspeaking: false, + side: null, + color: 0, + snddelay: 0, + preanimdelay: 0, + speed: UPDATE_INTERVAL, + } as ChatMsg; + let shouts = [undefined, "holdit", "objection", "takethat", "custom"]; + let colors = [ + "white", + "green", + "red", + "orange", + "blue", + "yellow", + "pink", + "cyan", + "grey", + ]; + const blipSelectors = document.getElementsByClassName( + "blipSound" + ) as HTMLCollectionOf; + + let blipChannels = [...blipSelectors]; + // Allocate multiple blip audio channels to make blips less jittery + blipChannels.forEach((channel: HTMLAudioElement) => (channel.volume = 0.5)); + blipChannels.forEach( + (channel: HTMLAudioElement) => (channel.onerror = opusCheck(channel)) + ); + let currentBlipChannel = 0; + let sfxaudio = document.getElementById("client_sfxaudio") as HTMLAudioElement; + sfxaudio.src = `${AO_HOST}sounds/general/sfx-realization.opus`; + let sfxplayed = 0; + + let shoutaudio = document.getElementById( + "client_shoutaudio" + ) as HTMLAudioElement; + shoutaudio.src = `${AO_HOST}misc/default/objection.opus`; + + let testimonyAudio = document.getElementById( + "client_testimonyaudio" + ) as HTMLAudioElement; + testimonyAudio.src = `${AO_HOST}sounds/general/sfx-guilty.opus`; + + const audioChannels = document.getElementsByClassName( + "audioChannel" + ) as HTMLCollectionOf; + let music: any; + music = [...audioChannels]; + music.forEach((channel: HTMLAudioElement) => (channel.volume = 0.5)); + music.forEach( + (channel: HTMLAudioElement) => (channel.onerror = opusCheck(channel)) + ); + let musicVolume = 0; + let updater: any; + let testimonyUpdater: any; + let bgname = "gs4"; + let lastChar = ""; + let lastEvi = 0; + let testimonyTimer = 0; + let shoutTimer = 0; + let tickTimer = 0; + let _animating = false; + let startFirstTickCheck: boolean; + let startSecondTickCheck: boolean; + let startThirdTickCheck: boolean; + let theme: string; + const bgFolder = `${AO_HOST}background/${encodeURI(bgname.toLowerCase())}/`; + /** + * Sets the volume of the music. + * @param {number} volume + */ + const changeMusicVolume = (volume: number = -1) => { + const clientVolume = Number( + (document.getElementById("client_mvolume")).value + ); + let musicVolume = volume === -1 ? clientVolume : volume; + music.forEach( + (channel: HTMLAudioElement) => (channel.volume = musicVolume) + ); + setCookie("musicVolume", String(musicVolume)); + }; + window.changeMusicVolume = changeMusicVolume; + + /** + * Play any SFX + * + * @param {string} sfxname + */ + const playSFX = async (sfxname: string, looping: boolean) => { + sfxaudio.pause(); + sfxaudio.loop = looping; + sfxaudio.src = sfxname; + sfxaudio.play(); + }; + + /** + * Changes the viewport background based on a given position. + * + * Valid positions: `def, pro, hld, hlp, wit, jud, jur, sea` + * @param {string} position the position to change into + */ + const set_side = async ({ + position, + showSpeedLines, + showDesk, + }: { + position: string; + showSpeedLines: boolean; + showDesk: boolean; + }) => { + const view = document.getElementById("client_fullview"); + + let bench: HTMLImageElement; + if ("def,pro,wit".includes(position)) { + bench = ( + document.getElementById(`client_${position}_bench`) + ); + } else { + bench = document.getElementById("client_bench_classic"); + } + + let court: HTMLImageElement; + if ("def,pro,wit".includes(position)) { + court = ( + document.getElementById(`client_court_${position}`) + ); + } else { + court = document.getElementById("client_court_classic"); + } + + interface Desk { + ao2?: string; + ao1?: string; + } + interface Position { + bg?: string; + desk?: Desk; + speedLines: string; + } + + interface Positions { + [key: string]: Position; + } + + const positions: Positions = { + def: { + bg: "defenseempty", + desk: { ao2: "defensedesk.png", ao1: "bancodefensa.png" } as Desk, + speedLines: "defense_speedlines.gif", + }, + pro: { + bg: "prosecutorempty", + desk: { ao2: "prosecutiondesk.png", ao1: "bancoacusacion.png" } as Desk, + speedLines: "prosecution_speedlines.gif", + }, + hld: { + bg: "helperstand", + desk: null as Desk, + speedLines: "defense_speedlines.gif", + }, + hlp: { + bg: "prohelperstand", + desk: null as Desk, + speedLines: "prosecution_speedlines.gif", + }, + wit: { + bg: "witnessempty", + desk: { ao2: "stand.png", ao1: "estrado.png" } as Desk, + speedLines: "prosecution_speedlines.gif", + }, + jud: { + bg: "judgestand", + desk: { ao2: "judgedesk.png", ao1: "judgedesk.gif" } as Desk, + speedLines: "prosecution_speedlines.gif", + }, + jur: { + bg: "jurystand", + desk: { ao2: "jurydesk.png", ao1: "estrado.png" } as Desk, + speedLines: "defense_speedlines.gif", + }, + sea: { + bg: "seancestand", + desk: { ao2: "seancedesk.png", ao1: "estrado.png" } as Desk, + speedLines: "prosecution_speedlines.gif", + }, + }; + + let bg; + let desk; + let speedLines; + + if ("def,pro,hld,hlp,wit,jud,jur,sea".includes(position)) { + bg = positions[position].bg; + desk = positions[position].desk; + speedLines = positions[position].speedLines; + } else { + bg = `${position}`; + desk = { ao2: `${position}_overlay.png`, ao1: "_overlay.png" }; + speedLines = "defense_speedlines.gif"; + } + + if (showSpeedLines === true) { + court.src = `${AO_HOST}themes/default/${encodeURI(speedLines)}`; + } else { + court.src = await tryUrls(bgFolder + bg); + } + + if (showDesk === true && desk) { + const deskFilename = (await fileExists(bgFolder + desk.ao2)) + ? desk.ao2 + : desk.ao1; + bench.src = bgFolder + deskFilename; + bench.style.opacity = "1"; + } else { + bench.style.opacity = "0"; + } + + if ("def,pro,wit".includes(position)) { + view.style.display = ""; + document.getElementById("client_classicview").style.display = "none"; + switch (position) { + case "def": + view.style.left = "0"; + break; + case "wit": + view.style.left = "-200%"; + break; + case "pro": + view.style.left = "-400%"; + break; + } + } else { + view.style.display = "none"; + document.getElementById("client_classicview").style.display = ""; + } + }; + + /** + * Intialize testimony updater + */ + const initTestimonyUpdater = () => { + const testimonyFilenames: Testimony = { + 1: "witnesstestimony", + 2: "crossexamination", + 3: "notguilty", + 4: "guilty", + }; + + const testimony = testimonyFilenames[masterClient.testimonyID]; + if (!testimony) { + console.warn(`Invalid testimony ID ${masterClient.testimonyID}`); + return; + } + + testimonyAudio.src = masterClient.resources[testimony].sfx; + testimonyAudio.play(); + + const testimonyOverlay = ( + document.getElementById("client_testimony") + ); + testimonyOverlay.src = masterClient.resources[testimony].src; + testimonyOverlay.style.opacity = "1"; + + testimonyTimer = 0; + testimonyUpdater = setTimeout(() => updateTestimony(), UPDATE_INTERVAL); + }; + + /** + * Updates the testimony overaly + */ + const updateTestimony = () => { + const testimonyFilenames: Testimony = { + 1: "witnesstestimony", + 2: "crossexamination", + 3: "notguilty", + 4: "guilty", + }; + + // Update timer + testimonyTimer += UPDATE_INTERVAL; + + const testimony = testimonyFilenames[masterClient.testimonyID]; + const resource = masterClient.resources[testimony]; + if (!resource) { + disposeTestimony(); + return; + } + + if (testimonyTimer >= resource.duration) { + disposeTestimony(); + } else { + testimonyUpdater = setTimeout(() => updateTestimony(), UPDATE_INTERVAL); + } + }; + + /** + * Dispose the testimony overlay + */ + const disposeTestimony = () => { + masterClient.testimonyID = 0; + testimonyTimer = 0; + document.getElementById("client_testimony").style.opacity = "0"; + clearTimeout(testimonyUpdater); + }; + + /** + * Sets a new emote. + * This sets up everything before the tick() loops starts + * a lot of things can probably be moved here, like starting the shout animation if there is one + * TODO: the preanim logic, on the other hand, should probably be moved to tick() + * @param {object} chatmsg the new chat message + */ + const handle_ic_speaking = async (playerChatMsg: ChatMsg) => { + chatmsg = playerChatMsg; + textnow = ""; + sfxplayed = 0; + tickTimer = 0; + _animating = true; + startFirstTickCheck = true; + startSecondTickCheck = false; + startThirdTickCheck = false; + let charLayers = document.getElementById("client_char"); + let pairLayers = document.getElementById("client_pair_char"); + console.log(chatmsg); + console.log("FICLK YOPU"); + // stop updater + clearTimeout(updater); + + // stop last sfx from looping any longer + sfxaudio.loop = false; + + const fg = document.getElementById("client_fg"); + const gamewindow = document.getElementById("client_gamewindow"); + const waitingBox = document.getElementById("client_chatwaiting"); + + // Reset CSS animation + gamewindow.style.animation = ""; + waitingBox.style.opacity = "0"; + + const eviBox = document.getElementById("client_evi"); + + if (lastEvi !== chatmsg.evidence) { + eviBox.style.opacity = "0"; + eviBox.style.height = "0%"; + } + lastEvi = chatmsg.evidence; + + const validSides: string[] = ["def", "pro", "wit"]; // these are for the full view pan, the other positions use 'client_char' + if (validSides.includes(chatmsg.side)) { + charLayers = document.getElementById(`client_${chatmsg.side}_char`); + pairLayers = document.getElementById(`client_${chatmsg.side}_pair_char`); + } + + const chatContainerBox = document.getElementById("client_chatcontainer"); + const nameBoxInner = document.getElementById("client_inner_name"); + const chatBoxInner = document.getElementById("client_inner_chat"); + + const displayname = + (document.getElementById("showname")).checked && + chatmsg.showname !== "" + ? chatmsg.showname + : chatmsg.nameplate; + + // Clear out the last message + chatBoxInner.innerText = textnow; + nameBoxInner.innerText = displayname; + + if (lastChar !== chatmsg.name) { + charLayers.style.opacity = "0"; + pairLayers.style.opacity = "0"; + } + lastChar = chatmsg.name; + + appendICLog(chatmsg.content, chatmsg.showname, chatmsg.nameplate); + + checkCallword(chatmsg.content, sfxaudio); + + setEmote( + AO_HOST, + client, + chatmsg.name.toLowerCase(), + chatmsg.sprite, + "(a)", + false, + chatmsg.side + ); + + if (chatmsg.other_name) { + setEmote( + AO_HOST, + client, + chatmsg.other_name.toLowerCase(), + chatmsg.other_emote, + "(a)", + false, + chatmsg.side + ); + } + + // gets which shout shall played + const shoutSprite = ( + document.getElementById("client_shout") + ); + const shout = shouts[chatmsg.objection]; + if (shout) { + // Hide message box + chatContainerBox.style.opacity = "0"; + if (chatmsg.objection === 4) { + shoutSprite.src = `${AO_HOST}characters/${encodeURI( + chatmsg.name.toLowerCase() + )}/custom.gif`; + } else { + shoutSprite.src = masterClient.resources[shout].src; + shoutSprite.style.animation = "bubble 700ms steps(10, jump-both)"; + } + shoutSprite.style.opacity = "1"; + + shoutaudio.src = `${AO_HOST}characters/${encodeURI( + chatmsg.name.toLowerCase() + )}/${shout}.opus`; + shoutaudio.play(); + shoutTimer = masterClient.resources[shout].duration; + } else { + shoutTimer = 0; + } + + chatmsg.startpreanim = true; + let gifLength = 0; + + if (chatmsg.type === 1 && chatmsg.preanim !== "-") { + //we have a preanim + chatContainerBox.style.opacity = "0"; + gifLength = await getAnimLength( + `${AO_HOST}characters/${encodeURI( + chatmsg.name.toLowerCase() + )}/${encodeURI(chatmsg.preanim)}` + ); + console.debug("preanim is " + gifLength + " long"); + chatmsg.startspeaking = false; + } else { + chatmsg.startspeaking = true; + if (chatmsg.content !== "") chatContainerBox.style.opacity = "1"; + } + chatmsg.preanimdelay = gifLength; + const setAside = { + position: chatmsg.side, + showSpeedLines: false, + showDesk: false, + }; + let skipoffset: boolean = false; + if (chatmsg.type === 5) { + setAside.showSpeedLines = true; + setAside.showDesk = false; + set_side(setAside); + } else { + switch (Number(chatmsg.deskmod)) { + case 0: //desk is hidden + setAside.showSpeedLines = false; + setAside.showDesk = false; + set_side(setAside); + break; + case 1: //desk is shown + setAside.showSpeedLines = false; + setAside.showDesk = true; + set_side(setAside); + break; + case 2: //desk is hidden during preanim, but shown during idle/talk + setAside.showSpeedLines = false; + setAside.showDesk = false; + set_side(setAside); + break; + case 3: //opposite of 2 + setAside.showSpeedLines = false; + setAside.showDesk = false; + set_side(setAside); + break; + case 4: //desk is hidden, character offset is ignored, pair character is hidden during preanim, normal behavior during idle/talk + setAside.showSpeedLines = false; + setAside.showDesk = false; + set_side(setAside); + skipoffset = true; + break; + case 5: //opposite of 4 + setAside.showSpeedLines = false; + setAside.showDesk = true; + set_side(setAside); + break; + default: + setAside.showSpeedLines = false; + setAside.showDesk = true; + set_side(setAside); + break; + } + } + + setChatbox(chatmsg.chatbox); + resizeChatbox(); + + if (!skipoffset) { + // Flip the character + charLayers.style.transform = + chatmsg.flip === 1 ? "scaleX(-1)" : "scaleX(1)"; + pairLayers.style.transform = + chatmsg.other_flip === 1 ? "scaleX(-1)" : "scaleX(1)"; + + // Shift by the horizontal offset + switch (chatmsg.side) { + case "wit": + pairLayers.style.left = `${200 + Number(chatmsg.other_offset[0])}%`; + charLayers.style.left = `${200 + Number(chatmsg.self_offset[0])}%`; + break; + case "pro": + pairLayers.style.left = `${400 + Number(chatmsg.other_offset[0])}%`; + charLayers.style.left = `${400 + Number(chatmsg.self_offset[0])}%`; + break; + default: + pairLayers.style.left = `${Number(chatmsg.other_offset[0])}%`; + charLayers.style.left = `${Number(chatmsg.self_offset[0])}%`; + break; + } + + // New vertical offsets + pairLayers.style.top = `${Number(chatmsg.other_offset[1])}%`; + charLayers.style.top = `${Number(chatmsg.self_offset[1])}%`; + } + + blipChannels.forEach( + (channel: HTMLAudioElement) => + (channel.src = `${AO_HOST}sounds/general/sfx-blip${encodeURI( + chatmsg.blips.toLowerCase() + )}.opus`) + ); + + // process markup + if (chatmsg.content.startsWith("~~")) { + chatBoxInner.style.textAlign = "center"; + chatmsg.content = chatmsg.content.substring(2, chatmsg.content.length); + } else { + chatBoxInner.style.textAlign = "inherit"; + } + + // apply effects + fg.style.animation = ""; + const badEffects = ["", "-", "none"]; + if ( + chatmsg.effects[0] && + !badEffects.includes(chatmsg.effects[0].toLowerCase()) + ) { + const baseEffectUrl = `${AO_HOST}themes/default/effects/`; + fg.src = `${baseEffectUrl}${encodeURI( + chatmsg.effects[0].toLowerCase() + )}.webp`; + } else { + fg.src = transparentPng; + } + + charLayers.style.opacity = "1"; + + const soundChecks = ["0", "1", "", undefined]; + if (soundChecks.some((check) => chatmsg.sound === check)) { + chatmsg.sound = chatmsg.effects[2]; + } + chatmsg.parsed = await attorneyMarkdown.applyMarkdown( + chatmsg.content, + colors[chatmsg.color] + ); + console.log("ASSHOLE"); + chat_tick(); + }; + + const handleTextTick = async (charLayers: HTMLImageElement) => { + console.log("tick"); + const chatBox = document.getElementById("client_chat"); + const waitingBox = document.getElementById("client_chatwaiting"); + const chatBoxInner = document.getElementById("client_inner_chat"); + const charName = chatmsg.name.toLowerCase(); + const charEmote = chatmsg.sprite.toLowerCase(); + + if (chatmsg.content.charAt(textnow.length) !== " ") { + blipChannels[currentBlipChannel].play(); + currentBlipChannel++; + currentBlipChannel %= blipChannels.length; + } + textnow = chatmsg.content.substring(0, textnow.length + 1); + console.log(textnow); + const characterElement = chatmsg.parsed[textnow.length - 1]; + if (characterElement) { + const COMMAND_IDENTIFIER = "\\"; + + const nextCharacterElement = chatmsg.parsed[textnow.length]; + const flash = async () => { + const effectlayer = document.getElementById("client_fg"); + playSFX(`${AO_HOST}sounds/general/sfx-realization.opus`, false); + effectlayer.style.animation = "flash 0.4s 1"; + await delay(400); + effectlayer.style.removeProperty("animation"); + }; + + const shake = async () => { + const gamewindow = document.getElementById("client_gamewindow"); + playSFX(`${AO_HOST}sounds/general/sfx-stab.opus`, false); + gamewindow.style.animation = "shake 0.2s 1"; + await delay(200); + gamewindow.style.removeProperty("animation"); + }; + + const commands = new Map( + Object.entries({ + s: shake, + f: flash, + }) + ); + const textSpeeds = new Set(["{", "}"]); + + // Changing Text Speed + if (textSpeeds.has(characterElement.innerHTML)) { + // Grab them all in a row + const MAX_SLOW_CHATSPEED = 120; + for (let i = textnow.length; i < chatmsg.content.length; i++) { + const currentCharacter = chatmsg.parsed[i - 1].innerHTML; + if (currentCharacter === "}") { + if (chatmsg.speed > 0) { + chatmsg.speed -= 20; + } + } else if (currentCharacter === "{") { + if (chatmsg.speed < MAX_SLOW_CHATSPEED) { + chatmsg.speed += 20; + } + } else { + // No longer at a speed character + textnow = chatmsg.content.substring(0, i); + break; + } + } + } + + if ( + characterElement.innerHTML === COMMAND_IDENTIFIER && + commands.has(nextCharacterElement?.innerHTML) + ) { + textnow = chatmsg.content.substring(0, textnow.length + 1); + await commands.get(nextCharacterElement.innerHTML)(); + } else { + chatBoxInner.appendChild(chatmsg.parsed[textnow.length - 1]); + } + } + console.log("ass"); + // scroll to bottom + chatBox.scrollTop = chatBox.scrollHeight; + + if (textnow === chatmsg.content) { + _animating = false; + setEmote( + AO_HOST, + client, + charName, + charEmote, + "(a)", + false, + chatmsg.side + ); + charLayers.style.opacity = "1"; + waitingBox.style.opacity = "1"; + clearTimeout(updater); + } + }; + /** + * Updates the chatbox based on the given text. + * + * OK, here's the documentation on how this works: + * + * 1 _animating + * If we're not done with this characters animation, i.e. his text isn't fully there, set a timeout for the next tick/step to happen + * + * 2 startpreanim + * If the shout timer is over it starts with the preanim + * The first thing it checks for is the shake effect (TODO on client this is handled by the @ symbol and not a flag ) + * Then is the flash/realization effect + * After that, the shout image set to be transparent + * and the main characters preanim gif is loaded + * If pairing is supported the paired character will just stand around with his idle sprite + * + * 3 preanimdelay over + * this animates the evidence popup and finally shows the character name and message box + * it sets the text color and the character speaking sprite + * + * 4 textnow != content + * this adds a character to the textbox and stops the animations if the entire message is present in the textbox + * + * 5 sfx + * independent of the stuff above, this will play any sound effects specified by the emote the character sent. + * happens after the shout delay + an sfx delay that comes with the message packet + * + * XXX: This relies on a global variable `chatmsg`! + */ + const chat_tick = async () => { + // note: this is called fairly often + // do not perform heavy operations here + + await delay(chatmsg.speed); + console.log("WHERE IS THE CHATMSG"); + console.log(chatmsg); + console.log(textnow + " UH " + chatmsg.content); + if (textnow === chatmsg.content) { + return; + } + + const gamewindow = document.getElementById("client_gamewindow"); + const waitingBox = document.getElementById("client_chatwaiting"); + const eviBox = document.getElementById("client_evi"); + const shoutSprite = ( + document.getElementById("client_shout") + ); + const effectlayer = document.getElementById("client_fg"); + const chatBoxInner = document.getElementById("client_inner_chat"); + let charLayers = document.getElementById("client_char"); + let pairLayers = ( + document.getElementById("client_pair_char") + ); + + const validSides: string[] = ["def", "pro", "wit"]; // these are for the full view pan, the other positions use 'client_char' + if (validSides.includes(chatmsg.side)) { + charLayers = ( + document.getElementById(`client_${chatmsg.side}_char`) + ); + pairLayers = ( + document.getElementById(`client_${chatmsg.side}_pair_char`) + ); + } + + const charName = chatmsg.name.toLowerCase(); + const charEmote = chatmsg.sprite.toLowerCase(); + + const pairName = chatmsg.other_name.toLowerCase(); + const pairEmote = chatmsg.other_emote.toLowerCase(); + + // TODO: preanims sometimes play when they're not supposed to + const isShoutOver = tickTimer >= shoutTimer; + const isShoutAndPreanimOver = + tickTimer >= shoutTimer + chatmsg.preanimdelay; + if (isShoutOver && startFirstTickCheck) { + // Effect stuff + if (chatmsg.screenshake === 1) { + // Shake screen + playSFX(`${AO_HOST}sounds/general/sfx-stab.opus`, false); + gamewindow.style.animation = "shake 0.2s 1"; + } + if (chatmsg.flash === 1) { + // Flash screen + playSFX(`${AO_HOST}sounds/general/sfx-realization.opus`, false); + effectlayer.style.animation = "flash 0.4s 1"; + } + + // Pre-animation stuff + if (chatmsg.preanimdelay > 0) { + shoutSprite.style.opacity = "0"; + shoutSprite.style.animation = ""; + const preanim = chatmsg.preanim.toLowerCase(); + setEmote(AO_HOST, client, charName, preanim, "", false, chatmsg.side); + } + + if (chatmsg.other_name) { + pairLayers.style.opacity = "1"; + } else { + pairLayers.style.opacity = "0"; + } + // Done with first check, move to second + startFirstTickCheck = false; + startSecondTickCheck = true; + + chatmsg.startpreanim = false; + chatmsg.startspeaking = true; + } + + const hasNonInterruptingPreAnim = chatmsg.noninterrupting_preanim === 1; + if (textnow !== chatmsg.content && hasNonInterruptingPreAnim) { + const chatContainerBox = document.getElementById("client_chatcontainer"); + chatContainerBox.style.opacity = "1"; + await handleTextTick(charLayers); + } else if (isShoutAndPreanimOver && startSecondTickCheck) { + if (chatmsg.startspeaking) { + chatmsg.startspeaking = false; + + // Evidence Bullshit + if (chatmsg.evidence > 0) { + // Prepare evidence + eviBox.src = safeTags( + masterClient.evidences[chatmsg.evidence - 1].icon + ); + + eviBox.style.width = "auto"; + eviBox.style.height = "36.5%"; + eviBox.style.opacity = "1"; + + testimonyAudio.src = `${AO_HOST}sounds/general/sfx-evidenceshoop.opus`; + testimonyAudio.play(); + + if (chatmsg.side === "def") { + // Only def show evidence on right + eviBox.style.right = "1em"; + eviBox.style.left = "initial"; + } else { + eviBox.style.right = "initial"; + eviBox.style.left = "1em"; + } + } + chatBoxInner.className = `text_${colors[chatmsg.color]}`; + + if (chatmsg.preanimdelay === 0) { + shoutSprite.style.opacity = "0"; + shoutSprite.style.animation = ""; + } + + switch (Number(chatmsg.deskmod)) { + case 2: + set_side({ + position: chatmsg.side, + showSpeedLines: false, + showDesk: true, + }); + break; + case 3: + set_side({ + position: chatmsg.side, + showSpeedLines: false, + showDesk: false, + }); + break; + case 4: + set_side({ + position: chatmsg.side, + showSpeedLines: false, + showDesk: true, + }); + break; + case 5: + set_side({ + position: chatmsg.side, + showSpeedLines: false, + showDesk: false, + }); + break; + } + + if (chatmsg.other_name) { + setEmote( + AO_HOST, + client, + pairName, + pairEmote, + "(a)", + true, + chatmsg.side + ); + pairLayers.style.opacity = "1"; + } else { + pairLayers.style.opacity = "0"; + } + + setEmote( + AO_HOST, + client, + charName, + charEmote, + "(b)", + false, + chatmsg.side + ); + charLayers.style.opacity = "1"; + + if (textnow === chatmsg.content) { + setEmote( + AO_HOST, + client, + charName, + charEmote, + "(a)", + false, + chatmsg.side + ); + charLayers.style.opacity = "1"; + waitingBox.style.opacity = "1"; + _animating = false; + clearTimeout(updater); + return; + } + } else if (textnow !== chatmsg.content) { + const chatContainerBox = document.getElementById( + "client_chatcontainer" + ); + chatContainerBox.style.opacity = "1"; + await handleTextTick(charLayers); + } + } + + if (!sfxplayed && chatmsg.snddelay + shoutTimer >= tickTimer) { + sfxplayed = 1; + if ( + chatmsg.sound !== "0" && + chatmsg.sound !== "1" && + chatmsg.sound !== "" && + chatmsg.sound !== undefined && + (chatmsg.type == 1 || chatmsg.type == 2 || chatmsg.type == 6) + ) { + playSFX( + `${AO_HOST}sounds/general/${encodeURI( + chatmsg.sound.toLowerCase() + )}.opus`, + chatmsg.looping_sfx + ); + } + } + if (_animating) { + chat_tick(); + } + tickTimer += UPDATE_INTERVAL; + }; + /** + * Triggered by the theme selector. + */ + function reloadTheme() { + theme = (document.getElementById("client_themeselect")) + .value; + + setCookie("theme", theme); + (( + document.getElementById("client_theme") + )).href = `styles/${theme}.css`; + } + window.reloadTheme = reloadTheme; + /** + * Triggered by the blip volume slider. + */ + function changeBlipVolume() { + const blipVolume = (( + document.getElementById("client_bvolume") + )).value; + blipChannels.forEach( + (channel: HTMLAudioElement) => (channel.volume = Number(blipVolume)) + ); + setCookie("blipVolume", blipVolume); + } + window.changeBlipVolume = changeBlipVolume; + + return { + chat_tick, + changeMusicVolume, + changeBlipVolume, + reloadTheme, + playSFX, + set_side, + initTestimonyUpdater, + updateTestimony, + disposeTestimony, + handle_ic_speaking, + handleTextTick, + theme, + chatmsg, + sfxaudio, + blipChannels, + lastChar, + music, + musicVolume, + bgFolder, + bgname, + }; +}; + +export default viewport; -- cgit