aboutsummaryrefslogtreecommitdiff
path: root/webAO
diff options
context:
space:
mode:
authorcaleb.mabry.15@cnu.edu <caleb.mabry.15@cnu.edu>2022-03-23 17:36:01 -0400
committercaleb.mabry.15@cnu.edu <caleb.mabry.15@cnu.edu>2022-03-23 17:36:01 -0400
commite28fcc59a76772eae64ed480919cddb60a3b5fce (patch)
treedb7ed6cf18f928fae734f3cc9430bb2b220ba2a0 /webAO
parenta7facd6e825e3a2d60752df0b8526482b19a12de (diff)
parentffa23343e0a0badd9e50a005359fdb79efead995 (diff)
Merge branch 'master' of https://github.com/AttorneyOnline/webAO into multipleBackgroundTypes
Diffstat (limited to 'webAO')
-rw-r--r--webAO/client.js43
-rw-r--r--webAO/client/aoHost.js5
-rw-r--r--webAO/components/__tests__/audioChannels.test.js9
-rw-r--r--webAO/components/__tests__/blips.test.js9
-rw-r--r--webAO/components/audioChannels.js13
-rw-r--r--webAO/components/blip.js17
-rw-r--r--webAO/encoding.ts (renamed from webAO/encoding.js)33
-rw-r--r--webAO/master.ts26
-rw-r--r--webAO/utils/__tests__/aoml.test.ts113
-rw-r--r--webAO/utils/aoml.ts117
10 files changed, 323 insertions, 62 deletions
diff --git a/webAO/client.js b/webAO/client.js
index dd7989b..06defa2 100644
--- a/webAO/client.js
+++ b/webAO/client.js
@@ -9,8 +9,8 @@ import { EventEmitter } from 'events';
import tryUrls from './utils/tryUrls'
import {
escapeChat, encodeChat, prepChat, safeTags,
-} from './encoding.js';
-
+} from './encoding';
+import mlConfig from './utils/aoml';
// Load some defaults for the background and evidence dropdowns
import vanilla_character_arr from './constants/characters.js';
import vanilla_music_arr from './constants/music.js';
@@ -43,6 +43,8 @@ const DEFAULT_HOST = 'http://attorneyoffline.de/base/';
let AO_HOST = asset || DEFAULT_HOST;
const THEME = theme || 'default';
+const attorneyMarkdown = mlConfig(AO_HOST)
+
const UPDATE_INTERVAL = 60;
/**
@@ -1671,15 +1673,8 @@ class Viewport {
];
// Allocate multiple blip audio channels to make blips less jittery
-
- this.blipChannels = new Array(
- new Audio(`${AO_HOST}sounds/general/sfx-blipmale.opus`),
- new Audio(`${AO_HOST}sounds/general/sfx-blipmale.opus`),
- new Audio(`${AO_HOST}sounds/general/sfx-blipmale.opus`),
- new Audio(`${AO_HOST}sounds/general/sfx-blipmale.opus`),
- new Audio(`${AO_HOST}sounds/general/sfx-blipmale.opus`),
- new Audio(`${AO_HOST}sounds/general/sfx-blipmale.opus`),
- );
+ const blipSelectors = document.getElementsByClassName('blipSound')
+ this.blipChannels = [...blipSelectors];
this.blipChannels.forEach((channel) => channel.volume = 0.5);
this.blipChannels.forEach((channel) => channel.onerror = opusCheck(channel));
this.currentBlipChannel = 0;
@@ -1695,12 +1690,8 @@ class Viewport {
this.testimonyAudio = document.getElementById('client_testimonyaudio');
this.testimonyAudio.src = `${AO_HOST}sounds/general/sfx-guilty.opus`;
- this.music = new Array(
- new Audio(`${AO_HOST}sounds/music/trial (aa).opus`),
- new Audio(`${AO_HOST}sounds/music/trial (aa).opus`),
- new Audio(`${AO_HOST}sounds/music/trial (aa).opus`),
- new Audio(`${AO_HOST}sounds/music/trial (aa).opus`),
- );
+ const audioChannels = document.getElementsByClassName('audioChannel')
+ this.music = [...audioChannels];
this.music.forEach((channel) => channel.volume = 0.5);
this.music.forEach((channel) => channel.onerror = opusCheck(channel));
@@ -1939,6 +1930,7 @@ class Viewport {
* @param {object} chatmsg the new chat message
*/
async say(chatmsg) {
+
this.chatmsg = chatmsg;
this.textnow = '';
this.sfxplayed = 0;
@@ -2090,7 +2082,7 @@ class Viewport {
if (soundChecks.some((check) => this.chatmsg.sound === check)) {
this.chatmsg.sound = this.chatmsg.effects[2];
}
-
+ this.chatmsg.parsed = await attorneyMarkdown.applyMarkdown(chatmsg.content, this.colors[this.chatmsg.color])
this.tick();
}
@@ -2242,8 +2234,10 @@ class Viewport {
this.currentBlipChannel %= this.blipChannels.length;
}
this.textnow = this.chatmsg.content.substring(0, this.textnow.length + 1);
-
- chatBoxInner.innerText = this.textnow;
+ const characterElement = this.chatmsg.parsed[this.textnow.length - 1]
+ if (characterElement) {
+ chatBoxInner.appendChild(this.chatmsg.parsed[this.textnow.length - 1]);
+ }
// scroll to bottom
chatBox.scrollTop = chatBox.scrollHeight;
@@ -2326,6 +2320,7 @@ export function onEnter(event) {
if (emote_mod === 0) { emote_mod = 1; }
} else if (emote_mod === 1) { emote_mod = 0; }
+
client.sendIC(
'chat',
myemo.preanim,
@@ -2620,6 +2615,10 @@ window.imgError = imgError;
* @param {HTMLImageElement} image the element containing the missing sound
*/
export function opusCheck(channel) {
+ const audio = channel.src
+ if (audio === '') {
+ return
+ }
console.info(`failed to load sound ${channel.src}`);
let oldsrc = '';
oldsrc = channel.src;
@@ -2959,7 +2958,7 @@ window.updateActionCommands = updateActionCommands;
*/
export function changeBackgroundOOC() {
const selectedBG = document.getElementById('bg_select');
- const changeBGCommand = document.getElementById('bg_command').value;
+ const changeBGCommand = "bg $1";
const bgFilename = document.getElementById('bg_filename');
let filename = '';
@@ -2989,7 +2988,7 @@ window.changeRoleOOC = changeRoleOOC;
* Random character via OOC.
*/
export function randomCharacterOOC() {
- client.sendOOC(`/${document.getElementById('randomchar_command').value}`);
+ client.sendOOC(`/randomchar`);
}
window.randomCharacterOOC = randomCharacterOOC;
diff --git a/webAO/client/aoHost.js b/webAO/client/aoHost.js
new file mode 100644
index 0000000..b387608
--- /dev/null
+++ b/webAO/client/aoHost.js
@@ -0,0 +1,5 @@
+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
diff --git a/webAO/components/__tests__/audioChannels.test.js b/webAO/components/__tests__/audioChannels.test.js
new file mode 100644
index 0000000..243d870
--- /dev/null
+++ b/webAO/components/__tests__/audioChannels.test.js
@@ -0,0 +1,9 @@
+import createAudioChannels from "../audioChannels";
+
+describe('createAudioChannels', () => {
+ test('Should create 4 channels', () => {
+ document.body.innerHTML = ''
+ createAudioChannels(4)
+ expect(document.getElementsByClassName('audioChannel').length).toBe(4)
+ })
+}) \ No newline at end of file
diff --git a/webAO/components/__tests__/blips.test.js b/webAO/components/__tests__/blips.test.js
new file mode 100644
index 0000000..9c57e78
--- /dev/null
+++ b/webAO/components/__tests__/blips.test.js
@@ -0,0 +1,9 @@
+import createBlip from "../blip";
+
+describe('createBlip', () => {
+ test('create 3 blips audios', () => {
+ document.body.innerHTML = ``
+ createBlip(3)
+ expect(document.getElementsByClassName('blipSound').length).toBe(3)
+ })
+}) \ No newline at end of file
diff --git a/webAO/components/audioChannels.js b/webAO/components/audioChannels.js
new file mode 100644
index 0000000..1979653
--- /dev/null
+++ b/webAO/components/audioChannels.js
@@ -0,0 +1,13 @@
+/**
+ *
+ * @param {number} amountOfChannels Amount of Blips to put on page
+ */
+const createAudioChannels = (amountOfChannels) => {
+ for (let i = 0; i < amountOfChannels; i++) {
+ const audioChannel = document.createElement('audio')
+ audioChannel.setAttribute('class', 'audioChannel')
+ document.body.appendChild(audioChannel)
+ }
+}
+createAudioChannels(4)
+export default createAudioChannels
diff --git a/webAO/components/blip.js b/webAO/components/blip.js
new file mode 100644
index 0000000..eacbeaf
--- /dev/null
+++ b/webAO/components/blip.js
@@ -0,0 +1,17 @@
+import AO_HOST from '../client/aoHost'
+
+/**
+ *
+ * @param {number} amountOfBlips Amount of Blips to put on page
+ */
+const createBlip = (amountOfBlips) => {
+ for (let i = 0; i < amountOfBlips; i++) {
+ const audio = document.createElement('audio')
+ const blipUrl = `${AO_HOST}sounds/general/sfx-blipmale.opus`
+ audio.setAttribute('class', 'blipSound')
+ audio.setAttribute('src', blipUrl)
+ document.body.appendChild(audio)
+ }
+}
+createBlip(6)
+export default createBlip \ No newline at end of file
diff --git a/webAO/encoding.js b/webAO/encoding.ts
index e6cc3ae..1018144 100644
--- a/webAO/encoding.js
+++ b/webAO/encoding.ts
@@ -2,7 +2,7 @@
* Escapes a string to AO1 escape codes.
* @param {string} estring the string to be escaped
*/
-export function escapeChat(estring) {
+export function escapeChat(estring: string): string {
return estring
.replace(/#/g, '<num>')
.replace(/&/g, '<and>')
@@ -14,7 +14,7 @@ export function escapeChat(estring) {
* Unescapes a string to AO1 escape codes.
* @param {string} estring the string to be unescaped
*/
-export function unescapeChat(estring) {
+export function unescapeChat(estring: string): string {
return estring
.replace(/<num>/g, '#')
.replace(/<and>/g, '&')
@@ -28,7 +28,7 @@ export function unescapeChat(estring) {
* XXX: This is unnecessary if we use `createTextNode` instead!
* @param {string} unsafe an unsanitized string
*/
-export function safeTags(unsafe) {
+export function safeTags(unsafe: string): string {
if (unsafe) {
return unsafe
.replace(/>/g, '&gt;')
@@ -41,21 +41,7 @@ export function safeTags(unsafe) {
* Encode text on client side.
* @param {string} estring the string to be encoded
*/
-export function encodeChat(estring) {
- const selectedEncoding = document.getElementById('client_encoding').value;
- if (selectedEncoding === 'unicode') {
- // This approach works by escaping all special characters to Unicode escape sequences.
- // Source: https://gist.github.com/mathiasbynens/1243213
- return estring.replace(/[^\0-~]/g, (ch) => `\\u${(`000${ch.charCodeAt().toString(16)}`).slice(-4)}`);
- } if (selectedEncoding === 'utf16') {
- // Source: https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
- const buffer = new ArrayBuffer(estring.length * 2);
- const result = new Uint16Array(buffer);
- for (let i = 0, strLen = estring.length; i < strLen; i++) {
- result[i] = estring.charCodeAt(i);
- }
- return String(result);
- }
+export function encodeChat(estring: string): string {
return estring;
}
@@ -63,23 +49,16 @@ export function encodeChat(estring) {
* Decodes text on client side.
* @param {string} estring the string to be decoded
*/
-export function decodeChat(estring) {
- const selectedDecoding = document.getElementById('client_decoding').value;
- if (selectedDecoding === 'unicode') {
+export function decodeChat(estring: string): string {
// Source: https://stackoverflow.com/questions/7885096/how-do-i-decode-a-string-with-escaped-unicode
return estring.replace(/\\u([\d\w]{1,})/gi, (match, group) => String.fromCharCode(parseInt(group, 16)));
- } if (selectedDecoding === 'utf16') {
- // Source: https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
- return String.fromCharCode.apply(null, new Uint16Array(estring.split(',')));
- }
- return estring;
}
/**
* XXX: a nasty hack made by gameboyprinter.
* @param {string} msg chat message to prepare for display
*/
-export function prepChat(msg) {
+export function prepChat(msg: string): string {
// TODO: make this less awful
return unescapeChat(decodeChat(msg));
}
diff --git a/webAO/master.ts b/webAO/master.ts
index 8fd8779..a48f4e9 100644
--- a/webAO/master.ts
+++ b/webAO/master.ts
@@ -1,6 +1,6 @@
import FingerprintJS from '@fingerprintjs/fingerprintjs';
-import { unescapeChat, safeTags } from './encoding.js';
+import { unescapeChat, safeTags } from './encoding';
declare global {
interface Window {
@@ -67,10 +67,10 @@ export function setServ(ID: number) {
window.setServ = setServ;
function checkOnline(serverID: number, coIP: string) {
- let oserv;
+ let serverConnection: WebSocket;
if (serverID !== -2) {
try {
- oserv = new WebSocket(`ws://${coIP}`);
+ serverConnection = new WebSocket(`ws://${coIP}`);
} catch (SecurityError) {
document.getElementById(`server${serverID}`).className = 'unavailable';
return;
@@ -78,24 +78,24 @@ function checkOnline(serverID: number, coIP: string) {
}
// define what the callbacks do
- function onCOOpen(_e) {
+ function onCOOpen() {
document.getElementById(`server${serverID}`).className = 'available';
- oserv.send(`HI#${hdid}#%`);
- oserv.send('ID#webAO#webAO#%');
+ serverConnection.send(`HI#${hdid}#%`);
+ serverConnection.send('ID#webAO#webAO#%');
}
- function onCOMessage(e) {
+ function onCOMessage(e: MessageEvent) {
const comsg = e.data;
const coheader = comsg.split('#', 2)[0];
const coarguments = comsg.split('#').slice(1);
if (coheader === 'PN') {
servers[serverID].online = `Online: ${Number(coarguments[0])}/${Number(coarguments[1])}`;
- oserv.close();
+ serverConnection.close();
return;
} if (coheader === 'BD') {
servers[serverID].online = 'Banned';
servers[serverID].description = coarguments[0];
- oserv.close();
+ serverConnection.close();
return;
}
if (serverID === selectedServer) {
@@ -104,15 +104,15 @@ function checkOnline(serverID: number, coIP: string) {
}
// assign the callbacks
- oserv.onopen = function (evt) {
- onCOOpen(evt);
+ serverConnection.onopen = function () {
+ onCOOpen();
};
- oserv.onmessage = function (evt) {
+ serverConnection.onmessage = function (evt: MessageEvent) {
onCOMessage(evt);
};
- oserv.onerror = function (_evt) {
+ serverConnection.onerror = function (_evt: Event) {
document.getElementById(`server${serverID}`).className = 'unavailable';
};
}
diff --git a/webAO/utils/__tests__/aoml.test.ts b/webAO/utils/__tests__/aoml.test.ts
new file mode 100644
index 0000000..90967d7
--- /dev/null
+++ b/webAO/utils/__tests__/aoml.test.ts
@@ -0,0 +1,113 @@
+import request from '../../services/request'
+import mlConfig from '../aoml';
+
+jest.mock('../../services/request')
+const networkRequest = `
+c0 = 247, 247, 247
+c0_name = White
+c0_talking = 1
+
+c2 = 247, 0, 57
+c2_name = Red
+c2_start = ~
+c2_end = ~
+c2_remove = 1
+c2_talking = 1
+
+c4 = 107, 198, 247
+c4_name = Blue
+c4_start = (
+c4_end = )
+c4_remove = 0
+c4_talking = 0
+
+c5 = 107, 198, 247
+c5_name = Blue
+c5_start = [
+c5_end = ]
+c5_remove = 1
+c5_talking = 0
+
+c6 = 107, 198, 247
+c6_name = Blue
+c6_start = |
+c6_end = |
+c6_remove = 0
+c6_talking = 0
+`
+
+const mockRequest = request as jest.MockedFunction<typeof request>;
+mockRequest.mockReturnValue(Promise.resolve(networkRequest))
+
+ describe('mlConfig', () => {
+ beforeEach(() => {
+ // Clear all instances and calls to constructor and all methods:
+ mockRequest.mockClear();
+
+ });
+
+ it('Should make a network request', () => {
+ mlConfig('localhost')
+ expect(mockRequest).toHaveBeenCalledTimes(1);
+ });
+ })
+ describe('applyMarkdown', () => {
+ const config = mlConfig('localhost')
+
+ beforeEach(() => {
+ // Clear all instances and calls to constructor and all methods:
+ mockRequest.mockClear();
+
+ });
+
+ it('Should create an array of spans containing letters', async () => {
+ const word = `hello`
+ const actual = await config.applyMarkdown(`hello`, `blue`)
+ let index = 0
+ for (const element of actual) {
+ expect(element.innerHTML).toBe(word[index])
+ index++
+ }
+ })
+ it('Should add colors based on settings', async () => {
+ const config = mlConfig('localhost')
+ const actual = await config.applyMarkdown(`(heya)`, `blue`)
+ expect(actual[0].getAttribute('style')).toBe('color: rgb(107, 198, 247);')
+ })
+ it('Should keep a letter if remove = 0', async () => {
+ const config = mlConfig('localhost')
+
+ const actual = await config.applyMarkdown(`(What())Hey!`, `white`)
+ const expected = `(`
+ expect(actual[5].innerHTML).toBe(expected)
+ })
+ it('Should remove a letter if remove = 1', async () => {
+ const config = mlConfig('localhost')
+
+ const actual = await config.applyMarkdown(`~What~()Hey!`, `white`)
+ const expected = ``
+ expect(actual[0].innerHTML).toBe(expected)
+ })
+ it('Should remove a letter if remove = 1', async () => {
+ const config = mlConfig('localhost')
+
+ const actual = await config.applyMarkdown(`~What~()Hey!`, `white`)
+ const expected = ``
+ expect(actual[0].innerHTML).toBe(expected)
+ })
+ it('Should keep a closing letter if remove = 0', async () => {
+ const config = mlConfig('localhost')
+
+ const actual = await config.applyMarkdown(`~NO[]~!`, `white`)
+ const expected = ``
+ expect(actual[4].innerHTML).toBe(expected)
+ })
+ it('Should remove a closing letter if remove = 1', async () => {
+ const config = mlConfig('localhost')
+ const actual = await config.applyMarkdown(`~NO||~!`, `white`)
+ const expected = ``
+ expect(actual[5].innerHTML).toBe(expected)
+ })
+
+ })
+
diff --git a/webAO/utils/aoml.ts b/webAO/utils/aoml.ts
new file mode 100644
index 0000000..154274d
--- /dev/null
+++ b/webAO/utils/aoml.ts
@@ -0,0 +1,117 @@
+import request from "../services/request"
+
+interface Aoml {
+ name: string;
+ start: string;
+ end: string;
+ remove: number;
+ talking: number;
+ color: string;
+}
+const aomlParser = (text: string) => {
+ let parsed: {[key: string]: Aoml}= {}
+ let currentHeader = ''
+ for (const line of text.split(/\r?\n/)) {
+ if (line === '') {
+ currentHeader = ''
+ continue;
+ }
+ const content = line.split(' = ')
+ const contentName = content[0]
+ const contentValue = content[1]
+ if (currentHeader === '') {
+ currentHeader = contentName
+ parsed[currentHeader] = {
+ color: contentValue
+ } as Aoml
+ } else {
+ const contentKey = contentName.split('_')[1]
+ parsed[currentHeader][contentKey] = contentValue
+ }
+ }
+ return parsed
+}
+
+const mlConfig = (AO_HOST: string) => {
+ const defaultUrl = `${AO_HOST}themes/default/chat_config.ini`
+ let aomlParsed: Promise<{[key: string]: Aoml}> = request(defaultUrl).then((data) => aomlParser(data));
+
+
+
+ const createIdentifiers = async () => {
+ const identifiers = new Map<string, Aoml>()
+ for (const [ruleName, value] of Object.entries(await aomlParsed)) {
+ if (value.start && value.end) {
+ identifiers.set(value.start, value)
+ identifiers.set(value.end, value)
+ }
+ }
+ return identifiers
+ }
+ const createStartIdentifiers = async () => {
+ const startingIdentifiers = new Set<string>()
+ for (const [ruleName, value] of Object.entries(await aomlParsed)) {
+ if (value?.start && value?.end) {
+ startingIdentifiers.add(value.start)
+ }
+ }
+ return startingIdentifiers
+ }
+ const applyMarkdown = async (text: string, defaultColor: string) => {
+ const identifiers = await createIdentifiers()
+ const startIdentifiers = await createStartIdentifiers()
+ const closingStack = []
+ const colorStack = []
+ // each value in output will be an html element
+ let output: HTMLSpanElement[] = []
+ for (const letter of text) {
+ let currentSelector = document.createElement('span')
+ let currentIdentifier = identifiers.get(letter)
+ const currentClosingLetter = closingStack[closingStack.length-1]
+ const keepChar = Number(currentIdentifier?.remove) === 0
+
+ if (startIdentifiers.has(letter)) {
+ const color = identifiers.get(letter).color.split(',')
+ const r = color[0]
+ const g = color[1]
+ const b = color[2]
+ colorStack.push([r,g,b])
+ closingStack.push(currentIdentifier.end)
+ const currentColor = `color: rgb(${r},${g},${b});`
+ currentSelector.setAttribute('style', currentColor)
+ if (keepChar) {
+ currentSelector.innerHTML = letter
+ }
+ } else if (currentClosingLetter === letter) {
+ const r = colorStack[colorStack.length-1][0]
+ const g = colorStack[colorStack.length-1][1]
+ const b = colorStack[colorStack.length-1][2]
+ const currentColor = `color: rgb(${r},${g},${b});`
+ currentSelector.setAttribute('style', currentColor)
+ closingStack.pop()
+ colorStack.pop()
+ if (keepChar) {
+ currentSelector.innerHTML = letter
+ }
+ } else {
+ currentSelector.innerHTML = letter
+ if (colorStack.length === 0) {
+ currentSelector.className = `text_${defaultColor}`
+ } else {
+ const r = colorStack[colorStack.length-1][0]
+ const g = colorStack[colorStack.length-1][1]
+ const b = colorStack[colorStack.length-1][2]
+ const currentColor = `color: rgb(${r},${g},${b});`
+ currentSelector.setAttribute('style', currentColor)
+ }
+ }
+ output.push(currentSelector)
+ }
+ return output
+ }
+ return {
+ applyMarkdown
+ }
+}
+
+export default mlConfig \ No newline at end of file