add username balloons under agents

This commit is contained in:
Elijah R 2024-07-10 22:07:58 -04:00
parent 55ce8b67f5
commit e9504c6359
3 changed files with 41 additions and 10 deletions

View file

@ -51,16 +51,25 @@ class AgentAnimationState {
} }
} }
enum AgentWordBalloonPosition {
AboveCentered,
BelowCentered
}
class AgentWordBalloonState { class AgentWordBalloonState {
char: Agent; char: Agent;
text: string; text: string;
hasTip: boolean;
position: AgentWordBalloonPosition;
balloonCanvas: HTMLCanvasElement; balloonCanvas: HTMLCanvasElement;
balloonCanvasCtx: CanvasRenderingContext2D; balloonCanvasCtx: CanvasRenderingContext2D;
constructor(char: Agent, text: string) { constructor(char: Agent, text: string, hasTip: boolean, position: AgentWordBalloonPosition) {
this.char = char; this.char = char;
this.text = text; this.text = text;
this.hasTip = hasTip;
this.position = position;
this.balloonCanvas = document.createElement('canvas'); this.balloonCanvas = document.createElement('canvas');
this.balloonCanvasCtx = this.balloonCanvas.getContext('2d')!; this.balloonCanvasCtx = this.balloonCanvas.getContext('2d')!;
@ -74,13 +83,13 @@ class AgentWordBalloonState {
// hack fix for above // hack fix for above
this.balloonCanvas.style.pointerEvents = 'none'; this.balloonCanvas.style.pointerEvents = 'none';
let rect = wordballoonDrawText(this.balloonCanvasCtx, { x: 0, y: 0 }, this.text, 20); let rect = wordballoonDrawText(this.balloonCanvasCtx, { x: 0, y: 0 }, this.text, 20, hasTip);
// Second pass, actually set the element to the right width and stuffs // Second pass, actually set the element to the right width and stuffs
this.balloonCanvas.width = rect.w; this.balloonCanvas.width = rect.w;
this.balloonCanvas.height = rect.h; this.balloonCanvas.height = rect.h;
wordballoonDrawText(this.balloonCanvasCtx, { x: 0, y: 0 }, this.text, 20); wordballoonDrawText(this.balloonCanvasCtx, { x: 0, y: 0 }, this.text, 20, hasTip);
this.char.getElement().appendChild(this.balloonCanvas); this.char.getElement().appendChild(this.balloonCanvas);
@ -101,9 +110,17 @@ class AgentWordBalloonState {
positionUpdated() { positionUpdated() {
let size = this.char.getSize(); let size = this.char.getSize();
this.balloonCanvas.style.top = -(this.balloonCanvas.height) + 'px';
this.balloonCanvas.style.left = -((this.balloonCanvas.width / 2) - (size.w / 2)) + 'px'; this.balloonCanvas.style.left = -((this.balloonCanvas.width / 2) - (size.w / 2)) + 'px';
switch (this.position) {
case AgentWordBalloonPosition.AboveCentered: {
this.balloonCanvas.style.top = -(this.balloonCanvas.height) + 'px';
break;
}
case AgentWordBalloonPosition.BelowCentered: {
this.balloonCanvas.style.bottom = -(this.balloonCanvas.height) + 'px';
break;
}
}
} }
} }
@ -119,6 +136,7 @@ export class Agent {
private animState: AgentAnimationState | null = null; private animState: AgentAnimationState | null = null;
private wordballoonState: AgentWordBalloonState | null = null; private wordballoonState: AgentWordBalloonState | null = null;
private usernameBalloonState: AgentWordBalloonState | null = null;
constructor(data: AcsData) { constructor(data: AcsData) {
this.data = data; this.data = data;
@ -262,12 +280,22 @@ export class Agent {
if (index !== -1) this.playAnimation(index, finishCallback); if (index !== -1) this.playAnimation(index, finishCallback);
} }
setUsername(username: string) {
if (this.usernameBalloonState !== null) {
this.usernameBalloonState.finish();
this.usernameBalloonState = null;
}
this.usernameBalloonState = new AgentWordBalloonState(this, username, false, AgentWordBalloonPosition.BelowCentered);
this.usernameBalloonState.show();
}
speak(text: string) { speak(text: string) {
if (this.wordballoonState != null) { if (this.wordballoonState != null) {
this.stopSpeaking(); this.stopSpeaking();
} }
this.wordballoonState = new AgentWordBalloonState(this, text); this.wordballoonState = new AgentWordBalloonState(this, text, true, AgentWordBalloonPosition.AboveCentered);
this.wordballoonState.positionUpdated(); this.wordballoonState.positionUpdated();
this.wordballoonState.show(); this.wordballoonState.show();
} }

View file

@ -37,7 +37,7 @@ export async function wordballoonInit() {
} }
// This function returns a rect which is the usable inner contents of the box. // This function returns a rect which is the usable inner contents of the box.
export function wordballoonDraw(ctx: CanvasRenderingContext2D, at: Point, size: Size): Rect { export function wordballoonDraw(ctx: CanvasRenderingContext2D, at: Point, size: Size, hasTip: boolean = true): Rect {
// Snap the size to a clean 12x12 system, // Snap the size to a clean 12x12 system,
// so we stay (as close to) pixel perfect as possible. // so we stay (as close to) pixel perfect as possible.
// This is "lazy" but oh well. It works! // This is "lazy" but oh well. It works!
@ -93,6 +93,7 @@ export function wordballoonDraw(ctx: CanvasRenderingContext2D, at: Point, size:
// For now, we always simply use the center of the bottom.. // For now, we always simply use the center of the bottom..
// Draw the tip. // Draw the tip.
if (hasTip)
spriteDraw(ctx, tip_sprite, at.x + size.w / 2, at.y + 12 * (j + 1) - 1); spriteDraw(ctx, tip_sprite, at.x + size.w / 2, at.y + 12 * (j + 1) - 1);
ctx.restore(); ctx.restore();
@ -128,7 +129,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): Rect { export function wordballoonDrawText(ctx: CanvasRenderingContext2D, at: Point, text: string, maxLen: number = 20, hasTip: boolean = true): Rect {
let lines = wordWrapToStringList(text, maxLen); let lines = wordWrapToStringList(text, maxLen);
// Create metrics for each line // Create metrics for each line
@ -158,7 +159,7 @@ export function wordballoonDrawText(ctx: CanvasRenderingContext2D, at: Point, te
size.h = Math.floor(size.h); size.h = Math.floor(size.h);
// Draw the word balloon and get the inner rect // Draw the word balloon and get the inner rect
let rectInner = wordballoonDraw(ctx, at, size); let rectInner = wordballoonDraw(ctx, at, size, hasTip);
// Draw all the lines of text // Draw all the lines of text
let y = 0; let y = 0;

View file

@ -142,6 +142,7 @@ export class MSAgentClient {
this.charlimit = initMsg.data.charlimit; this.charlimit = initMsg.data.charlimit;
for (let _user of initMsg.data.users) { for (let _user of initMsg.data.users) {
let agent = await agentCreateCharacterFromUrl(this.url + '/api/agents/' + _user.agent); let agent = await agentCreateCharacterFromUrl(this.url + '/api/agents/' + _user.agent);
agent.setUsername(_user.username);
agent.addToDom(this.agentContainer); agent.addToDom(this.agentContainer);
agent.show(); agent.show();
let user = new User(_user.username, agent); let user = new User(_user.username, agent);
@ -153,6 +154,7 @@ export class MSAgentClient {
case MSAgentProtocolMessageType.AddUser: { case MSAgentProtocolMessageType.AddUser: {
let addUserMsg = msg as MSAgentAddUserMessage; let addUserMsg = msg as MSAgentAddUserMessage;
let agent = await agentCreateCharacterFromUrl(this.url + '/api/agents/' + addUserMsg.data.agent); let agent = await agentCreateCharacterFromUrl(this.url + '/api/agents/' + addUserMsg.data.agent);
agent.setUsername(addUserMsg.data.username);
agent.addToDom(this.agentContainer); agent.addToDom(this.agentContainer);
agent.show(); agent.show();
let user = new User(addUserMsg.data.username, agent); let user = new User(addUserMsg.data.username, agent);