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 { 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,10 +151,10 @@ 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
|
||||||
});
|
});
|
||||||
document.addEventListener('mousemove', (e) => {
|
document.addEventListener('mousemove', (e) => {
|
||||||
if (!this.dragging) return;
|
if (!this.dragging) return;
|
||||||
this.x += e.movementX;
|
this.x += e.movementX;
|
||||||
|
@ -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';
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue