aboutsummaryrefslogtreecommitdiff
path: root/webAO/client
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/client
parenta83c8962b68f2cc0a0e22d988b8ff030057454e5 (diff)
parent82983e0c38383ec2602b4f41327342d1c8d0a8fd (diff)
Merge branch 'master' into 2fa
Diffstat (limited to 'webAO/client')
-rw-r--r--webAO/client/__tests__/setEmote.test.js11
-rw-r--r--webAO/client/addTrack.ts15
-rw-r--r--webAO/client/aoHost.ts (renamed from webAO/client/aoHost.js)6
-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.ts60
-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/index.ts68
-rw-r--r--webAO/client/sender/sendCharacter.ts11
-rw-r--r--webAO/client/sender/sendCheck.ts8
-rw-r--r--webAO/client/sender/sendDE.ts9
-rw-r--r--webAO/client/sender/sendEE.ts16
-rw-r--r--webAO/client/sender/sendHP.ts10
-rw-r--r--webAO/client/sender/sendIC.ts106
-rw-r--r--webAO/client/sender/sendMusic.ts10
-rw-r--r--webAO/client/sender/sendMusicChange.ts10
-rw-r--r--webAO/client/sender/sendOOC.ts33
-rw-r--r--webAO/client/sender/sendPE.ts14
-rw-r--r--webAO/client/sender/sendRT.ts9
-rw-r--r--webAO/client/sender/sendSelf.ts13
-rw-r--r--webAO/client/sender/sendServer.ts10
-rw-r--r--webAO/client/sender/sendZZ.ts13
-rw-r--r--webAO/client/setEmote.js40
-rw-r--r--webAO/client/setEmote.ts55
32 files changed, 866 insertions, 46 deletions
diff --git a/webAO/client/__tests__/setEmote.test.js b/webAO/client/__tests__/setEmote.test.js
index 1db13c9..53bb68d 100644
--- a/webAO/client/__tests__/setEmote.test.js
+++ b/webAO/client/__tests__/setEmote.test.js
@@ -9,10 +9,13 @@ jest.mock('../../utils/fileExists');
describe('setEmote', () => {
const AO_HOST = '';
Client.mockReturnValue({
- lastChar: 'long',
- chatmsg: {
- name: 'byte',
- },
+ viewport: {
+ lastChar: 'long',
+ chatmsg: {
+ name: 'byte',
+ },
+ }
+
});
const client = new Client('127.0.0.1');
const firstExtension = '.gif';
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/aoHost.js b/webAO/client/aoHost.ts
index b387608..9b0a768 100644
--- a/webAO/client/aoHost.js
+++ b/webAO/client/aoHost.ts
@@ -1,5 +1,7 @@
import queryParser from '../utils/queryParser'
let { asset } = queryParser();
const DEFAULT_HOST = 'http://attorneyoffline.de/base/';
-const AO_HOST = asset || DEFAULT_HOST
-export default AO_HOST
+export let AO_HOST = asset || DEFAULT_HOST
+export const setAOhost = (val: string) => {
+ AO_HOST = val
+}
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..e9772cb
--- /dev/null
+++ b/webAO/client/fetchLists.ts
@@ -0,0 +1,60 @@
+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 () => {
+ 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");
+ }
+}
+
+
+export const fetchEvidenceList = async () => {
+ 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");
+ }
+} \ 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/index.ts b/webAO/client/sender/index.ts
new file mode 100644
index 0000000..41a6bd5
--- /dev/null
+++ b/webAO/client/sender/index.ts
@@ -0,0 +1,68 @@
+import { sendIC } from "./sendIC";
+import { sendSelf } from './sendSelf'
+import { sendServer } from './sendServer'
+import { sendCheck } from './sendCheck'
+import {sendHP} from './sendHP'
+import {sendOOC} from './sendOOC'
+import {sendCharacter} from './sendCharacter'
+import {sendRT} from './sendRT'
+import {sendMusicChange} from './sendMusicChange'
+import {sendZZ} from './sendZZ'
+import {sendEE} from './sendEE'
+import {sendDE} from './sendDE'
+import {sendPE} from './sendPE'
+export interface ISender {
+ sendIC: (deskmod: number,
+ preanim: string,
+ name: string,
+ emote: string,
+ message: string,
+ side: string,
+ sfx_name: string,
+ emote_modifier: number,
+ sfx_delay: number,
+ objection_modifier: number,
+ evidence: number,
+ flip: boolean,
+ realization: boolean,
+ text_color: number,
+ showname: string,
+ other_charid: string,
+ self_hoffset: number,
+ self_yoffset: number,
+ noninterrupting_preanim: boolean,
+ looping_sfx: boolean,
+ screenshake: boolean,
+ frame_screenshake: string,
+ frame_realization: string,
+ frame_sfx: string,
+ additive: boolean,
+ effect: string) => void
+ sendSelf: (message: string) => void
+ sendServer: (message: string) => void
+ sendCheck: () => void
+ sendHP: (side: number, hp: number) => void
+ sendOOC: (message: string) => void
+ sendCharacter: (character: number) => void
+ sendRT: (testimony: string) => void
+ sendMusicChange: (track: string) => void
+ sendZZ: (msg: string) => void
+ sendEE: (id: number, name: string, desc: string, img: string) => void
+ sendDE: (id: number) => void
+ sendPE: (name: string, desc: string, img: string) => void
+}
+export const sender = {
+ sendIC,
+ sendSelf,
+ sendServer,
+ sendCheck,
+ sendHP,
+ sendOOC,
+ sendCharacter,
+ sendRT,
+ sendMusicChange,
+ sendZZ,
+ sendEE,
+ sendDE,
+ sendPE
+} \ No newline at end of file
diff --git a/webAO/client/sender/sendCharacter.ts b/webAO/client/sender/sendCharacter.ts
new file mode 100644
index 0000000..5e81727
--- /dev/null
+++ b/webAO/client/sender/sendCharacter.ts
@@ -0,0 +1,11 @@
+import { client } from "../../client";
+
+/**
+ * Requests to play as a specified character.
+ * @param {number} character the character ID
+ */
+export const sendCharacter = (character: number) => {
+ if (character === -1 || client.chars[character].name) {
+ client.sender.sendServer(`CC#${client.playerID}#${character}#web#%`);
+ }
+} \ No newline at end of file
diff --git a/webAO/client/sender/sendCheck.ts b/webAO/client/sender/sendCheck.ts
new file mode 100644
index 0000000..91b3a02
--- /dev/null
+++ b/webAO/client/sender/sendCheck.ts
@@ -0,0 +1,8 @@
+import { client } from "../../client";
+
+/**
+ * Sends a keepalive packet.
+ */
+export const sendCheck = () => {
+ client.sender.sendServer(`CH#${client.charID}#%`);
+}
diff --git a/webAO/client/sender/sendDE.ts b/webAO/client/sender/sendDE.ts
new file mode 100644
index 0000000..4d94d65
--- /dev/null
+++ b/webAO/client/sender/sendDE.ts
@@ -0,0 +1,9 @@
+import { client } from "../../client";
+
+/**
+ * Sends delete evidence command.
+ * @param {number} evidence id
+ */
+export const sendDE = (id: number) => {
+ client.sender.sendServer(`DE#${id}#%`);
+} \ No newline at end of file
diff --git a/webAO/client/sender/sendEE.ts b/webAO/client/sender/sendEE.ts
new file mode 100644
index 0000000..7c5bfe3
--- /dev/null
+++ b/webAO/client/sender/sendEE.ts
@@ -0,0 +1,16 @@
+import { client } from "../../client";
+import { escapeChat } from "../../encoding";
+
+
+/**
+ * Sends edit evidence command.
+ * @param {number} evidence id
+ * @param {string} evidence name
+ * @param {string} evidence description
+ * @param {string} evidence image filename
+ */
+export const sendEE = (id: number, name: string, desc: string, img: string) => {
+ client.sender.sendServer(
+ `EE#${id}#${escapeChat(name)}#${escapeChat(desc)}#${escapeChat(img)}#%`
+ );
+} \ No newline at end of file
diff --git a/webAO/client/sender/sendHP.ts b/webAO/client/sender/sendHP.ts
new file mode 100644
index 0000000..d007094
--- /dev/null
+++ b/webAO/client/sender/sendHP.ts
@@ -0,0 +1,10 @@
+import { client } from "../../client";
+
+/**
+ * Sends health point command.
+ * @param {number} side the position
+ * @param {number} hp the health point
+ */
+export const sendHP = (side: number, hp: number) => {
+ client.sender.sendServer(`HP#${side}#${hp}#%`);
+} \ No newline at end of file
diff --git a/webAO/client/sender/sendIC.ts b/webAO/client/sender/sendIC.ts
new file mode 100644
index 0000000..9064115
--- /dev/null
+++ b/webAO/client/sender/sendIC.ts
@@ -0,0 +1,106 @@
+import { extrafeatures } from "../../client";
+import { escapeChat } from "../../encoding";
+import {client} from '../../client'
+import queryParser from "../../utils/queryParser";
+let {mode} = queryParser()
+
+/**
+ * Sends an in-character chat message.
+ * @param {number} deskmod controls the desk
+ * @param {string} speaking who is speaking
+ * @param {string} name the name of the current character
+ * @param {string} silent whether or not it's silent
+ * @param {string} message the message to be sent
+ * @param {string} side the name of the side in the background
+ * @param {string} sfx_name the name of the sound effect
+ * @param {number} emote_modifier whether or not to zoom
+ * @param {number} sfx_delay the delay (in milliseconds) to play the sound effect
+ * @param {number} objection_modifier the number of the shout to play
+ * @param {string} evidence the filename of evidence to show
+ * @param {boolean} flip change to 1 to reverse sprite for position changes
+ * @param {boolean} realization screen flash effect
+ * @param {number} text_color text color
+ * @param {string} showname custom name to be displayed (optional)
+ * @param {number} other_charid paired character (optional)
+ * @param {number} self_offset offset to paired character (optional)
+ * @param {number} noninterrupting_preanim play the full preanim (optional)
+ */
+export const sendIC = (
+ deskmod: number,
+ preanim: string,
+ name: string,
+ emote: string,
+ message: string,
+ side: string,
+ sfx_name: string,
+ emote_modifier: number,
+ sfx_delay: number,
+ objection_modifier: number,
+ evidence: number,
+ flip: boolean,
+ realization: boolean,
+ text_color: number,
+ showname: string,
+ other_charid: string,
+ self_hoffset: number,
+ self_yoffset: number,
+ noninterrupting_preanim: boolean,
+ looping_sfx: boolean,
+ screenshake: boolean,
+ frame_screenshake: string,
+ frame_realization: string,
+ frame_sfx: string,
+ additive: boolean,
+ effect: string
+) => {
+ let extra_cccc = "";
+ let other_emote = "";
+ let other_offset = "";
+ let extra_27 = "";
+ let extra_28 = "";
+
+ if (extrafeatures.includes("cccc_ic_support")) {
+ const self_offset = extrafeatures.includes("y_offset")
+ ? `${self_hoffset}<and>${self_yoffset}`
+ : self_hoffset; // HACK: this should be an & but client fucked it up and all the servers adopted it
+ if (mode === "replay") {
+ other_emote = "##";
+ other_offset = "#0#0";
+ }
+ extra_cccc = `${escapeChat(
+ showname
+ )}#${other_charid}${other_emote}#${self_offset}${other_offset}#${Number(
+ noninterrupting_preanim
+ )}#`;
+
+ if (extrafeatures.includes("looping_sfx")) {
+ extra_27 = `${Number(looping_sfx)}#${Number(
+ screenshake
+ )}#${frame_screenshake}#${frame_realization}#${frame_sfx}#`;
+ if (extrafeatures.includes("effects")) {
+ extra_28 = `${Number(additive)}#${escapeChat(effect)}#`;
+ }
+ }
+ }
+
+ const serverMessage =
+ `MS#${deskmod}#${escapeChat(preanim)}#${escapeChat(name)}#${escapeChat(
+ emote
+ )}` +
+ `#${escapeChat(message)}#${escapeChat(side)}#${escapeChat(
+ sfx_name
+ )}#${emote_modifier}` +
+ `#${client.charID}#${sfx_delay}#${Number(objection_modifier)}#${Number(
+ evidence
+ )}#${Number(flip)}#${Number(
+ realization
+ )}#${text_color}#${extra_cccc}${extra_27}${extra_28}%`;
+
+ client.sender.sendServer(serverMessage);
+ if (mode === "replay") {
+ (<HTMLInputElement>(
+ document.getElementById("client_ooclog")
+ )).value += `wait#${(<HTMLInputElement>document.getElementById("client_replaytimer")).value
+ }#%\r\n`;
+ }
+} \ No newline at end of file
diff --git a/webAO/client/sender/sendMusic.ts b/webAO/client/sender/sendMusic.ts
new file mode 100644
index 0000000..eceba08
--- /dev/null
+++ b/webAO/client/sender/sendMusic.ts
@@ -0,0 +1,10 @@
+import { client } from "../../client";
+
+
+/**
+ * Requests to select a music track.
+ * @param {number?} song the song to be played
+ */
+export const sendMusic = (song: string) => {
+ client.sender.sendServer(`MC#${song}#${client.charID}#%`);
+} \ No newline at end of file
diff --git a/webAO/client/sender/sendMusicChange.ts b/webAO/client/sender/sendMusicChange.ts
new file mode 100644
index 0000000..50c6306
--- /dev/null
+++ b/webAO/client/sender/sendMusicChange.ts
@@ -0,0 +1,10 @@
+import { client } from "../../client";
+
+
+/**
+ * Requests to change the music to the specified track.
+ * @param {string} track the track ID
+ */
+export const sendMusicChange = (track: string) => {
+ client.sender.sendServer(`MC#${track}#${client.charID}#%`);
+}
diff --git a/webAO/client/sender/sendOOC.ts b/webAO/client/sender/sendOOC.ts
new file mode 100644
index 0000000..9674ad9
--- /dev/null
+++ b/webAO/client/sender/sendOOC.ts
@@ -0,0 +1,33 @@
+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
+ */
+export const sendOOC = (message: string) => {
+ setCookie(
+ "OOC_name",
+ (<HTMLInputElement>document.getElementById("OOC_name")).value
+ );
+ const oocName = `${escapeChat(
+ (<HTMLInputElement>document.getElementById("OOC_name")).value
+ )}`;
+ const oocMessage = `${escapeChat(message)}`;
+
+ const commands = {
+ "/save_chatlog": saveChatlogHandle,
+ };
+ const commandsMap = new Map(Object.entries(commands));
+
+ if (oocMessage && commandsMap.has(oocMessage.toLowerCase())) {
+ try {
+ commandsMap.get(oocMessage.toLowerCase())();
+ } catch (e) {
+ // Command Not Recognized
+ }
+ } else {
+ client.sender.sendServer(`CT#${oocName}#${oocMessage}#%`);
+ }
+} \ No newline at end of file
diff --git a/webAO/client/sender/sendPE.ts b/webAO/client/sender/sendPE.ts
new file mode 100644
index 0000000..984fc4d
--- /dev/null
+++ b/webAO/client/sender/sendPE.ts
@@ -0,0 +1,14 @@
+import { client } from "../../client";
+import { escapeChat } from "../../encoding";
+
+/**
+ * Sends add evidence command.
+ * @param {string} evidence name
+ * @param {string} evidence description
+ * @param {string} evidence image filename
+ */
+export const sendPE = (name: string, desc: string, img: string) => {
+ client.sender.sendServer(
+ `PE#${escapeChat(name)}#${escapeChat(desc)}#${escapeChat(img)}#%`
+ );
+} \ No newline at end of file
diff --git a/webAO/client/sender/sendRT.ts b/webAO/client/sender/sendRT.ts
new file mode 100644
index 0000000..2d6c60a
--- /dev/null
+++ b/webAO/client/sender/sendRT.ts
@@ -0,0 +1,9 @@
+import { client } from "../../client";
+
+/**
+ * Sends testimony command.
+ * @param {string} testimony type
+ */
+export const sendRT = (testimony: string) => {
+ client.sender.sendServer(`RT#${testimony}#%`);
+} \ No newline at end of file
diff --git a/webAO/client/sender/sendSelf.ts b/webAO/client/sender/sendSelf.ts
new file mode 100644
index 0000000..66c35fa
--- /dev/null
+++ b/webAO/client/sender/sendSelf.ts
@@ -0,0 +1,13 @@
+import { client } from "../../client";
+
+
+/**
+ * Hook for sending messages to the client
+ * @param {string} message the message to send
+ */
+export const sendSelf = (message: string) => {
+ (<HTMLInputElement>(
+ document.getElementById("client_ooclog")
+ )).value += `${message}\r\n`;
+ client.handleSelf(message);
+} \ No newline at end of file
diff --git a/webAO/client/sender/sendServer.ts b/webAO/client/sender/sendServer.ts
new file mode 100644
index 0000000..a9da3bd
--- /dev/null
+++ b/webAO/client/sender/sendServer.ts
@@ -0,0 +1,10 @@
+import { client } from "../../client";
+import queryParser from "../../utils/queryParser";
+let { mode } = queryParser()
+/**
+ * Hook for sending messages to the server
+ * @param {string} message the message to send
+ */
+export const sendServer = (message: string) => {
+ mode === "replay" ? client.sender.sendSelf(message) : client.serv.send(message);
+} \ No newline at end of file
diff --git a/webAO/client/sender/sendZZ.ts b/webAO/client/sender/sendZZ.ts
new file mode 100644
index 0000000..237ab37
--- /dev/null
+++ b/webAO/client/sender/sendZZ.ts
@@ -0,0 +1,13 @@
+import { client, extrafeatures } from "../../client";
+
+/**
+ * Sends call mod command.
+ * @param {string} message to mod
+ */
+export const sendZZ = (msg: string) => {
+ if (extrafeatures.includes("modcall_reason")) {
+ client.sender.sendServer(`ZZ#${msg}#%`);
+ } else {
+ client.sender.sendServer("ZZ#%");
+ }
+} \ No newline at end of file
diff --git a/webAO/client/setEmote.js b/webAO/client/setEmote.js
deleted file mode 100644
index f682fe5..0000000
--- a/webAO/client/setEmote.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import transparentPng from '../constants/transparentPng';
-import fileExists from '../utils/fileExists';
-
-/**
- * Sets all the img tags to the right sources
- * @param {*} chatmsg
- */
-
-const setEmote = async (AO_HOST, client, charactername, emotename, prefix, pair, side) => {
- const pairID = pair ? 'pair' : 'char';
- const characterFolder = `${AO_HOST}characters/`;
- const acceptedPositions = ['def', 'pro', 'wit'];
- const position = acceptedPositions.includes(side) ? `${side}_` : '';
- const emoteSelector = document.getElementById(`client_${position}${pairID}_img`)
- const extensionsMap = [
- '.gif',
- '.png',
- '.apng',
- '.webp'
- ];
-
- for (const extension of extensionsMap) {
- // Hides all sprites before creating a new sprite
- if (client.lastChar !== client.chatmsg.name) {
- emoteSelector.src = transparentPng;
- }
- let url;
- if (extension === '.png') {
- url = `${characterFolder}${encodeURI(charactername)}/${encodeURI(emotename)}${extension}`;
- } else {
- url = `${characterFolder}${encodeURI(charactername)}/${encodeURI(prefix)}${encodeURI(emotename)}${extension}`;
- }
- const exists = await fileExists(url);
- if (exists) {
- emoteSelector.src = url;
- break;
- }
- }
-};
-export default setEmote;
diff --git a/webAO/client/setEmote.ts b/webAO/client/setEmote.ts
new file mode 100644
index 0000000..f4fbdbb
--- /dev/null
+++ b/webAO/client/setEmote.ts
@@ -0,0 +1,55 @@
+import Client from "../client";
+import transparentPng from "../constants/transparentPng";
+import fileExists from "../utils/fileExists";
+
+/**
+ * Sets all the img tags to the right sources
+ * @param {*} chatmsg
+ */
+
+const setEmote = async (
+ AO_HOST: string,
+ client: Client,
+ charactername: string,
+ emotename: string,
+ prefix: string,
+ pair: boolean,
+ side: string
+) => {
+ const pairID = pair ? "pair" : "char";
+ const characterFolder = `${AO_HOST}characters/`;
+ const acceptedPositions = ["def", "pro", "wit"];
+ const position = acceptedPositions.includes(side) ? `${side}_` : "";
+ const emoteSelector = document.getElementById(
+ `client_${position}${pairID}_img`
+ ) as HTMLImageElement;
+ const extensionsMap = [".gif", ".png", ".apng", ".webp", ".webp.static"];
+
+ for (const extension of extensionsMap) {
+ // Hides all sprites before creating a new sprite
+
+ if (client.viewport.getLastCharacter() !== client.viewport.getChatmsg().name) {
+ emoteSelector.src = transparentPng;
+ }
+ let url;
+ if (extension === ".png") {
+ url = `${characterFolder}${encodeURI(charactername)}/${encodeURI(
+ emotename
+ )}${extension}`;
+ } else if (extension === ".webp.static") {
+ url = `${characterFolder}${encodeURI(charactername)}/${encodeURI(
+ emotename
+ )}.webp`;
+ } else {
+ url = `${characterFolder}${encodeURI(charactername)}/${encodeURI(
+ prefix
+ )}${encodeURI(emotename)}${extension}`;
+ }
+ const exists = await fileExists(url);
+ if (exists) {
+ emoteSelector.src = url;
+ break;
+ }
+ }
+};
+export default setEmote;