Add a resource page indexing all public commands (#2477)

* Add a resource page indexing all public commands

* Add preact
This commit is contained in:
Mia 2025-07-22 13:51:10 -05:00 committed by GitHub
parent 30a3bba112
commit fa99d0ee25
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 537 additions and 10 deletions

8
build
View File

@ -49,6 +49,9 @@ case 'minidex':
case 'sprites':
execSync(`node ./build-tools/build-minidex`, options);
break;
case 'commands':
execSync(`node ./build-tools/build-commands`, options);
break;
case 'replays':
execSync(`node ./build-tools/build-replays`, options);
process.exit();
@ -68,6 +71,9 @@ execSync(`node ./build-tools/update` + full, options);
if (full) {
try {
execSync(`node ./build-tools/build-commands`, options);
execSync(`node ./build-tools/build-replays`, options);
} catch (e) {}
} catch (e) {
console.error(e);
}
}

75
build-tools/build-commands Executable file
View File

@ -0,0 +1,75 @@
#!/usr/bin/env node
/**
* Builds out a cache of commands from the server & their help commands
*/
const fs = require('fs');
console.log('Building `data/commands.json` index...');
const noop = () => null;
Object.assign(globalThis, {
Config: { routes: { root: '' } },
Monitor: { crashlog: noop },
Punishments: { addRoomPunishmentType: noop },
Rooms: {
rooms: new Map(),
search: noop,
get: noop,
RoomGame: class {},
RoomGamePlayer: class {},
SimpleRoomGame: class {},
},
toID: str => ("" + str).toLowerCase().replace(/[^a-z0-9]/ig, ''),
});
const { Chat } = require('../caches/pokemon-showdown/dist/server/chat');
// yes, these have to be done separately, yes it sucks
Object.assign(globalThis, {
Tournaments: require('../caches/pokemon-showdown/dist/server/tournaments'),
Chat,
Dex: { includeData: noop },
});
Chat.commands = Object.create(null);
Chat.pages = Object.create(null);
Chat.loadPluginDirectory("dist/server/chat-commands");
Chat.loadPlugin(globalThis.Tournaments, "tournaments");
Chat.loadPluginDirectory("dist/server/chat-plugins");
const recurseCommands = (table = Chat.commands, namespace = '', results = {}) => {
for (const cmd in table) {
const handler = table[cmd];
if (!handler || ['string', 'boolean'].includes(typeof handler) || cmd === 'aliases') {
continue;
}
const fullCmd = (namespace + ' ' + cmd).trim();
if (Array.isArray(handler)) {
/** handler = Chat.parseCommand(`/${fullCmd.replace('help', '')}`).handler;
if (handler.requiredPermission) continue;
results.push(fullCmd); */
continue;
}
if (typeof handler === 'object') {
recurseCommands(handler, (namespace + " " + cmd).trim(), results);
continue;
}
const pKey = handler.plugin.split('/server/')[1];
if (!results[pKey]) {
results[pKey] = [];
}
if (!results[pKey].includes(fullCmd) && !handler.requiredPermission) {
results[pKey].push(fullCmd);
}
}
return results;
};
fs.writeFileSync(
'play.pokemonshowdown.com/data/commands.js',
`exports.BattleChatCommands = ${JSON.stringify(recurseCommands())}`
);
console.log('DONE');
process.exit(0);

View File

