aboutsummaryrefslogtreecommitdiff
path: root/webAO
diff options
context:
space:
mode:
authorCaleb Mabry <36182383+caleb-mabry@users.noreply.github.com>2023-02-01 15:54:26 -0500
committerGitHub <noreply@github.com>2023-02-01 15:54:26 -0500
commit39edb1077725a6b9db58b4c32323e847ece5f2f6 (patch)
tree2ab4e9d3c0a007684cb3fb706f70fc677e0f7eca /webAO
parent0eabb5da1759439fd01e54dc986900448f483330 (diff)
parentf8ea065b5e6bccbebb918293e13cab4adb536b70 (diff)
Merge branch 'master' into fix-pipeline
Diffstat (limited to 'webAO')
-rw-r--r--webAO/client.ts489
-rw-r--r--webAO/client/addTrack.ts15
-rw-r--r--webAO/client/appendICLog.ts57
-rw-r--r--webAO/client/checkCallword.ts17
-rw-r--r--webAO/client/createArea.ts30
-rw-r--r--webAO/client/fetchLists.ts77
-rw-r--r--webAO/client/fixLastArea.ts15
-rw-r--r--webAO/client/handleBans.ts17
-rw-r--r--webAO/client/handleCharacterInfo.ts105
-rw-r--r--webAO/client/isAudio.ts6
-rw-r--r--webAO/client/isLowMemory.ts10
-rw-r--r--webAO/client/loadResources.ts81
-rw-r--r--webAO/client/resetICParams.ts21
-rw-r--r--webAO/client/saveChatLogHandle.ts26
-rw-r--r--webAO/client/sender/sendOOC.ts4
-rw-r--r--webAO/client/sender/sendServer.ts1
-rw-r--r--webAO/client/setEmote.ts2
-rw-r--r--webAO/dom/changeBlipVolume.ts15
-rw-r--r--webAO/dom/changeMusicVolume.ts14
-rw-r--r--webAO/dom/iniEdit.ts10
-rw-r--r--webAO/dom/muteListClick.ts19
-rw-r--r--webAO/dom/opusCheck.ts2
-rw-r--r--webAO/dom/reloadTheme.ts16
-rw-r--r--webAO/dom/resizeChatbox.ts1
-rw-r--r--webAO/dom/setChatbox.ts3
-rw-r--r--webAO/dom/showNameClick.ts2
-rw-r--r--webAO/dom/twofactor.ts10
-rw-r--r--webAO/dom/updateIniswap.ts18
-rw-r--r--webAO/dom/window.ts2
-rw-r--r--webAO/encoding.ts20
-rw-r--r--webAO/master.ts11
-rw-r--r--webAO/packets/handlers/handleASS.ts3
-rw-r--r--webAO/packets/handlers/handleBD.ts5
-rw-r--r--webAO/packets/handlers/handleCI.ts3
-rw-r--r--webAO/packets/handlers/handleEM.ts16
-rw-r--r--webAO/packets/handlers/handleFA.ts3
-rw-r--r--webAO/packets/handlers/handleFL.ts11
-rw-r--r--webAO/packets/handlers/handleFM.ts4
-rw-r--r--webAO/packets/handlers/handleKB.ts5
-rw-r--r--webAO/packets/handlers/handleKK.ts5
-rw-r--r--webAO/packets/handlers/handleMC.ts3
-rw-r--r--webAO/packets/handlers/handleMS.ts234
-rw-r--r--webAO/packets/handlers/handleRT.ts4
-rw-r--r--webAO/packets/handlers/handleSC.ts3
-rw-r--r--webAO/packets/handlers/handleSM.ts15
-rw-r--r--webAO/packets/handlers/handleackMS.ts2
-rw-r--r--webAO/packets/packets.ts2
-rw-r--r--webAO/styles/chatbox/999.css (renamed from webAO/styles/chatbox/chat999.css)0
-rw-r--r--webAO/styles/chatbox/chatboxes.js12
-rw-r--r--webAO/styles/chatbox/dd.css (renamed from webAO/styles/chatbox/chatdd.css)0
-rw-r--r--webAO/styles/chatbox/dr2.css (renamed from webAO/styles/chatbox/chatdr2.css)6
-rw-r--r--webAO/styles/chatbox/future.css (renamed from webAO/styles/chatbox/chatfuture.css)0
-rw-r--r--webAO/styles/chatbox/p3.css (renamed from webAO/styles/chatbox/chatp3.css)4
-rw-r--r--webAO/styles/chatbox/p4.css4
-rw-r--r--webAO/styles/chatbox/p5.css4
-rw-r--r--webAO/styles/chatbox/plvsaa.css (renamed from webAO/styles/chatbox/chatplvsaa.css)0
-rw-r--r--webAO/styles/client.css20
-rw-r--r--webAO/viewport.ts1091
-rw-r--r--webAO/viewport/constants/colors.ts12
-rw-r--r--webAO/viewport/constants/defaultChatMsg.ts15
-rw-r--r--webAO/viewport/constants/positions.ts45
-rw-r--r--webAO/viewport/constants/shouts.ts1
-rw-r--r--webAO/viewport/interfaces/ChatMsg.ts34
-rw-r--r--webAO/viewport/interfaces/Desk.ts4
-rw-r--r--webAO/viewport/interfaces/Position.ts7
-rw-r--r--webAO/viewport/interfaces/Positions.ts5
-rw-r--r--webAO/viewport/interfaces/Testimony.ts3
-rw-r--r--webAO/viewport/interfaces/Viewport.ts43
-rw-r--r--webAO/viewport/utils/createBlipChannels.ts15
-rw-r--r--webAO/viewport/utils/createMusic.ts13
-rw-r--r--webAO/viewport/utils/createSfxAudio.ts9
-rw-r--r--webAO/viewport/utils/createShoutAudio.ts9
-rw-r--r--webAO/viewport/utils/createTestimonyAudio.ts9
-rw-r--r--webAO/viewport/utils/handleICSpeaking.ts311
-rw-r--r--webAO/viewport/utils/initTestimonyUpdater.ts31
-rw-r--r--webAO/viewport/utils/setSide.ts90
-rw-r--r--webAO/viewport/viewport.ts495
77 files changed, 1981 insertions, 1745 deletions
diff --git a/webAO/client.ts b/webAO/client.ts
index e92489c..a86664c 100644
--- a/webAO/client.ts
+++ b/webAO/client.ts
@@ -3,35 +3,19 @@
* made by sD, refactored by oldmud0 and Qubrick
* credits to aleks for original idea and source
*/
-
+import { isLowMemory } from './client/isLowMemory'
import FingerprintJS from "@fingerprintjs/fingerprintjs";
-import vanilla_background_arr from "./constants/backgrounds.js";
-import vanilla_evidence_arr from "./constants/evidence.js";
-import {sender, ISender} from './client/sender/index'
-import iniParse from "./iniParse";
-import getCookie from "./utils/getCookie";
-import setCookie from "./utils/setCookie";
-import fileExists from "./utils/fileExists.js";
+import { sender, ISender } from './client/sender/index'
import queryParser from "./utils/queryParser";
import getResources from "./utils/getResources.js";
-import downloadFile from "./services/downloadFile";
-import masterViewport, { Viewport } from "./viewport";
+import masterViewport from "./viewport/viewport";
+import { Viewport } from './viewport/interfaces/Viewport';
import { EventEmitter } from "events";
-import { area_click } from './dom/areaClick'
import { onReplayGo } from './dom/onReplayGo'
-import { escapeChat, safeTags, unescapeChat } from "./encoding";
-import { setChatbox } from "./dom/setChatbox";
-import { request } from "./services/request.js";
-import {
- changeShoutVolume,
- changeSFXVolume,
- changeTestimonyVolume,
-} from "./dom/changeVolume.js";
-import { getFilenameFromPath } from "./utils/paths";
import { packetHandler } from './packets/packetHandler'
-import { showname_click } from './dom/showNameClick'
+import { loadResources } from './client/loadResources'
import { AO_HOST } from './client/aoHost'
-
+import { fetchBackgroundList, fetchEvidenceList, fetchCharacterList, fetchManifest } from './client/fetchLists'
const version = process.env.npm_package_version;
let { ip: serverIP, mode, theme } = queryParser();
@@ -77,38 +61,25 @@ export const setBanned = (val: boolean) => {
}
let hdid: string;
-function isLowMemory() {
- if (
- /webOS|iPod|BlackBerry|BB|PlayBook|IEMobile|Windows Phone|Kindle|Silk|PlayStation|Nintendo|Opera Mini/i.test(
- navigator.userAgent
- )
- ) {
- oldLoading = true;
- }
-}
-
const fpPromise = FingerprintJS.load();
-const connect = (address: string) => {
-}
fpPromise
.then((fp) => fp.get())
.then((result) => {
hdid = result.visitorId;
- console.log("NEW CLIENT");
- // Create the new client and connect it
client = new Client(serverIP);
client.connect()
-
isLowMemory();
- client.loadResources();
+ loadResources();
});
export const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
-let lastICMessageTime = new Date(0);
-
+export let lastICMessageTime = new Date(0);
+export const setLastICMessageTime = (val: Date) => {
+ lastICMessageTime = val
+}
class Client extends EventEmitter {
serv: any;
hp: number[];
@@ -132,8 +103,11 @@ class Client extends EventEmitter {
sender: ISender;
checkUpdater: any;
_lastTimeICReceived: any;
+ manifest: string[];
viewport: Viewport;
connect: () => void;
+ loadResources: () => void
+ isLowMemory: () => void
constructor(address: string) {
super();
@@ -168,13 +142,16 @@ class Client extends EventEmitter {
this.musics = [];
this.musics_time = false;
this.callwords = [];
+ this.manifest = [];
this.resources = getResources(AO_HOST, THEME);
this.selectedEmote = -1;
this.selectedEvidence = 0;
this.checkUpdater = null;
this.sender = sender
- this.viewport = masterViewport(this);
+ this.viewport = masterViewport();
this._lastTimeICReceived = new Date(0);
+ loadResources
+ isLowMemory
}
/**
@@ -200,8 +177,6 @@ class Client extends EventEmitter {
: 0;
}
-
-
/**
* Hook for sending messages to the client
* @param {string} message the message to send
@@ -216,87 +191,14 @@ class Client extends EventEmitter {
* to the server.
*/
joinServer() {
- console.log(this.sender)
this.sender.sendServer(`HI#${hdid}#%`);
- this.sender.sendServer("ID#webAO#webAO#%");
+ this.sender.sendServer(`ID#webAO#${version}#%`);
if (mode !== "replay") {
this.checkUpdater = setInterval(() => this.sender.sendCheck(), 5000);
}
}
/**
- * Load game resources and stored settings.
- */
- loadResources() {
- document.getElementById("client_version").innerText = `version ${version}`;
-
- // Load background array to select
- const background_select = <HTMLSelectElement>(
- 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 = <HTMLSelectElement>(
- 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
- (<HTMLInputElement>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";
-
- (<HTMLOptionElement>(
- document.querySelector(`#client_themeselect [value="${cookietheme}"]`)
- )).selected = true;
- this.viewport.reloadTheme();
-
- const cookiechatbox = getCookie("chatbox") || "dynamic";
-
- (<HTMLOptionElement>(
- document.querySelector(`#client_chatboxselect [value="${cookiechatbox}"]`)
- )).selected = true;
- setChatbox(cookiechatbox);
-
- (<HTMLInputElement>document.getElementById("client_mvolume")).value =
- getCookie("musicVolume") || "1";
- this.viewport.changeMusicVolume();
- (<HTMLAudioElement>document.getElementById("client_sfxaudio")).volume =
- Number(getCookie("sfxVolume")) || 1;
- changeSFXVolume();
- (<HTMLAudioElement>document.getElementById("client_shoutaudio")).volume =
- Number(getCookie("shoutVolume")) || 1;
- changeShoutVolume();
- (<HTMLAudioElement>(
- document.getElementById("client_testimonyaudio")
- )).volume = Number(getCookie("testimonyVolume")) || 1;
- changeTestimonyVolume();
- (<HTMLInputElement>document.getElementById("client_bvolume")).value =
- getCookie("blipVolume") || "1";
- this.viewport.changeBlipVolume();
-
- (<HTMLInputElement>document.getElementById("ic_chat_name")).value =
- getCookie("ic_chat_name");
- (<HTMLInputElement>document.getElementById("showname")).checked = Boolean(
- getCookie("showname")
- );
- showname_click(null);
-
- (<HTMLInputElement>document.getElementById("client_callwords")).value =
- getCookie("callwords");
- }
-
- /**
* Triggered when a connection is established to the server.
*/
onOpen(_e: Event) {
@@ -335,7 +237,6 @@ class Client extends EventEmitter {
packetHandler.has(packetHeader)
? packetHandler.get(packetHeader)(splitPacket)
: console.warn(`Invalid packet header ${packetHeader}`);
-
}
/**
@@ -353,7 +254,6 @@ class Client extends EventEmitter {
*/
cleanup() {
clearInterval(this.checkUpdater);
-
this.serv.close();
}
@@ -383,129 +283,6 @@ class Client extends EventEmitter {
}
}
- saveChatlogHandle = async () => {
- 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 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);
-
- const filename = `chatlog-${da}-${mo}-${ye}`.toLowerCase();
- downloadFile(messages.join("\n"), filename);
-
- // Reset Chatbox to Empty
- (<HTMLInputElement>document.getElementById("client_inputbox")).value = "";
- };
-
- /**
- * 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 = <HTMLImageElement>document.getElementById(`demo_${charid}`);
- if (chargs[0]) {
- let cini: any = {};
- const getCharIcon = async () => {
- const extensions = [".png", ".webp"];
- img.alt = chargs[0];
- 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);
- if (exists) {
- img.alt = chargs[0];
- img.title = chargs[0];
- img.src = fileUrl;
- return;
- }
- }
- };
- getCharIcon();
-
- // 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`
- );
- cini = iniParse(cinidata);
- } catch (err) {
- cini = {};
- 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 = <HTMLSelectElement>(
- document.getElementById("mute_select")
- );
- mute_select.add(new Option(safeTags(chargs[0]), String(charid)));
- const pair_select = <HTMLSelectElement>(
- 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: "",
- };
- cini.options = Object.assign(default_options, cini.options);
-
- // sometimes ini files lack important settings
- const default_emotions = {
- number: 0,
- };
- cini.emotions = Object.assign(default_emotions, cini.emotions);
-
- this.chars[charid] = {
- name: safeTags(chargs[0]),
- showname: safeTags(cini.options.showname),
- desc: safeTags(chargs[1]),
- 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(),
- 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;
- }
-
- } else {
- console.warn(`missing charid ${charid}`);
- img.style.display = "none";
- }
- }
-
resetMusicList() {
this.musics = [];
document.getElementById("client_musiclist").innerHTML = "";
@@ -515,231 +292,11 @@ class Client extends EventEmitter {
this.areas = [];
document.getElementById("areas").innerHTML = "";
- this.fetchBackgroundList();
- this.fetchEvidenceList();
- }
-
- async fetchBackgroundList() {
- try {
- const bgdata = await request(`${AO_HOST}backgrounds.json`);
- const bg_array = JSON.parse(bgdata);
- // the try catch will fail before here when there is no file
-
- const bg_select = <HTMLSelectElement>document.getElementById("bg_select");
- bg_select.innerHTML = "";
-
- 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");
- }
- }
-
- async fetchCharacterList() {
- try {
- const chardata = await request(`${AO_HOST}characters.json`);
- const char_array = JSON.parse(chardata);
- // the try catch will fail before here when there is no file
-
- const char_select = <HTMLSelectElement>(
- 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");
- }
- }
-
- async fetchEvidenceList() {
- try {
- const evidata = await request(`${AO_HOST}evidence.json`);
- const evi_array = JSON.parse(evidata);
- // the try catch will fail before here when there is no file
-
- const evi_select = <HTMLSelectElement>(
- 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"));
- } catch (err) {
- 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
- );
+ fetchBackgroundList();
+ fetchEvidenceList();
+ fetchCharacterList();
}
- addTrack(trackname: string) {
- const newentry = <HTMLOptionElement>document.createElement("OPTION");
- const songName = getFilenameFromPath(trackname);
- newentry.text = unescapeChat(songName);
- newentry.value = trackname;
- (<HTMLSelectElement>(
- document.getElementById("client_musiclist")
- )).options.add(newentry);
- this.musics.push(trackname);
- }
-
- createArea(id: number, name: string) {
- const thisarea = {
- name,
- players: 0,
- status: "IDLE",
- cm: "",
- locked: "FREE",
- };
-
- this.areas.push(thisarea);
-
- // Create area button
- 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.onclick = function () {
- area_click(newarea);
- };
-
- document.getElementById("areas").appendChild(newarea);
- }
-
- /**
- * Area list fuckery
- */
- fix_last_area() {
- if (this.areas.length > 0) {
- const malplaced = this.areas.pop().name;
- const areas = document.getElementById("areas");
- areas.removeChild(areas.lastChild);
- this.addTrack(malplaced);
- }
- }
-
- /**
- * 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}:<br>${reason.replace(/\n/g, "<br />")}`;
- (<HTMLElement>(
- document.getElementsByClassName("client_reconnect")[0]
- )).style.display = "none";
- (<HTMLElement>(
- document.getElementsByClassName("client_reconnect")[1]
- )).style.display = "none";
- }
-}
-
-/**
- * Resets the IC parameters for the player to enter a new chat message.
- * This should only be called when the player's previous chat message
- * was successfully sent/presented.
- */
-export function resetICParams() {
- (<HTMLInputElement>document.getElementById("client_inputbox")).value = "";
- document.getElementById("button_flash").className = "client_button";
- document.getElementById("button_shake").className = "client_button";
-
- (<HTMLInputElement>document.getElementById("sendpreanim")).checked = false;
- (<HTMLInputElement>document.getElementById("sendsfx")).checked = false;
-
- if (selectedShout) {
- document.getElementById(`button_${selectedShout}`).className =
- "client_button";
- selectedShout = 0;
- }
-}
-
-/**
- * Appends a message to the in-character chat log.
- * @param {string} msg the string to be added
- * @param {string} name the name of the sender
- */
-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));
- }
-
- textField.className = "iclog_text";
- textField.appendChild(document.createTextNode(msg));
-
- entry.appendChild(shownameField);
- entry.appendChild(nameplateField);
- entry.appendChild(textField);
-
- // Only put a timestamp if the minute has changed.
- if (lastICMessageTime.getMinutes() !== time.getMinutes()) {
- const timeStamp = document.createElement("span");
- timeStamp.className = "iclog_time";
- timeStamp.innerText = time.toLocaleTimeString(undefined, {
- hour: "numeric",
- minute: "2-digit",
- });
- entry.appendChild(timeStamp);
- }
-
- const clientLog = document.getElementById("client_log");
- clientLog.appendChild(entry);
-
- /* This is a little buggy - some troubleshooting might be desirable */
- if (clientLog.scrollTop > clientLog.scrollHeight - 800) {
- clientLog.scrollTop = clientLog.scrollHeight;
- }
-
- lastICMessageTime = new Date();
-}
-
-/**
- * check if the message contains an entry on our callword list
- * @param {string} message
- */
-export function checkCallword(message: string, sfxAudio: HTMLAudioElement) {
- client.callwords.forEach(testCallword);
- function testCallword(item: string) {
- if (item !== "" && message.toLowerCase().includes(item.toLowerCase())) {
- sfxAudio.pause();
- sfxAudio.src = `${AO_HOST}sounds/general/sfx-gallery.opus`;
- sfxAudio.play();
- }
- }
}
-export default Client; \ No newline at end of file
+export default Client;
diff --git a/webAO/client/addTrack.ts b/webAO/client/addTrack.ts
new file mode 100644
index 0000000..247f07e
--- /dev/null
+++ b/webAO/client/addTrack.ts
@@ -0,0 +1,15 @@
+import { client } from "../client";
+import { unescapeChat } from "../encoding";
+import { getFilenameFromPath } from "../utils/paths";
+
+
+export const addTrack = (trackname: string) => {
+ const newentry = <HTMLOptionElement>document.createElement("OPTION");
+ const songName = getFilenameFromPath(trackname);
+ newentry.text = unescapeChat(songName);
+ newentry.value = trackname;
+ (<HTMLSelectElement>(
+ document.getElementById("client_musiclist")
+ )).options.add(newentry);
+ client.musics.push(trackname);
+} \ No newline at end of file
diff --git a/webAO/client/appendICLog.ts b/webAO/client/appendICLog.ts
new file mode 100644
index 0000000..f8b7852
--- /dev/null
+++ b/webAO/client/appendICLog.ts
@@ -0,0 +1,57 @@
+import { lastICMessageTime, setLastICMessageTime } from "../client";
+
+
+
+/**
+ * Appends a message to the in-character chat log.
+ * @param {string} msg the string to be added
+ * @param {string} name the name of the sender
+ */
+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));
+ }
+
+ textField.className = "iclog_text";
+ textField.appendChild(document.createTextNode(msg));
+
+ entry.appendChild(shownameField);
+ entry.appendChild(nameplateField);
+ entry.appendChild(textField);
+
+ // Only put a timestamp if the minute has changed.
+ if (lastICMessageTime.getMinutes() !== time.getMinutes()) {
+ const timeStamp = document.createElement("span");
+ timeStamp.className = "iclog_time";
+ timeStamp.innerText = time.toLocaleTimeString(undefined, {
+ hour: "numeric",
+ minute: "2-digit",
+ });
+ entry.appendChild(timeStamp);
+ }
+
+ const clientLog = document.getElementById("client_log")!;
+ clientLog.appendChild(entry);
+
+ /* This is a little buggy - some troubleshooting might be desirable */
+ if (clientLog.scrollTop > clientLog.scrollHeight - 800) {
+ clientLog.scrollTop = clientLog.scrollHeight;
+ }
+
+ setLastICMessageTime(new Date());
+} \ No newline at end of file
diff --git a/webAO/client/checkCallword.ts b/webAO/client/checkCallword.ts
new file mode 100644
index 0000000..f6cffbc
--- /dev/null
+++ b/webAO/client/checkCallword.ts
@@ -0,0 +1,17 @@
+import { client } from "../client";
+import { AO_HOST } from "./aoHost";
+
+/**
+ * check if the message contains an entry on our callword list
+ * @param {string} message
+ */
+export function checkCallword(message: string, sfxAudio: HTMLAudioElement) {
+ client.callwords.forEach(testCallword);
+ function testCallword(item: string) {
+ if (item !== "" && message.toLowerCase().includes(item.toLowerCase())) {
+ sfxAudio.pause();
+ sfxAudio.src = `${AO_HOST}sounds/general/sfx-gallery.opus`;
+ sfxAudio.play();
+ }
+ }
+} \ No newline at end of file
diff --git a/webAO/client/createArea.ts b/webAO/client/createArea.ts
new file mode 100644
index 0000000..63af644
--- /dev/null
+++ b/webAO/client/createArea.ts
@@ -0,0 +1,30 @@
+import { client } from "../client";
+import { area_click } from "../dom/areaClick";
+
+export const createArea = (id: number, name: string) => {
+ const thisarea = {
+ name,
+ players: 0,
+ status: "IDLE",
+ cm: "",
+ locked: "FREE",
+ };
+
+ client.areas.push(thisarea);
+
+ // Create area button
+ 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.onclick = function () {
+ area_click(newarea);
+ };
+
+ document.getElementById("areas")!.appendChild(newarea);
+} \ No newline at end of file
diff --git a/webAO/client/fetchLists.ts b/webAO/client/fetchLists.ts
new file mode 100644
index 0000000..9efd181
--- /dev/null
+++ b/webAO/client/fetchLists.ts
@@ -0,0 +1,77 @@
+import { client } from "../client";
+import { AO_HOST } from "./aoHost";
+import { request } from "../services/request.js";
+
+export const fetchBackgroundList = async () => {
+ try {
+ const bgdata = await request(`${AO_HOST}backgrounds.json`);
+ const bg_array = JSON.parse(bgdata);
+ // the try catch will fail before here when there is no file
+
+ const bg_select = <HTMLSelectElement>document.getElementById("bg_select");
+ bg_select.innerHTML = "";
+
+ 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");
+ }
+}
+
+export const fetchCharacterList = async () => {
+ const char_select = <HTMLSelectElement>(
+ document.getElementById("client_iniselect")
+ );
+ char_select.innerHTML = "";
+
+ char_select.add(new Option("Custom", "0"));
+
+ try {
+ const chardata = await request(`${AO_HOST}characters.json`);
+ const char_array = JSON.parse(chardata);
+ // the try catch will fail before here when there is no file
+
+ char_array.forEach((character: string) => {
+ char_select.add(new Option(character));
+ });
+ } catch (err) {
+ console.warn("there was no characters.json file");
+ }
+}
+
+
+export const fetchEvidenceList = async () => {
+ const evi_select = <HTMLSelectElement>(
+ document.getElementById("evi_select")
+ );
+ evi_select.innerHTML = "";
+
+ evi_select.add(new Option("Custom", "0"));
+
+ try {
+ const evidata = await request(`${AO_HOST}evidence.json`);
+ const evi_array = JSON.parse(evidata);
+ // the try catch will fail before here when there is no file
+
+ evi_array.forEach((evi: string) => {
+ evi_select.add(new Option(evi));
+ });
+
+ } catch (err) {
+ console.warn("there was no evidence.json file");
+ }
+}
+
+
+export const fetchManifest = async () => {
+ try {
+ const manifestdata = await request(`${AO_HOST}manifest.txt`);
+ client.manifest = manifestdata.split(/\r\n|\n\r|\n|\r/);
+ // the try catch will fail before here when there is no file
+
+ } catch (err) {
+ console.warn("there was no manifest.txt file");
+ }
+} \ No newline at end of file
diff --git a/webAO/client/fixLastArea.ts b/webAO/client/fixLastArea.ts
new file mode 100644
index 0000000..f1aa99f
--- /dev/null
+++ b/webAO/client/fixLastArea.ts
@@ -0,0 +1,15 @@
+import { client } from "../client";
+import { addTrack } from "./addTrack";
+
+
+/**
+ * Area list fuckery
+ */
+export const fix_last_area = () => {
+ if (client.areas.length > 0) {
+ const malplaced = client.areas.pop().name;
+ const areas = document.getElementById("areas")!;
+ areas.removeChild(areas.lastChild);
+ addTrack(malplaced);
+ }
+} \ No newline at end of file
diff --git a/webAO/client/handleBans.ts b/webAO/client/handleBans.ts
new file mode 100644
index 0000000..b977fc8
--- /dev/null
+++ b/webAO/client/handleBans.ts
@@ -0,0 +1,17 @@
+/**
+ * Handles the kicked packet
+ * @param {string} type is it a kick or a ban
+ * @param {string} reason why
+ */
+export const handleBans = (type: string, reason: string) => {
+ document.getElementById("client_error")!.style.display = "flex";
+ document.getElementById(
+ "client_errortext"
+ )!.innerHTML = `${type}:<br>${reason.replace(/\n/g, "<br />")}`;
+ (<HTMLElement>(
+ document.getElementsByClassName("client_reconnect")[0]
+ )).style.display = "none";
+ (<HTMLElement>(
+ document.getElementsByClassName("client_reconnect")[1]
+ )).style.display = "none";
+} \ No newline at end of file
diff --git a/webAO/client/handleCharacterInfo.ts b/webAO/client/handleCharacterInfo.ts
new file mode 100644
index 0000000..9d74a8b
--- /dev/null
+++ b/webAO/client/handleCharacterInfo.ts
@@ -0,0 +1,105 @@
+import { client } from "../client";
+import { safeTags } from "../encoding";
+import iniParse from "../iniParse";
+import request from "../services/request";
+import fileExists from "../utils/fileExists";
+import { AO_HOST } from "./aoHost";
+
+
+/**
+ * Handles the incoming character information, and downloads the sprite + ini for it
+ * @param {Array} chargs packet arguments
+ * @param {Number} charid character ID
+ */
+export const handleCharacterInfo = async (chargs: string[], charid: number) => {
+ const img = <HTMLImageElement>document.getElementById(`demo_${charid}`);
+ if (chargs[0]) {
+ let cini: any = {};
+ const getCharIcon = async () => {
+ const extensions = [".png", ".webp"];
+ img.alt = chargs[0];
+ 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);
+ if (exists) {
+ img.alt = chargs[0];
+ img.title = chargs[0];
+ img.src = fileUrl;
+ return;
+ }
+ }
+ };
+ getCharIcon();
+
+ // 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`
+ );
+ cini = iniParse(cinidata);
+ } catch (err) {
+ cini = {};
+ 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 = <HTMLSelectElement>(
+ document.getElementById("mute_select")
+ );
+ mute_select.add(new Option(safeTags(chargs[0]), String(charid)));
+ const pair_select = <HTMLSelectElement>(
+ 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: "",
+ };
+ cini.options = Object.assign(default_options, cini.options);
+
+ // sometimes ini files lack important settings
+ const default_emotions = {
+ number: 0,
+ };
+ cini.emotions = Object.assign(default_emotions, cini.emotions);
+
+ client.chars[charid] = {
+ name: safeTags(chargs[0]),
+ showname: safeTags(cini.options.showname),
+ desc: safeTags(chargs[1]),
+ 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(),
+ evidence: chargs[3],
+ icon: img.src,
+ inifile: cini,
+ muted: false,
+ };
+
+ if (
+ client.chars[charid].blips === "male" &&
+ client.chars[charid].gender !== "male" &&
+ client.chars[charid].gender !== ""
+ ) {
+ client.chars[charid].blips = client.chars[charid].gender;
+ }
+
+ } else {
+ console.warn(`missing charid ${charid}`);
+ img.style.display = "none";
+ }
+} \ No newline at end of file
diff --git a/webAO/client/isAudio.ts b/webAO/client/isAudio.ts
new file mode 100644
index 0000000..430f543
--- /dev/null
+++ b/webAO/client/isAudio.ts
@@ -0,0 +1,6 @@
+export const isAudio = (trackname: string) => {
+ const audioEndings = [".wav", ".mp3", ".ogg", ".opus"];
+ return (
+ audioEndings.filter((ending) => trackname.endsWith(ending)).length === 1
+ );
+} \ No newline at end of file
diff --git a/webAO/client/isLowMemory.ts b/webAO/client/isLowMemory.ts
new file mode 100644
index 0000000..caa6784
--- /dev/null
+++ b/webAO/client/isLowMemory.ts
@@ -0,0 +1,10 @@
+import { setOldLoading } from '../client'
+export const isLowMemory = () => {
+ if (
+ /webOS|iPod|BlackBerry|BB|PlayBook|IEMobile|Windows Phone|Kindle|Silk|PlayStation|Nintendo|Opera Mini/i.test(
+ navigator.userAgent
+ )
+ ) {
+ setOldLoading(true);
+ }
+}
diff --git a/webAO/client/loadResources.ts b/webAO/client/loadResources.ts
new file mode 100644
index 0000000..4954966
--- /dev/null
+++ b/webAO/client/loadResources.ts
@@ -0,0 +1,81 @@
+import getCookie from "../utils/getCookie";
+import vanilla_evidence_arr from "../constants/evidence.js";
+import vanilla_background_arr from "../constants/backgrounds.js";
+import { changeMusicVolume } from '../dom/changeMusicVolume'
+import { setChatbox } from "../dom/setChatbox";
+import { changeSFXVolume, changeShoutVolume, changeTestimonyVolume } from "../dom/changeVolume";
+import { showname_click } from "../dom/showNameClick";
+import { changeBlipVolume } from '../dom/changeBlipVolume'
+import { reloadTheme } from '../dom/reloadTheme'
+const version = process.env.npm_package_version;
+
+/**
+ * Load game resources and stored settings.
+ */
+export const loadResources = () => {
+ document.getElementById("client_version")!.innerText = `version ${version}`;
+ // Load background array to select
+ const background_select = <HTMLSelectElement>(
+ 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 = <HTMLSelectElement>(
+ 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
+ (<HTMLInputElement>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";
+
+ (<HTMLOptionElement>(
+ document.querySelector(`#client_themeselect [value="${cookietheme}"]`)
+ )).selected = true;
+ reloadTheme();
+
+ const cookiechatbox = getCookie("chatbox") || "dynamic";
+
+ (<HTMLOptionElement>(
+ document.querySelector(`#client_chatboxselect [value="${cookiechatbox}"]`)
+ )).selected = true;
+ setChatbox(cookiechatbox);
+
+ (<HTMLInputElement>document.getElementById("client_mvolume")).value =
+ getCookie("musicVolume") || "1";
+ changeMusicVolume();
+ (<HTMLAudioElement>document.getElementById("client_sfxaudio")).volume =
+ Number(getCookie("sfxVolume")) || 1;
+ changeSFXVolume();
+ (<HTMLAudioElement>document.getElementById("client_shoutaudio")).volume =
+ Number(getCookie("shoutVolume")) || 1;
+ changeShoutVolume();
+ (<HTMLAudioElement>(
+ document.getElementById("client_testimonyaudio")
+ )).volume = Number(getCookie("testimonyVolume")) || 1;
+ changeTestimonyVolume();
+ (<HTMLInputElement>document.getElementById("client_bvolume")).value =
+ getCookie("blipVolume") || "1";
+ changeBlipVolume();
+
+ (<HTMLInputElement>document.getElementById("ic_chat_name")).value =
+ getCookie("ic_chat_name");
+ (<HTMLInputElement>document.getElementById("showname")).checked = Boolean(
+ getCookie("showname")
+ );
+ showname_click(null);
+
+ (<HTMLInputElement>document.getElementById("client_callwords")).value =
+ getCookie("callwords");
+} \ No newline at end of file
diff --git a/webAO/client/resetICParams.ts b/webAO/client/resetICParams.ts
new file mode 100644
index 0000000..414da27
--- /dev/null
+++ b/webAO/client/resetICParams.ts
@@ -0,0 +1,21 @@
+import { selectedShout, setSelectedShout } from "../client";
+
+/**
+ * Resets the IC parameters for the player to enter a new chat message.
+ * This should only be called when the player's previous chat message
+ * was successfully sent/presented.
+ */
+export function resetICParams() {
+ (<HTMLInputElement>document.getElementById("client_inputbox")).value = "";
+ document.getElementById("button_flash")!.className = "client_button";
+ document.getElementById("button_shake")!.className = "client_button";
+
+ (<HTMLInputElement>document.getElementById("sendpreanim")).checked = false;
+ (<HTMLInputElement>document.getElementById("sendsfx")).checked = false;
+
+ if (selectedShout) {
+ document.getElementById(`button_${selectedShout}`)!.className =
+ "client_button";
+ setSelectedShout(0);
+ }
+} \ No newline at end of file
diff --git a/webAO/client/saveChatLogHandle.ts b/webAO/client/saveChatLogHandle.ts
new file mode 100644
index 0000000..bcc1075
--- /dev/null
+++ b/webAO/client/saveChatLogHandle.ts
@@ -0,0 +1,26 @@
+import downloadFile from "../services/downloadFile";
+
+export const saveChatlogHandle = async () => {
+ const clientLog = document.getElementById("client_log")!;
+ const icMessageLogs = clientLog.getElementsByTagName("p");
+ const messages: string[] = [];
+
+ 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 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);
+
+ const filename = `chatlog-${da}-${mo}-${ye}`.toLowerCase();
+ downloadFile(messages.join("\n"), filename);
+
+ // Reset Chatbox to Empty
+ (<HTMLInputElement>document.getElementById("client_inputbox")).value = "";
+}; \ No newline at end of file
diff --git a/webAO/client/sender/sendOOC.ts b/webAO/client/sender/sendOOC.ts
index a410b5f..9674ad9 100644
--- a/webAO/client/sender/sendOOC.ts
+++ b/webAO/client/sender/sendOOC.ts
@@ -1,7 +1,7 @@
import { client } from '../../client'
import { escapeChat } from '../../encoding';
import setCookie from '../../utils/setCookie';
-
+import { saveChatlogHandle } from '../../client/saveChatLogHandle'
/**
* Sends an out-of-character chat message.
* @param {string} message the message to send
@@ -17,7 +17,7 @@ export const sendOOC = (message: string) => {
const oocMessage = `${escapeChat(message)}`;
const commands = {
- "/save_chatlog": client.saveChatlogHandle,
+ "/save_chatlog": saveChatlogHandle,
};
const commandsMap = new Map(Object.entries(commands));
diff --git a/webAO/client/sender/sendServer.ts b/webAO/client/sender/sendServer.ts
index a9da3bd..7678381 100644
--- a/webAO/client/sender/sendServer.ts
+++ b/webAO/client/sender/sendServer.ts
@@ -6,5 +6,6 @@ let { mode } = queryParser()
* @param {string} message the message to send
*/
export const sendServer = (message: string) => {
+ console.debug("C: "+message)
mode === "replay" ? client.sender.sendSelf(message) : client.serv.send(message);
} \ No newline at end of file
diff --git a/webAO/client/setEmote.ts b/webAO/client/setEmote.ts
index 161eb51..f4fbdbb 100644
--- a/webAO/client/setEmote.ts
+++ b/webAO/client/setEmote.ts
@@ -28,7 +28,7 @@ const setEmote = async (
for (const extension of extensionsMap) {
// Hides all sprites before creating a new sprite
- if (client.viewport.lastChar !== client.viewport.chatmsg.name) {
+ if (client.viewport.getLastCharacter() !== client.viewport.getChatmsg().name) {
emoteSelector.src = transparentPng;
}
let url;
diff --git a/webAO/dom/changeBlipVolume.ts b/webAO/dom/changeBlipVolume.ts
new file mode 100644
index 0000000..572f389
--- /dev/null
+++ b/webAO/dom/changeBlipVolume.ts
@@ -0,0 +1,15 @@
+import setCookie from "../utils/setCookie";
+import { client } from '../client'
+/**
+ * Triggered by the blip volume slider.
+ */
+export const changeBlipVolume = () => {
+ const blipVolume = (<HTMLInputElement>(
+ document.getElementById("client_bvolume")
+ )).value;
+ client.viewport.blipChannels.forEach(
+ (channel: HTMLAudioElement) => (channel.volume = Number(blipVolume))
+ );
+ setCookie("blipVolume", blipVolume);
+}
+window.changeBlipVolume = changeBlipVolume;
diff --git a/webAO/dom/changeMusicVolume.ts b/webAO/dom/changeMusicVolume.ts
new file mode 100644
index 0000000..9e5d51a
--- /dev/null
+++ b/webAO/dom/changeMusicVolume.ts
@@ -0,0 +1,14 @@
+import { client } from '../client'
+import setCookie from '../utils/setCookie';
+
+export const changeMusicVolume = (volume: number = -1) => {
+ const clientVolume = Number(
+ (<HTMLInputElement>document.getElementById("client_mvolume")).value
+ );
+ let musicVolume = volume === -1 ? clientVolume : volume;
+ client.viewport.music.forEach(
+ (channel: HTMLAudioElement) => (channel.volume = musicVolume)
+ );
+ setCookie("musicVolume", String(musicVolume));
+};
+window.changeMusicVolume = changeMusicVolume; \ No newline at end of file
diff --git a/webAO/dom/iniEdit.ts b/webAO/dom/iniEdit.ts
index 359a226..b26c179 100644
--- a/webAO/dom/iniEdit.ts
+++ b/webAO/dom/iniEdit.ts
@@ -1,14 +1,18 @@
import { client } from "../client";
+import { handleCharacterInfo } from "../client/handleCharacterInfo";
import { packetHandler } from "../packets/packetHandler";
/**
* Triggered by the ini button.
*/
export async function iniedit() {
- const ininame = (<HTMLInputElement>document.getElementById("client_ininame"))
- .value;
+ const iniselect = (<HTMLSelectElement>document.getElementById("client_iniselect"))
+ const ininame = (<HTMLInputElement>document.getElementById("client_ininame"));
const inicharID = client.charID;
- await client.handleCharacterInfo(ininame.split("&"), inicharID);
+
+ const newname = iniselect.selectedIndex === 0 ? ininame.value : iniselect.value;
+
+ await handleCharacterInfo(newname.split("&"), inicharID);
packetHandler.get("PV")!(`PV#0#CID#${inicharID}`.split("#"));
}
window.iniedit = iniedit;
diff --git a/webAO/dom/muteListClick.ts b/webAO/dom/muteListClick.ts
new file mode 100644
index 0000000..e7c9357
--- /dev/null
+++ b/webAO/dom/muteListClick.ts
@@ -0,0 +1,19 @@
+import { client } from "../client";
+/**
+ * Triggered when a character in the mute list is clicked
+ * @param {MouseEvent} event
+ */
+export function mutelist_click(_event: Event) {
+ const mutelist = <HTMLSelectElement>(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)`;
+ console.info(`muted ${client.chars[selected_character.value].name}`);
+ } else {
+ client.chars[selected_character.value].muted = false;
+ selected_character.text = client.chars[selected_character.value].name;
+ }
+}
+window.mutelist_click = mutelist_click; \ No newline at end of file
diff --git a/webAO/dom/opusCheck.ts b/webAO/dom/opusCheck.ts
index 939fdc6..5f0248d 100644
--- a/webAO/dom/opusCheck.ts
+++ b/webAO/dom/opusCheck.ts
@@ -9,7 +9,7 @@ export function opusCheck(
if (audio === "") {
return;
}
- console.info(`failed to load sound ${channel.src}`);
+ console.warn(`failed to load sound ${channel.src}`);
let oldsrc = "";
let newsrc = "";
oldsrc = channel.src;
diff --git a/webAO/dom/reloadTheme.ts b/webAO/dom/reloadTheme.ts
new file mode 100644
index 0000000..bfa46b6
--- /dev/null
+++ b/webAO/dom/reloadTheme.ts
@@ -0,0 +1,16 @@
+import { client } from '../client'
+import setCookie from '../utils/setCookie';
+
+/**
+ * Triggered by the theme selector.
+ */
+export const reloadTheme = () => {
+ client.viewport.setTheme((<HTMLSelectElement>document.getElementById("client_themeselect"))
+ .value);
+
+ setCookie("theme", client.viewport.getTheme());
+ (<HTMLAnchorElement>(
+ document.getElementById("client_theme")
+ )).href = `styles/${client.viewport.getTheme()}.css`;
+}
+window.reloadTheme = reloadTheme; \ No newline at end of file
diff --git a/webAO/dom/resizeChatbox.ts b/webAO/dom/resizeChatbox.ts
index efb8bdc..887877d 100644
--- a/webAO/dom/resizeChatbox.ts
+++ b/webAO/dom/resizeChatbox.ts
@@ -17,7 +17,6 @@ export function resizeChatbox() {
let weekday = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
const month = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
document.getElementById("client_clock_month")!.innerText = month[now.getMonth()];
- console.debug(CHATBOX);
if (CHATBOX == "acww") {
weekday = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
document.getElementById("client_clock_weekday")!.innerText = weekday[now.getDay()];
diff --git a/webAO/dom/setChatbox.ts b/webAO/dom/setChatbox.ts
index 6d1a78c..095ead3 100644
--- a/webAO/dom/setChatbox.ts
+++ b/webAO/dom/setChatbox.ts
@@ -5,7 +5,7 @@ import setCookie from "../utils/setCookie";
/**
* Set the style of the chatbox
*/
-export function setChatbox(style: string) {
+export function setChatbox(setstyle: string) {
const chatbox_theme = <HTMLAnchorElement>(
document.getElementById("chatbox_theme")
);
@@ -16,6 +16,7 @@ export function setChatbox(style: string) {
setCookie("chatbox", CHATBOX);
if (CHATBOX === "dynamic") {
+ const style = setstyle.replace("chat","");
if (chatbox_arr.includes(style)) {
chatbox_theme.href = `styles/chatbox/${style}.css`;
} else {
diff --git a/webAO/dom/showNameClick.ts b/webAO/dom/showNameClick.ts
index 265f6c8..3e48b70 100644
--- a/webAO/dom/showNameClick.ts
+++ b/webAO/dom/showNameClick.ts
@@ -5,7 +5,7 @@ import setCookie from "../utils/setCookie";
* Triggered when the showname checkboc is clicked
* @param {MouseEvent} event
*/
-export function showname_click(_event: Event) {
+export function showname_click(_event: Event | null) {
setCookie(
"showname",
String((<HTMLInputElement>document.getElementById("showname")).checked)
diff --git a/webAO/dom/twofactor.ts b/webAO/dom/twofactor.ts
new file mode 100644
index 0000000..b7e947a
--- /dev/null
+++ b/webAO/dom/twofactor.ts
@@ -0,0 +1,10 @@
+import { client } from "../client";
+
+function handleCredentialResponse(response: any) {
+ client.sender.sendServer(`2T#${response.credential}#%`);
+ }
+window.handleCredentialResponse = handleCredentialResponse;
+
+export function showFactorDialog(args: string[]) {
+ document.getElementById("client_secondfactor").style.display = args[1];
+} \ No newline at end of file
diff --git a/webAO/dom/updateIniswap.ts b/webAO/dom/updateIniswap.ts
new file mode 100644
index 0000000..5bea0f5
--- /dev/null
+++ b/webAO/dom/updateIniswap.ts
@@ -0,0 +1,18 @@
+/**
+ * Update iniswap drowdown
+ */
+export function updateIniswap() {
+ const ini_select = <HTMLSelectElement>(
+ document.getElementById("client_iniselect")
+ );
+ const ini_name = <HTMLInputElement>(
+ document.getElementById("client_ininame")
+ );
+
+ if (ini_select.selectedIndex === 0) {
+ ini_name.style.display = "initial";
+ } else {
+ ini_name.style.display = "none";
+ }
+}
+window.updateIniswap = updateIniswap;
diff --git a/webAO/dom/window.ts b/webAO/dom/window.ts
index 2535768..0b3bd34 100644
--- a/webAO/dom/window.ts
+++ b/webAO/dom/window.ts
@@ -17,6 +17,7 @@ declare global {
changeBackgroundOOC: () => void;
updateActionCommands: (side: string) => void;
updateEvidenceIcon: () => void;
+ updateIniswap: () => void;
resizeChatbox: () => void;
setChatbox: (style: string) => void;
getIndexFromSelect: (select_box: string, value: string) => Number;
@@ -51,6 +52,7 @@ declare global {
onEnter: (event: any) => void;
onReplayGo: (_event: any) => void;
onOOCEnter: (_event: any) => void;
+ handleCredentialResponse: (_event: any) => void;
}
}
export { } \ No newline at end of file
diff --git a/webAO/encoding.ts b/webAO/encoding.ts
index 54770d0..3477d7b 100644
--- a/webAO/encoding.ts
+++ b/webAO/encoding.ts
@@ -4,10 +4,10 @@
*/
export function escapeChat(estring: string): string {
return estring
- .replace(/#/g, '<num>')
- .replace(/&/g, '<and>')
- .replace(/%/g, '<percent>')
- .replace(/\$/g, '<dollar>');
+ .replaceAll('#', '<num>')
+ .replaceAll('&', '<and>')
+ .replaceAll('%', '<percent>')
+ .replaceAll('$', '<dollar>');
}
/**
@@ -16,10 +16,10 @@ export function escapeChat(estring: string): string {
*/
export function unescapeChat(estring: string): string {
return estring
- .replace(/<num>/g, '#')
- .replace(/<and>/g, '&')
- .replace(/<percent>/g, '%')
- .replace(/<dollar>/g, '$');
+ .replaceAll('<num>', '#')
+ .replaceAll('<and>', '&')
+ .replaceAll('<percent>', '%')
+ .replaceAll('<dollar>', '$');
}
/**
@@ -31,8 +31,8 @@ export function unescapeChat(estring: string): string {
export function safeTags(unsafe: string): string {
if (unsafe) {
return unsafe
- .replace(/>/g, '&gt;')
- .replace(/</g, '&lt;');
+ .replaceAll('>', '>')
+ .replaceAll('<', '<');
}
return '';
}
diff --git a/webAO/master.ts b/webAO/master.ts
index 5a31024..7e9c994 100644
--- a/webAO/master.ts
+++ b/webAO/master.ts
@@ -20,10 +20,10 @@ let selectedServer: number = -1;
let servers: { name: string, description: string, ip: string, port: number, ws_port: number, assets: string, online: string }[] = [];
servers[-2] = {
- name: 'Singleplayer', description: 'Build cases, try out new things', ip: '127.0.0.1', port: 50001, ws_port: 50001, assets: '', online: '',
+ name: 'Singleplayer', description: 'Build cases, try out new things', ip: '127.0.0.1', port: 50001, ws_port: 50001, assets: '', online: 'Online: 0/1',
};
servers[-1] = {
- name: 'Localhost', description: 'This is your computer on port 50001', ip: '127.0.0.1', port: 50001, ws_port: 50001, assets: '', online: 'Online: ?/?',
+ name: 'Localhost', description: 'This is your computer on port 50001', ip: '127.0.0.1', port: 50001, ws_port: 50001, assets: '', online: 'Offline',
};
const fpPromise = FingerprintJS.load();
@@ -58,11 +58,7 @@ export function setServ(ID: number) {
if (document.getElementById(`server${ID}`).className === '') { checkOnline(ID, `${servers[ID].ip}:${servers[ID].ws_port}`); }
- if (servers[ID].description !== undefined) {
- document.getElementById('serverdescription_content').innerHTML = `<b>${servers[ID].online}</b><br>${safeTags(servers[ID].description)}`;
- } else {
- document.getElementById('serverdescription_content').innerHTML = '';
- }
+ document.getElementById('serverdescription_content').innerHTML = `<b>${servers[ID].online}</b><br>${safeTags(servers[ID].description)}`;
}
window.setServ = setServ;
@@ -137,6 +133,7 @@ function processServerlist(thelist: { name: string, description: string, ip: str
const serverEntry: { name: string, description: string, ip: string, port: number, ws_port: number, assets: string, online: string } = thelist[i];
servers[i] = serverEntry;
+ servers[i].online = "Offline";
const ipport = `${serverEntry.ip}:${serverEntry.ws_port}`;
diff --git a/webAO/packets/handlers/handleASS.ts b/webAO/packets/handlers/handleASS.ts
index ea8a0d3..bfd764d 100644
--- a/webAO/packets/handlers/handleASS.ts
+++ b/webAO/packets/handlers/handleASS.ts
@@ -6,5 +6,6 @@ import { setAOhost } from "../../client/aoHost";
* @param {Array} args packet arguments
*/
export const handleASS = (args: string[]) => {
- setAOhost(args[1]);
+ if(args[1] !== "None")
+ setAOhost(args[1]);
}
diff --git a/webAO/packets/handlers/handleBD.ts b/webAO/packets/handlers/handleBD.ts
index 4ec291e..dbfb54b 100644
--- a/webAO/packets/handlers/handleBD.ts
+++ b/webAO/packets/handlers/handleBD.ts
@@ -1,5 +1,6 @@
-import { client, setBanned } from "../../client";
+import { setBanned } from "../../client";
import { safeTags } from "../../encoding";
+import { handleBans } from '../../client/handleBans'
/**
@@ -8,6 +9,6 @@ import { safeTags } from "../../encoding";
* @param {Array} args ban reason
*/
export const handleBD = (args: string[]) => {
- client.handleBans("Banned", safeTags(args[1]));
+ handleBans("Banned", safeTags(args[1]));
setBanned(true);
} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleCI.ts b/webAO/packets/handlers/handleCI.ts
index 53e42f7..cb693bc 100644
--- a/webAO/packets/handlers/handleCI.ts
+++ b/webAO/packets/handlers/handleCI.ts
@@ -1,4 +1,5 @@
import { client } from '../../client'
+import { handleCharacterInfo } from '../../client/handleCharacterInfo'
/**
* Handles incoming character information, bundling multiple characters
* per packet.
@@ -18,7 +19,7 @@ export const handleCI = (args: string[]) => {
(<HTMLProgressElement>(
document.getElementById("client_loadingbar")
)).value = charid;
- setTimeout(() => client.handleCharacterInfo(chargs, charid), 500);
+ setTimeout(() => handleCharacterInfo(chargs, charid), 500);
}
}
// Request the next pack
diff --git a/webAO/packets/handlers/handleEM.ts b/webAO/packets/handlers/handleEM.ts
index 9f3cb87..cfe63b7 100644
--- a/webAO/packets/handlers/handleEM.ts
+++ b/webAO/packets/handlers/handleEM.ts
@@ -1,4 +1,8 @@
import { client } from '../../client'
+import { addTrack } from '../../client/addTrack';
+import { createArea } from '../../client/createArea';
+import { fix_last_area } from '../../client/fixLastArea';
+import { isAudio } from '../../client/isAudio';
import { safeTags } from '../../encoding';
/**
@@ -16,20 +20,20 @@ export const handleEM = (args: string[]) => {
for (let i = 2; i < args.length - 1; i++) {
if (i % 2 === 0) {
- const trackname = safeTags(args[i]);
+ const trackname = args[i];
const trackindex = Number(args[i - 1]);
(<HTMLProgressElement>(
document.getElementById("client_loadingbar")
)).value =
client.char_list_length + client.evidence_list_length + trackindex;
if (client.musics_time) {
- client.addTrack(trackname);
- } else if (client.isAudio(trackname)) {
+ addTrack(trackname);
+ } else if (isAudio(trackname)) {
client.musics_time = true;
- client.fix_last_area();
- client.addTrack(trackname);
+ fix_last_area();
+ addTrack(trackname);
} else {
- client.createArea(trackindex, trackname);
+ createArea(trackindex, trackname);
}
}
}
diff --git a/webAO/packets/handlers/handleFA.ts b/webAO/packets/handlers/handleFA.ts
index ccfe923..7a373e8 100644
--- a/webAO/packets/handlers/handleFA.ts
+++ b/webAO/packets/handlers/handleFA.ts
@@ -1,4 +1,5 @@
import { client } from '../../client'
+import { createArea } from '../../client/createArea';
import { safeTags } from '../../encoding';
/**
@@ -9,6 +10,6 @@ export const handleFA = (args: string[]) => {
client.resetAreaList();
for (let i = 1; i < args.length - 1; i++) {
- client.createArea(i - 1, safeTags(args[i]));
+ createArea(i - 1, safeTags(args[i]));
}
}
diff --git a/webAO/packets/handlers/handleFL.ts b/webAO/packets/handlers/handleFL.ts
index 378d5a9..89df4ed 100644
--- a/webAO/packets/handlers/handleFL.ts
+++ b/webAO/packets/handlers/handleFL.ts
@@ -15,13 +15,10 @@ export const handleFL = (args: string[]) => {
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("Pink", "6");
+ colorselect.options[colorselect.options.length] = new Option("Cyan", "7");
+ colorselect.options[colorselect.options.length] = new Option("Grey", "8");
}
if (args.includes("cccc_ic_support")) {
diff --git a/webAO/packets/handlers/handleFM.ts b/webAO/packets/handlers/handleFM.ts
index 630477b..fce10e3 100644
--- a/webAO/packets/handlers/handleFM.ts
+++ b/webAO/packets/handlers/handleFM.ts
@@ -1,7 +1,7 @@
import { client } from "../../client";
+import { addTrack } from "../../client/addTrack";
import { safeTags } from "../../encoding";
-
/**
* Handles updated music list
* @param {Array} args packet arguments
@@ -11,6 +11,6 @@ export const handleFM = (args: string[]) => {
for (let i = 1; i < args.length - 1; i++) {
// Check when found the song for the first time
- client.addTrack(safeTags(args[i]));
+ addTrack(safeTags(args[i]));
}
}
diff --git a/webAO/packets/handlers/handleKB.ts b/webAO/packets/handlers/handleKB.ts
index 8705b83..b0aa2b2 100644
--- a/webAO/packets/handlers/handleKB.ts
+++ b/webAO/packets/handlers/handleKB.ts
@@ -1,5 +1,6 @@
-import { client, setBanned } from "../../client";
+import { setBanned } from "../../client";
import { safeTags } from "../../encoding";
+import { handleBans } from '../../client/handleBans'
/**
* Handles the banned packet
@@ -7,6 +8,6 @@ import { safeTags } from "../../encoding";
* @param {Array} args ban reason
*/
export const handleKB = (args: string[]) => {
- client.handleBans("Banned", safeTags(args[1]));
+ handleBans("Banned", safeTags(args[1]));
setBanned(true);
}
diff --git a/webAO/packets/handlers/handleKK.ts b/webAO/packets/handlers/handleKK.ts
index fd9a88c..c8a97b1 100644
--- a/webAO/packets/handlers/handleKK.ts
+++ b/webAO/packets/handlers/handleKK.ts
@@ -1,11 +1,10 @@
-import { client } from "../../client";
import { safeTags } from "../../encoding";
-
+import { handleBans } from '../../client/handleBans'
/**
* Handles the kicked packet
* @param {Array} args kick reason
*/
export const handleKK = (args: string[]) => {
- client.handleBans("Kicked", safeTags(args[1]));
+ handleBans("Kicked", safeTags(args[1]));
}
diff --git a/webAO/packets/handlers/handleMC.ts b/webAO/packets/handlers/handleMC.ts
index bf60eb9..aeb178d 100644
--- a/webAO/packets/handlers/handleMC.ts
+++ b/webAO/packets/handlers/handleMC.ts
@@ -1,6 +1,7 @@
import { prepChat } from "../../encoding";
-import { appendICLog, client } from '../../client'
+import { client } from '../../client'
import { AO_HOST } from "../../client/aoHost";
+import { appendICLog } from '../../client/appendICLog'
/**
* Handles a music change to an arbitrary resource.
diff --git a/webAO/packets/handlers/handleMS.ts b/webAO/packets/handlers/handleMS.ts
index 0aad19a..c30e777 100644
--- a/webAO/packets/handlers/handleMS.ts
+++ b/webAO/packets/handlers/handleMS.ts
@@ -1,124 +1,111 @@
-import { client, extrafeatures, resetICParams, UPDATE_INTERVAL } from "../../client";
+import { client, extrafeatures, UPDATE_INTERVAL } from "../../client";
+import { handleCharacterInfo } from "../../client/handleCharacterInfo";
+import { resetICParams } from "../../client/resetICParams";
import { prepChat, safeTags } from "../../encoding";
-
+import { handle_ic_speaking } from '../../viewport/utils/handleICSpeaking'
/**
* Handles an in-character chat message.
* @param {*} args packet arguments
*/
export const handleMS = (args: string[]) => {
- // TODO: this if-statement might be a bug.
- if (args[4] !== client.viewport.chatmsg.content) {
- document.getElementById("client_inner_chat")!.innerHTML = "";
+ // TODO: this if-statement might be a bug.
+ if (args[4] !== client.viewport.getChatmsg().content) {
+ document.getElementById("client_inner_chat")!.innerHTML = "";
- const char_id = Number(args[9]);
- const char_name = safeTags(args[3]);
+ 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 char_muted = false;
+ let msg_nameplate = args[3];
+ let msg_blips = "male";
+ let char_chatbox = "default";
+ let char_muted = false;
- if (char_id < client.char_list_length && char_id >= 0) {
- if(client.chars[char_id].name !== char_name) {
+ if (char_id < client.char_list_length && char_id >= 0) {
+ if (client.chars[char_id].name !== char_name) {
console.info(
`${client.chars[char_id].name} is iniediting to ${char_name}`
);
const chargs = (`${char_name}&` + "iniediter").split("&");
- client.handleCharacterInfo(chargs, char_id);
- }
+ handleCharacterInfo(chargs, char_id);
}
+ }
- try {
- msg_nameplate = client.chars[char_id].showname;
- } catch (e) {
- msg_nameplate = args[3];
- }
+ try {
+ msg_nameplate = client.chars[char_id].showname;
+ } catch (e) {
+ msg_nameplate = args[3];
+ }
- try {
- msg_blips = client.chars[char_id].blips;
- } catch (e) {}
+ try {
+ msg_blips = client.chars[char_id].blips;
+ } catch (e) { }
- try {
- char_chatbox = client.chars[char_id].chat;
- } catch (e) {
- char_chatbox = "default";
- }
+ try {
+ char_chatbox = client.chars[char_id].chat;
+ } catch (e) {
+ char_chatbox = "default";
+ }
- try {
- char_muted = client.chars[char_id].muted;
- } catch (e) {
- char_muted = false;
- console.error("we're still missing some character data");
- }
+ try {
+ char_muted = client.chars[char_id].muted;
+ } catch (e) {
+ char_muted = false;
+ console.error("we're still missing some character data");
+ }
+
+ if (char_muted === false) {
+ let chatmsg = {
+ deskmod: Number(safeTags(args[1]).toLowerCase()),
+ preanim: safeTags(args[2]).toLowerCase(), // get preanim
+ nameplate: msg_nameplate,
+ chatbox: char_chatbox,
+ name: char_name,
+ sprite: safeTags(args[4]).toLowerCase(),
+ content: prepChat(args[5]), // Escape HTML tags
+ side: args[6].toLowerCase(),
+ sound: safeTags(args[7]).toLowerCase(),
+ blips: safeTags(msg_blips),
+ type: Number(args[8]),
+ charid: char_id,
+ snddelay: Number(args[10]),
+ objection: Number(args[11]),
+ evidence: Number(safeTags(args[12])),
+ flip: Number(args[13]),
+ flash: Number(args[14]),
+ color: Number(args[15]),
+ speed: UPDATE_INTERVAL,
+ };
- if (char_muted === false) {
- let chatmsg = {
- deskmod: safeTags(args[1]).toLowerCase(),
- preanim: safeTags(args[2]).toLowerCase(), // get preanim
- nameplate: msg_nameplate,
- chatbox: char_chatbox,
- name: char_name,
- sprite: safeTags(args[4]).toLowerCase(),
- content: prepChat(args[5]), // Escape HTML tags
- side: args[6].toLowerCase(),
- sound: safeTags(args[7]).toLowerCase(),
- blips: safeTags(msg_blips),
- type: Number(args[8]),
- charid: char_id,
- snddelay: Number(args[10]),
- objection: Number(args[11]),
- evidence: safeTags(args[12]),
- flip: Number(args[13]),
- flash: Number(args[14]),
- color: Number(args[15]),
- speed: UPDATE_INTERVAL,
+ if (extrafeatures.includes("cccc_ic_support")) {
+ const extra_cccc = {
+ showname: prepChat(args[16]),
+ other_charid: Number(args[17]),
+ other_name: safeTags(args[18]),
+ other_emote: safeTags(args[19]),
+ self_offset: args[20].split("<and>"), // HACK: here as well, client is fucked and uses this instead of &
+ other_offset: args[21].split("<and>"),
+ other_flip: Number(args[22]),
+ noninterrupting_preanim: Number(args[23]),
};
+ chatmsg = Object.assign(extra_cccc, chatmsg);
- 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("<and>"), // HACK: here as well, client is fucked and uses this instead of &
- other_offset: args[21].split("<and>"),
- other_flip: Number(args[22]),
- noninterrupting_preanim: Number(args[23]),
+ if (extrafeatures.includes("looping_sfx")) {
+ const extra_27 = {
+ looping_sfx: Number(args[24]),
+ screenshake: Number(args[25]),
+ frame_screenshake: safeTags(args[26]),
+ frame_realization: safeTags(args[27]),
+ frame_sfx: safeTags(args[28]),
};
- chatmsg = Object.assign(extra_cccc, chatmsg);
+ chatmsg = Object.assign(extra_27, chatmsg);
- if (extrafeatures.includes("looping_sfx")) {
- const extra_27 = {
- looping_sfx: Number(args[24]),
- screenshake: Number(args[25]),
- frame_screenshake: safeTags(args[26]),
- frame_realization: safeTags(args[27]),
- frame_sfx: safeTags(args[28]),
+ if (extrafeatures.includes("effects")) {
+ const extra_28 = {
+ additive: Number(args[29]),
+ effects: args[30].split("|"),
};
- chatmsg = Object.assign(extra_27, chatmsg);
-
- if (extrafeatures.includes("effects")) {
- const extra_28 = {
- additive: Number(args[29]),
- effects: args[30].split("|"),
- };
- chatmsg = Object.assign(extra_28, chatmsg);
- } else {
- const extra_28 = {
- additive: 0,
- effects: ["", "", ""],
- };
- chatmsg = Object.assign(extra_28, chatmsg);
- }
+ chatmsg = Object.assign(extra_28, chatmsg);
} else {
- const extra_27 = {
- looping_sfx: 0,
- screenshake: 0,
- frame_screenshake: "",
- frame_realization: "",
- frame_sfx: "",
- };
- chatmsg = Object.assign(extra_27, chatmsg);
const extra_28 = {
additive: 0,
effects: ["", "", ""],
@@ -126,17 +113,6 @@ export const handleMS = (args: string[]) => {
chatmsg = Object.assign(extra_28, chatmsg);
}
} else {
- const extra_cccc = {
- showname: "",
- other_charid: 0,
- other_name: "",
- other_emote: "",
- self_offset: [0, 0],
- other_offset: [0, 0],
- other_flip: 0,
- noninterrupting_preanim: 0,
- };
- chatmsg = Object.assign(extra_cccc, chatmsg);
const extra_27 = {
looping_sfx: 0,
screenshake: 0,
@@ -151,12 +127,44 @@ export const handleMS = (args: string[]) => {
};
chatmsg = Object.assign(extra_28, chatmsg);
}
+ } else {
+ const extra_cccc = {
+ showname: "",
+ other_charid: 0,
+ other_name: "",
+ other_emote: "",
+ self_offset: [0, 0],
+ other_offset: [0, 0],
+ other_flip: 0,
+ noninterrupting_preanim: 0,
+ };
+ chatmsg = Object.assign(extra_cccc, chatmsg);
+ const extra_27 = {
+ looping_sfx: 0,
+ screenshake: 0,
+ frame_screenshake: "",
+ frame_realization: "",
+ frame_sfx: "",
+ };
+ chatmsg = Object.assign(extra_27, chatmsg);
+ const extra_28 = {
+ additive: 0,
+ effects: ["", "", ""],
+ };
+ chatmsg = Object.assign(extra_28, chatmsg);
+ }
- // our own message appeared, reset the buttons
- if (chatmsg.charid === client.charID) {
- resetICParams();
- }
- client.viewport.handle_ic_speaking(chatmsg); // no await
+ if (chatmsg.content.trim() === "") {
+ //blankpost
+ chatmsg.content = "";
}
+
+ // our own message appeared, reset the buttons
+ if (chatmsg.charid === client.charID) {
+ resetICParams();
+ }
+
+ handle_ic_speaking(chatmsg); // no await
}
- } \ No newline at end of file
+ }
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleRT.ts b/webAO/packets/handlers/handleRT.ts
index 5bbf2b2..62ebb1e 100644
--- a/webAO/packets/handlers/handleRT.ts
+++ b/webAO/packets/handlers/handleRT.ts
@@ -1,5 +1,5 @@
import { client } from "../../client";
-
+import { initTestimonyUpdater } from '../../viewport/utils/initTestimonyUpdater'
/**
* Handles a testimony states.
@@ -21,5 +21,5 @@ export const handleRT = (args: string[]) => {
default:
console.warn("Invalid testimony");
}
- client.viewport.initTestimonyUpdater();
+ initTestimonyUpdater();
} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleSC.ts b/webAO/packets/handlers/handleSC.ts
index f4953e0..b42a4cd 100644
--- a/webAO/packets/handlers/handleSC.ts
+++ b/webAO/packets/handlers/handleSC.ts
@@ -1,6 +1,7 @@
import queryParser from "../../utils/queryParser";
import { client } from '../../client'
+import { handleCharacterInfo } from "../../client/handleCharacterInfo";
let { mode } = queryParser();
/**
@@ -30,7 +31,7 @@ export const handleSC = async (args: string[]) => {
document.getElementById("client_loadingbar")
)).value = charid;
await sleep(0.1); // TODO: Too many network calls without this. net::ERR_INSUFFICIENT_RESOURCES
- client.handleCharacterInfo(chargs, charid);
+ handleCharacterInfo(chargs, charid);
}
// We're done with the characters, request the music
client.sender.sendServer("RM#%");
diff --git a/webAO/packets/handlers/handleSM.ts b/webAO/packets/handlers/handleSM.ts
index 48f9cd9..08bf7e0 100644
--- a/webAO/packets/handlers/handleSM.ts
+++ b/webAO/packets/handlers/handleSM.ts
@@ -1,4 +1,8 @@
import { client } from '../../client'
+import { addTrack } from '../../client/addTrack'
+import { isAudio } from '../../client/isAudio'
+import { fix_last_area } from '../../client/fixLastArea'
+import { createArea } from '../../client/createArea'
/**
* Handles incoming music information, containing all music in one packet.
* @param {Array} args packet arguments
@@ -21,14 +25,15 @@ export const handleSM = (args: string[]) => {
document.getElementById("client_loadingbar")
)).value = client.char_list_length + client.evidence_list_length + i;
if (client.musics_time) {
- client.addTrack(trackname);
- } else if (client.isAudio(trackname)) {
+ addTrack(trackname);
+ } else if (isAudio(trackname)) {
client.musics_time = true;
- client.fix_last_area();
- client.addTrack(trackname);
+ fix_last_area();
+ addTrack(trackname);
} else {
- client.createArea(trackindex, trackname);
+ createArea(trackindex, trackname);
}
+
}
// Music done, carry on
diff --git a/webAO/packets/handlers/handleackMS.ts b/webAO/packets/handlers/handleackMS.ts
index 2b971b0..dcca118 100644
--- a/webAO/packets/handlers/handleackMS.ts
+++ b/webAO/packets/handlers/handleackMS.ts
@@ -1,4 +1,4 @@
-import { resetICParams } from "../../client";
+import { resetICParams } from '../../client/resetICParams'
/**
* server got our message
diff --git a/webAO/packets/packets.ts b/webAO/packets/packets.ts
index 79c43c1..c9d0bb8 100644
--- a/webAO/packets/packets.ts
+++ b/webAO/packets/packets.ts
@@ -2,6 +2,7 @@ import { handleMS } from './handlers/handleMS';
import { handleCT } from './handlers/handleCT'
import { handleMC } from './handlers/handleMC'
import { handleRMC } from './handlers/handleRMC'
+import { showFactorDialog } from '../dom/twofactor'
import { handleFL } from './handlers/handleFL'
import { handleLE } from './handlers/handleLE'
import { handleEM } from './handlers/handleEM'
@@ -47,6 +48,7 @@ export const packets = {
"CI": handleCI,
"SC": handleSC,
"EI": handleEI,
+ "2A": showFactorDialog,
"FL": handleFL,
"LE": handleLE,
"EM": handleEM,
diff --git a/webAO/styles/chatbox/chat999.css b/webAO/styles/chatbox/999.css
index de20d28..de20d28 100644
--- a/webAO/styles/chatbox/chat999.css
+++ b/webAO/styles/chatbox/999.css
diff --git a/webAO/styles/chatbox/chatboxes.js b/webAO/styles/chatbox/chatboxes.js
index 0e0367f..bc73109 100644
--- a/webAO/styles/chatbox/chatboxes.js
+++ b/webAO/styles/chatbox/chatboxes.js
@@ -1,21 +1,21 @@
export default [
'aa',
'acww',
- 'chatdd',
+ 'dd',
'dgs',
- 'chatplvsaa',
+ 'plvsaa',
'trilogy',
- 'chatfuture',
+ 'future',
'legacy',
'ddlc',
'dr1',
- 'chatdr2',
+ 'dr2',
'drv3',
'drae',
- 'chatp3',
+ 'p3',
'p4',
'p5',
- 'chat999',
+ '999',
'halla',
'homestuck',
'key',
diff --git a/webAO/styles/chatbox/chatdd.css b/webAO/styles/chatbox/dd.css
index d1561a4..d1561a4 100644
--- a/webAO/styles/chatbox/chatdd.css
+++ b/webAO/styles/chatbox/dd.css
diff --git a/webAO/styles/chatbox/chatdr2.css b/webAO/styles/chatbox/dr2.css
index 59b11dc..c6701d1 100644
--- a/webAO/styles/chatbox/chatdr2.css
+++ b/webAO/styles/chatbox/dr2.css
@@ -123,5 +123,9 @@
}
#client_trackstatus {
- display: fuck;
+ display: block;
+}
+
+#client_clock {
+ display: none;
} \ No newline at end of file
diff --git a/webAO/styles/chatbox/chatfuture.css b/webAO/styles/chatbox/future.css
index cd638f4..cd638f4 100644
--- a/webAO/styles/chatbox/chatfuture.css
+++ b/webAO/styles/chatbox/future.css
diff --git a/webAO/styles/chatbox/chatp3.css b/webAO/styles/chatbox/p3.css
index 8c6a39d..c9234b6 100644
--- a/webAO/styles/chatbox/chatp3.css
+++ b/webAO/styles/chatbox/p3.css
@@ -114,4 +114,8 @@
#client_trackstatus {
display: none;
+}
+
+#client_clock {
+ display: none;
} \ No newline at end of file
diff --git a/webAO/styles/chatbox/p4.css b/webAO/styles/chatbox/p4.css
index 7389fae..e31ffe7 100644
--- a/webAO/styles/chatbox/p4.css
+++ b/webAO/styles/chatbox/p4.css
@@ -112,4 +112,8 @@
#client_trackstatus {
display: none;
+}
+
+#client_clock {
+ display: none;
} \ No newline at end of file
diff --git a/webAO/styles/chatbox/p5.css b/webAO/styles/chatbox/p5.css
index da27529..112c521 100644
--- a/webAO/styles/chatbox/p5.css
+++ b/webAO/styles/chatbox/p5.css
@@ -108,4 +108,8 @@
#client_trackstatus {
display: none;
+}
+
+#client_clock {
+ display: none;
} \ No newline at end of file
diff --git a/webAO/styles/chatbox/chatplvsaa.css b/webAO/styles/chatbox/plvsaa.css
index a656e9b..a656e9b 100644
--- a/webAO/styles/chatbox/chatplvsaa.css
+++ b/webAO/styles/chatbox/plvsaa.css
diff --git a/webAO/styles/client.css b/webAO/styles/client.css
index 8c7898b..ced8339 100644
--- a/webAO/styles/client.css
+++ b/webAO/styles/client.css
@@ -21,15 +21,12 @@
.text_rainbow {
background-color: #fff;
- background-image: repeating-linear-gradient(to right,
- red 0% 8%, orange 8% 16%, yellow 16% 24%, green 24% 32%, blue 32% 40%,
- red 40% 48%, orange 48% 56%, yellow 56% 64%, green 64% 72%, blue 72% 80%,
- red 80% 88%, orange 88% 96%, yellow 96% 100%);
- background-size: 40% 40%;
+ background-image: repeating-linear-gradient(to right, red 0% 20%, orange 20% 40%, yellow 40% 60%, green 60% 80%, blue 80% 100%);
+ background-size: 75% 75%;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
- animation: rainbow 4s linear infinite;
+ animation: rainbow 2s linear infinite;
}
@keyframes rainbow_alt {
@@ -75,6 +72,17 @@
animation: error_blink 3s ease-in-out infinite;
}
+#client_secondfactor {
+ display: block;
+ flex-direction: column;
+ justify-content: center;
+ text-align: center;
+ align-items: center;
+ background: black;
+ color: lightgreen;
+ font-size: large;
+}
+
#client_waiting {
display: block;
flex-direction: column;
diff --git a/webAO/viewport.ts b/webAO/viewport.ts
deleted file mode 100644
index 6c39784..0000000
--- a/webAO/viewport.ts
+++ /dev/null
@@ -1,1091 +0,0 @@
-import tryUrls from "./utils/tryUrls";
-import fileExists from "./utils/fileExists";
-import Client, { delay } from "./client";
-import {opusCheck} from './dom/opusCheck'
-import { UPDATE_INTERVAL } from "./client";
-import { setChatbox } from "./dom/setChatbox";
-import { resizeChatbox } from "./dom/resizeChatbox";
-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";
-import { AO_HOST } from "./client/aoHost";
-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;
- setSfxAudio: Function;
- getSfxAudio: Function;
- getBackgroundFolder: Function;
- blipChannels: HTMLAudioElement[];
- music: any;
- musicVolume: number;
- setBackgroundName: Function;
- lastChar: string;
- getBackgroundName: Function;
-}
-const SHOUTS = [undefined, "holdit", "objection", "takethat", "custom"];
-
-const COLORS = [
- "white",
- "green",
- "red",
- "orange",
- "blue",
- "yellow",
- "pink",
- "cyan",
- "grey",
-];
-const createMusic = () => {
- const audioChannels = document.getElementsByClassName(
- "audioChannel"
- ) as HTMLCollectionOf<HTMLAudioElement>;
- let music = [...audioChannels];
- music.forEach((channel: HTMLAudioElement) => (channel.volume = 0.5));
- music.forEach(
- (channel: HTMLAudioElement) => (channel.onerror = opusCheck(channel))
- );
- return music;
-};
-const createTestimonyAudio = () => {
- const testimonyAudio = document.getElementById(
- "client_testimonyaudio"
- ) as HTMLAudioElement;
- testimonyAudio.src = `${AO_HOST}sounds/general/sfx-guilty.opus`;
- return testimonyAudio;
-};
-
-const createShoutAudio = () => {
- const shoutAudio = document.getElementById(
- "client_shoutaudio"
- ) as HTMLAudioElement;
- shoutAudio.src = `${AO_HOST}misc/default/objection.opus`;
- return shoutAudio;
-};
-const createSfxAudio = () => {
- const sfxAudio = document.getElementById(
- "client_sfxaudio"
- ) as HTMLAudioElement;
- sfxAudio.src = `${AO_HOST}sounds/general/sfx-realization.opus`;
- return sfxAudio;
-};
-const createBlipsChannels = () => {
- const blipSelectors = document.getElementsByClassName(
- "blipSound"
- ) as HTMLCollectionOf<HTMLAudioElement>;
-
- const 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))
- );
- return blipChannels;
-};
-const defaultChatMsg = {
- content: "",
- objection: 0,
- sound: "",
- startpreanim: true,
- startspeaking: false,
- side: null,
- color: 0,
- snddelay: 0,
- preanimdelay: 0,
- speed: UPDATE_INTERVAL,
-} as ChatMsg;
-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",
- },
-};
-const viewport = (masterClient: Client): Viewport => {
- let animating = false;
- let attorneyMarkdown = mlConfig(AO_HOST);
- let blipChannels = createBlipsChannels();
- let chatmsg = defaultChatMsg;
- let client = masterClient;
- let currentBlipChannel = 0;
- let lastChar = "";
- let lastEvi = 0;
- let music = createMusic();
- let musicVolume = 0;
- let sfxAudio = createSfxAudio();
- let sfxplayed = 0;
- let shoutTimer = 0;
- let shoutaudio = createShoutAudio();
- let startFirstTickCheck: boolean;
- let startSecondTickCheck: boolean;
- let startThirdTickCheck: boolean;
- let testimonyAudio = createTestimonyAudio();
- let testimonyTimer = 0;
- let testimonyUpdater: any;
- let textnow = "";
- let theme: string;
- let tickTimer = 0;
- let updater: any;
- let backgroundName = "";
- const getSfxAudio = () => sfxAudio;
- const setSfxAudio = (value: HTMLAudioElement) => (sfxAudio = value);
- const getBackgroundName = () => backgroundName;
- const setBackgroundName = (value: string) => (backgroundName = value);
- const getBackgroundFolder = () =>
- `${AO_HOST}background/${encodeURI(backgroundName.toLowerCase())}/`;
-
- 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 = <HTMLImageElement>(
- document.getElementById(`client_${position}_bench`)
- );
- } else {
- bench = <HTMLImageElement>document.getElementById("client_bench_classic");
- }
-
- let court: HTMLImageElement;
- if ("def,pro,wit".includes(position)) {
- court = <HTMLImageElement>(
- document.getElementById(`client_court_${position}`)
- );
- } else {
- court = <HTMLImageElement>document.getElementById("client_court_classic");
- }
-
- 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(getBackgroundFolder() + bg);
- }
-
- if (showDesk === true && desk) {
- const deskFilename = (await fileExists(getBackgroundFolder() + desk.ao2))
- ? desk.ao2
- : desk.ao1;
- bench.src = getBackgroundFolder() + 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 = <HTMLImageElement>(
- 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;
- client.viewport.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");
- // stop updater
- clearTimeout(updater);
-
- // stop last sfx from looping any longer
- sfxAudio.loop = false;
-
- const fg = <HTMLImageElement>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 =
- (<HTMLInputElement>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;
- client.viewport.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 = <HTMLImageElement>(
- 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 effectName = chatmsg.effects[0].toLowerCase();
- const badEffects = ["", "-", "none"];
- if (effectName.startsWith("rain") ) {
- (<HTMLLinkElement>document.getElementById("effect_css")).href = "styles/effects/rain.css";
- let intensity = 200;
- if(effectName.endsWith("weak")) {
- intensity = 100;
- } else if (effectName.endsWith("strong")) {
- intensity = 400;
- }
- if ( intensity < fg.childElementCount)
- fg.innerHTML = '';
- else
- intensity = intensity - fg.childElementCount;
-
- for (let i = 0; i < intensity; i++) {
- let drop = document.createElement("p");
- drop.style.left = (Math.random() * 100) + "%";
- drop.style.animationDelay = String(Math.random())+"s";
- fg.appendChild(drop)
- }
- } else if (
- chatmsg.effects[0] &&
- !badEffects.includes(effectName)
- ) {
- (<HTMLLinkElement>document.getElementById("effect_css")).href = "";
- fg.innerHTML = '';
- const baseEffectUrl = `${AO_HOST}themes/default/effects/`;
- fg.src = `${baseEffectUrl}${encodeURI(effectName)}.webp`;
- } else {
- fg.innerHTML = '';
- 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]
- );
- chat_tick();
- };
-
- const handleTextTick = async (charLayers: HTMLImageElement) => {
- 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);
- 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]);
- }
- }
- // 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);
- if (textnow === chatmsg.content) {
- return;
- }
-
- const gamewindow = document.getElementById("client_gamewindow");
- const waitingBox = document.getElementById("client_chatwaiting");
- const eviBox = <HTMLImageElement>document.getElementById("client_evi");
- const shoutSprite = <HTMLImageElement>(
- document.getElementById("client_shout")
- );
- const effectlayer = <HTMLImageElement>document.getElementById("client_fg");
- const chatBoxInner = document.getElementById("client_inner_chat");
- let charLayers = <HTMLImageElement>document.getElementById("client_char");
- let pairLayers = <HTMLImageElement>(
- 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 = <HTMLImageElement>(
- document.getElementById(`client_${chatmsg.side}_char`)
- );
- pairLayers = <HTMLImageElement>(
- 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 = (<HTMLSelectElement>document.getElementById("client_themeselect"))
- .value;
-
- setCookie("theme", theme);
- (<HTMLAnchorElement>(
- document.getElementById("client_theme")
- )).href = `styles/${theme}.css`;
- }
- window.reloadTheme = reloadTheme;
- /**
- * Triggered by the blip volume slider.
- */
- function changeBlipVolume() {
- const blipVolume = (<HTMLInputElement>(
- document.getElementById("client_bvolume")
- )).value;
- blipChannels.forEach(
- (channel: HTMLAudioElement) => (channel.volume = Number(blipVolume))
- );
- setCookie("blipVolume", blipVolume);
- }
- window.changeBlipVolume = changeBlipVolume;
-
- const changeMusicVolume = (volume: number = -1) => {
- const clientVolume = Number(
- (<HTMLInputElement>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;
-
- return {
- chat_tick,
- changeMusicVolume,
- changeBlipVolume,
- reloadTheme,
- playSFX,
- set_side,
- setBackgroundName,
- initTestimonyUpdater,
- updateTestimony,
- disposeTestimony,
- handle_ic_speaking,
- handleTextTick,
- getBackgroundFolder,
- getBackgroundName,
- getSfxAudio,
- setSfxAudio,
- theme,
- chatmsg,
- blipChannels,
- lastChar,
- music,
- musicVolume,
- };
-};
-
-export default viewport;
diff --git a/webAO/viewport/constants/colors.ts b/webAO/viewport/constants/colors.ts
new file mode 100644
index 0000000..4858081
--- /dev/null
+++ b/webAO/viewport/constants/colors.ts
@@ -0,0 +1,12 @@
+export const COLORS = [
+ "white",
+ "green",
+ "red",
+ "orange",
+ "blue",
+ "yellow",
+ "pink",
+ "cyan",
+ "grey",
+ "rainbow",
+ ]; \ No newline at end of file
diff --git a/webAO/viewport/constants/defaultChatMsg.ts b/webAO/viewport/constants/defaultChatMsg.ts
new file mode 100644
index 0000000..8a5db6b
--- /dev/null
+++ b/webAO/viewport/constants/defaultChatMsg.ts
@@ -0,0 +1,15 @@
+import { UPDATE_INTERVAL } from "../../client";
+import { ChatMsg } from "../interfaces/ChatMsg";
+
+export const defaultChatMsg = {
+ content: "",
+ objection: 0,
+ sound: "",
+ startpreanim: true,
+ startspeaking: false,
+ side: null,
+ color: 0,
+ snddelay: 0,
+ preanimdelay: 0,
+ speed: UPDATE_INTERVAL,
+ } as ChatMsg; \ No newline at end of file
diff --git a/webAO/viewport/constants/positions.ts b/webAO/viewport/constants/positions.ts
new file mode 100644
index 0000000..1712ac6
--- /dev/null
+++ b/webAO/viewport/constants/positions.ts
@@ -0,0 +1,45 @@
+import { Positions } from '../interfaces/Positions'
+import { Desk } from '../interfaces/Desk';
+
+export 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: {} as Desk,
+ speedLines: "defense_speedlines.gif",
+ },
+ hlp: {
+ bg: "prohelperstand",
+ desk: {} 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",
+ },
+}; \ No newline at end of file
diff --git a/webAO/viewport/constants/shouts.ts b/webAO/viewport/constants/shouts.ts
new file mode 100644
index 0000000..eddd6d3
--- /dev/null
+++ b/webAO/viewport/constants/shouts.ts
@@ -0,0 +1 @@
+export const SHOUTS = [undefined, "holdit", "objection", "takethat", "custom"];
diff --git a/webAO/viewport/interfaces/ChatMsg.ts b/webAO/viewport/interfaces/ChatMsg.ts
new file mode 100644
index 0000000..6b96c6e
--- /dev/null
+++ b/webAO/viewport/interfaces/ChatMsg.ts
@@ -0,0 +1,34 @@
+export 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;
+ } \ No newline at end of file
diff --git a/webAO/viewport/interfaces/Desk.ts b/webAO/viewport/interfaces/Desk.ts
new file mode 100644
index 0000000..872426a
--- /dev/null
+++ b/webAO/viewport/interfaces/Desk.ts
@@ -0,0 +1,4 @@
+export interface Desk {
+ ao2?: string;
+ ao1?: string;
+} \ No newline at end of file
diff --git a/webAO/viewport/interfaces/Position.ts b/webAO/viewport/interfaces/Position.ts
new file mode 100644
index 0000000..dea7238
--- /dev/null
+++ b/webAO/viewport/interfaces/Position.ts
@@ -0,0 +1,7 @@
+import { Desk } from './Desk'
+
+export interface Position {
+ bg?: string;
+ desk?: Desk;
+ speedLines: string;
+} \ No newline at end of file
diff --git a/webAO/viewport/interfaces/Positions.ts b/webAO/viewport/interfaces/Positions.ts
new file mode 100644
index 0000000..0644962
--- /dev/null
+++ b/webAO/viewport/interfaces/Positions.ts
@@ -0,0 +1,5 @@
+import { Position } from './Position'
+
+export interface Positions {
+ [key: string]: Position;
+} \ No newline at end of file
diff --git a/webAO/viewport/interfaces/Testimony.ts b/webAO/viewport/interfaces/Testimony.ts
new file mode 100644
index 0000000..61a7491
--- /dev/null
+++ b/webAO/viewport/interfaces/Testimony.ts
@@ -0,0 +1,3 @@
+export interface Testimony {
+ [key: number]: string;
+} \ No newline at end of file
diff --git a/webAO/viewport/interfaces/Viewport.ts b/webAO/viewport/interfaces/Viewport.ts
new file mode 100644
index 0000000..5b428c1
--- /dev/null
+++ b/webAO/viewport/interfaces/Viewport.ts
@@ -0,0 +1,43 @@
+import { ChatMsg } from "./ChatMsg";
+
+export interface Viewport {
+ getTextNow: Function;
+ setTextNow: Function;
+ getChatmsg: Function;
+ setChatmsg: Function;
+ getSfxPlayed: Function;
+ setSfxPlayed: Function;
+ setTickTimer: Function;
+ getTickTimer: Function;
+ getAnimating: Function;
+ setAnimating: Function;
+ getLastEvidence: Function;
+ setLastEvidence: Function;
+ setLastCharacter: Function;
+ getLastCharacter: Function;
+ setShoutTimer: Function;
+ getShoutTimer: Function;
+ setTestimonyTimer: Function;
+ getTestimonyTimer: Function;
+ setTestimonyUpdater: Function;
+ getTestimonyUpdater: Function;
+ getTheme: Function;
+ setTheme: Function;
+ testimonyAudio: HTMLAudioElement;
+ chat_tick: Function;
+ playSFX: Function;
+ set_side: Function;
+ updateTestimony: Function;
+ disposeTestimony: Function;
+ handleTextTick: Function;
+ setSfxAudio: Function;
+ getSfxAudio: Function;
+ getBackgroundFolder: Function;
+ blipChannels: HTMLAudioElement[];
+ music: any;
+ musicVolume: number;
+ setBackgroundName: Function;
+ getBackgroundName: Function;
+ shoutaudio: HTMLAudioElement;
+ updater: any;
+}
diff --git a/webAO/viewport/utils/createBlipChannels.ts b/webAO/viewport/utils/createBlipChannels.ts
new file mode 100644
index 0000000..6296b3b
--- /dev/null
+++ b/webAO/viewport/utils/createBlipChannels.ts
@@ -0,0 +1,15 @@
+import { opusCheck } from "../../dom/opusCheck";
+
+export const createBlipsChannels = () => {
+ const blipSelectors = document.getElementsByClassName(
+ "blipSound"
+ ) as HTMLCollectionOf<HTMLAudioElement>;
+
+ const 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))
+ );
+ return blipChannels;
+}; \ No newline at end of file
diff --git a/webAO/viewport/utils/createMusic.ts b/webAO/viewport/utils/createMusic.ts
new file mode 100644
index 0000000..9bf5240
--- /dev/null
+++ b/webAO/viewport/utils/createMusic.ts
@@ -0,0 +1,13 @@
+import { opusCheck } from '../../dom/opusCheck'
+
+export const createMusic = () => {
+ const audioChannels = document.getElementsByClassName(
+ "audioChannel"
+ ) as HTMLCollectionOf<HTMLAudioElement>;
+ let music = [...audioChannels];
+ music.forEach((channel: HTMLAudioElement) => (channel.volume = 0.5));
+ music.forEach(
+ (channel: HTMLAudioElement) => (channel.onerror = opusCheck(channel))
+ );
+ return music;
+}; \ No newline at end of file
diff --git a/webAO/viewport/utils/createSfxAudio.ts b/webAO/viewport/utils/createSfxAudio.ts
new file mode 100644
index 0000000..7e03563
--- /dev/null
+++ b/webAO/viewport/utils/createSfxAudio.ts
@@ -0,0 +1,9 @@
+import { AO_HOST } from "../../client/aoHost";
+
+export const createSfxAudio = () => {
+ const sfxAudio = document.getElementById(
+ "client_sfxaudio"
+ ) as HTMLAudioElement;
+ sfxAudio.src = `${AO_HOST}sounds/general/sfx-realization.opus`;
+ return sfxAudio;
+}; \ No newline at end of file
diff --git a/webAO/viewport/utils/createShoutAudio.ts b/webAO/viewport/utils/createShoutAudio.ts
new file mode 100644
index 0000000..8211116
--- /dev/null
+++ b/webAO/viewport/utils/createShoutAudio.ts
@@ -0,0 +1,9 @@
+import { AO_HOST } from "../../client/aoHost";
+
+export const createShoutAudio = () => {
+ const shoutAudio = document.getElementById(
+ "client_shoutaudio"
+ ) as HTMLAudioElement;
+ shoutAudio.src = `${AO_HOST}misc/default/objection.opus`;
+ return shoutAudio;
+}; \ No newline at end of file
diff --git a/webAO/viewport/utils/createTestimonyAudio.ts b/webAO/viewport/utils/createTestimonyAudio.ts
new file mode 100644
index 0000000..2ff98f6
--- /dev/null
+++ b/webAO/viewport/utils/createTestimonyAudio.ts
@@ -0,0 +1,9 @@
+import { AO_HOST } from '../../client/aoHost'
+
+export const createTestimonyAudio = () => {
+ const testimonyAudio = document.getElementById(
+ "client_testimonyaudio"
+ ) as HTMLAudioElement;
+ testimonyAudio.src = `${AO_HOST}sounds/general/sfx-guilty.opus`;
+ return testimonyAudio;
+}; \ No newline at end of file
diff --git a/webAO/viewport/utils/handleICSpeaking.ts b/webAO/viewport/utils/handleICSpeaking.ts
new file mode 100644
index 0000000..e2d147d
--- /dev/null
+++ b/webAO/viewport/utils/handleICSpeaking.ts
@@ -0,0 +1,311 @@
+import { ChatMsg } from "../interfaces/ChatMsg";
+import { client } from "../../client";
+import { appendICLog } from "../../client/appendICLog";
+import { checkCallword } from "../../client/checkCallword";
+import setEmote from "../../client/setEmote";
+import { AO_HOST } from "../../client/aoHost";
+import { SHOUTS } from "../constants/shouts";
+import getAnimLength from "../../utils/getAnimLength";
+import { setChatbox } from "../../dom/setChatbox";
+import { resizeChatbox } from "../../dom/resizeChatbox";
+import transparentPng from "../../constants/transparentPng";
+import { COLORS } from "../constants/colors";
+import mlConfig from "../../utils/aoml";
+
+const attorneyMarkdown = mlConfig(AO_HOST);
+
+export let startFirstTickCheck: boolean;
+export const setStartFirstTickCheck = (val: boolean) => {startFirstTickCheck = val}
+export let startSecondTickCheck: boolean;
+export const setStartSecondTickCheck = (val: boolean) => {startSecondTickCheck = val}
+export let startThirdTickCheck: boolean;
+export const setStartThirdTickCheck = (val: boolean) => {startThirdTickCheck = val}
+/**
+ * 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
+ */
+export const handle_ic_speaking = async (playerChatMsg: ChatMsg) => {
+ client.viewport.setChatmsg(playerChatMsg);
+ client.viewport.setTextNow("");
+ client.viewport.setSfxPlayed(0);
+ client.viewport.setTickTimer(0);
+ client.viewport.setAnimating(true);
+
+ startFirstTickCheck = true;
+ startSecondTickCheck = false;
+ startThirdTickCheck = false;
+ let charLayers = document.getElementById("client_char")!;
+ let pairLayers = document.getElementById("client_pair_char")!;
+ // stop updater
+ clearTimeout(client.viewport.updater);
+
+ // stop last sfx from looping any longer
+ client.viewport.getSfxAudio().loop = false;
+
+ const fg = <HTMLImageElement>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 (client.viewport.getLastEvidence() !== client.viewport.getChatmsg().evidence) {
+ eviBox.style.opacity = "0";
+ eviBox.style.height = "0%";
+ }
+ client.viewport.setLastEvidence(client.viewport.getChatmsg().evidence);
+
+ const validSides: string[] = ["def", "pro", "wit"]; // these are for the full view pan, the other positions use 'client_char'
+ if (validSides.includes(client.viewport.getChatmsg().side)) {
+ charLayers = document.getElementById(`client_${client.viewport.getChatmsg().side}_char`);
+ pairLayers = document.getElementById(`client_${client.viewport.getChatmsg().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 =
+ (<HTMLInputElement>document.getElementById("showname")).checked &&
+ client.viewport.getChatmsg().showname !== ""
+ ? client.viewport.getChatmsg().showname!
+ : client.viewport.getChatmsg().nameplate!;
+
+ // Clear out the last message
+ chatBoxInner.innerText = client.viewport.getTextNow();
+ nameBoxInner.innerText = displayname;
+
+ if (client.viewport.getLastCharacter() !== client.viewport.getChatmsg().name) {
+ charLayers.style.opacity = "0";
+ pairLayers.style.opacity = "0";
+ }
+
+ client.viewport.setLastCharacter(client.viewport.getChatmsg().name);
+
+ appendICLog(client.viewport.getChatmsg().content, client.viewport.getChatmsg().showname, client.viewport.getChatmsg().nameplate);
+
+ checkCallword(client.viewport.getChatmsg().content, client.viewport.getSfxAudio());
+
+ setEmote(
+ AO_HOST,
+ client,
+ client.viewport.getChatmsg().name!.toLowerCase(),
+ client.viewport.getChatmsg().sprite!,
+ "(a)",
+ false,
+ client.viewport.getChatmsg().side
+ );
+
+ if (client.viewport.getChatmsg().other_name) {
+ setEmote(
+ AO_HOST,
+ client,
+ client.viewport.getChatmsg().other_name.toLowerCase(),
+ client.viewport.getChatmsg().other_emote!,
+ "(a)",
+ false,
+ client.viewport.getChatmsg().side
+ );
+ }
+
+ // gets which shout shall played
+ const shoutSprite = <HTMLImageElement>(
+ document.getElementById("client_shout")
+ );
+
+ const shout = SHOUTS[client.viewport.getChatmsg().objection];
+ if (shout) {
+ // Hide message box
+ chatContainerBox.style.opacity = "0";
+ if (client.viewport.getChatmsg().objection === 4) {
+ shoutSprite.src = `${AO_HOST}characters/${encodeURI(
+ client.viewport.getChatmsg().name!.toLowerCase()
+ )}/custom.gif`;
+ } else {
+ shoutSprite.src = client.resources[shout].src;
+ shoutSprite.style.animation = "bubble 700ms steps(10, jump-both)";
+ }
+ shoutSprite.style.opacity = "1";
+
+ client.viewport.shoutaudio.src = `${AO_HOST}characters/${encodeURI(
+ client.viewport.getChatmsg().name.toLowerCase()
+ )}/${shout}.opus`;
+ client.viewport.shoutaudio.play();
+ client.viewport.setShoutTimer(client.resources[shout].duration);
+ } else {
+ client.viewport.setShoutTimer(0);
+ }
+
+ client.viewport.getChatmsg().startpreanim = true;
+ let gifLength = 0;
+
+ if (client.viewport.getChatmsg().type === 1 && client.viewport.getChatmsg().preanim !== "-") {
+ //we have a preanim
+ chatContainerBox.style.opacity = "0";
+
+ gifLength = await getAnimLength(
+ `${AO_HOST}characters/${encodeURI(
+ client.viewport.getChatmsg().name!.toLowerCase()
+ )}/${encodeURI(client.viewport.getChatmsg().preanim)}`
+ );
+ client.viewport.getChatmsg().startspeaking = false;
+ } else {
+ client.viewport.getChatmsg().startspeaking = true;
+ if (client.viewport.getChatmsg().content.trim() !== "") chatContainerBox.style.opacity = "1";
+ }
+ client.viewport.getChatmsg().preanimdelay = gifLength;
+ const setAside = {
+ position: client.viewport.getChatmsg().side,
+ showSpeedLines: false,
+ showDesk: false,
+ };
+ let skipoffset: boolean = false;
+ if (client.viewport.getChatmsg().type === 5) {
+ setAside.showSpeedLines = true;
+ setAside.showDesk = false;
+ client.viewport.set_side(setAside);
+ } else {
+ switch (Number(client.viewport.getChatmsg().deskmod)) {
+ case 0: //desk is hidden
+ setAside.showSpeedLines = false;
+ setAside.showDesk = false;
+ client.viewport.set_side(setAside);
+ break;
+ case 1: //desk is shown
+ setAside.showSpeedLines = false;
+ setAside.showDesk = true;
+ client.viewport.set_side(setAside);
+ break;
+ case 2: //desk is hidden during preanim, but shown during idle/talk
+ setAside.showSpeedLines = false;
+ setAside.showDesk = false;
+ client.viewport.set_side(setAside);
+ break;
+ case 3: //opposite of 2
+ setAside.showSpeedLines = false;
+ setAside.showDesk = false;
+ client.viewport.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;
+ client.viewport.set_side(setAside);
+ skipoffset = true;
+ break;
+ case 5: //opposite of 4
+ setAside.showSpeedLines = false;
+ setAside.showDesk = true;
+ client.viewport.set_side(setAside);
+ break;
+ default:
+ setAside.showSpeedLines = false;
+ setAside.showDesk = true;
+ client.viewport.set_side(setAside);
+ break;
+ }
+ }
+
+ setChatbox(client.viewport.getChatmsg().chatbox);
+ resizeChatbox();
+
+ if (!skipoffset) {
+ // Flip the character
+ charLayers.style.transform =
+ client.viewport.getChatmsg().flip === 1 ? "scaleX(-1)" : "scaleX(1)";
+ pairLayers.style.transform =
+ client.viewport.getChatmsg().other_flip === 1 ? "scaleX(-1)" : "scaleX(1)";
+
+ // Shift by the horizontal offset
+ switch (client.viewport.getChatmsg().side) {
+ case "wit":
+ pairLayers.style.left = `${200 + Number(client.viewport.getChatmsg().other_offset[0])}%`;
+ charLayers.style.left = `${200 + Number(client.viewport.getChatmsg().self_offset[0])}%`;
+ break;
+ case "pro":
+ pairLayers.style.left = `${400 + Number(client.viewport.getChatmsg().other_offset[0])}%`;
+ charLayers.style.left = `${400 + Number(client.viewport.getChatmsg().self_offset[0])}%`;
+ break;
+ default:
+ pairLayers.style.left = `${Number(client.viewport.getChatmsg().other_offset[0])}%`;
+ charLayers.style.left = `${Number(client.viewport.getChatmsg().self_offset[0])}%`;
+ break;
+ }
+
+ // New vertical offsets
+ pairLayers.style.top = `${Number(client.viewport.getChatmsg().other_offset[1])}%`;
+ charLayers.style.top = `${Number(client.viewport.getChatmsg().self_offset[1])}%`;
+ }
+
+ client.viewport.blipChannels.forEach(
+ (channel: HTMLAudioElement) =>
+ (channel.src = `${AO_HOST}sounds/general/sfx-blip${encodeURI(
+ client.viewport.getChatmsg().blips.toLowerCase()
+ )}.opus`)
+ );
+
+ // process markup
+ if (client.viewport.getChatmsg().content.startsWith("~~")) {
+ chatBoxInner.style.textAlign = "center";
+ client.viewport.getChatmsg().content = client.viewport.getChatmsg().content.substring(2, client.viewport.getChatmsg().content.length);
+ } else {
+ chatBoxInner.style.textAlign = "inherit";
+ }
+
+ // apply effects
+ fg.style.animation = "";
+ const effectName = client.viewport.getChatmsg().effects[0].toLowerCase();
+ const badEffects = ["", "-", "none"];
+ if (effectName.startsWith("rain")) {
+ (<HTMLLinkElement>document.getElementById("effect_css")).href = "styles/effects/rain.css";
+ let intensity = 200;
+ if (effectName.endsWith("weak")) {
+ intensity = 100;
+ } else if (effectName.endsWith("strong")) {
+ intensity = 400;
+ }
+ if (intensity < fg.childElementCount)
+ fg.innerHTML = '';
+ else
+ intensity = intensity - fg.childElementCount;
+
+ for (let i = 0; i < intensity; i++) {
+ let drop = document.createElement("p");
+ drop.style.left = (Math.random() * 100) + "%";
+ drop.style.animationDelay = String(Math.random()) + "s";
+ fg.appendChild(drop)
+ }
+ } else if (
+ client.viewport.getChatmsg().effects[0] &&
+ !badEffects.includes(effectName)
+ ) {
+ (<HTMLLinkElement>document.getElementById("effect_css")).href = "";
+ fg.innerHTML = '';
+ const baseEffectUrl = `${AO_HOST}themes/default/effects/`;
+ fg.src = `${baseEffectUrl}${encodeURI(effectName)}.webp`;
+ } else {
+ fg.innerHTML = '';
+ fg.src = transparentPng;
+ }
+
+
+ charLayers.style.opacity = "1";
+
+ const soundChecks = ["0", "1", "", undefined];
+ if (soundChecks.some((check) => client.viewport.getChatmsg().sound === check)) {
+ client.viewport.getChatmsg().sound = client.viewport.getChatmsg().effects[2];
+ }
+
+ client.viewport.getChatmsg().parsed = await attorneyMarkdown.applyMarkdown(
+ client.viewport.getChatmsg().content,
+
+ COLORS[client.viewport.getChatmsg().color]
+
+ );
+ client.viewport.chat_tick();
+}; \ No newline at end of file
diff --git a/webAO/viewport/utils/initTestimonyUpdater.ts b/webAO/viewport/utils/initTestimonyUpdater.ts
new file mode 100644
index 0000000..e6f6e9d
--- /dev/null
+++ b/webAO/viewport/utils/initTestimonyUpdater.ts
@@ -0,0 +1,31 @@
+import { Testimony } from '../interfaces/Testimony'
+import { client, UPDATE_INTERVAL } from '../../client'
+/**
+ * Intialize testimony updater
+ */
+export const 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;
+ }
+
+ client.viewport.testimonyAudio.src = client.resources[testimony].sfx;
+ client.viewport.testimonyAudio.play();
+
+ const testimonyOverlay = <HTMLImageElement>(
+ document.getElementById("client_testimony")
+ );
+ testimonyOverlay.src = client.resources[testimony].src;
+ testimonyOverlay.style.opacity = "1";
+
+ client.viewport.setTestimonyTimer(0);
+ client.viewport.setTestimonyUpdater(setTimeout(() => client.viewport.updateTestimony(), UPDATE_INTERVAL));
+}; \ No newline at end of file
diff --git a/webAO/viewport/utils/setSide.ts b/webAO/viewport/utils/setSide.ts
new file mode 100644
index 0000000..3726e83
--- /dev/null
+++ b/webAO/viewport/utils/setSide.ts
@@ -0,0 +1,90 @@
+import { positions } from '../constants/positions'
+import { AO_HOST } from '../../client/aoHost'
+import { client } from '../../client'
+import tryUrls from '../../utils/tryUrls';
+import fileExists from '../../utils/fileExists';
+
+/**
+ * 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
+ */
+export 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 = <HTMLImageElement>(
+ document.getElementById(`client_${position}_bench`)
+ );
+ } else {
+ bench = <HTMLImageElement>document.getElementById("client_bench_classic");
+ }
+
+ let court: HTMLImageElement;
+ if ("def,pro,wit".includes(position)) {
+ court = <HTMLImageElement>(
+ document.getElementById(`client_court_${position}`)
+ );
+ } else {
+ court = <HTMLImageElement>document.getElementById("client_court_classic");
+ }
+
+ 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(client.viewport.getBackgroundFolder() + bg);
+ }
+
+
+ if (showDesk === true && desk) {
+ const deskFilename = (await fileExists(client.viewport.getBackgroundFolder() + desk.ao2))
+ ? desk.ao2
+ : desk.ao1;
+ bench.src = client.viewport.getBackgroundFolder() + 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 = "";
+ }
+};
diff --git a/webAO/viewport/viewport.ts b/webAO/viewport/viewport.ts
new file mode 100644
index 0000000..de95030
--- /dev/null
+++ b/webAO/viewport/viewport.ts
@@ -0,0 +1,495 @@
+import { client, delay } from "../client";
+import { UPDATE_INTERVAL } from "../client";
+import setEmote from "../client/setEmote";
+import { safeTags } from "../encoding";
+import { AO_HOST } from "../client/aoHost";
+import { Viewport } from './interfaces/Viewport'
+import { createBlipsChannels } from './utils/createBlipChannels'
+import { defaultChatMsg } from './constants/defaultChatMsg'
+import { createMusic } from './utils/createMusic'
+import { createSfxAudio } from './utils/createSfxAudio'
+import { createShoutAudio } from './utils/createShoutAudio'
+import { createTestimonyAudio } from './utils/createTestimonyAudio'
+import { Testimony } from './interfaces/Testimony'
+import { COLORS } from './constants/colors'
+import { set_side } from './utils/setSide'
+import { ChatMsg } from "./interfaces/ChatMsg";
+import { setStartFirstTickCheck, setStartSecondTickCheck, startFirstTickCheck, startSecondTickCheck } from "./utils/handleICSpeaking";
+
+const viewport = (): Viewport => {
+ let animating = false;
+ let blipChannels = createBlipsChannels();
+ let chatmsg = defaultChatMsg;
+ let currentBlipChannel = 0;
+ let lastChar = "";
+ let lastEvi = 0;
+ let music = createMusic();
+ let musicVolume = 0;
+ let sfxAudio = createSfxAudio();
+ let sfxplayed = 0;
+ let shoutTimer = 0;
+ let shoutaudio = createShoutAudio();
+ let testimonyAudio = createTestimonyAudio();
+ let testimonyTimer = 0;
+ let testimonyUpdater: any;
+ let textnow = "";
+ let theme: string;
+ let tickTimer = 0;
+ let updater: any;
+ let backgroundName = "";
+ const getSfxAudio = () => sfxAudio;
+ const setSfxAudio = (value: HTMLAudioElement) => { sfxAudio = value };
+ const getBackgroundName = () => backgroundName;
+ const setBackgroundName = (value: string) => { backgroundName = value };
+ const getBackgroundFolder = () =>
+ `${AO_HOST}background/${encodeURI(backgroundName.toLowerCase())}/`;
+ const getTextNow = () => {return textnow}
+ const setTextNow = (val: string) => {textnow = val}
+ const getChatmsg = () => {return chatmsg}
+ const setChatmsg = (val: ChatMsg) => {chatmsg = val}
+ const getSfxPlayed = () => sfxplayed
+ const setSfxPlayed = (val: number) => {sfxplayed = val}
+ const getTickTimer = () => tickTimer
+ const setTickTimer = (val: number) => {tickTimer = val}
+ const getAnimating = () => animating
+ const setAnimating = (val: boolean) => {animating = val}
+ const getLastEvidence = () => lastEvi
+ const setLastEvidence = (val: number) => {lastEvi = val}
+ const setLastCharacter = (val: string) => {lastChar = val}
+ const getLastCharacter = () => lastChar
+ const getShoutTimer = () => shoutTimer
+ const setShoutTimer = (val: number) => {shoutTimer = val}
+ const getTheme = () => theme
+ const setTheme = (val: string) => {theme = val}
+ const getTestimonyTimer = () => testimonyTimer;
+ const setTestimonyTimer = (val: number) => {testimonyTimer = val}
+ const setTestimonyUpdater = (val: any) => {testimonyUpdater = val}
+ const getTestimonyUpdater = () => testimonyUpdater
+ const playSFX = async (sfxname: string, looping: boolean) => {
+ sfxAudio.pause();
+ sfxAudio.loop = looping;
+ sfxAudio.src = sfxname;
+ sfxAudio.play();
+ };
+ /**
+ * 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[client.testimonyID];
+ const resource = client.resources[testimony];
+ if (!resource) {
+ disposeTestimony();
+ return;
+ }
+
+ if (testimonyTimer >= resource.duration) {
+ disposeTestimony();
+ } else {
+ testimonyUpdater = setTimeout(() => updateTestimony(), UPDATE_INTERVAL);
+ }
+ };
+ /**
+ * Dispose the testimony overlay
+ */
+ const disposeTestimony = () => {
+ client.testimonyID = 0;
+ testimonyTimer = 0;
+ document.getElementById("client_testimony").style.opacity = "0";
+ clearTimeout(testimonyUpdater);
+ };
+ const handleTextTick = async (charLayers: HTMLImageElement) => {
+ 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);
+ 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]);
+ }
+ }
+ // 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);
+ if (textnow === chatmsg.content) {
+ return;
+ }
+
+ const gamewindow = document.getElementById("client_gamewindow");
+ const waitingBox = document.getElementById("client_chatwaiting");
+ const eviBox = <HTMLImageElement>document.getElementById("client_evi");
+ const shoutSprite = <HTMLImageElement>(
+ document.getElementById("client_shout")
+ );
+ const effectlayer = <HTMLImageElement>document.getElementById("client_fg");
+ const chatBoxInner = document.getElementById("client_inner_chat");
+ let charLayers = <HTMLImageElement>document.getElementById("client_char");
+ let pairLayers = <HTMLImageElement>(
+ 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 = <HTMLImageElement>(
+ document.getElementById(`client_${chatmsg.side}_char`)
+ );
+ pairLayers = <HTMLImageElement>(
+ 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
+ setStartFirstTickCheck(false)
+ setStartSecondTickCheck(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(
+ client.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;
+ };
+
+ return {
+ getTextNow,
+ setTextNow,
+ getChatmsg,
+ setChatmsg,
+ getSfxPlayed,
+ setSfxPlayed,
+ setTickTimer,
+ getTickTimer,
+ setAnimating,
+ getAnimating,
+ getLastEvidence,
+ setLastEvidence,
+ setLastCharacter,
+ getLastCharacter,
+ getShoutTimer,
+ setShoutTimer,
+ setTheme,
+ getTheme,
+ setTestimonyTimer,
+ getTestimonyTimer,
+ setTestimonyUpdater,
+ getTestimonyUpdater,
+ testimonyAudio,
+ chat_tick,
+ playSFX,
+ set_side,
+ setBackgroundName,
+ updateTestimony,
+ disposeTestimony,
+ handleTextTick,
+ getBackgroundFolder,
+ getBackgroundName,
+ getSfxAudio,
+ setSfxAudio,
+ blipChannels,
+ music,
+ musicVolume,
+ shoutaudio,
+ updater,
+ };
+};
+
+export default viewport;