@haelp/teto
Version:
A typescript-based controllable TETR.IO client.
323 lines (322 loc) • 11.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: Object.getOwnPropertyDescriptor(all, name).get
});
}
_export(exports, {
get ReplayManager () {
return _replayManager.ReplayManager;
},
get Room () {
return Room;
}
});
const _game = require("../game");
const _presets = require("./presets");
const _replayManager = require("./replayManager");
class Room {
client;
listeners = [];
/** the ID of the room */ id;
/** Whether or not the room is public */ public;
/** The type of the room (public | private) */ type;
/** Name of the room */ name;
/** Safe Name of the room */ name_safe;
/** UID of the host */ owner;
/** UID of the room creator (this person can reclaim host) */ creator;
/** The autostart state of the room */ autostart;
/** The match config for the room */ match;
/** The maxiumum number of players that can play in the room (override by moving as host) */ userLimit;
/** The players in the room */ players;
/** The room config */ options;
/** The current state of the room (ingame | lobby) */ state;
/** The time the last game started */ gameStart = null;
/** The replay data for the last played game */ replay = null;
/** Room chat history */ chats = [];
/** @hideconstructor */ constructor(client, data){
this.client = client;
this.handleUpdate(data);
this.init();
}
handleUpdate(data) {
this.id = data.id;
this.autostart = data.auto;
[
"public",
"type",
"name",
"name_safe",
"owner",
"creator",
"state",
"match",
"players",
"userLimit"
].forEach((key)=>Object.assign(this, {
[key]: data[key]
}));
this.options = data.options;
}
listen(event, cb, once = false) {
this.listeners.push([
event,
cb
]);
if (once) {
this.client.once(event, cb);
} else {
this.client.on(event, cb);
}
}
init() {
const emitPlayers = ()=>this.client.emit("client.room.players", this.players);
let abortTimeout = null;
this.listen("room.update.host", (data)=>{
this.owner = data;
});
this.listen("room.update.bracket", (data)=>{
const idx = this.players.findIndex((p)=>p._id === data.uid);
if (idx >= 0) this.players[idx].bracket = data.bracket;
emitPlayers();
});
this.listen("room.update.auto", (auto)=>{
this.autostart = auto;
});
this.listen("room.update", this.handleUpdate.bind(this));
this.listen("room.player.add", (data)=>{
this.players.push(data);
emitPlayers();
});
this.listen("room.player.remove", (data)=>{
this.players = this.players.filter((p)=>p._id !== data);
emitPlayers();
});
this.listen("game.ready", (data)=>{
try {
this.client.game = new _game.Game(this.client, data);
} catch {
return; // not in room, don't do anything
}
if (data.isNew) {
this.gameStart = performance.now();
this.replay = new _replayManager.ReplayManager(data.players, this.players);
this.client.emit("client.game.start", {
multi: this.match.ft > 1 || this.match.wb > 1,
ft: this.match.ft,
wb: this.match.wb,
players: data.players.map((p)=>({
id: p.userid,
name: p.options.username,
points: 0
}))
});
}
this.replay?.addRound(data.players);
});
this.listen("game.replay", (event)=>this.replay?.pipe(event));
this.listen("game.replay.end", async ({ gameid, data })=>{
this.replay?.die({
gameid,
data,
game: this.client.game
});
if (!this.client.game || this.client.game.gameid !== gameid) return;
this.client.game.stop();
this.client.emit("client.game.over", {
reason: "finish",
data
});
});
this.listen("game.advance", ()=>{
this.replay?.endRound({
game: this.client.game
});
if (this.client.game) {
this.client.game = this.client.game.destroy();
this.client.emit("client.game.over", {
reason: "end"
});
}
});
this.listen("game.score", (data)=>{
if (this.client.game) {
this.client.game = this.client.game.destroy();
this.client.emit("client.game.over", {
reason: "end"
});
}
this.client.emit("client.game.round.end", data.victor);
});
this.listen("game.abort", ()=>{
if (abortTimeout) return;
abortTimeout = setTimeout(()=>{
abortTimeout = null;
}, 50);
this.client.emit("client.game.abort");
if (!this.client.game) return;
this.client.game = this.client.game.destroy();
this.client.emit("client.game.over", {
reason: "abort"
});
});
this.listen("game.end", (data)=>{
this.client.emit("client.game.round.end", data.leaderboard[0].id);
const maxWins = data.leaderboard.reduce((max, item)=>Math.max(max, item.wins), 0);
this.client.emit("client.game.end", {
duration: performance.now() - (this.gameStart ?? 0),
players: data.leaderboard.map((item)=>({
id: item.id,
name: item.username,
points: item.wins,
won: item.wins === maxWins,
raw: item
}))
});
if (!this.client.game) return;
this.client.game = this.client.game.destroy();
this.client.emit("client.game.over", {
reason: "end"
});
});
this.listen("client.game.end", ()=>this.replay?.end({
self: this.client.user.id
}));
// chat
this.listen("room.chat", (item)=>this.chats.push(item));
// get booted
this.listen("room.kick", ()=>this.destroy());
this.listen("room.leave", ()=>this.destroy());
}
/** Whether or not the client is the host */ get isHost() {
return this.client.user.id === this.owner;
}
destroy() {
this.listeners.forEach((l)=>this.client.off(l[0], l[1]));
if (this.client.game) {
this.client.game.destroy();
this.client.emit("client.game.over", {
reason: "leave"
});
}
delete this.client.room;
}
/**
* Leave the current room
* @example
* await client.room!.leave();
*/ async leave() {
await this.client.wrap("room.leave", undefined, "room.leave");
this.destroy();
}
/**
* Kick a user from the room for a specified duration (if host)
* @param id - id of user to kick
* @param duration - duration to kick the user, in seconds
* @example
* await client.room!.kick('646f633d276f42a80ba44304', 100);
*/ async kick(id, duration = 900) {
return await this.client.wrap("room.kick", {
uid: id,
duration
}, "room.player.remove");
}
/**
* Unban a user from the room
* @example
* client.room!.unban('halp');
*/ unban(username) {
return this.client.emit("room.unban", username);
}
/**
* Send a public message to the room's chat.
* The `pinned` parameter is the same as using the `/announce` command in TETR.IO
* The `pinned` parameter being true will result in an error if the client is not host.
* @example
* await client.room!.chat('hi!');
* @example
* await client.room!.chat('Important info:', true);
*/ async chat(message, pinned = false) {
return await this.client.wrap("room.chat.send", {
content: message,
pinned
}, "room.chat");
}
/**
* Clears the chat
*/ async clearChat() {
return await this.client.wrap("room.chat.clear", undefined, "room.chat.clear");
}
/**
* Sets the room id (only works for supporter accounts)
* @example
* client.room!.setID('TEST');
*/ async setID(id) {
return await this.client.wrap("room.setid", id.toUpperCase(), "room.update");
}
/**
* Update the room's config, similar to using the /set command in tetr.io
* await client.room!.update({ index: 'name', value: 'test room'});
* @returns
*/ async update(...options) {
return await this.client.wrap("room.setconfig", options.map((opt)=>typeof opt.value === "number" ? {
index: opt.index,
value: opt.value.toString()
} : opt), "room.update");
}
/**
* Sets the room's preset
* @example
* await client.room!.usePreset('tetra league (season 1)');
*/ async usePreset(preset) {
return await this.update(..._presets.roomConfigPresets[preset]);
}
/**
* Start the game
*/ async start() {
return await this.client.wrap("room.start", undefined, "game.ready");
}
/**
* Abort the game
*/ async abort() {
return await this.client.wrap("room.abort", undefined, "game.abort");
}
/**
* Give the host to someone else
* @example
* await client.room!.transferHost(await client.social.resolve('halp'));
*/ async transferHost(player) {
return await this.client.wrap("room.owner.transfer", player, "room.update.host");
}
/** Take host if you created the room */ async takeHost() {
return await this.client.wrap("room.owner.revoke", undefined, "room.update.host");
}
/**
* Switch bracket
* @example
* await client.room!.switch('player');
*/ async switch(bracket) {
if (this.players.some((p)=>p._id === this.client.user.id && p.bracket === bracket)) return;
return await this.client.wrap("room.bracket.switch", bracket, "room.update.bracket");
}
/**
* Move someone's bracket
* @example
* await client.room!.move('646f633d276f42a80ba44304', 'spectator');
*/ async move(uid, bracket) {
const player = this.players.find((p)=>p._id === uid);
if (!player) {
throw new Error(`Player with UID ${uid} not found in room.`);
}
if (player.bracket === bracket) return;
return await this.client.wrap("room.bracket.move", {
uid,
bracket
}, "room.update.bracket");
}
}
//# sourceMappingURL=index.js.map