aboutsummaryrefslogtreecommitdiff
path: root/webAO
diff options
context:
space:
mode:
authorstonedDiscord <Tukz@gmx.de>2026-02-11 12:28:49 +0100
committerGitHub <noreply@github.com>2026-02-11 12:28:49 +0100
commit8e7af5b93009c987a4c0c3fef014cf7b64463e03 (patch)
tree04b0c383ca35962dc3054ec7ace34bb64f019622 /webAO
parent726d89ce8605d27be1c14bba59f81cfd6254ccb3 (diff)
parent5bb35a981e2f35df7fbf126faa8655ee3b3ca142 (diff)
Merge pull request #293 from OmniTroid/better-charloading
Better charloading
Diffstat (limited to 'webAO')
-rw-r--r--webAO/client.ts3
-rw-r--r--webAO/client/aoHost.ts3
-rw-r--r--webAO/client/checkCallword.ts2
-rw-r--r--webAO/client/handleCharacterInfo.ts176
-rw-r--r--webAO/dom/areaClick.ts4
-rw-r--r--webAO/dom/renderPlayerList.ts58
-rw-r--r--webAO/dom/updatePlayerAreas.ts24
-rw-r--r--webAO/packets/handlers/handleASS.ts2
-rw-r--r--webAO/packets/handlers/handleMC.ts2
-rw-r--r--webAO/packets/handlers/handleMS.ts5
-rw-r--r--webAO/packets/handlers/handlePR.ts56
-rw-r--r--webAO/packets/handlers/handlePU.ts36
-rw-r--r--webAO/packets/handlers/handlePV.ts3
-rw-r--r--webAO/packets/handlers/handleRMC.ts2
-rw-r--r--webAO/packets/handlers/handleSC.ts7
-rw-r--r--webAO/packets/handlers/handleSI.ts1
-rw-r--r--webAO/packets/handlers/handleZZ.ts2
-rw-r--r--webAO/styles/client.css17
-rw-r--r--webAO/viewport/utils/handleICSpeaking.ts2
-rw-r--r--webAO/viewport/utils/initTestimonyUpdater.ts2
-rw-r--r--webAO/viewport/viewport.ts6
21 files changed, 235 insertions, 178 deletions
diff --git a/webAO/client.ts b/webAO/client.ts
index b7a15a4..05a40c9 100644
--- a/webAO/client.ts
+++ b/webAO/client.ts
@@ -150,6 +150,8 @@ class Client extends EventEmitter {
connect: () => void;
loadResources: () => void;
isLowMemory: () => void;
+ /** Maps player ID to player data */
+ playerlist: Map<number, { charId: number; charName: string; showName: string; name: string; area: number }>;
charicon_extensions: string[];
emote_extensions: string[];
emotions_extensions: string[];
@@ -211,6 +213,7 @@ class Client extends EventEmitter {
this.temp_packet = "";
loadResources;
isLowMemory;
+ this.playerlist = new Map();
this.charicon_extensions = [".png", ".webp"];
this.emote_extensions = [".gif", ".png", ".apng", ".webp", ".webp.static"];
this.emotions_extensions = [".png", ".webp"];
diff --git a/webAO/client/aoHost.ts b/webAO/client/aoHost.ts
index 003e237..0d7cb0f 100644
--- a/webAO/client/aoHost.ts
+++ b/webAO/client/aoHost.ts
@@ -2,7 +2,7 @@ import queryParser from "../utils/queryParser";
const { asset } = queryParser();
export let AO_HOST = asset;
-export const setAOhost = (val: string) => {
+export const setAOhost = (val: string): string => {
const currentProtocol = window.location.protocol;
const assetProtocol = val.split(":")[0] + ":";
@@ -19,4 +19,5 @@ export const setAOhost = (val: string) => {
AO_HOST = val;
}
console.log("Asset URL ist now " + AO_HOST);
+ return AO_HOST;
};
diff --git a/webAO/client/checkCallword.ts b/webAO/client/checkCallword.ts
index b7413f7..795eac9 100644
--- a/webAO/client/checkCallword.ts
+++ b/webAO/client/checkCallword.ts
@@ -11,7 +11,7 @@ export function checkCallword(message: string, sfxAudio: HTMLAudioElement) {
if (item !== "" && message.toLowerCase().includes(item.toLowerCase())) {
sfxAudio.pause();
sfxAudio.src = `${AO_HOST}sounds/general/sfx-gallery.opus`;
- sfxAudio.play();
+ sfxAudio.play().catch(() => {});
}
}
}
diff --git a/webAO/client/handleCharacterInfo.ts b/webAO/client/handleCharacterInfo.ts
index 3764c84..28992e9 100644
--- a/webAO/client/handleCharacterInfo.ts
+++ b/webAO/client/handleCharacterInfo.ts
@@ -2,50 +2,22 @@ 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";
-export const getCharIcon = async (img: HTMLImageElement, charname: string) => {
- img.alt = charname;
- const charIconBaseUrl = `${AO_HOST}characters/${encodeURI(
- charname.toLowerCase(),
- )}/char_icon`;
- for (let i = 0; i < client.charicon_extensions.length; i++) {
- const fileUrl = charIconBaseUrl + client.charicon_extensions[i];
- const exists = await fileExists(fileUrl);
- if (exists) {
- img.alt = charname;
- img.title = charname;
- img.src = fileUrl;
- return;
- }
- }
-};
-
/**
- * Handles the incoming character information, and downloads the sprite + ini for it
- * @param {Array} chargs packet arguments
- * @param {Number} charid character ID
+ * Lightweight character setup that runs on join. Sets the icon src directly
+ * (letting the browser handle loading) and stores default character data.
+ * Does NOT fetch char.ini — that is deferred until needed via ensureCharIni.
*/
-export const handleCharacterInfo = async (chargs: string[], charid: number) => {
+export const setupCharacterBasic = (chargs: string[], charid: number) => {
const img = <HTMLImageElement>document.getElementById(`demo_${charid}`);
if (chargs[0]) {
- let cini: any = {};
-
- getCharIcon(img, chargs[0]);
-
- // 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
- }
+ img.alt = chargs[0];
+ img.title = chargs[0];
+ const iconExt = client.charicon_extensions[0] || ".png";
+ img.src = `${AO_HOST}characters/${encodeURI(
+ chargs[0].toLowerCase(),
+ )}/char_icon${iconExt}`;
const mute_select = <HTMLSelectElement>(
document.getElementById("mute_select")
@@ -56,47 +28,109 @@ export const handleCharacterInfo = async (chargs: string[], charid: number) => {
);
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);
-
+ // Store defaults — these get replaced with actual ini values by ensureCharIni
client.chars[charid] = {
name: safeTags(chargs[0]),
- showname: safeTags(cini.options.showname),
+ showname: safeTags(chargs[0]),
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(),
+ blips: "male",
+ gender: "",
+ side: "def",
+ chat: "",
evidence: chargs[3],
- icon: img.src,
- inifile: cini,
+ icon: "",
muted: false,
};
+ } else {
+ console.warn(`missing charid ${charid}`);
+ img.style.display = "none";
+ }
+};
- if (
- client.chars[charid].blips === "male" &&
- client.chars[charid].gender !== "male" &&
- client.chars[charid].gender !== ""
- ) {
- client.chars[charid].blips = client.chars[charid].gender;
+/**
+ * Fetches and parses char.ini for a character if not already loaded.
+ * Replaces default values in client.chars[charid] with actual ini values.
+ */
+export const ensureCharIni = async (charid: number): Promise<any> => {
+ const char = client.chars[charid];
+ if (!char) return {};
+ if (char.inifile) return char.inifile;
+
+ const img = <HTMLImageElement>document.getElementById(`demo_${charid}`);
+ let cini: any = {};
+
+ try {
+ const cinidata = await request(
+ `${AO_HOST}characters/${encodeURI(char.name.toLowerCase())}/char.ini`,
+ );
+ cini = iniParse(cinidata);
+ } catch (err) {
+ cini = {};
+ if (img) img.classList.add("noini");
+ console.warn(`character ${char.name} is missing from webAO`);
+ }
+
+ const default_options = {
+ name: char.name,
+ showname: char.name,
+ side: "def",
+ blips: "male",
+ chat: "",
+ category: "",
+ };
+ cini.options = Object.assign(default_options, cini.options);
+
+ const default_emotions = {
+ number: 0,
+ };
+ cini.emotions = Object.assign(default_emotions, cini.emotions);
+
+ // Replace defaults with actual ini values
+ char.showname = safeTags(cini.options.showname);
+ char.blips = safeTags(cini.options.blips).toLowerCase();
+ char.gender = safeTags(cini.options.gender).toLowerCase();
+ char.side = safeTags(cini.options.side).toLowerCase();
+ char.chat =
+ cini.options.chat === ""
+ ? safeTags(cini.options.category).toLowerCase()
+ : safeTags(cini.options.chat).toLowerCase();
+ char.icon = img ? img.src : "";
+ char.inifile = cini;
+
+ if (
+ char.blips === "male" &&
+ char.gender !== "male" &&
+ char.gender !== ""
+ ) {
+ char.blips = char.gender;
+ }
+
+ return cini;
+};
+
+/**
+ * Full character info load (used by iniEdit and handleMS ini-edit path).
+ * Fetches icon + ini for a single character, replacing any existing data.
+ */
+export const handleCharacterInfo = async (chargs: string[], charid: number) => {
+ const img = <HTMLImageElement>document.getElementById(`demo_${charid}`);
+ if (chargs[0]) {
+ img.alt = chargs[0];
+ img.title = chargs[0];
+ const iconExt = client.charicon_extensions[0] || ".png";
+ img.src = `${AO_HOST}characters/${encodeURI(
+ chargs[0].toLowerCase(),
+ )}/char_icon${iconExt}`;
+
+ // Reset inifile so ensureCharIni will re-fetch
+ if (client.chars[charid]) {
+ client.chars[charid].name = safeTags(chargs[0]);
+ client.chars[charid].inifile = null;
+ } else {
+ setupCharacterBasic(chargs, charid);
}
+
+ await ensureCharIni(charid);
} else {
console.warn(`missing charid ${charid}`);
img.style.display = "none";
diff --git a/webAO/dom/areaClick.ts b/webAO/dom/areaClick.ts
index 1e41f4b..27682c7 100644
--- a/webAO/dom/areaClick.ts
+++ b/webAO/dom/areaClick.ts
@@ -1,5 +1,5 @@
import { client } from "../client";
-import { updatePlayerAreas } from "./updatePlayerAreas";
+import { renderPlayerList } from "./renderPlayerList";
/**
* Triggered when an item on the area list is clicked.
* @param {HTMLElement} el
@@ -13,6 +13,6 @@ export function area_click(el: HTMLElement) {
areaHr.textContent = `switched to ${el.textContent}`;
document.getElementById("client_log")!.appendChild(areaHr);
client.area = Number(el.id.substring(4));
- updatePlayerAreas(client.area);
+ renderPlayerList();
}
window.area_click = area_click;
diff --git a/webAO/dom/renderPlayerList.ts b/webAO/dom/renderPlayerList.ts
new file mode 100644
index 0000000..48354ae
--- /dev/null
+++ b/webAO/dom/renderPlayerList.ts
@@ -0,0 +1,58 @@
+import { client } from "../client";
+import { AO_HOST } from "../client/aoHost";
+
+export function renderPlayerList() {
+ const list = document.getElementById("client_playerlist") as HTMLTableElement;
+ list.innerHTML = "";
+
+ const header = list.createTHead().insertRow();
+ for (const label of ["Icon", "Character", "Showname", "OOC Name"]) {
+ const th = document.createElement("th");
+ th.textContent = label;
+ header.appendChild(th);
+ }
+
+ const body = list.createTBody();
+ for (const [playerID, player] of client.playerlist) {
+ const playerRow = body.insertRow();
+ playerRow.id = `client_playerlist_entry${playerID}`;
+ playerRow.style.display = player.area === client.area ? "" : "none";
+
+ const imgCell = playerRow.insertCell(0);
+ imgCell.style.width = "64px";
+ const img = document.createElement("img");
+ img.style.maxWidth = "60px";
+ img.style.maxHeight = "60px";
+ if (player.charName) {
+ const iconExt = client.charicon_extensions[0] || ".png";
+ img.src = `${AO_HOST}characters/${encodeURI(player.charName.toLowerCase())}/char_icon${iconExt}`;
+ img.alt = player.charName;
+ img.title = player.charName;
+ }
+ imgCell.appendChild(img);
+
+ const charNameCell = playerRow.insertCell(1);
+ charNameCell.textContent =
+ player.charName ? `[${playerID}] ${player.charName}` : "";
+
+ const showNameCell = playerRow.insertCell(2);
+ showNameCell.textContent = player.showName;
+
+ const oocNameCell = playerRow.insertCell(3);
+ oocNameCell.textContent = player.name;
+
+ const kickCell = playerRow.insertCell(4);
+ kickCell.style.width = "64px";
+ const kick = document.createElement("button");
+ kick.innerText = "Kick";
+ kick.onclick = () => window.kickPlayer(playerID);
+ kickCell.appendChild(kick);
+
+ const banCell = playerRow.insertCell(5);
+ banCell.style.width = "64px";
+ const ban = document.createElement("button");
+ ban.innerText = "Ban";
+ ban.onclick = () => window.banPlayer(playerID);
+ banCell.appendChild(ban);
+ }
+}
diff --git a/webAO/dom/updatePlayerAreas.ts b/webAO/dom/updatePlayerAreas.ts
deleted file mode 100644
index 99eccf1..0000000
--- a/webAO/dom/updatePlayerAreas.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { client } from "../client";
-import { area_click } from "./areaClick";
-/**
- * Triggered when someone switches areas
- * @param {Number} ownarea
- */
-export function updatePlayerAreas(ownarea: number) {
- for (let i = 0; i < client.areas.length; i++) {
- if (i === ownarea)
- for (let classelement of Array.from(
- document.getElementsByClassName(
- `area${i}`,
- ) as HTMLCollectionOf<HTMLElement>,
- ))
- classelement.style.display = "";
- else
- for (let classelement of Array.from(
- document.getElementsByClassName(
- `area${i}`,
- ) as HTMLCollectionOf<HTMLElement>,
- ))
- classelement.style.display = "none";
- }
-}
diff --git a/webAO/packets/handlers/handleASS.ts b/webAO/packets/handlers/handleASS.ts
index a46d68e..c50443a 100644
--- a/webAO/packets/handlers/handleASS.ts
+++ b/webAO/packets/handlers/handleASS.ts
@@ -1,4 +1,5 @@
import { setAOhost } from "../../client/aoHost";
+import { renderPlayerList } from "../../dom/renderPlayerList";
/**
* new asset url!!
@@ -6,4 +7,5 @@ import { setAOhost } from "../../client/aoHost";
*/
export const handleASS = (args: string[]) => {
if (args[1] !== "None") setAOhost(args[1]);
+ renderPlayerList();
};
diff --git a/webAO/packets/handlers/handleMC.ts b/webAO/packets/handlers/handleMC.ts
index 8a1c0a2..44237ea 100644
--- a/webAO/packets/handlers/handleMC.ts
+++ b/webAO/packets/handlers/handleMC.ts
@@ -24,7 +24,7 @@ export const handleMC = (args: string[]) => {
music.src = `${AO_HOST}sounds/music/${encodeURI(track.toLowerCase())}`;
}
music.loop = looping;
- music.play();
+ music.play().catch(() => {});
try {
musicname = client.chars[charID].name;
diff --git a/webAO/packets/handlers/handleMS.ts b/webAO/packets/handlers/handleMS.ts
index 2622fe6..9b46bc0 100644
--- a/webAO/packets/handlers/handleMS.ts
+++ b/webAO/packets/handlers/handleMS.ts
@@ -1,7 +1,7 @@
/* eslint indent: ["error", 2, { "SwitchCase": 1 }] */
import { client, extrafeatures, UPDATE_INTERVAL } from "../../client";
-import { handleCharacterInfo } from "../../client/handleCharacterInfo";
+import { handleCharacterInfo, ensureCharIni } from "../../client/handleCharacterInfo";
import { resetICParams } from "../../client/resetICParams";
import { prepChat, safeTags } from "../../encoding";
import { handle_ic_speaking } from "../../viewport/utils/handleICSpeaking";
@@ -27,6 +27,9 @@ export const handleMS = (args: string[]) => {
);
const chargs = (`${char_name}&` + "iniediter").split("&");
handleCharacterInfo(chargs, char_id);
+ } else if (!client.chars[char_id].inifile) {
+ // Lazily load char.ini in background so future messages have proper data
+ ensureCharIni(char_id);
}
}
diff --git a/webAO/packets/handlers/handlePR.ts b/webAO/packets/handlers/handlePR.ts
index aeb3969..f81d8dc 100644
--- a/webAO/packets/handlers/handlePR.ts
+++ b/webAO/packets/handlers/handlePR.ts
@@ -1,51 +1,5 @@
import { client } from "../../client";
-import { kickPlayer, banPlayer } from "../../dom/banPlayer";
-
-function addPlayer(playerID: number) {
- const list = <HTMLTableElement>document.getElementById("client_playerlist");
- const playerRow = list.insertRow();
- playerRow.id = `client_playerlist_entry${playerID}`;
- playerRow.className = `area0`;
-
- const imgCell = playerRow.insertCell(0);
- imgCell.style.width = "64px";
- const img = document.createElement("img");
- imgCell.appendChild(img);
-
- const name = document.createTextNode("No Data");
-
- const charNameCell = playerRow.insertCell(1);
- charNameCell.appendChild(name);
- const showNameCell = playerRow.insertCell(2);
- showNameCell.appendChild(name);
- const oocNameCell = playerRow.insertCell(3);
- oocNameCell.appendChild(name);
-
- const kickCell = playerRow.insertCell(4);
- kickCell.style.width = "64px";
- const kick = <HTMLButtonElement>document.createElement("button");
- kick.innerText = "Kick";
- kick.onclick = () => {
- window.kickPlayer(playerID);
- };
- kickCell.appendChild(kick);
-
- const banCell = playerRow.insertCell(5);
- banCell.style.width = "64px";
- const ban = <HTMLButtonElement>document.createElement("button");
- ban.innerText = "Ban";
- ban.onclick = () => {
- window.banPlayer(playerID);
- };
- banCell.appendChild(ban);
-}
-
-function removePlayer(playerID: number) {
- const playerRow = <HTMLTableElement>(
- document.getElementById(`client_playerlist_entry${playerID}`)
- );
- playerRow.remove();
-}
+import { renderPlayerList } from "../../dom/renderPlayerList";
/**
* Handles a player joining or leaving
@@ -53,6 +7,10 @@ function removePlayer(playerID: number) {
*/
export const handlePR = (args: string[]) => {
const playerID = Number(args[1]);
- if (Number(args[2]) === 0) addPlayer(playerID);
- else if (Number(args[2]) === 1) removePlayer(playerID);
+ if (Number(args[2]) === 0) {
+ client.playerlist.set(playerID, { charId: -1, charName: "", showName: "", name: "", area: 0 });
+ } else if (Number(args[2]) === 1) {
+ client.playerlist.delete(playerID);
+ }
+ renderPlayerList();
};
diff --git a/webAO/packets/handlers/handlePU.ts b/webAO/packets/handlers/handlePU.ts
index 508bb51..3b70ad3 100644
--- a/webAO/packets/handlers/handlePU.ts
+++ b/webAO/packets/handlers/handlePU.ts
@@ -1,36 +1,42 @@
import { client } from "../../client";
-import { getCharIcon } from "../../client/handleCharacterInfo";
-import { updatePlayerAreas } from "../../dom/updatePlayerAreas";
+import { ensureCharIni } from "../../client/handleCharacterInfo";
+import { renderPlayerList } from "../../dom/renderPlayerList";
/**
* Handles a playerlist update
* @param {Array} args packet arguments
*/
export const handlePU = (args: string[]) => {
- const playerRow = <HTMLTableElement>(
- document.getElementById(`client_playerlist_entry${Number(args[1])}`)
- );
+ const playerID = Number(args[1]);
+ const player = client.playerlist.get(playerID);
+ if (!player) return;
+
const type = Number(args[2]);
const data = args[3];
+
switch (type) {
case 0:
- const oocName = <HTMLElement>playerRow.childNodes[3];
- oocName.innerText = data;
+ player.name = data;
break;
case 1:
- const playerImg = <HTMLImageElement>playerRow.childNodes[0].firstChild;
- getCharIcon(playerImg, data);
- const charName = <HTMLElement>playerRow.childNodes[1];
- charName.innerText = `[${args[1]}] ${data}`;
+ player.charName = data;
+ const charId = client.chars.findIndex(
+ (c: any) => c && c.name.toLowerCase() === data.toLowerCase()
+ );
+ if (charId >= 0) {
+ player.charId = charId;
+ ensureCharIni(charId);
+ }
break;
case 2:
- const showName = <HTMLElement>playerRow.childNodes[2];
- showName.innerText = data;
+ player.showName = data;
break;
case 3:
- playerRow.className = `area${data}`;
- updatePlayerAreas(client.area);
+ player.area = Number(data);
+ break;
default:
break;
}
+
+ renderPlayerList();
};
diff --git a/webAO/packets/handlers/handlePV.ts b/webAO/packets/handlers/handlePV.ts
index a39a4dd..1ee13c8 100644
--- a/webAO/packets/handlers/handlePV.ts
+++ b/webAO/packets/handlers/handlePV.ts
@@ -3,6 +3,7 @@ import fileExists from "../../utils/fileExists";
import { updateActionCommands } from "../../dom/updateActionCommands";
import { pickEmotion } from "../../dom/pickEmotion";
import { AO_HOST } from "../../client/aoHost";
+import { ensureCharIni } from "../../client/handleCharacterInfo";
function addEmoteButton(i: number, imgurl: string, desc: string) {
const emotesList = document.getElementById("client_emo");
@@ -34,7 +35,7 @@ export const handlePV = async (args: string[]) => {
const emotesList = document.getElementById("client_emo");
emotesList.style.display = "";
emotesList.innerHTML = ""; // Clear emote box
- const ini = me.inifile;
+ const ini = await ensureCharIni(client.charID);
me.side = ini.options.side;
updateActionCommands(me.side);
if (ini.emotions.number === 0) {
diff --git a/webAO/packets/handlers/handleRMC.ts b/webAO/packets/handlers/handleRMC.ts
index 7758844..0cc6aa7 100644
--- a/webAO/packets/handlers/handleRMC.ts
+++ b/webAO/packets/handlers/handleRMC.ts
@@ -17,7 +17,7 @@ export const handleRMC = (args: string[]) => {
music.currentTime += parseFloat(
music.totime + (new Date().getTime() / 1000 - music.offset),
).toFixed(3);
- music.play();
+ music.play().catch(() => {});
},
false,
);
diff --git a/webAO/packets/handlers/handleSC.ts b/webAO/packets/handlers/handleSC.ts
index 271a164..f7b789f 100644
--- a/webAO/packets/handlers/handleSC.ts
+++ b/webAO/packets/handlers/handleSC.ts
@@ -1,7 +1,7 @@
import queryParser from "../../utils/queryParser";
import { client } from "../../client";
-import { handleCharacterInfo } from "../../client/handleCharacterInfo";
+import { setupCharacterBasic } from "../../client/handleCharacterInfo";
const { mode } = queryParser();
/**
@@ -17,13 +17,10 @@ export const handleSC = async (args: string[]) => {
document.getElementById("client_charselect")!.style.display = "block";
}
- document.getElementById("client_loadingtext")!.innerHTML =
- "Loading Characters";
for (let i = 1; i < args.length; i++) {
const chargs = args[i].split("&");
const charid = i - 1;
-
- setTimeout(() => handleCharacterInfo(chargs, charid), charid * 6);
+ setupCharacterBasic(chargs, charid);
}
// We're done with the characters, request the music
client.sender.sendServer("RM#%");
diff --git a/webAO/packets/handlers/handleSI.ts b/webAO/packets/handlers/handleSI.ts
index f20f4b2..eac84e0 100644
--- a/webAO/packets/handlers/handleSI.ts
+++ b/webAO/packets/handlers/handleSI.ts
@@ -20,6 +20,7 @@ export const handleSI = (args: string[]) => {
const demothing = document.createElement("img");
demothing.className = "demothing";
+ demothing.loading = "lazy";
demothing.id = `demo_${i}`;
const demoonclick = document.createAttribute("onclick");
demoonclick.value = `pickChar(${i})`;
diff --git a/webAO/packets/handlers/handleZZ.ts b/webAO/packets/handlers/handleZZ.ts
index 31ceef5..0496d42 100644
--- a/webAO/packets/handlers/handleZZ.ts
+++ b/webAO/packets/handlers/handleZZ.ts
@@ -18,6 +18,6 @@ export const handleZZ = (args: string[]) => {
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().play().catch(() => {});
client.viewport.getSfxAudio().volume = oldvolume;
};
diff --git a/webAO/styles/client.css b/webAO/styles/client.css
index 88eca59..e3ee820 100644
--- a/webAO/styles/client.css
+++ b/webAO/styles/client.css
@@ -714,6 +714,23 @@
opacity: 0.5;
}
+#client_playerlist {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+#client_playerlist th,
+#client_playerlist td {
+ border-bottom: 1px solid #555;
+ padding: 4px 6px;
+ text-align: left;
+}
+
+#client_playerlist th {
+ font-weight: bold;
+ border-bottom: 2px solid #888;
+}
+
.hrtext {
overflow: hidden;
text-align: center;
diff --git a/webAO/viewport/utils/handleICSpeaking.ts b/webAO/viewport/utils/handleICSpeaking.ts
index 6cf665c..ec791c5 100644
--- a/webAO/viewport/utils/handleICSpeaking.ts
+++ b/webAO/viewport/utils/handleICSpeaking.ts
@@ -169,7 +169,7 @@ export const handle_ic_speaking = async (playerChatMsg: ChatMsg) => {
)}/${shout}.opus`;
const exists = await fileExists(perCharPath);
client.viewport.shoutaudio.src = exists ? perCharPath : client.resources[shout].sfx;
- client.viewport.shoutaudio.play();
+ client.viewport.shoutaudio.play().catch(() => {});
client.viewport.setShoutTimer(client.resources[shout].duration);
} else {
client.viewport.setShoutTimer(0);
diff --git a/webAO/viewport/utils/initTestimonyUpdater.ts b/webAO/viewport/utils/initTestimonyUpdater.ts
index c1e4d93..a545c97 100644
--- a/webAO/viewport/utils/initTestimonyUpdater.ts
+++ b/webAO/viewport/utils/initTestimonyUpdater.ts
@@ -18,7 +18,7 @@ export const initTestimonyUpdater = () => {
}
client.viewport.testimonyAudio.src = client.resources[testimony].sfx;
- client.viewport.testimonyAudio.play();
+ client.viewport.testimonyAudio.play().catch(() => {});
const testimonyOverlay = <HTMLImageElement>(
document.getElementById("client_testimony")
diff --git a/webAO/viewport/viewport.ts b/webAO/viewport/viewport.ts
index c03d750..a7363a9 100644
--- a/webAO/viewport/viewport.ts
+++ b/webAO/viewport/viewport.ts
@@ -104,7 +104,7 @@ const viewport = (): Viewport => {
sfxAudio.pause();
sfxAudio.loop = looping;
sfxAudio.src = sfxname;
- sfxAudio.play();
+ sfxAudio.play().catch(() => {});
};
/**
* Updates the testimony overaly
@@ -150,7 +150,7 @@ const viewport = (): Viewport => {
const charEmote = chatmsg.sprite.toLowerCase();
if (chatmsg.content.charAt(textnow.length) !== " ") {
- blipChannels[currentBlipChannel].play();
+ blipChannels[currentBlipChannel].play().catch(() => {});
currentBlipChannel++;
currentBlipChannel %= blipChannels.length;
}
@@ -356,7 +356,7 @@ const viewport = (): Viewport => {
eviBox.style.opacity = "1";
testimonyAudio.src = `${AO_HOST}sounds/general/sfx-evidenceshoop.opus`;
- testimonyAudio.play();
+ testimonyAudio.play().catch(() => {});
if (chatmsg.side === "def") {
// Only def show evidence on right