add wordballoon support
needs some fixing but its working
This commit is contained in:
parent
918ab5f95e
commit
d8b7681ff5
2 changed files with 129 additions and 19 deletions
|
@ -2,6 +2,8 @@ import { BufferStream, SeekDir } from './buffer.js';
|
|||
import { AcsData } from './character.js';
|
||||
import { AcsAnimation, AcsAnimationFrameInfo } from './structs/animation.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
|
||||
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 {
|
||||
private data: AcsData;
|
||||
private charDiv: HTMLDivElement;
|
||||
private cnv: HTMLCanvasElement;
|
||||
private ctx: CanvasRenderingContext2D;
|
||||
|
||||
|
@ -59,16 +120,23 @@ export class Agent {
|
|||
private y: number;
|
||||
|
||||
private animState: AgentAnimationState | null = null;
|
||||
private wordballoonState: AgentWordBalloonState | null = null;
|
||||
|
||||
constructor(data: AcsData) {
|
||||
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.ctx = this.cnv.getContext('2d')!;
|
||||
this.cnv.width = data.characterInfo.charWidth;
|
||||
this.cnv.height = data.characterInfo.charHeight;
|
||||
this.cnv.style.position = 'fixed';
|
||||
this.cnv.style.display = 'none';
|
||||
|
||||
this.charDiv.appendChild(this.cnv);
|
||||
|
||||
this.dragging = false;
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
|
@ -83,7 +151,7 @@ export class Agent {
|
|||
{ once: true }
|
||||
);
|
||||
});
|
||||
this.cnv.addEventListener('contextmenu', e => {
|
||||
this.cnv.addEventListener('contextmenu', (e) => {
|
||||
e.preventDefault();
|
||||
// TODO: Custom context menu support
|
||||
});
|
||||
|
@ -103,8 +171,29 @@ export class Agent {
|
|||
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';
|
||||
this.charDiv.style.top = this.y + '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) {
|
||||
|
@ -149,12 +238,13 @@ export class Agent {
|
|||
}
|
||||
|
||||
addToDom(parent: HTMLElement = document.body) {
|
||||
if (this.cnv.parentElement) return;
|
||||
parent.appendChild(this.cnv);
|
||||
if (this.charDiv.parentElement) return;
|
||||
parent.appendChild(this.charDiv);
|
||||
('');
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.cnv.parentElement?.removeChild(this.cnv);
|
||||
this.charDiv.remove();
|
||||
}
|
||||
|
||||
// add promise versions later.
|
||||
|
@ -170,22 +260,35 @@ export class Agent {
|
|||
this.animState.play();
|
||||
}
|
||||
|
||||
playAnimationByName(name: String, finishCallback: () => void) {
|
||||
playAnimationByName(name: string, finishCallback: () => void) {
|
||||
let index = this.data.animInfo.findIndex((n) => n.name == name);
|
||||
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() {
|
||||
this.cnv.style.display = 'block';
|
||||
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';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ export function wordballoonDraw(ctx: CanvasRenderingContext2D, at: Point, size:
|
|||
ctx.fillStyle = '#ffffe7';
|
||||
|
||||
// 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
|
||||
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.
|
||||
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);
|
||||
|
||||
// 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);
|
||||
y += height * 1.25;
|
||||
}
|
||||
|
||||
return {
|
||||
x: at.x,
|
||||
y: at.y,
|
||||
w: rectInner.w + (12*3) + 12,
|
||||
h: rectInner.h + (13*3) + 18
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue