multiplayerpiano/src/Channel.ts

484 lines
13 KiB
TypeScript

/**
* Multiplayer Piano Server
* Copyright (c) The Dev Channel 2020-2022
* Licensed under the GPL v3.0 license
*
* Channel module
*/
import { EventEmitter } from "stream";
import { Client } from "./Client";
import { Color } from "./Color";
import { Database } from "./Database";
import { PublicUser, User } from "./models/User";
import { RateLimitChain } from "./RateLimit";
import { Server } from "./Server";
class Channel extends EventEmitter { // TODO channel
// static subscribers = new Map<string, string>();
// static updateSubscribers(server) {
// this.subscribers.forEach((_id, participantId, map) => {
// let list = [];
// let cl = server.findClientBy_ID(_id);
// cl.server.channels.forEach((channel, channelId, map) => {
// list.push(channel.getChannelProperties());
// });
// console.log(list);
// cl.sendArray([{
// m: 'ls',
// c: true,
// u: list
// }]);
// });
// }
server: Server;
_id: string;
settings: ChannelSettings;
connectedClients: {_id: string, ids: string[]}[];
crown?: Crown;
chatHistory: any[];
destroyTimeout: any;
constructor (server: Server, _id: string, set: any, p?: PublicUser, crownX?: number, crownY?: number) {
super();
this.server = server;
this.connectedClients = [];
this._id = _id;
this.settings = new ChannelSettings(set, undefined, false);
if (this.isLobby()) {
this.settings.lobby = true;
let colors = Database.getDefaultLobbySettings();
this.settings.color = colors.color;
this.settings.color2 = colors.color2;
} else {
// TODO add crown to non-lobby rooms
this.crown = new Crown(p._id, p.id, crownX || 50, crownY || 50);
}
this.chatHistory = [];
this.bindEventListeners();
server.channels.set(this._id, this);
}
// getClient(_id) {
// return this.server.findClientBy_ID(_id);
// }
bindEventListeners() {
this.on('update', msg => {
this.sendUpdate();
clearTimeout(this.destroyTimeout);
if (this.connectedClients.length <= 0) {
this.destroyTimeout = setTimeout(() => {
this.server.destroyChannel(this._id);
}, 1000);
}
});
}
getChannelProperties() {
return {
settings: this.settings,
_id: this._id,
id: this._id,
count: this.connectedClients.length,
crown: this.crown ? this.crown : undefined
}
}
setSettings(set: any, admin: boolean = false) {
// if (typeof set.color == 'string' && !set.color2) {
// if (!set.match(/^#[0-9a-f]{6}$/i)) return;
// let c = new Color(set.color);
// c.add(-0x40, -0x40, -0x40);
// set.color2 = c.toHexa();
// }
this.settings = new ChannelSettings(set, this.settings, admin);
this.sendChannelMessageAll();
this.emit('update');
}
sendUpdate() {
// this.server.sendChannelUpdateIncomplete([this.getChannelProperties()]);
this.server.emit('channel_update', this.getChannelProperties());
}
addClient(cl: Client) {
//this.applyQuota(cl);
if (this.hasParticipant(cl.getOwnParticipant())) {
this.connectedClients.find(p => {
p._id == cl.getOwnParticipant()._id;
}).ids.push(cl.getOwnParticipant().id);
} else {
// console.log('does not have client');
this.connectedClients.push({
_id: cl.getOwnParticipant()._id,
ids: [cl.getOwnParticipant().id]
});
}
// console.log(this.connectedClients);
cl.emit("bye");
cl.currentChannelID = this._id;
this.sendChannelMessageAll();
cl.sendChatHistory(this.chatHistory);
this.emit('update');
}
removeClient(cl: Client) {
// this.connectedClients.delete(cl.getOwnParticipant()._id);
// this.sendChannelMessageAll();
// this.connectedClients.splice(this.connectedClients.indexOf(cl.getOwnParticipant()._id), 1);
// this.sendByeMessageAll(cl.participantID);
// remove the user's _id from the array
// remove this user's _id from the connected clients array
this.connectedClients.splice(this.connectedClients.indexOf(this.connectedClients.find(p => p._id == cl.getOwnParticipant()._id)), 1);
// console.log(this.connectedClients);
this.sendChannelMessageAll();
this.sendByeMessageAll(cl.participantID);
this.emit('update');
}
hasClient(cl: Client): boolean {
for (let {_id, ids} of this.connectedClients) {
for (let id of ids) {
if (cl.participantID == id) {
return true;
}
}
}
return false;
}
hasParticipant(p: PublicUser) {
for (let {_id, ids} of this.connectedClients) {
for (let id of ids) {
if (p._id == _id) {
return true;
}
}
}
return false;
}
sendChat(p: PublicUser, clmsg: any): void {
// TODO chat quota
let msg = {
m: 'a',
a: clmsg.message,
p: p,
t: Date.now()
}
const badWords = [
'AMIGHTYWIND',
'CHECKLYHQ'
]
for (let word of badWords) {
if (clmsg.message.toUpperCase().split(' ').join('').includes(word)) {
return;
}
}
this.chatHistory.push(msg);
this.sendArray([msg]);
}
sendNoteMessage(p: PublicUser, clmsg: any): void {
// TODO channel note messages
for (let note of clmsg.n) {
note.d = note.d ? note.d : 0;
}
// console.log(clmsg);
let msg = {
m: 'n',
t: clmsg.t,
n: clmsg.n,
p: p.id
}
// this.connectedClients.forEach(_id => {
// let cl = this.server.findClientBy_ID(_id);
// if (cl.getOwnParticipant().id !== p.id) {
// cl.sendArray([msg]);
// }
// });
for (let {_id, ids} of this.connectedClients) {
for (let id of ids) {
let cl = this.server.findClientByID(id);
if (cl.participantID !== p.id) {
cl.sendArray([msg]);
}
}
}
}
findParticipantById(id: string) {
for (let {_id, ids} of this.connectedClients) {
for (let id of ids) {
if (id == id) {
return this.server.findClientByID(id);
}
}
}
}
sendCursorPosition(p: User | PublicUser, x: number, y: number): void {
let msg = {
m: 'm',
x: x,
y: y,
id: p.id
}
this.sendArray([msg]);
}
sendChannelMessageAll() {
// console.log("This is running.")
this.connectedClients.forEach(({_id, ids}) => {
for (let id of ids) {
// console.log(_id);
let cl = this.server.findClientByID(id);
// if (cl.currentChannelID !== this._id) {
// console.log('sendChannelMessageAll ' + cl.participantID, cl.getOwnParticipant()._id);
// }
cl.sendChannelMessage(this);
}
});
}
setCrown(p: PublicUser, id?: string, admin: boolean = false) {
if (!admin && id !== p.id) return;
if (!this.canChown(p.id)) return;
if (!id) {
this.dropCrown();
} else {
if (!this.hasParticipant(p)) {
}
}
}
dropCrown() {
this.crown.startPos.x = 50;
this.crown.startPos.y = this.server.findClientBy_ID(this.crown.userId).cursor.y || 50;
delete this.crown.participantId;
delete this.crown.userId;
}
canChown(participantId: string, admin: boolean = false) {
if (admin) return true;
if (!this.crown) return false;
if (this.crown.participantId === participantId) return true;
if (this.crown.time >= Date.now() + 15000) return true;
return false;
}
sendByeMessageAll(id: string): void {
this.sendArray([{
m: 'bye',
p: id
}]);
}
applyQuota(cl: Client) {
let q = new RateLimitChain(2500, 800);
let msg: any = q;
msg.m = 'nq';
cl.sendArray([msg]);
}
isLobby(): boolean {
let reg = /^(lobby[0-9].*|test\/([A-z]{1,})|lobby$)/;
return reg.test(this._id);
}
sendArray(arr: any[]) {
// for (let cl of this.connectedClients) {
// cl.sendArray(arr);
// }
// this.connectedClients.forEach(_id => {
// let cl = this.server.findClientBy_ID(_id);
// cl.sendArray(arr);
// });
for (let {_id, ids} of this.connectedClients) {
for (let id of ids) {
// console.log('id: ', id);
let cl = this.server.findClientByID(id);
// console.log(cl);
// console.log(cl);
if (cl.currentChannelID !== this._id) continue;
cl.sendArray(arr);
}
}
}
sendUserUpdate(user: User | PublicUser, x?: number, y?: number) {
// for (let cl of this.connectedClients) {
// cl.sendParticipantMessage(user, {x: x, y: y});
// }
// this.connectedClients.forEach(_id => {
// let cl = this.server.findClientBy_ID(_id);
// cl.sendParticipantMessage(user, {x: x, y: y});
// });
for (let {_id, ids} of this.connectedClients) {
for (let id of ids) {
let cl = this.server.findClientByID(id);
cl.sendParticipantMessage(user, {x: x, y: y});
}
}
}
getParticipantList() {
// console.log('getting participant list');
let ppl = [];
for (let {_id, ids} of this.connectedClients) {
for (let id of ids) {
let cl = this.server.findClientByID(id);
if (!cl.getOwnParticipant()) return;
ppl.push(cl.getOwnParticipant());
}
}
// this.connectedClients.forEach(_id => {
// let cl = this.server.findClientBy_ID(_id);
// if (!cl.getOwnParticipant()) return;
// ppl.push(cl.getOwnParticipant());
// });
// console.log('ppl: ', ppl);
return ppl;
}
}
class ChannelSettings {
crownsolo?: boolean;
lobby?: boolean;
color?: string;
color2?: string;
"owner_id"?: string;
"lyrical notes"?: boolean;
static VALID = {
"lobby": "boolean",
"visible": "boolean",
"chat": "boolean",
"crownsolo": "boolean",
"no cussing": "boolean",
"lyrical notes": "boolean",
"color": function(val) {
return typeof val === "string" && val.match(/^#[0-9a-f]{6}$/i);
},
"color2": function(val) {
return typeof val === "string" && val.match(/^#[0-9a-f]{6}$/i);
},
"owner_id": "string"
};
static ADMIN_ONLY = [
"lobby",
"owner_id",
"lyrical notes"
]
constructor (set: any, oldset?: any, admin: boolean = false) {
// set defaults
let def = Database.getDefaultChannelSettings();
for (let key of Object.keys(def)) {
this[key] = def[key];
}
if (oldset) {
for (let key of Object.keys(oldset)) {
this[key] = oldset[key];
}
}
// set values
for (let key of Object.keys(set)) {
if (ChannelSettings.ADMIN_ONLY.indexOf(key) !== -1 && !admin) continue;
if (typeof ChannelSettings.VALID[key] === "function") {
if (ChannelSettings.VALID[key](set[key])) {
this[key] = set[key];
}
} else {
if (typeof set[key] === ChannelSettings.VALID[key]) {
this[key] = set[key];
}
}
}
}
}
type Vector2 = {
x: number,
y: number
}
class Crown {
userId: string;
participantId?: string;
time: number;
endPos: Vector2;
startPos: Vector2;
constructor (user_id: string, partid?: string, x?: number, y?: number) {
this.userId = user_id;
this.participantId = partid;
this.time = Date.now();
this.startPos = {
x: 50,
y: 50
}
this.endPos = {
x: this.randomPos() || 50,
y: y || 50
}
}
randomPos(): number {
return Math.floor(Math.random() * 10000) / 100;
}
}
export {
Channel,
ChannelSettings
}