extended animation support, add /anim command (TODO: animation list, command help)
This commit is contained in:
parent
5550303284
commit
59efab9300
10 changed files with 155 additions and 18 deletions
|
@ -378,6 +378,12 @@ export class Agent {
|
|||
if (index !== -1) this.playAnimation(index, finishCallback);
|
||||
}
|
||||
|
||||
playAnimationByNamePromise(name: string): Promise<void> {
|
||||
return new Promise((res, rej) => {
|
||||
this.playAnimationByName(name, () => res());
|
||||
});
|
||||
}
|
||||
|
||||
setUsername(username: string, color: string) {
|
||||
if (this.usernameBalloonState !== null) {
|
||||
this.usernameBalloonState.finish();
|
||||
|
@ -420,13 +426,10 @@ export class Agent {
|
|||
this.y = randint(0, document.documentElement.clientHeight - this.data.characterInfo.charHeight);
|
||||
this.setLoc();
|
||||
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';
|
||||
});
|
||||
if (remove) this.remove();
|
||||
else this.cnv.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ export enum MSAgentProtocolMessageType {
|
|||
KeepAlive = 'nop',
|
||||
Join = 'join',
|
||||
Talk = 'talk',
|
||||
PlayAnimation = 'anim',
|
||||
SendImage = 'img',
|
||||
Admin = 'admin',
|
||||
// Server-to-client
|
||||
|
@ -20,6 +21,14 @@ export interface MSAgentProtocolMessage {
|
|||
op: MSAgentProtocolMessageType;
|
||||
}
|
||||
|
||||
export interface AgentAnimationConfig {
|
||||
join: string[];
|
||||
chat: string[];
|
||||
idle: string[];
|
||||
rest: string[];
|
||||
leave: string[];
|
||||
}
|
||||
|
||||
// Client-to-server
|
||||
|
||||
export interface MSAgentJoinMessage extends MSAgentProtocolMessage {
|
||||
|
@ -37,6 +46,14 @@ export interface MSAgentTalkMessage extends MSAgentProtocolMessage {
|
|||
};
|
||||
}
|
||||
|
||||
export interface MSAgentPlayAnimationMessage extends MSAgentProtocolMessage {
|
||||
op: MSAgentProtocolMessageType.PlayAnimation;
|
||||
data: {
|
||||
anim: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface MSAgentSendImageMessage extends MSAgentProtocolMessage {
|
||||
op: MSAgentProtocolMessageType.SendImage;
|
||||
data: {
|
||||
|
@ -56,6 +73,7 @@ export interface MSAgentInitMessage extends MSAgentProtocolMessage {
|
|||
username: string;
|
||||
agent: string;
|
||||
admin: boolean;
|
||||
animations: AgentAnimationConfig;
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
@ -65,6 +83,7 @@ export interface MSAgentAddUserMessage extends MSAgentProtocolMessage {
|
|||
data: {
|
||||
username: string;
|
||||
agent: string;
|
||||
animations: AgentAnimationConfig;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -84,6 +103,14 @@ export interface MSAgentChatMessage extends MSAgentProtocolMessage {
|
|||
};
|
||||
}
|
||||
|
||||
export interface MSAgentAnimationMessage extends MSAgentProtocolMessage {
|
||||
op: MSAgentProtocolMessageType.PlayAnimation;
|
||||
data: {
|
||||
username: string;
|
||||
anim: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MSAgentImageMessage extends MSAgentProtocolMessage {
|
||||
op: MSAgentProtocolMessageType.SendImage;
|
||||
data: {
|
||||
|
|
|
@ -23,6 +23,7 @@ bannedWords = []
|
|||
|
||||
[chat.ratelimits]
|
||||
chat = {seconds = 10, limit = 8}
|
||||
anim = {seconds = 10, limit = 8}
|
||||
|
||||
[motd]
|
||||
version = 1
|
||||
|
@ -58,71 +59,89 @@ expirySeconds = 60
|
|||
[[agents]]
|
||||
friendlyName = "Clippy"
|
||||
filename = "CLIPPIT.ACS"
|
||||
animations = { join = ["Greeting"], chat = ["Explain"], idle = [], leave = ["GoodBye"], rest = ["RestPose"] }
|
||||
|
||||
[[agents]]
|
||||
friendlyName = "Courtney"
|
||||
filename = "courtney.acs"
|
||||
animations = { join = ["Show", "Greet"], chat = ["GetAttentionMinor"], idle = [], leave = ["Disappear"], rest = ["RestPose"] }
|
||||
|
||||
[[agents]]
|
||||
friendlyName = "Dot"
|
||||
filename = "DOT.ACS"
|
||||
animations = { join = ["Greeting", "Show"], chat = ["Alert", "Show"], idle = [], leave = ["Goodbye"], rest = ["RestPose"] }
|
||||
|
||||
[[agents]]
|
||||
friendlyName = "Earl"
|
||||
filename = "earl.acs"
|
||||
animations = { join = ["Show", "Greet"], chat = ["LookUp"], idle = [], leave = ["Disappear"], rest = ["RestPose"] }
|
||||
|
||||
[[agents]]
|
||||
friendlyName = "F1"
|
||||
filename = "F1.ACS"
|
||||
animations = { join = ["Show"], chat = ["Explain"], idle = [], leave = ["Hide"], rest = ["RestPose"] }
|
||||
|
||||
[[agents]]
|
||||
friendlyName = "Genie"
|
||||
filename = "Genie.acs"
|
||||
animations = { join = ["Show"], chat = ["Suggest", "RestPose"], idle = [], leave = ["Hide"], rest = ["RestPose"] }
|
||||
|
||||
[[agents]]
|
||||
friendlyName = "James"
|
||||
filename = "James.acs"
|
||||
animations = { join = ["Show"], chat = ["Suggest", "RestPose"], idle = [], leave = ["Hide"], rest = ["RestPose"] }
|
||||
|
||||
[[agents]]
|
||||
friendlyName = "Links"
|
||||
filename = "Links.ACS"
|
||||
animations = { join = ["Greeting"], chat = ["GetAttention"], idle = [], leave = ["GoodBye"], rest = ["RestPose"] }
|
||||
|
||||
[[agents]]
|
||||
friendlyName = "Merlin"
|
||||
filename = "merlin.acs"
|
||||
animations = { join = ["Show"], chat = ["Explain", "RestPose"], idle = [], leave = ["Hide"], rest = ["RestPose"] }
|
||||
|
||||
[[agents]]
|
||||
friendlyName = "Mother Nature"
|
||||
filename = "Mother_NATURE.ACS"
|
||||
animations = { join = ["Greeting"], chat = ["Explain"], idle = [], leave = ["Hide"], rest = ["RestPose"] }
|
||||
|
||||
[[agents]]
|
||||
friendlyName = "Office Logo"
|
||||
filename = "Office_Logo.ACS"
|
||||
animations = { join = ["Greeting"], chat = ["Explain"], idle = [], leave = ["Goodbye"], rest = ["RestPose"] }
|
||||
|
||||
[[agents]]
|
||||
friendlyName = "Peedy"
|
||||
filename = "Peedy.acs"
|
||||
animations = { join = ["Show", "Greet", "RestPose"], chat = ["Thinking", "RestPose"], idle = [], leave = ["Hide"], rest = ["RestPose"] }
|
||||
|
||||
#[[agents]]
|
||||
#friendlyName = "Question"
|
||||
#filename = "question_mark.acs"
|
||||
[[agents]]
|
||||
friendlyName = "Question"
|
||||
filename = "question_mark.acs"
|
||||
animations = { join = ["Welcome"], chat = ["Shimmer"], idle = [], leave = ["Fade"], rest = ["Restpose"] }
|
||||
|
||||
[[agents]]
|
||||
friendlyName = "Robby"
|
||||
filename = "Robby.acs"
|
||||
animations = { join = ["Show", "Greet", "RestPose"], chat = ["Thinking", "RestPose"], idle = [], leave = ["Hide"], rest = ["RestPose"] }
|
||||
|
||||
[[agents]]
|
||||
friendlyName = "Rocky"
|
||||
filename = "ROCKY.ACS"
|
||||
animations = { join = ["Show"], chat = ["Alert"], idle = [], leave = ["Hide"], rest = ["RestPose"] }
|
||||
|
||||
[[agents]]
|
||||
friendlyName = "Rover"
|
||||
filename = "rover.acs"
|
||||
animations = { join = ["Show", "Greet", "RestPose"], chat = ["Thinking", "RestPose"], idle = [], leave = ["Hide"], rest = ["RestPose"] }
|
||||
|
||||
[[agents]]
|
||||
friendlyName = "Victor"
|
||||
filename = "Victor.acs"
|
||||
animations = { join = ["Show", "Greet", "RestPose"], chat = ["Thinking", "RestPose"], idle = [], leave = ["Hide"], rest = ["RestPose"] }
|
||||
|
||||
[[agents]]
|
||||
friendlyName = "Bonzi"
|
||||
filename = "Bonzi.acs"
|
||||
animations = { join = ["Show", "Wave", "RestPose"], chat = ["Giggle"], idle = [], leave = ["Hide"], rest = ["RestPose"] }
|
|
@ -14,7 +14,8 @@ import {
|
|||
MSAgentJoinMessage,
|
||||
MSAgentProtocolMessage,
|
||||
MSAgentProtocolMessageType,
|
||||
MSAgentTalkMessage
|
||||
MSAgentTalkMessage,
|
||||
MSAgentPlayAnimationMessage
|
||||
} from '@msagent-chat/protocol';
|
||||
import { MSAgentChatRoom } from './room.js';
|
||||
import * as htmlentities from 'html-entities';
|
||||
|
@ -27,6 +28,7 @@ export interface Client {
|
|||
on(event: 'join', listener: () => void): this;
|
||||
on(event: 'close', listener: () => void): this;
|
||||
on(event: 'talk', listener: (msg: string) => void): this;
|
||||
on(event: 'animation', listener: (anim: string) => void): this;
|
||||
on(event: 'image', listener: (id: string) => void): this;
|
||||
|
||||
on(event: string, listener: Function): this;
|
||||
|
@ -45,6 +47,7 @@ export class Client extends EventEmitter {
|
|||
nopLevel: number;
|
||||
|
||||
chatRateLimit: RateLimiter;
|
||||
animRateLimit: RateLimiter;
|
||||
|
||||
constructor(socket: WebSocket, room: MSAgentChatRoom, ip: string) {
|
||||
super();
|
||||
|
@ -58,6 +61,7 @@ export class Client extends EventEmitter {
|
|||
this.nopLevel = 0;
|
||||
|
||||
this.chatRateLimit = new RateLimiter(this.room.config.ratelimits.chat);
|
||||
this.animRateLimit = new RateLimiter(this.room.config.ratelimits.anim);
|
||||
|
||||
this.socket.on('message', (msg, isBinary) => {
|
||||
if (isBinary) {
|
||||
|
@ -162,6 +166,12 @@ export class Client extends EventEmitter {
|
|||
this.emit('talk', talkMsg.data.msg);
|
||||
break;
|
||||
}
|
||||
case MSAgentProtocolMessageType.PlayAnimation: {
|
||||
let animMsg = msg as MSAgentPlayAnimationMessage;
|
||||
if (!animMsg.data || !animMsg.data.anim || !this.animRateLimit.request()) return;
|
||||
this.emit('animation', animMsg.data.anim);
|
||||
break;
|
||||
}
|
||||
case MSAgentProtocolMessageType.SendImage: {
|
||||
let imgMsg = msg as MSAgentSendImageMessage;
|
||||
if (!imgMsg.data || !imgMsg.data.id || !this.chatRateLimit.request()) return;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { AgentAnimationConfig } from "@msagent-chat/protocol";
|
||||
|
||||
export interface IConfig {
|
||||
http: {
|
||||
host: string;
|
||||
|
@ -31,6 +33,7 @@ export interface ChatConfig {
|
|||
bannedWords: string[];
|
||||
ratelimits: {
|
||||
chat: RateLimitConfig;
|
||||
anim: RateLimitConfig;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -42,6 +45,7 @@ export interface motdConfig {
|
|||
export interface AgentConfig {
|
||||
friendlyName: string;
|
||||
filename: string;
|
||||
animations: AgentAnimationConfig;
|
||||
}
|
||||
|
||||
export interface RateLimitConfig {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import {
|
||||
MSAgentAddUserMessage,
|
||||
MSAgentAnimationMessage,
|
||||
MSAgentChatMessage,
|
||||
MSAgentImageMessage,
|
||||
MSAgentInitMessage,
|
||||
MSAgentPromoteMessage,
|
||||
MSAgentProtocolMessage,
|
||||
MSAgentProtocolMessageType,
|
||||
MSAgentRemoveUserMessage
|
||||
} from '@msagent-chat/protocol';
|
||||
|
@ -52,6 +52,7 @@ export class MSAgentChatRoom {
|
|||
}
|
||||
});
|
||||
client.on('join', () => {
|
||||
let agent = this.agents.find(a => a.filename === client.agent)!;
|
||||
let initmsg: MSAgentInitMessage = {
|
||||
op: MSAgentProtocolMessageType.Init,
|
||||
data: {
|
||||
|
@ -64,7 +65,8 @@ export class MSAgentChatRoom {
|
|||
return {
|
||||
username: c.username!,
|
||||
agent: c.agent!,
|
||||
admin: c.admin
|
||||
admin: c.admin,
|
||||
animations: this.agents.find(a => a.filename === c.agent)!.animations
|
||||
};
|
||||
})
|
||||
}
|
||||
|
@ -74,7 +76,8 @@ export class MSAgentChatRoom {
|
|||
op: MSAgentProtocolMessageType.AddUser,
|
||||
data: {
|
||||
username: client.username!,
|
||||
agent: client.agent!
|
||||
agent: client.agent!,
|
||||
animations: agent.animations
|
||||
}
|
||||
};
|
||||
for (const _client of this.getActiveClients().filter((c) => c !== client)) {
|
||||
|
@ -104,6 +107,18 @@ export class MSAgentChatRoom {
|
|||
}
|
||||
this.discord?.logMsg(client.username!, message);
|
||||
});
|
||||
client.on('animation', async anim => {
|
||||
let msg: MSAgentAnimationMessage = {
|
||||
op: MSAgentProtocolMessageType.PlayAnimation,
|
||||
data: {
|
||||
username: client.username!,
|
||||
anim: anim
|
||||
}
|
||||
};
|
||||
for (const _client of this.getActiveClients()) {
|
||||
_client.send(msg);
|
||||
}
|
||||
});
|
||||
client.on('image', async (id) => {
|
||||
if (!this.img.has(id)) return;
|
||||
|
||||
|
|
|
@ -9,11 +9,13 @@ import {
|
|||
MSAgentAdminLoginResponse,
|
||||
MSAgentAdminMessage,
|
||||
MSAgentAdminOperation,
|
||||
MSAgentAnimationMessage,
|
||||
MSAgentChatMessage,
|
||||
MSAgentErrorMessage,
|
||||
MSAgentImageMessage,
|
||||
MSAgentInitMessage,
|
||||
MSAgentJoinMessage,
|
||||
MSAgentPlayAnimationMessage,
|
||||
MSAgentPromoteMessage,
|
||||
MSAgentProtocolMessage,
|
||||
MSAgentProtocolMessageType,
|
||||
|
@ -174,6 +176,16 @@ export class MSAgentClient {
|
|||
this.send(talkMsg);
|
||||
}
|
||||
|
||||
animation(anim: string) {
|
||||
let msg: MSAgentPlayAnimationMessage = {
|
||||
op: MSAgentProtocolMessageType.PlayAnimation,
|
||||
data: {
|
||||
anim
|
||||
}
|
||||
};
|
||||
this.send(msg);
|
||||
}
|
||||
|
||||
async sendImage(img: ArrayBuffer, type: string) {
|
||||
// Upload image
|
||||
let res = await fetch(this.url + '/api/upload', {
|
||||
|
@ -296,8 +308,9 @@ export class MSAgentClient {
|
|||
let agent = await agentCreateCharacterFromUrl(this.url + '/api/agents/' + _user.agent);
|
||||
agent.setUsername(_user.username, _user.admin ? '#FF0000' : '#000000');
|
||||
agent.addToDom(this.agentContainer);
|
||||
let user = new User(_user.username, agent, _user.animations);
|
||||
agent.show();
|
||||
let user = new User(_user.username, agent);
|
||||
user.doAnim('join');
|
||||
this.setContextMenu(user);
|
||||
this.users.push(user);
|
||||
}
|
||||
|
@ -309,8 +322,9 @@ export class MSAgentClient {
|
|||
let agent = await agentCreateCharacterFromUrl(this.url + '/api/agents/' + addUserMsg.data.agent);
|
||||
agent.setUsername(addUserMsg.data.username, '#000000');
|
||||
agent.addToDom(this.agentContainer);
|
||||
let user = new User(addUserMsg.data.username, agent, addUserMsg.data.animations);
|
||||
agent.show();
|
||||
let user = new User(addUserMsg.data.username, agent);
|
||||
user.doAnim('join');
|
||||
this.setContextMenu(user);
|
||||
this.users.push(user);
|
||||
this.events.emit('adduser', user);
|
||||
|
@ -320,7 +334,9 @@ export class MSAgentClient {
|
|||
let remUserMsg = msg as MSAgentRemoveUserMessage;
|
||||
let user = this.users.find((u) => u.username === remUserMsg.data.username);
|
||||
if (!user) return;
|
||||
user.agent.hide(true);
|
||||
user.doAnim('leave').then(() => {
|
||||
user.agent.hide(true);
|
||||
});
|
||||
if (this.playingAudio.has(user!.username)) {
|
||||
this.playingAudio.get(user!.username)?.pause();
|
||||
this.playingAudio.delete(user!.username);
|
||||
|
@ -337,6 +353,7 @@ export class MSAgentClient {
|
|||
this.events.emit('chat', user, chatMsg.data.message);
|
||||
|
||||
user?.agent.speak(chatMsg.data.message);
|
||||
user?.doAnim('chat');
|
||||
let msgId = ++user!.msgId;
|
||||
|
||||
if (chatMsg.data.audio !== undefined) {
|
||||
|
@ -368,6 +385,14 @@ export class MSAgentClient {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case MSAgentProtocolMessageType.PlayAnimation: {
|
||||
let animMsg = msg as MSAgentAnimationMessage;
|
||||
let user = this.users.find((u) => u.username === animMsg.data.username);
|
||||
if (!user || user.muted) return;
|
||||
await user.agent.playAnimationByNamePromise(animMsg.data.anim);
|
||||
await user.doAnim('rest');
|
||||
break;
|
||||
}
|
||||
case MSAgentProtocolMessageType.SendImage: {
|
||||
let imgMsg = msg as MSAgentImageMessage;
|
||||
let user = this.users.find((u) => u.username === imgMsg.data.username);
|
||||
|
|
18
webapp/src/ts/commands.ts
Normal file
18
webapp/src/ts/commands.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { MSAgentClient } from "./client.js";
|
||||
|
||||
export function InitCommands() {
|
||||
|
||||
}
|
||||
|
||||
export function RunCommand(command: string, room: MSAgentClient) {
|
||||
let arr = command.split(' ');
|
||||
let cmd = arr[0];
|
||||
let args = arr.slice(1);
|
||||
switch (cmd) {
|
||||
case '/anim': {
|
||||
let anim = args.join(' ');
|
||||
room.animation(anim);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ import { MSWindow, MSWindowStartPosition } from './MSWindow.js';
|
|||
import { agentInit } from '@msagent-chat/msagent.js';
|
||||
import { MSAgentClient } from './client.js';
|
||||
import { Config } from '../../config.js';
|
||||
import { RunCommand } from './commands.js';
|
||||
|
||||
const elements = {
|
||||
motdWindow: document.getElementById('motdWindow') as HTMLDivElement,
|
||||
|
@ -120,7 +121,12 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
|
||||
function talk() {
|
||||
if (Room === null) return;
|
||||
Room.talk(elements.chatInput.value);
|
||||
let msg = elements.chatInput.value;
|
||||
if (msg.startsWith('/')) {
|
||||
RunCommand(msg, Room);
|
||||
} else {
|
||||
Room.talk(msg);
|
||||
}
|
||||
elements.chatInput.value = '';
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Agent } from '@msagent-chat/msagent.js';
|
||||
import { AgentAnimationConfig } from '@msagent-chat/protocol';
|
||||
|
||||
export class User {
|
||||
username: string;
|
||||
|
@ -6,11 +7,20 @@ export class User {
|
|||
muted: boolean;
|
||||
admin: boolean;
|
||||
msgId: number = 0;
|
||||
animations: AgentAnimationConfig;
|
||||
|
||||
constructor(username: string, agent: Agent) {
|
||||
constructor(username: string, agent: Agent, animations: AgentAnimationConfig) {
|
||||
this.username = username;
|
||||
this.agent = agent;
|
||||
this.muted = false;
|
||||
this.admin = false;
|
||||
this.animations = animations;
|
||||
}
|
||||
|
||||
async doAnim(action: string) {
|
||||
// @ts-ignore
|
||||
for (let anim of this.animations[action]) {
|
||||
await this.agent.playAnimationByNamePromise(anim);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue