diff --git a/msagent.js/src/agent.ts b/msagent.js/src/agent.ts index db37fec..213d5e0 100644 --- a/msagent.js/src/agent.ts +++ b/msagent.js/src/agent.ts @@ -13,39 +13,40 @@ function dwAlign(off: number): number { return ul; } +// animation state (used during animation playback) class AgentAnimationState { - char: Agent; - anim: AcsAnimation; + char: Agent; + anim: AcsAnimation; - finishCallback: () => void; - frameIndex = 0; + finishCallback: () => void; + frameIndex = 0; - interval = 0; + interval = 0; - constructor(char: Agent, anim: AcsAnimation, finishCallback: () => void) { - this.char = char; - this.anim = anim; - this.finishCallback = finishCallback; - } + constructor(char: Agent, anim: AcsAnimation, finishCallback: () => void) { + this.char = char; + this.anim = anim; + this.finishCallback = finishCallback; + } - // start playing the animation - play() { - this.nextFrame(); - } + // start playing the animation + play() { + this.nextFrame(); + } - nextFrame() { - this.char.renderFrame(this.anim.frameInfo[this.frameIndex++]); + nextFrame() { + this.char.renderFrame(this.anim.frameInfo[this.frameIndex++]); - if(this.frameIndex >= this.anim.frameInfo.length) { - this.finishCallback(); - return; - } - //@ts-ignore - this.interval = setTimeout(() => { - this.nextFrame(); - }, this.anim.frameInfo[this.frameIndex].frameDuration * 10) - } + if (this.frameIndex >= this.anim.frameInfo.length) { + this.finishCallback(); + return; + } + //@ts-ignore + this.interval = setTimeout(() => { + this.nextFrame(); + }, this.anim.frameInfo[this.frameIndex].frameDuration * 10); + } } export class Agent { @@ -53,11 +54,11 @@ export class Agent { private cnv: HTMLCanvasElement; private ctx: CanvasRenderingContext2D; - private dragging: boolean; - private x: number; - private y: number; + private dragging: boolean; + private x: number; + private y: number; - private animState: AgentAnimationState | null = null; + private animState: AgentAnimationState | null = null; constructor(data: AcsData) { this.data = data; @@ -68,44 +69,48 @@ export class Agent { this.cnv.style.position = 'fixed'; this.cnv.style.display = 'none'; - this.dragging = false; - this.x = 0; - this.y = 0; - this.setLoc(); - this.cnv.addEventListener('mousedown', () => { - this.dragging = true; - document.addEventListener('mouseup', () => { - this.dragging = false; - }, {once: true}); - }); - document.addEventListener('mousemove', e => { - if (!this.dragging) return; - this.x += e.movementX; - this.y += e.movementY; - this.setLoc(); - }); - window.addEventListener('resize', () => { - this.setLoc(); - }); + this.dragging = false; + this.x = 0; + this.y = 0; + this.setLoc(); + this.cnv.addEventListener('mousedown', () => { + this.dragging = true; + document.addEventListener( + 'mouseup', + () => { + this.dragging = false; + }, + { once: true } + ); + }); + document.addEventListener('mousemove', (e) => { + if (!this.dragging) return; + this.x += e.movementX; + this.y += e.movementY; + this.setLoc(); + }); + window.addEventListener('resize', () => { + this.setLoc(); + }); } - private setLoc() { - if (this.x < 0) this.x = 0; - if (this.y < 0) this.y = 0; - if (this.x > document.documentElement.clientWidth - this.cnv.width) this.x = document.documentElement.clientWidth - this.cnv.width; - if (this.y > document.documentElement.clientHeight - this.cnv.height) this.y = document.documentElement.clientHeight - this.cnv.height; - this.cnv.style.top = this.y + "px"; - this.cnv.style.left = this.x + "px"; - } + private setLoc() { + if (this.x < 0) this.x = 0; + if (this.y < 0) this.y = 0; + if (this.x > document.documentElement.clientWidth - this.cnv.width) this.x = document.documentElement.clientWidth - this.cnv.width; + if (this.y > document.documentElement.clientHeight - this.cnv.height) this.y = document.documentElement.clientHeight - this.cnv.height; + this.cnv.style.top = this.y + 'px'; + this.cnv.style.left = this.x + 'px'; + } renderFrame(frame: AcsAnimationFrameInfo) { - this.ctx.clearRect(0, 0, this.cnv.width, this.cnv.height); + this.ctx.clearRect(0, 0, this.cnv.width, this.cnv.height); for (const mimg of frame.images) { this.drawImage(this.data.images[mimg.imageIndex], mimg.xOffset, mimg.yOffset); } } - // Draw a single image from the agent's image table. + // Draw a single image from the agent's image table. drawImage(imageEntry: AcsImageEntry, xOffset: number, yOffset: number) { let rgbaBuffer = new Uint32Array(imageEntry.image.width * imageEntry.image.height); @@ -114,20 +119,20 @@ export class Agent { let rows = new Array(imageEntry.image.height - 1); - // Read all the rows bottom-up first. This idiosyncracy is due to the fact - // that the bitmap data is actually formatted to be used as a GDI DIB - // (device-independent bitmap), so it inherits all the strange baggage from that. + // Read all the rows bottom-up first. This idiosyncracy is due to the fact + // that the bitmap data is actually formatted to be used as a GDI DIB + // (device-independent bitmap), so it inherits all the strange baggage from that. for (let y = imageEntry.image.height - 1; y >= 0; --y) { let row = bufStream.subBuffer(imageEntry.image.width).raw(); let rowResized = row.slice(0, imageEntry.image.width); rows[y] = rowResized; - // Seek to the next DWORD aligned spot to get to the next row. - // For most images this may mean not seeking at all. - bufStream.seek(dwAlign(bufStream.tell()), SeekDir.BEG); + // Seek to the next DWORD aligned spot to get to the next row. + // For most images this may mean not seeking at all. + bufStream.seek(dwAlign(bufStream.tell()), SeekDir.BEG); } - // Next, draw the rows converted to RGBA, top down (so it's drawn as you'd expect) + // Next, draw the rows converted to RGBA, top down (so it's drawn as you'd expect) for (let y = 0; y < imageEntry.image.height - 1; ++y) { let row = rows[y]; for (let x = 0; x < imageEntry.image.width; ++x) { @@ -143,46 +148,41 @@ export class Agent { parent.appendChild(this.cnv); } - remove() { - this.cnv.parentElement?.removeChild(this.cnv); - } + remove() { + this.cnv.parentElement?.removeChild(this.cnv); + } - // add promise versions later. - playAnimation(index: number, finishCallback: () => void) { - if(this.animState != null) - throw new Error('Cannot play multiple animations at once.'); - let animInfo = this.data.animInfo[index]; + // add promise versions later. + playAnimation(index: number, finishCallback: () => void) { + if (this.animState != null) throw new Error('Cannot play multiple animations at once.'); + let animInfo = this.data.animInfo[index]; - // Create and start the animation state - this.animState = new AgentAnimationState(this, animInfo.animationData, () => { - this.animationFinished(); - finishCallback(); - }); - this.animState.play(); - } + // Create and start the animation state + this.animState = new AgentAnimationState(this, animInfo.animationData, () => { + this.animationFinished(); + finishCallback(); + }); + this.animState.play(); + } - playAnimationByName(name: String, finishCallback: () => void) { - let index = this.data.animInfo.findIndex((n) => n.name == name); - if(index !== -1) - this.playAnimation(index, finishCallback); - } + playAnimationByName(name: String, finishCallback: () => void) { + let index = this.data.animInfo.findIndex((n) => n.name == name); + if (index !== -1) this.playAnimation(index, finishCallback); + } - animationFinished() { - this.animState = null; - } + animationFinished() { + this.animState = null; + } show() { this.cnv.style.display = 'block'; - this.playAnimationByName("Show", () => {}); + this.playAnimationByName('Show', () => {}); } hide(remove: boolean = false) { - this.playAnimationByName("Hide", () => { - if(remove) - this.remove(); - else - this.cnv.style.display = 'none'; - - }); + this.playAnimationByName('Hide', () => { + if (remove) this.remove(); + else this.cnv.style.display = 'none'; + }); } } diff --git a/msagent.js/src/character.ts b/msagent.js/src/character.ts index 2c915a6..55d2df4 100644 --- a/msagent.js/src/character.ts +++ b/msagent.js/src/character.ts @@ -6,19 +6,14 @@ import { AcsAnimationEntry } from './structs/animation.js'; import { AcsImageEntry } from './structs/image.js'; import { Agent } from './agent.js'; -// Experiment for storing parsed data +// Data export class AcsData { characterInfo = new AcsCharacterInfo(); animInfo: AcsAnimationEntry[] = []; images: AcsImageEntry[] = []; } -function logOffset(o: number, name: string) { - let n = o >>> 0; - console.log(name, 'offset:', '0x' + n.toString(16)); -} - -function agentCharacterParseACS(buffer: BufferStream) { +function agentCharacterParseACS(buffer: BufferStream): AcsData { // Make sure the magic is correct for the ACS file. if (buffer.readU32LE() != 0xabcdabc3) { throw new Error('The provided data buffer does not contain valid ACS data.'); @@ -32,10 +27,6 @@ function agentCharacterParseACS(buffer: BufferStream) { let imageInfoLocation = LOCATION.read(buffer); let audioInfoLocation = LOCATION.read(buffer); - logOffset(characterInfoLocation.offset, 'character info'); - logOffset(animationInfoLocation.offset, 'animation info'); - logOffset(imageInfoLocation.offset, 'image info'); - logOffset(audioInfoLocation.offset, 'audio info'); buffer.withOffset(characterInfoLocation.offset, () => { acsData.characterInfo = AcsCharacterInfo.read(buffer); @@ -53,19 +44,12 @@ function agentCharacterParseACS(buffer: BufferStream) { }); }); - console.log(acsData); return acsData; } -// For the testbed code only, remove when that gets axed -// (or don't, I'm not your dad) -export function agentParseCharacterTestbed(buffer: Uint8Array) { - return new Agent(agentCharacterParseACS(new BufferStream(buffer))); -} - // TODO this will be the public API // Dunno about maintaining canvases. We can pass a div into agentInit and add a characterInit() which recieves it // (which we then mount characters and their wordballoons into?) -export function agentCreateCharacter(data: Uint8Array): Promise { - throw new Error('Not implemented yet'); +export function agentCreateCharacter(data: Uint8Array): Agent { + return new Agent(agentCharacterParseACS(new BufferStream(data))); } diff --git a/webapp/src/ts/testbed.ts b/webapp/src/ts/testbed.ts index 54fc0ca..cc1af10 100644 --- a/webapp/src/ts/testbed.ts +++ b/webapp/src/ts/testbed.ts @@ -8,8 +8,8 @@ let input = document.getElementById("testbed-input") as HTMLInputElement; input.addEventListener("change", async () => { let buffer = await input.files![0].arrayBuffer(); - console.log("About to parse character"); - let agent = msagent.agentParseCharacterTestbed(new Uint8Array(buffer)); + console.log("Creating agent"); + let agent = msagent.agentCreateCharacter(new Uint8Array(buffer)); // destroy the previous agent if(w.agent != null) { @@ -20,7 +20,7 @@ input.addEventListener("change", async () => { agent.addToDom(document.body); agent.show(); - console.log("parsed character"); + console.log("Agent created"); }) document.addEventListener("DOMContentLoaded", async () => {