diff options
Diffstat (limited to 'webAO')
| -rw-r--r-- | webAO/client.js | 37 | ||||
| -rw-r--r-- | webAO/client/aoHost.js | 5 | ||||
| -rw-r--r-- | webAO/components/__tests__/audioChannels.test.js | 9 | ||||
| -rw-r--r-- | webAO/components/__tests__/blips.test.js | 9 | ||||
| -rw-r--r-- | webAO/components/audioChannels.js | 13 | ||||
| -rw-r--r-- | webAO/components/blip.js | 17 | ||||
| -rw-r--r-- | webAO/master.ts (renamed from webAO/master.js) | 32 | ||||
| -rw-r--r-- | webAO/styles/client.css | 4 | ||||
| -rw-r--r-- | webAO/utils/__tests__/aoml.test.ts | 113 | ||||
| -rw-r--r-- | webAO/utils/aoml.ts | 117 |
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 |
