aboutsummaryrefslogtreecommitdiff
path: root/webAO/packets
diff options
context:
space:
mode:
authorstonedDiscord <Tukz@gmx.de>2022-10-12 18:25:14 +0200
committerstonedDiscord <Tukz@gmx.de>2022-10-12 18:25:14 +0200
commit8a7942c0565298c29edbf0b271d5d7c7f9e56fd8 (patch)
tree67b788f40b7a4713904836de05f0e5876c32bd79 /webAO/packets
parenta83c8962b68f2cc0a0e22d988b8ff030057454e5 (diff)
parent82983e0c38383ec2602b4f41327342d1c8d0a8fd (diff)
Merge branch 'master' into 2fa
Diffstat (limited to 'webAO/packets')
-rw-r--r--webAO/packets/handlers/handleARUP.ts42
-rw-r--r--webAO/packets/handlers/handleASS.ts10
-rw-r--r--webAO/packets/handlers/handleBB.ts11
-rw-r--r--webAO/packets/handlers/handleBD.ts14
-rw-r--r--webAO/packets/handlers/handleBN.ts84
-rw-r--r--webAO/packets/handlers/handleCC.ts9
-rw-r--r--webAO/packets/handlers/handleCI.ts27
-rw-r--r--webAO/packets/handlers/handleCT.ts17
-rw-r--r--webAO/packets/handlers/handleCharsCheck.ts17
-rw-r--r--webAO/packets/handlers/handleDONE.ts16
-rw-r--r--webAO/packets/handlers/handleEI.ts30
-rw-r--r--webAO/packets/handlers/handleEM.ts43
-rw-r--r--webAO/packets/handlers/handleFA.ts15
-rw-r--r--webAO/packets/handlers/handleFL.ts48
-rw-r--r--webAO/packets/handlers/handleFM.ts16
-rw-r--r--webAO/packets/handlers/handleHI.ts14
-rw-r--r--webAO/packets/handlers/handleHP.ts23
-rw-r--r--webAO/packets/handlers/handleID.ts24
-rw-r--r--webAO/packets/handlers/handleJD.ts13
-rw-r--r--webAO/packets/handlers/handleKB.ts13
-rw-r--r--webAO/packets/handlers/handleKK.ts10
-rw-r--r--webAO/packets/handlers/handleLE.ts35
-rw-r--r--webAO/packets/handlers/handleMC.ts43
-rw-r--r--webAO/packets/handlers/handleMM.ts8
-rw-r--r--webAO/packets/handlers/handleMS.ts165
-rw-r--r--webAO/packets/handlers/handlePN.ts9
-rw-r--r--webAO/packets/handlers/handlePV.ts85
-rw-r--r--webAO/packets/handlers/handleRC.ts10
-rw-r--r--webAO/packets/handlers/handleRD.ts18
-rw-r--r--webAO/packets/handlers/handleRM.ts10
-rw-r--r--webAO/packets/handlers/handleRMC.ts24
-rw-r--r--webAO/packets/handlers/handleRT.ts25
-rw-r--r--webAO/packets/handlers/handleSC.ts38
-rw-r--r--webAO/packets/handlers/handleSI.ts41
-rw-r--r--webAO/packets/handlers/handleSM.ts41
-rw-r--r--webAO/packets/handlers/handleSP.ts8
-rw-r--r--webAO/packets/handlers/handleTI.ts21
-rw-r--r--webAO/packets/handlers/handleZZ.ts23
-rw-r--r--webAO/packets/handlers/handleackMS.ts8
-rw-r--r--webAO/packets/handlers/handleaskchaa.ts10
-rw-r--r--webAO/packets/ms.js28
-rw-r--r--webAO/packets/packetHandler.ts3
-rw-r--r--webAO/packets/packets.ts86
43 files changed, 1207 insertions, 28 deletions
diff --git a/webAO/packets/handlers/handleARUP.ts b/webAO/packets/handlers/handleARUP.ts
new file mode 100644
index 0000000..97db9cc
--- /dev/null
+++ b/webAO/packets/handlers/handleARUP.ts
@@ -0,0 +1,42 @@
+import { client } from "../../client";
+import { safeTags } from "../../encoding";
+
+/**
+ * Handle the change of players in an area.
+ * @param {Array} args packet arguments
+ */
+export const handleARUP = (args: string[]) => {
+ args = args.slice(1);
+ for (let i = 0; i < args.length - 2; i++) {
+ if (client.areas[i]) {
+ // the server sends us ARUP before we even get the area list
+ const thisarea = document.getElementById(`area${i}`)!;
+ switch (Number(args[0])) {
+ case 0: // playercount
+ client.areas[i].players = Number(args[i + 1]);
+ break;
+ case 1: // status
+ client.areas[i].status = safeTags(args[i + 1]);
+ break;
+ case 2:
+ client.areas[i].cm = safeTags(args[i + 1]);
+ break;
+ case 3:
+ client.areas[i].locked = safeTags(args[i + 1]);
+ break;
+ }
+
+ thisarea.className = `area-button area-${client.areas[
+ i
+ ].status.toLowerCase()}`;
+
+ thisarea.innerText = `${client.areas[i].name} (${client.areas[i].players}) [${client.areas[i].status}]`;
+
+ thisarea.title =
+ `Players: ${client.areas[i].players}\n` +
+ `Status: ${client.areas[i].status}\n` +
+ `CM: ${client.areas[i].cm}\n` +
+ `Area lock: ${client.areas[i].locked}`;
+ }
+ }
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleASS.ts b/webAO/packets/handlers/handleASS.ts
new file mode 100644
index 0000000..ea8a0d3
--- /dev/null
+++ b/webAO/packets/handlers/handleASS.ts
@@ -0,0 +1,10 @@
+import { setAOhost } from "../../client/aoHost";
+
+
+/**
+* new asset url!!
+* @param {Array} args packet arguments
+*/
+export const handleASS = (args: string[]) => {
+ setAOhost(args[1]);
+}
diff --git a/webAO/packets/handlers/handleBB.ts b/webAO/packets/handlers/handleBB.ts
new file mode 100644
index 0000000..c12c4f6
--- /dev/null
+++ b/webAO/packets/handlers/handleBB.ts
@@ -0,0 +1,11 @@
+import { safeTags } from "../../encoding";
+
+
+/**
+ * Handles the warning packet
+ * on client this spawns a message box you can't close for 2 seconds
+ * @param {Array} args ban reason
+ */
+export const handleBB = (args: string[]) => {
+ alert(safeTags(args[1]));
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleBD.ts b/webAO/packets/handlers/handleBD.ts
new file mode 100644
index 0000000..dbfb54b
--- /dev/null
+++ b/webAO/packets/handlers/handleBD.ts
@@ -0,0 +1,14 @@
+import { setBanned } from "../../client";
+import { safeTags } from "../../encoding";
+import { handleBans } from '../../client/handleBans'
+
+
+/**
+ * Handles the banned packet
+ * this one is sent when you try to reconnect but you're banned
+ * @param {Array} args ban reason
+ */
+export const handleBD = (args: string[]) => {
+ handleBans("Banned", safeTags(args[1]));
+ setBanned(true);
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleBN.ts b/webAO/packets/handlers/handleBN.ts
new file mode 100644
index 0000000..3c5d95f
--- /dev/null
+++ b/webAO/packets/handlers/handleBN.ts
@@ -0,0 +1,84 @@
+import { client } from "../../client";
+import { AO_HOST } from "../../client/aoHost";
+import { safeTags } from "../../encoding";
+import { updateBackgroundPreview } from '../../dom/updateBackgroundPreview'
+import { getIndexFromSelect } from '../../dom/getIndexFromSelect'
+import tryUrls from "../../utils/tryUrls";
+
+
+/**
+ * Handles a background change.
+ * @param {Array} args packet arguments
+ */
+
+export const handleBN = (args: string[]) => {
+ const bgFromArgs = safeTags(args[1]);
+ client.viewport.setBackgroundName(bgFromArgs);
+ const bgfolder = client.viewport.getBackgroundFolder();
+ const bg_index = getIndexFromSelect(
+ "bg_select",
+ client.viewport.getBackgroundName()
+ );
+ (<HTMLSelectElement>document.getElementById("bg_select")).selectedIndex =
+ bg_index;
+ updateBackgroundPreview();
+ if (bg_index === 0) {
+ (<HTMLInputElement>document.getElementById("bg_filename")).value =
+ client.viewport.getBackgroundName();
+ }
+
+ tryUrls(
+ `${AO_HOST}background/${encodeURI(args[1].toLowerCase())}/defenseempty`
+ ).then((resp) => {
+ (<HTMLImageElement>document.getElementById("bg_preview")).src = resp;
+ });
+ tryUrls(`${bgfolder}defensedesk`).then((resp) => {
+ (<HTMLImageElement>document.getElementById("client_def_bench")).src =
+ resp;
+ });
+ tryUrls(`${bgfolder}stand`).then((resp) => {
+ (<HTMLImageElement>document.getElementById("client_wit_bench")).src =
+ resp;
+ });
+ tryUrls(`${bgfolder}prosecutiondesk`).then((resp) => {
+ (<HTMLImageElement>document.getElementById("client_pro_bench")).src =
+ resp;
+ });
+ tryUrls(`${bgfolder}full`).then((resp) => {
+ (<HTMLImageElement>document.getElementById("client_court")).src = resp;
+ });
+ tryUrls(`${bgfolder}defenseempty`).then((resp) => {
+ (<HTMLImageElement>document.getElementById("client_court_def")).src =
+ resp;
+ });
+ tryUrls(`${bgfolder}transition_def`).then((resp) => {
+ (<HTMLImageElement>document.getElementById("client_court_deft")).src =
+ resp;
+ });
+ tryUrls(`${bgfolder}witnessempty`).then((resp) => {
+ (<HTMLImageElement>document.getElementById("client_court_wit")).src =
+ resp;
+ });
+ tryUrls(`${bgfolder}transition_pro`).then((resp) => {
+ (<HTMLImageElement>document.getElementById("client_court_prot")).src =
+ resp;
+ });
+ tryUrls(`${bgfolder}prosecutorempty`).then((resp) => {
+ (<HTMLImageElement>document.getElementById("client_court_pro")).src =
+ resp;
+ });
+
+ if (client.charID === -1) {
+ client.viewport.set_side({
+ position: "jud",
+ showSpeedLines: false,
+ showDesk: true,
+ });
+ } else {
+ client.viewport.set_side({
+ position: client.chars[client.charID].side,
+ showSpeedLines: false,
+ showDesk: true,
+ });
+ }
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleCC.ts b/webAO/packets/handlers/handleCC.ts
new file mode 100644
index 0000000..36bcdc7
--- /dev/null
+++ b/webAO/packets/handlers/handleCC.ts
@@ -0,0 +1,9 @@
+import { client } from "../../client";
+
+/**
+ * What? you want a character??
+ * @param {Array} args packet arguments
+ */
+export const handleCC = (args: string[]) => {
+ client.sender.sendSelf(`PV#1#CID#${args[2]}#%`);
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleCI.ts b/webAO/packets/handlers/handleCI.ts
new file mode 100644
index 0000000..cb693bc
--- /dev/null
+++ b/webAO/packets/handlers/handleCI.ts
@@ -0,0 +1,27 @@
+import { client } from '../../client'
+import { handleCharacterInfo } from '../../client/handleCharacterInfo'
+/**
+ * Handles incoming character information, bundling multiple characters
+ * per packet.
+ * CI#0#Phoenix&description&&&&&#1#Miles ...
+ * @param {Array} args packet arguments
+ */
+export const handleCI = (args: string[]) => {
+ // Loop through the 10 characters that were sent
+
+ for (let i = 2; i <= args.length - 2; i++) {
+ if (i % 2 === 0) {
+ document.getElementById(
+ "client_loadingtext"
+ )!.innerHTML = `Loading Character ${args[1]}/${client.char_list_length}`;
+ const chargs = args[i].split("&");
+ const charid = Number(args[i - 1]);
+ (<HTMLProgressElement>(
+ document.getElementById("client_loadingbar")
+ )).value = charid;
+ setTimeout(() => handleCharacterInfo(chargs, charid), 500);
+ }
+ }
+ // Request the next pack
+ client.sender.sendServer(`AN#${Number(args[1]) / 10 + 1}#%`);
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleCT.ts b/webAO/packets/handlers/handleCT.ts
new file mode 100644
index 0000000..cff9b24
--- /dev/null
+++ b/webAO/packets/handlers/handleCT.ts
@@ -0,0 +1,17 @@
+import queryParser from '../../utils/queryParser'
+import { prepChat } from '../../encoding'
+let { mode } = queryParser();
+
+/**
+ * Handles an out-of-character chat message.
+ * @param {Array} args packet arguments
+ */
+export const handleCT = (args: string[]) => {
+ if (mode !== "replay") {
+ const oocLog = document.getElementById("client_ooclog")!;
+ oocLog.innerHTML += `${prepChat(args[1])}: ${prepChat(args[2])}\r\n`;
+ if (oocLog.scrollTop > oocLog.scrollHeight - 600) {
+ oocLog.scrollTop = oocLog.scrollHeight;
+ }
+ }
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleCharsCheck.ts b/webAO/packets/handlers/handleCharsCheck.ts
new file mode 100644
index 0000000..2d891ef
--- /dev/null
+++ b/webAO/packets/handlers/handleCharsCheck.ts
@@ -0,0 +1,17 @@
+import { client } from "../../client";
+
+/**
+ * Handles the list of all used and vacant characters.
+ * @param {Array} args list of all characters represented as a 0 for free or a -1 for taken
+ */
+export const handleCharsCheck = (args: string[]) => {
+ for (let i = 0; i < client.char_list_length; i++) {
+ const img = document.getElementById(`demo_${i}`)!;
+
+ if (args[i + 1] === "-1") {
+ img.style.opacity = "0.25";
+ } else if (args[i + 1] === "0") {
+ img.style.opacity = "1";
+ }
+ }
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleDONE.ts b/webAO/packets/handlers/handleDONE.ts
new file mode 100644
index 0000000..3cafd5e
--- /dev/null
+++ b/webAO/packets/handlers/handleDONE.ts
@@ -0,0 +1,16 @@
+import queryParser from "../../utils/queryParser";
+
+let { mode } = queryParser()
+ /**
+ * Handles the handshake completion packet, meaning the player
+ * is ready to select a character.
+ *
+ * @param {Array} args packet arguments
+ */
+export const handleDONE = (_args: string[]) => {
+ document.getElementById("client_loading")!.style.display = "none";
+ if (mode === "watch") {
+ // Spectators don't need to pick a character
+ document.getElementById("client_waiting")!.style.display = "none";
+ }
+ } \ No newline at end of file
diff --git a/webAO/packets/handlers/handleEI.ts b/webAO/packets/handlers/handleEI.ts
new file mode 100644
index 0000000..428baf1
--- /dev/null
+++ b/webAO/packets/handlers/handleEI.ts
@@ -0,0 +1,30 @@
+import { client } from '../../client'
+import { AO_HOST } from '../../client/aoHost';
+import { prepChat, safeTags } from '../../encoding';
+
+/**
+ * Handles incoming evidence information, containing only one evidence
+ * item per packet.
+ *
+ * EI#id#name&description&type&image&##%
+ *
+ * @param {Array} args packet arguments
+ */
+export const handleEI = (args: string[]) => {
+ document.getElementById(
+ "client_loadingtext"
+ )!.innerHTML = `Loading Evidence ${args[1]}/${client.evidence_list_length}`;
+ const evidenceID = Number(args[1]);
+ (<HTMLProgressElement>document.getElementById("client_loadingbar")).value =
+ client.char_list_length + evidenceID;
+
+ const arg = args[2].split("&");
+ client.evidences[evidenceID] = {
+ name: prepChat(arg[0]),
+ desc: prepChat(arg[1]),
+ filename: safeTags(arg[3]),
+ icon: `${AO_HOST}evidence/${encodeURI(arg[3].toLowerCase())}`,
+ };
+
+ client.sender.sendServer("AE" + (evidenceID + 1) + "#%");
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleEM.ts b/webAO/packets/handlers/handleEM.ts
new file mode 100644
index 0000000..5e49ea4
--- /dev/null
+++ b/webAO/packets/handlers/handleEM.ts
@@ -0,0 +1,43 @@
+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';
+
+/**
+ * Handles incoming music information, containing multiple entries
+ * per packet.
+ * @param {Array} args packet arguments
+ */
+export const handleEM = (args: string[]) => {
+ document.getElementById("client_loadingtext")!.innerHTML = "Loading Music";
+ if (args[1] === "0") {
+ client.resetMusicList();
+ client.resetAreaList();
+ client.musics_time = false;
+ }
+
+ for (let i = 2; i < args.length - 1; i++) {
+ if (i % 2 === 0) {
+ const trackname = safeTags(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) {
+ addTrack(trackname);
+ } else if (isAudio(trackname)) {
+ client.musics_time = true;
+ fix_last_area();
+ addTrack(trackname);
+ } else {
+ createArea(trackindex, trackname);
+ }
+ }
+ }
+
+ // get the next batch of tracks
+ client.sender.sendServer(`AM#${Number(args[1]) / 10 + 1}#%`);
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleFA.ts b/webAO/packets/handlers/handleFA.ts
new file mode 100644
index 0000000..7a373e8
--- /dev/null
+++ b/webAO/packets/handlers/handleFA.ts
@@ -0,0 +1,15 @@
+import { client } from '../../client'
+import { createArea } from '../../client/createArea';
+import { safeTags } from '../../encoding';
+
+/**
+ * Handles updated area list
+ * @param {Array} args packet arguments
+ */
+export const handleFA = (args: string[]) => {
+ client.resetAreaList();
+
+ for (let i = 1; i < args.length - 1; i++) {
+ createArea(i - 1, safeTags(args[i]));
+ }
+}
diff --git a/webAO/packets/handlers/handleFL.ts b/webAO/packets/handlers/handleFL.ts
new file mode 100644
index 0000000..378d5a9
--- /dev/null
+++ b/webAO/packets/handlers/handleFL.ts
@@ -0,0 +1,48 @@
+import { setExtraFeatures } from "../../client";
+
+
+/**
+ * With this the server tells us which features it supports
+ * @param {Array} args list of features
+ */
+export const handleFL = (args: string[]) => {
+ console.info("Server-supported features:");
+ console.info(args);
+ setExtraFeatures(args);
+
+ if (args.includes("yellowtext")) {
+ const colorselect = <HTMLSelectElement>(
+ 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");
+ }
+
+ if (args.includes("cccc_ic_support")) {
+ document.getElementById("cccc")!.style.display = "";
+ document.getElementById("pairing")!.style.display = "";
+ }
+
+ if (args.includes("flipping")) {
+ document.getElementById("button_flip")!.style.display = "";
+ }
+
+ if (args.includes("looping_sfx")) {
+ document.getElementById("button_shake")!.style.display = "";
+ document.getElementById("2.7")!.style.display = "";
+ }
+
+ if (args.includes("effects")) {
+ document.getElementById("2.8")!.style.display = "";
+ }
+
+ if (args.includes("y_offset")) {
+ document.getElementById("y_offset")!.style.display = "";
+ }
+}
diff --git a/webAO/packets/handlers/handleFM.ts b/webAO/packets/handlers/handleFM.ts
new file mode 100644
index 0000000..fce10e3
--- /dev/null
+++ b/webAO/packets/handlers/handleFM.ts
@@ -0,0 +1,16 @@
+import { client } from "../../client";
+import { addTrack } from "../../client/addTrack";
+import { safeTags } from "../../encoding";
+
+/**
+ * Handles updated music list
+ * @param {Array} args packet arguments
+ */
+export const handleFM = (args: string[]) => {
+ client.resetMusicList();
+
+ for (let i = 1; i < args.length - 1; i++) {
+ // Check when found the song for the first time
+ addTrack(safeTags(args[i]));
+ }
+}
diff --git a/webAO/packets/handlers/handleHI.ts b/webAO/packets/handlers/handleHI.ts
new file mode 100644
index 0000000..b4f00a8
--- /dev/null
+++ b/webAO/packets/handlers/handleHI.ts
@@ -0,0 +1,14 @@
+import { client } from "../../client";
+const version = process.env.npm_package_version;
+
+
+/**
+ * Handle the player
+ * @param {Array} args packet arguments
+ */
+export const handleHI = (_args: string[]) => {
+ client.sender.sendSelf(`ID#1#webAO#${version}#%`);
+ client.sender.sendSelf(
+ "FL#fastloading#yellowtext#cccc_ic_support#flipping#looping_sfx#effects#%"
+ );
+}
diff --git a/webAO/packets/handlers/handleHP.ts b/webAO/packets/handlers/handleHP.ts
new file mode 100644
index 0000000..f365590
--- /dev/null
+++ b/webAO/packets/handlers/handleHP.ts
@@ -0,0 +1,23 @@
+import { client } from "../../client";
+
+
+ /**
+ * Handles a change in the health bars' states.
+ * @param {Array} args packet arguments
+ */
+export const handleHP = (args: string[]) => {
+ const percent_hp = Number(args[2]) * 10;
+ let healthbox;
+ if (args[1] === "1") {
+ // Def hp
+ client.hp[0] = Number(args[2]);
+ healthbox = document.getElementById("client_defense_hp");
+ } else {
+ // Pro hp
+ client.hp[1] = Number(args[2]);
+ healthbox = document.getElementById("client_prosecutor_hp");
+ }
+ (<HTMLElement>(
+ healthbox.getElementsByClassName("health-bar")[0]
+ )).style.width = `${percent_hp}%`;
+ } \ No newline at end of file
diff --git a/webAO/packets/handlers/handleID.ts b/webAO/packets/handlers/handleID.ts
new file mode 100644
index 0000000..cd7b6ed
--- /dev/null
+++ b/webAO/packets/handlers/handleID.ts
@@ -0,0 +1,24 @@
+import { client, setOldLoading } from "../../client";
+
+
+/**
+ * Identifies the server and issues a playerID
+ * @param {Array} args packet arguments
+ */
+export const handleID = (args: string[]) => {
+ client.playerID = Number(args[1]);
+ const serverSoftware = args[2].split("&")[0];
+ let serverVersion;
+ if (serverSoftware === "serverD") {
+ serverVersion = args[2].split("&")[1];
+ } else if (serverSoftware === "webAO") {
+ setOldLoading(false);
+ client.sender.sendSelf("PN#0#1#%");
+ } else {
+ serverVersion = args[3];
+ }
+
+ if (serverSoftware === "serverD" && serverVersion === "1377.152") {
+ setOldLoading(true);
+ } // bugged version
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleJD.ts b/webAO/packets/handlers/handleJD.ts
new file mode 100644
index 0000000..98d7988
--- /dev/null
+++ b/webAO/packets/handlers/handleJD.ts
@@ -0,0 +1,13 @@
+/**
+* show/hide judge controls
+* @param {number} show either a 1 or a 0
+*/
+export const handleJD = (args: string[]) => {
+ if (Number(args[1]) === 1) {
+ document.getElementById("judge_action")!.style.display = "inline-table";
+ document.getElementById("no_action")!.style.display = "none";
+ } else {
+ document.getElementById("judge_action")!.style.display = "none";
+ document.getElementById("no_action")!.style.display = "inline-table";
+ }
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleKB.ts b/webAO/packets/handlers/handleKB.ts
new file mode 100644
index 0000000..b0aa2b2
--- /dev/null
+++ b/webAO/packets/handlers/handleKB.ts
@@ -0,0 +1,13 @@
+import { setBanned } from "../../client";
+import { safeTags } from "../../encoding";
+import { handleBans } from '../../client/handleBans'
+
+/**
+ * Handles the banned packet
+ * this one is sent when you are kicked off the server
+ * @param {Array} args ban reason
+ */
+export const handleKB = (args: string[]) => {
+ handleBans("Banned", safeTags(args[1]));
+ setBanned(true);
+}
diff --git a/webAO/packets/handlers/handleKK.ts b/webAO/packets/handlers/handleKK.ts
new file mode 100644
index 0000000..c8a97b1
--- /dev/null
+++ b/webAO/packets/handlers/handleKK.ts
@@ -0,0 +1,10 @@
+import { safeTags } from "../../encoding";
+import { handleBans } from '../../client/handleBans'
+
+/**
+ * Handles the kicked packet
+ * @param {Array} args kick reason
+ */
+export const handleKK = (args: string[]) => {
+ handleBans("Kicked", safeTags(args[1]));
+}
diff --git a/webAO/packets/handlers/handleLE.ts b/webAO/packets/handlers/handleLE.ts
new file mode 100644
index 0000000..1eaeb27
--- /dev/null
+++ b/webAO/packets/handlers/handleLE.ts
@@ -0,0 +1,35 @@
+import { client } from '../../client'
+import { AO_HOST } from '../../client/aoHost';
+import { prepChat, safeTags } from '../../encoding';
+
+/**
+ * Handles incoming evidence list, all evidences at once
+ * item per packet.
+ *
+ * @param {Array} args packet arguments
+ */
+export const handleLE = (args: string[]) => {
+ client.evidences = [];
+ for (let i = 1; i < args.length - 1; i++) {
+ (<HTMLProgressElement>(
+ document.getElementById("client_loadingbar")
+ )).value = client.char_list_length + i;
+ const arg = args[i].split("&");
+ client.evidences[i - 1] = {
+ name: prepChat(arg[0]),
+ desc: prepChat(arg[1]),
+ filename: safeTags(arg[2]),
+ icon: `${AO_HOST}evidence/${encodeURI(arg[2].toLowerCase())}`,
+ };
+ }
+
+ const evidence_box = document.getElementById("evidences")!;
+ evidence_box.innerHTML = "";
+ for (let i = 1; i <= client.evidences.length; i++) {
+ evidence_box.innerHTML += `<img src="${client.evidences[i - 1].icon}"
+ id="evi_${i}"
+ alt="${client.evidences[i - 1].name}"
+ class="evi_icon"
+ onclick="pickEvidence(${i})">`;
+ }
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleMC.ts b/webAO/packets/handlers/handleMC.ts
new file mode 100644
index 0000000..aeb178d
--- /dev/null
+++ b/webAO/packets/handlers/handleMC.ts
@@ -0,0 +1,43 @@
+import { prepChat } from "../../encoding";
+import { client } from '../../client'
+import { AO_HOST } from "../../client/aoHost";
+import { appendICLog } from '../../client/appendICLog'
+
+/**
+ * Handles a music change to an arbitrary resource.
+ * @param {Array} args packet arguments
+ */
+export const handleMC = (args: string[]) => {
+ const track = prepChat(args[1]);
+ let charID = Number(args[2]);
+ const showname = args[3] || "";
+ const looping = Boolean(args[4]);
+ const channel = Number(args[5]) || 0;
+ // const fading = Number(args[6]) || 0; // unused in web
+
+ const music = client.viewport.music[channel];
+ let musicname;
+ music.pause();
+ if (track.startsWith("http")) {
+ music.src = track;
+ } else {
+ music.src = `${AO_HOST}sounds/music/${encodeURI(track.toLowerCase())}`;
+ }
+ music.loop = looping;
+ music.play();
+
+ try {
+ musicname = client.chars[charID].name;
+ } catch (e) {
+ charID = -1;
+ }
+
+ if (charID >= 0) {
+ musicname = client.chars[charID].name;
+ appendICLog(`${musicname} changed music to ${track}`);
+ } else {
+ appendICLog(`The music was changed to ${track}`);
+ }
+
+ document.getElementById("client_trackstatustext")!.innerText = track;
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleMM.ts b/webAO/packets/handlers/handleMM.ts
new file mode 100644
index 0000000..077140f
--- /dev/null
+++ b/webAO/packets/handlers/handleMM.ts
@@ -0,0 +1,8 @@
+
+/**
+ * Handles the "MusicMode" packet
+ * @param {Array} args packet arguments
+ */
+export const handleMM = (_args: string[]) => {
+ // It's unused nowadays, as preventing people from changing the music is now serverside
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleMS.ts b/webAO/packets/handlers/handleMS.ts
new file mode 100644
index 0000000..92d65db
--- /dev/null
+++ b/webAO/packets/handlers/handleMS.ts
@@ -0,0 +1,165 @@
+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.getChatmsg().content) {
+ document.getElementById("client_inner_chat")!.innerHTML = "";
+
+ const char_id = Number(args[9]);
+ const char_name = safeTags(args[3]);
+
+ let msg_nameplate = args[3];
+ let msg_blips = "male";
+ let char_chatbox = "default";
+ let char_muted = false;
+
+ 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("&");
+ handleCharacterInfo(chargs, char_id);
+ }
+ }
+
+ 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 {
+ 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");
+ }
+
+ 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 (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]),
+ };
+ chatmsg = Object.assign(extra_cccc, 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]),
+ };
+ 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);
+ }
+ } 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: ["", "", ""],
+ };
+ 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();
+ }
+
+ handle_ic_speaking(chatmsg); // no await
+ }
+ }
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handlePN.ts b/webAO/packets/handlers/handlePN.ts
new file mode 100644
index 0000000..1b66fb9
--- /dev/null
+++ b/webAO/packets/handlers/handlePN.ts
@@ -0,0 +1,9 @@
+import { client } from "../../client";
+
+/**
+ * Indicates how many users are on this server
+ * @param {Array} args packet arguments
+ */
+export const handlePN = (_args: string[]) => {
+ client.sender.sendServer("askchaa#%");
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handlePV.ts b/webAO/packets/handlers/handlePV.ts
new file mode 100644
index 0000000..3c669b9
--- /dev/null
+++ b/webAO/packets/handlers/handlePV.ts
@@ -0,0 +1,85 @@
+import { client } from "../../client";
+import fileExists from "../../utils/fileExists";
+import { updateActionCommands } from '../../dom/updateActionCommands'
+import { pickEmotion } from '../../dom/pickEmotion'
+import { AO_HOST } from "../../client/aoHost";
+
+/**
+ * Handles the server's assignment of a character for the player to use.
+ * PV # playerID (unused) # CID # character ID
+ * @param {Array} args packet arguments
+ */
+export const handlePV = async (args: string[]) => {
+ client.charID = Number(args[3]);
+ document.getElementById("client_waiting")!.style.display = "none";
+ document.getElementById("client_charselect")!.style.display = "none";
+
+ const me = client.chars[client.charID];
+ client.selectedEmote = -1;
+ const { emotes } = client;
+ const emotesList = document.getElementById("client_emo")!;
+ emotesList.style.display = "";
+ emotesList.innerHTML = ""; // Clear emote box
+ const ini = me.inifile;
+ me.side = ini.options.side;
+ updateActionCommands(me.side);
+ if (ini.emotions.number === 0) {
+ emotesList.innerHTML = `<span
+ id="emo_0"
+ alt="unavailable"
+ class="emote_button">No emotes available</span>`;
+ } else {
+ for (let i = 1; i <= ini.emotions.number; i++) {
+ try {
+ const emoteinfo = ini.emotions[i].split("#");
+ let esfx;
+ let esfxd;
+ try {
+ esfx = ini.soundn[i] || "0";
+ esfxd = Number(ini.soundt[i]) || 0;
+ } catch (e) {
+ console.warn("ini sound is completly missing");
+ esfx = "0";
+ esfxd = 0;
+ }
+ // Make sure the asset server is case insensitive, or that everything on it is lowercase
+
+ emotes[i] = {
+ desc: emoteinfo[0].toLowerCase(),
+ preanim: emoteinfo[1].toLowerCase(),
+ emote: emoteinfo[2].toLowerCase(),
+ zoom: Number(emoteinfo[3]) || 0,
+ deskmod: Number(emoteinfo[4]) || 1,
+ sfx: esfx.toLowerCase(),
+ sfxdelay: esfxd,
+ frame_screenshake: "",
+ frame_realization: "",
+ frame_sfx: "",
+ button: `${AO_HOST}characters/${encodeURI(
+ me.name.toLowerCase()
+ )}/emotions/button${i}_off.png`,
+ };
+ emotesList.innerHTML += `<img src=${emotes[i].button}
+ id="emo_${i}"
+ alt="${emotes[i].desc}"
+ title="${emotes[i].desc}"
+ class="emote_button"
+ onclick="pickEmotion(${i})">`;
+ } catch (e) {
+ console.error(`missing emote ${i}`);
+ }
+ }
+ pickEmotion(1);
+ }
+
+ if (
+ await fileExists(
+ `${AO_HOST}characters/${encodeURI(me.name.toLowerCase())}/custom.gif`
+ )
+ ) {
+ document.getElementById("button_4")!.style.display = "";
+ } else {
+ document.getElementById("button_4")!.style.display = "none";
+ }
+
+}
diff --git a/webAO/packets/handlers/handleRC.ts b/webAO/packets/handlers/handleRC.ts
new file mode 100644
index 0000000..0b5679f
--- /dev/null
+++ b/webAO/packets/handlers/handleRC.ts
@@ -0,0 +1,10 @@
+import { client } from "../../client";
+import vanilla_character_arr from "../../constants/characters.js";
+
+/**
+ * we are asking ourselves what characters there are
+ * @param {Array} args packet arguments
+ */
+export const handleRC = (_args: string[]) => {
+ client.sender.sendSelf(`SC#${vanilla_character_arr.join("#")}#%`);
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleRD.ts b/webAO/packets/handlers/handleRD.ts
new file mode 100644
index 0000000..dde994c
--- /dev/null
+++ b/webAO/packets/handlers/handleRD.ts
@@ -0,0 +1,18 @@
+import { client } from "../../client";
+
+
+/**
+ * we are asking ourselves what characters there are
+ * @param {Array} args packet arguments
+ */
+export const handleRD = (_args: string[]) => {
+ client.sender.sendSelf("BN#gs4#%");
+ client.sender.sendSelf("DONE#%");
+ const ooclog = <HTMLInputElement>document.getElementById("client_ooclog");
+ ooclog.value = "";
+ ooclog.readOnly = false;
+
+ document.getElementById("client_oocinput")!.style.display = "none";
+ document.getElementById("client_replaycontrols")!.style.display =
+ "inline-block";
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleRM.ts b/webAO/packets/handlers/handleRM.ts
new file mode 100644
index 0000000..c18821b
--- /dev/null
+++ b/webAO/packets/handlers/handleRM.ts
@@ -0,0 +1,10 @@
+import {client} from '../../client'
+import vanilla_music_arr from "../../constants/music.js";
+
+ /**
+ * we are asking ourselves what characters there are
+ * @param {Array} args packet arguments
+ */
+export const handleRM = (_args: string[]) => {
+ client.sender.sendSelf(`SM#${vanilla_music_arr.join("#")}#%`);
+ } \ No newline at end of file
diff --git a/webAO/packets/handlers/handleRMC.ts b/webAO/packets/handlers/handleRMC.ts
new file mode 100644
index 0000000..ada1ad2
--- /dev/null
+++ b/webAO/packets/handlers/handleRMC.ts
@@ -0,0 +1,24 @@
+import { client } from '../../client'
+// TODO BUG:
+// this.viewport.music is an array. Therefore you must access elements
+/**
+ * Handles a music change to an arbitrary resource, with an offset in seconds.
+ * @param {Array} args packet arguments
+ */
+export const handleRMC = (args: string[]) => {
+ client.viewport.music.pause();
+ const { music } = client.viewport;
+ // Music offset + drift from song loading
+ music.totime = args[1];
+ music.offset = new Date().getTime() / 1000;
+ music.addEventListener(
+ "loadedmetadata",
+ () => {
+ music.currentTime += parseFloat(
+ music.totime + (new Date().getTime() / 1000 - music.offset)
+ ).toFixed(3);
+ music.play();
+ },
+ false
+ );
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleRT.ts b/webAO/packets/handlers/handleRT.ts
new file mode 100644
index 0000000..62ebb1e
--- /dev/null
+++ b/webAO/packets/handlers/handleRT.ts
@@ -0,0 +1,25 @@
+import { client } from "../../client";
+import { initTestimonyUpdater } from '../../viewport/utils/initTestimonyUpdater'
+
+/**
+ * Handles a testimony states.
+ * @param {Array} args packet arguments
+ */
+export const handleRT = (args: string[]) => {
+ const judgeid = Number(args[2]);
+ switch (args[1]) {
+ case "testimony1":
+ client.testimonyID = 1;
+ break;
+ case "testimony2":
+ // Cross Examination
+ client.testimonyID = 2;
+ break;
+ case "judgeruling":
+ client.testimonyID = 3 + judgeid;
+ break;
+ default:
+ console.warn("Invalid testimony");
+ }
+ initTestimonyUpdater();
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleSC.ts b/webAO/packets/handlers/handleSC.ts
new file mode 100644
index 0000000..b42a4cd
--- /dev/null
+++ b/webAO/packets/handlers/handleSC.ts
@@ -0,0 +1,38 @@
+import queryParser from "../../utils/queryParser";
+
+import { client } from '../../client'
+import { handleCharacterInfo } from "../../client/handleCharacterInfo";
+let { mode } = queryParser();
+
+/**
+ * Handles incoming character information, containing all characters
+ * in one packet.
+ * @param {Array} args packet arguments
+ */
+export const handleSC = async (args: string[]) => {
+ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
+
+ if (mode === "watch") {
+ // Spectators don't need to pick a character
+ document.getElementById("client_charselect")!.style.display = "none";
+ } else {
+ document.getElementById("client_charselect")!.style.display = "block";
+ }
+
+ document.getElementById("client_loadingtext")!.innerHTML =
+ "Loading Characters";
+ for (let i = 1; i < args.length - 1; i++) {
+ document.getElementById(
+ "client_loadingtext"
+ )!.innerHTML = `Loading Character ${i}/${client.char_list_length}`;
+ const chargs = args[i].split("&");
+ const charid = i - 1;
+ (<HTMLProgressElement>(
+ document.getElementById("client_loadingbar")
+ )).value = charid;
+ await sleep(0.1); // TODO: Too many network calls without this. net::ERR_INSUFFICIENT_RESOURCES
+ handleCharacterInfo(chargs, charid);
+ }
+ // We're done with the characters, request the music
+ client.sender.sendServer("RM#%");
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleSI.ts b/webAO/packets/handlers/handleSI.ts
new file mode 100644
index 0000000..b32fbc1
--- /dev/null
+++ b/webAO/packets/handlers/handleSI.ts
@@ -0,0 +1,41 @@
+import { client, extrafeatures, oldLoading } from "../../client";
+
+
+/**
+ * Received when the server announces its server info,
+ * but we use it as a cue to begin retrieving characters.
+ * @param {Array} args packet arguments
+ */
+export const handleSI = (args: string[]) => {
+ client.char_list_length = Number(args[1]);
+ client.char_list_length += 1; // some servers count starting from 0 some from 1...
+ client.evidence_list_length = Number(args[2]);
+ client.music_list_length = Number(args[3]);
+
+ (<HTMLProgressElement>document.getElementById("client_loadingbar")).max =
+ client.char_list_length +
+ client.evidence_list_length +
+ client.music_list_length;
+
+ // create the charselect grid, to be filled by the character loader
+ document.getElementById("client_chartable")!.innerHTML = "";
+
+ for (let i = 0; i < client.char_list_length; i++) {
+ const demothing = document.createElement("img");
+
+ demothing.className = "demothing";
+ demothing.id = `demo_${i}`;
+ const demoonclick = document.createAttribute("onclick");
+ demoonclick.value = `pickChar(${i})`;
+ demothing.setAttributeNode(demoonclick);
+
+ document.getElementById("client_chartable")!.appendChild(demothing);
+ }
+
+ // this is determined at the top of this file
+ if (!oldLoading && extrafeatures.includes("fastloading")) {
+ client.sender.sendServer("RC#%");
+ } else {
+ client.sender.sendServer("askchar2#%");
+ }
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleSM.ts b/webAO/packets/handlers/handleSM.ts
new file mode 100644
index 0000000..08bf7e0
--- /dev/null
+++ b/webAO/packets/handlers/handleSM.ts
@@ -0,0 +1,41 @@
+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
+ */
+export const handleSM = (args: string[]) => {
+ document.getElementById("client_loadingtext")!.innerHTML = "Loading Music ";
+ client.resetMusicList();
+ client.resetAreaList();
+
+ client.musics_time = false;
+
+ for (let i = 1; i < args.length - 1; i++) {
+ // Check when found the song for the first time
+ const trackname = args[i];
+ const trackindex = i - 1;
+ document.getElementById(
+ "client_loadingtext"
+ )!.innerHTML = `Loading Music ${i}/${client.music_list_length}`;
+ (<HTMLProgressElement>(
+ document.getElementById("client_loadingbar")
+ )).value = client.char_list_length + client.evidence_list_length + i;
+ if (client.musics_time) {
+ addTrack(trackname);
+ } else if (isAudio(trackname)) {
+ client.musics_time = true;
+ fix_last_area();
+ addTrack(trackname);
+ } else {
+ createArea(trackindex, trackname);
+ }
+
+ }
+
+ // Music done, carry on
+ client.sender.sendServer("RD#%");
+}
diff --git a/webAO/packets/handlers/handleSP.ts b/webAO/packets/handlers/handleSP.ts
new file mode 100644
index 0000000..e176eeb
--- /dev/null
+++ b/webAO/packets/handlers/handleSP.ts
@@ -0,0 +1,8 @@
+import { updateActionCommands } from '../../dom/updateActionCommands'
+/**
+* position change
+* @param {string} pos new position
+*/
+export const handleSP = (args: string[]) => {
+ updateActionCommands(args[1]);
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleTI.ts b/webAO/packets/handlers/handleTI.ts
new file mode 100644
index 0000000..e418088
--- /dev/null
+++ b/webAO/packets/handlers/handleTI.ts
@@ -0,0 +1,21 @@
+/**
+ * Handles a timer update
+ * @param {Array} args packet arguments
+ */
+export const handleTI = (args: string[]) => {
+ const timerid = Number(args[1]);
+ const type = Number(args[2]);
+ const timer_value = args[3];
+ switch (type) {
+ case 0:
+ //
+ case 1:
+ document.getElementById(`client_timer${timerid}`)!.innerText =
+ timer_value;
+ case 2:
+ document.getElementById(`client_timer${timerid}`)!.style.display = "";
+ case 3:
+ document.getElementById(`client_timer${timerid}`)!.style.display =
+ "none";
+ }
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleZZ.ts b/webAO/packets/handlers/handleZZ.ts
new file mode 100644
index 0000000..1c1cb1d
--- /dev/null
+++ b/webAO/packets/handlers/handleZZ.ts
@@ -0,0 +1,23 @@
+import { client } from "../../client";
+import { AO_HOST } from "../../client/aoHost";
+import { prepChat } from "../../encoding";
+
+
+/**
+ * Handles a modcall
+ * @param {Array} args packet arguments
+ */
+export const handleZZ = (args: string[]) => {
+ const oocLog = document.getElementById("client_ooclog")!;
+ oocLog.innerHTML += `$Alert: ${prepChat(args[1])}\r\n`;
+ if (oocLog.scrollTop > oocLog.scrollHeight - 60) {
+ oocLog.scrollTop = oocLog.scrollHeight;
+ }
+
+ client.viewport.getSfxAudio().pause();
+ const oldvolume = client.viewport.getSfxAudio().volume;
+ client.viewport.getSfxAudio().volume = 1;
+ client.viewport.getSfxAudio().src = `${AO_HOST}sounds/general/sfx-gallery.opus`;
+ client.viewport.getSfxAudio().play();
+ client.viewport.getSfxAudio().volume = oldvolume;
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleackMS.ts b/webAO/packets/handlers/handleackMS.ts
new file mode 100644
index 0000000..dcca118
--- /dev/null
+++ b/webAO/packets/handlers/handleackMS.ts
@@ -0,0 +1,8 @@
+import { resetICParams } from '../../client/resetICParams'
+
+/**
+* server got our message
+*/
+export const handleackMS = () => {
+ resetICParams();
+} \ No newline at end of file
diff --git a/webAO/packets/handlers/handleaskchaa.ts b/webAO/packets/handlers/handleaskchaa.ts
new file mode 100644
index 0000000..0f9e730
--- /dev/null
+++ b/webAO/packets/handlers/handleaskchaa.ts
@@ -0,0 +1,10 @@
+import { client } from "../../client";
+import vanilla_character_arr from "../../constants/characters.js";
+
+/**
+ * What? you want a character list from me??
+ * @param {Array} args packet arguments
+ */
+export const handleaskchaa = (_args: string[]) => {
+ client.sender.sendSelf(`SI#${vanilla_character_arr.length}#0#0#%`);
+}
diff --git a/webAO/packets/ms.js b/webAO/packets/ms.js
deleted file mode 100644
index 26851bd..0000000
--- a/webAO/packets/ms.js
+++ /dev/null
@@ -1,28 +0,0 @@
-export default {
- deskmod,
- preanim,
- name,
- emote,
- message,
- side,
- sfx_name,
- emote_modifier,
- sfx_delay,
- objection_modifier,
- evidence,
- flip,
- realization,
- text_color,
- showname,
- other_charid,
- self_hoffset,
- self_yoffset,
- noninterrupting_preanim,
- looping_sfx,
- screenshake,
- frame_screenshake,
- frame_realization,
- frame_sfx,
- additive,
- effect,
-};
diff --git a/webAO/packets/packetHandler.ts b/webAO/packets/packetHandler.ts
new file mode 100644
index 0000000..a9b567a
--- /dev/null
+++ b/webAO/packets/packetHandler.ts
@@ -0,0 +1,3 @@
+import { packets } from './packets'
+
+export const packetHandler = new Map(Object.entries(packets)) \ No newline at end of file
diff --git a/webAO/packets/packets.ts b/webAO/packets/packets.ts
new file mode 100644
index 0000000..79c43c1
--- /dev/null
+++ b/webAO/packets/packets.ts
@@ -0,0 +1,86 @@
+import { handleMS } from './handlers/handleMS';
+import { handleCT } from './handlers/handleCT'
+import { handleMC } from './handlers/handleMC'
+import { handleRMC } from './handlers/handleRMC'
+import { handleFL } from './handlers/handleFL'
+import { handleLE } from './handlers/handleLE'
+import { handleEM } from './handlers/handleEM'
+import { handleEI } from './handlers/handleEI'
+import { handleSC } from './handlers/handleSC'
+import { handleCI } from './handlers/handleCI'
+import { handleFM } from './handlers/handleFM'
+import { handleFA } from './handlers/handleFA'
+import { handleSM } from './handlers/handleSM'
+import { handleMM } from './handlers/handleMM'
+import { handleBD } from './handlers/handleBD'
+import { handleBB } from './handlers/handleBB'
+import { handleKB } from './handlers/handleKB'
+import { handleKK } from './handlers/handleKK'
+import { handleDONE } from './handlers/handleDONE'
+import { handleBN } from './handlers/handleBN'
+import { handleHP } from './handlers/handleHP'
+import { handleRT } from './handlers/handleRT'
+import { handleTI } from './handlers/handleTI'
+import { handleZZ } from './handlers/handleZZ'
+import { handleHI } from './handlers/handleHI'
+import { handleID } from './handlers/handleID'
+import { handlePN } from './handlers/handlePN'
+import { handleSI } from './handlers/handleSI'
+import { handleARUP } from './handlers/handleARUP'
+import { handleaskchaa } from './handlers/handleaskchaa'
+import { handleCC } from './handlers/handleCC'
+import { handleRC } from './handlers/handleRC'
+import { handleRM } from './handlers/handleRM'
+import { handleRD } from './handlers/handleRD'
+import { handleCharsCheck } from './handlers/handleCharsCheck'
+import { handlePV } from './handlers/handlePV'
+import { handleASS } from './handlers/handleASS'
+import { handleackMS } from './handlers/handleackMS'
+import { handleSP } from './handlers/handleSP'
+import { handleJD } from './handlers/handleJD'
+
+export const packets = {
+ "MS": handleMS,
+ "CT": handleCT,
+ "MC": handleMC,
+ "RMC": handleRMC,
+ "CI": handleCI,
+ "SC": handleSC,
+ "EI": handleEI,
+ "FL": handleFL,
+ "LE": handleLE,
+ "EM": handleEM,
+ "FM": handleFM,
+ "FA": handleFA,
+ "SM": handleSM,
+ "MM": handleMM,
+ "BD": handleBD,
+ "BB": handleBB,
+ "KB": handleKB,
+ "KK": handleKK,
+ "DONE": handleDONE,
+ "BN": handleBN,
+ "HP": handleHP,
+ "RT": handleRT,
+ "TI": handleTI,
+ "ZZ": handleZZ,
+ "HI": handleHI,
+ "ID": handleID,
+ "PN": handlePN,
+ "SI": handleSI,
+ "ARUP": handleARUP,
+ "askchaa": handleaskchaa,
+ "CC": handleCC,
+ "RC": handleRC,
+ "RM": handleRM,
+ "RD": handleRD,
+ "CharsCheck": handleCharsCheck,
+ "PV": handlePV,
+ "ASS": handleASS,
+ "ackMS": handleackMS,
+ "SP": handleSP,
+ "JD": handleJD,
+ "decryptor": () => { },
+ "CHECK": () => { },
+ "CH": () => { },
+} \ No newline at end of file