add wordballoon support

needs some fixing but its working
This commit is contained in:
Lily Tsuru 2024-07-10 03:08:55 -04:00
parent 918ab5f95e
commit d8b7681ff5
2 changed files with 129 additions and 19 deletions

View file

@ -2,6 +2,8 @@ import { BufferStream, SeekDir } from './buffer.js';
import { AcsData } from './character.js'; import { AcsData } from './character.js';
import { AcsAnimation, AcsAnimationFrameInfo } from './structs/animation.js'; import { AcsAnimation, AcsAnimationFrameInfo } from './structs/animation.js';
import { AcsImageEntry } from './structs/image.js'; import { AcsImageEntry } from './structs/image.js';
import { Point, Size } from './types.js';
import { wordballoonDrawText } from './wordballoon.js';
// probably should be in a utility module // probably should be in a utility module
function dwAlign(off: number): number { function dwAlign(off: number): number {
@ -49,8 +51,67 @@ class AgentAnimationState {
} }
} }
class AgentWordBalloonState {
char: Agent;
text: string;
balloonCanvas: HTMLCanvasElement;
balloonCanvasCtx: CanvasRenderingContext2D;
constructor(char: Agent, text: string) {
this.char = char;
this.text = text;
this.balloonCanvas = document.createElement('canvas');
this.balloonCanvasCtx = this.balloonCanvas.getContext('2d')!;
this.balloonCanvas.style.position = 'relative';
this.balloonCanvasCtx.font = '14px arial';
this.balloonCanvas.width = 300;
this.balloonCanvas.height = 300;
// hack fix for above
this.balloonCanvas.style.pointerEvents = 'none';
let rect = wordballoonDrawText(this.balloonCanvasCtx, { x: 0, y: 0 }, this.text, 20);
// Second pass, actually set the element to the right width and stuffs
this.balloonCanvas.width = rect.w;
this.balloonCanvas.height = rect.h;
wordballoonDrawText(this.balloonCanvasCtx, { x: 0, y: 0 }, this.text, 20);
this.char.getElement().appendChild(this.balloonCanvas);
this.show();
}
show() {
this.balloonCanvas.style.display = 'block';
}
hide() {
this.balloonCanvas.style.display = 'none';
}
finish() {
this.balloonCanvas.remove();
}
positionUpdated() {
let pos = this.char.getAt();
let size = this.char.getSize();
// TODO: fix so this isnt broken
this.balloonCanvas.style.top = -(this.balloonCanvas.width + size.w / 2) + 'px';
this.balloonCanvas.style.left = -(this.balloonCanvas.width - size.w / 2) + 'px';
}
}
export class Agent { export class Agent {
private data: AcsData; private data: AcsData;
private charDiv: HTMLDivElement;
private cnv: HTMLCanvasElement; private cnv: HTMLCanvasElement;
private ctx: CanvasRenderingContext2D; private ctx: CanvasRenderingContext2D;
@ -59,16 +120,23 @@ export class Agent {
private y: number; private y: number;
private animState: AgentAnimationState | null = null; private animState: AgentAnimationState | null = null;
private wordballoonState: AgentWordBalloonState | null = null;
constructor(data: AcsData) { constructor(data: AcsData) {
this.data = data; this.data = data;
this.charDiv = document.createElement('div');
this.charDiv.classList.add('agent-character');
this.charDiv.style.position = 'fixed';
this.cnv = document.createElement('canvas'); this.cnv = document.createElement('canvas');
this.ctx = this.cnv.getContext('2d')!; this.ctx = this.cnv.getContext('2d')!;
this.cnv.width = data.characterInfo.charWidth; this.cnv.width = data.characterInfo.charWidth;
this.cnv.height = data.characterInfo.charHeight; this.cnv.height = data.characterInfo.charHeight;
this.cnv.style.position = 'fixed';
this.cnv.style.display = 'none'; this.cnv.style.display = 'none';
this.charDiv.appendChild(this.cnv);
this.dragging = false; this.dragging = false;
this.x = 0; this.x = 0;
this.y = 0; this.y = 0;
@ -83,7 +151,7 @@ export class Agent {
{ once: true } { once: true }
); );
}); });
this.cnv.addEventListener('contextmenu', e => { this.cnv.addEventListener('contextmenu', (e) => {
e.preventDefault(); e.preventDefault();
// TODO: Custom context menu support // TODO: Custom context menu support
}); });
@ -103,8 +171,29 @@ export class Agent {
if (this.y < 0) this.y = 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.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; 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.charDiv.style.top = this.y + 'px';
this.cnv.style.left = this.x + 'px'; this.charDiv.style.left = this.x + 'px';
if (this.wordballoonState) this.wordballoonState.positionUpdated();
}
getElement() {
return this.charDiv;
}
getAt() {
let point: Point = {
x: this.x,
y: this.y
};
return point;
}
getSize(): Size {
return {
w: this.data.characterInfo.charWidth,
h: this.data.characterInfo.charHeight
};
} }
renderFrame(frame: AcsAnimationFrameInfo) { renderFrame(frame: AcsAnimationFrameInfo) {
@ -149,12 +238,13 @@ export class Agent {
} }
addToDom(parent: HTMLElement = document.body) { addToDom(parent: HTMLElement = document.body) {
if (this.cnv.parentElement) return; if (this.charDiv.parentElement) return;
parent.appendChild(this.cnv); parent.appendChild(this.charDiv);
('');
} }
remove() { remove() {
this.cnv.parentElement?.removeChild(this.cnv); this.charDiv.remove();
} }
// add promise versions later. // add promise versions later.
@ -170,22 +260,35 @@ export class Agent {
this.animState.play(); this.animState.play();
} }
playAnimationByName(name: String, finishCallback: () => void) { playAnimationByName(name: string, finishCallback: () => void) {
let index = this.data.animInfo.findIndex((n) => n.name == name); let index = this.data.animInfo.findIndex((n) => n.name == name);
if (index !== -1) this.playAnimation(index, finishCallback); if (index !== -1) this.playAnimation(index, finishCallback);
} }
speak(text: string) {
if (this.wordballoonState == null) {
this.wordballoonState = new AgentWordBalloonState(this, text);
this.wordballoonState.positionUpdated();
this.wordballoonState.show();
}
}
stopSpeaking() {
if (this.wordballoonState !== null) {
this.wordballoonState.finish();
this.wordballoonState = null;
}
}
show() { show() {
this.cnv.style.display = 'block'; this.cnv.style.display = 'block';
this.playAnimationByName('Show', () => {}); this.playAnimationByName('Show', () => {});
} }
hide(remove: boolean = false) { hide(remove: boolean = false) {
this.playAnimationByName("Hide", () => { this.playAnimationByName('Hide', () => {
if(remove) if (remove) this.remove();
this.remove(); else this.cnv.style.display = 'none';
else
this.cnv.style.display = 'none';
}); });
} }
} }

View file

@ -53,7 +53,7 @@ export function wordballoonDraw(ctx: CanvasRenderingContext2D, at: Point, size:
ctx.fillStyle = '#ffffe7'; ctx.fillStyle = '#ffffe7';
// Fill the inner portion of the balloon. // Fill the inner portion of the balloon.
ctx.fillRect(at.x + 12, at.y + 13, size.w, size.h); ctx.fillRect(at.x + 12, at.y + 12, size.w, size.h);
// draw the left side corner // draw the left side corner
spriteDraw(ctx, corner_sprite, at.x, at.y); spriteDraw(ctx, corner_sprite, at.x, at.y);
@ -128,7 +128,7 @@ function wordWrapToStringList(text: string, maxLength: number) {
} }
// This draws a wordballoon with text. This function respects the current context's font settings and does *not* modify them. // This draws a wordballoon with text. This function respects the current context's font settings and does *not* modify them.
export function wordballoonDrawText(ctx: CanvasRenderingContext2D, at: Point, text: string, maxLen: number = 20) { export function wordballoonDrawText(ctx: CanvasRenderingContext2D, at: Point, text: string, maxLen: number = 20): Rect {
let lines = wordWrapToStringList(text, maxLen); let lines = wordWrapToStringList(text, maxLen);
// Create metrics for each line // Create metrics for each line
@ -169,4 +169,11 @@ export function wordballoonDrawText(ctx: CanvasRenderingContext2D, at: Point, te
ctx.fillText(lines[i], rectInner.x - 12, rectInner.y + y); ctx.fillText(lines[i], rectInner.x - 12, rectInner.y + y);
y += height * 1.25; y += height * 1.25;
} }
return {
x: at.x,
y: at.y,
w: rectInner.w + (12*3) + 12,
h: rectInner.h + (13*3) + 18
}
} }