mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-04-25 07:22:09 -05:00
191 lines
5.3 KiB
JavaScript
191 lines
5.3 KiB
JavaScript
/**
|
|
* Room games
|
|
* Pokemon Showdown - http://pokemonshowdown.com/
|
|
*
|
|
* Room games are an abstract representation of an activity that a room
|
|
* can be focused on, such as a battle, tournament, or chat game like
|
|
* Hangman. Rooms are limited to one roomgame at a time.
|
|
*
|
|
* Room games can keep track of designated players. If a user is a player,
|
|
* they will not be allowed to change name until their games are complete.
|
|
*
|
|
* The player system is optional: Some games, like Hangman, don't designate
|
|
* players and just allow any user in the room to play.
|
|
*
|
|
* @license MIT license
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
// globally Rooms.RoomGamePlayer
|
|
class RoomGamePlayer {
|
|
constructor(user, game) {
|
|
// we explicitly don't hold a reference to the user
|
|
this.userid = user.userid;
|
|
this.name = user.name;
|
|
this.game = game;
|
|
user.games.add(this.game.id);
|
|
user.updateSearch();
|
|
}
|
|
destroy() {
|
|
let user = Users.getExact(this.userid);
|
|
if (user) {
|
|
user.games.delete(this.game.id);
|
|
user.updateSearch();
|
|
}
|
|
}
|
|
|
|
toString() {
|
|
return this.userid;
|
|
}
|
|
send(data) {
|
|
let user = Users.getExact(this.userid);
|
|
if (user) user.send(data);
|
|
}
|
|
sendRoom(data) {
|
|
let user = Users.getExact(this.userid);
|
|
if (user) user.sendTo(this.game.id, data);
|
|
}
|
|
}
|
|
|
|
// globally Rooms.RoomGame
|
|
class RoomGame {
|
|
constructor(room) {
|
|
this.id = room.id;
|
|
this.room = room;
|
|
this.gameid = 'game';
|
|
this.title = 'Game';
|
|
this.allowRenames = false;
|
|
this.players = Object.create(null);
|
|
this.playerCount = 0;
|
|
this.playerCap = 0;
|
|
}
|
|
|
|
destroy() {
|
|
this.room.game = null;
|
|
this.room = null;
|
|
for (let i in this.players) {
|
|
this.players[i].destroy();
|
|
}
|
|
}
|
|
|
|
addPlayer(user, ...rest) {
|
|
if (user.userid in this.players) return false;
|
|
if (this.playerCount >= this.playerCap) return false;
|
|
let player = this.makePlayer(user, ...rest);
|
|
if (!player) return false;
|
|
this.players[user.userid] = player;
|
|
this.playerCount++;
|
|
return true;
|
|
}
|
|
|
|
makePlayer(user) {
|
|
return new RoomGamePlayer(user, this);
|
|
}
|
|
|
|
removePlayer(user) {
|
|
if (!this.allowRenames) return false;
|
|
if (!(user.userid in this.players)) return false;
|
|
this.players[user.userid].destroy();
|
|
delete this.players[user.userid];
|
|
this.playerCount--;
|
|
return true;
|
|
}
|
|
|
|
renamePlayer(user, oldUserid) {
|
|
if (user.userid === oldUserid) {
|
|
this.players[user.userid].name = user.name;
|
|
} else {
|
|
this.players[user.userid] = this.players[oldUserid];
|
|
this.players[user.userid].userid = user.userid;
|
|
this.players[user.userid].name = user.name;
|
|
delete this.players[oldUserid];
|
|
}
|
|
}
|
|
|
|
// Commands:
|
|
|
|
// These are all optional to implement:
|
|
|
|
// forfeit(user)
|
|
// Called when a user uses /forfeit
|
|
// Also planned to be used for some force-forfeit situations, such
|
|
// as when a user changes their name and .allowRenames === false
|
|
// This is strongly recommended to be supported, as the user is
|
|
// extremely unlikely to keep playing after this function is
|
|
// called.
|
|
|
|
// choose(user, text)
|
|
// Called when a user uses /choose [text]
|
|
// If you have buttons, you are recommended to use this interface
|
|
// instead of making your own commands.
|
|
|
|
// undo(user, text)
|
|
// Called when a user uses /undo [text]
|
|
|
|
// joinGame(user, text)
|
|
// Called when a user uses /joingame [text]
|
|
|
|
// leaveGame(user, text)
|
|
// Called when a user uses /leavegame [text]
|
|
|
|
// Events:
|
|
|
|
// Note:
|
|
// A user can have multiple connections. For instance, if you have
|
|
// two tabs open and connected to PS, those tabs represent two
|
|
// connections, but a single PS user. Each tab can be in separate
|
|
// rooms.
|
|
|
|
// onJoin(user)
|
|
// Called when a user joins a room. (i.e. when the user's first
|
|
// connection joins)
|
|
|
|
// onRename(user, oldUserid, isJoining, isForceRenamed)
|
|
// Called when a user in the game is renamed. `isJoining` is true
|
|
// if the user was previously a guest, but now has a username.
|
|
// Check `!user.named` for the case where a user previously had a
|
|
// username but is now a guest. By default, updates a player's
|
|
// name as long as allowRenames is set to true.
|
|
|
|
// onLeave(user)
|
|
// Called when a user leaves the room. (i.e. when the user's last
|
|
// connection leaves)
|
|
|
|
// onConnect(user, connection)
|
|
// Called each time a connection joins a room (after onJoin if
|
|
// applicable). By default, this is also called when connection
|
|
// is updated in some way (such as by changing user or renaming).
|
|
// If you don't want this behavior, override onUpdateConnection
|
|
// and/or onRename.
|
|
|
|
// onUpdateConnection(user, connection)
|
|
// Called for each connection in a room that changes users by
|
|
// merging into a different user. By default, runs the onConnect
|
|
// handler.
|
|
|
|
// Player updates and an up-to-date report of what's going on in
|
|
// the game should be sent during `onConnect`. You should rarely
|
|
// need to handle the other events.
|
|
|
|
onRename(user, oldUserid, isJoining, isForceRenamed) {
|
|
if (!this.allowRenames || (!user.named && !isForceRenamed)) {
|
|
if (!(user.userid in this.players)) {
|
|
user.games.delete(this.id);
|
|
user.updateSearch();
|
|
}
|
|
return;
|
|
}
|
|
if (!(oldUserid in this.players)) return;
|
|
this.renamePlayer(user, oldUserid);
|
|
}
|
|
|
|
onUpdateConnection(user, connection) {
|
|
if (this.onConnect) this.onConnect(user, connection);
|
|
}
|
|
}
|
|
|
|
// these exports are traditionally attached to rooms.js
|
|
exports.RoomGame = RoomGame;
|
|
exports.RoomGamePlayer = RoomGamePlayer;
|