/** * System commands * Pokemon Showdown - http://pokemonshowdown.com/ * * These are system commands - commands required for Pokemon Showdown * to run. A lot of these are sent by the client. * * If you'd like to modify commands, please go to config/commands.js, * which also teaches you how to use commands. * * @license MIT license */ var crypto = require('crypto'); var commands = exports.commands = { version: function(target, room, user) { if (!this.canBroadcast()) return; this.sendReplyBox('Server version: '+CommandParser.package.version+' (' + CommandParser.serverVersion.substr(0,10) + ')'); }, me: function(target, room, user, connection) { target = this.canTalk(target); if (!target) return; return '/me ' + target; }, mee: function(target, room, user, connection) { target = this.canTalk(target); if (!target) return; return '/mee ' + target; }, avatar: function(target, room, user) { if (!target) return this.parse('/avatars'); var parts = target.split(','); var avatar = parseInt(parts[0]); if (!avatar || avatar > 294 || avatar < 1) { if (!parts[1]) { this.sendReply("Invalid avatar."); } return false; } user.avatar = avatar; if (!parts[1]) { this.sendReply("Avatar changed to:\n" + '|raw|'); } }, logout: function(target, room, user) { user.resetName(); }, r: 'reply', reply: function(target, room, user) { if (!target) return this.parse('/help reply'); if (!user.lastPM) { return this.sendReply('No one has PMed you yet.'); } return this.parse('/msg '+(user.lastPM||'')+', '+target); }, pm: 'msg', whisper: 'msg', w: 'msg', msg: function(target, room, user) { if (!target) return this.parse('/help msg'); target = this.splitTarget(target); var targetUser = this.targetUser; if (!target) { this.sendReply('You forgot the comma.'); return this.parse('/help msg'); } if (!targetUser || !targetUser.connected) { if (!target) { this.sendReply('User '+this.targetUsername+' not found. Did you forget a comma?'); } else { this.sendReply('User '+this.targetUsername+' not found. Did you misspell their name?'); } return this.parse('/help msg'); } if (user.locked && !targetUser.can('lock', user)) { return this.popupReply('You can only private message members of the moderation team (users marked by %, @, &, or ~) when locked.'); } if (targetUser.locked && !user.can('lock', targetUser)) { return this.popupReply('This user is locked and cannot PM.'); } if (!user.named) { return this.popupReply('You must choose a name before you can send private messages.'); } var message = '|pm|'+user.getIdentity()+'|'+targetUser.getIdentity()+'|'+target; user.send(message); if (targetUser !== user) targetUser.send(message); targetUser.lastPM = user.userid; user.lastPM = targetUser.userid; }, makechatroom: function(target, room, user) { if (!this.can('makeroom')) return; if (Rooms.rooms[target]) { return this.sendReply("The room '"+target+"' already exists."); } Rooms.rooms[target] = new Rooms.ChatRoom(toId(target), target); return this.sendReply("The room '"+target+"' was created."); }, privateroom: function(target, room, user) { if (!this.can('makeroom')) return; if (target === 'off') { room.isPrivate = false; this.addModCommand(user.name+' made the room public.'); } else { room.isPrivate = true; this.addModCommand(user.name+' made the room private.'); } }, join: function(target, room, user, connection) { var targetRoom = Rooms.get(target); if (target && !targetRoom) { return connection.sendTo(target, "|noinit|nonexistent|The room '"+target+"' does not exist."); } if (targetRoom && !targetRoom.battle && targetRoom !== Rooms.lobby && !user.named) { return connection.sendTo(target, "|noinit|namerequired|You must have a name in order to join the room '"+target+"'."); } if (!user.joinRoom(targetRoom || room, connection)) { // This condition appears to be impossible for now. return connection.sendTo(target, "|noinit|joinfailed|The room '"+target+"' could not be joined."); } }, leave: 'part', part: function(target, room, user, connection) { if (room.id === 'global') return false; var targetRoom = Rooms.get(target); if (target && !targetRoom) { return this.sendReply("The room '"+target+"' does not exist."); } user.leaveRoom(targetRoom || room, connection); }, /********************************************************* * Moderating: Punishments *********************************************************/ kick: 'warn', k: 'warn', warn: function(target, room, user) { if (!target) return this.parse('/help warn'); target = this.splitTarget(target); var targetUser = this.targetUser; if (!targetUser || !targetUser.connected) { return this.sendReply('User '+this.targetUsername+' not found.'); } if (!this.can('warn', targetUser)) return false; this.addModCommand(''+targetUser.name+' was warned by '+user.name+'.' + (target ? " (" + target + ")" : "")); targetUser.send('|c|~|/warn '+target); }, m: 'mute', mute: function(target, room, user) { if (!target) return this.parse('/help mute'); target = this.splitTarget(target); var targetUser = this.targetUser; if (!targetUser) { return this.sendReply('User '+this.targetUsername+' not found.'); } if (!this.can('mute', targetUser)) return false; if (targetUser.muteDuration[room.id] || targetUser.locked || !targetUser.connected) { var problem = ' but was already '+(!targetUser.connected ? 'offline' : targetUser.locked ? 'locked' : 'muted'); if (!target) { return this.privateModCommand('('+targetUser.name+' would be muted by '+user.name+problem+'.)'); } return this.addModCommand(''+targetUser.name+' would be muted by '+user.name+problem+'.' + (target ? " (" + target + ")" : "")); } targetUser.popup(user.name+' has muted you for 7 minutes. '+target); this.addModCommand(''+targetUser.name+' was muted by '+user.name+' for 7 minutes.' + (target ? " (" + target + ")" : "")); var alts = targetUser.getAlts(); if (alts.length) this.addModCommand(""+targetUser.name+"'s alts were also muted: "+alts.join(", ")); targetUser.mute(room.id, 7*60*1000); }, hourmute: function(target, room, user) { if (!target) return this.parse('/help hourmute'); target = this.splitTarget(target); var targetUser = this.targetUser; if (!targetUser) { return this.sendReply('User '+this.targetUsername+' not found.'); } if (!this.can('mute', targetUser)) return false; if (((targetUser.muteDuration[room.id]||0) >= 50*60*1000 || targetUser.locked) && !target) { var problem = ' but was already '+(!targetUser.connected ? 'offline' : targetUser.locked ? 'locked' : 'muted'); return this.privateModCommand('('+targetUser.name+' would be muted by '+user.name+problem+'.)'); } targetUser.popup(user.name+' has muted you for 60 minutes. '+target); this.addModCommand(''+targetUser.name+' was muted by '+user.name+' for 60 minutes.' + (target ? " (" + target + ")" : "")); var alts = targetUser.getAlts(); if (alts.length) this.addModCommand(""+targetUser.name+"'s alts were also muted: "+alts.join(", ")); targetUser.mute(room.id, 60*60*1000, true); }, um: 'unmute', unmute: function(target, room, user) { if (!target) return this.parse('/help something'); var targetid = toUserid(target); var targetUser = Users.get(target); if (!targetUser) { return this.sendReply('User '+target+' not found.'); } if (!this.can('mute', targetUser)) return false; if (!targetUser.mutedRooms[room.id]) { return this.sendReply(''+targetUser.name+' isn\'t muted.'); } this.addModCommand(''+targetUser.name+' was unmuted by '+user.name+'.'); targetUser.unmute(room.id); }, ipmute: 'lock', lock: function(target, room, user) { if (!target) return this.parse('/help lock'); target = this.splitTarget(target); var targetUser = this.targetUser; if (!targetUser) { return this.sendReply('User '+this.targetUser+' not found.'); } if (!user.can('lock', targetUser)) { return this.sendReply('/lock - Access denied.'); } if ((targetUser.locked || Users.checkBanned(Object.keys(targetUser.ips)[0])) && !target) { var problem = ' but was already '+(targetUser.locked ? 'locked' : 'banned'); return this.privateModCommand('('+targetUser.name+' would be locked by '+user.name+problem+'.)'); } targetUser.popup(user.name+' has locked you from talking in chats, battles, and PMing regular users.\n\n'+target+'\n\nIf you feel that your lock was unjustified, you can still PM staff members (%, @, &, and ~) to discuss it.'); this.addModCommand(""+targetUser.name+" was locked from talking by "+user.name+"." + (target ? " (" + target + ")" : "")); var alts = targetUser.getAlts(); if (alts.length) this.addModCommand(""+targetUser.name+"'s alts were also locked: "+alts.join(", ")); targetUser.lock(); }, unlock: function(target, room, user) { if (!target) return this.parse('/help unlock'); if (!this.can('lock')) return false; var unlocked = Users.unlock(target); if (unlocked) { var names = Object.keys(unlocked); this.addModCommand('' + names.join(', ') + ' ' + ((names.length > 1) ? 'were' : 'was') + ' unlocked by ' + user.name + '.'); } else { this.sendReply('User '+target+' is not locked.'); } }, b: 'ban', ban: function(target, room, user) { if (!target) return this.parse('/help ban'); target = this.splitTarget(target); var targetUser = this.targetUser; if (!targetUser) { return this.sendReply('User '+this.targetUsername+' not found.'); } if (!this.can('ban', targetUser)) return false; if (Users.checkBanned(Object.keys(targetUser.ips)[0]) && !target) { var problem = ' but was already banned'; return this.privateModCommand('('+targetUser.name+' would be banned by '+user.name+problem+'.)'); } targetUser.popup(user.name+" has banned you. If you feel that your banning was unjustified you can appeal the ban:\nhttp://www.smogon.com/forums/announcement.php?f=126&a=204\n\n"+target); this.addModCommand(""+targetUser.name+" was banned by "+user.name+"." + (target ? " (" + target + ")" : "")); var alts = targetUser.getAlts(); if (alts.length) this.addModCommand(""+targetUser.name+"'s alts were also banned: "+alts.join(", ")); targetUser.ban(); }, unban: function(target, room, user) { if (!target) return this.parse('/help unban'); if (!user.can('ban')) { return this.sendReply('/unban - Access denied.'); } var name = Users.unban(target); if (name) { this.addModCommand(''+name+' was unbanned by '+user.name+'.'); } else { this.sendReply('User '+target+' is not banned.'); } }, unbanall: function(target, room, user) { if (!user.can('ban')) { return this.sendReply('/unbanall - Access denied.'); } // we have to do this the hard way since it's no longer a global for (var i in Users.bannedIps) { delete Users.bannedIps[i]; } for (var i in Users.lockedIps) { delete Users.lockedIps[i]; } this.addModCommand('All bans and locks have been lifted by '+user.name+'.'); }, banip: function(target, room, user) { target = target.trim(); if (!target) { return this.parse('/help banip'); } if (!this.can('rangeban')) return false; Users.bannedIps[target] = '#ipban'; this.addModCommand(user.name+' temporarily banned the '+(target.charAt(target.length-1)==='*'?'IP range':'IP')+': '+target); }, unbanip: function(target, room, user) { target = target.trim(); if (!target) { return this.parse('/help unbanip'); } if (!this.can('rangeban')) return false; if (!Users.bannedIps[target]) { return this.sendReply(''+target+' is not a banned IP or IP range.'); } delete Users.bannedIps[target]; this.addModCommand(user.name+' unbanned the '+(target.charAt(target.length-1)==='*'?'IP range':'IP')+': '+target); }, /********************************************************* * Moderating: Other *********************************************************/ demote: 'promote', promote: function(target, room, user, connection, cmd) { if (!target) return this.parse('/help promote'); var target = this.splitTarget(target, true); var targetUser = this.targetUser; var userid = toUserid(this.targetUsername); var name = targetUser ? targetUser.name : this.targetUsername; var currentGroup = ' '; if (targetUser) { currentGroup = targetUser.group; } else if (Users.usergroups[userid]) { currentGroup = Users.usergroups[userid].substr(0,1); } var nextGroup = target ? target : Users.getNextGroupSymbol(currentGroup, cmd === 'demote'); if (target === 'deauth') nextGroup = config.groupsranking[0]; if (!config.groups[nextGroup]) { return this.sendReply('Group \'' + nextGroup + '\' does not exist.'); } if (!user.checkPromotePermission(currentGroup, nextGroup)) { return this.sendReply('/promote - Access denied.'); } var isDemotion = (config.groups[nextGroup].rank < config.groups[currentGroup].rank); if (!Users.setOfflineGroup(name, nextGroup)) { return this.sendReply('/promote - WARNING: This user is offline and could be unregistered. Use /forcepromote if you\'re sure you want to risk it.'); } var groupName = (config.groups[nextGroup].name || nextGroup || '').trim() || 'a regular user'; if (isDemotion) { this.privateModCommand('('+name+' was demoted to ' + groupName + ' by '+user.name+'.)'); if (targetUser) { targetUser.popup('You were demoted to ' + groupName + ' by ' + user.name + '.'); } } else { this.addModCommand(''+name+' was promoted to ' + groupName + ' by '+user.name+'.'); } if (targetUser) { targetUser.updateIdentity(); } }, forcepromote: function(target, room, user) { // warning: never document this command in /help if (!this.can('forcepromote')) return false; var target = this.splitTarget(target, true); var name = this.targetUsername; var nextGroup = target ? target : Users.getNextGroupSymbol(' ', false); if (!Users.setOfflineGroup(name, nextGroup, true)) { return this.sendReply('/forcepromote - Don\'t forcepromote unless you have to.'); } var groupName = config.groups[nextGroup].name || nextGroup || ''; this.addModCommand(''+name+' was promoted to ' + (groupName.trim()) + ' by '+user.name+'.'); }, deauth: function(target, room, user) { return this.parse('/demote '+target+', deauth'); }, modchat: function(target, room, user) { if (!target) { return this.sendReply('Moderated chat is currently set to: '+config.modchat); } if (!this.can('modchat') || !this.canTalk()) return false; target = target.toLowerCase(); switch (target) { case 'on': case 'true': case 'yes': this.sendReply("If you're dealing with a spammer, make sure to run /loadbanlist first."); this.sendReply("That said, the command you've been looking for has been renamed to: /modchat registered"); return false; break; case 'registered': if (!user.can('modchatall')) { return this.sendReply('/modchat - Access denied for registered setting.'); } config.modchat = true; break; case 'off': case 'false': case 'no': config.modchat = false; break; default: if (!config.groups[target]) { return this.parse('/help modchat'); } if (config.groupsranking.indexOf(target) > 1 && !user.can('modchatall')) { return this.sendReply('/modchat - Access denied for setting higher than ' + config.groupsranking[1] + '.'); } config.modchat = target; break; } if (config.modchat === true) { this.add('|raw|
Moderated chat was enabled!
Only registered users can talk.
'); } else if (!config.modchat) { this.add('|raw|
Moderated chat was disabled!
Anyone may talk now.
'); } else { var modchat = sanitize(config.modchat); this.add('|raw|
Moderated chat was set to '+modchat+'!
Only users of rank '+modchat+' and higher can talk.
'); } this.logModCommand(user.name+' set modchat to '+config.modchat); }, declare: function(target, room, user) { if (!target) return this.parse('/help declare'); if (!this.can('declare')) return false; if (!this.canTalk()) return; this.add('|raw|
'+target+'
'); this.logModCommand(user.name+' declared '+target); }, wall: 'announce', announce: function(target, room, user) { if (!target) return this.parse('/help announce'); if (!this.can('announce')) return false; target = this.canTalk(target); if (!target) return; return '/announce '+target; }, fr: 'forcerename', forcerename: function(target, room, user) { if (!target) return this.parse('/help forcerename'); target = this.splitTarget(target); var targetUser = this.targetUser; if (!targetUser) { return this.sendReply('User '+this.targetUsername+' not found.'); } if (!this.can('forcerename', targetUser)) return false; if (targetUser.userid === toUserid(this.targetUser)) { var entry = ''+targetUser.name+' was forced to choose a new name by '+user.name+'.' + (target ? " (" + target + ")" : ""); this.logModCommand(entry); Rooms.lobby.sendAuth(entry); if (room.id !== 'lobby') { this.add(entry); } else { this.logEntry(entry); } targetUser.resetName(); targetUser.send('|nametaken||'+user.name+" has forced you to change your name. "+target); } else { this.sendReply("User "+targetUser.name+" is no longer using that name."); } }, frt: 'forcerenameto', forcerenameto: function(target, room, user) { if (!target) return this.parse('/help forcerenameto'); target = this.splitTarget(target); var targetUser = this.targetUser; if (!targetUser) { return this.sendReply('User '+this.targetUsername+' not found.'); } if (!target) { return this.sendReply('No new name was specified.'); } if (!this.can('forcerenameto', targetUser)) return false; if (targetUser.userid === toUserid(this.targetUser)) { var entry = ''+targetUser.name+' was forcibly renamed to '+target+' by '+user.name+'.'; this.logModCommand(entry); Rooms.lobby.sendAuth(entry); if (room.id !== 'lobby') { room.add(entry); } else { room.logEntry(entry); } targetUser.forceRename(target); } else { this.sendReply("User "+targetUser.name+" is no longer using that name."); } }, modlog: function(target, room, user, connection) { if (!this.can('modlog')) return false; var lines = 0; if (!target.match('[^0-9]')) { lines = parseInt(target || 15, 10); if (lines > 100) lines = 100; } var filename = 'logs/modlog.txt'; var command = 'tail -'+lines+' '+filename; var grepLimit = 100; if (!lines || lines < 0) { // searching for a word instead if (target.match(/^["'].+["']$/)) target = target.substring(1,target.length-1); command = "awk '{print NR,$0}' "+filename+" | sort -nr | cut -d' ' -f2- | grep -m"+grepLimit+" -i '"+target.replace(/\\/g,'\\\\\\\\').replace(/["'`]/g,'\'\\$&\'').replace(/[\{\}\[\]\(\)\$\^\.\?\+\-\*]/g,'[$&]')+"'"; } require('child_process').exec(command, function(error, stdout, stderr) { if (error && stderr) { connection.popup('/modlog erred - modlog does not support Windows'); console.log('/modlog error: '+error); return false; } if (lines) { if (!stdout) { connection.popup('The modlog is empty. (Weird.)'); } else { connection.popup('Displaying the last '+lines+' lines of the Moderator Log:\n\n'+stdout); } } else { if (!stdout) { connection.popup('No moderator actions containing "'+target+'" were found.'); } else { connection.popup('Displaying the last '+grepLimit+' logged actions containing "'+target+'":\n\n'+stdout); } } }); }, bw: 'banword', banword: function(target, room, user) { if (!this.can('declare')) return false; target = toId(target); if (!target) { return this.sendReply('Specify a word or phrase to ban.'); } Users.addBannedWord(target); this.sendReply('Added \"'+target+'\" to the list of banned words.'); }, ubw: 'unbanword', unbanword: function(target, room, user) { if (!this.can('declare')) return false; target = toId(target); if (!target) { return this.sendReply('Specify a word or phrase to unban.'); } Users.removeBannedWord(target); this.sendReply('Removed \"'+target+'\" from the list of banned words.'); }, /********************************************************* * Server management commands *********************************************************/ hotpatch: function(target, room, user) { if (!target) return this.parse('/help hotpatch'); if (!this.can('hotpatch')) return false; if (target === 'chat') { CommandParser.uncacheTree('./command-parser.js'); CommandParser = require('./command-parser.js'); return this.sendReply('Chat commands have been hot-patched.'); } else if (target === 'battles') { Simulator.SimulatorProcess.respawn(); return this.sendReply('Battles have been hotpatched. Any battles started after now will use the new code; however, in-progress battles will continue to use the old code.'); } else if (target === 'formats') { // uncache the tools.js dependency tree CommandParser.uncacheTree('./tools.js'); // reload tools.js Data = {}; Tools = require('./tools.js'); // note: this will lock up the server for a few seconds // rebuild the formats list Rooms.global.formatListText = Rooms.global.getFormatListText(); // respawn simulator processes Simulator.SimulatorProcess.respawn(); // broadcast the new formats list to clients Rooms.global.send(Rooms.global.formatListText); return this.sendReply('Formats have been hotpatched.'); } this.sendReply('Your hot-patch command was unrecognized.'); }, savelearnsets: function(target, room, user) { if (this.can('hotpatch')) return false; fs.writeFile('data/learnsets.js', 'exports.BattleLearnsets = '+JSON.stringify(BattleLearnsets)+";\n"); this.sendReply('learnsets.js saved.'); }, disableladder: function(target, room, user) { if (!this.can('disableladder')) return false; if (LoginServer.disabled) { return this.sendReply('/disableladder - Ladder is already disabled.'); } LoginServer.disabled = true; this.logModCommand('The ladder was disabled by ' + user.name + '.'); this.add('|raw|
Due to high server load, the ladder has been temporarily disabled
Rated games will no longer update the ladder. It will be back momentarily.
'); }, enableladder: function(target, room, user) { if (!this.can('disableladder')) return false; if (!LoginServer.disabled) { return this.sendReply('/enable - Ladder is already enabled.'); } LoginServer.disabled = false; this.logModCommand('The ladder was enabled by ' + user.name + '.'); this.add('|raw|
The ladder is now back.
Rated games will update the ladder now.
'); }, lockdown: function(target, room, user) { if (!this.can('lockdown')) return false; lockdown = true; for (var id in Rooms.rooms) { if (id !== 'global') Rooms.rooms[id].addRaw('
The server is restarting soon.
Please finish your battles quickly. No new battles can be started until the server resets in a few minutes.
'); if (Rooms.rooms[id].requestKickInactive) Rooms.rooms[id].requestKickInactive(user, true); } Rooms.lobby.logEntry(user.name + ' used /lockdown'); }, endlockdown: function(target, room, user) { if (!this.can('lockdown')) return false; lockdown = false; for (var id in Rooms.rooms) { if (id !== 'global') Rooms.rooms[id].addRaw('
The server shutdown was canceled.
'); } Rooms.lobby.logEntry(user.name + ' used /endlockdown'); }, kill: function(target, room, user) { if (!this.can('lockdown')) return false; if (!lockdown) { return this.sendReply('For safety reasons, /kill can only be used during lockdown.'); } if (CommandParser.updateServerLock) { return this.sendReply('Wait for /updateserver to finish before using /kill.'); } Rooms.lobby.destroyLog(function() { Rooms.lobby.logEntry(user.name + ' used /kill'); }, function() { process.exit(); }); // Just in the case the above never terminates, kill the process // after 10 seconds. setTimeout(function() { process.exit(); }, 10000); }, loadbanlist: function(target, room, user, connection) { if (!this.can('modchat')) return false; connection.sendTo(room, 'Loading ipbans.txt...'); fs.readFile('config/ipbans.txt', function (err, data) { if (err) return; data = (''+data).split("\n"); var count = 0; for (var i=0; iWe fixed the crash without restarting the server!
You may resume talking in the lobby and starting new battles.'); Rooms.lobby.logEntry(user.name + ' used /crashfixed'); }, crashlogged: function(target, room, user) { if (!lockdown) { return this.sendReply('/crashlogged - There is no active crash.'); } if (!this.can('declare')) return false; lockdown = false; config.modchat = false; Rooms.lobby.addRaw('
We have logged the crash and are working on fixing it!
You may resume talking in the lobby and starting new battles.
'); Rooms.lobby.logEntry(user.name + ' used /crashlogged'); }, eval: function(target, room, user, connection, cmd, message) { if (!user.checkConsolePermission(connection.socket)) { return this.sendReply("/eval - Access denied."); } if (!this.canBroadcast()) return; if (!this.broadcasting) this.sendReply('||>> '+target); try { var battle = room.battle; var me = user; this.sendReply('||<< '+eval(target)); } catch (e) { this.sendReply('||<< error: '+e.message); var stack = '||'+(''+e.stack).replace(/\n/g,'\n||'); connection.sendTo(room, stack); } }, evalbattle: function(target, room, user, connection, cmd, message) { if (!user.checkConsolePermission(connection.socket)) { return this.sendReply("/evalbattle - Access denied."); } if (!this.canBroadcast()) return; if (!room.battle) { return this.sendReply("/evalbattle - This isn't a battle room."); } room.battle.send('eval', target.replace(/\n/g, '\f')); }, /********************************************************* * Battle commands *********************************************************/ concede: 'forfeit', surrender: 'forfeit', forfeit: function(target, room, user) { if (!room.battle) { return this.sendReply("There's nothing to forfeit here."); } if (!room.forfeit(user)) { return this.sendReply("You can't forfeit this battle."); } }, savereplay: function(target, room, user, connection) { if (!room || !room.battle) return; var logidx = 2; // spectator log (no exact HP) if (room.battle.ended) { // If the battle is finished when /savereplay is used, include // exact HP in the replay log. logidx = 3; } var data = room.getLog(logidx).join("\n"); var datahash = crypto.createHash('md5').update(data.replace(/[^(\x20-\x7F)]+/g,'')).digest('hex'); LoginServer.request('prepreplay', { id: room.id.substr(7), loghash: datahash, p1: room.p1.name, p2: room.p2.name, format: room.format }, function(success) { connection.send('|queryresponse|savereplay|'+JSON.stringify({ log: data, room: 'lobby', id: room.id.substr(7) })); }); }, mv: 'move', attack: 'move', move: function(target, room, user) { if (!room.decision) return this.sendReply('You can only do this in battle rooms.'); room.decision(user, 'choose', 'move '+target); }, sw: 'switch', switch: function(target, room, user) { if (!room.decision) return this.sendReply('You can only do this in battle rooms.'); room.decision(user, 'choose', 'switch '+parseInt(target,10)); }, choose: function(target, room, user) { if (!room.decision) return this.sendReply('You can only do this in battle rooms.'); room.decision(user, 'choose', target); }, undo: function(target, room, user) { if (!room.decision) return this.sendReply('You can only do this in battle rooms.'); room.decision(user, 'undo', target); }, team: function(target, room, user) { if (!room.decision) return this.sendReply('You can only do this in battle rooms.'); room.decision(user, 'choose', 'team '+target); }, joinbattle: function(target, room, user) { if (!room.joinBattle) return this.sendReply('You can only do this in battle rooms.'); room.joinBattle(user); }, partbattle: 'leavebattle', leavebattle: function(target, room, user) { if (!room.leaveBattle) return this.sendReply('You can only do this in battle rooms.'); room.leaveBattle(user); }, kickbattle: function(target, room, user) { if (!room.leaveBattle) return this.sendReply('You can only do this in battle rooms.'); target = this.splitTarget(target); var targetUser = this.targetUser; if (!targetUser || !targetUser.connected) { return this.sendReply('User '+this.targetUsername+' not found.'); } if (!this.can('kick', targetUser)) return false; if (room.leaveBattle(targetUser)) { this.addModCommand(''+targetUser.name+' was kicked from a battle by '+user.name+'' + (target ? " (" + target + ")" : "")); } else { this.sendReply("/kickbattle - User isn\'t in battle."); } }, kickinactive: function(target, room, user) { if (room.requestKickInactive) { room.requestKickInactive(user); } else { this.sendReply('You can only kick inactive players from inside a room.'); } }, timer: function(target, room, user) { target = toId(target); if (room.requestKickInactive) { if (target === 'off' || target === 'stop') { room.stopKickInactive(user, user.can('timer')); } else if (target === 'on' || !target) { room.requestKickInactive(user, user.can('timer')); } else { this.sendReply("'"+target+"' is not a recognized timer state."); } } else { this.sendReply('You can only set the timer from inside a room.'); } }, forcetie: 'forcewin', forcewin: function(target, room, user) { if (!this.can('forcewin')) return false; if (!room.battle) { this.sendReply('/forcewin - This is not a battle room.'); } room.battle.endType = 'forced'; if (!target) { room.battle.tie(); this.logModCommand(user.name+' forced a tie.'); return false; } target = Users.get(target); if (target) target = target.userid; else target = ''; if (target) { room.battle.win(target); this.logModCommand(user.name+' forced a win for '+target+'.'); } }, /********************************************************* * Challenging and searching commands *********************************************************/ cancelsearch: 'search', search: function(target, room, user) { if (target) { Rooms.global.searchBattle(user, target); } else { Rooms.global.cancelSearch(user); } }, chall: 'challenge', challenge: function(target, room, user) { target = this.splitTarget(target); var targetUser = this.targetUser; if (!targetUser || !targetUser.connected) { return this.popupReply("The user '"+this.targetUsername+"' was not found."); } if (targetUser.blockChallenges && !user.can('bypassblocks', targetUser)) { return this.popupReply("The user '"+this.targetUsername+"' is not accepting challenges right now."); } if (typeof target !== 'string') target = 'customgame'; var problems = Tools.validateTeam(user.team, target); if (problems) { return this.popupReply("Your team was rejected for the following reasons:\n\n- "+problems.join("\n- ")); } user.makeChallenge(targetUser, target); }, away: 'blockchallenges', idle: 'blockchallenges', blockchallenges: function(target, room, user) { user.blockChallenges = true; this.sendReply('You are now blocking all incoming challenge requests.'); }, back: 'allowchallenges', allowchallenges: function(target, room, user) { user.blockChallenges = false; this.sendReply('You are available for challenges from now on.'); }, cchall: 'cancelChallenge', cancelchallenge: function(target, room, user) { user.cancelChallengeTo(target); }, accept: function(target, room, user) { var userid = toUserid(target); var format = ''; if (user.challengesFrom[userid]) format = user.challengesFrom[userid].format; if (!format) { this.popupReply(target+" cancelled their challenge before you could accept it."); return false; } var problems = Tools.validateTeam(user.team, format); if (problems) { this.popupReply("Your team was rejected for the following reasons:\n\n- "+problems.join("\n- ")); return false; } user.acceptChallengeFrom(userid); }, reject: function(target, room, user) { user.rejectChallengeFrom(toUserid(target)); }, saveteam: 'useteam', utm: 'useteam', useteam: function(target, room, user) { try { user.team = JSON.parse(target); } catch (e) { this.popupReply('Not a valid team.'); } }, /********************************************************* * Low-level *********************************************************/ cmd: 'query', query: function(target, room, user, connection) { var spaceIndex = target.indexOf(' '); var cmd = target; if (spaceIndex > 0) { cmd = target.substr(0, spaceIndex); target = target.substr(spaceIndex+1); } else { target = ''; } if (cmd === 'userdetails') { var targetUser = Users.get(target); if (!targetUser) { connection.send('|queryresponse|userdetails|'+JSON.stringify({ userid: toId(target), rooms: false })); return false; } var roomList = {}; for (var i in targetUser.roomCount) { if (i==='global') continue; var targetRoom = Rooms.get(i); if (!targetRoom || targetRoom.isPrivate) continue; var roomData = {}; if (targetRoom.battle) { var battle = targetRoom.battle; roomData.p1 = battle.p1?' '+battle.p1:''; roomData.p2 = battle.p2?' '+battle.p2:''; } roomList[i] = roomData; } if (!targetUser.roomCount['global']) roomList = false; var userdetails = { userid: targetUser.userid, avatar: targetUser.avatar, rooms: roomList }; if (user.can('ip', targetUser)) { var ips = Object.keys(targetUser.ips); if (ips.length === 1) { userdetails.ip = ips[0]; } else { userdetails.ips = ips; } } connection.send('|queryresponse|userdetails|'+JSON.stringify(userdetails)); } else if (cmd === 'roomlist') { connection.send('|queryresponse|roomlist|'+JSON.stringify({ rooms: Rooms.global.getRoomList(true) })); } }, trn: function(target, room, user, connection) { var commaIndex = target.indexOf(','); var targetName = target; var targetAuth = false; var targetToken = ''; if (commaIndex >= 0) { targetName = target.substr(0,commaIndex); target = target.substr(commaIndex+1); commaIndex = target.indexOf(','); targetAuth = target; if (commaIndex >= 0) { targetAuth = !!parseInt(target.substr(0,commaIndex),10); targetToken = target.substr(commaIndex+1); } } user.rename(targetName, targetToken, targetAuth, connection.socket); }, };