aboutsummaryrefslogtreecommitdiff
path: root/webAO
diff options
context:
space:
mode:
Diffstat (limited to 'webAO')
-rw-r--r--webAO/client.js37
-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/master.ts (renamed from webAO/master.js)32
-rw-r--r--webAO/styles/client.css4
-rw-r--r--webAO/utils/__tests__/aoml.test.ts113
-rw-r--r--webAO/utils/aoml.ts117
10 files changed, 322 insertions, 34 deletions
diff --git a/webAO/client.js b/webAO/client.js
index 0d52ee7..62c343e 100644
--- a/webAO/client.js
+++ b/webAO/client.js
@@ -10,7 +10,7 @@ import fileExistsSync from './utils/fileExistsSync';
import {
escapeChat, encodeChat, prepChat, safeTags,
} from './encoding.js';
-
+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;
/**
@@ -1694,15 +1696,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;
@@ -1718,12 +1713,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));
@@ -1960,6 +1951,7 @@ class Viewport {
* @param {object} chatmsg the new chat message
*/
async say(chatmsg) {
+
this.chatmsg = chatmsg;
this.textnow = '';
this.sfxplayed = 0;
@@ -2111,7 +2103,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();
}
@@ -2263,8 +2255,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;
@@ -2347,6 +2341,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,
@@ -2642,6 +2637,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;
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/master.js b/webAO/master.ts
index b4ead94..8fd8779 100644
--- a/webAO/master.js
+++ b/webAO/master.ts
@@ -2,24 +2,28 @@ import FingerprintJS from '@fingerprintjs/fingerprintjs';
import { unescapeChat, safeTags } from './encoding.js';
+declare global {
+ interface Window {
+ setServ: (ID: any) => void;
+ }
+}
+
const myStorage = window.localStorage;
const version = process.env.npm_package_version;
const MASTERSERVER_IP = 'master.aceattorneyonline.com:27014';
-let masterserver;
-
-let hdid;
+let hdid: string;
-let selectedServer = -1;
+let selectedServer: number = -1;
-const servers = [];
+let servers: { name: string, description: string, ip: string, port: number, ws_port: number, assets: string, online: string }[] = [];
servers[-2] = {
- name: 'Singleplayer', description: 'Build cases, try out new things', ip: '127.0.0.1', port: 50001, assets: '', online: '',
+ name: 'Singleplayer', description: 'Build cases, try out new things', ip: '127.0.0.1', port: 50001, ws_port: 50001, assets: '', online: '',
};
servers[-1] = {
- name: 'Localhost', description: 'This is your computer on port 50001', ip: '127.0.0.1', port: 50001, assets: '', online: 'Online: ?/?',
+ name: 'Localhost', description: 'This is your computer on port 50001', ip: '127.0.0.1', port: 50001, ws_port: 50001, assets: '', online: 'Online: ?/?',
};
const fpPromise = FingerprintJS.load();
@@ -49,7 +53,7 @@ export function check_https() {
}
}
-export function setServ(ID) {
+export function setServ(ID: number) {
selectedServer = ID;
if (document.getElementById(`server${ID}`).className === '') { checkOnline(ID, `${servers[ID].ip}:${servers[ID].ws_port}`); }
@@ -62,7 +66,7 @@ export function setServ(ID) {
}
window.setServ = setServ;
-function checkOnline(serverID, coIP) {
+function checkOnline(serverID: number, coIP: string) {
let oserv;
if (serverID !== -2) {
try {
@@ -113,12 +117,12 @@ function checkOnline(serverID, coIP) {
};
}
-function loadServerlist(thelist) {
+function loadServerlist(thelist: { name: string, description: string, ip: string, port: number, ws_port: number, assets: string, online: string }[]) {
localStorage.setItem('masterlist', JSON.stringify(thelist));
processServerlist(thelist);
}
-function cachedServerlist(response) {
+function cachedServerlist(response: Response) {
if (!response.ok) {
document.getElementById('ms_error').style.display = 'block';
processServerlist(JSON.parse(localStorage.getItem('masterlist')));
@@ -127,9 +131,9 @@ function cachedServerlist(response) {
return response.json();
}
-function processServerlist(thelist) {
+function processServerlist(thelist: { name: string, description: string, ip: string, port: number, ws_port: number, assets: string, online: string }[]) {
for (let i = 0; i < thelist.length - 1; i++) {
- const serverEntry = thelist[i];
+ const serverEntry: { name: string, description: string, ip: string, port: number, ws_port: number, assets: string, online: string } = thelist[i];
servers[i] = serverEntry;
@@ -144,7 +148,7 @@ function processServerlist(thelist) {
}
}
-function processVersion(data) {
+function processVersion(data: string) {
console.debug(data);
document.getElementById('clientinfo').innerHTML = `Client version: ${version}`;
document.getElementById('serverinfo').innerHTML = `Master server version: ${data}`;
diff --git a/webAO/styles/client.css b/webAO/styles/client.css
index c778d6f..7f72eb1 100644
--- a/webAO/styles/client.css
+++ b/webAO/styles/client.css
@@ -174,14 +174,16 @@
#client_background {
position: relative;
- padding-bottom: 75%;
background-color: transparent;
top: 0;
left: 0;
}
#client_gamewindow {
+ position: inherit;
width: 100%;
+ padding-bottom: 75%;
+
}
#client_court_static {
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..da66d0c
--- /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) => {
+ 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