@ -49,7 +49,7 @@ export default configure([
"BattleOtherAnims": false, "BattlePokedex": false, "BattlePokemonSprites": false, "BattlePokemonSpritesBW": false, "BattleSearchCountIndex": false, "BattleSearchIndex": false, "BattleArticleTitles": false,
"BattleSearchIndexOffset": false, "BattleSearchIndexType": false, "BattleStatIDs": false, "BattleStatNames": false, "BattleStatusAnims": false, "BattleStatuses": false, "BattleTeambuilderTable": false,
"ModifiableValue": false, "BattleStatGuesser": false, "BattleStatOptimizer": false, "BattleText": true, "BattleTextAFD": false, "BattleTextNotAFD": false,
"BattleTextParser": false,
"BattleTextParser": false, "BattleChatCommands": false,
// Generic global variables
"Config": false, "BattleSearch": false, "Storage": false, "Dex": false, "DexSearch": false,
@ -57,7 +57,7 @@ export default configure([
"ChatHistory": false, "Topbar": false, "UserList": false,
// Rooms
"Room": false, "BattleRoom": false, "ChatRoom": false, "ConsoleRoom": false, "HTMLRoom": false, "LadderRoom": false, "MainMenuRoom": false, "RoomsRoom": false, "BattlesRoom": false, "TeambuilderRoom": false,
"Room": false, "BattleRoom": false, "ChatRoom": false, "ConsoleRoom": false, "HTMLRoom": false, "LadderRoom": false, "MainMenuRoom": false, "RoomsRoom": false, "BattlesRoom": false, "TeambuilderRoom": false, "ResourceRoom": false,
// Tons of popups
"Popup": false, "ForfeitPopup": false, "BracketPopup": false, "LoginPasswordPopup": false, "UserPopup": false, "UserOptionsPopup": false, "UserOptions": false, "TeamPopup": false,

View File

@ -130,5 +130,7 @@ https://psim.us/dev
<script src="//play.pokemonshowdown.com/js/battle-dex-search.js?"></script>
<script src="//play.pokemonshowdown.com/js/search.js?"></script>
<script src="//play.pokemonshowdown.com/data/commands.js?"></script>
<script src="//play.pokemonshowdown.com/data/aliases.js?" async></script>
<script src="//play.pokemonshowdown.com/js/clean-cookies.php" async></script>

View File

@ -2068,6 +2068,115 @@
}
});
this.ResourceRoom = HTMLRoom.extend({
events: { 'keydown [name=search]': 'searchCommands' },
type: 'resources',
title: 'Help',
search: '',
searchCommands: function (e) {
this.search = toID($(e.currentTarget).val());
this.update();
},
initialize: function () {
this.$el.addClass('ps-room-light').addClass('scrollable');
var buf = '<div class="pad">';
buf += '<h2>PS! Informational Resources</h2><hr />';
buf += 'PS! is a wide and varied site, with more facets than can be covered here easily.<br />';
buf += 'While this page chiefly documents the ever-shifting set of commands available to PS! users, here are some useful resources for newcomers:<br />';
buf += '- <a href="https://www.smogon.com/forums/threads/3676132/">Beginner\'s Guide to Pok&eacute;mon Showdown</a><br />';
buf += '- <a href="https://www.smogon.com/dp/articles/intro_comp_pokemon">An introduction to competitive Pok&eacute;mon</a><br />';
buf += '- <a href="https://www.smogon.com/sm/articles/sm_tiers">What do \'OU\', \'UU\', etc mean?</a><br />';
buf += '- <a href="https://www.smogon.com/dex/ss/formats/">What are the rules for each format?</a><br />';
buf += '- <a href="https://www.smogon.com/ss/articles/clauses">What is \'Sleep Clause\' and other clauses?</a><br />';
buf += '- <a href="https://www.smogon.com/articles/getting-started">Next Steps for Competitive Battling</a><br />';
buf += '- <button class="button" name="send" value="/report">Report a user</button><br />';
buf += '- <button class="button" name="send" value="/join help">Join the Help room for live help</button>';
buf += '<hr /><strong>Commands:</strong><br />';
buf += 'Within any of the chats, and in private messages, it is possible to type in commands (messages beginning with <code>/</code>) in order to perform a particular action. ';
buf += 'A great number of these commands exist, with some only available to certain users. ';
buf += 'For instance, you can broadcast commands to others with the <code>!</code> command prefix, but only when you\'re a player in a battle or a Voice (+) user. ';
buf += 'For more information on ranks, type <code>/groups</code> in any chat. You can also use the "chat self" button on your username in the top right if you need a place to send these commands ';
buf += 'without joining a room.<br /><br />';
buf += '<details class="readmore"><summary>Here\'s a list of the most useful commands for the average Pokemon Showdown experience:</summary>';
buf += '<p>COMMANDS: /report, /msg, /reply, /logout, /challenge, /search, /rating, /whois, /user, /join, /leave, /userauth, /roomauth</p>';
buf += '<p>BATTLE ROOM COMMANDS: /savereplay, /hideroom, /inviteonly, /invite, /timer, /forfeit</p>';
buf += '<p>OPTION COMMANDS: /nick, /avatar, /ignore, /status, /away, /busy, /back, /timestamps, /highlight, /showjoins, /hidejoins, /blockchallenges, /blockpms</p>';
buf += '<p>INFORMATIONAL/RESOURCE COMMANDS: /groups, /faq, /rules, /intro, /formatshelp, /othermetas, /analysis, /punishments, /calc, /git, /cap, /roomhelp, /roomfaq (replace / with ! to broadcast. Broadcasting requires: + % @ # ~)</p>';
buf += '<p>DATA COMMANDS: /data, /dexsearch, /movesearch, /itemsearch, /learn, /statcalc, /effectiveness, /weakness, /coverage, /randommove, /randompokemon (replace / with ! to broadcast. Broadcasting requires: + % @ # ~)</p>';
buf += '<p>For an overview of room commands, use /roomhelp</p>';
buf += '<p>For details of a specific command, you can use the command <code>/help [command]</code>, for example <code>/help data</code>.</p>';
buf += '</details><br />';
buf += 'A complete list of commands available to regular users is provided below. Use <code>/help [commandname]</code> for more in-depth information on how to use them.';
buf += '<hr />';
buf += '<label for="search">Search/filter commands:</label> ';
buf += '<input name="search" placeholder="search" style="width: 25%" value="' + this.search + '" /><br />';
buf += '<span name="cmdbuffer">' + this.getCommandList() + '</span>';
this.$el.html(buf);
},
update: function () {
this.$el.find('[name="cmdbuffer"]').html(this.getCommandList());
},
getCommandList: function () {
var buf = '';
var search = this.search;
var keys = Object.keys(BattleChatCommands).sort(function (a, b) {
// pin info to the top ALWAYS
if (b.endsWith('info')) return 2;
// prefer default commands near the top, more generally useful
if (b.includes('chat-commands')) return 1;
var aCount = BattleChatCommands[a].filter(function (x) {
return toID(x).includes(search);
}).length;
var bCount = BattleChatCommands[b].filter(function (x) {
return toID(x).includes(search);
}).length;
return (bCount - aCount) || a.localeCompare(b);
}).filter(function (plugin) {
return !(plugin.endsWith('admin'));
});
for (var i = 0; i < keys.length; i++) {
var pluginName = keys[i];
var cmdTable = BattleChatCommands[pluginName];
if (!cmdTable.length) continue;
if (pluginName.startsWith('chat-plugins')) {
pluginName = 'Chat plugin: ' + pluginName.split('/').slice(1).join('/');
} else if (pluginName.startsWith('chat-commands')) {
pluginName = 'Core commands: ' + pluginName.split('/').slice(1).join('/'); ;
}
var matchedCmds = [];
for (var j = 0; j < cmdTable.length; j++) {
if (this.search.length && !toID(cmdTable[j]).includes(this.search)) {
continue;
}
matchedCmds.push('<li>' + cmdTable[j] + '</li>');
}
var open = ((
this.search.length && matchedCmds.length) || pluginName.includes('Core')
) ? ' open' : '';
var empty = this.search.length && !matchedCmds.length;
if (!empty) buf += '<details class="readmore" ' + open + '><summary>';
buf += '<strong>' + pluginName + '</strong>';
if (!empty) {
buf += '</summary><ul>';
buf += matchedCmds.join('');
buf += '</ul></details>';
} else {
buf += '<br /><br />';
}
buf += '<br />';
}
return buf;
},
join: function () {},
leave: function () {}
});
}).call(this, jQuery);
function ChatHistory() {

View File

@ -60,7 +60,8 @@
buf += '<div class="menugroup"><p><button class="button mainmenu4 onlineonly disabled" name="joinRoom" value="battles">Watch a battle</button></p>';
buf += '<p><button class="button mainmenu5 onlineonly disabled" name="finduser">Find a user</button></p>';
buf += '<p><button class="button mainmenu6 onlineonly disabled" name="send" value="/friends">Friends</button></p></div>';
buf += '<p><button class="button mainmenu6 onlineonly disabled" name="send" value="/friends">Friends</button></p>';
buf += '<p><button class="button mainmenu7" name="joinRoom" value="resources">Info & Resources</button></p></div>';
this.$('.mainmenu').html(buf);

View File

@ -86,6 +86,8 @@
return buf + '><i class="fa fa-pencil-square-o"></i> <span>Teambuilder</span></a><button class="closebutton" name="closeRoom" value="' + 'teambuilder" aria-label="Close"><i class="fa fa-times-circle"></i></button></li>';
case 'ladder':
return buf + '><i class="fa fa-list-ol"></i> <span>Ladder</span></a><button class="closebutton" name="closeRoom" value="' + 'ladder" aria-label="Close"><i class="fa fa-times-circle"></i></button></li>';
case 'resources':
return buf + '><i class="fa fa-question-circle"></i> <span>Resources</span></a><button class="closebutton" name="closeRoom" value="' + 'ladder" aria-label="Close"><i class="fa fa-times-circle"></i></button></li>';
case 'battles':
return buf + '><i class="fa fa-caret-square-o-right"></i> <span>Battles</span></a><button class="closebutton" name="closeRoom" value="' + 'battles" aria-label="Close"><i class="fa fa-times-circle"></i></button></li>';
case 'rooms':
@ -130,7 +132,12 @@
this.$('.logo').show();
this.$('.maintabbar').removeClass('minitabbar');
var buf = '<ul>' + this.renderRoomTab(app.rooms['']) + this.renderRoomTab(app.rooms['teambuilder']) + this.renderRoomTab(app.rooms['ladder']) + '</ul>';
var buf = '<ul>' + (
this.renderRoomTab(app.rooms['']) +
this.renderRoomTab(app.rooms['teambuilder']) +
this.renderRoomTab(app.rooms['ladder']) +
this.renderRoomTab(app.rooms['resources'])
) + '</ul>';
var sideBuf = '';
var notificationCount = app.rooms[''].notifications ? 1 : 0;

View File

@ -1691,6 +1691,7 @@ function toId() {
'ladder': LadderRoom,
'lobby': ChatRoom,
'staff': ChatRoom,
'resources': ResourceRoom,
'constructor': ChatRoom
};
var typeTable = {
@ -1727,7 +1728,7 @@ function toId() {
if (this.curSideRoom === oldRoom) this.curSideRoom = room;
if (this.sideRoom === oldRoom) this.sideRoom = room;
}
if (['', 'teambuilder', 'ladder', 'rooms'].indexOf(room.id) < 0) {
if (['', 'teambuilder', 'ladder', 'rooms', 'resources'].indexOf(room.id) < 0) {
if (room.isSideRoom) {
this.sideRoomList.push(room);
} else {

View File

@ -0,0 +1,145 @@
"use strict";function _inheritsLoose(t,o){t.prototype=Object.create(o.prototype),t.prototype.constructor=t,_setPrototypeOf(t,o);}function _setPrototypeOf(t,e){return _setPrototypeOf=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t;},_setPrototypeOf(t,e);}var
ResourceRoom=function(_PSRoom){
function ResourceRoom(options){var _this;
_this=_PSRoom.call(this,options)||this;_this.classType='resources';_this.canConnect=false;
_this.title='Resources';return _this;
}_inheritsLoose(ResourceRoom,_PSRoom);var _proto=ResourceRoom.prototype;_proto.
connect=function connect(){};return ResourceRoom;}(PSRoom);var
ResourcePanel=function(_PSRoomPanel){function ResourcePanel(){var _this2;for(var _len=arguments.length,args=new Array(_len),_key=0;_key<_len;_key++){args[_key]=arguments[_key];}_this2=_PSRoomPanel.call.apply(_PSRoomPanel,[this].concat(args))||this;_this2.
state={search:''};return _this2;}_inheritsLoose(ResourcePanel,_PSRoomPanel);var _proto2=ResourcePanel.prototype;_proto2.
receiveLine=function receiveLine(){};_proto2.
render=function render(){var _this3=this;
var room=this.props.room;
return preact.h(PSPanelWrapper,{room:room,scrollable:true},
preact.h("div",{className:"pad"},
preact.h("h2",null,"PS! Informational Resources"),
preact.h("hr",null),
preact.h("p",null,"PS! is a wide and varied site, with more facets than can be covered here easily.",
preact.h("br",null),"While this page chiefly documents the ever-shifting set of commands available to PS! users, here are some useful resources for newcomers:"
),
preact.h("ul",null,
preact.h("li",null,
preact.h("a",{href:"https://www.smogon.com/forums/threads/3676132/"},"Beginner's Guide to Pok\xE9mon Showdown")
),
preact.h("li",null,
preact.h("a",{href:"https://www.smogon.com/dp/articles/intro_comp_pokemon"},"An introduction to competitive Pok\xE9mon")
),
preact.h("li",null,
preact.h("a",{href:"https://www.smogon.com/sm/articles/sm_tiers"},"What do 'OU', 'UU', etc mean?")
),
preact.h("li",null,
preact.h("a",{href:"https://www.smogon.com/dex/ss/formats/"},"What are the rules for each format?")
),
preact.h("li",null,
preact.h("a",{href:"https://www.smogon.com/ss/articles/clauses"},"What is 'Sleep Clause' and other clauses?")
),
preact.h("li",null,
preact.h("a",{href:"https://www.smogon.com/articles/getting-started"},"Next Steps for Competitive Battling")
),
preact.h("li",null,
preact.h("button",{className:"button","data-cmd":"/report"},"Report a user")
),
preact.h("li",null,
preact.h("button",{className:"button","data-cmd":"/join help"},"Join the Help room for live help")
)
),
preact.h("hr",null),
preact.h("strong",null,"Commands:"),
preact.h("p",null,"Within any of the chats, and in private messages, it is possible to type in commands (messages beginning with ",
preact.h("code",null,"/"),") to perform a particular action. A great number of these commands exist, with some only available to certain users. For instance, you can broadcast commands to others with the ",preact.h("code",null,"!")," prefix, but only when you're a player in a battle or a Voice (+) user.",
preact.h("br",null),"For more information on ranks, type ",
preact.h("code",null,"/groups")," in any chat. You can also use the \"chat self\" button on your username in the top right if you need a place to send these commands without joining a room."
),
preact.h("details",{className:"readmore"},
preact.h("summary",null,"Here's a list of the most useful commands for the average Pok\xE9mon Showdown experience:"),
preact.h("p",null,"COMMANDS: /report, /msg, /reply, /logout, /challenge, /search, /rating, /whois, /user, /join, /leave, /userauth, /roomauth"),
preact.h("p",null,"BATTLE ROOM COMMANDS: /savereplay, /hideroom, /inviteonly, /invite, /timer, /forfeit"),
preact.h("p",null,"OPTION COMMANDS: /nick, /avatar, /ignore, /status, /away, /busy, /back, /timestamps, /highlight, /showjoins, /hidejoins, /blockchallenges, /blockpms"),
preact.h("p",null,"INFORMATIONAL/RESOURCE COMMANDS: /groups, /faq, /rules, /intro, /formatshelp, /othermetas, /analysis, /punishments, /calc, /git, /cap, /roomhelp, /roomfaq (replace / with ! to broadcast. Broadcasting requires: + % @ # ~)"),
preact.h("p",null,"DATA COMMANDS: /data, /dexsearch, /movesearch, /itemsearch, /learn, /statcalc, /effectiveness, /weakness, /coverage, /randommove, /randompokemon (replace / with ! to broadcast. Broadcasting requires: + % @ # ~)"),
preact.h("p",null,"For an overview of room commands, use ",preact.h("code",null,"/roomhelp")),
preact.h("p",null,"For details of a specific command, you can use ",preact.h("code",null,"/help [command]"),", for example ",preact.h("code",null,"/help data"),".")
),
preact.h("br",null),
preact.h("p",null,"A complete list of commands available to regular users is provided below. Use ",preact.h("code",null,"/help [commandname]")," for more in-depth information on how to use them."),
preact.h("hr",null),
preact.h("label",{"for":"search"},"Search/filter commands:"),' ',
preact.h("input",{
name:"search",
placeholder:"search",
style:{width:'25%'},
value:this.state.search,
onChange:function(e){var _e$target;return _this3.setState({search:toID((_e$target=e.target)==null?void 0:_e$target.value)});}}
),
preact.h("br",null),
preact.h("span",null,this.getCommandList())
)
);
};_proto2.
getCommandList=function getCommandList(){
var buf=[];
var search=this.state.search;
var keys=Object.keys(BattleChatCommands).sort(function(a,b){
if(b.endsWith('info'))return 2;
if(b.includes('chat-commands'))return 1;
var aCount=BattleChatCommands[a].filter(function(x){return toID(x).includes(search);}).length;
var bCount=BattleChatCommands[b].filter(function(x){return toID(x).includes(search);}).length;
return bCount-aCount||a.localeCompare(b);
}).filter(function(plugin){return!plugin.endsWith('admin');});for(var _i2=0;_i2<
keys.length;_i2++){var pluginName=keys[_i2];
var cmdTable=BattleChatCommands[pluginName];
if(!cmdTable.length)continue;
if(pluginName.startsWith('chat-plugins')){
pluginName='Chat plugin: '+pluginName.split('/').slice(1).join('/');
}else if(pluginName.startsWith('chat-commands')){
pluginName='Core commands: '+pluginName.split('/').slice(1).join('/');;
}
var matchedCmds=[];for(var _i4=0;_i4<
cmdTable.length;_i4++){var cmd=cmdTable[_i4];
if(search.length&&!toID(cmd).includes(search)){
continue;
}
matchedCmds.push(preact.h("li",null,cmd));
}
buf.push(
preact.h("details",{"class":"readmore"},
preact.h("summary",null,preact.h("strong",null,pluginName)),
preact.h("ul",null,matchedCmds)
)
);
buf.push(preact.h("br",null));
}
return buf;
};return ResourcePanel;}(PSRoomPanel);ResourcePanel.id='resources';ResourcePanel.routes=['resources'];ResourcePanel.Model=ResourceRoom;ResourcePanel.icon=preact.h("i",{"class":"fa fa-question-circle","aria-hidden":true});ResourcePanel.title='Resources';
PS.addRoomType(ResourcePanel);
//# sourceMappingURL=page-resources.js.map

View File

@ -32,7 +32,7 @@ Storage.bg = {
changeCount: 0,
// futureproofing in case we ever add more?
// because doing this once was annoying
MENU_BUTTONS: 6,
MENU_BUTTONS: 7,
set: function (bgUrl, bgid, noSave) {
if (!this.load(bgUrl, bgid)) {
this.extractMenuColors(bgUrl, bgid, noSave);

View File

@ -106,6 +106,7 @@ https://psim.us/dev
<script defer src="/js/panel-popups.js?"></script>
<script defer src="/js/panel-page.js?"></script>
<script defer src="/js/panel-ladder.js?"></script>
<script defer src="/js/panel-resources.js?"></script>
<script defer src="/js/battle-sound.js"></script>
<script defer src="/js/lib/jquery-2.2.4.min.js"></script>
@ -136,6 +137,7 @@ https://psim.us/dev
<script defer src="/data/aliases.js?"></script>
<script defer src="/js/lib/d3.v3.min.js"></script>
<script defer src="/js/lib/color-thief.min.js"></script>
<script defer src="/data/commands.js?"></script>
<script defer src="/js/client-endload.js?"></script>

View File

@ -657,6 +657,7 @@ class MainMenuPanel extends PSRoomPanel<MainMenuRoom> {
<p><a class={"mainmenu4 mainmenu" + onlineButton} href="battles">Watch a battle</a></p>
<p><a class={"mainmenu5 mainmenu" + onlineButton} href="users">Find a user</a></p>
<p><a class={"mainmenu6 mainmenu" + onlineButton} href="view-friends-all">Friends</a></p>
<p><a class={"mainmenu7 mainmenu" + onlineButton} href="resources">Info & Resources</a></p>
</div>
</div>
<div class="mainmenu-right" style={{ display: PS.leftPanelWidth ? 'none' : 'block' }}>

View File

@ -0,0 +1,175 @@
/**
* A panel displaying lists of commands and some basic informational resources
* @author mia-pi-git
*/
import { PS, PSRoom, type RoomOptions } from "./client-main";
import { PSPanelWrapper, PSRoomPanel } from "./panels";
import { toID } from "./battle-dex";
declare const BattleChatCommands: Record<string, string[]>;
class ResourceRoom extends PSRoom {
override readonly classType: string = 'resources';
override readonly canConnect = false;
constructor(options: RoomOptions) {
super(options);
this.title = 'Resources';
}
override connect() {}
}
class ResourcePanel extends PSRoomPanel<ResourceRoom> {
static readonly id = 'resources';
static readonly routes = ['resources'];
static readonly Model = ResourceRoom;
static readonly icon = <i class="fa fa-question-circle" aria-hidden></i>;
static readonly title = 'Resources';
override state = { search: '' };
override receiveLine() {}
override render() {
const { room } = this.props;
return <PSPanelWrapper room={room} scrollable>
<div className="pad">
<h2>PS! Informational Resources</h2>
<hr />
<p>
PS! is a wide and varied site, with more facets than can be covered here easily.
<br />
While this page chiefly documents the ever-shifting set of commands available to PS! users,{' '}
here are some useful resources for newcomers:
</p>
<ul>
<li>
<a href="https://www.smogon.com/forums/threads/3676132/">Beginner's Guide to Pokémon Showdown</a>
</li>
<li>
<a href="https://www.smogon.com/dp/articles/intro_comp_pokemon">An introduction to competitive Pokémon</a>
</li>
<li>
<a href="https://www.smogon.com/sm/articles/sm_tiers">What do 'OU', 'UU', etc mean?</a>
</li>
<li>
<a href="https://www.smogon.com/dex/ss/formats/">What are the rules for each format?</a>
</li>
<li>
<a href="https://www.smogon.com/ss/articles/clauses">What is 'Sleep Clause' and other clauses?</a>
</li>
<li>
<a href="https://www.smogon.com/articles/getting-started">Next Steps for Competitive Battling</a>
</li>
<li>
<button className="button" data-cmd="/report">Report a user</button>
</li>
<li>
<button className="button" data-cmd="/join help">Join the Help room for live help</button>
</li>
</ul>
<hr />
<strong>Commands:</strong>
<p>
Within any of the chats, and in private messages,{' '}
it is possible to type in commands (messages beginning with <code>/</code>){' '}
to perform a particular action. A great number of these commands exist, {' '}
with some only available to certain users. For instance, you can broadcast commands to others with the{' '}
<code>!</code> prefix, but only when you're a player in a battle or a Voice (+) user.
<br />
For more information on ranks, type <code>/groups</code> in any chat.{' '}
You can also use the "chat self" button on your username in the top right {' '}
if you need a place to send these commands without joining a room.
</p>
<details className="readmore">
<summary>Here's a list of the most useful commands for the average Pokémon Showdown experience:</summary>
<p>
COMMANDS: /report, /msg, /reply, /logout,{' '}
/challenge, /search, /rating, /whois, /user, /join, /leave, /userauth, /roomauth
</p>
<p>
BATTLE ROOM COMMANDS: /savereplay, /hideroom, /inviteonly, /invite,{' '}
/timer, /forfeit
</p>
<p>
OPTION COMMANDS: /nick, /avatar, /ignore, /status, /away, /busy, /back, /timestamps,{' '}
/highlight, /showjoins, /hidejoins, /blockchallenges, /blockpms
</p>
<p>
INFORMATIONAL/RESOURCE COMMANDS: /groups, /faq, /rules, /intro, /formatshelp,{' '}
/othermetas, /analysis, /punishments, /calc, /git, /cap, /roomhelp, /roomfaq{' '}
(replace / with ! to broadcast. Broadcasting requires: + % @ # ~)
</p>
<p>
DATA COMMANDS: /data, /dexsearch, /movesearch, /itemsearch, /learn,{' '}
/statcalc, /effectiveness, /weakness, /coverage, /randommove, /randompokemon</p>
<p>For an overview of room commands, use <code>/roomhelp</code></p>
<p>For details of a specific command, you can use <code>/help [command]</code>, for example <code>/help data</code>.</p>
</details>
<br />
<p>
A complete list of commands available to regular users is provided below. Use {' '}
<code>/help [commandname]</code> for more in-depth information on how to use them.
</p>
<hr />
<label for="search">Search/filter commands:</label>{' '}
<input
name="search"
placeholder="search"
style={{ width: '25%' }}
value={this.state.search}
onChange={e => this.setState({ search: toID((e.target as any)?.value) })}
/>
<br />
<span>{this.getCommandList()}</span>
</div>
</PSPanelWrapper>;
}
getCommandList() {
if (typeof BattleChatCommands !== 'object') {
document.addEventListener('ready', () => this.setState({ commandsLoaded: true }));
return <>Loading command data, please try again in a few moments...</>;
}
const buf = [];
const search = this.state.search;
const keys = Object.keys(BattleChatCommands).sort((a, b) => {
// pin info to the top ALWAYS
if (b.endsWith('info')) return 2;
// prefer default commands near the top, more generally useful
if (b.includes('chat-commands')) return 1;
const aCount = BattleChatCommands[a].filter(x => toID(x).includes(search)).length;
const bCount = BattleChatCommands[b].filter(x => toID(x).includes(search)).length;
return (bCount - aCount) || a.localeCompare(b);
}).filter(plugin => !(plugin.endsWith('admin')));
for (let pluginName of keys) {
const cmdTable = BattleChatCommands[pluginName];
if (!cmdTable.length) continue;
if (pluginName.startsWith('chat-plugins')) {
pluginName = 'Chat plugin: ' + pluginName.split('/').slice(1).join('/');
} else if (pluginName.startsWith('chat-commands')) {
pluginName = 'Core commands: ' + pluginName.split('/').slice(1).join('/'); ;
}
let matchedCmds = [];
for (const cmd of cmdTable) {
if (search.length && !toID(cmd).includes(search)) {
continue;
}
matchedCmds.push(<li>{cmd}</li>);
}
buf.push(
<details class="readmore">
<summary>
<strong>{pluginName} {search.length ? <>({matchedCmds.length}))</> : ''}</strong>
</summary>
<ul>{matchedCmds}</ul>
</details>
);
buf.push(<br />);
}
return buf;
}
}
PS.addRoomType(ResourcePanel);

View File

@ -128,6 +128,7 @@
<script src="js/panel-popups.js"></script>
<script src="js/panel-page.js?"></script>
<script src="js/panel-resources.js?" onload="console.log('loaded')"></script>
<script src="js/panel-ladder.js?"></script>
<script src="js/battle-sound.js"></script>
@ -135,6 +136,7 @@
<script src="data/graphics.js" onerror="loadRemoteData(this.src)"></script>
<script src="data/text.js" onerror="loadRemoteData(this.src)"></script>
<script src="data/text-afd.js" onerror="loadRemoteData(this.src)"></script>
<script src="data/commands.js?" onerror="loadRemoteData(this.src)"></script>
<script src="js/battle-tooltips.js"></script>
<script src="js/battle.js"></script>
<script src="js/battle-choices.js"></script>

View File

@ -123,6 +123,7 @@
<script src="js/search.js"></script>
<script src="data/aliases.js" async="async" onerror="loadRemoteData(this.src)"></script>
<script src="data/commands.js" async="async" onerror="loadRemoteData(this.src)"></script>
<script>
window.onload = () => {