From 108636666d474119892c4b3a2f3beadb767b006e Mon Sep 17 00:00:00 2001 From: Caleb Date: Sat, 10 Sep 2022 11:09:49 -0400 Subject: Structured viewport a little differently --- webAO/viewport/viewport.ts | 901 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 901 insertions(+) create mode 100644 webAO/viewport/viewport.ts (limited to 'webAO/viewport/viewport.ts') diff --git a/webAO/viewport/viewport.ts b/webAO/viewport/viewport.ts new file mode 100644 index 0000000..9772796 --- /dev/null +++ b/webAO/viewport/viewport.ts @@ -0,0 +1,901 @@ +import tryUrls from "../utils/tryUrls"; +import fileExists from "../utils/fileExists"; +import Client, { delay } from "../client"; +import { UPDATE_INTERVAL } from "../client"; +import { setChatbox } from "../dom/setChatbox"; +import { resizeChatbox } from "../dom/resizeChatbox"; +import transparentPng from "../constants/transparentPng"; +import mlConfig from "../utils/aoml"; +import setEmote from "../client/setEmote"; +import getAnimLength from "../utils/getAnimLength"; +import { safeTags } from "../encoding"; +import setCookie from "../utils/setCookie"; +import { AO_HOST } from "../client/aoHost"; +import { appendICLog } from "../client/appendICLog"; +import { checkCallword } from '../client/checkCallword' +import { Viewport } from './interfaces/Viewport' +import { createBlipsChannels } from './utils/createBlipChannels' +import { defaultChatMsg } from './constants/defaultChatMsg' +import { createMusic } from './utils/createMusic' +import { createSfxAudio } from './utils/createSfxAudio' +import { createShoutAudio } from './utils/createShoutAudio' +import { createTestimonyAudio } from './utils/createTestimonyAudio' +import { ChatMsg } from "./interfaces/ChatMsg"; +import { Testimony } from './interfaces/Testimony' +import { COLORS } from './constants/colors' +import { SHOUTS } from './constants/shouts' +import { positions } from './constants/positions' + +const viewport = (masterClient: Client): Viewport => { + let animating = false; + let attorneyMarkdown = mlConfig(AO_HOST); + let blipChannels = createBlipsChannels(); + let chatmsg = defaultChatMsg; + let client = masterClient; + let currentBlipChannel = 0; + let lastChar = ""; + let lastEvi = 0; + let music = createMusic(); + let musicVolume = 0; + let sfxAudio = createSfxAudio(); + let sfxplayed = 0; + let shoutTimer = 0; + let shoutaudio = createShoutAudio(); + let startFirstTickCheck: boolean; + let startSecondTickCheck: boolean; + let startThirdTickCheck: boolean; + let testimonyAudio = createTestimonyAudio(); + let testimonyTimer = 0; + let testimonyUpdater: any; + let textnow = ""; + let theme: string; + let tickTimer = 0; + let updater: any; + let backgroundName = ""; + const getSfxAudio = () => sfxAudio; + const setSfxAudio = (value: HTMLAudioElement) => { sfxAudio = value }; + const getBackgroundName = () => backgroundName; + const setBackgroundName = (value: string) => { backgroundName = value }; + const getBackgroundFolder = () => + `${AO_HOST}background/${encodeURI(backgroundName.toLowerCase())}/`; + + const playSFX = async (sfxname: string, looping: boolean) => { + sfxAudio.pause(); + sfxAudio.loop = looping; + sfxAudio.src = sfxname; + sfxAudio.play(); + }; + + /** + * Changes the viewport background based on a given position. + * + * Valid positions: `def, pro, hld, hlp, wit, jud, jur, sea` + * @param {string} position the position to change into + */ + const set_side = async ({ + position, + showSpeedLines, + showDesk, + }: { + position: string; + showSpeedLines: boolean; + showDesk: boolean; + }) => { + const view = document.getElementById("client_fullview"); + + let bench: HTMLImageElement; + if ("def,pro,wit".includes(position)) { + bench = ( + document.getElementById(`client_${position}_bench`) + ); + } else { + bench = document.getElementById("client_bench_classic"); + } + + let court: HTMLImageElement; + if ("def,pro,wit".includes(position)) { + court = ( + document.getElementById(`client_court_${position}`) + ); + } else { + court = document.getElementById("client_court_classic"); + } + + let bg; + let desk; + let speedLines; + + if ("def,pro,hld,hlp,wit,jud,jur,sea".includes(position)) { + bg = positions[position].bg; + desk = positions[position].desk; + speedLines = positions[position].speedLines; + } else { + bg = `${position}`; + desk = { ao2: `${position}_overlay.png`, ao1: "_overlay.png" }; + speedLines = "defense_speedlines.gif"; + } + + if (showSpeedLines === true) { + court.src = `${AO_HOST}themes/default/${encodeURI(speedLines)}`; + } else { + court.src = await tryUrls(getBackgroundFolder() + bg); + } + + if (showDesk === true && desk) { + const deskFilename = (await fileExists(getBackgroundFolder() + desk.ao2)) + ? desk.ao2 + : desk.ao1; + bench.src = getBackgroundFolder() + deskFilename; + bench.style.opacity = "1"; + } else { + bench.style.opacity = "0"; + } + + if ("def,pro,wit".includes(position)) { + view.style.display = ""; + document.getElementById("client_classicview").style.display = "none"; + switch (position) { + case "def": + view.style.left = "0"; + break; + case "wit": + view.style.left = "-200%"; + break; + case "pro": + view.style.left = "-400%"; + break; + } + } else { + view.style.display = "none"; + document.getElementById("client_classicview").style.display = ""; + } + }; + + /** + * Intialize testimony updater + */ + const initTestimonyUpdater = () => { + const testimonyFilenames: Testimony = { + 1: "witnesstestimony", + 2: "crossexamination", + 3: "notguilty", + 4: "guilty", + }; + + const testimony = testimonyFilenames[masterClient.testimonyID]; + if (!testimony) { + console.warn(`Invalid testimony ID ${masterClient.testimonyID}`); + return; + } + + testimonyAudio.src = masterClient.resources[testimony].sfx; + testimonyAudio.play(); + + const testimonyOverlay = ( + document.getElementById("client_testimony") + ); + testimonyOverlay.src = masterClient.resources[testimony].src; + testimonyOverlay.style.opacity = "1"; + + testimonyTimer = 0; + testimonyUpdater = setTimeout(() => updateTestimony(), UPDATE_INTERVAL); + }; + + /** + * Updates the testimony overaly + */ + const updateTestimony = () => { + const testimonyFilenames: Testimony = { + 1: "witnesstestimony", + 2: "crossexamination", + 3: "notguilty", + 4: "guilty", + }; + + // Update timer + testimonyTimer += UPDATE_INTERVAL; + + const testimony = testimonyFilenames[masterClient.testimonyID]; + const resource = masterClient.resources[testimony]; + if (!resource) { + disposeTestimony(); + return; + } + + if (testimonyTimer >= resource.duration) { + disposeTestimony(); + } else { + testimonyUpdater = setTimeout(() => updateTestimony(), UPDATE_INTERVAL); + } + }; + + /** + * Dispose the testimony overlay + */ + const disposeTestimony = () => { + masterClient.testimonyID = 0; + testimonyTimer = 0; + document.getElementById("client_testimony").style.opacity = "0"; + clearTimeout(testimonyUpdater); + }; + + /** + * Sets a new emote. + * This sets up everything before the tick() loops starts + * a lot of things can probably be moved here, like starting the shout animation if there is one + * TODO: the preanim logic, on the other hand, should probably be moved to tick() + * @param {object} chatmsg the new chat message + */ + const handle_ic_speaking = async (playerChatMsg: ChatMsg) => { + chatmsg = playerChatMsg; + client.viewport.chatmsg = playerChatMsg; + + textnow = ""; + sfxplayed = 0; + tickTimer = 0; + animating = true; + startFirstTickCheck = true; + startSecondTickCheck = false; + startThirdTickCheck = false; + let charLayers = document.getElementById("client_char"); + let pairLayers = document.getElementById("client_pair_char"); + // stop updater + clearTimeout(updater); + + // stop last sfx from looping any longer + sfxAudio.loop = false; + + const fg = document.getElementById("client_fg"); + const gamewindow = document.getElementById("client_gamewindow"); + const waitingBox = document.getElementById("client_chatwaiting"); + + // Reset CSS animation + gamewindow.style.animation = ""; + waitingBox.style.opacity = "0"; + + const eviBox = document.getElementById("client_evi"); + + if (lastEvi !== chatmsg.evidence) { + eviBox.style.opacity = "0"; + eviBox.style.height = "0%"; + } + lastEvi = chatmsg.evidence; + + const validSides: string[] = ["def", "pro", "wit"]; // these are for the full view pan, the other positions use 'client_char' + if (validSides.includes(chatmsg.side)) { + charLayers = document.getElementById(`client_${chatmsg.side}_char`); + pairLayers = document.getElementById(`client_${chatmsg.side}_pair_char`); + } + + const chatContainerBox = document.getElementById("client_chatcontainer"); + const nameBoxInner = document.getElementById("client_inner_name"); + const chatBoxInner = document.getElementById("client_inner_chat"); + + const displayname = + (document.getElementById("showname")).checked && + chatmsg.showname !== "" + ? chatmsg.showname + : chatmsg.nameplate; + + // Clear out the last message + chatBoxInner.innerText = textnow; + nameBoxInner.innerText = displayname; + + if (lastChar !== chatmsg.name) { + charLayers.style.opacity = "0"; + pairLayers.style.opacity = "0"; + } + + lastChar = chatmsg.name; + client.viewport.lastChar = chatmsg.name; + + appendICLog(chatmsg.content, chatmsg.showname, chatmsg.nameplate); + + checkCallword(chatmsg.content, sfxAudio); + + setEmote( + AO_HOST, + client, + chatmsg.name.toLowerCase(), + chatmsg.sprite, + "(a)", + false, + chatmsg.side + ); + + if (chatmsg.other_name) { + setEmote( + AO_HOST, + client, + chatmsg.other_name.toLowerCase(), + chatmsg.other_emote, + "(a)", + false, + chatmsg.side + ); + } + + // gets which shout shall played + const shoutSprite = ( + document.getElementById("client_shout") + ); + const shout = SHOUTS[chatmsg.objection]; + if (shout) { + // Hide message box + chatContainerBox.style.opacity = "0"; + if (chatmsg.objection === 4) { + shoutSprite.src = `${AO_HOST}characters/${encodeURI( + chatmsg.name.toLowerCase() + )}/custom.gif`; + } else { + shoutSprite.src = masterClient.resources[shout].src; + shoutSprite.style.animation = "bubble 700ms steps(10, jump-both)"; + } + shoutSprite.style.opacity = "1"; + + shoutaudio.src = `${AO_HOST}characters/${encodeURI( + chatmsg.name.toLowerCase() + )}/${shout}.opus`; + shoutaudio.play(); + shoutTimer = masterClient.resources[shout].duration; + } else { + shoutTimer = 0; + } + + chatmsg.startpreanim = true; + let gifLength = 0; + + if (chatmsg.type === 1 && chatmsg.preanim !== "-") { + //we have a preanim + chatContainerBox.style.opacity = "0"; + gifLength = await getAnimLength( + `${AO_HOST}characters/${encodeURI( + chatmsg.name.toLowerCase() + )}/${encodeURI(chatmsg.preanim)}` + ); + console.debug("preanim is " + gifLength + " long"); + chatmsg.startspeaking = false; + } else { + chatmsg.startspeaking = true; + if (chatmsg.content !== "") chatContainerBox.style.opacity = "1"; + } + chatmsg.preanimdelay = gifLength; + const setAside = { + position: chatmsg.side, + showSpeedLines: false, + showDesk: false, + }; + let skipoffset: boolean = false; + if (chatmsg.type === 5) { + setAside.showSpeedLines = true; + setAside.showDesk = false; + set_side(setAside); + } else { + switch (Number(chatmsg.deskmod)) { + case 0: //desk is hidden + setAside.showSpeedLines = false; + setAside.showDesk = false; + set_side(setAside); + break; + case 1: //desk is shown + setAside.showSpeedLines = false; + setAside.showDesk = true; + set_side(setAside); + break; + case 2: //desk is hidden during preanim, but shown during idle/talk + setAside.showSpeedLines = false; + setAside.showDesk = false; + set_side(setAside); + break; + case 3: //opposite of 2 + setAside.showSpeedLines = false; + setAside.showDesk = false; + set_side(setAside); + break; + case 4: //desk is hidden, character offset is ignored, pair character is hidden during preanim, normal behavior during idle/talk + setAside.showSpeedLines = false; + setAside.showDesk = false; + set_side(setAside); + skipoffset = true; + break; + case 5: //opposite of 4 + setAside.showSpeedLines = false; + setAside.showDesk = true; + set_side(setAside); + break; + default: + setAside.showSpeedLines = false; + setAside.showDesk = true; + set_side(setAside); + break; + } + } + + setChatbox(chatmsg.chatbox); + resizeChatbox(); + + if (!skipoffset) { + // Flip the character + charLayers.style.transform = + chatmsg.flip === 1 ? "scaleX(-1)" : "scaleX(1)"; + pairLayers.style.transform = + chatmsg.other_flip === 1 ? "scaleX(-1)" : "scaleX(1)"; + + // Shift by the horizontal offset + switch (chatmsg.side) { + case "wit": + pairLayers.style.left = `${200 + Number(chatmsg.other_offset[0])}%`; + charLayers.style.left = `${200 + Number(chatmsg.self_offset[0])}%`; + break; + case "pro": + pairLayers.style.left = `${400 + Number(chatmsg.other_offset[0])}%`; + charLayers.style.left = `${400 + Number(chatmsg.self_offset[0])}%`; + break; + default: + pairLayers.style.left = `${Number(chatmsg.other_offset[0])}%`; + charLayers.style.left = `${Number(chatmsg.self_offset[0])}%`; + break; + } + + // New vertical offsets + pairLayers.style.top = `${Number(chatmsg.other_offset[1])}%`; + charLayers.style.top = `${Number(chatmsg.self_offset[1])}%`; + } + + blipChannels.forEach( + (channel: HTMLAudioElement) => + (channel.src = `${AO_HOST}sounds/general/sfx-blip${encodeURI( + chatmsg.blips.toLowerCase() + )}.opus`) + ); + + // process markup + if (chatmsg.content.startsWith("~~")) { + chatBoxInner.style.textAlign = "center"; + chatmsg.content = chatmsg.content.substring(2, chatmsg.content.length); + } else { + chatBoxInner.style.textAlign = "inherit"; + } + + // apply effects + fg.style.animation = ""; + const effectName = chatmsg.effects[0].toLowerCase(); + const badEffects = ["", "-", "none"]; + if (effectName.startsWith("rain")) { + (document.getElementById("effect_css")).href = "styles/effects/rain.css"; + let intensity = 200; + if (effectName.endsWith("weak")) { + intensity = 100; + } else if (effectName.endsWith("strong")) { + intensity = 400; + } + if (intensity < fg.childElementCount) + fg.innerHTML = ''; + else + intensity = intensity - fg.childElementCount; + + for (let i = 0; i < intensity; i++) { + let drop = document.createElement("p"); + drop.style.left = (Math.random() * 100) + "%"; + drop.style.animationDelay = String(Math.random()) + "s"; + fg.appendChild(drop) + } + } else if ( + chatmsg.effects[0] && + !badEffects.includes(effectName) + ) { + (document.getElementById("effect_css")).href = ""; + fg.innerHTML = ''; + const baseEffectUrl = `${AO_HOST}themes/default/effects/`; + fg.src = `${baseEffectUrl}${encodeURI(effectName)}.webp`; + } else { + fg.innerHTML = ''; + fg.src = transparentPng; + } + + charLayers.style.opacity = "1"; + + const soundChecks = ["0", "1", "", undefined]; + if (soundChecks.some((check) => chatmsg.sound === check)) { + chatmsg.sound = chatmsg.effects[2]; + } + chatmsg.parsed = await attorneyMarkdown.applyMarkdown( + chatmsg.content, + COLORS[chatmsg.color] + ); + chat_tick(); + }; + + const handleTextTick = async (charLayers: HTMLImageElement) => { + const chatBox = document.getElementById("client_chat"); + const waitingBox = document.getElementById("client_chatwaiting"); + const chatBoxInner = document.getElementById("client_inner_chat"); + const charName = chatmsg.name.toLowerCase(); + const charEmote = chatmsg.sprite.toLowerCase(); + + if (chatmsg.content.charAt(textnow.length) !== " ") { + blipChannels[currentBlipChannel].play(); + currentBlipChannel++; + currentBlipChannel %= blipChannels.length; + } + textnow = chatmsg.content.substring(0, textnow.length + 1); + const characterElement = chatmsg.parsed[textnow.length - 1]; + if (characterElement) { + const COMMAND_IDENTIFIER = "\\"; + + const nextCharacterElement = chatmsg.parsed[textnow.length]; + const flash = async () => { + const effectlayer = document.getElementById("client_fg"); + playSFX(`${AO_HOST}sounds/general/sfx-realization.opus`, false); + effectlayer.style.animation = "flash 0.4s 1"; + await delay(400); + effectlayer.style.removeProperty("animation"); + }; + + const shake = async () => { + const gamewindow = document.getElementById("client_gamewindow"); + playSFX(`${AO_HOST}sounds/general/sfx-stab.opus`, false); + gamewindow.style.animation = "shake 0.2s 1"; + await delay(200); + gamewindow.style.removeProperty("animation"); + }; + + const commands = new Map( + Object.entries({ + s: shake, + f: flash, + }) + ); + const textSpeeds = new Set(["{", "}"]); + + // Changing Text Speed + if (textSpeeds.has(characterElement.innerHTML)) { + // Grab them all in a row + const MAX_SLOW_CHATSPEED = 120; + for (let i = textnow.length; i < chatmsg.content.length; i++) { + const currentCharacter = chatmsg.parsed[i - 1].innerHTML; + if (currentCharacter === "}") { + if (chatmsg.speed > 0) { + chatmsg.speed -= 20; + } + } else if (currentCharacter === "{") { + if (chatmsg.speed < MAX_SLOW_CHATSPEED) { + chatmsg.speed += 20; + } + } else { + // No longer at a speed character + textnow = chatmsg.content.substring(0, i); + break; + } + } + } + + if ( + characterElement.innerHTML === COMMAND_IDENTIFIER && + commands.has(nextCharacterElement?.innerHTML) + ) { + textnow = chatmsg.content.substring(0, textnow.length + 1); + await commands.get(nextCharacterElement.innerHTML)(); + } else { + chatBoxInner.appendChild(chatmsg.parsed[textnow.length - 1]); + } + } + // scroll to bottom + chatBox.scrollTop = chatBox.scrollHeight; + + if (textnow === chatmsg.content) { + animating = false; + setEmote( + AO_HOST, + client, + charName, + charEmote, + "(a)", + false, + chatmsg.side + ); + charLayers.style.opacity = "1"; + waitingBox.style.opacity = "1"; + clearTimeout(updater); + } + }; + /** + * Updates the chatbox based on the given text. + * + * OK, here's the documentation on how this works: + * + * 1 _animating + * If we're not done with this characters animation, i.e. his text isn't fully there, set a timeout for the next tick/step to happen + * + * 2 startpreanim + * If the shout timer is over it starts with the preanim + * The first thing it checks for is the shake effect (TODO on client this is handled by the @ symbol and not a flag ) + * Then is the flash/realization effect + * After that, the shout image set to be transparent + * and the main characters preanim gif is loaded + * If pairing is supported the paired character will just stand around with his idle sprite + * + * 3 preanimdelay over + * this animates the evidence popup and finally shows the character name and message box + * it sets the text color and the character speaking sprite + * + * 4 textnow != content + * this adds a character to the textbox and stops the animations if the entire message is present in the textbox + * + * 5 sfx + * independent of the stuff above, this will play any sound effects specified by the emote the character sent. + * happens after the shout delay + an sfx delay that comes with the message packet + * + * XXX: This relies on a global variable `chatmsg`! + */ + const chat_tick = async () => { + // note: this is called fairly often + // do not perform heavy operations here + + await delay(chatmsg.speed); + if (textnow === chatmsg.content) { + return; + } + + const gamewindow = document.getElementById("client_gamewindow"); + const waitingBox = document.getElementById("client_chatwaiting"); + const eviBox = document.getElementById("client_evi"); + const shoutSprite = ( + document.getElementById("client_shout") + ); + const effectlayer = document.getElementById("client_fg"); + const chatBoxInner = document.getElementById("client_inner_chat"); + let charLayers = document.getElementById("client_char"); + let pairLayers = ( + document.getElementById("client_pair_char") + ); + + const validSides: string[] = ["def", "pro", "wit"]; // these are for the full view pan, the other positions use 'client_char' + if (validSides.includes(chatmsg.side)) { + charLayers = ( + document.getElementById(`client_${chatmsg.side}_char`) + ); + pairLayers = ( + document.getElementById(`client_${chatmsg.side}_pair_char`) + ); + } + + const charName = chatmsg.name.toLowerCase(); + const charEmote = chatmsg.sprite.toLowerCase(); + + const pairName = chatmsg.other_name.toLowerCase(); + const pairEmote = chatmsg.other_emote.toLowerCase(); + + // TODO: preanims sometimes play when they're not supposed to + const isShoutOver = tickTimer >= shoutTimer; + const isShoutAndPreanimOver = + tickTimer >= shoutTimer + chatmsg.preanimdelay; + if (isShoutOver && startFirstTickCheck) { + // Effect stuff + if (chatmsg.screenshake === 1) { + // Shake screen + playSFX(`${AO_HOST}sounds/general/sfx-stab.opus`, false); + gamewindow.style.animation = "shake 0.2s 1"; + } + if (chatmsg.flash === 1) { + // Flash screen + playSFX(`${AO_HOST}sounds/general/sfx-realization.opus`, false); + effectlayer.style.animation = "flash 0.4s 1"; + } + + // Pre-animation stuff + if (chatmsg.preanimdelay > 0) { + shoutSprite.style.opacity = "0"; + shoutSprite.style.animation = ""; + const preanim = chatmsg.preanim.toLowerCase(); + setEmote(AO_HOST, client, charName, preanim, "", false, chatmsg.side); + } + + if (chatmsg.other_name) { + pairLayers.style.opacity = "1"; + } else { + pairLayers.style.opacity = "0"; + } + // Done with first check, move to second + startFirstTickCheck = false; + startSecondTickCheck = true; + + chatmsg.startpreanim = false; + chatmsg.startspeaking = true; + } + + const hasNonInterruptingPreAnim = chatmsg.noninterrupting_preanim === 1; + if (textnow !== chatmsg.content && hasNonInterruptingPreAnim) { + const chatContainerBox = document.getElementById("client_chatcontainer"); + chatContainerBox.style.opacity = "1"; + await handleTextTick(charLayers); + } else if (isShoutAndPreanimOver && startSecondTickCheck) { + if (chatmsg.startspeaking) { + chatmsg.startspeaking = false; + + // Evidence Bullshit + if (chatmsg.evidence > 0) { + // Prepare evidence + eviBox.src = safeTags( + masterClient.evidences[chatmsg.evidence - 1].icon + ); + + eviBox.style.width = "auto"; + eviBox.style.height = "36.5%"; + eviBox.style.opacity = "1"; + + testimonyAudio.src = `${AO_HOST}sounds/general/sfx-evidenceshoop.opus`; + testimonyAudio.play(); + + if (chatmsg.side === "def") { + // Only def show evidence on right + eviBox.style.right = "1em"; + eviBox.style.left = "initial"; + } else { + eviBox.style.right = "initial"; + eviBox.style.left = "1em"; + } + } + chatBoxInner.className = `text_${COLORS[chatmsg.color]}`; + + if (chatmsg.preanimdelay === 0) { + shoutSprite.style.opacity = "0"; + shoutSprite.style.animation = ""; + } + + switch (Number(chatmsg.deskmod)) { + case 2: + set_side({ + position: chatmsg.side, + showSpeedLines: false, + showDesk: true, + }); + break; + case 3: + set_side({ + position: chatmsg.side, + showSpeedLines: false, + showDesk: false, + }); + break; + case 4: + set_side({ + position: chatmsg.side, + showSpeedLines: false, + showDesk: true, + }); + break; + case 5: + set_side({ + position: chatmsg.side, + showSpeedLines: false, + showDesk: false, + }); + break; + } + + if (chatmsg.other_name) { + setEmote( + AO_HOST, + client, + pairName, + pairEmote, + "(a)", + true, + chatmsg.side + ); + pairLayers.style.opacity = "1"; + } else { + pairLayers.style.opacity = "0"; + } + + setEmote( + AO_HOST, + client, + charName, + charEmote, + "(b)", + false, + chatmsg.side + ); + charLayers.style.opacity = "1"; + + if (textnow === chatmsg.content) { + setEmote( + AO_HOST, + client, + charName, + charEmote, + "(a)", + false, + chatmsg.side + ); + charLayers.style.opacity = "1"; + waitingBox.style.opacity = "1"; + animating = false; + clearTimeout(updater); + return; + } + } else if (textnow !== chatmsg.content) { + const chatContainerBox = document.getElementById( + "client_chatcontainer" + ); + chatContainerBox.style.opacity = "1"; + await handleTextTick(charLayers); + } + } + + if (!sfxplayed && chatmsg.snddelay + shoutTimer >= tickTimer) { + sfxplayed = 1; + if ( + chatmsg.sound !== "0" && + chatmsg.sound !== "1" && + chatmsg.sound !== "" && + chatmsg.sound !== undefined && + (chatmsg.type == 1 || chatmsg.type == 2 || chatmsg.type == 6) + ) { + playSFX( + `${AO_HOST}sounds/general/${encodeURI( + chatmsg.sound.toLowerCase() + )}.opus`, + chatmsg.looping_sfx + ); + } + } + if (animating) { + chat_tick(); + } + tickTimer += UPDATE_INTERVAL; + }; + /** + * Triggered by the theme selector. + */ + function reloadTheme() { + theme = (document.getElementById("client_themeselect")) + .value; + + setCookie("theme", theme); + (( + document.getElementById("client_theme") + )).href = `styles/${theme}.css`; + } + window.reloadTheme = reloadTheme; + + const changeMusicVolume = (volume: number = -1) => { + const clientVolume = Number( + (document.getElementById("client_mvolume")).value + ); + let musicVolume = volume === -1 ? clientVolume : volume; + music.forEach( + (channel: HTMLAudioElement) => (channel.volume = musicVolume) + ); + setCookie("musicVolume", String(musicVolume)); + }; + window.changeMusicVolume = changeMusicVolume; + + return { + chat_tick, + changeMusicVolume, + reloadTheme, + playSFX, + set_side, + setBackgroundName, + initTestimonyUpdater, + updateTestimony, + disposeTestimony, + handle_ic_speaking, + handleTextTick, + getBackgroundFolder, + getBackgroundName, + getSfxAudio, + setSfxAudio, + theme, + chatmsg, + blipChannels, + lastChar, + music, + musicVolume, + }; +}; + +export default viewport; -- cgit From f8e8ab1b0d0edcddc55f57df76ee81fa14a1e5e8 Mon Sep 17 00:00:00 2001 From: Caleb Date: Sun, 11 Sep 2022 16:31:04 -0400 Subject: Finishing of refactor --- webAO/viewport/viewport.ts | 529 ++++++--------------------------------------- 1 file changed, 63 insertions(+), 466 deletions(-) (limited to 'webAO/viewport/viewport.ts') diff --git a/webAO/viewport/viewport.ts b/webAO/viewport/viewport.ts index 9772796..9ac6e96 100644 --- a/webAO/viewport/viewport.ts +++ b/webAO/viewport/viewport.ts @@ -1,18 +1,8 @@ -import tryUrls from "../utils/tryUrls"; -import fileExists from "../utils/fileExists"; -import Client, { delay } from "../client"; +import { client, delay } from "../client"; import { UPDATE_INTERVAL } from "../client"; -import { setChatbox } from "../dom/setChatbox"; -import { resizeChatbox } from "../dom/resizeChatbox"; -import transparentPng from "../constants/transparentPng"; -import mlConfig from "../utils/aoml"; import setEmote from "../client/setEmote"; -import getAnimLength from "../utils/getAnimLength"; import { safeTags } from "../encoding"; -import setCookie from "../utils/setCookie"; import { AO_HOST } from "../client/aoHost"; -import { appendICLog } from "../client/appendICLog"; -import { checkCallword } from '../client/checkCallword' import { Viewport } from './interfaces/Viewport' import { createBlipsChannels } from './utils/createBlipChannels' import { defaultChatMsg } from './constants/defaultChatMsg' @@ -20,18 +10,16 @@ import { createMusic } from './utils/createMusic' import { createSfxAudio } from './utils/createSfxAudio' import { createShoutAudio } from './utils/createShoutAudio' import { createTestimonyAudio } from './utils/createTestimonyAudio' -import { ChatMsg } from "./interfaces/ChatMsg"; import { Testimony } from './interfaces/Testimony' import { COLORS } from './constants/colors' -import { SHOUTS } from './constants/shouts' -import { positions } from './constants/positions' +import { set_side } from './utils/setSide' +import { ChatMsg } from "./interfaces/ChatMsg"; +import { setStartFirstTickCheck, setStartSecondTickCheck, startFirstTickCheck, startSecondTickCheck } from "./utils/handleICSpeaking"; -const viewport = (masterClient: Client): Viewport => { +const viewport = (): Viewport => { let animating = false; - let attorneyMarkdown = mlConfig(AO_HOST); let blipChannels = createBlipsChannels(); let chatmsg = defaultChatMsg; - let client = masterClient; let currentBlipChannel = 0; let lastChar = ""; let lastEvi = 0; @@ -41,9 +29,6 @@ const viewport = (masterClient: Client): Viewport => { let sfxplayed = 0; let shoutTimer = 0; let shoutaudio = createShoutAudio(); - let startFirstTickCheck: boolean; - let startSecondTickCheck: boolean; - let startThirdTickCheck: boolean; let testimonyAudio = createTestimonyAudio(); let testimonyTimer = 0; let testimonyUpdater: any; @@ -58,129 +43,34 @@ const viewport = (masterClient: Client): Viewport => { const setBackgroundName = (value: string) => { backgroundName = value }; const getBackgroundFolder = () => `${AO_HOST}background/${encodeURI(backgroundName.toLowerCase())}/`; - + const getTextNow = () => {return textnow} + const setTextNow = (val: string) => {textnow = val} + const getChatmsg = () => {return chatmsg} + const setChatmsg = (val: ChatMsg) => {chatmsg = val} + const getSfxPlayed = () => sfxplayed + const setSfxPlayed = (val: number) => {sfxplayed = val} + const getTickTimer = () => tickTimer + const setTickTimer = (val: number) => {tickTimer = val} + const getAnimating = () => animating + const setAnimating = (val: boolean) => {animating = val} + const getLastEvidence = () => lastEvi + const setLastEvidence = (val: number) => {lastEvi = val} + const setLastCharacter = (val: string) => {lastChar = val} + const getLastCharacter = () => lastChar + const getShoutTimer = () => shoutTimer + const setShoutTimer = (val: number) => {shoutTimer = val} + const getTheme = () => theme + const setTheme = (val: string) => {theme = val} + const getTestimonyTimer = () => testimonyTimer; + const setTestimonyTimer = (val: number) => {testimonyTimer = val} + const setTestimonyUpdater = (val: any) => {testimonyUpdater = val} + const getTestimonyUpdater = () => testimonyUpdater const playSFX = async (sfxname: string, looping: boolean) => { sfxAudio.pause(); sfxAudio.loop = looping; sfxAudio.src = sfxname; sfxAudio.play(); }; - - /** - * Changes the viewport background based on a given position. - * - * Valid positions: `def, pro, hld, hlp, wit, jud, jur, sea` - * @param {string} position the position to change into - */ - const set_side = async ({ - position, - showSpeedLines, - showDesk, - }: { - position: string; - showSpeedLines: boolean; - showDesk: boolean; - }) => { - const view = document.getElementById("client_fullview"); - - let bench: HTMLImageElement; - if ("def,pro,wit".includes(position)) { - bench = ( - document.getElementById(`client_${position}_bench`) - ); - } else { - bench = document.getElementById("client_bench_classic"); - } - - let court: HTMLImageElement; - if ("def,pro,wit".includes(position)) { - court = ( - document.getElementById(`client_court_${position}`) - ); - } else { - court = document.getElementById("client_court_classic"); - } - - let bg; - let desk; - let speedLines; - - if ("def,pro,hld,hlp,wit,jud,jur,sea".includes(position)) { - bg = positions[position].bg; - desk = positions[position].desk; - speedLines = positions[position].speedLines; - } else { - bg = `${position}`; - desk = { ao2: `${position}_overlay.png`, ao1: "_overlay.png" }; - speedLines = "defense_speedlines.gif"; - } - - if (showSpeedLines === true) { - court.src = `${AO_HOST}themes/default/${encodeURI(speedLines)}`; - } else { - court.src = await tryUrls(getBackgroundFolder() + bg); - } - - if (showDesk === true && desk) { - const deskFilename = (await fileExists(getBackgroundFolder() + desk.ao2)) - ? desk.ao2 - : desk.ao1; - bench.src = getBackgroundFolder() + deskFilename; - bench.style.opacity = "1"; - } else { - bench.style.opacity = "0"; - } - - if ("def,pro,wit".includes(position)) { - view.style.display = ""; - document.getElementById("client_classicview").style.display = "none"; - switch (position) { - case "def": - view.style.left = "0"; - break; - case "wit": - view.style.left = "-200%"; - break; - case "pro": - view.style.left = "-400%"; - break; - } - } else { - view.style.display = "none"; - document.getElementById("client_classicview").style.display = ""; - } - }; - - /** - * Intialize testimony updater - */ - const initTestimonyUpdater = () => { - const testimonyFilenames: Testimony = { - 1: "witnesstestimony", - 2: "crossexamination", - 3: "notguilty", - 4: "guilty", - }; - - const testimony = testimonyFilenames[masterClient.testimonyID]; - if (!testimony) { - console.warn(`Invalid testimony ID ${masterClient.testimonyID}`); - return; - } - - testimonyAudio.src = masterClient.resources[testimony].sfx; - testimonyAudio.play(); - - const testimonyOverlay = ( - document.getElementById("client_testimony") - ); - testimonyOverlay.src = masterClient.resources[testimony].src; - testimonyOverlay.style.opacity = "1"; - - testimonyTimer = 0; - testimonyUpdater = setTimeout(() => updateTestimony(), UPDATE_INTERVAL); - }; - /** * Updates the testimony overaly */ @@ -195,8 +85,8 @@ const viewport = (masterClient: Client): Viewport => { // Update timer testimonyTimer += UPDATE_INTERVAL; - const testimony = testimonyFilenames[masterClient.testimonyID]; - const resource = masterClient.resources[testimony]; + const testimony = testimonyFilenames[client.testimonyID]; + const resource = client.resources[testimony]; if (!resource) { disposeTestimony(); return; @@ -208,304 +98,15 @@ const viewport = (masterClient: Client): Viewport => { testimonyUpdater = setTimeout(() => updateTestimony(), UPDATE_INTERVAL); } }; - /** * Dispose the testimony overlay */ const disposeTestimony = () => { - masterClient.testimonyID = 0; + client.testimonyID = 0; testimonyTimer = 0; document.getElementById("client_testimony").style.opacity = "0"; clearTimeout(testimonyUpdater); }; - - /** - * Sets a new emote. - * This sets up everything before the tick() loops starts - * a lot of things can probably be moved here, like starting the shout animation if there is one - * TODO: the preanim logic, on the other hand, should probably be moved to tick() - * @param {object} chatmsg the new chat message - */ - const handle_ic_speaking = async (playerChatMsg: ChatMsg) => { - chatmsg = playerChatMsg; - client.viewport.chatmsg = playerChatMsg; - - textnow = ""; - sfxplayed = 0; - tickTimer = 0; - animating = true; - startFirstTickCheck = true; - startSecondTickCheck = false; - startThirdTickCheck = false; - let charLayers = document.getElementById("client_char"); - let pairLayers = document.getElementById("client_pair_char"); - // stop updater - clearTimeout(updater); - - // stop last sfx from looping any longer - sfxAudio.loop = false; - - const fg = document.getElementById("client_fg"); - const gamewindow = document.getElementById("client_gamewindow"); - const waitingBox = document.getElementById("client_chatwaiting"); - - // Reset CSS animation - gamewindow.style.animation = ""; - waitingBox.style.opacity = "0"; - - const eviBox = document.getElementById("client_evi"); - - if (lastEvi !== chatmsg.evidence) { - eviBox.style.opacity = "0"; - eviBox.style.height = "0%"; - } - lastEvi = chatmsg.evidence; - - const validSides: string[] = ["def", "pro", "wit"]; // these are for the full view pan, the other positions use 'client_char' - if (validSides.includes(chatmsg.side)) { - charLayers = document.getElementById(`client_${chatmsg.side}_char`); - pairLayers = document.getElementById(`client_${chatmsg.side}_pair_char`); - } - - const chatContainerBox = document.getElementById("client_chatcontainer"); - const nameBoxInner = document.getElementById("client_inner_name"); - const chatBoxInner = document.getElementById("client_inner_chat"); - - const displayname = - (document.getElementById("showname")).checked && - chatmsg.showname !== "" - ? chatmsg.showname - : chatmsg.nameplate; - - // Clear out the last message - chatBoxInner.innerText = textnow; - nameBoxInner.innerText = displayname; - - if (lastChar !== chatmsg.name) { - charLayers.style.opacity = "0"; - pairLayers.style.opacity = "0"; - } - - lastChar = chatmsg.name; - client.viewport.lastChar = chatmsg.name; - - appendICLog(chatmsg.content, chatmsg.showname, chatmsg.nameplate); - - checkCallword(chatmsg.content, sfxAudio); - - setEmote( - AO_HOST, - client, - chatmsg.name.toLowerCase(), - chatmsg.sprite, - "(a)", - false, - chatmsg.side - ); - - if (chatmsg.other_name) { - setEmote( - AO_HOST, - client, - chatmsg.other_name.toLowerCase(), - chatmsg.other_emote, - "(a)", - false, - chatmsg.side - ); - } - - // gets which shout shall played - const shoutSprite = ( - document.getElementById("client_shout") - ); - const shout = SHOUTS[chatmsg.objection]; - if (shout) { - // Hide message box - chatContainerBox.style.opacity = "0"; - if (chatmsg.objection === 4) { - shoutSprite.src = `${AO_HOST}characters/${encodeURI( - chatmsg.name.toLowerCase() - )}/custom.gif`; - } else { - shoutSprite.src = masterClient.resources[shout].src; - shoutSprite.style.animation = "bubble 700ms steps(10, jump-both)"; - } - shoutSprite.style.opacity = "1"; - - shoutaudio.src = `${AO_HOST}characters/${encodeURI( - chatmsg.name.toLowerCase() - )}/${shout}.opus`; - shoutaudio.play(); - shoutTimer = masterClient.resources[shout].duration; - } else { - shoutTimer = 0; - } - - chatmsg.startpreanim = true; - let gifLength = 0; - - if (chatmsg.type === 1 && chatmsg.preanim !== "-") { - //we have a preanim - chatContainerBox.style.opacity = "0"; - gifLength = await getAnimLength( - `${AO_HOST}characters/${encodeURI( - chatmsg.name.toLowerCase() - )}/${encodeURI(chatmsg.preanim)}` - ); - console.debug("preanim is " + gifLength + " long"); - chatmsg.startspeaking = false; - } else { - chatmsg.startspeaking = true; - if (chatmsg.content !== "") chatContainerBox.style.opacity = "1"; - } - chatmsg.preanimdelay = gifLength; - const setAside = { - position: chatmsg.side, - showSpeedLines: false, - showDesk: false, - }; - let skipoffset: boolean = false; - if (chatmsg.type === 5) { - setAside.showSpeedLines = true; - setAside.showDesk = false; - set_side(setAside); - } else { - switch (Number(chatmsg.deskmod)) { - case 0: //desk is hidden - setAside.showSpeedLines = false; - setAside.showDesk = false; - set_side(setAside); - break; - case 1: //desk is shown - setAside.showSpeedLines = false; - setAside.showDesk = true; - set_side(setAside); - break; - case 2: //desk is hidden during preanim, but shown during idle/talk - setAside.showSpeedLines = false; - setAside.showDesk = false; - set_side(setAside); - break; - case 3: //opposite of 2 - setAside.showSpeedLines = false; - setAside.showDesk = false; - set_side(setAside); - break; - case 4: //desk is hidden, character offset is ignored, pair character is hidden during preanim, normal behavior during idle/talk - setAside.showSpeedLines = false; - setAside.showDesk = false; - set_side(setAside); - skipoffset = true; - break; - case 5: //opposite of 4 - setAside.showSpeedLines = false; - setAside.showDesk = true; - set_side(setAside); - break; - default: - setAside.showSpeedLines = false; - setAside.showDesk = true; - set_side(setAside); - break; - } - } - - setChatbox(chatmsg.chatbox); - resizeChatbox(); - - if (!skipoffset) { - // Flip the character - charLayers.style.transform = - chatmsg.flip === 1 ? "scaleX(-1)" : "scaleX(1)"; - pairLayers.style.transform = - chatmsg.other_flip === 1 ? "scaleX(-1)" : "scaleX(1)"; - - // Shift by the horizontal offset - switch (chatmsg.side) { - case "wit": - pairLayers.style.left = `${200 + Number(chatmsg.other_offset[0])}%`; - charLayers.style.left = `${200 + Number(chatmsg.self_offset[0])}%`; - break; - case "pro": - pairLayers.style.left = `${400 + Number(chatmsg.other_offset[0])}%`; - charLayers.style.left = `${400 + Number(chatmsg.self_offset[0])}%`; - break; - default: - pairLayers.style.left = `${Number(chatmsg.other_offset[0])}%`; - charLayers.style.left = `${Number(chatmsg.self_offset[0])}%`; - break; - } - - // New vertical offsets - pairLayers.style.top = `${Number(chatmsg.other_offset[1])}%`; - charLayers.style.top = `${Number(chatmsg.self_offset[1])}%`; - } - - blipChannels.forEach( - (channel: HTMLAudioElement) => - (channel.src = `${AO_HOST}sounds/general/sfx-blip${encodeURI( - chatmsg.blips.toLowerCase() - )}.opus`) - ); - - // process markup - if (chatmsg.content.startsWith("~~")) { - chatBoxInner.style.textAlign = "center"; - chatmsg.content = chatmsg.content.substring(2, chatmsg.content.length); - } else { - chatBoxInner.style.textAlign = "inherit"; - } - - // apply effects - fg.style.animation = ""; - const effectName = chatmsg.effects[0].toLowerCase(); - const badEffects = ["", "-", "none"]; - if (effectName.startsWith("rain")) { - (document.getElementById("effect_css")).href = "styles/effects/rain.css"; - let intensity = 200; - if (effectName.endsWith("weak")) { - intensity = 100; - } else if (effectName.endsWith("strong")) { - intensity = 400; - } - if (intensity < fg.childElementCount) - fg.innerHTML = ''; - else - intensity = intensity - fg.childElementCount; - - for (let i = 0; i < intensity; i++) { - let drop = document.createElement("p"); - drop.style.left = (Math.random() * 100) + "%"; - drop.style.animationDelay = String(Math.random()) + "s"; - fg.appendChild(drop) - } - } else if ( - chatmsg.effects[0] && - !badEffects.includes(effectName) - ) { - (document.getElementById("effect_css")).href = ""; - fg.innerHTML = ''; - const baseEffectUrl = `${AO_HOST}themes/default/effects/`; - fg.src = `${baseEffectUrl}${encodeURI(effectName)}.webp`; - } else { - fg.innerHTML = ''; - fg.src = transparentPng; - } - - charLayers.style.opacity = "1"; - - const soundChecks = ["0", "1", "", undefined]; - if (soundChecks.some((check) => chatmsg.sound === check)) { - chatmsg.sound = chatmsg.effects[2]; - } - chatmsg.parsed = await attorneyMarkdown.applyMarkdown( - chatmsg.content, - COLORS[chatmsg.color] - ); - chat_tick(); - }; - const handleTextTick = async (charLayers: HTMLImageElement) => { const chatBox = document.getElementById("client_chat"); const waitingBox = document.getElementById("client_chatwaiting"); @@ -631,7 +232,8 @@ const viewport = (masterClient: Client): Viewport => { const chat_tick = async () => { // note: this is called fairly often // do not perform heavy operations here - + console.log(textnow) + console.log(chatmsg.content) await delay(chatmsg.speed); if (textnow === chatmsg.content) { return; @@ -696,14 +298,15 @@ const viewport = (masterClient: Client): Viewport => { } else { pairLayers.style.opacity = "0"; } + // Done with first check, move to second - startFirstTickCheck = false; - startSecondTickCheck = true; + setStartFirstTickCheck(false) + setStartSecondTickCheck(true) chatmsg.startpreanim = false; chatmsg.startspeaking = true; } - + const hasNonInterruptingPreAnim = chatmsg.noninterrupting_preanim === 1; if (textnow !== chatmsg.content && hasNonInterruptingPreAnim) { const chatContainerBox = document.getElementById("client_chatcontainer"); @@ -717,7 +320,7 @@ const viewport = (masterClient: Client): Viewport => { if (chatmsg.evidence > 0) { // Prepare evidence eviBox.src = safeTags( - masterClient.evidences[chatmsg.evidence - 1].icon + client.evidences[chatmsg.evidence - 1].icon ); eviBox.style.width = "auto"; @@ -842,59 +445,53 @@ const viewport = (masterClient: Client): Viewport => { ); } } + console.log(animating) if (animating) { chat_tick(); } tickTimer += UPDATE_INTERVAL; }; - /** - * Triggered by the theme selector. - */ - function reloadTheme() { - theme = (document.getElementById("client_themeselect")) - .value; - - setCookie("theme", theme); - (( - document.getElementById("client_theme") - )).href = `styles/${theme}.css`; - } - window.reloadTheme = reloadTheme; - - const changeMusicVolume = (volume: number = -1) => { - const clientVolume = Number( - (document.getElementById("client_mvolume")).value - ); - let musicVolume = volume === -1 ? clientVolume : volume; - music.forEach( - (channel: HTMLAudioElement) => (channel.volume = musicVolume) - ); - setCookie("musicVolume", String(musicVolume)); - }; - window.changeMusicVolume = changeMusicVolume; return { + getTextNow, + setTextNow, + getChatmsg, + setChatmsg, + getSfxPlayed, + setSfxPlayed, + setTickTimer, + getTickTimer, + setAnimating, + getAnimating, + getLastEvidence, + setLastEvidence, + setLastCharacter, + getLastCharacter, + getShoutTimer, + setShoutTimer, + setTheme, + getTheme, + setTestimonyTimer, + getTestimonyTimer, + setTestimonyUpdater, + getTestimonyUpdater, + testimonyAudio, chat_tick, - changeMusicVolume, - reloadTheme, playSFX, set_side, setBackgroundName, - initTestimonyUpdater, updateTestimony, disposeTestimony, - handle_ic_speaking, handleTextTick, getBackgroundFolder, getBackgroundName, getSfxAudio, setSfxAudio, - theme, - chatmsg, blipChannels, - lastChar, music, musicVolume, + shoutaudio, + updater, }; }; -- cgit