mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-03-21 17:25:10 -05:00
Typescript uno/othermetas/hangman (#6229)
This commit is contained in:
parent
de7d8168af
commit
649ac8ea6b
|
|
@ -8,30 +8,32 @@
|
|||
const maxMistakes = 6;
|
||||
|
||||
class Hangman extends Rooms.RoomGame {
|
||||
/**
|
||||
* @param {ChatRoom | GameRoom} room
|
||||
* @param {User} user
|
||||
* @param {string} word
|
||||
* @param {string?} [hint]
|
||||
*/
|
||||
constructor(room, user, word, hint = '') {
|
||||
gameNumber: number;
|
||||
creator: ID;
|
||||
word: string;
|
||||
hint: string;
|
||||
incorrectGuesses: number;
|
||||
|
||||
guesses: string[];
|
||||
letterGuesses: string[];
|
||||
lastGuesser: string;
|
||||
wordSoFar: string[];
|
||||
|
||||
constructor(room: ChatRoom | GameRoom, user: User, word: string, hint = '') {
|
||||
super(room);
|
||||
|
||||
this.gameNumber = ++room.gameNumber;
|
||||
|
||||
this.gameid = /** @type {ID} */ ('hangman');
|
||||
this.gameid = 'hangman' as ID;
|
||||
this.title = 'Hangman';
|
||||
this.creator = user.id;
|
||||
this.word = word;
|
||||
this.hint = hint;
|
||||
this.incorrectGuesses = 0;
|
||||
|
||||
/** @type {string[]} */
|
||||
this.guesses = [];
|
||||
/** @type {string[]} */
|
||||
this.letterGuesses = [];
|
||||
this.lastGuesser = '';
|
||||
/** @type {string[]} */
|
||||
this.wordSoFar = [];
|
||||
|
||||
for (const letter of word) {
|
||||
|
|
@ -43,15 +45,11 @@ class Hangman extends Rooms.RoomGame {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} word
|
||||
* @param {User} user
|
||||
*/
|
||||
guess(word, user) {
|
||||
guess(word: string, user: User) {
|
||||
if (user.id === this.creator) return user.sendTo(this.room, "You can't guess in your own hangman game.");
|
||||
|
||||
let sanitized = word.replace(/[^A-Za-z ]/g, '');
|
||||
let normalized = toID(sanitized);
|
||||
const sanitized = word.replace(/[^A-Za-z ]/g, '');
|
||||
const normalized = toID(sanitized);
|
||||
if (normalized.length < 1) return user.sendTo(this.room, "Guess too short.");
|
||||
if (sanitized.length > 30) return user.sendTo(this.room, "Guess too long.");
|
||||
|
||||
|
|
@ -72,11 +70,7 @@ class Hangman extends Rooms.RoomGame {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} letter
|
||||
* @param {string} guesser
|
||||
*/
|
||||
guessLetter(letter, guesser) {
|
||||
guessLetter(letter: string, guesser: string) {
|
||||
letter = letter.toUpperCase();
|
||||
if (this.guesses.includes(letter)) return false;
|
||||
if (this.word.toUpperCase().includes(letter)) {
|
||||
|
|
@ -106,15 +100,11 @@ class Hangman extends Rooms.RoomGame {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} word
|
||||
* @param {string} guesser
|
||||
*/
|
||||
guessWord(word, guesser) {
|
||||
let ourWord = toID(this.word);
|
||||
let guessedWord = toID(word);
|
||||
guessWord(word: string, guesser: string) {
|
||||
const ourWord = toID(this.word);
|
||||
const guessedWord = toID(word);
|
||||
if (ourWord === guessedWord) {
|
||||
for (let [i, letter] of this.wordSoFar.entries()) {
|
||||
for (const [i, letter] of this.wordSoFar.entries()) {
|
||||
if (letter === '_') {
|
||||
this.wordSoFar[i] = this.word[i];
|
||||
}
|
||||
|
|
@ -155,7 +145,7 @@ class Hangman extends Rooms.RoomGame {
|
|||
|
||||
let wordString = this.wordSoFar.join('');
|
||||
if (result === 1) {
|
||||
let word = this.word;
|
||||
const word = this.word;
|
||||
wordString = wordString.replace(/_+/g, (match, offset) =>
|
||||
`<font color="#7af87a">${word.substr(offset, match.length)}</font>`
|
||||
);
|
||||
|
|
@ -184,11 +174,7 @@ class Hangman extends Rooms.RoomGame {
|
|||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {User} user
|
||||
* @param {boolean} [broadcast]
|
||||
*/
|
||||
display(user, broadcast = false) {
|
||||
display(user: User, broadcast = false) {
|
||||
if (broadcast) {
|
||||
this.room.add(`|uhtml|hangman${this.gameNumber}|${this.generateWindow()}`);
|
||||
} else {
|
||||
|
|
@ -217,23 +203,21 @@ class Hangman extends Rooms.RoomGame {
|
|||
}
|
||||
}
|
||||
|
||||
/** @type {ChatCommands} */
|
||||
const commands = {
|
||||
export const commands: ChatCommands = {
|
||||
hangman: {
|
||||
create: 'new',
|
||||
new(target, room, user, connection) {
|
||||
let text = this.filter(target);
|
||||
const text = this.filter(target);
|
||||
if (target !== text) return this.errorReply("You are not allowed to use filtered words in hangmans.");
|
||||
let params = text.split(',');
|
||||
const params = text.split(',');
|
||||
|
||||
if (!this.can('minigame', null, room)) return false;
|
||||
// @ts-ignore
|
||||
if (room.hangmanDisabled) return this.errorReply("Hangman is disabled for this room.");
|
||||
if (!this.canTalk()) return;
|
||||
if (room.game) return this.errorReply(`There is already a game of ${room.game.title} in progress in this room.`);
|
||||
|
||||
if (!params) return this.errorReply("No word entered.");
|
||||
let word = params[0].replace(/[^A-Za-z '-]/g, '');
|
||||
const word = params[0].replace(/[^A-Za-z '-]/g, '');
|
||||
if (word.replace(/ /g, '').length < 1) return this.errorReply("Enter a valid word");
|
||||
if (word.length > 30) return this.errorReply("Phrase must be less than 30 characters.");
|
||||
if (word.split(' ').some(w => w.length > 20)) return this.errorReply("Each word in the phrase must be less than 20 characters.");
|
||||
|
|
@ -259,7 +243,7 @@ const commands = {
|
|||
if (!room.game || room.game.gameid !== 'hangman') return this.errorReply("There is no game of hangman running in this room.");
|
||||
if (!this.canTalk()) return;
|
||||
|
||||
const game = /** @type {Hangman} */ (room.game);
|
||||
const game = room.game as Hangman;
|
||||
game.guess(target, user);
|
||||
},
|
||||
guesshelp: [
|
||||
|
|
@ -273,7 +257,7 @@ const commands = {
|
|||
if (!this.canTalk()) return;
|
||||
if (!room.game || room.game.gameid !== 'hangman') return this.errorReply("There is no game of hangman running in this room.");
|
||||
|
||||
const game = /** @type {Hangman} */ (room.game);
|
||||
const game = room.game as Hangman;
|
||||
game.end();
|
||||
this.modlog('ENDHANGMAN');
|
||||
return this.privateModAction(`(The game of hangman was ended by ${user.name}.)`);
|
||||
|
|
@ -282,14 +266,11 @@ const commands = {
|
|||
|
||||
disable(target, room, user) {
|
||||
if (!this.can('gamemanagement', null, room)) return;
|
||||
// @ts-ignore
|
||||
if (room.hangmanDisabled) {
|
||||
return this.errorReply("Hangman is already disabled.");
|
||||
}
|
||||
// @ts-ignore
|
||||
room.hangmanDisabled = true;
|
||||
if (room.chatRoomData) {
|
||||
// @ts-ignore
|
||||
room.chatRoomData.hangmanDisabled = true;
|
||||
Rooms.global.writeChatRoomData();
|
||||
}
|
||||
|
|
@ -298,14 +279,11 @@ const commands = {
|
|||
|
||||
enable(target, room, user) {
|
||||
if (!this.can('gamemanagement', null, room)) return;
|
||||
// @ts-ignore
|
||||
if (!room.hangmanDisabled) {
|
||||
return this.errorReply("Hangman is already enabled.");
|
||||
}
|
||||
// @ts-ignore
|
||||
delete room.hangmanDisabled;
|
||||
if (room.chatRoomData) {
|
||||
// @ts-ignore
|
||||
delete room.chatRoomData.hangmanDisabled;
|
||||
Rooms.global.writeChatRoomData();
|
||||
}
|
||||
|
|
@ -317,7 +295,7 @@ const commands = {
|
|||
if (!this.runBroadcast()) return;
|
||||
room.update();
|
||||
|
||||
const game = /** @type {Hangman} */ (room.game);
|
||||
const game = room.game as Hangman;
|
||||
game.display(user, this.broadcasting);
|
||||
},
|
||||
|
||||
|
|
@ -340,11 +318,9 @@ const commands = {
|
|||
guess(target, room, user) {
|
||||
if (!room.game) return this.errorReply("There is no game running in this room.");
|
||||
if (!this.canTalk()) return;
|
||||
// @ts-ignore
|
||||
if (!room.game.guess) return this.errorReply("You can't guess anything in this game.");
|
||||
if (!(room.game as Hangman).guess) return this.errorReply("You can't guess anything in this game.");
|
||||
|
||||
// @ts-ignore
|
||||
room.game.guess(target, user);
|
||||
(room.game as Hangman).guess(target, user);
|
||||
},
|
||||
guesshelp: [
|
||||
`/guess - Shortcut for /hangman guess.`,
|
||||
|
|
@ -352,8 +328,8 @@ const commands = {
|
|||
`/hangman guess [word] - Same as a letter, but guesses an entire word.`,
|
||||
],
|
||||
};
|
||||
/** @type {SettingsHandler} */
|
||||
const roomSettings = room => ({
|
||||
|
||||
export const roomSettings: SettingsHandler = room => ({
|
||||
label: "Hangman",
|
||||
permission: 'editroom',
|
||||
options: [
|
||||
|
|
@ -361,8 +337,3 @@ const roomSettings = room => ({
|
|||
[`enabled`, !room.hangmanDisabled || 'hangman enable'],
|
||||
],
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
commands,
|
||||
roomSettings,
|
||||
};
|
||||
|
|
@ -1,18 +1,21 @@
|
|||
// Other Metas plugin by Spandan
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @param {string} stone
|
||||
* @param {string} mod
|
||||
* @return {Item | null}
|
||||
*/
|
||||
function getMegaStone(stone, mod = 'gen8') {
|
||||
interface StoneDeltas {
|
||||
baseStats: {[stat in StatName]: number};
|
||||
weighthg: number;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
type TierShiftTiers = 'UU' | 'RUBL' | 'RU' | 'NUBL' | 'NU' | 'PUBL' | 'PU' | 'NFE' | 'LC Uber' | 'LC';
|
||||
|
||||
function getMegaStone(stone: string, mod = 'gen8'): Item | null {
|
||||
let dex = Dex;
|
||||
if (mod && toID(mod) in Dex.dexes) dex = Dex.mod(toID(mod));
|
||||
let item = dex.getItem(stone);
|
||||
const item = dex.getItem(stone);
|
||||
if (!item.exists) {
|
||||
if (toID(stone) === 'dragonascent') {
|
||||
let move = dex.getMove(stone);
|
||||
const move = dex.getMove(stone);
|
||||
return {
|
||||
id: move.id,
|
||||
name: move.name,
|
||||
|
|
@ -34,8 +37,7 @@ function getMegaStone(stone, mod = 'gen8') {
|
|||
return item;
|
||||
}
|
||||
|
||||
/** @type {ChatCommands} */
|
||||
const commands = {
|
||||
export const commands: ChatCommands = {
|
||||
'!othermetas': true,
|
||||
om: 'othermetas',
|
||||
othermetas(target, room, user) {
|
||||
|
|
@ -51,7 +53,7 @@ const commands = {
|
|||
buffer += `- <a href="https://www.smogon.com/forums/forums/394/">Other Metagames Forum</a><br />`;
|
||||
if (!target) return this.sendReplyBox(buffer);
|
||||
}
|
||||
let showMonthly = (target === 'all' || target === 'omofthemonth' || target === 'omotm' || target === 'month');
|
||||
const showMonthly = (target === 'all' || target === 'omofthemonth' || target === 'omotm' || target === 'month');
|
||||
|
||||
if (target === 'all') {
|
||||
// Display OMotM formats, with forum thread links as caption
|
||||
|
|
@ -87,7 +89,7 @@ const commands = {
|
|||
const template = dex.getTemplate(sep[0]);
|
||||
if (!stone || (dex.gen >= 8 && ['redorb', 'blueorb'].includes(stone.id))) return this.errorReply(`Error: Mega Stone not found.`);
|
||||
if (!template.exists) return this.errorReply(`Error: Pokemon not found.`);
|
||||
let banlist = Dex.getFormat('gen8mixandmega').banlist;
|
||||
const banlist = Dex.getFormat('gen8mixandmega').banlist;
|
||||
if (banlist.includes(stone.name)) {
|
||||
this.errorReply(`Warning: ${stone.name} is banned from Mix and Mega.`);
|
||||
}
|
||||
|
|
@ -107,14 +109,12 @@ const commands = {
|
|||
megaTemplate = dex.getTemplate("Kyogre-Primal");
|
||||
baseTemplate = dex.getTemplate("Kyogre");
|
||||
}
|
||||
/** @type {{baseStats: {[k: string]: number}, weighthg: number, type?: string}} */
|
||||
let deltas = {
|
||||
baseStats: {},
|
||||
const deltas: StoneDeltas = {
|
||||
baseStats: Object.create(null),
|
||||
weighthg: megaTemplate.weighthg - baseTemplate.weighthg,
|
||||
};
|
||||
for (let statId in megaTemplate.baseStats) {
|
||||
// @ts-ignore
|
||||
deltas.baseStats[statId] = megaTemplate.baseStats[statId] - baseTemplate.baseStats[statId];
|
||||
for (const statId in megaTemplate.baseStats) {
|
||||
deltas.baseStats[statId as StatName] = megaTemplate.baseStats[statId as StatName] - baseTemplate.baseStats[statId as StatName];
|
||||
}
|
||||
if (megaTemplate.types.length > baseTemplate.types.length) {
|
||||
deltas.type = megaTemplate.types[1];
|
||||
|
|
@ -124,8 +124,8 @@ const commands = {
|
|||
deltas.type = megaTemplate.types[1];
|
||||
}
|
||||
//////////////////////////////////////////
|
||||
let mixedTemplate = Dex.deepClone(template);
|
||||
mixedTemplate.abilities = Object.assign({}, megaTemplate.abilities);
|
||||
const mixedTemplate = Dex.deepClone(template);
|
||||
mixedTemplate.abilities = Dex.deepClone(megaTemplate.abilities);
|
||||
if (mixedTemplate.types[0] === deltas.type) { // Add any type gains
|
||||
mixedTemplate.types = [deltas.type];
|
||||
} else if (deltas.type === 'mono') {
|
||||
|
|
@ -133,8 +133,8 @@ const commands = {
|
|||
} else if (deltas.type) {
|
||||
mixedTemplate.types = [mixedTemplate.types[0], deltas.type];
|
||||
}
|
||||
for (let statName in template.baseStats) { // Add the changed stats and weight
|
||||
mixedTemplate.baseStats[statName] = Dex.clampIntRange(mixedTemplate.baseStats[statName] + deltas.baseStats[statName], 1, 255);
|
||||
for (const statName in template.baseStats) { // Add the changed stats and weight
|
||||
mixedTemplate.baseStats[statName] = Dex.clampIntRange(mixedTemplate.baseStats[statName] + deltas.baseStats[statName as StatName], 1, 255);
|
||||
}
|
||||
mixedTemplate.weighthg = Math.max(1, template.weighthg + deltas.weighthg);
|
||||
mixedTemplate.tier = "MnM";
|
||||
|
|
@ -150,12 +150,11 @@ const commands = {
|
|||
} else if (mixedTemplate.weighthg >= 100) {
|
||||
weighthit = 40;
|
||||
}
|
||||
/** @type {{[k: string]: string}} */
|
||||
let details = {
|
||||
const details: {[k: string]: string} = {
|
||||
"Dex#": '' + mixedTemplate.num,
|
||||
"Gen": '' + mixedTemplate.gen,
|
||||
"Height": mixedTemplate.heightm + " m",
|
||||
"Weight": mixedTemplate.weighthg / 10 + " kg <em>(" + weighthit + " BP)</em>",
|
||||
Gen: '' + mixedTemplate.gen,
|
||||
Height: mixedTemplate.heightm + " m",
|
||||
Weight: mixedTemplate.weighthg / 10 + " kg <em>(" + weighthit + " BP)</em>",
|
||||
"Dex Colour": mixedTemplate.color,
|
||||
};
|
||||
if (mixedTemplate.eggGroups) details["Egg Group(s)"] = mixedTemplate.eggGroups.join(", ");
|
||||
|
|
@ -173,18 +172,18 @@ const commands = {
|
|||
megastone: 'stone',
|
||||
stone(target) {
|
||||
if (!this.runBroadcast()) return;
|
||||
let sep = target.split(',');
|
||||
const sep = target.split(',');
|
||||
let dex = Dex;
|
||||
if (sep[1] && toID(sep[1]) in Dex.dexes) dex = Dex.mod(toID(sep[1]));
|
||||
let targetid = toID(sep[0]);
|
||||
const targetid = toID(sep[0]);
|
||||
if (!targetid) return this.parse('/help stone');
|
||||
let stone = getMegaStone(targetid, sep[1]);
|
||||
const stone = getMegaStone(targetid, sep[1]);
|
||||
if (!stone || (dex.gen >= 8 && ['redorb', 'blueorb'].includes(stone.id))) return this.errorReply(`Error: Mega Stone not found.`);
|
||||
let banlist = Dex.getFormat('gen8mixandmega').banlist;
|
||||
const banlist = Dex.getFormat('gen8mixandmega').banlist;
|
||||
if (banlist.includes(stone.name)) {
|
||||
this.errorReply(`Warning: ${stone.name} is banned from Mix and Mega.`);
|
||||
}
|
||||
let restrictedStones = Dex.getFormat('gen8mixandmega').restrictedStones || [];
|
||||
const restrictedStones = Dex.getFormat('gen8mixandmega').restrictedStones || [];
|
||||
if (restrictedStones.includes(stone.name)) {
|
||||
if (dex.getTemplate(stone.megaEvolves).isNonstandard === "Past") {
|
||||
this.errorReply(`Warning: ${stone.name} is restricted in Mix and Mega.`);
|
||||
|
|
@ -212,14 +211,12 @@ const commands = {
|
|||
megaTemplate = dex.getTemplate("Kyogre-Primal");
|
||||
baseTemplate = dex.getTemplate("Kyogre");
|
||||
}
|
||||
/** @type {{baseStats: {[k: string]: number}, weighthg: number, type?: string}} */
|
||||
let deltas = {
|
||||
baseStats: {},
|
||||
const deltas: StoneDeltas = {
|
||||
baseStats: Object.create(null),
|
||||
weighthg: megaTemplate.weighthg - baseTemplate.weighthg,
|
||||
};
|
||||
for (let statId in megaTemplate.baseStats) {
|
||||
// @ts-ignore
|
||||
deltas.baseStats[statId] = megaTemplate.baseStats[statId] - baseTemplate.baseStats[statId];
|
||||
for (const statId in megaTemplate.baseStats) {
|
||||
deltas.baseStats[statId as StatName] = megaTemplate.baseStats[statId as StatName] - baseTemplate.baseStats[statId as StatName];
|
||||
}
|
||||
if (megaTemplate.types.length > baseTemplate.types.length) {
|
||||
deltas.type = megaTemplate.types[1];
|
||||
|
|
@ -228,9 +225,9 @@ const commands = {
|
|||
} else if (megaTemplate.types[1] !== baseTemplate.types[1]) {
|
||||
deltas.type = megaTemplate.types[1];
|
||||
}
|
||||
let details = {
|
||||
"Gen": stone.gen,
|
||||
"Weight": (deltas.weighthg < 0 ? "" : "+") + deltas.weighthg / 10 + " kg",
|
||||
const details = {
|
||||
Gen: stone.gen,
|
||||
Weight: (deltas.weighthg < 0 ? "" : "+") + deltas.weighthg / 10 + " kg",
|
||||
};
|
||||
let tier;
|
||||
if (['redorb', 'blueorb'].includes(stone.id)) {
|
||||
|
|
@ -281,17 +278,17 @@ const commands = {
|
|||
stonehelp: [`/stone <mega stone>[, generation] - Shows the changes that a mega stone/orb applies to a Pokemon.`],
|
||||
|
||||
'!350cup': true,
|
||||
'350': '350cup',
|
||||
350: '350cup',
|
||||
'350cup'(target, room, user) {
|
||||
if (!this.runBroadcast()) return;
|
||||
if (!toID(target)) return this.parse('/help 350cup');
|
||||
let template = Dex.deepClone(Dex.getTemplate(target));
|
||||
const template = Dex.deepClone(Dex.getTemplate(target));
|
||||
if (!template.exists) return this.errorReply("Error: Pokemon not found.");
|
||||
let bst = 0;
|
||||
for (let i in template.baseStats) {
|
||||
for (const i in template.baseStats) {
|
||||
bst += template.baseStats[i];
|
||||
}
|
||||
for (let i in template.baseStats) {
|
||||
for (const i in template.baseStats) {
|
||||
template.baseStats[i] = template.baseStats[i] * (bst <= 350 ? 2 : 1);
|
||||
}
|
||||
this.sendReply(`|html|${Chat.getDataPokemonHTML(template)}`);
|
||||
|
|
@ -303,26 +300,25 @@ const commands = {
|
|||
tiershift(target, room, user) {
|
||||
if (!this.runBroadcast()) return;
|
||||
if (!toID(target)) return this.parse('/help tiershift');
|
||||
let template = Dex.deepClone(Dex.mod('gen7').getTemplate(target));
|
||||
const template = Dex.deepClone(Dex.mod('gen7').getTemplate(target));
|
||||
if (!template.exists) return this.errorReply("Error: Pokemon not found.");
|
||||
/** @type {{[k: string]: number}} */
|
||||
let boosts = {
|
||||
'UU': 10,
|
||||
'RUBL': 10,
|
||||
'RU': 20,
|
||||
'NUBL': 20,
|
||||
'NU': 30,
|
||||
'PUBL': 30,
|
||||
'PU': 40,
|
||||
'NFE': 40,
|
||||
const boosts: {[tier in TierShiftTiers]: number} = {
|
||||
UU: 10,
|
||||
RUBL: 10,
|
||||
RU: 20,
|
||||
NUBL: 20,
|
||||
NU: 30,
|
||||
PUBL: 30,
|
||||
PU: 40,
|
||||
NFE: 40,
|
||||
'LC Uber': 40,
|
||||
'LC': 40,
|
||||
LC: 40,
|
||||
};
|
||||
let tier = template.tier;
|
||||
if (tier[0] === '(') tier = tier.slice(1, -1);
|
||||
if (!(tier in boosts)) return this.sendReply(`|html|${Chat.getDataPokemonHTML(template)}`);
|
||||
let boost = boosts[tier];
|
||||
for (let statName in template.baseStats) {
|
||||
const boost = boosts[tier as TierShiftTiers];
|
||||
for (const statName in template.baseStats) {
|
||||
if (statName === 'hp') continue;
|
||||
template.baseStats[statName] = Dex.clampIntRange(template.baseStats[statName] + boost, 1, 255);
|
||||
}
|
||||
|
|
@ -335,11 +331,11 @@ const commands = {
|
|||
scalemons(target, room, user) {
|
||||
if (!this.runBroadcast()) return;
|
||||
if (!toID(target)) return this.parse(`/help scalemons`);
|
||||
let template = Dex.deepClone(Dex.getTemplate(target));
|
||||
const template = Dex.deepClone(Dex.getTemplate(target));
|
||||
if (!template.exists) return this.errorReply(`Error: Pokemon ${target} not found.`);
|
||||
let stats = ['atk', 'def', 'spa', 'spd', 'spe'];
|
||||
let pst = stats.map(stat => template.baseStats[stat]).reduce((x, y) => x + y);
|
||||
let scale = 600 - template.baseStats['hp'];
|
||||
const stats = ['atk', 'def', 'spa', 'spd', 'spe'];
|
||||
const pst = stats.map(stat => template.baseStats[stat]).reduce((x, y) => x + y);
|
||||
const scale = 600 - template.baseStats['hp'];
|
||||
for (const stat of stats) {
|
||||
template.baseStats[stat] = Dex.clampIntRange(template.baseStats[stat] * scale / pst, 1, 255);
|
||||
}
|
||||
|
|
@ -351,15 +347,15 @@ const commands = {
|
|||
ns: 'natureswap',
|
||||
natureswap(target, room, user) {
|
||||
if (!this.runBroadcast()) return;
|
||||
let nature = target.trim().split(' ')[0];
|
||||
let pokemon = target.trim().split(' ')[1];
|
||||
const nature = target.trim().split(' ')[0];
|
||||
const pokemon = target.trim().split(' ')[1];
|
||||
if (!toID(nature) || !toID(pokemon)) return this.parse(`/help natureswap`);
|
||||
let natureObj = /** @type {{name: string, plus?: string | undefined, minus?: string | undefined, exists?: boolean}} */ Dex.getNature(nature);
|
||||
const natureObj: {name: string, plus?: string | undefined, minus?: string | undefined, exists?: boolean} = Dex.getNature(nature);
|
||||
if (!natureObj.exists) return this.errorReply(`Error: Nature ${nature} not found.`);
|
||||
let template = Dex.deepClone(Dex.getTemplate(pokemon));
|
||||
const template = Dex.deepClone(Dex.getTemplate(pokemon));
|
||||
if (!template.exists) return this.errorReply(`Error: Pokemon ${pokemon} not found.`);
|
||||
if (natureObj.minus && natureObj.plus) {
|
||||
let swap = template.baseStats[natureObj.minus];
|
||||
const swap = template.baseStats[natureObj.minus];
|
||||
template.baseStats[natureObj.minus] = template.baseStats[natureObj.plus];
|
||||
template.baseStats[natureObj.plus] = swap;
|
||||
template.tier = 'NS';
|
||||
|
|
@ -406,9 +402,8 @@ const commands = {
|
|||
const mixedTemplate = Dex.deepClone(template);
|
||||
mixedTemplate.abilities = Dex.deepClone(crossTemplate.abilities);
|
||||
mixedTemplate.baseStats = Dex.deepClone(mixedTemplate.baseStats);
|
||||
for (let statName in template.baseStats) {
|
||||
// @ts-ignore
|
||||
mixedTemplate.baseStats[statName] += crossTemplate.baseStats[statName] - prevo.baseStats[statName];
|
||||
for (const statName in template.baseStats) {
|
||||
mixedTemplate.baseStats[statName as StatName] += crossTemplate.baseStats[statName as StatName] - prevo.baseStats[statName as StatName];
|
||||
}
|
||||
mixedTemplate.types = [template.types[0]];
|
||||
if (template.types[1]) mixedTemplate.types.push(template.types[1]);
|
||||
|
|
@ -419,8 +414,8 @@ const commands = {
|
|||
if (mixedTemplate.weighthg < 1) {
|
||||
mixedTemplate.weighthg = 1;
|
||||
}
|
||||
for (const stat of Object.values(mixedTemplate.baseStats)) {
|
||||
if (stat < 1 || stat > 255) {
|
||||
for (const stat in mixedTemplate.baseStats) {
|
||||
if (mixedTemplate.baseStats[stat] < 1 || mixedTemplate.baseStats[stat] > 255) {
|
||||
this.errorReply(`Warning: This Cross Evolution cannot happen since a stat goes below 0 or above 255.`);
|
||||
break;
|
||||
}
|
||||
|
|
@ -438,12 +433,11 @@ const commands = {
|
|||
} else if (mixedTemplate.weighthg >= 100) {
|
||||
weighthit = 40;
|
||||
}
|
||||
/** @type {{[k: string]: string}} */
|
||||
let details = {
|
||||
const details: {[k: string]: string} = {
|
||||
"Dex#": mixedTemplate.num,
|
||||
"Gen": mixedTemplate.gen,
|
||||
"Height": mixedTemplate.heightm + " m",
|
||||
"Weight": mixedTemplate.weighthg / 10 + " kg <em>(" + weighthit + " BP)</em>",
|
||||
Gen: mixedTemplate.gen,
|
||||
Height: mixedTemplate.heightm + " m",
|
||||
Weight: mixedTemplate.weighthg / 10 + " kg <em>(" + weighthit + " BP)</em>",
|
||||
"Dex Colour": mixedTemplate.color,
|
||||
};
|
||||
if (mixedTemplate.eggGroups) details["Egg Group(s)"] = mixedTemplate.eggGroups.join(", ");
|
||||
|
|
@ -456,5 +450,3 @@ const commands = {
|
|||
},
|
||||
crossevolvehelp: ["/crossevo <base pokemon>, <evolved pokemon> - Shows the type and stats for the Cross Evolved Pokemon."],
|
||||
};
|
||||
|
||||
exports.commands = commands;
|
||||
|
|
@ -9,99 +9,101 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
type Color = 'Green' | 'Yellow' | 'Red' | 'Blue' | 'Black';
|
||||
interface Card {
|
||||
value: string;
|
||||
color: Color;
|
||||
changedColor?: Color;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const maxTime = 60; // seconds
|
||||
|
||||
const rgbGradients = {
|
||||
'Green': "rgba(0, 122, 0, 1), rgba(0, 185, 0, 0.9)",
|
||||
'Yellow': "rgba(255, 225, 0, 1), rgba(255, 255, 85, 0.9)",
|
||||
'Blue': "rgba(40, 40, 255, 1), rgba(125, 125, 255, 0.9)",
|
||||
'Red': "rgba(255, 0, 0, 1), rgba(255, 125, 125, 0.9)",
|
||||
'Black': "rgba(0, 0, 0, 1), rgba(0, 0, 0, 0.55)",
|
||||
const rgbGradients: {[k in Color]: string} = {
|
||||
Green: "rgba(0, 122, 0, 1), rgba(0, 185, 0, 0.9)",
|
||||
Yellow: "rgba(255, 225, 0, 1), rgba(255, 255, 85, 0.9)",
|
||||
Blue: "rgba(40, 40, 255, 1), rgba(125, 125, 255, 0.9)",
|
||||
Red: "rgba(255, 0, 0, 1), rgba(255, 125, 125, 0.9)",
|
||||
Black: "rgba(0, 0, 0, 1), rgba(0, 0, 0, 0.55)",
|
||||
};
|
||||
|
||||
const textColors = {
|
||||
'Green': "rgb(0, 128, 0)",
|
||||
'Yellow': "rgb(175, 165, 40)",
|
||||
'Blue': "rgb(75, 75, 255)",
|
||||
'Red': "rgb(255, 0, 0)",
|
||||
'Black': 'inherit',
|
||||
const textColors: {[k in Color]: string} = {
|
||||
Green: "rgb(0, 128, 0)",
|
||||
Yellow: "rgb(175, 165, 40)",
|
||||
Blue: "rgb(75, 75, 255)",
|
||||
Red: "rgb(255, 0, 0)",
|
||||
Black: 'inherit',
|
||||
};
|
||||
|
||||
const textShadow = 'text-shadow: 1px 0px black, -1px 0px black, 0px -1px black, 0px 1px black, 2px -2px black;';
|
||||
|
||||
/** @typedef {'Green' | 'Yellow' | 'Red' | 'Blue' | 'Black'} Color */
|
||||
/** @typedef {{value: string, color: Color, changedColor?: Color, name: string}} Card */
|
||||
|
||||
/**
|
||||
* @param {Card} card
|
||||
* @param {boolean} fullsize
|
||||
* @return {string}
|
||||
*/
|
||||
function cardHTML(card, fullsize) {
|
||||
function cardHTML(card: Card, fullsize: boolean) {
|
||||
let surface = card.value.replace(/[^A-Z0-9+]/g, "");
|
||||
let background = rgbGradients[card.color];
|
||||
const background = rgbGradients[card.color];
|
||||
if (surface === 'R') surface = '<i class="fa fa-refresh" aria-hidden="true"></i>';
|
||||
|
||||
return `<button class="button" style="font-size: 14px; font-weight: bold; color: white; ${textShadow} padding-bottom: 117px; text-align: left; height: 135px; width: ${fullsize ? '72' : '37'}px; border-radius: 10px 2px 2px 3px; color: white; background: ${card.color}; background: -webkit-linear-gradient(${background}); background: -o-linear-gradient(${background}); background: -moz-linear-gradient(${background}); background: linear-gradient(${background})" name=send value="/uno play ${card.name}" aria-label="${card.name}">${surface}</button>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Card[]}
|
||||
*/
|
||||
function createDeck() {
|
||||
/** @type {Color[]} */
|
||||
const colors = ['Red', 'Blue', 'Green', 'Yellow'];
|
||||
const values = ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'Reverse', 'Skip', '+2'];
|
||||
|
||||
let basic = /** @type {Card[]} */ ([]);
|
||||
const basic: Card[] = [];
|
||||
|
||||
for (const color of colors) {
|
||||
basic.push(...values.map(v => {
|
||||
/** @type {Card} */
|
||||
let c = {value: v, color: color, name: color + " " + v};
|
||||
const c: Card = {value: v, color: color as Color, name: color + " " + v};
|
||||
return c;
|
||||
}));
|
||||
}
|
||||
|
||||
return [
|
||||
// two copies of the basic stuff (total 96)
|
||||
...basic,
|
||||
...basic,
|
||||
...basic as Card[],
|
||||
...basic as Card[],
|
||||
// The four 0s
|
||||
...[0, 1, 2, 3].map(v => {
|
||||
/** @type {Card} */
|
||||
let c = {color: colors[v], value: '0', name: colors[v] + ' 0'};
|
||||
const c: Card = {color: colors[v] as Color, value: '0', name: colors[v] + ' 0'};
|
||||
return c;
|
||||
}),
|
||||
// Wild cards
|
||||
...[0, 1, 2, 3].map(v => {
|
||||
/** @type {Card} */
|
||||
let c = {color: 'Black', value: 'Wild', name: 'Wild'};
|
||||
const c: Card = {color: 'Black', value: 'Wild', name: 'Wild'};
|
||||
return c;
|
||||
}),
|
||||
// Wild +4 cards
|
||||
...[0, 1, 2, 3].map(v => {
|
||||
/** @type {Card} */
|
||||
let c = {color: 'Black', value: '+4', name: 'Wild +4'};
|
||||
const c: Card = {color: 'Black', value: '+4', name: 'Wild +4'};
|
||||
return c;
|
||||
}),
|
||||
]; // 108 cards
|
||||
}
|
||||
|
||||
class UnoGame extends Rooms.RoomGame {
|
||||
/**
|
||||
* @param {ChatRoom | GameRoom} room
|
||||
* @param {number} cap
|
||||
* @param {boolean} suppressMessages
|
||||
*/
|
||||
constructor(room, cap, suppressMessages) {
|
||||
playerTable: {[userid: string]: UnoGamePlayer};
|
||||
players: UnoGamePlayer[];
|
||||
playerCap: number;
|
||||
allowRenames: boolean;
|
||||
maxTime: number;
|
||||
timer: NodeJS.Timer | null;
|
||||
autostartTimer: NodeJS.Timer | null;
|
||||
state: string;
|
||||
currentPlayerid: ID;
|
||||
deck: Card[];
|
||||
discards: Card[];
|
||||
topCard: Card | null;
|
||||
awaitUno: string | null;
|
||||
unoId: ID | null;
|
||||
direction: number;
|
||||
suppressMessages: boolean;
|
||||
spectators: {[k: string]: number};
|
||||
isPlusFour: boolean;
|
||||
|
||||
constructor(room: ChatRoom | GameRoom, cap: number, suppressMessages: boolean) {
|
||||
super(room);
|
||||
|
||||
// TypeScript bug: no `T extends RoomGamePlayer`
|
||||
/** @type {{[userid: string]: UnoGamePlayer}} */
|
||||
this.playerTable = Object.create(null);
|
||||
// TypeScript bug: no `T extends RoomGamePlayer`
|
||||
/** @type {UnoGamePlayer[]} */
|
||||
this.players = [];
|
||||
|
||||
if (room.gameNumber) {
|
||||
|
|
@ -110,34 +112,23 @@ class UnoGame extends Rooms.RoomGame {
|
|||
room.gameNumber = 1;
|
||||
}
|
||||
|
||||
/** @type {number} */
|
||||
this.playerCap = cap;
|
||||
this.allowRenames = true;
|
||||
/** @type {number} */
|
||||
this.maxTime = maxTime;
|
||||
/** @type {NodeJS.Timer?} */
|
||||
this.timer = null;
|
||||
/** @type {NodeJS.Timer?} */
|
||||
this.autostartTimer = null;
|
||||
|
||||
this.gameid = /** @type {ID} */ ('uno');
|
||||
this.gameid = 'uno' as ID;
|
||||
this.title = 'UNO';
|
||||
|
||||
/** @type {string} */
|
||||
this.state = 'signups';
|
||||
|
||||
/** @type {string} */
|
||||
this.currentPlayerid = '';
|
||||
/** @type {Card[]} */
|
||||
this.deck = Dex.shuffle(createDeck());
|
||||
/** @type {Card[]} */
|
||||
this.discards = [];
|
||||
/** @type {Card?} */
|
||||
this.topCard = null;
|
||||
/** @type {string?} */
|
||||
this.awaitUno = null;
|
||||
/** @type {string?} */
|
||||
this.unoId = null;
|
||||
this.isPlusFour = false;
|
||||
|
||||
this.direction = 1;
|
||||
|
||||
|
|
@ -149,21 +140,28 @@ class UnoGame extends Rooms.RoomGame {
|
|||
|
||||
onUpdateConnection() {}
|
||||
|
||||
/**
|
||||
* @param {User} user
|
||||
* @param {Connection} connection
|
||||
*/
|
||||
onConnect(user, connection) {
|
||||
onConnect(user: User, connection: Connection) {
|
||||
if (this.state === 'signups') {
|
||||
connection.sendTo(this.room, `|uhtml|uno-${this.room.gameNumber}|<div class="broadcast-green"><p style="font-size: 14pt; text-align: center">A new game of <strong>UNO</strong> is starting!</p><p style="font-size: 9pt; text-align: center"><button name="send" value="/uno join">Join</button><br />Or use <strong>/uno join</strong> to join the game.</p>${(this.suppressMessages ? `<p style="font-size: 6pt; text-align: center">Game messages will be shown to only players. If you would like to spectate the game, use <strong>/uno spectate</strong></p>` : '')}</div>`);
|
||||
connection.sendTo(
|
||||
this.room,
|
||||
`|uhtml|uno-${this.room.gameNumber}|<div class="broadcast-green">` +
|
||||
`<p style="font-size: 14pt; text-align: center">A new game of <strong>UNO</strong> is starting!</p>` +
|
||||
`<p style="font-size: 9pt; text-align: center"><button name="send" value="/uno join">Join</button>` +
|
||||
`<br />Or use <strong>/uno join</strong> to join the game.</p>` +
|
||||
`${this.suppressMessages ?
|
||||
`<p style="font-size: 6pt; text-align: center">Game messages will be shown to only players. ` +
|
||||
`If you would like to spectate the game, use <strong>/uno spectate</strong></p>` : ''}</div>`
|
||||
);
|
||||
} else if (this.onSendHand(user) === false) {
|
||||
connection.sendTo(this.room, `|uhtml|uno-${this.room.gameNumber}|<div class="infobox"><p>A UNO game is currently in progress.</p>${(this.suppressMessages ? `<p style="font-size: 6pt">Game messages will be shown to only players. If you would like to spectate the game, use <strong>/uno spectate</strong></p>` : '')}</div>`);
|
||||
connection.sendTo(
|
||||
this.room,
|
||||
`|uhtml|uno-${this.room.gameNumber}|<div class="infobox"><p>A UNO game is currently in progress.</p>` +
|
||||
`${(this.suppressMessages ? `<p style="font-size: 6pt">Game messages will be shown to only players. ` +
|
||||
`If you would like to spectate the game, use <strong>/uno spectate</strong></p>` : '')}</div>`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {false | void}
|
||||
*/
|
||||
onStart() {
|
||||
if (this.playerCount < 2) return false;
|
||||
if (this.autostartTimer) clearTimeout(this.autostartTimer);
|
||||
|
|
@ -173,7 +171,7 @@ class UnoGame extends Rooms.RoomGame {
|
|||
this.onNextPlayer(); // determines the first player
|
||||
|
||||
// give cards to the players
|
||||
for (let i in this.playerTable) {
|
||||
for (const i in this.playerTable) {
|
||||
this.playerTable[i].hand.push(...this.drawCard(7));
|
||||
}
|
||||
|
||||
|
|
@ -189,11 +187,7 @@ class UnoGame extends Rooms.RoomGame {
|
|||
this.nextTurn(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {User} user
|
||||
* @return {boolean}
|
||||
*/
|
||||
joinGame(user) {
|
||||
joinGame(user: User) {
|
||||
if (this.state === 'signups' && this.addPlayer(user)) {
|
||||
this.sendToRoom(`${user.name} has joined the game of UNO.`);
|
||||
return true;
|
||||
|
|
@ -201,11 +195,7 @@ class UnoGame extends Rooms.RoomGame {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {User} user
|
||||
* @return {boolean}
|
||||
*/
|
||||
leaveGame(user) {
|
||||
leaveGame(user: User) {
|
||||
if (!(user.id in this.playerTable)) return false;
|
||||
if (this.state === 'signups' && this.removePlayer(user)) {
|
||||
this.sendToRoom(`${user.name} has left the game of UNO.`);
|
||||
|
|
@ -216,21 +206,12 @@ class UnoGame extends Rooms.RoomGame {
|
|||
|
||||
/**
|
||||
* Overwrite the default makePlayer so it makes an UnoGamePlayer instead.
|
||||
* @param {User} user
|
||||
* @return {UnoGamePlayer}
|
||||
*/
|
||||
makePlayer(user) {
|
||||
makePlayer(user: User) {
|
||||
return new UnoGamePlayer(user, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {User} user
|
||||
* @param {string} oldUserid
|
||||
* @param {boolean} isJoining
|
||||
* @param {boolean} isForceRenamed
|
||||
* @return {false | void}
|
||||
*/
|
||||
onRename(user, oldUserid, isJoining, isForceRenamed) {
|
||||
onRename(user: User, oldUserid: ID, isJoining: boolean, isForceRenamed: boolean) {
|
||||
if (!(oldUserid in this.playerTable) || user.id === oldUserid) return false;
|
||||
if (!user.named && !isForceRenamed) {
|
||||
user.games.delete(this.roomid);
|
||||
|
|
@ -238,7 +219,8 @@ class UnoGame extends Rooms.RoomGame {
|
|||
return; // dont set users to their guest accounts.
|
||||
}
|
||||
this.playerTable[user.id] = this.playerTable[oldUserid];
|
||||
if (user.id !== oldUserid) delete this.playerTable[oldUserid]; // only run if it's a rename that involves a change of userid
|
||||
// only run if it's a rename that involves a change of userid
|
||||
if (user.id !== oldUserid) delete this.playerTable[oldUserid];
|
||||
|
||||
// update the user's name information
|
||||
this.playerTable[user.id].name = user.name;
|
||||
|
|
@ -248,14 +230,10 @@ class UnoGame extends Rooms.RoomGame {
|
|||
if (this.currentPlayerid === oldUserid) this.currentPlayerid = user.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} userid
|
||||
* @return {string | false}
|
||||
*/
|
||||
eliminate(userid) {
|
||||
eliminate(userid: ID) {
|
||||
if (!(userid in this.playerTable)) return false;
|
||||
|
||||
let name = this.playerTable[userid].name;
|
||||
const name = this.playerTable[userid].name;
|
||||
|
||||
if (this.playerCount === 2) {
|
||||
this.removePlayer(this.playerTable[userid]);
|
||||
|
|
@ -286,46 +264,39 @@ class UnoGame extends Rooms.RoomGame {
|
|||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} msg
|
||||
* @param {boolean} [overrideSuppress]
|
||||
*/
|
||||
sendToRoom(msg, overrideSuppress = false) {
|
||||
sendToRoom(msg: string, overrideSuppress = false) {
|
||||
if (!this.suppressMessages || overrideSuppress) {
|
||||
this.room.add(msg).update();
|
||||
} else {
|
||||
// send to the players first
|
||||
for (let i in this.playerTable) {
|
||||
for (const i in this.playerTable) {
|
||||
this.playerTable[i].sendRoom(msg);
|
||||
}
|
||||
|
||||
// send to spectators
|
||||
for (let i in this.spectators) {
|
||||
for (const i in this.spectators) {
|
||||
if (i in this.playerTable) continue; // don't double send to users already in the game.
|
||||
let user = Users.getExact(i);
|
||||
const user = Users.getExact(i);
|
||||
if (user) user.sendTo(this.roomid, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} [showCards]
|
||||
* @return {string[]}
|
||||
*/
|
||||
getPlayers(showCards) {
|
||||
getPlayers(showCards?: boolean): string[] {
|
||||
let playerList = Object.keys(this.playerTable);
|
||||
if (!showCards) {
|
||||
return playerList.sort().map(id => Chat.escapeHTML(this.playerTable[id].name));
|
||||
}
|
||||
if (this.direction === -1) playerList = playerList.reverse();
|
||||
return playerList.map(id => `${(this.currentPlayerid === id ? '<strong>' : '')}${Chat.escapeHTML(this.playerTable[id].name)} (${this.playerTable[id].hand.length}) ${(this.currentPlayerid === id ? '</strong>' : "")}`);
|
||||
return playerList.map(id =>
|
||||
`${(this.currentPlayerid === id ? '<strong>' : '')}` +
|
||||
`${Chat.escapeHTML(this.playerTable[id].name)} (${this.playerTable[id].hand.length})` +
|
||||
`${(this.currentPlayerid === id ? '</strong>' : "")}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
onAwaitUno() {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise(resolve => {
|
||||
if (!this.awaitUno) return resolve();
|
||||
|
||||
this.state = "uno";
|
||||
|
|
@ -339,16 +310,13 @@ class UnoGame extends Rooms.RoomGame {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} [starting]
|
||||
*/
|
||||
nextTurn(starting) {
|
||||
this.onAwaitUno()
|
||||
.then(() => {
|
||||
nextTurn(starting?: boolean) {
|
||||
void this.onAwaitUno()
|
||||
.then(x => {
|
||||
if (!starting) this.onNextPlayer();
|
||||
|
||||
if (this.timer) clearTimeout(this.timer);
|
||||
let player = this.playerTable[this.currentPlayerid];
|
||||
const player = this.playerTable[this.currentPlayerid];
|
||||
|
||||
this.sendToRoom(`|c:|${(Math.floor(Date.now() / 1000))}|~|${player.name}'s turn.`);
|
||||
this.state = 'play';
|
||||
|
|
@ -365,32 +333,25 @@ class UnoGame extends Rooms.RoomGame {
|
|||
onNextPlayer() {
|
||||
// if none is set
|
||||
if (!this.currentPlayerid) {
|
||||
let userList = Object.keys(this.playerTable);
|
||||
this.currentPlayerid = userList[Math.floor(this.playerCount * Math.random())];
|
||||
const userList = Object.keys(this.playerTable);
|
||||
this.currentPlayerid = userList[Math.floor(this.playerCount * Math.random())] as ID;
|
||||
}
|
||||
|
||||
this.currentPlayerid = this.getNextPlayer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
getNextPlayer() {
|
||||
let userList = Object.keys(this.playerTable);
|
||||
const userList = Object.keys(this.playerTable);
|
||||
|
||||
let player = userList[(userList.indexOf(this.currentPlayerid) + this.direction)];
|
||||
let player: ID = userList[(userList.indexOf(this.currentPlayerid) + this.direction)] as ID;
|
||||
|
||||
if (!player) {
|
||||
player = this.direction === 1 ? userList[0] : userList[this.playerCount - 1];
|
||||
player = toID(this.direction === 1 ? userList[0] : userList[this.playerCount - 1]);
|
||||
}
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {UnoGamePlayer} player
|
||||
* @return {boolean | void}
|
||||
*/
|
||||
onDraw(player) {
|
||||
onDraw(player: UnoGamePlayer) {
|
||||
if (this.currentPlayerid !== player.id || this.state !== 'play') return false;
|
||||
if (player.cardLock) return true;
|
||||
|
||||
|
|
@ -398,20 +359,15 @@ class UnoGame extends Rooms.RoomGame {
|
|||
|
||||
this.sendToRoom(`${player.name} has drawn a card.`);
|
||||
|
||||
let card = this.onDrawCard(player, 1);
|
||||
const card = this.onDrawCard(player, 1);
|
||||
player.sendDisplay();
|
||||
player.cardLock = card[0].name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {UnoGamePlayer} player
|
||||
* @param {string} cardName
|
||||
* @return {false | string | void}
|
||||
*/
|
||||
onPlay(player, cardName) {
|
||||
onPlay(player: UnoGamePlayer, cardName: string) {
|
||||
if (this.currentPlayerid !== player.id || this.state !== 'play') return false;
|
||||
|
||||
let card = player.hasCard(cardName);
|
||||
const card = player.hasCard(cardName);
|
||||
if (!card) return "You do not have that card.";
|
||||
|
||||
// check for legal play
|
||||
|
|
@ -420,8 +376,18 @@ class UnoGame extends Rooms.RoomGame {
|
|||
throw new Error(`No top card in the discard pile.`);
|
||||
}
|
||||
if (player.cardLock && player.cardLock !== cardName) return `You can only play ${player.cardLock} after drawing.`;
|
||||
if (card.color !== 'Black' && card.color !== (this.topCard.changedColor || this.topCard.color) && card.value !== this.topCard.value) return `You cannot play this card - you can only play: Wild cards, ${(this.topCard.changedColor ? 'and' : '')} ${(this.topCard.changedColor || this.topCard.color)} cards${this.topCard.changedColor ? "" : ` and ${this.topCard.value}'s`}.`;
|
||||
if (card.value === '+4' && !player.canPlayWildFour()) return "You cannot play Wild +4 when you still have a card with the same color as the top card.";
|
||||
if (
|
||||
card.color !== 'Black' &&
|
||||
card.color !== (this.topCard.changedColor || this.topCard.color) &&
|
||||
card.value !== this.topCard.value
|
||||
) {
|
||||
return `You cannot play this card - you can only play: Wild cards, ` +
|
||||
`${(this.topCard.changedColor ? 'and' : '')} ${(this.topCard.changedColor || this.topCard.color)} ` +
|
||||
`cards${this.topCard.changedColor ? "" : ` and ${this.topCard.value}'s`}.`;
|
||||
}
|
||||
if (card.value === '+4' && !player.canPlayWildFour()) {
|
||||
return "You cannot play Wild +4 when you still have a card with the same color as the top card.";
|
||||
}
|
||||
|
||||
if (this.timer) clearTimeout(this.timer); // reset the autodq timer.
|
||||
|
||||
|
|
@ -435,7 +401,7 @@ class UnoGame extends Rooms.RoomGame {
|
|||
// update the unoId here, so when the display is sent to the player when the play is made
|
||||
if (player.hand.length === 1) {
|
||||
this.awaitUno = player.id;
|
||||
this.unoId = Math.floor(Math.random() * 100).toString();
|
||||
this.unoId = Math.floor(Math.random() * 100).toString() as ID;
|
||||
}
|
||||
|
||||
player.sendDisplay(); // update display without the card in it for purposes such as choosing colors
|
||||
|
|
@ -453,18 +419,15 @@ class UnoGame extends Rooms.RoomGame {
|
|||
if (this.state === 'play') this.nextTurn();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
* @param {boolean} [initialize]
|
||||
*/
|
||||
onRunEffect(value, initialize) {
|
||||
onRunEffect(value: string, initialize?: boolean) {
|
||||
const colorDisplay = `|uhtml|uno-hand|<table style="width: 100%; border: 1px solid black"><tr><td style="width: 50%"><button style="width: 100%; background-color: red; border: 2px solid rgba(0 , 0 , 0 , 0.59); border-radius: 5px; padding: 5px" name=send value="/uno color Red">Red</button></td><td style="width: 50%"><button style="width: 100%; background-color: blue; border: 2px solid rgba(0 , 0 , 0 , 0.59); border-radius: 5px; color: white; padding: 5px" name=send value="/uno color Blue">Blue</button></td></tr><tr><td style="width: 50%"><button style="width: 100%; background-color: green; border: 2px solid rgba(0 , 0 , 0 , 0.59); border-radius: 5px; padding: 5px" name=send value="/uno color Green">Green</button></td><td style="width: 50%"><button style="width: 100%; background-color: yellow; border: 2px solid rgba(0 , 0 , 0 , 0.59); border-radius: 5px; padding: 5px" name=send value="/uno color Yellow">Yellow</button></td></tr></table>`;
|
||||
|
||||
switch (value) {
|
||||
case 'Reverse':
|
||||
this.direction *= -1;
|
||||
this.sendToRoom("The direction of the game has changed.");
|
||||
if (!initialize && this.playerCount === 2) this.onNextPlayer(); // in 2 player games, reverse sends the turn back to the player.
|
||||
// in 2 player games, reverse sends the turn back to the player.
|
||||
if (!initialize && this.playerCount === 2) this.onNextPlayer();
|
||||
break;
|
||||
case 'Skip':
|
||||
this.onNextPlayer();
|
||||
|
|
@ -479,7 +442,7 @@ class UnoGame extends Rooms.RoomGame {
|
|||
this.playerTable[this.currentPlayerid].sendRoom(colorDisplay);
|
||||
this.state = 'color';
|
||||
// apply to the next in line, since the current player still has to choose the color
|
||||
let next = this.getNextPlayer();
|
||||
const next = this.getNextPlayer();
|
||||
this.sendToRoom(this.playerTable[next].name + " has been forced to draw 4 cards.");
|
||||
this.onDrawCard(this.playerTable[next], 4);
|
||||
this.isPlusFour = true;
|
||||
|
|
@ -500,13 +463,14 @@ class UnoGame extends Rooms.RoomGame {
|
|||
if (initialize) this.onNextPlayer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {UnoGamePlayer} player
|
||||
* @param {Color} color
|
||||
* @return {false | void}
|
||||
*/
|
||||
onSelectColor(player, color) {
|
||||
if (!['Red', 'Blue', 'Green', 'Yellow'].includes(color) || player.id !== this.currentPlayerid || this.state !== 'color') return false;
|
||||
onSelectColor(player: UnoGamePlayer, color: Color) {
|
||||
if (
|
||||
!['Red', 'Blue', 'Green', 'Yellow'].includes(color) ||
|
||||
player.id !== this.currentPlayerid ||
|
||||
this.state !== 'color'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (!this.topCard) {
|
||||
// should never happen
|
||||
throw new Error(`No top card in the discard pile.`);
|
||||
|
|
@ -526,33 +490,28 @@ class UnoGame extends Rooms.RoomGame {
|
|||
this.nextTurn();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {UnoGamePlayer} player
|
||||
* @param {number} count
|
||||
* @return {Card[]}
|
||||
*/
|
||||
onDrawCard(player, count) {
|
||||
onDrawCard(player: UnoGamePlayer, count: number) {
|
||||
if (typeof count === 'string') count = parseInt(count);
|
||||
if (!count || isNaN(count) || count < 1) count = 1;
|
||||
let drawnCards = /** @type {Card[]} */ (this.drawCard(count));
|
||||
const drawnCards = this.drawCard(count);
|
||||
|
||||
player.hand.push(...drawnCards);
|
||||
player.sendRoom(`|raw|You have drawn the following card${Chat.plural(drawnCards)}: ${drawnCards.map(card => `<span style="color: ${textColors[card.color]}">${card.name}</span>`).join(', ')}.`);
|
||||
player.sendRoom(
|
||||
`|raw|You have drawn the following card${Chat.plural(drawnCards)}: ` +
|
||||
`${drawnCards.map(card => `<span style="color: ${textColors[card.color]}">${card.name}</span>`).join(', ')}.`
|
||||
);
|
||||
return drawnCards;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} count
|
||||
* @return {Card[]}
|
||||
*/
|
||||
drawCard(count) {
|
||||
drawCard(count: number) {
|
||||
if (typeof count === 'string') count = parseInt(count);
|
||||
if (!count || isNaN(count) || count < 1) count = 1;
|
||||
let drawnCards = /** @type {Card[]} */ ([]);
|
||||
const drawnCards: Card[] = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (!this.deck.length) {
|
||||
this.deck = this.discards.length ? Dex.shuffle(this.discards) : Dex.shuffle(createDeck()); // shuffle the cards back into the deck, or if there are no discards, add another deck into the game.
|
||||
// shuffle the cards back into the deck, or if there are no discards, add another deck into the game.
|
||||
this.deck = this.discards.length ? Dex.shuffle(this.discards) : Dex.shuffle(createDeck());
|
||||
this.discards = []; // clear discard pile
|
||||
}
|
||||
drawnCards.push(this.deck[this.deck.length - 1]);
|
||||
|
|
@ -561,12 +520,7 @@ class UnoGame extends Rooms.RoomGame {
|
|||
return drawnCards;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {UnoGamePlayer} player
|
||||
* @param {string} unoId
|
||||
* @return {false | void}
|
||||
*/
|
||||
onUno(player, unoId) {
|
||||
onUno(player: UnoGamePlayer, unoId: ID) {
|
||||
// uno id makes spamming /uno uno impossible
|
||||
if (this.unoId !== unoId || player.id !== this.awaitUno) return false;
|
||||
this.sendToRoom(Chat.html`|raw|<strong>UNO!</strong> ${player.name} is down to their last card!`);
|
||||
|
|
@ -586,21 +540,17 @@ class UnoGame extends Rooms.RoomGame {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {User} user
|
||||
* @return {false | void}
|
||||
*/
|
||||
onSendHand(user) {
|
||||
onSendHand(user: User) {
|
||||
if (!(user.id in this.playerTable) || this.state === 'signups') return false;
|
||||
|
||||
this.playerTable[user.id].sendDisplay();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {UnoGamePlayer} player
|
||||
*/
|
||||
onWin(player) {
|
||||
this.sendToRoom(Chat.html`|raw|<div class="broadcast-green">Congratulations to ${player.name} for winning the game of UNO!</div>`, true);
|
||||
onWin(player: UnoGamePlayer) {
|
||||
this.sendToRoom(
|
||||
Chat.html`|raw|<div class="broadcast-green">Congratulations to ${player.name} for winning the game of UNO!</div>`,
|
||||
true
|
||||
);
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
|
|
@ -610,7 +560,7 @@ class UnoGame extends Rooms.RoomGame {
|
|||
this.sendToRoom(`|uhtmlchange|uno-${this.room.gameNumber}|<div class="infobox">The game of UNO has ended.</div>`, true);
|
||||
|
||||
// deallocate games for each player.
|
||||
for (let i in this.playerTable) {
|
||||
for (const i in this.playerTable) {
|
||||
this.playerTable[i].destroy();
|
||||
}
|
||||
delete this.room.game;
|
||||
|
|
@ -618,43 +568,33 @@ class UnoGame extends Rooms.RoomGame {
|
|||
}
|
||||
|
||||
class UnoGamePlayer extends Rooms.RoomGamePlayer {
|
||||
/**
|
||||
* @param {User} user
|
||||
* @param {UnoGame} game
|
||||
*/
|
||||
constructor(user, game) {
|
||||
hand: Card[];
|
||||
game: UnoGame;
|
||||
cardLock: string | null;
|
||||
|
||||
constructor(user: User, game: UnoGame) {
|
||||
super(user, game);
|
||||
this.hand = /** @type {Card[]} */ ([]);
|
||||
this.hand = [];
|
||||
this.game = game;
|
||||
/** @type {string?} */
|
||||
this.cardLock = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
canPlayWildFour() {
|
||||
if (!this.game.topCard) {
|
||||
// should never happen
|
||||
throw new Error(`No top card in the discard pile.`);
|
||||
}
|
||||
let color = (this.game.topCard.changedColor || this.game.topCard.color);
|
||||
const color = (this.game.topCard.changedColor || this.game.topCard.color);
|
||||
|
||||
if (this.hand.some(c => c.color === color)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} cardName
|
||||
*/
|
||||
hasCard(cardName) {
|
||||
hasCard(cardName: string) {
|
||||
return this.hand.find(card => card.name === cardName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} cardName
|
||||
*/
|
||||
removeCard(cardName) {
|
||||
removeCard(cardName: string) {
|
||||
for (const [i, card] of this.hand.entries()) {
|
||||
if (card.name === cardName) {
|
||||
this.hand.splice(i, 1);
|
||||
|
|
@ -663,26 +603,23 @@ class UnoGamePlayer extends Rooms.RoomGamePlayer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string[]}
|
||||
*/
|
||||
buildHand() {
|
||||
return this.hand.sort((a, b) => a.color.localeCompare(b.color) || a.value.localeCompare(b.value))
|
||||
.map((c, i) => cardHTML(c, i === this.hand.length - 1));
|
||||
}
|
||||
|
||||
sendDisplay() {
|
||||
let hand = this.buildHand().join('');
|
||||
let players = `<p><strong>Players (${this.game.playerCount}):</strong></p>${this.game.getPlayers(true).join('<br />')}`;
|
||||
let draw = '<button class="button" style="width: 45%; background: rgba(0, 0, 255, 0.05)" name=send value="/uno draw">Draw a card!</button>';
|
||||
let pass = '<button class="button" style=" width: 45%; background: rgba(255, 0, 0, 0.05)" name=send value="/uno pass">Pass!</button>';
|
||||
let uno = `<button class="button" style=" width: 90%; background: rgba(0, 255, 0, 0.05); height: 30px; margin-top: 2px;" name=send value="/uno uno ${this.game.unoId || '0'}">UNO!</button>`;
|
||||
const hand = this.buildHand().join('');
|
||||
const players = `<p><strong>Players (${this.game.playerCount}):</strong></p>${this.game.getPlayers(true).join('<br />')}`;
|
||||
const draw = '<button class="button" style="width: 45%; background: rgba(0, 0, 255, 0.05)" name=send value="/uno draw">Draw a card!</button>';
|
||||
const pass = '<button class="button" style=" width: 45%; background: rgba(255, 0, 0, 0.05)" name=send value="/uno pass">Pass!</button>';
|
||||
const uno = `<button class="button" style=" width: 90%; background: rgba(0, 255, 0, 0.05); height: 30px; margin-top: 2px;" name=send value="/uno uno ${this.game.unoId || '0'}">UNO!</button>`;
|
||||
|
||||
if (!this.game.topCard) {
|
||||
// should never happen
|
||||
throw new Error(`No top card in the discard pile.`);
|
||||
}
|
||||
let top = `<strong>Top Card: <span style="color: ${textColors[this.game.topCard.changedColor || this.game.topCard.color]}">${this.game.topCard.name}</span></strong>`;
|
||||
const top = `<strong>Top Card: <span style="color: ${textColors[this.game.topCard.changedColor || this.game.topCard.color]}">${this.game.topCard.name}</span></strong>`;
|
||||
|
||||
// clear previous display and show new display
|
||||
this.sendRoom("|uhtmlchange|uno-hand|");
|
||||
|
|
@ -694,8 +631,7 @@ class UnoGamePlayer extends Rooms.RoomGamePlayer {
|
|||
}
|
||||
}
|
||||
|
||||
/** @type {ChatCommands} */
|
||||
const commands = {
|
||||
export const commands: ChatCommands = {
|
||||
uno: {
|
||||
// roomowner commands
|
||||
off: 'disable',
|
||||
|
|
@ -738,7 +674,7 @@ const commands = {
|
|||
if (room.unoDisabled) return this.errorReply("UNO is currently disabled for this room.");
|
||||
if (room.game) return this.errorReply("There is already a game in progress in this room.");
|
||||
|
||||
let suppressMessages = cmd.includes('private') || !(cmd.includes('public') || room.roomid === 'gamecorner');
|
||||
const suppressMessages = cmd.includes('private') || !(cmd.includes('public') || room.roomid === 'gamecorner');
|
||||
|
||||
let cap = parseInt(target);
|
||||
if (isNaN(cap)) cap = 6;
|
||||
|
|
@ -750,8 +686,10 @@ const commands = {
|
|||
|
||||
start(target, room, user) {
|
||||
if (!this.can('minigame', null, room)) return;
|
||||
const game = /** @type {UnoGame} */ (room.game);
|
||||
if (!game || game.gameid !== 'uno' || game.state !== 'signups') return this.errorReply("There is no UNO game in signups phase in this room.");
|
||||
const game = room.game as UnoGame;
|
||||
if (!game || game.gameid !== 'uno' || game.state !== 'signups') {
|
||||
return this.errorReply("There is no UNO game in signups phase in this room.");
|
||||
}
|
||||
if (game.onStart()) {
|
||||
this.privateModAction(`(The game of UNO was started by ${user.name}.)`);
|
||||
this.modlog('UNO START');
|
||||
|
|
@ -770,9 +708,9 @@ const commands = {
|
|||
|
||||
timer(target, room, user) {
|
||||
if (!this.can('minigame', null, room)) return;
|
||||
const game = /** @type {UnoGame} */ (room.game);
|
||||
const game = room.game as UnoGame;
|
||||
if (!game || game.gameid !== 'uno') return this.errorReply("There is no UNO game going on in this room.");
|
||||
let amount = parseInt(target);
|
||||
const amount = parseInt(target);
|
||||
if (!amount || amount < 5 || amount > 300) return this.errorReply("The amount must be a number between 5 and 300.");
|
||||
|
||||
game.maxTime = amount;
|
||||
|
|
@ -786,7 +724,7 @@ const commands = {
|
|||
|
||||
autostart(target, room, user) {
|
||||
if (!this.can('minigame', null, room)) return;
|
||||
const game = /** @type {UnoGame} */ (room.game);
|
||||
const game = room.game as UnoGame;
|
||||
if (!game || game.gameid !== 'uno') return this.errorReply("There is no UNO game going on in this room right now.");
|
||||
if (toID(target) === 'off') {
|
||||
if (!game.autostartTimer) return this.errorReply("There is no autostart timer running on.");
|
||||
|
|
@ -795,7 +733,9 @@ const commands = {
|
|||
return;
|
||||
}
|
||||
const amount = parseInt(target);
|
||||
if (!amount || amount < 30 || amount > 600) return this.errorReply("The amount must be a number between 30 and 600 seconds.");
|
||||
if (!amount || amount < 30 || amount > 600) {
|
||||
return this.errorReply("The amount must be a number between 30 and 600 seconds.");
|
||||
}
|
||||
if (game.state !== 'signups') return this.errorReply("The game of UNO has already started.");
|
||||
if (game.autostartTimer) clearTimeout(game.autostartTimer);
|
||||
game.autostartTimer = setTimeout(() => {
|
||||
|
|
@ -807,10 +747,10 @@ const commands = {
|
|||
dq: 'disqualify',
|
||||
disqualify(target, room, user) {
|
||||
if (!this.can('minigame', null, room)) return;
|
||||
const game = /** @type {UnoGame} */ (room.game);
|
||||
const game = room.game as UnoGame;
|
||||
if (!game || game.gameid !== 'uno') return this.errorReply("There is no UNO game going on in this room right now.");
|
||||
|
||||
let disqualified = game.eliminate(toID(target));
|
||||
const disqualified = game.eliminate(toID(target));
|
||||
if (disqualified === false) return this.errorReply(`Unable to disqualify ${target}.`);
|
||||
this.privateModAction(`(${user.name} has disqualified ${disqualified} from the UNO game.)`);
|
||||
this.modlog('UNO DQ', toID(target));
|
||||
|
|
@ -822,7 +762,7 @@ const commands = {
|
|||
// TypeScript doesn't like 'join' being defined as a function
|
||||
join: 'unojoin',
|
||||
unojoin(target, room, user) {
|
||||
const game = /** @type {UnoGame} */ (room.game);
|
||||
const game = room.game as UnoGame;
|
||||
if (!game || game.gameid !== 'uno') return this.errorReply("There is no UNO game going on in this room right now.");
|
||||
if (!this.canTalk()) return false;
|
||||
if (!game.joinGame(user)) return this.errorReply("Unable to join the game.");
|
||||
|
|
@ -832,38 +772,35 @@ const commands = {
|
|||
|
||||
l: 'leave',
|
||||
leave(target, room, user) {
|
||||
const game = /** @type {UnoGame} */ (room.game);
|
||||
const game = room.game as UnoGame;
|
||||
if (!game || game.gameid !== 'uno') return this.errorReply("There is no UNO game going on in this room right now.");
|
||||
if (!game.leaveGame(user)) return this.errorReply("Unable to leave the game.");
|
||||
return this.sendReply("You have left the game of UNO.");
|
||||
},
|
||||
|
||||
play(target, room, user) {
|
||||
const game = /** @type {UnoGame} */ (room.game);
|
||||
const game = room.game as UnoGame;
|
||||
if (!game || game.gameid !== 'uno') return this.errorReply("There is no UNO game going on in this room right now.");
|
||||
/** @type {UnoGamePlayer | undefined} */
|
||||
let player = game.playerTable[user.id];
|
||||
const player: UnoGamePlayer | undefined = game.playerTable[user.id];
|
||||
if (!player) return this.errorReply(`You are not in the game of UNO.`);
|
||||
let error = game.onPlay(player, target);
|
||||
const error = game.onPlay(player, target);
|
||||
if (error) this.errorReply(error);
|
||||
},
|
||||
|
||||
draw(target, room, user) {
|
||||
const game = /** @type {UnoGame} */ (room.game);
|
||||
const game = room.game as UnoGame;
|
||||
if (!game || game.gameid !== 'uno') return this.errorReply("There is no UNO game going on in this room right now.");
|
||||
/** @type {UnoGamePlayer | undefined} */
|
||||
let player = game.playerTable[user.id];
|
||||
const player: UnoGamePlayer | undefined = game.playerTable[user.id];
|
||||
if (!player) return this.errorReply(`You are not in the game of UNO.`);
|
||||
let error = game.onDraw(player);
|
||||
const error = game.onDraw(player);
|
||||
if (error) return this.errorReply("You have already drawn a card this turn.");
|
||||
},
|
||||
|
||||
pass(target, room, user) {
|
||||
const game = /** @type {UnoGame} */ (room.game);
|
||||
const game = room.game as UnoGame;
|
||||
if (!game || game.gameid !== 'uno') return this.errorReply("There is no UNO game going on in this room right now.");
|
||||
if (game.currentPlayerid !== user.id) return this.errorReply("It is currently not your turn.");
|
||||
/** @type {UnoGamePlayer | undefined} */
|
||||
let player = game.playerTable[user.id];
|
||||
const player: UnoGamePlayer | undefined = game.playerTable[user.id];
|
||||
if (!player) return this.errorReply(`You are not in the game of UNO.`);
|
||||
if (!player.cardLock) return this.errorReply("You cannot pass until you draw a card.");
|
||||
if (game.state === 'color') return this.errorReply("You cannot pass until you choose a color.");
|
||||
|
|
@ -873,13 +810,11 @@ const commands = {
|
|||
},
|
||||
|
||||
color(target, room, user) {
|
||||
const game = /** @type {UnoGame} */ (room.game);
|
||||
const game = room.game as UnoGame;
|
||||
if (!game || game.gameid !== 'uno') return false;
|
||||
/** @type {UnoGamePlayer | undefined} */
|
||||
let player = game.playerTable[user.id];
|
||||
const player: UnoGamePlayer | undefined = game.playerTable[user.id];
|
||||
if (!player) return this.errorReply(`You are not in the game of UNO.`);
|
||||
/** @type {Color} */
|
||||
let color;
|
||||
let color: Color;
|
||||
if (target === 'Red' || target === 'Green' || target === 'Blue' || target === 'Yellow' || target === 'Black') {
|
||||
color = target;
|
||||
} else {
|
||||
|
|
@ -889,18 +824,17 @@ const commands = {
|
|||
},
|
||||
|
||||
uno(target, room, user) {
|
||||
const game = /** @type {UnoGame} */ (room.game);
|
||||
const game = room.game as UnoGame;
|
||||
if (!game || game.gameid !== 'uno') return false;
|
||||
/** @type {UnoGamePlayer | undefined} */
|
||||
let player = game.playerTable[user.id];
|
||||
const player: UnoGamePlayer | undefined = game.playerTable[user.id];
|
||||
if (!player) return this.errorReply(`You are not in the game of UNO.`);
|
||||
game.onUno(player, target);
|
||||
game.onUno(player, toID(target));
|
||||
},
|
||||
|
||||
// information commands
|
||||
'': 'hand',
|
||||
hand(target, room, user) {
|
||||
const game = /** @type {UnoGame} */ (room.game);
|
||||
const game = room.game as UnoGame;
|
||||
if (!game || game.gameid !== 'uno') return this.parse("/help uno");
|
||||
game.onSendHand(user);
|
||||
},
|
||||
|
|
@ -909,7 +843,7 @@ const commands = {
|
|||
users: 'getusers',
|
||||
getplayers: 'getusers',
|
||||
getusers(target, room, user) {
|
||||
const game = /** @type {UnoGame} */ (room.game);
|
||||
const game = room.game as UnoGame;
|
||||
if (!game || game.gameid !== 'uno') return this.errorReply("There is no UNO game going on in this room right now.");
|
||||
if (!this.runBroadcast()) return false;
|
||||
this.sendReplyBox(`<strong>Players (${game.playerCount})</strong>:<br />${game.getPlayers().join(', ')}`);
|
||||
|
|
@ -921,15 +855,19 @@ const commands = {
|
|||
|
||||
// suppression commands
|
||||
suppress(target, room, user) {
|
||||
const game = /** @type {UnoGame} */ (room.game);
|
||||
const game = room.game as UnoGame;
|
||||
if (!game || game.gameid !== 'uno') return this.errorReply("There is no UNO game going on in this room right now.");
|
||||
if (!this.can('minigame', null, room)) return;
|
||||
|
||||
target = toID(target);
|
||||
let state = target === 'on' ? true : target === 'off' ? false : undefined;
|
||||
const state = target === 'on' ? true : target === 'off' ? false : undefined;
|
||||
|
||||
if (state === undefined) return this.sendReply(`Suppression of UNO game messages is currently ${(game.suppressMessages ? 'on' : 'off')}.`);
|
||||
if (state === game.suppressMessages) return this.errorReply(`Suppression of UNO game messages is already ${(game.suppressMessages ? 'on' : 'off')}.`);
|
||||
if (state === undefined) {
|
||||
return this.sendReply(`Suppression of UNO game messages is currently ${(game.suppressMessages ? 'on' : 'off')}.`);
|
||||
}
|
||||
if (state === game.suppressMessages) {
|
||||
return this.errorReply(`Suppression of UNO game messages is already ${(game.suppressMessages ? 'on' : 'off')}.`);
|
||||
}
|
||||
|
||||
game.suppressMessages = state;
|
||||
|
||||
|
|
@ -938,7 +876,7 @@ const commands = {
|
|||
},
|
||||
|
||||
spectate(target, room, user) {
|
||||
const game = /** @type {UnoGame} */ (room.game);
|
||||
const game = room.game as UnoGame;
|
||||
if (!game || game.gameid !== 'uno') return this.errorReply("There is no UNO game going on in this room right now.");
|
||||
|
||||
if (!game.suppressMessages) return this.errorReply("The current UNO game is not suppressing messages.");
|
||||
|
|
@ -949,7 +887,7 @@ const commands = {
|
|||
},
|
||||
|
||||
unspectate(target, room, user) {
|
||||
const game = /** @type {UnoGame} */ (room.game);
|
||||
const game = room.game as UnoGame;
|
||||
if (!game || game.gameid !== 'uno') return this.errorReply("There is no UNO game going on in this room right now.");
|
||||
|
||||
if (!game.suppressMessages) return this.errorReply("The current UNO game is not suppressing messages.");
|
||||
|
|
@ -973,8 +911,8 @@ const commands = {
|
|||
`/uno suppress [on|off] - Toggles suppression of game messages.`,
|
||||
],
|
||||
};
|
||||
/** @type {SettingsHandler} */
|
||||
const roomSettings = room => ({
|
||||
|
||||
export const roomSettings: SettingsHandler = room => ({
|
||||
label: "UNO",
|
||||
permission: 'editroom',
|
||||
options: [
|
||||
|
|
@ -982,8 +920,3 @@ const roomSettings = room => ({
|
|||
[`enabled`, !room.unoDisabled || 'uno enable'],
|
||||
],
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
commands,
|
||||
roomSettings,
|
||||
};
|
||||
|
|
@ -27,14 +27,11 @@
|
|||
"./server/chat-plugins/*.ts",
|
||||
"./server/chat-plugins/chat-monitor.js",
|
||||
"./server/chat-plugins/daily-spotlight.js",
|
||||
"./server/chat-plugins/hangman.js",
|
||||
"./server/chat-plugins/helptickets.js",
|
||||
"./server/chat-plugins/mafia-data.js",
|
||||
"./server/chat-plugins/othermetas.js",
|
||||
"./server/chat-plugins/room-faqs.js",
|
||||
"./server/chat-plugins/thecafe.js",
|
||||
"./server/chat-plugins/thing-of-the-day.js",
|
||||
"./server/chat-plugins/uno.js",
|
||||
"./server/chat-plugins/wifi.js",
|
||||
"./sim/**/*",
|
||||
"./tools/set-import/*.ts"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user