PageRenderTime 122ms CodeModel.GetById 3ms app.highlight 105ms RepoModel.GetById 2ms app.codeStats 0ms

/chat-commands.js

https://github.com/CanBoy67/Pokemon-Showdown
JavaScript | 2134 lines | 1890 code | 181 blank | 63 comment | 424 complexity | b230d2c5713942189b1ead1f84151dd4 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/* to reload chat commands:
   2
   3>> for (var i in require.cache) delete require.cache[i];parseCommand = require('./chat-commands.js').parseCommand;''
   4
   5*/
   6
   7var crypto = require('crypto');
   8
   9/**
  10 * `parseCommand`. This is the function most of you are interested in,
  11 * apparently.
  12 *
  13 * `message` is exactly what the user typed in.
  14 * If the user typed in a command, `cmd` and `target` are the command (with "/"
  15 * omitted) and command target. Otherwise, they're both the empty string.
  16 *
  17 * For instance, say a user types in "/foo":
  18 * cmd === "/foo", target === "", message === "/foo bar baz"
  19 *
  20 * Or, say a user types in "/foo bar baz":
  21 * cmd === "foo", target === "bar baz", message === "/foo bar baz"
  22 *
  23 * Or, say a user types in "!foo bar baz":
  24 * cmd === "!foo", target === "bar baz", message === "!foo bar baz"
  25 *
  26 * Or, say a user types in "foo bar baz":
  27 * cmd === "", target === "", message === "foo bar baz"
  28 *
  29 * `user` and `socket` are the user and socket that sent the message,
  30 * and `room` is the room that sent the message.
  31 *
  32 * Deal with the message however you wish:
  33 *   return; will output the message normally: "user: message"
  34 *   return false; will supress the message output.
  35 *   returning a string will replace the message with that string,
  36 *     then output it normally.
  37 *
  38 */
  39
  40var modlog = modlog || fs.createWriteStream('logs/modlog.txt', {flags:'a+'});
  41var updateServerLock = false;
  42
  43function parseCommandLocal(user, cmd, target, room, socket, message) {
  44	if (!room) return;
  45	cmd = cmd.toLowerCase();
  46	switch (cmd) {
  47	case 'cmd':
  48		var spaceIndex = target.indexOf(' ');
  49		var cmd = target;
  50		if (spaceIndex > 0) {
  51			cmd = target.substr(0, spaceIndex);
  52			target = target.substr(spaceIndex+1);
  53		} else {
  54			target = '';
  55		}
  56		if (cmd === 'userdetails') {
  57			var targetUser = Users.get(target);
  58			if (!targetUser || !room) return false;
  59			var roomList = {};
  60			for (var i in targetUser.roomCount) {
  61				if (i==='lobby') continue;
  62				var targetRoom = Rooms.get(i);
  63				if (!targetRoom) continue;
  64				var roomData = {};
  65				if (targetRoom.battle) {
  66					var battle = targetRoom.battle;
  67					roomData.p1 = battle.p1?' '+battle.p1:'';
  68					roomData.p2 = battle.p2?' '+battle.p2:'';
  69				}
  70				roomList[i] = roomData;
  71			}
  72			var userdetails = {
  73				command: 'userdetails',
  74				userid: targetUser.userid,
  75				avatar: targetUser.avatar,
  76				rooms: roomList,
  77				room: room.id
  78			};
  79			if (user.can('ip', targetUser)) {
  80				userdetails.ip = targetUser.ip;
  81			}
  82			emit(socket, 'command', userdetails);
  83		} else if (cmd === 'roomlist') {
  84			if (!room || !room.getRoomList) return false;
  85			emit(socket, 'command', {
  86				command: 'roomlist',
  87				rooms: room.getRoomList(true),
  88				room: room.id
  89			});
  90		}
  91		return false;
  92		break;
  93
  94	case 'me':
  95	case 'mee':
  96		if (canTalk(user, room)) return true;
  97		break;
  98
  99	case '!birkal':
 100	case 'birkal':
 101		if (canTalk(user, room) && user.can('broadcast') && room.id === 'lobby') {
 102			if (cmd === '!birkal') {
 103				room.add('|c|'+user.getIdentity()+'|!birkal '+target, true);
 104			}
 105			room.logEntry(user.name + ' used /birkal ' + target);
 106			room.add('|c| Birkal|/me '+target, true);
 107			return false;
 108		}
 109		break;
 110
 111	case 'namelock':
 112	case 'nl':
 113		if(!target) {
 114			return false;
 115		}
 116		var targets = splitTarget(target);
 117		var targetUser = targets[0];
 118		var targetName = targets[1] || (targetUser && targetUser.name);
 119		if (!user.can('namelock', targetUser)) {
 120			emit(socket, 'console', '/namelock - access denied.');
 121			return false;
 122		} else if (targetUser && targetName) {
 123			var oldname = targetUser.name;
 124			var targetId = toUserid(targetName);
 125			var userOfName = Users.users[targetId];
 126			var isAlt = false;
 127			if (userOfName) {
 128				for(var altName in userOfName.getAlts()) {
 129					var altUser = Users.users[toUserid(altName)];
 130					if (!altUser) continue;
 131					if (targetId === altUser.userid) {
 132						isAlt = true;
 133						break;
 134					}
 135					for (var prevName in altUser.prevNames) {
 136						if (targetId === toUserid(prevName)) {
 137							isAlt = true;
 138							break;
 139						}
 140					}
 141					if (isAlt) break;
 142				}
 143			}
 144			if (!userOfName || oldname === targetName || isAlt) {
 145				targetUser.nameLock(targetName, true);
 146			}
 147			if (targetUser.nameLocked()) {
 148				logModCommand(room,user.name+" name-locked "+oldname+" to "+targetName+".");
 149				return false;
 150			}
 151			emit(socket, 'console', oldname+" can't be name-locked to "+targetName+".");
 152		} else {
 153			emit(socket, 'console', "User "+targets[2]+" not found.");
 154		}
 155		return false;
 156		break;
 157	case 'nameunlock':
 158	case 'unnamelock':
 159	case 'nul':
 160	case 'unl':
 161		if(!user.can('namelock') || !target) {
 162			return false;
 163		}
 164		var removed = false;
 165		for (var i in nameLockedIps) {
 166			if (nameLockedIps[i] === target) {
 167				delete nameLockedIps[i];
 168				removed = true;
 169			}
 170		}
 171		if (removed) {
 172			var targetUser = Users.get(target);
 173			if (targetUser) {
 174				rooms.lobby.sendIdentity(targetUser);
 175			}
 176			logModCommand(room,user.name+" unlocked the name of "+target+".");
 177		} else {
 178			emit(socket, 'console', target+" not found.");
 179		}
 180		return false;
 181		break;
 182
 183	case 'forfeit':
 184	case 'concede':
 185	case 'surrender':
 186		if (!room.battle) {
 187			emit(socket, 'console', "There's nothing to forfeit here.");
 188			return false;
 189		}
 190		if (!room.forfeit(user)) {
 191			emit(socket, 'console', "You can't forfeit this battle.");
 192		}
 193		return false;
 194		break;
 195
 196	case 'register':
 197		emit(socket, 'console', 'You must win a rated battle to register.');
 198		return false;
 199		break;
 200
 201	case 'avatar':
 202		if (!target) return parseCommand(user, 'avatars', '', room, socket);
 203		var parts = target.split(',');
 204		var avatar = parseInt(parts[0]);
 205		if (!avatar || avatar > 294 || avatar < 1) {
 206			if (!parts[1]) {
 207				emit(socket, 'console', 'Invalid avatar.');
 208			}
 209			return false;
 210		}
 211
 212		user.avatar = avatar;
 213		if (!parts[1]) {
 214			emit(socket, 'console', 'Avatar changed to:');
 215			emit(socket, 'console', {rawMessage: '<img src="/sprites/trainers/'+avatar+'.png" alt="" width="80" height="80" />'});
 216		}
 217
 218		return false;
 219		break;
 220
 221	case 'rooms':
 222		var targetUser = user;
 223		if (target) targetUser = Users.get(target);
 224		if (!targetUser) {
 225			emit(socket, 'console', 'User '+target+' not found.');
 226		} else {
 227			var output = "";
 228			var first = true;
 229			for (var i in targetUser.roomCount) {
 230				if (!first) output += ' | ';
 231				first = false;
 232
 233				output += '<a href="/'+i+'" room="'+i+'">'+i+'</a>';
 234			}
 235			if (!output) {
 236				emit(socket, 'console', ""+targetUser.name+" is offline.");
 237			} else {
 238				emit(socket, 'console', {rawMessage: ""+targetUser.name+" is in: "+output});
 239			}
 240		}
 241		return false;
 242		break;
 243
 244	case 'altcheck':
 245	case 'alt':
 246	case 'alts':
 247	case 'getalts':
 248		if (!target) return parseCommand(user, '?', cmd, room, socket);
 249		var targetUser = Users.get(target);
 250		if (!targetUser) {
 251			emit(socket, 'console', 'User '+target+' not found.');
 252			return false;
 253		}
 254		if (!user.can('alts', targetUser)) {
 255			emit(socket, 'console', '/alts - Access denied.');
 256			return false;
 257		}
 258
 259		var alts = targetUser.getAlts();
 260
 261		emit(socket, 'console', 'User: '+targetUser.name);
 262
 263		if (!user.can('alts', targetUser.getHighestRankedAlt())) {
 264			return false;
 265		}
 266
 267		var output = '';
 268		for (var i in targetUser.prevNames) {
 269			if (output) output += ", ";
 270			output += targetUser.prevNames[i];
 271		}
 272		if (output) emit(socket, 'console', 'Previous names: '+output);
 273
 274		for (var j=0; j<alts.length; j++) {
 275			var targetAlt = Users.get(alts[j]);
 276			if (!targetAlt.named && !targetAlt.connected) continue;
 277
 278			emit(socket, 'console', 'Alt: '+targetAlt.name);
 279			output = '';
 280			for (var i in targetAlt.prevNames) {
 281				if (output) output += ", ";
 282				output += targetAlt.prevNames[i];
 283			}
 284			if (output) emit(socket, 'console', 'Previous names: '+output);
 285		}
 286		return false;
 287		break;
 288
 289	case 'whois':
 290		var targetUser = user;
 291		if (target) {
 292			targetUser = Users.get(target);
 293		}
 294		if (!targetUser) {
 295			emit(socket, 'console', 'User '+target+' not found.');
 296		} else {
 297			emit(socket, 'console', 'User: '+targetUser.name);
 298			if (config.groups[targetUser.group] && config.groups[targetUser.group].name) {
 299				emit(socket, 'console', 'Group: ' + config.groups[targetUser.group].name + ' (' + targetUser.group + ')');
 300			}
 301			if (!targetUser.authenticated) {
 302				emit(socket, 'console', '(Unregistered)');
 303			}
 304			if (user.can('ip', targetUser)) {
 305				emit(socket, 'console', 'IP: '+targetUser.ip);
 306			}
 307			var output = 'In rooms: ';
 308			var first = true;
 309			for (var i in targetUser.roomCount) {
 310				if (!first) output += ' | ';
 311				first = false;
 312
 313				output += '<a href="/'+i+'" room="'+i+'">'+i+'</a>';
 314			}
 315			emit(socket, 'console', {rawMessage: output});
 316		}
 317		return false;
 318		break;
 319
 320	case 'ban':
 321	case 'b':
 322		if (!target) return parseCommand(user, '?', cmd, room, socket);
 323		var targets = splitTarget(target);
 324		var targetUser = targets[0];
 325		if (!targetUser) {
 326			emit(socket, 'console', 'User '+targets[2]+' not found.');
 327			return false;
 328		}
 329		if (!user.can('ban', targetUser)) {
 330			emit(socket, 'console', '/ban - Access denied.');
 331			return false;
 332		}
 333
 334		logModCommand(room,""+targetUser.name+" was banned by "+user.name+"." + (targets[1] ? " (" + targets[1] + ")" : ""));
 335		targetUser.emit('message', user.name+' has banned you.  If you feel that your banning was unjustified you can <a href="http://www.smogon.com/forums/announcement.php?f=126&a=204" target="_blank">appeal the ban</a>. '+targets[1]);
 336		var alts = targetUser.getAlts();
 337		if (alts.length) logModCommand(room,""+targetUser.name+"'s alts were also banned: "+alts.join(", "));
 338
 339		targetUser.ban();
 340		return false;
 341		break;
 342
 343	case 'banredirect':
 344	case 'br':
 345		emit(socket, 'console', '/banredirect - This command is obsolete and has been removed.');
 346		return false;
 347		break;
 348
 349	case 'redirect':
 350	case 'redir':
 351		emit(socket, 'console', '/redirect - This command is obsolete and has been removed.');
 352		return false;
 353		break;
 354
 355	case 'kick':
 356	case 'k':
 357		// TODO: /kick will be removed in due course.
 358		if (!target) return parseCommand(user, '?', cmd, room, socket);
 359		var targets = splitTarget(target);
 360		var targetUser = targets[0];
 361		if (!targetUser || !targetUser.connected) {
 362			emit(socket, 'console', 'User '+targets[2]+' not found.');
 363			return false;
 364		}
 365		if (!user.can('redirect', targetUser)) {
 366			emit(socket, 'console', '/redirect - Access denied.');
 367			return false;
 368		}
 369
 370		logModCommand(room,''+targetUser.name+' was kicked to the Rules page by '+user.name+'' + (targets[1] ? " (" + targets[1] + ")" : ""));
 371		targetUser.emit('console', {evalRulesRedirect: 1});
 372		return false;
 373		break;
 374
 375	case 'unban':
 376		if (!target) return parseCommand(user, '?', cmd, room, socket);
 377		if (!user.can('ban')) {
 378			emit(socket, 'console', '/unban - Access denied.');
 379			return false;
 380		}
 381
 382		var targetid = toUserid(target);
 383		var success = false;
 384
 385		for (var ip in bannedIps) {
 386			if (bannedIps[ip] === targetid) {
 387				delete bannedIps[ip];
 388				success = true;
 389			}
 390		}
 391		if (success) {
 392			logModCommand(room,''+target+' was unbanned by '+user.name+'.');
 393		} else {
 394			emit(socket, 'console', 'User '+target+' is not banned.');
 395		}
 396		return false;
 397		break;
 398
 399	case 'unbanall':
 400		if (!user.can('ban')) {
 401			emit(socket, 'console', '/unbanall - Access denied.');
 402			return false;
 403		}
 404		logModCommand(room,'All bans and ip mutes have been lifted by '+user.name+'.');
 405		bannedIps = {};
 406		mutedIps = {};
 407		return false;
 408		break;
 409
 410	case 'reply':
 411	case 'r':
 412		if (!target) return parseCommand(user, '?', cmd, room, socket);
 413		if (!user.lastPM) {
 414			emit(socket, 'console', 'No one has PMed you yet.');
 415			return false;
 416		}
 417		return parseCommand(user, 'msg', ''+(user.lastPM||'')+', '+target, room, socket);
 418		break;
 419
 420	case 'msg':
 421	case 'pm':
 422	case 'whisper':
 423	case 'w':
 424		if (!target) return parseCommand(user, '?', cmd, room, socket);
 425		var targets = splitTarget(target);
 426		var targetUser = targets[0];
 427		if (!targets[1]) {
 428			emit(socket, 'console', 'You forgot the comma.');
 429			return parseCommand(user, '?', cmd, room, socket);
 430		}
 431		if (!targets[0] || !targetUser.connected) {
 432			if (target.indexOf(' ')) {
 433				emit(socket, 'console', 'User '+targets[2]+' not found. Did you forget a comma?');
 434			} else {
 435				emit(socket, 'console', 'User '+targets[2]+' not found. Did you misspell their name?');
 436			}
 437			return parseCommand(user, '?', cmd, room, socket);
 438		}
 439		// temporarily disable this because blarajan
 440		/* if (user.muted && !targetUser.can('mute', user)) {
 441			emit(socket, 'console', 'You can only private message members of the Moderation Team (users marked by %, @, &, or ~) when muted.');
 442			return false;
 443		} */
 444
 445		if (!user.named) {
 446			emit(socket, 'console', 'You must choose a name before you can send private messages.');
 447			return false;
 448		}
 449
 450		var message = {
 451			name: user.getIdentity(),
 452			pm: targetUser.getIdentity(),
 453			message: targets[1]
 454		};
 455		user.emit('console', message);
 456		targets[0].emit('console', message);
 457		targets[0].lastPM = user.userid;
 458		user.lastPM = targets[0].userid;
 459		return false;
 460		break;
 461
 462	case 'ip':
 463	case 'getip':
 464		if (!target) {
 465			emit(socket, 'console', 'Your IP is: '+user.ip);
 466			return false;
 467		}
 468		var targetUser = Users.get(target);
 469		if (!targetUser) {
 470			emit(socket, 'console', 'User '+target+' not found.');
 471			return false;
 472		}
 473		if (!user.can('ip', targetUser)) {
 474			emit(socket, 'console', '/ip - Access denied.');
 475			return false;
 476		}
 477		emit(socket, 'console', 'User '+targetUser.name+' has IP: '+targetUser.ip);
 478		return false;
 479		break;
 480
 481	case 'mute':
 482	case 'm':
 483		if (!target) return parseCommand(user, '?', cmd, room, socket);
 484		var targets = splitTarget(target);
 485		var targetUser = targets[0];
 486		if (!targetUser) {
 487			emit(socket, 'console', 'User '+targets[2]+' not found.');
 488			return false;
 489		}
 490		if (!user.can('mute', targetUser)) {
 491			emit(socket, 'console', '/mute - Access denied.');
 492			return false;
 493		}
 494
 495		logModCommand(room,''+targetUser.name+' was muted by '+user.name+'.' + (targets[1] ? " (" + targets[1] + ")" : ""));
 496		targetUser.emit('message', user.name+' has muted you. '+targets[1]);
 497		var alts = targetUser.getAlts();
 498		if (alts.length) logModCommand(room,""+targetUser.name+"'s alts were also muted: "+alts.join(", "));
 499
 500		targetUser.muted = true;
 501		rooms.lobby.sendIdentity(targetUser);
 502		for (var i=0; i<alts.length; i++) {
 503			var targetAlt = Users.get(alts[i]);
 504			if (targetAlt) {
 505				targetAlt.muted = true;
 506				rooms.lobby.sendIdentity(targetAlt);
 507			}
 508		}
 509
 510		return false;
 511		break;
 512
 513	case 'ipmute':
 514		if (!target) return parseCommand(user, '?', cmd, room, socket);
 515		var targetUser = Users.get(target);
 516		if (!targetUser) {
 517			emit(socket, 'console', 'User '+target+' not found.');
 518			return false;
 519		}
 520		if (!user.can('mute', targetUser)) {
 521			emit(socket, 'console', '/ipmute - Access denied.');
 522			return false;
 523		}
 524
 525		logModCommand(room,''+targetUser.name+"'s IP was muted by "+user.name+'.');
 526		var alts = targetUser.getAlts();
 527		if (alts.length) logModCommand(room,""+targetUser.name+"'s alts were also muted: "+alts.join(", "));
 528
 529		targetUser.muted = true;
 530		rooms.lobby.sendIdentity(targetUser);
 531		mutedIps[targetUser.ip] = targetUser.userid;
 532		for (var i=0; i<alts.length; i++) {
 533			var targetAlt = Users.get(alts[i]);
 534			if (targetAlt) {
 535				targetAlt.muted = true;
 536				rooms.lobby.sendIdentity(targetAlt);
 537			}
 538		}
 539
 540		return false;
 541		break;
 542
 543	case 'unmute':
 544	case 'um':
 545		if (!target) return parseCommand(user, '?', cmd, room, socket);
 546		var targetid = toUserid(target);
 547		var targetUser = Users.get(target);
 548		if (!targetUser) {
 549			emit(socket, 'console', 'User '+target+' not found.');
 550			return false;
 551		}
 552		if (!user.can('mute', targetUser)) {
 553			emit(socket, 'console', '/unmute - Access denied.');
 554			return false;
 555		}
 556
 557		var success = false;
 558
 559		for (var ip in mutedIps) {
 560			if (mutedIps[ip] === targetid) {
 561				delete mutedIps[ip];
 562				success = true;
 563			}
 564		}
 565
 566		if (success) {
 567			logModCommand(room,''+(targetUser?targetUser.name:target)+"'s IP was unmuted by "+user.name+'.');
 568		}
 569
 570		targetUser.muted = false;
 571		rooms.lobby.sendIdentity(targetUser);
 572		logModCommand(room,''+targetUser.name+' was unmuted by '+user.name+'.');
 573		return false;
 574		break;
 575
 576	case 'promote':
 577	case 'demote':
 578		if (!target) return parseCommand(user, '?', cmd, room, socket);
 579		var targets = splitTarget(target, true);
 580		var targetUser = targets[0];
 581		var userid = toUserid(targets[2]);
 582
 583		var currentGroup = ' ';
 584		if (targetUser) {
 585			currentGroup = targetUser.group;
 586		} else if (Users.usergroups[userid]) {
 587			currentGroup = Users.usergroups[userid].substr(0,1);
 588		}
 589		var name = targetUser ? targetUser.name : targets[2];
 590
 591		var nextGroup = targets[1] ? targets[1] : Users.getNextGroupSymbol(currentGroup, cmd === 'demote');
 592		if (targets[1] === 'deauth') nextGroup = config.groupsranking[0];
 593		if (!config.groups[nextGroup]) {
 594			emit(socket, 'console', 'Group \'' + nextGroup + '\' does not exist.');
 595			return false;
 596		}
 597		if (!user.checkPromotePermission(currentGroup, nextGroup)) {
 598			emit(socket, 'console', '/promote - Access denied.');
 599			return false;
 600		}
 601
 602		var isDemotion = (config.groups[nextGroup].rank < config.groups[currentGroup].rank);
 603		if (!Users.setOfflineGroup(name, nextGroup)) {
 604			emit(socket, 'console', '/promote - WARNING: This user is offline and could be unregistered. Use /forcepromote if you\'re sure you want to risk it.');
 605			return false;
 606		}
 607		var groupName = (config.groups[nextGroup].name || nextGroup || '').trim() || 'a regular user';
 608		var entry = ''+name+' was '+(isDemotion?'demoted':'promoted')+' to ' + groupName + ' by '+user.name+'.';
 609		logModCommand(room, entry, isDemotion);
 610		if (isDemotion) {
 611			rooms.lobby.logEntry(entry);
 612			emit(socket, 'console', 'You demoted ' + name + ' to ' + groupName + '.');
 613			if (targetUser) {
 614				targetUser.emit('console', 'You were demoted to ' + groupName + ' by ' + user.name + '.');
 615			}
 616		}
 617		rooms.lobby.sendIdentity(targetUser);
 618		return false;
 619		break;
 620
 621	case 'forcepromote':
 622		// warning: never document this command in /help
 623		if (!user.can('forcepromote')) {
 624			emit(socket, 'console', '/forcepromote - Access denied.');
 625			return false;
 626		}
 627		var targets = splitTarget(target, true);
 628		var name = targets[2];
 629		var nextGroup = targets[1] ? targets[1] : Users.getNextGroupSymbol(' ', false);
 630
 631		if (!Users.setOfflineGroup(name, nextGroup, true)) {
 632			emit(socket, 'console', '/forcepromote - Don\'t forcepromote unless you have to.');
 633			return false;
 634		}
 635		var groupName = config.groups[nextGroup].name || nextGroup || '';
 636		logModCommand(room,''+name+' was promoted to ' + (groupName.trim()) + ' by '+user.name+'.');
 637		return false;
 638		break;
 639
 640	case 'deauth':
 641		return parseCommand(user, 'demote', target+', deauth', room, socket);
 642		break;
 643
 644	case 'modchat':
 645		if (!target) {
 646			emit(socket, 'console', 'Moderated chat is currently set to: '+config.modchat);
 647			return false;
 648		}
 649		if (!user.can('modchat')) {
 650			emit(socket, 'console', '/modchat - Access denied.');
 651			return false;
 652		}
 653
 654		target = target.toLowerCase();
 655		switch (target) {
 656		case 'on':
 657		case 'true':
 658		case 'yes':
 659			config.modchat = true;
 660			break;
 661		case 'off':
 662		case 'false':
 663		case 'no':
 664			config.modchat = false;
 665			break;
 666		default:
 667			if (!config.groups[target]) {
 668				emit(socket, 'console', 'That moderated chat setting is unrecognized.');
 669				return false;
 670			}
 671			if (config.groupsranking.indexOf(target) > 1 && !user.can('modchatall')) {
 672				emit(socket, 'console', '/modchat - Access denied for setting higher than ' + config.groupsranking[1] + '.');
 673				return false;
 674			}
 675			config.modchat = target;
 676			break;
 677		}
 678		if (config.modchat === true) {
 679			room.addRaw('<div class="broadcast-red"><b>Moderated chat was enabled!</b><br />Only registered users can talk.</div>');
 680		} else if (!config.modchat) {
 681			room.addRaw('<div class="broadcast-blue"><b>Moderated chat was disabled!</b><br />Anyone may talk now.</div>');
 682		} else {
 683			var modchat = sanitize(config.modchat);
 684			room.addRaw('<div class="broadcast-red"><b>Moderated chat was set to '+modchat+'!</b><br />Only users of rank '+modchat+' and higher can talk.</div>');
 685		}
 686		logModCommand(room,user.name+' set modchat to '+config.modchat,true);
 687		return false;
 688		break;
 689
 690	case 'declare':
 691		if (!target) return parseCommand(user, '?', cmd, room, socket);
 692		if (!user.can('declare')) {
 693			emit(socket, 'console', '/declare - Access denied.');
 694			return false;
 695		}
 696		room.addRaw('<div class="broadcast-blue"><b>'+target+'</b></div>');
 697		logModCommand(room,user.name+' declared '+target,true);
 698		return false;
 699		break;
 700
 701	case 'announce':
 702	case 'wall':
 703		if (!target) return parseCommand(user, '?', cmd, room, socket);
 704		if (!user.can('announce')) {
 705			emit(socket, 'console', '/announce - Access denied.');
 706			return false;
 707		}
 708		return '/announce '+target;
 709		break;
 710
 711	case 'hotpatch':
 712		if (!target) return parseCommand(user, '?', cmd, room, socket);
 713		if (!user.can('hotpatch')) {
 714			emit(socket, 'console', '/hotpatch - Access denied.');
 715			return false;
 716		}
 717
 718		if (target === 'all') {
 719			for (var i in require.cache) delete require.cache[i];
 720			Tools = require('./tools.js');
 721
 722			parseCommand = require('./chat-commands.js').parseCommand;
 723
 724			sim = require('./battles.js');
 725			BattlePokemon = sim.BattlePokemon;
 726			BattleSide = sim.BattleSide;
 727			Battle = sim.Battle;
 728			emit(socket, 'console', 'The game engine has been hot-patched.');
 729			return false;
 730		} else if (target === 'data') {
 731			for (var i in require.cache) delete require.cache[i];
 732			Tools = require('./tools.js');
 733			emit(socket, 'console', 'Game resources have been hot-patched.');
 734			return false;
 735		} else if (target === 'chat') {
 736			for (var i in require.cache) delete require.cache[i];
 737			parseCommand = require('./chat-commands.js').parseCommand;
 738			emit(socket, 'console', 'Chat commands have been hot-patched.');
 739			return false;
 740		}
 741		emit(socket, 'console', 'Your hot-patch command was unrecognized.');
 742		return false;
 743		break;
 744
 745	case 'savelearnsets':
 746		if (user.can('hotpatch')) {
 747			emit(socket, 'console', '/savelearnsets - Access denied.');
 748			return false;
 749		}
 750		fs.writeFile('data/learnsets.js', 'exports.BattleLearnsets = '+JSON.stringify(BattleLearnsets)+";\n");
 751		emit(socket, 'console', 'learnsets.js saved.');
 752		return false;
 753		break;
 754
 755	case 'rating':
 756	case 'ranking':
 757	case 'rank':
 758	case 'ladder':
 759		emit(socket, 'console', 'You are using an old version of Pokemon Showdown. Please reload the page.');
 760		return false;
 761		break;
 762
 763	case 'nick':
 764		if (!target) return parseCommand(user, '?', cmd, room, socket);
 765		user.rename(target);
 766		return false;
 767		break;
 768
 769	case 'disableladder':
 770		if (!user.can('disableladder')) {
 771			emit(socket, 'console', '/disableladder - Access denied.');
 772			return false;
 773		}
 774		if (LoginServer.disabled) {
 775			emit(socket, 'console', '/disableladder - Ladder is already disabled.');
 776			return false;
 777		}
 778		LoginServer.disabled = true;
 779		logModCommand(room, 'The ladder was disabled by ' + user.name + '.', true);
 780		room.addRaw('<div class="broadcast-red"><b>Due to high server load, the ladder has been temporarily disabled</b><br />Rated games will no longer update the ladder. It will be back momentarily.</div>');
 781		return false;
 782		break;
 783	case 'enableladder':
 784		if (!user.can('disableladder')) {
 785			emit(socket, 'console', '/enable - Access denied.');
 786			return false;
 787		}
 788		if (!LoginServer.disabled) {
 789			emit(socket, 'console', '/enable - Ladder is already enabled.');
 790			return false;
 791		}
 792		LoginServer.disabled = false;
 793		logModCommand(room, 'The ladder was enabled by ' + user.name + '.', true);
 794		room.addRaw('<div class="broadcast-green"><b>The ladder is now back.</b><br />Rated games will update the ladder now.</div>');
 795		return false;
 796		break;
 797
 798	case 'savereplay':
 799		if (!room || !room.battle) return false;
 800		var data = room.log.join("\n");
 801		var datahash = crypto.createHash('md5').update(data.replace(/[^(\x20-\x7F)]+/g,'')).digest('hex');
 802
 803		LoginServer.request('prepreplay', {
 804			id: room.id.substr(7),
 805			loghash: datahash,
 806			p1: room.p1.name,
 807			p2: room.p2.name,
 808			format: room.format
 809		}, function(success) {
 810			emit(socket, 'command', {
 811				command: 'savereplay',
 812				log: data,
 813				room: 'lobby',
 814				id: room.id.substr(7)
 815			});
 816		});
 817		return false;
 818		break;
 819
 820	case 'trn':
 821		var commaIndex = target.indexOf(',');
 822		var targetName = target;
 823		var targetAuth = false;
 824		var targetToken = '';
 825		if (commaIndex >= 0) {
 826			targetName = target.substr(0,commaIndex);
 827			target = target.substr(commaIndex+1);
 828			commaIndex = target.indexOf(',');
 829			targetAuth = target;
 830			if (commaIndex >= 0) {
 831				targetAuth = !!parseInt(target.substr(0,commaIndex),10);
 832				targetToken = target.substr(commaIndex+1);
 833			}
 834		}
 835		user.rename(targetName, targetToken, targetAuth, socket);
 836		return false;
 837		break;
 838
 839	case 'forcerename':
 840	case 'fr':
 841		if (!target) return parseCommand(user, '?', cmd, room, socket);
 842		var targets = splitTarget(target);
 843		var targetUser = targets[0];
 844		if (!targetUser) {
 845			emit(socket, 'console', 'User '+targets[2]+' not found.');
 846			return false;
 847		}
 848		if (!user.can('forcerename', targetUser)) {
 849			emit(socket, 'console', '/forcerename - Access denied.');
 850			return false;
 851		}
 852
 853		if (targetUser.userid === toUserid(targets[2])) {
 854			var entry = ''+targetUser.name+' was forced to choose a new name by '+user.name+'.' + (targets[1] ? " (" + targets[1] + ")" : "");
 855			logModCommand(room, entry, true);
 856			rooms.lobby.sendAuth(entry);
 857			if (room.id !== 'lobby') {
 858				room.add(entry);
 859			} else {
 860				room.logEntry(entry);
 861			}
 862			targetUser.resetName();
 863			targetUser.emit('nameTaken', {reason: user.name+" has forced you to change your name. "+targets[1]});
 864		} else {
 865			emit(socket, 'console', "User "+targetUser.name+" is no longer using that name.");
 866		}
 867		return false;
 868		break;
 869
 870	case 'forcerenameto':
 871	case 'frt':
 872		if (!target) return parseCommand(user, '?', cmd, room, socket);
 873		var targets = splitTarget(target);
 874		var targetUser = targets[0];
 875		if (!targetUser) {
 876			emit(socket, 'console', 'User '+targets[2]+' not found.');
 877			return false;
 878		}
 879		if (!targets[1]) {
 880			emit(socket, 'console', 'No new name was specified.');
 881			return false;
 882		}
 883		if (!user.can('forcerenameto', targetUser)) {
 884			emit(socket, 'console', '/forcerenameto - Access denied.');
 885			return false;
 886		}
 887
 888		if (targetUser.userid === toUserid(targets[2])) {
 889			var entry = ''+targetUser.name+' was forcibly renamed to '+targets[1]+' by '+user.name+'.';
 890			logModCommand(room, entry, true);
 891			rooms.lobby.sendAuth(entry);
 892			if (room.id !== 'lobby') {
 893				room.add(entry);
 894			} else {
 895				room.logEntry(entry);
 896			}
 897			targetUser.forceRename(targets[1]);
 898		} else {
 899			emit(socket, 'console', "User "+targetUser.name+" is no longer using that name.");
 900		}
 901		return false;
 902		break;
 903
 904	// INFORMATIONAL COMMANDS
 905
 906	case 'data':
 907	case '!data':
 908	case 'stats':
 909	case '!stats':
 910	case 'dex':
 911	case '!dex':
 912	case 'pokedex':
 913	case '!pokedex':
 914		showOrBroadcastStart(user, cmd, room, socket, message);
 915		var dataMessages = getDataMessage(target);
 916		for (var i=0; i<dataMessages.length; i++) {
 917			if (cmd.substr(0,1) !== '!') {
 918				sendData(socket, '>'+room.id+'\n'+dataMessages[i]);
 919			} else if (user.can('broadcast') && canTalk(user, room)) {
 920				room.add(dataMessages[i]);
 921			}
 922		}
 923		return false;
 924		break;
 925
 926	case 'learnset':
 927	case '!learnset':
 928	case 'learn':
 929	case '!learn':
 930	case 'learnall':
 931	case '!learnall':
 932	case 'learn5':
 933	case '!learn5':
 934		var lsetData = {set:{}};
 935		var targets = target.split(',');
 936		if (!targets[1]) return parseCommand(user, 'help', 'learn', room, socket);
 937		var template = Tools.getTemplate(targets[0]);
 938		var move = {};
 939		var problem;
 940		var all = (cmd.substr(cmd.length-3) === 'all');
 941
 942		if (cmd === 'learn5' || cmd === '!learn5') lsetData.set.level = 5;
 943
 944		showOrBroadcastStart(user, cmd, room, socket, message);
 945
 946		if (!template.exists) {
 947			showOrBroadcast(user, cmd, room, socket,
 948				'Pokemon "'+template.id+'" not found.');
 949			return false;
 950		}
 951
 952		for (var i=1, len=targets.length; i<len; i++) {
 953			move = Tools.getMove(targets[i]);
 954			if (!move.exists) {
 955				showOrBroadcast(user, cmd, room, socket,
 956					'Move "'+move.id+'" not found.');
 957				return false;
 958			}
 959			problem = Tools.checkLearnset(move, template, lsetData);
 960			if (problem) break;
 961		}
 962		var buffer = ''+template.name+(problem?" <span class=\"message-learn-cannotlearn\">can't</span> learn ":" <span class=\"message-learn-canlearn\">can</span> learn ")+(targets.length>2?"these moves":move.name);
 963		if (!problem) {
 964			var sourceNames = {E:"egg",S:"event",D:"dream world"};
 965			if (lsetData.sources || lsetData.sourcesBefore) buffer += " only when obtained from:<ul class=\"message-learn-list\">";
 966			if (lsetData.sources) {
 967				var sources = lsetData.sources.sort();
 968				var prevSource;
 969				var prevSourceType;
 970				for (var i=0, len=sources.length; i<len; i++) {
 971					var source = sources[i];
 972					if (source.substr(0,2) === prevSourceType) {
 973						if (prevSourceCount < 0) buffer += ": "+source.substr(2);
 974						else if (all || prevSourceCount < 3) buffer += ', '+source.substr(2);
 975						else if (prevSourceCount == 3) buffer += ', ...';
 976						prevSourceCount++;
 977						continue;
 978					}
 979					prevSourceType = source.substr(0,2);
 980					prevSourceCount = source.substr(2)?0:-1;
 981					buffer += "<li>gen "+source.substr(0,1)+" "+sourceNames[source.substr(1,1)];
 982					if (prevSourceType === '5E' && template.maleOnlyDreamWorld) buffer += " (cannot have DW ability)";
 983					if (source.substr(2)) buffer += ": "+source.substr(2);
 984				}
 985			}
 986			if (lsetData.sourcesBefore) buffer += "<li>any generation before "+(lsetData.sourcesBefore+1);
 987			buffer += "</ul>";
 988		}
 989		showOrBroadcast(user, cmd, room, socket,
 990			buffer);
 991		return false;
 992		break;
 993
 994	case 'uptime':
 995	case '!uptime':
 996		var uptime = process.uptime();
 997		var uptimeText;
 998		if (uptime > 24*60*60) {
 999			var uptimeDays = Math.floor(uptime/(24*60*60));
1000			uptimeText = ''+uptimeDays+' '+(uptimeDays == 1 ? 'day' : 'days');
1001			var uptimeHours = Math.floor(uptime/(60*60)) - uptimeDays*24;
1002			if (uptimeHours) uptimeText += ', '+uptimeHours+' '+(uptimeHours == 1 ? 'hour' : 'hours');
1003		} else {
1004			uptimeText = uptime.seconds().duration();
1005		}
1006		showOrBroadcastStart(user, cmd, room, socket, message);
1007		showOrBroadcast(user, cmd, room, socket,
1008			'<div class="infobox">' +
1009			'Uptime: <b>'+uptimeText+'</b>'+
1010			'</div>');
1011		return false;
1012		break;
1013
1014	case 'groups':
1015	case '!groups':
1016		showOrBroadcastStart(user, cmd, room, socket, message);
1017		showOrBroadcast(user, cmd, room, socket,
1018			'<div class="infobox">' +
1019			'+ <b>Voice</b> - They can use ! commands like !groups, and talk during moderated chat<br />' +
1020			'% <b>Driver</b> - The above, and they can also mute users and run tournaments<br />' +
1021			'@ <b>Moderator</b> - The above, and they can ban users and check for alts<br />' +
1022			'&amp; <b>Leader</b> - The above, and they can promote moderators and force ties<br />'+
1023			'~ <b>Administrator</b> - They can do anything, like change what this message says'+
1024			'</div>');
1025		return false;
1026		break;
1027
1028	case 'opensource':
1029	case '!opensource':
1030		showOrBroadcastStart(user, cmd, room, socket, message);
1031		showOrBroadcast(user, cmd, room, socket,
1032			'<div class="infobox">Pokemon Showdown is open source:<br />- Language: JavaScript<br />- <a href="https://github.com/Zarel/Pokemon-Showdown/commits/master" target="_blank">What\'s new?</a><br />- <a href="https://github.com/Zarel/Pokemon-Showdown" target="_blank">Server source code</a><br />- <a href="https://github.com/Zarel/Pokemon-Showdown-Client" target="_blank">Client source code</a></div>');
1033		return false;
1034		break;
1035
1036	case 'avatars':
1037	case '!avatars':
1038		showOrBroadcastStart(user, cmd, room, socket, message);
1039		showOrBroadcast(user, cmd, room, socket,
1040			'<div class="infobox">Want a custom avatar?<br />- <a href="/sprites/trainers/" target="_blank">How to change your avatar</a></div>');
1041		return false;
1042		break;
1043
1044	case 'intro':
1045	case 'introduction':
1046	case '!intro':
1047	case '!introduction':
1048		showOrBroadcastStart(user, cmd, room, socket, message);
1049		showOrBroadcast(user, cmd, room, socket,
1050			'<div class="infobox">New to competitive pokemon?<br />' +
1051			'- <a href="http://www.smogon.com/dp/articles/intro_comp_pokemon" target="_blank">An introduction to competitive pokemon</a><br />' +
1052			'- <a href="http://www.smogon.com/bw/articles/bw_tiers" target="_blank">What do "OU", "UU", etc mean?</a><br />' +
1053			'- <a href="http://www.smogon.com/bw/banlist/" target="_blank">What are the rules for each format? What is "Sleep Clause"?</a>' +
1054			'</div>');
1055		return false;
1056		break;
1057		
1058	case 'calc':
1059	case '!calc':
1060	case 'calculator':
1061	case '!calculator':
1062		showOrBroadcastStart(user, cmd, room, socket, message);
1063		showOrBroadcast(user, cmd , room , socket,
1064			'<div class="infobox">Pokemon Showdown! damage calculator. (Courtesy of Honko)<br />' +
1065			'- <a href="http://pokemonshowdown.com/damagecalc/" target="_blank">Damage Calculator</a><br />' +
1066			'</div>');
1067		return false;
1068		break;
1069	
1070	case 'cap':
1071	case '!cap':
1072		showOrBroadcastStart(user, cmd, room, socket, message);
1073		showOrBroadcast(user, cmd, room, socket,
1074			'<div class="infobox">An introduction to the Create-A-Pokemon project:<br />' +
1075			'- <a href="http://www.smogon.com/cap/" target="_blank">CAP project website and description</a><br />' +
1076			'- <a href="http://www.smogon.com/forums/showthread.php?t=48782" target="_blank">What Pokemon have been made?</a><br />' +
1077			'- <a href="http://www.smogon.com/forums/showthread.php?t=3464513" target="_blank">Talk about the metagame here</a><br />' +
1078			'- <a href="http://www.smogon.com/forums/showthread.php?t=3466826" target="_blank">Practice BW CAP teams</a>' +
1079			'</div>');
1080		return false;
1081		break;
1082
1083	case 'om':
1084	case 'othermetas':
1085	case '!om':
1086	case '!othermetas':
1087		showOrBroadcastStart(user, cmd, room, socket, message);
1088		showOrBroadcast(user, cmd, room, socket,
1089			'<div class="infobox">Information on the Other Metagames:<br />' +
1090			'- <a href="http://www.smogon.com/forums/showthread.php?t=3463764" target="_blank">Balanced Hackmons</a><br />' +
1091			'- <a href="http://www.smogon.com/forums/showthread.php?t=3471810" target="_blank">Dream World OU</a><br />' +
1092			'- <a href="http://www.smogon.com/forums/showthread.php?t=3467120" target="_blank">Glitchmons</a><br />' +
1093			'- <a href="http://www.smogon.com/sim/seasonal" target="_blank">Seasonal: Spring Forward</a><br />' +
1094			'- <a href="http://www.smogon.com/forums/showthread.php?t=3476469" target="_blank">Smogon Doubles</a><br />' +
1095			'- <a href="http://www.smogon.com/forums/showthread.php?t=3471161" target="_blank">VGC 2013</a>' +
1096			'</div>');
1097		return false;
1098		break;
1099
1100	case 'rules':
1101	case 'rule':
1102	case '!rules':
1103	case '!rule':
1104		showOrBroadcastStart(user, cmd, room, socket, message);
1105		showOrBroadcast(user, cmd, room, socket,
1106			'<div class="infobox">Please follow the rules:<br />' +
1107			'- <a href="http://pokemonshowdown.com/rules" target="_blank">Rules</a><br />' +
1108			'</div>');
1109		return false;
1110		break;
1111		
1112	case 'faq':
1113	case '!faq':
1114		target = target.toLowerCase();
1115		var buffer = '<div class="infobox">';
1116		var matched = false;
1117		if (!target || target === 'all') {
1118			matched = true;
1119			buffer += '<a href="http://www.smogon.com/sim/faq" target="_blank">Frequently Asked Questions</a><br />';
1120		}
1121		if (target === 'all' || target === 'deviation') {
1122			matched = true;
1123			buffer += '<a href="http://www.smogon.com/sim/faq#deviation" target="_blank">Why did this user gain or lose so many points?</a><br />';
1124		}
1125		if (target === 'all' || target === 'doubles' || target === 'triples' || target === 'rotation') {
1126			matched = true;
1127			buffer += '<a href="http://www.smogon.com/sim/faq#doubles" target="_blank">Can I play doubles/triples/rotation battles here?</a><br />';
1128		}
1129		if (target === 'all' || target === 'randomcap') {
1130			matched = true;
1131			buffer += '<a href="http://www.smogon.com/sim/faq#randomcap" target="_blank">What is this fakemon and what is it doing in my random battle?</a><br />';
1132		}
1133		if (target === 'all' || target === 'restarts') {
1134			matched = true;
1135			buffer += '<a href="http://www.smogon.com/sim/faq#restarts" target="_blank">Why is the server restarting?</a><br />';
1136		}
1137		if (target === 'all' || target === 'staff') {
1138			matched = true;
1139			buffer += '<a href="http://www.smogon.com/sim/staff_faq" target="_blank">Staff FAQ</a><br />';
1140		}
1141		if (!matched) {
1142			emit(socket, 'console', 'The FAQ entry "'+target+'" was not found. Try /faq for general help.');
1143			return false;
1144		}
1145		buffer += '</div>';
1146		showOrBroadcastStart(user, cmd, room, socket, message);
1147		showOrBroadcast(user, cmd, room, socket, buffer);
1148		return false;
1149		break;
1150
1151	case 'banlists':
1152	case 'tiers':
1153	case '!banlists':
1154	case '!tiers':
1155		showOrBroadcastStart(user, cmd, room, socket, message);
1156		showOrBroadcast(user, cmd, room, socket,
1157			'<div class="infobox">Smogon tiers:<br />' +
1158			'- <a href="http://www.smogon.com/bw/banlist/" target="_blank">The banlists for each tier</a><br />' +
1159			'- <a href="http://www.smogon.com/bw/tiers/uber" target="_blank">Uber Pokemon</a><br />' +
1160			'- <a href="http://www.smogon.com/bw/tiers/ou" target="_blank">Overused Pokemon</a><br />' +
1161			'- <a href="http://www.smogon.com/bw/tiers/uu" target="_blank">Underused Pokemon</a><br />' +
1162			'- <a href="http://www.smogon.com/bw/tiers/ru" target="_blank">Rarelyused Pokemon</a><br />' +
1163			'- <a href="http://www.smogon.com/bw/tiers/nu" target="_blank">Neverused Pokemon</a><br />' +
1164			'- <a href="http://www.smogon.com/bw/tiers/lc" target="_blank">Little Cup Pokemon</a><br />' +
1165			'</div>');
1166		return false;
1167		break;
1168
1169	case 'analysis':
1170	case '!analysis':
1171	case 'strategy':
1172	case '!strategy':
1173	case 'smogdex':
1174	case '!smogdex':
1175		var targets = target.split(',');
1176		var pokemon = Tools.getTemplate(targets[0]);
1177		var item = Tools.getItem(targets[0]);
1178		var move = Tools.getMove(targets[0]);
1179		var ability = Tools.getAbility(targets[0]);
1180		var atLeastOne = false;
1181		var generation = (targets[1] || "bw").trim().toLowerCase();
1182		var genNumber = 5;
1183
1184		showOrBroadcastStart(user, cmd, room, socket, message);
1185
1186		if (generation === "bw" || generation === "bw2" || generation === "5" || generation === "five") {
1187			generation = "bw";
1188		} else if (generation === "dp" || generation === "dpp" || generation === "4" || generation === "four") {
1189			generation = "dp";
1190			genNumber = 4;
1191		} else if (generation === "adv" || generation === "rse" || generation === "rs" || generation === "3" || generation === "three") {
1192			generation = "rs";
1193			genNumber = 3;
1194		} else if (generation === "gsc" || generation === "gs" || generation === "2" || generation === "two") {
1195			generation = "gs";
1196			genNumber = 2;
1197		} else if(generation === "rby" || generation === "rb" || generation === "1" || generation === "one") {
1198			generation = "rb";
1199			genNumber = 1;
1200		} else {
1201			generation = "bw";
1202		}
1203		
1204		// Pokemon
1205		if (pokemon.exists) {
1206			atLeastOne = true;
1207			if (genNumber < pokemon.gen) {
1208				showOrBroadcast(user, cmd, room, socket, pokemon.name+' did not exist in '+generation.toUpperCase()+'!');
1209				return false;
1210			}
1211			if (pokemon.tier === 'G4CAP' || pokemon.tier === 'G5CAP') {
1212				generation = "cap";
1213			}
1214	
1215			var poke = pokemon.name.toLowerCase();
1216			if (poke === 'nidoranm') poke = 'nidoran-m';
1217			if (poke === 'nidoranf') poke = 'nidoran-f';
1218			if (poke === 'farfetch\'d') poke = 'farfetchd';
1219			if (poke === 'mr. mime') poke = 'mr_mime';
1220			if (poke === 'mime jr.') poke = 'mime_jr';
1221			if (poke === 'deoxys-attack' || poke === 'deoxys-defense' || poke === 'deoxys-speed' || poke === 'kyurem-black' || poke === 'kyurem-white') poke = poke.substr(0,8);
1222			if (poke === 'wormadam-trash') poke = 'wormadam-s';
1223			if (poke === 'wormadam-sandy') poke = 'wormadam-g';
1224			if (poke === 'rotom-wash' || poke === 'rotom-frost' || poke === 'rotom-heat') poke = poke.substr(0,7);
1225			if (poke === 'rotom-mow') poke = 'rotom-c';
1226			if (poke === 'rotom-fan') poke = 'rotom-s';
1227			if (poke === 'giratina-origin' || poke === 'tornadus-therian' || poke === 'landorus-therian') poke = poke.substr(0,10);
1228			if (poke === 'shaymin-sky') poke = 'shaymin-s';
1229			if (poke === 'arceus') poke = 'arceus-normal';
1230			if (poke === 'thundurus-therian') poke = 'thundurus-t';
1231	
1232			showOrBroadcast(user, cmd, room, socket,
1233				'<a href="http://www.smogon.com/'+generation+'/pokemon/'+poke+'" target="_blank">'+generation.toUpperCase()+' '+pokemon.name+' analysis</a>, brought to you by <a href="http://www.smogon.com" target="_blank">Smogon University</a>');
1234		}
1235		
1236		// Item
1237		if (item.exists && genNumber > 1) {
1238			atLeastOne = true;
1239			var itemName = item.name.toLowerCase().replace(' ', '_');
1240			showOrBroadcast(user, cmd, room, socket,
1241					'<a href="http://www.smogon.com/'+generation+'/items/'+itemName+'" target="_blank">'+generation.toUpperCase()+' '+item.name+' item analysis</a>, brought to you by <a href="http://www.smogon.com" target="_blank">Smogon University</a>');
1242		}
1243		
1244		// Ability
1245		if (ability.exists && genNumber > 2) {
1246			atLeastOne = true;
1247			var abilityName = ability.name.toLowerCase().replace(' ', '_');
1248			showOrBroadcast(user, cmd, room, socket,
1249					'<a href="http://www.smogon.com/'+generation+'/abilities/'+abilityName+'" target="_blank">'+generation.toUpperCase()+' '+ability.name+' ability analysis</a>, brought to you by <a href="http://www.smogon.com" target="_blank">Smogon University</a>');
1250		}
1251		
1252		// Move
1253		if (move.exists) {
1254			atLeastOne = true;
1255			var moveName = move.name.toLowerCase().replace(' ', '_');
1256			showOrBroadcast(user, cmd, room, socket,
1257					'<a href="http://www.smogon.com/'+generation+'/moves/'+moveName+'" target="_blank">'+generation.toUpperCase()+' '+move.name+' move analysis</a>, brought to you by <a href="http://www.smogon.com" target="_blank">Smogon University</a>');
1258		}
1259		
1260		if (!atLeastOne) {
1261			showOrBroadcast(user, cmd, room, socket, 'Pokemon, item, move, or ability not found for generation ' + generation.toUpperCase() + '.');
1262			return false;
1263		}
1264		
1265		return false;
1266		break;
1267
1268	case 'join':
1269		var targetRoom = Rooms.get(target);
1270		if (!targetRoom) {
1271			emit(socket, 'console', "The room '"+target+"' does not exist.");
1272			return false;
1273		}
1274		if (!user.joinRoom(targetRoom, socket)) {
1275			emit(socket, 'console', "The room '"+target+"' could not be joined (most likely, you're already in it).");
1276			return false;
1277		}
1278		return false;
1279		break;
1280
1281	case 'leave':
1282	case 'part':
1283		if (room.id === 'lobby') return false;
1284
1285		user.leaveRoom(room, socket);
1286		return false;
1287		break;
1288
1289	// Battle commands
1290
1291	case 'reset':
1292	case 'restart':
1293		emit(socket, 'console', 'This functionality is no longer available.');
1294		return false;
1295		break;
1296
1297	case 'move':
1298	case 'attack':
1299	case 'mv':
1300		if (!room.decision) { emit(socket, 'console', 'You can only do this in battle rooms.'); return false; }
1301
1302		room.decision(user, 'choose', 'move '+target);
1303		return false;
1304		break;
1305
1306	case 'switch':
1307	case 'sw':
1308		if (!room.decision) { emit(socket, 'console', 'You can only do this in battle rooms.'); return false; }
1309
1310		room.decision(user, 'choose', 'switch '+parseInt(target,10));
1311		return false;
1312		break;
1313
1314	case 'choose':
1315		if (!room.decision) { emit(socket, 'console', 'You can only do this in battle rooms.'); return false; }
1316
1317		room.decision(user, 'choose', target);
1318		return false;
1319		break;
1320
1321	case 'undo':
1322		if (!room.decision) { emit(socket, 'console', 'You can only do this in battle rooms.'); return false; }
1323
1324		room.decision(user, 'undo', target);
1325		return false;
1326		break;
1327
1328	case 'team':
1329		if (!room.decision) { emit(socket, 'console', 'You can only do this in battle rooms.'); return false; }
1330
1331		room.decision(user, 'choose', 'team '+target);
1332		return false;
1333		break;
1334
1335	case 'search':
1336	case 'cancelsearch':
1337		if (!room.searchBattle) { emit(socket, 'console', 'You can only do this in lobby rooms.'); return false; }
1338
1339		if (target) {
1340			room.searchBattle(user, target);
1341		} else {
1342			room.cancelSearch(user);
1343		}
1344		return false;
1345		break;
1346
1347	case 'challenge':
1348	case 'chall':
1349		var targets = splitTarget(target);
1350		var targetUser = targets[0];
1351		target = targets[1];
1352		if (!targetUser || !targetUser.connected) {
1353			emit(socket, 'message', "The user '"+targets[2]+"' was not found.");
1354			return false;
1355		}
1356		if (targetUser.blockChallenges && !user.can('bypassblocks', targetUser)) {
1357			emit(socket, 'message', "The user '"+targets[2]+"' is not accepting challenges right now.");
1358			return false;
1359		}
1360		if (typeof target !== 'string') target = 'customgame';
1361		var problems = Tools.validateTeam(user.team, target);
1362		if (problems) {
1363			emit(socket, 'message', "Your team was rejected for the following reasons:\n\n- "+problems.join("\n- "));
1364			return false;
1365		}
1366		user.makeChallenge(targetUser, target);
1367		return false;
1368		break;
1369		
1370	case 'away':
1371	case 'idle':
1372	case 'blockchallenges':
1373		user.blockChallenges = true;
1374		emit(socket, 'console', 'You are now blocking all incoming challenge requests.');
1375		return false;
1376		break;
1377
1378	case 'back':
1379	case 'allowchallenges':
1380		user.blockChallenges = false;
1381		emit(socket, 'console', 'You are available for challenges from now on.');
1382		return false;
1383		break;
1384
1385	case 'cancelchallenge':
1386	case 'cchall':
1387		user.cancelChallengeTo(target);
1388		return false;
1389		break;
1390
1391	case 'accept':
1392		var userid = toUserid(target);
1393		var format = 'debugmode';
1394		if (user.challengesFrom[userid]) format = user.challengesFrom[userid].format;
1395		var problems = Tools.validateTeam(user.team, format);
1396		if (problems) {
1397			emit(socket, 'message', "Your team was rejected for the following reasons:\n\n- "+problems.join("\n- "));
1398			return false;
1399		}
1400		user.acceptChallengeFrom(userid);
1401		return false;
1402		break;
1403
1404	case 'reject':
1405		user.rejectChallengeFrom(toUserid(target));
1406		return false;
1407		break;
1408
1409	case 'saveteam':
1410	case 'utm':
1411		try {
1412			user.team = JSON.parse(target);
1413			user.emit('update', {team: 'saved', room: 'teambuilder'});
1414		} catch (e) {
1415			emit(socket, 'console', 'Not a valid team.');
1416		}
1417		return false;
1418		break;
1419
1420	case 'joinbattle':
1421	case 'partbattle':
1422		if (!room.joinBattle) { emit(socket, 'console', 'You can only do this in battle rooms.'); return false; }
1423
1424		room.joinBattle(user);
1425		return false;
1426		break;
1427
1428	case 'leavebattle':
1429		if (!room.leaveBattle) { emit(socket, 'console', 'You can only do this in battle rooms.'); return false; }
1430
1431		room.leaveBattle(user);
1432		return false;
1433		break;
1434
1435	case 'kickinactive':
1436		if (room.requestKickInactive) {
1437			room.requestKickInactive(user);
1438		} else {
1439			emit(socket, 'console', 'You can only kick inactive players from inside a room.');
1440		}
1441		return false;
1442		break;
1443
1444	case 'timer':
1445		target = toId(target);
1446		if (room.requestKickInactive) {
1447			if (target === 'off' || target === 'stop') {
1448				room.stopKickInactive(user, user.can('timer'));
1449			} else if (target === 'on' || !target) {
1450				room.requestKickInactive(user, user.can('timer'));
1451			} else {
1452				emit(socket, 'console', "'"+target+"' is not a recognized timer state.");
1453			}
1454		} else {
1455			emit(socket, 'console', 'You can only set the timer from inside a room.');
1456		}
1457		return false;
1458		break;
1459		break;
1460
1461	case 'lobbychat':
1462		target = toId(target);
1463		if (target === 'off') {
1464			user.blockLobbyChat = true;
1465			emit(socket, 'console', 'You are now blocking lobby chat.');
1466		} else {
1467			user.blockLobbyChat = false;
1468			emit(socket, 'console', 'You are now receiving lobby chat.');
1469		}
1470		return false;
1471		break;
1472		break;
1473
1474	case 'a':
1475		if (user.can('battlemessage')) {
1476			// secret sysop command
1477			room.battle.add(target);
1478			return false;
1479		}
1480		break;
1481
1482	// Admin commands
1483
1484	case 'forcewin':
1485	case 'forcetie':
1486		if (!user.can('forcewin') || !room.battle) {
1487			emit(socket, 'console', '/forcewin - Access denied.');
1488			return false;
1489		}
1490
1491		room.battle.endType = 'forced';
1492		if (!target) {
1493			room.battle.tie();
1494			logModCommand(room,user.name+' forced a tie.',true);
1495			return false;
1496		}
1497		target = Users.get(target);
1498		if (target) target = target.userid;
1499		else target = '';
1500
1501		if (target) {
1502			room.battle.win(target);
1503			logModCommand(room,user.name+' forced a win for '+target+'.',true);
1504		}
1505
1506		return false;
1507		break;
1508
1509	case 'potd':
1510		if (!user.can('potd')) {
1511			emit(socket, 'console', '/potd - Access denied.');
1512			return false;
1513		}
1514
1515		config.potd = target;
1516		Simulator.eval('config.potd = \''+toId(target)+'\'');
1517		if (target) {
1518			rooms.lobby.addRaw('<div class="broadcast-blue"><b>The Pokemon of the Day is now '+target+'!</b><br />This Pokemon will be guaranteed to show up in random battles.</div>');
1519			logModCommand(room, 'The Pokemon of the Day was changed to '+target+' by '+user.name+'.', true);
1520		} else {
1521			rooms.lobby.addRaw('<div class="broadcast-blue"><b>The Pokemon of the Day was removed!</b><br />No pokemon will be guaranteed in random battles.</div>');
1522			logModCommand(room, 'The Pokemon of the Day was removed by '+user.name+'.', true);
1523		}
1524		return false;
1525		break;
1526
1527	case 'lockdown':
1528		if (!user.can('lockdown')) {
1529			emit(socket, 'console', '/lockdown - Access denied.');
1530			return false;
1531		}
1532
1533		lockdown = true;
1534		for (var id in rooms) {
1535			rooms[id].addRaw('<div class="broadcast-red"><b>The server is restarting soon.</b><br />Please finish your battles quickly. No new battles can be started until the server resets in a few minutes.</div>');
1536			if (rooms[id].requestKickInactive) rooms[id].requestKickInactive(user, true);
1537		}
1538
1539		rooms.lobby.logEntry(user.name + ' used /lockdown');
1540
1541		return false;
1542		break;
1543
1544	case 'endlockdown':
1545		if (!user.can('lockdown')) {
1546			emit(socket, 'console', '/endlockdown - Access denied.');
1547			return false;
1548		}
1549
1550		lockdown = false;
1551		for (var id in rooms) {
1552			rooms[id].addRaw('<div class="broadcast-green"><b>The server shutdown was canceled.</b></div>');
1553		}
1554
1555		rooms.lobby.logEntry(user.name + ' used /endlockdown');
1556
1557		return false;
1558		break;
1559
1560	case 'kill':
1561		if (!user.can('lockdown')) {
1562			emit(socket, 'console', '/lockdown - Access denied.');
1563			return false;
1564		}
1565
1566		if (!lockdown) {
1567			emit(socket, 'console', 'For safety reasons, /kill can only be used during lockdown.');
1568			return false;
1569		}
1570
1571		if (updateServerLock) {
1572			emit(socket, 'console', 'Wait for /updateserver to fin

Large files files are truncated, but you can click here to view the full file