mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-03-21 17:50:29 -05:00
Support playing battles
This should be a much nicer architecture than the old `client-battle.js`. In particular, much of the logic of choosing moves/switches has been moved into a new `battle-choices.ts`, with `panel-battle.tsx` only covering the UI.
This commit is contained in:
parent
523d63ab01
commit
40d077903f
|
|
@ -6,6 +6,7 @@ node_modules/
|
|||
/js/battle.js
|
||||
/js/battledata.js
|
||||
/js/battle-log.js
|
||||
/js/battle-choices.js
|
||||
/js/battle-text-parser.js
|
||||
/js/battle-dex.js
|
||||
/js/battle-dex-data.js
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -16,6 +16,7 @@ package-lock.json
|
|||
/js/battle.js
|
||||
/js/battledata.js
|
||||
/js/battle-log.js
|
||||
/js/battle-choices.js
|
||||
/js/battle-text-parser.js
|
||||
/js/battle-dex.js
|
||||
/js/battle-dex-data.js
|
||||
|
|
|
|||
|
|
@ -570,7 +570,7 @@
|
|||
} else if (!pokemon || pokemon.fainted) {
|
||||
targetMenus[0] += '<button name="chooseMoveTarget" value="' + (i + 1) + '"><span class="picon" style="' + Dex.getPokemonIcon('missingno') + '"></span></button> ';
|
||||
} else {
|
||||
targetMenus[0] += '<button name="chooseMoveTarget" value="' + (i + 1) + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + (this.battle.ignoreOpponent || this.battle.ignoreNicks ? pokemon.species : BattleLog.escapeHTML(pokemon.name)) + '<span class="hpbar' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') + '</button> ';
|
||||
targetMenus[0] += '<button name="chooseMoveTarget" value="' + (i + 1) + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + (this.battle.ignoreOpponent || this.battle.ignoreNicks ? pokemon.species : BattleLog.escapeHTML(pokemon.name)) + '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') + '</button> ';
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < myActive.length; i++) {
|
||||
|
|
@ -590,7 +590,7 @@
|
|||
} else if (!pokemon || pokemon.fainted) {
|
||||
targetMenus[1] += '<button name="chooseMoveTarget" value="' + (-(i + 1)) + '"><span class="picon" style="' + Dex.getPokemonIcon('missingno') + '"></span></button> ';
|
||||
} else {
|
||||
targetMenus[1] += '<button name="chooseMoveTarget" value="' + (-(i + 1)) + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + '<span class="hpbar' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') + '</button> ';
|
||||
targetMenus[1] += '<button name="chooseMoveTarget" value="' + (-(i + 1)) + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') + '</button> ';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -701,9 +701,9 @@
|
|||
pokemon.name = pokemon.ident.substr(4);
|
||||
var tooltipArgs = 'switchpokemon|' + i;
|
||||
if (pokemon.fainted || i < this.battle.mySide.active.length || this.choice.switchFlags[i]) {
|
||||
switchMenu += '<button class="disabled has-tooltip" name="chooseDisabled" value="' + BattleLog.escapeHTML(pokemon.name) + (pokemon.fainted ? ',fainted' : i < this.battle.mySide.active.length ? ',active' : '') + '" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + (pokemon.hp ? '<span class="hpbar' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') : '') + '</button> ';
|
||||
switchMenu += '<button class="disabled has-tooltip" name="chooseDisabled" value="' + BattleLog.escapeHTML(pokemon.name) + (pokemon.fainted ? ',fainted' : i < this.battle.mySide.active.length ? ',active' : '') + '" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + (pokemon.hp ? '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') : '') + '</button> ';
|
||||
} else {
|
||||
switchMenu += '<button name="chooseSwitch" value="' + i + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + '<span class="hpbar' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') + '</button> ';
|
||||
switchMenu += '<button name="chooseSwitch" value="' + i + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') + '</button> ';
|
||||
}
|
||||
}
|
||||
if (this.finalDecisionSwitch && this.battle.gen > 2) {
|
||||
|
|
@ -751,11 +751,11 @@
|
|||
var pokemon = this.battle.myPokemon[i];
|
||||
var tooltipArgs = 'switchpokemon|' + i;
|
||||
if (pokemon && !pokemon.fainted || this.choice.switchOutFlags[i]) {
|
||||
controls += '<button disabled class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + (!pokemon.fainted ? '<span class="hpbar' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') : '') + '</button> ';
|
||||
controls += '<button disabled class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + (!pokemon.fainted ? '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') : '') + '</button> ';
|
||||
} else if (!pokemon) {
|
||||
controls += '<button disabled></button> ';
|
||||
} else {
|
||||
controls += '<button name="chooseSwitchTarget" value="' + i + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + '<span class="hpbar' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') + '</button> ';
|
||||
controls += '<button name="chooseSwitchTarget" value="' + i + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '"><span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') + '</button> ';
|
||||
}
|
||||
}
|
||||
controls += '</div>';
|
||||
|
|
@ -781,7 +781,7 @@
|
|||
} else {
|
||||
switchMenu += '<button name="chooseSwitch" value="' + i + '" class="has-tooltip" data-tooltip="' + BattleLog.escapeHTML(tooltipArgs) + '">';
|
||||
}
|
||||
switchMenu += '<span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + (!pokemon.fainted ? '<span class="hpbar' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') : '') + '</button> ';
|
||||
switchMenu += '<span class="picon" style="' + Dex.getPokemonIcon(pokemon) + '"></span>' + BattleLog.escapeHTML(pokemon.name) + (!pokemon.fainted ? '<span class="' + pokemon.getHPColorClass() + '"><span style="width:' + (Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1) + 'px"></span></span>' + (pokemon.status ? '<span class="status ' + pokemon.status + '"></span>' : '') : '') + '</button> ';
|
||||
}
|
||||
|
||||
var controls = (
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@
|
|||
<script defer src="/js/client-core.js?"></script>
|
||||
|
||||
<script defer src="/js/battle-dex.js?"></script>
|
||||
<script defer src="/js/battle-text-parser.js"></script>
|
||||
<script defer src="/js/battle-text-parser.js?"></script>
|
||||
<script defer src="/js/client-main.js?"></script>
|
||||
<script defer src="/js/lib/sockjs-1.4.0-nwjsfix.min.js?"></script>
|
||||
<script defer src="/js/client-connection.js?"></script>
|
||||
|
|
@ -71,11 +71,12 @@
|
|||
|
||||
<script defer src="/js/lib/soundmanager2-nodebug-jsmin.js"></script>
|
||||
<script defer src="/js/lib/jquery-2.1.4.min.js"></script>
|
||||
<script defer src="/data/graphics.js"></script>
|
||||
<script defer src="/data/text.js"></script>
|
||||
<script defer src="/data/graphics.js?"></script>
|
||||
<script defer src="/data/text.js?"></script>
|
||||
<script defer src="/js/battle-tooltips.js"></script>
|
||||
<script defer src="/js/battle.js"></script>
|
||||
<script defer src="/js/panel-battle.js"></script>
|
||||
<script defer src="/js/battle.js?"></script>
|
||||
<script defer src="/js/battle-choices.js?"></script>
|
||||
<script defer src="/js/panel-battle.js?"></script>
|
||||
|
||||
<script defer src="/js/battle-dex-data.js?"></script>
|
||||
<script defer src="/data/pokedex.js?"></script>
|
||||
|
|
@ -84,10 +85,13 @@
|
|||
<script defer src="/data/abilities.js?"></script>
|
||||
<script defer src="/data/search-index.js?"></script>
|
||||
<script defer src="/data/teambuilder-tables.js?"></script>
|
||||
<script defer src="/js/panel-teamdropdown.js"></script>
|
||||
<script defer src="/js/panel-teamdropdown.js?"></script>
|
||||
<script defer src="/js/panel-teambuilder.js?"></script>
|
||||
<script defer src="/js/battle-search.js?"></script>
|
||||
<script defer src="/js/battle-searchresults.js?"></script>
|
||||
<script defer src="/js/panel-teambuilder-team.js?"></script>
|
||||
|
||||
<script defer src="/data/pokedex-mini.js?"></script>
|
||||
<script defer src="/data/pokedex-mini-bw.js?"></script>
|
||||
|
||||
</body></html>
|
||||
|
|
|
|||
|
|
@ -1314,7 +1314,7 @@ class BattleScene {
|
|||
|
||||
let $hp = pokemon.sprite.$statbar.find('div.hp');
|
||||
let w = pokemon.hpWidth(150);
|
||||
let hpcolor = pokemon.getHPColor();
|
||||
let hpcolor = BattleScene.getHPColor(pokemon);
|
||||
let callback;
|
||||
if (hpcolor === 'y') {
|
||||
callback = () => { $hp.addClass('hp-yellow'); };
|
||||
|
|
@ -1337,7 +1337,7 @@ class BattleScene {
|
|||
|
||||
let $hp = pokemon.sprite.$statbar.find('div.hp');
|
||||
let w = pokemon.hpWidth(150);
|
||||
let hpcolor = pokemon.getHPColor();
|
||||
let hpcolor = BattleScene.getHPColor(pokemon);
|
||||
let callback;
|
||||
if (hpcolor === 'g') {
|
||||
callback = () => { $hp.removeClass('hp-yellow hp-red'); };
|
||||
|
|
@ -1553,6 +1553,12 @@ class BattleScene {
|
|||
}
|
||||
this.battle = null!;
|
||||
}
|
||||
static getHPColor(pokemon: {hp: number, maxhp: number}) {
|
||||
let ratio = pokemon.hp / pokemon.maxhp;
|
||||
if (ratio > 0.5) return 'g';
|
||||
if (ratio > 0.2) return 'y';
|
||||
return 'r';
|
||||
}
|
||||
}
|
||||
|
||||
interface ScenePos {
|
||||
|
|
@ -2485,7 +2491,7 @@ class PokemonSprite extends Sprite {
|
|||
}
|
||||
let hpcolor;
|
||||
if (updatePrevhp || updateHp) {
|
||||
hpcolor = pokemon.getHPColor();
|
||||
hpcolor = BattleScene.getHPColor(pokemon);
|
||||
let w = pokemon.hpWidth(150);
|
||||
let $hp = this.$statbar.find('.hp');
|
||||
$hp.css({
|
||||
|
|
|
|||
472
src/battle-choices.ts
Normal file
472
src/battle-choices.ts
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
/**
|
||||
* Battle choices
|
||||
*
|
||||
* PS will send requests "what do you do this turn?", and you send back
|
||||
* choices "I switch Pikachu for Caterpie, and Squirtle uses Water Gun"
|
||||
*
|
||||
* This file contains classes for handling requests and choices.
|
||||
*
|
||||
* Dependencies: battle-dex
|
||||
*
|
||||
* @author Guangcong Luo <guangcongluo@gmail.com>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
interface BattleRequestSideInfo {
|
||||
name: string;
|
||||
id: 'p1' | 'p2' | 'p3' | 'p4';
|
||||
pokemon: ServerPokemon[];
|
||||
}
|
||||
interface BattleRequestActivePokemon {
|
||||
moves: {
|
||||
name: string,
|
||||
id: ID,
|
||||
pp: number,
|
||||
maxpp: number,
|
||||
target: MoveTarget,
|
||||
disabled?: boolean,
|
||||
}[];
|
||||
maxMoves?: {
|
||||
name: string,
|
||||
id: ID,
|
||||
target: MoveTarget,
|
||||
disabled?: boolean,
|
||||
}[];
|
||||
zMoves?: ({
|
||||
name: string,
|
||||
id: ID,
|
||||
target: MoveTarget,
|
||||
} | null)[];
|
||||
/** also true if the pokemon can Gigantamax */
|
||||
canDynamax?: boolean;
|
||||
canGigantamax?: boolean;
|
||||
canMegaEvo?: boolean;
|
||||
canUltraBurst?: boolean;
|
||||
trapped?: boolean;
|
||||
maybeTrapped?: boolean;
|
||||
}
|
||||
|
||||
interface BattleMoveRequest {
|
||||
requestType: 'move';
|
||||
rqid: number;
|
||||
side: BattleRequestSideInfo;
|
||||
active: (BattleRequestActivePokemon | null)[];
|
||||
noCancel?: boolean;
|
||||
}
|
||||
interface BattleSwitchRequest {
|
||||
requestType: 'switch';
|
||||
rqid: number;
|
||||
side: BattleRequestSideInfo;
|
||||
forceSwitch: boolean[];
|
||||
noCancel?: boolean;
|
||||
}
|
||||
interface BattleTeamRequest {
|
||||
requestType: 'team';
|
||||
rqid: number;
|
||||
side: BattleRequestSideInfo;
|
||||
maxTeamSize?: number;
|
||||
noCancel?: boolean;
|
||||
}
|
||||
interface BattleWaitRequest {
|
||||
requestType: 'wait';
|
||||
rqid: number;
|
||||
side: undefined;
|
||||
noCancel?: boolean;
|
||||
}
|
||||
type BattleRequest = BattleMoveRequest | BattleSwitchRequest | BattleTeamRequest | BattleWaitRequest;
|
||||
|
||||
interface BattleMoveChoice {
|
||||
choiceType: 'move';
|
||||
/** 1-based move */
|
||||
move: number;
|
||||
targetLoc: number;
|
||||
mega: boolean;
|
||||
ultra: boolean;
|
||||
max: boolean;
|
||||
z: boolean;
|
||||
}
|
||||
interface BattleSwitchChoice {
|
||||
choiceType: 'switch' | 'team';
|
||||
/** 1-based pokemon */
|
||||
targetPokemon: number;
|
||||
}
|
||||
type BattleChoice = BattleMoveChoice | BattleSwitchChoice;
|
||||
|
||||
/**
|
||||
* Tracks a partial choice, allowing you to build it up one step at a time,
|
||||
* and maybe even construct a UI to build it!
|
||||
*
|
||||
* Doesn't support going backwards; just use `new BattleChoiceBuilder`.
|
||||
*/
|
||||
class BattleChoiceBuilder {
|
||||
request: BattleRequest;
|
||||
/** Completed choices in string form */
|
||||
choices: string[] = [];
|
||||
/** Currently active partial move choice - not used for other choices, which don't have partial states */
|
||||
current: BattleMoveChoice = {
|
||||
choiceType: 'move', // unused
|
||||
/** if nonzero, show target screen; if zero, show move screen */
|
||||
move: 0,
|
||||
targetLoc: 0, // unused
|
||||
mega: false,
|
||||
ultra: false,
|
||||
z: false,
|
||||
max: false,
|
||||
};
|
||||
/** Indexes of Pokémon already chosen to switch in */
|
||||
plannedToSwitchIn: number[] = [];
|
||||
alreadyMega = false;
|
||||
alreadyMax = false;
|
||||
alreadyZ = false;
|
||||
|
||||
constructor(request: BattleRequest) {
|
||||
this.request = request;
|
||||
this.fillPasses();
|
||||
}
|
||||
|
||||
toString() {
|
||||
let choices = this.choices;
|
||||
if (this.current.move) choices = choices.concat(this.stringChoice(this.current));
|
||||
return choices.join(', ').replace(/, team /g, ', ');
|
||||
}
|
||||
|
||||
isDone() {
|
||||
return this.choices.length >= this.requestLength();
|
||||
}
|
||||
isEmpty() {
|
||||
for (const choice of this.choices) {
|
||||
if (choice !== 'pass') return false;
|
||||
}
|
||||
if (this.current.move) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Index of the current Pokémon to make choices for */
|
||||
index() {
|
||||
return this.choices.length;
|
||||
}
|
||||
/** How many choices is the server expecting? */
|
||||
requestLength() {
|
||||
const request = this.request;
|
||||
switch (request.requestType) {
|
||||
case 'move':
|
||||
return request.active.length;
|
||||
case 'switch':
|
||||
return request.forceSwitch.length;
|
||||
case 'team':
|
||||
if (request.maxTeamSize) return request.maxTeamSize;
|
||||
return 1;
|
||||
case 'wait':
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
currentMoveRequest() {
|
||||
if (this.request.requestType !== 'move') return null;
|
||||
return this.request.active[this.index()];
|
||||
}
|
||||
|
||||
addChoice(choiceString: string) {
|
||||
let choice: BattleChoice | null;
|
||||
try {
|
||||
choice = this.parseChoice(choiceString);
|
||||
} catch (err) {
|
||||
return (err as Error).message;
|
||||
}
|
||||
if (!choice) {
|
||||
return "You do not need to manually choose to pass; the client handles it for you automatically";
|
||||
}
|
||||
if (choice.choiceType === 'move') {
|
||||
if (!choice.targetLoc && this.requestLength() > 1) {
|
||||
const choosableTargets = ['normal', 'any', 'adjacentAlly', 'adjacentAllyOrSelf', 'adjacentFoe'];
|
||||
if (choosableTargets.includes(this.getChosenMove(choice, this.index()).target)) {
|
||||
this.current.move = choice.move;
|
||||
this.current.mega = choice.mega;
|
||||
this.current.ultra = choice.ultra;
|
||||
this.current.z = choice.z;
|
||||
this.current.max = choice.max;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (choice.mega) this.alreadyMega = true;
|
||||
if (choice.z) this.alreadyZ = true;
|
||||
if (choice.max) this.alreadyMax = true;
|
||||
this.current.move = 0;
|
||||
this.current.mega = false;
|
||||
this.current.ultra = false;
|
||||
this.current.z = false;
|
||||
this.current.max = false;
|
||||
} else if (choice.choiceType === 'switch' || choice.choiceType === 'team') {
|
||||
if (this.plannedToSwitchIn.includes(choice.targetPokemon)) {
|
||||
if (choice.choiceType === 'switch') {
|
||||
return "You've already chosen to switch that Pokémon in";
|
||||
}
|
||||
// remove choice instead
|
||||
for (let i = 0; i < this.plannedToSwitchIn.length; i++) {
|
||||
if (this.plannedToSwitchIn[i] === choice.targetPokemon) {
|
||||
this.plannedToSwitchIn.splice(i, 1);
|
||||
this.choices.splice(i, 1);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return "Unexpected bug, please report this";
|
||||
}
|
||||
this.plannedToSwitchIn.push(choice.targetPokemon);
|
||||
}
|
||||
this.choices.push(this.stringChoice(choice));
|
||||
this.fillPasses();
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move and switch requests will often skip over some active Pokémon (mainly
|
||||
* fainted Pokémon). This will fill them in automatically, so we don't need
|
||||
* to ask a user for them.
|
||||
*/
|
||||
fillPasses() {
|
||||
const request = this.request;
|
||||
switch (request.requestType) {
|
||||
case 'move':
|
||||
while (this.choices.length < request.active.length && !request.active[this.choices.length]) {
|
||||
this.choices.push('pass');
|
||||
}
|
||||
break;
|
||||
case 'switch':
|
||||
while (this.choices.length < request.forceSwitch.length && !request.forceSwitch[this.choices.length]) {
|
||||
this.choices.push('pass');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getChosenMove(choice: BattleMoveChoice, pokemonIndex: number) {
|
||||
const request = this.request as BattleMoveRequest;
|
||||
const activePokemon = request.active[pokemonIndex]!;
|
||||
const moveIndex = choice.move - 1;
|
||||
if (choice.z) {
|
||||
return activePokemon.zMoves![moveIndex]!;
|
||||
}
|
||||
if (choice.max || (activePokemon.maxMoves && !activePokemon.canDynamax)) {
|
||||
return activePokemon.maxMoves![moveIndex];
|
||||
}
|
||||
return activePokemon.moves[moveIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a choice from string form to BattleChoice form
|
||||
*/
|
||||
parseChoice(choice: string) {
|
||||
const request = this.request;
|
||||
if (request.requestType === 'wait') throw new Error(`It's not your turn to choose anything`);
|
||||
|
||||
const index = this.choices.length;
|
||||
|
||||
if (choice.startsWith('move ')) {
|
||||
if (request.requestType !== 'move') {
|
||||
throw new Error(`You must switch in a Pokémon, not move.`);
|
||||
}
|
||||
const moveRequest = request.active[index]!;
|
||||
choice = choice.slice(5);
|
||||
let current: BattleMoveChoice = {
|
||||
choiceType: 'move',
|
||||
move: 0,
|
||||
targetLoc: 0,
|
||||
mega: false,
|
||||
ultra: false,
|
||||
z: false,
|
||||
max: false,
|
||||
};
|
||||
while (true) {
|
||||
// If data ends with a number, treat it as a target location.
|
||||
// We need to special case 'Conversion 2' so it doesn't get
|
||||
// confused with 'Conversion' erroneously sent with the target
|
||||
// '2' (since Conversion targets 'self', targetLoc can't be 2).
|
||||
if (/\s(?:-|\+)?[1-3]$/.test(choice) && toID(choice) !== 'conversion2') {
|
||||
if (current.targetLoc) throw new Error(`Move choice has multiple targets`);
|
||||
current.targetLoc = parseInt(choice.slice(-2), 10);
|
||||
choice = choice.slice(0, -2).trim();
|
||||
} else if (choice.endsWith(' mega')) {
|
||||
current.mega = true;
|
||||
choice = choice.slice(0, -5);
|
||||
} else if (choice.endsWith(' zmove')) {
|
||||
current.z = true;
|
||||
choice = choice.slice(0, -6);
|
||||
} else if (choice.endsWith(' ultra')) {
|
||||
current.ultra = true;
|
||||
choice = choice.slice(0, -6);
|
||||
} else if (choice.endsWith(' dynamax')) {
|
||||
current.max = true;
|
||||
choice = choice.slice(0, -8);
|
||||
} else if (choice.endsWith(' max')) {
|
||||
current.max = true;
|
||||
choice = choice.slice(0, -4);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (/^[0-9]+$/.test(choice)) {
|
||||
// Parse a one-based move index.
|
||||
current.move = parseInt(choice, 10);
|
||||
} else {
|
||||
// Parse a move ID.
|
||||
// Move names are also allowed, but may cause ambiguity (see client issue #167).
|
||||
let moveid = toID(choice);
|
||||
if (moveid.startsWith('hiddenpower')) moveid = 'hiddenpower' as ID;
|
||||
|
||||
for (let i = 0; i < moveRequest.moves.length; i++) {
|
||||
if (moveid === moveRequest.moves[i].id) {
|
||||
current.move = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!current.move && moveRequest.zMoves) {
|
||||
for (let i = 0; i < moveRequest.zMoves.length; i++) {
|
||||
if (!moveRequest.zMoves[i]) continue;
|
||||
if (moveid === moveRequest.zMoves[i]!.id) {
|
||||
current.move = i + 1;
|
||||
current.z = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!current.move && moveRequest.maxMoves) {
|
||||
for (let i = 0; i < moveRequest.maxMoves.length; i++) {
|
||||
if (moveid === moveRequest.maxMoves[i].id) {
|
||||
current.move = i + 1;
|
||||
current.max = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (current.max && !moveRequest.canDynamax) current.max = false;
|
||||
return current;
|
||||
}
|
||||
|
||||
if (choice.startsWith('switch ') || choice.startsWith('team ')) {
|
||||
choice = choice.slice(choice.startsWith('team ') ? 5 : 7);
|
||||
const isTeamPreview = request.requestType === 'team';
|
||||
let current: BattleSwitchChoice = {
|
||||
choiceType: isTeamPreview ? 'team' : 'switch',
|
||||
targetPokemon: 0,
|
||||
};
|
||||
if (/^[0-9]+$/.test(choice)) {
|
||||
// Parse a one-based move index.
|
||||
current.targetPokemon = parseInt(choice, 10);
|
||||
} else {
|
||||
// Parse a pokemon name
|
||||
const lowerChoice = choice.toLowerCase();
|
||||
const choiceid = toID(choice);
|
||||
let matchLevel = 0;
|
||||
let match = 0;
|
||||
for (let i = 0 ; i < request.side.pokemon.length; i++) {
|
||||
const serverPokemon = request.side.pokemon[i];
|
||||
let curMatchLevel = 0;
|
||||
if (choice === serverPokemon.name) {
|
||||
curMatchLevel = 10;
|
||||
} else if (lowerChoice === serverPokemon.name.toLowerCase()) {
|
||||
curMatchLevel = 9;
|
||||
} else if (choiceid === toID(serverPokemon.name)) {
|
||||
curMatchLevel = 8;
|
||||
} else if (choiceid === toID(serverPokemon.species)) {
|
||||
curMatchLevel = 7;
|
||||
} else if (choiceid === toID(Dex.getTemplate(serverPokemon.species).baseSpecies)) {
|
||||
curMatchLevel = 6;
|
||||
}
|
||||
if (curMatchLevel > matchLevel) {
|
||||
match = i + 1;
|
||||
matchLevel = curMatchLevel;
|
||||
}
|
||||
}
|
||||
if (!match) {
|
||||
throw new Error(`Couldn't find Pokémon "${choice}" to switch to`);
|
||||
}
|
||||
current.targetPokemon = match;
|
||||
}
|
||||
if (!isTeamPreview && current.targetPokemon - 1 < this.requestLength()) {
|
||||
throw new Error(`That Pokémon is already in battle!`);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
if (choice === 'pass') return null;
|
||||
|
||||
throw new Error(`Unrecognized choice "${choice}"`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a choice from `BattleChoice` into string form
|
||||
*/
|
||||
stringChoice(choice: BattleChoice) {
|
||||
switch (choice.choiceType) {
|
||||
case 'move':
|
||||
const target = choice.targetLoc ? ` ${choice.targetLoc > 0 ? '+' : ''}${choice.targetLoc}` : ``;
|
||||
const boost = `${choice.max ? ' max' : ''}${choice.mega ? ' mega' : ''}${choice.z ? ' zmove' : ''}`;
|
||||
return `move ${choice.move}${boost}${target}`;
|
||||
case 'switch':
|
||||
case 'team':
|
||||
return `${choice.choiceType} ${choice.targetPokemon}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The request sent from the server is actually really gross, but we'll have
|
||||
* to wait until we transition to the new client before fixing it in the
|
||||
* protocol, in the interests of not needing to fix it twice (or needing to
|
||||
* fix it without TypeScript).
|
||||
*
|
||||
* In the meantime, this function converts a request from a shitty request
|
||||
* to a request that makes sense.
|
||||
*
|
||||
* I'm sorry for literally all of this.
|
||||
*/
|
||||
static fixRequest(request: any, battle: Battle) {
|
||||
if (!request.requestType) {
|
||||
request.requestType = 'move';
|
||||
if (request.forceSwitch) {
|
||||
request.requestType = 'switch';
|
||||
} else if (request.teamPreview) {
|
||||
request.requestType = 'team';
|
||||
} else if (request.wait) {
|
||||
request.requestType = 'wait';
|
||||
}
|
||||
}
|
||||
|
||||
if (request.side) {
|
||||
for (const serverPokemon of request.side.pokemon) {
|
||||
battle.parseDetails(serverPokemon.ident.substr(4), serverPokemon.ident, serverPokemon.details, serverPokemon);
|
||||
battle.parseHealth(serverPokemon.condition, serverPokemon);
|
||||
}
|
||||
}
|
||||
|
||||
if (request.active) {
|
||||
request.active = request.active.map(
|
||||
(active: any, i: number) => request.side.pokemon[i].fainted ? null : active
|
||||
);
|
||||
for (const active of request.active) {
|
||||
if (!active) continue;
|
||||
for (const move of active.moves) {
|
||||
if (move.move) move.name = move.move;
|
||||
move.id = toID(move.name);
|
||||
}
|
||||
if (active.maxMoves) {
|
||||
if (active.maxMoves.maxMoves) {
|
||||
active.canGigantamax = active.maxMoves.gigantamax;
|
||||
active.maxMoves = active.maxMoves.maxMoves;
|
||||
}
|
||||
for (const move of active.maxMoves) {
|
||||
if (move.move) move.name = Dex.getMove(move.move).name;
|
||||
move.id = toID(move.name);
|
||||
}
|
||||
}
|
||||
if (active.canZMove) {
|
||||
active.zMoves = active.canZMove;
|
||||
for (const move of active.zMoves) {
|
||||
if (!move) continue;
|
||||
if (move.move) move.name = move.move;
|
||||
move.id = toID(move.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1089,6 +1089,11 @@ interface MoveFlags {
|
|||
sound?: 1 | 0;
|
||||
}
|
||||
|
||||
type MoveTarget = 'normal' | 'any' | 'adjacentAlly' | 'adjacentFoe' | 'adjacentAllyOrSelf' | // single-target
|
||||
'self' | 'randomNormal' | // single-target, automatic
|
||||
'allAdjacent' | 'allAdjacentFoes' | // spread
|
||||
'allySide' | 'foeSide' | 'all'; // side and field
|
||||
|
||||
class Move implements Effect {
|
||||
// effect
|
||||
readonly effectType = 'Move';
|
||||
|
|
@ -1103,11 +1108,7 @@ class Move implements Effect {
|
|||
readonly type: TypeName;
|
||||
readonly category: 'Physical' | 'Special' | 'Status';
|
||||
readonly priority: number;
|
||||
readonly target:
|
||||
'normal' | 'any' | 'adjacentAlly' | 'adjacentFoe' | 'adjacentAllyOrSelf' | // single-target
|
||||
'self' | 'randomNormal' | // single-target, automatic
|
||||
'allAdjacent' | 'allAdjacentFoes' | // spread
|
||||
'allySide' | 'foeSide' | 'all'; // side and field
|
||||
readonly target: MoveTarget;
|
||||
readonly flags: Readonly<MoveFlags>;
|
||||
readonly critRatio: number;
|
||||
|
||||
|
|
|
|||
|
|
@ -699,20 +699,23 @@ const Dex = new class implements ModdedDex {
|
|||
return num;
|
||||
}
|
||||
|
||||
getPokemonIcon(pokemon: any, facingLeft?: boolean) {
|
||||
getPokemonIcon(pokemon: string | Pokemon | ServerPokemon | null, facingLeft?: boolean) {
|
||||
if (pokemon === 'pokeball') {
|
||||
return `background:transparent url(${Dex.resourcePrefix}sprites/pokemonicons-pokeball-sheet.png) no-repeat scroll -0px 4px`;
|
||||
} else if (pokemon === 'pokeball-statused') {
|
||||
return `background:transparent url(${Dex.resourcePrefix}sprites/pokemonicons-pokeball-sheet.png) no-repeat scroll -40px 4px`;
|
||||
} else if (pokemon === 'pokeball-fainted') {
|
||||
return `background:transparent url(${Dex.resourcePrefix}sprites/pokemonicons-pokeball-sheet.png) no-repeat scroll -80px 4px;opacity:.4;filter:contrast(0)`;
|
||||
} else if (pokemon === 'pokeball-none') {
|
||||
} else if (pokemon === 'pokeball-none' || !pokemon) {
|
||||
return `background:transparent url(${Dex.resourcePrefix}sprites/pokemonicons-pokeball-sheet.png) no-repeat scroll -80px 4px`;
|
||||
}
|
||||
|
||||
let id = toID(pokemon);
|
||||
if (typeof pokemon === 'string') pokemon = null;
|
||||
if (pokemon?.species) id = toID(pokemon.species);
|
||||
// @ts-ignore
|
||||
if (pokemon?.volatiles?.formechange && !pokemon.volatiles.transform) {
|
||||
// @ts-ignore
|
||||
id = toID(pokemon.volatiles.formechange[1]);
|
||||
}
|
||||
let num = this.getPokemonIconNum(id, pokemon?.gender === 'F', facingLeft);
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class PSSearchResults extends preact.Component<{search: BattleSearch}> {
|
|||
<span class="col numcol">{search.getTier(pokemon)}</span>
|
||||
|
||||
<span class="col iconcol">
|
||||
<span style={Dex.getPokemonIcon(pokemon)}></span>
|
||||
<span style={Dex.getPokemonIcon(pokemon.id)}></span>
|
||||
</span>
|
||||
|
||||
<span class="col pokemonnamecol">{this.renderName(pokemon.name, matchStart, matchEnd, tagStart)}</span>
|
||||
|
|
@ -68,7 +68,7 @@ class PSSearchResults extends preact.Component<{search: BattleSearch}> {
|
|||
<span class="col numcol">{search.getTier(pokemon)}</span>
|
||||
|
||||
<span class="col iconcol">
|
||||
<span style={Dex.getPokemonIcon(pokemon)}></span>
|
||||
<span style={Dex.getPokemonIcon(pokemon.id)}></span>
|
||||
</span>
|
||||
|
||||
<span class="col pokemonnamecol">{this.renderName(pokemon.name, matchStart, matchEnd, tagStart)}</span>
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ class BattleTooltips {
|
|||
if (!BattleTooltips.isLocked) BattleTooltips.hideTooltip();
|
||||
}
|
||||
|
||||
listen(elem: HTMLElement) {
|
||||
listen(elem: HTMLElement | JQuery<HTMLElement>) {
|
||||
const $elem = $(elem);
|
||||
$elem.on('mouseover', '.has-tooltip', this.showTooltipEvent);
|
||||
$elem.on('click', '.has-tooltip', this.clickTooltipEvent);
|
||||
|
|
|
|||
|
|
@ -121,19 +121,21 @@ class Pokemon implements PokemonDetails, PokemonHealth {
|
|||
return this.side.active.includes(this);
|
||||
}
|
||||
|
||||
getHPColor(): HPColor {
|
||||
/** @deprecated */
|
||||
private getHPColor(): HPColor {
|
||||
if (this.hpcolor) return this.hpcolor;
|
||||
let ratio = this.hp / this.maxhp;
|
||||
if (ratio > 0.5) return 'g';
|
||||
if (ratio > 0.2) return 'y';
|
||||
return 'r';
|
||||
}
|
||||
getHPColorClass() {
|
||||
/** @deprecated */
|
||||
private getHPColorClass() {
|
||||
switch (this.getHPColor()) {
|
||||
case 'y': return ' hpbar-yellow';
|
||||
case 'r': return ' hpbar-red';
|
||||
case 'y': return 'hpbar hpbar-yellow';
|
||||
case 'r': return 'hpbar hpbar-red';
|
||||
}
|
||||
return '';
|
||||
return 'hpbar';
|
||||
}
|
||||
static getPixelRange(pixels: number, color: HPColor | ''): [number, number] {
|
||||
let epsilon = 0.5 / 714;
|
||||
|
|
|
|||
|
|
@ -87,7 +87,13 @@ class BattleRoom extends ChatRoom {
|
|||
challengeMenuOpen!: false;
|
||||
challengingFormat!: null;
|
||||
challengedFormat!: null;
|
||||
|
||||
battle: Battle = null!;
|
||||
/** null if spectator, otherwise current player's info */
|
||||
side: BattleRequestSideInfo | null = null;
|
||||
request: BattleRequest | null = null;
|
||||
choices: BattleChoiceBuilder | null = null;
|
||||
|
||||
/**
|
||||
* @return true to prevent line from being sent to server
|
||||
*/
|
||||
|
|
@ -123,6 +129,30 @@ class BattleRoom extends ChatRoom {
|
|||
} case 'switchsides': {
|
||||
this.battle.switchSides();
|
||||
return true;
|
||||
} case 'cancel': case 'undo': {
|
||||
if (!this.choices || !this.request) {
|
||||
this.receiveLine([`error`, `/choose - You are not a player in this battle`]);
|
||||
return true;
|
||||
}
|
||||
if (this.choices.isDone() || this.choices.isEmpty()) {
|
||||
this.send('/undo', true);
|
||||
}
|
||||
this.choices = new BattleChoiceBuilder(this.request);
|
||||
this.update(null);
|
||||
return true;
|
||||
} case 'move': case 'switch': case 'team': case 'choose': {
|
||||
if (!this.choices) {
|
||||
this.receiveLine([`error`, `/choose - You are not a player in this battle`]);
|
||||
return true;
|
||||
}
|
||||
const possibleError = this.choices.addChoice(line.slice(cmd === 'choose' ? 8 : 1));
|
||||
if (possibleError) {
|
||||
this.receiveLine([`error`, possibleError]);
|
||||
return true;
|
||||
}
|
||||
if (this.choices.isDone()) this.send(`/choose ${this.choices.toString()}`, true);
|
||||
this.update(null);
|
||||
return true;
|
||||
}}
|
||||
return super.handleMessage(line);
|
||||
}
|
||||
|
|
@ -137,6 +167,50 @@ class BattleDiv extends preact.Component {
|
|||
}
|
||||
}
|
||||
|
||||
function MoveButton(props: {
|
||||
children: string, cmd: string, moveData: {pp: number, maxpp: number}, type: TypeName, tooltip: string,
|
||||
}) {
|
||||
return <button name="cmd" value={props.cmd} class={`type-${props.type} has-tooltip`} data-tooltip={props.tooltip}>
|
||||
{props.children}<br />
|
||||
<small class="type">{props.type}</small> <small class="pp">{props.moveData.pp}/{props.moveData.maxpp}</small>
|
||||
</button>;
|
||||
}
|
||||
function PokemonButton(props: {
|
||||
pokemon: Pokemon | ServerPokemon | null, cmd: string, noHPBar?: boolean, disabled?: boolean | 'fade', tooltip: string,
|
||||
}) {
|
||||
const pokemon = props.pokemon;
|
||||
if (!pokemon) {
|
||||
return <button
|
||||
name="cmd" value={props.cmd} class={`${props.disabled ? 'disabled ' : ''}has-tooltip`}
|
||||
style={{opacity: props.disabled === 'fade' ? 0.5 : 1}} data-tooltip={props.tooltip}
|
||||
>
|
||||
(empty slot)
|
||||
</button>;
|
||||
}
|
||||
|
||||
let hpColorClass;
|
||||
switch (BattleScene.getHPColor(pokemon)) {
|
||||
case 'y': hpColorClass = 'hpbar hpbar-yellow'; break;
|
||||
case 'r': hpColorClass = 'hpbar hpbar-red'; break;
|
||||
default: hpColorClass = 'hpbar'; break;
|
||||
}
|
||||
|
||||
return <button
|
||||
name="cmd" value={props.cmd} class={`${props.disabled ? 'disabled ' : ''}has-tooltip`}
|
||||
style={{opacity: props.disabled === 'fade' ? 0.5 : 1}} data-tooltip={props.tooltip}
|
||||
>
|
||||
<span class="picon" style={Dex.getPokemonIcon(pokemon)}></span>
|
||||
{pokemon.name}
|
||||
{
|
||||
!props.noHPBar && !pokemon.fainted &&
|
||||
<span class={hpColorClass}>
|
||||
<span style={{width: Math.round(pokemon.hp * 92 / pokemon.maxhp) || 1}}></span>
|
||||
</span>
|
||||
}
|
||||
{!props.noHPBar && pokemon.status && <span class={`status ${pokemon.status}`}></span>}
|
||||
</button>;
|
||||
}
|
||||
|
||||
class BattlePanel extends PSRoomPanel<BattleRoom> {
|
||||
send = (text: string) => {
|
||||
this.props.room.send(text);
|
||||
|
|
@ -161,29 +235,87 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
|
|||
}
|
||||
return false;
|
||||
};
|
||||
toggleBoostedMove = (e: Event) => {
|
||||
const checkbox = e.currentTarget as HTMLInputElement;
|
||||
const choices = this.props.room.choices;
|
||||
if (!choices) return; // shouldn't happen
|
||||
switch (checkbox.name) {
|
||||
case 'mega':
|
||||
choices.current.mega = checkbox.checked;
|
||||
break;
|
||||
case 'ultra':
|
||||
choices.current.ultra = checkbox.checked;
|
||||
break;
|
||||
case 'z':
|
||||
choices.current.z = checkbox.checked;
|
||||
break;
|
||||
case 'max':
|
||||
choices.current.max = checkbox.checked;
|
||||
break;
|
||||
}
|
||||
this.props.room.update(null);
|
||||
};
|
||||
componentDidMount() {
|
||||
const battle = new Battle($(this.base!).find('.battle'), $(this.base!).find('.battle-log'));
|
||||
const $elem = $(this.base!);
|
||||
const battle = new Battle($elem.find('.battle'), $elem.find('.battle-log'));
|
||||
this.props.room.battle = battle;
|
||||
battle.endCallback = () => this.forceUpdate();
|
||||
battle.play();
|
||||
(battle.scene as BattleScene).tooltips.listen($elem.find('.battle-controls'));
|
||||
super.componentDidMount();
|
||||
}
|
||||
receiveLine(args: Args) {
|
||||
if (args[0] === `initdone`) {
|
||||
this.props.room.battle.fastForwardTo(-1);
|
||||
const room = this.props.room;
|
||||
switch (args[0]) {
|
||||
case 'initdone':
|
||||
room.battle.fastForwardTo(-1);
|
||||
return;
|
||||
case 'request':
|
||||
this.receiveRequest(args[1] ? JSON.parse(args[1]) : null);
|
||||
return;
|
||||
case 'error':
|
||||
if (args[1].startsWith('[Invalid choice]') && room.request) {
|
||||
room.choices = new BattleChoiceBuilder(room.request);
|
||||
room.update(null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
room.battle.add('|' + args.join('|'));
|
||||
}
|
||||
receiveRequest(request: BattleRequest | null) {
|
||||
const room = this.props.room;
|
||||
if (!request) {
|
||||
room.request = null;
|
||||
room.choices = null;
|
||||
return;
|
||||
}
|
||||
this.props.room.battle.add('|' + args.join('|'));
|
||||
|
||||
BattleChoiceBuilder.fixRequest(request, room.battle);
|
||||
|
||||
if (request.side) {
|
||||
room.battle.myPokemon = request.side.pokemon;
|
||||
if (room.battle.sidesSwitched !== !!(request.side.id === 'p2')) {
|
||||
room.battle.switchSides();
|
||||
}
|
||||
room.side = request.side;
|
||||
}
|
||||
|
||||
room.request = request;
|
||||
room.choices = new BattleChoiceBuilder(request);
|
||||
room.update(null);
|
||||
}
|
||||
renderControls() {
|
||||
const battle = this.props.room.battle;
|
||||
if (!battle) return null;
|
||||
const atEnd = battle.playbackState === Playback.Finished;
|
||||
return <div class="battle-controls" role="complementary" aria-label="Battle Controls" style="top: 370px;">
|
||||
const room = this.props.room;
|
||||
if (!room.battle) return null;
|
||||
if (room.side) {
|
||||
return this.renderPlayerControls();
|
||||
}
|
||||
const atEnd = room.battle.playbackState === Playback.Finished;
|
||||
return <div class="controls">
|
||||
<p>
|
||||
{atEnd ?
|
||||
<button class="button disabled" name="cmd" value="/play"><i class="fa fa-play"></i><br />Play</button>
|
||||
: battle.paused ?
|
||||
: room.battle.paused ?
|
||||
<button class="button" name="cmd" value="/play"><i class="fa fa-play"></i><br />Play</button>
|
||||
:
|
||||
<button class="button" name="cmd" value="/pause"><i class="fa fa-pause"></i><br />Pause</button>
|
||||
|
|
@ -198,6 +330,311 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
|
|||
</p>
|
||||
</div>;
|
||||
}
|
||||
renderMoveControls(request: BattleMoveRequest, choices: BattleChoiceBuilder) {
|
||||
const dex = this.props.room.battle.dex;
|
||||
const pokemonIndex = choices.index();
|
||||
const active = choices.currentMoveRequest();
|
||||
if (!active) return <div class="message-error">Invalid pokemon</div>;
|
||||
|
||||
if (choices.current.max || (active.maxMoves && !active.canDynamax)) {
|
||||
if (!active.maxMoves) {
|
||||
return <div class="message-error">Maxed with no max moves</div>;
|
||||
}
|
||||
return active.moves.map((moveData, i) => {
|
||||
const move = dex.getMove(moveData.name);
|
||||
const maxMoveData = active.maxMoves![i];
|
||||
const gmaxTooltip = maxMoveData.id.startsWith('gmax') ? `|${maxMoveData.id}` : ``;
|
||||
const tooltip = `maxmove|${moveData.name}|${pokemonIndex}${gmaxTooltip}`;
|
||||
return <MoveButton cmd={`/move ${i + 1} max`} type={move.type} tooltip={tooltip} moveData={moveData}>
|
||||
{maxMoveData.name}
|
||||
</MoveButton>;
|
||||
});
|
||||
}
|
||||
|
||||
if (choices.current.z) {
|
||||
if (!active.zMoves) {
|
||||
return <div class="message-error">No Z moves</div>;
|
||||
}
|
||||
return active.moves.map((moveData, i) => {
|
||||
const move = dex.getMove(moveData.name);
|
||||
const zMoveData = active.zMoves![i];
|
||||
if (!zMoveData) {
|
||||
return <button disabled> </button>;
|
||||
}
|
||||
const tooltip = `zmove|${moveData.name}|${pokemonIndex}`;
|
||||
return <MoveButton cmd={`/move ${i + 1} zmove`} type={move.type} tooltip={tooltip} moveData={{pp: 1, maxpp: 1}}>
|
||||
{zMoveData.name}
|
||||
</MoveButton>;
|
||||
});
|
||||
}
|
||||
|
||||
return active.moves.map((moveData, i) => {
|
||||
const move = dex.getMove(moveData.name);
|
||||
const tooltip = `move|${moveData.name}|${pokemonIndex}`;
|
||||
return <MoveButton cmd={`/move ${i + 1}`} type={move.type} tooltip={tooltip} moveData={moveData}>
|
||||
{move.name}
|
||||
</MoveButton>;
|
||||
});
|
||||
}
|
||||
renderMoveTargetControls(request: BattleMoveRequest, choices: BattleChoiceBuilder) {
|
||||
const battle = this.props.room.battle;
|
||||
const moveTarget = choices.getChosenMove(choices.current, choices.index()).target;
|
||||
const moveChoice = choices.stringChoice(choices.current);
|
||||
|
||||
const userSlot = choices.index();
|
||||
const userSlotCross = battle.yourSide.active.length - 1 - userSlot;
|
||||
|
||||
return [
|
||||
battle.yourSide.active.map((pokemon, i) => {
|
||||
let disabled = false;
|
||||
if (moveTarget === 'adjacentAlly' || moveTarget === 'adjacentAllyOrSelf') {
|
||||
disabled = true;
|
||||
} else if (moveTarget === 'normal' || moveTarget === 'adjacentFoe') {
|
||||
if (Math.abs(userSlotCross - i) > 1) disabled = true;
|
||||
}
|
||||
|
||||
if (pokemon?.fainted) pokemon = null;
|
||||
return <PokemonButton
|
||||
pokemon={pokemon}
|
||||
cmd={disabled ? `` : `/${moveChoice} +${i + 1}`} disabled={disabled && 'fade'} tooltip={`activepokemon|1|${i}`}
|
||||
/>;
|
||||
}).reverse(),
|
||||
<div style="clear: left"></div>,
|
||||
battle.mySide.active.map((pokemon, i) => {
|
||||
let disabled = false;
|
||||
if (moveTarget === 'adjacentFoe') {
|
||||
disabled = true;
|
||||
} else if (moveTarget === 'normal' || moveTarget === 'adjacentAlly' || moveTarget === 'adjacentAllyOrSelf') {
|
||||
if (Math.abs(userSlot - i) > 1) disabled = true;
|
||||
}
|
||||
if (moveTarget !== 'adjacentAllyOrSelf' && userSlot === i) disabled = true;
|
||||
|
||||
if (pokemon?.fainted) pokemon = null;
|
||||
return <PokemonButton
|
||||
pokemon={pokemon}
|
||||
cmd={disabled ? `` : `/${moveChoice} -${i + 1}`} disabled={disabled && 'fade'} tooltip={`activepokemon|0|${i}`}
|
||||
/>;
|
||||
}),
|
||||
];
|
||||
}
|
||||
renderSwitchControls(request: BattleMoveRequest | BattleSwitchRequest, choices: BattleChoiceBuilder) {
|
||||
const numActive = choices.requestLength();
|
||||
|
||||
const trapped = choices.currentMoveRequest()?.trapped;
|
||||
|
||||
return request.side.pokemon.map((serverPokemon, i) => {
|
||||
const cantSwitch = trapped || i < numActive || choices.plannedToSwitchIn.includes(i + 1);
|
||||
return <PokemonButton
|
||||
pokemon={serverPokemon} cmd={`/switch ${i + 1}`} disabled={cantSwitch} tooltip={`switchpokemon|${i}`}
|
||||
/>;
|
||||
});
|
||||
}
|
||||
renderTeamControls(request: | BattleTeamRequest, choices: BattleChoiceBuilder) {
|
||||
return request.side.pokemon.map((serverPokemon, i) => {
|
||||
const cantSwitch = choices.plannedToSwitchIn.includes(i + 1);
|
||||
return <PokemonButton
|
||||
pokemon={serverPokemon} cmd={`/switch ${i + 1}`} noHPBar disabled={cantSwitch && 'fade'} tooltip={`switchpokemon|${i}`}
|
||||
/>;
|
||||
});
|
||||
}
|
||||
renderTeamList() {
|
||||
const team = this.props.room.battle.myPokemon;
|
||||
if (!team) return;
|
||||
return <div class="switchcontrols">
|
||||
<h3 class="switchselect">Team</h3>
|
||||
<div class="switchmenu">
|
||||
{team.map((serverPokemon, i) => {
|
||||
return <PokemonButton
|
||||
pokemon={serverPokemon} cmd={``} noHPBar disabled={true} tooltip={`switchpokemon|${i}`}
|
||||
/>;
|
||||
})}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
renderChosenTeam(request: BattleTeamRequest, choices: BattleChoiceBuilder) {
|
||||
return choices.plannedToSwitchIn.map(slot => {
|
||||
const serverPokemon = request.side.pokemon[slot - 1];
|
||||
return <PokemonButton
|
||||
pokemon={serverPokemon} cmd={`/switch ${slot}`} disabled tooltip={`switchpokemon|${slot - 1}`}
|
||||
/>;
|
||||
});
|
||||
}
|
||||
renderOldChoices(request: BattleRequest, choices: BattleChoiceBuilder) {
|
||||
if (!choices) return null; // should not happen
|
||||
if (request.requestType !== 'move' && request.requestType !== 'switch') return;
|
||||
if (choices.isEmpty()) return null;
|
||||
|
||||
let buf: preact.ComponentChild[] = [
|
||||
<button name="cmd" value="/cancel" class="button"><i class="fa fa-chevron-left"></i> Back</button>, ' ',
|
||||
];
|
||||
if (choices.isDone() && request.noCancel) {
|
||||
buf = [];
|
||||
}
|
||||
|
||||
const battle = this.props.room.battle;
|
||||
for (let i = 0; i < choices.choices.length; i++) {
|
||||
const choiceString = choices.choices[i];
|
||||
const choice = choices.parseChoice(choiceString);
|
||||
if (!choice) continue;
|
||||
const pokemon = request.side.pokemon[i];
|
||||
const active = request.requestType === 'move' ? request.active[i] : null;
|
||||
if (choice.choiceType === 'move') {
|
||||
buf.push(`${pokemon.name} will `);
|
||||
if (choice.mega) buf.push(`Mega Evolve and `);
|
||||
if (choice.ultra) buf.push(`Ultra Burst and `);
|
||||
if (choice.max && active?.canDynamax) buf.push(active?.canGigantamax ? `Gigantamax and ` : `Dynamax and `);
|
||||
buf.push(`use `, <strong>{choices.getChosenMove(choice, i).name}</strong>);
|
||||
if (choice.targetLoc > 0) {
|
||||
const target = battle.yourSide.active[choice.targetLoc - 1];
|
||||
if (!target) {
|
||||
buf.push(` at slot ${choice.targetLoc}`);
|
||||
} else {
|
||||
buf.push(` at ${target.name}`);
|
||||
}
|
||||
} else if (choice.targetLoc < 0) {
|
||||
const target = battle.mySide.active[-choice.targetLoc - 1];
|
||||
if (!target) {
|
||||
buf.push(` at ally slot ${choice.targetLoc}`);
|
||||
} else {
|
||||
buf.push(` at ally ${target.name}`);
|
||||
}
|
||||
}
|
||||
} else if (choice.choiceType === 'switch') {
|
||||
const target = request.side.pokemon[choice.targetPokemon - 1];
|
||||
buf.push(`${pokemon.name} will switch to `, <strong>{target.name}</strong>);
|
||||
}
|
||||
buf.push(<br />);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
renderPlayerControls() {
|
||||
const room = this.props.room;
|
||||
const request = room.request;
|
||||
let choices = room.choices;
|
||||
if (!request) return 'Error: Missing request';
|
||||
if (!choices) return 'Error: Missing BattleChoiceBuilder';
|
||||
if (choices.request !== request) {
|
||||
choices = new BattleChoiceBuilder(request);
|
||||
room.choices = choices;
|
||||
}
|
||||
|
||||
if (choices.isDone()) {
|
||||
return <div class="controls">
|
||||
<div class="whatdo">
|
||||
<button name="openTimer" class="button disabled timerbutton"><i class="fa fa-hourglass-start"></i> Timer</button>
|
||||
{this.renderOldChoices(request, choices)}
|
||||
</div>
|
||||
<div class="pad">
|
||||
{request.noCancel ? null : <button name="cmd" value="/cancel" class="button">Cancel</button>}
|
||||
</div>
|
||||
{this.renderTeamList()}
|
||||
</div>;
|
||||
}
|
||||
if (request.side) room.battle.myPokemon = request.side.pokemon;
|
||||
switch (request.requestType) {
|
||||
case 'move': {
|
||||
const index = choices.index();
|
||||
const pokemon = request.side.pokemon[index];
|
||||
const moveRequest = choices.currentMoveRequest()!;
|
||||
|
||||
const canDynamax = moveRequest.canDynamax && !choices.alreadyMax;
|
||||
const canMegaEvo = moveRequest.canMegaEvo && !choices.alreadyMega;
|
||||
const canZMove = moveRequest.zMoves && !choices.alreadyZ;
|
||||
|
||||
if (choices.current.move) {
|
||||
const moveName = choices.getChosenMove(choices.current, choices.index()).name;
|
||||
return <div class="controls">
|
||||
<div class="whatdo">
|
||||
<button name="openTimer" class="button disabled timerbutton"><i class="fa fa-hourglass-start"></i> Timer</button>
|
||||
{this.renderOldChoices(request, choices)}
|
||||
{pokemon.name} should use <strong>{moveName}</strong> at where? {}
|
||||
</div>
|
||||
<div class="switchcontrols">
|
||||
<div class="switchmenu">
|
||||
{this.renderMoveTargetControls(request, choices)}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <div class="controls">
|
||||
<div class="whatdo">
|
||||
<button name="openTimer" class="button disabled timerbutton"><i class="fa fa-hourglass-start"></i> Timer</button>
|
||||
{this.renderOldChoices(request, choices)}
|
||||
What will <strong>{pokemon.name}</strong> do?
|
||||
</div>
|
||||
<div class="movecontrols">
|
||||
<h3 class="moveselect">Attack</h3>
|
||||
<div class="movemenu">
|
||||
{this.renderMoveControls(request, choices)}
|
||||
<div style="clear:left"></div>
|
||||
{canDynamax && <label class={`megaevo${choices.current.max ? ' cur' : ''}`}>
|
||||
<input type="checkbox" name="max" checked={choices.current.max} onChange={this.toggleBoostedMove} /> {}
|
||||
{moveRequest.canGigantamax ? 'Gigantamax' : 'Dynamax'}
|
||||
</label>}
|
||||
{canMegaEvo && <label class={`megaevo${choices.current.mega ? ' cur' : ''}`}>
|
||||
<input type="checkbox" name="mega" checked={choices.current.mega} onChange={this.toggleBoostedMove} /> {}
|
||||
Mega Evolution
|
||||
</label>}
|
||||
{moveRequest.canUltraBurst && <label class={`megaevo${choices.current.ultra ? ' cur' : ''}`}>
|
||||
<input type="checkbox" name="ultra" checked={choices.current.ultra} onChange={this.toggleBoostedMove} /> {}
|
||||
Ultra Burst
|
||||
</label>}
|
||||
{canZMove && <label class={`megaevo${choices.current.z ? ' cur' : ''}`}>
|
||||
<input type="checkbox" name="z" checked={choices.current.z} onChange={this.toggleBoostedMove} /> {}
|
||||
Z-Power
|
||||
</label>}
|
||||
</div>
|
||||
</div>
|
||||
<div class="switchcontrols">
|
||||
<h3 class="switchselect">Switch</h3>
|
||||
<div class="switchmenu">
|
||||
{this.renderSwitchControls(request, choices)}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
} case 'switch': {
|
||||
const pokemon = request.side.pokemon[choices.index()];
|
||||
return <div class="controls">
|
||||
<div class="whatdo">
|
||||
<button name="openTimer" class="button disabled timerbutton"><i class="fa fa-hourglass-start"></i> Timer</button>
|
||||
{this.renderOldChoices(request, choices)}
|
||||
What will <strong>{pokemon.name}</strong> do?
|
||||
</div>
|
||||
<div class="switchcontrols">
|
||||
<h3 class="switchselect">Switch</h3>
|
||||
<div class="switchmenu">
|
||||
{this.renderSwitchControls(request, choices)}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
} case 'team': {
|
||||
return <div class="controls">
|
||||
<div class="whatdo">
|
||||
<button name="openTimer" class="button disabled timerbutton"><i class="fa fa-hourglass-start"></i> Timer</button>
|
||||
{choices.plannedToSwitchIn.length > 0 ?
|
||||
[<button name="cmd" value="/cancel" class="button"><i class="fa fa-chevron-left"></i> Back</button>,
|
||||
" What about the rest of your team? "]
|
||||
:
|
||||
"How will you start the battle? "
|
||||
}
|
||||
</div>
|
||||
<div class="switchcontrols">
|
||||
<h3 class="switchselect">Choose {choices.plannedToSwitchIn.length <= 0 ? `lead` : `slot ${choices.plannedToSwitchIn.length + 1}`}</h3>
|
||||
<div class="switchmenu">
|
||||
{this.renderTeamControls(request, choices)}
|
||||
<div style="clear:left"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="switchcontrols">
|
||||
{choices.plannedToSwitchIn.length > 0 && <h3 class="switchselect">Team so far</h3>}
|
||||
<div class="switchmenu">
|
||||
{this.renderChosenTeam(request, choices)}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}}
|
||||
}
|
||||
render() {
|
||||
const room = this.props.room;
|
||||
|
||||
|
|
@ -208,7 +645,9 @@ class BattlePanel extends PSRoomPanel<BattleRoom> {
|
|||
</ChatLog>
|
||||
<ChatTextEntry room={this.props.room} onMessage={this.send} onKey={this.onKey} left={640} />
|
||||
<ChatUserList room={this.props.room} left={640} minimized />
|
||||
{this.renderControls()}
|
||||
<div class="battle-controls" role="complementary" aria-label="Battle Controls" style="top: 370px;">
|
||||
{this.renderControls()}
|
||||
</div>
|
||||
</PSPanelWrapper>;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ class ChatRoom extends PSRoom {
|
|||
}
|
||||
send(line: string, direct?: boolean) {
|
||||
this.updateTarget();
|
||||
if (!direct && !line) return;
|
||||
if (!direct && this.handleMessage(line)) return;
|
||||
if (this.pmTarget) {
|
||||
PS.send(`|/pm ${this.pmTarget}, ${line}`);
|
||||
|
|
@ -366,14 +367,14 @@ class ChatPanel extends PSRoomPanel<ChatRoom> {
|
|||
</TeamForm>
|
||||
</div> : room.challengeMenuOpen ? <div class="challenge">
|
||||
<TeamForm onSubmit={this.makeChallenge}>
|
||||
<button type="submit" class="button disabled"><strong>Challenge</strong></button> {}
|
||||
<button type="submit" class="button"><strong>Challenge</strong></button> {}
|
||||
<button name="cmd" value="/cancelchallenge" class="button">Cancel</button>
|
||||
</TeamForm>
|
||||
</div> : null;
|
||||
|
||||
const challengeFrom = room.challengedFormat ? <div class="challenge">
|
||||
<TeamForm format={room.challengedFormat} onSubmit={this.acceptChallenge}>
|
||||
<button type="submit" class="button disabled"><strong>Accept</strong></button> {}
|
||||
<button type="submit" class="button"><strong>Accept</strong></button> {}
|
||||
<button name="cmd" value="/reject" class="button">Reject</button>
|
||||
</TeamForm>
|
||||
</div> : null;
|
||||
|
|
|
|||
|
|
@ -1497,6 +1497,7 @@ a.ilink.yours {
|
|||
margin-top: -2px;
|
||||
padding: 0 8px;
|
||||
font-size: 9pt;
|
||||
line-height: 2;
|
||||
color: #555555;
|
||||
}
|
||||
.battle-controls .whatdo small {
|
||||
|
|
@ -1539,29 +1540,24 @@ a.ilink.yours {
|
|||
left: 0;
|
||||
display: block;
|
||||
}
|
||||
.shiftselect button,
|
||||
.moveselect button,
|
||||
.switchselect button {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
.shiftselect,
|
||||
.moveselect,
|
||||
.switchselect {
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
color: #555555;
|
||||
font-size: 12pt;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 9px 7px 0 7px;
|
||||
}
|
||||
.shiftselect button {
|
||||
.shiftselect {
|
||||
color: #445588;
|
||||
}
|
||||
.moveselect button {
|
||||
.moveselect {
|
||||
color: #884422;
|
||||
cursor: default;
|
||||
}
|
||||
.switchselect button {
|
||||
.switchselect {
|
||||
color: #445588;
|
||||
cursor: default;
|
||||
}
|
||||
.switchmenu button {
|
||||
position: relative;
|
||||
|
|
@ -1668,28 +1664,34 @@ a.ilink.yours {
|
|||
font-size: 8pt;
|
||||
}
|
||||
.megaevo {
|
||||
clear: both;
|
||||
display: block;
|
||||
width: 180px;
|
||||
margin: 0 auto 0;
|
||||
position: relative;
|
||||
top: 6px;
|
||||
left: -7px;
|
||||
padding: 2px;
|
||||
text-align: center;
|
||||
clear: both;
|
||||
display: block;
|
||||
width: 180px;
|
||||
margin: 0 auto 0;
|
||||
position: relative;
|
||||
top: 6px;
|
||||
left: -7px;
|
||||
padding: 2px;
|
||||
text-align: center;
|
||||
|
||||
cursor: pointer;
|
||||
border: 1px solid #BBB;
|
||||
border-radius: 3px;
|
||||
color: #333;
|
||||
background: #EEF2F5;
|
||||
font-size: 10pt;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
border: 1px solid #BBB;
|
||||
border-radius: 3px;
|
||||
color: #333;
|
||||
background: #EEF2F5;
|
||||
font-size: 10pt;
|
||||
font-weight: bold;
|
||||
}
|
||||
.megaevo:hover {
|
||||
border-color: #888;
|
||||
background: #E5E5E5;
|
||||
color: black;
|
||||
border-color: #888;
|
||||
background: #E5E5E5;
|
||||
color: black;
|
||||
}
|
||||
.megaevo input {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: -1px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.switchmenu,
|
||||
|
|
@ -1740,93 +1742,6 @@ a.ilink.yours {
|
|||
color: #777777 !important;
|
||||
}
|
||||
|
||||
@media (max-height:570px) and (min-width: 440px) {
|
||||
/*
|
||||
* This is the black move/switch menu for low-res screens
|
||||
*/
|
||||
|
||||
.controls {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: auto;
|
||||
background: #444444;
|
||||
background: rgba(40,40,40,.85);
|
||||
color: #FFFFFF;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.battle-controls .whatdo {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.battle-controls .whatdo small.weak {
|
||||
color: #DDDD55;
|
||||
border-color: #DDDD55;
|
||||
}
|
||||
.battle-controls .whatdo small.critical {
|
||||
color: #FF7766;
|
||||
border-color: #FF7766;
|
||||
}
|
||||
.battle-controls .movecontrols, .battle-controls .switchcontrols {
|
||||
max-width: 640px;
|
||||
}
|
||||
.movemenu {
|
||||
display: none;
|
||||
padding: 0 75px 0 85px;
|
||||
}
|
||||
.switchmenu {
|
||||
display: none;
|
||||
max-width: 325px;
|
||||
padding: 0 75px 0 85px;
|
||||
margin: 0 0 0 auto;
|
||||
}
|
||||
.moveselect {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
bottom: 20px;
|
||||
}
|
||||
.switchselect {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
}
|
||||
.shiftselect {
|
||||
position: absolute;
|
||||
right: 150px;
|
||||
bottom: 20px;
|
||||
}
|
||||
.moveselect button, .switchselect button, .shiftselect button {
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
background: #E5E5E5;
|
||||
}
|
||||
.megaevo {
|
||||
margin: 0 auto 8px 50px;
|
||||
}
|
||||
|
||||
.battle-controls .whatdo {
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
.battle-controls .move-controls .whatdo,
|
||||
.battle-controls .switch-controls .whatdo {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
.move-controls .movemenu,
|
||||
.switch-controls .switchmenu {
|
||||
display: block;
|
||||
margin-right: 0;
|
||||
}
|
||||
.move-controls .moveselect button,
|
||||
.switch-controls .switchselect button,
|
||||
.shiftselect button {
|
||||
background: #BBBBBB;
|
||||
}
|
||||
.controls .timer {
|
||||
float: right;
|
||||
margin-top: -25px;
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************************
|
||||
* Team Selector
|
||||
*********************************************************/
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@
|
|||
<script src="data/text.js"></script>
|
||||
<script src="js/battle-tooltips.js"></script>
|
||||
<script src="js/battle.js"></script>
|
||||
<script src="js/battle-choices.js"></script>
|
||||
<script src="js/panel-battle.js"></script>
|
||||
|
||||
<script src="js/battle-dex-data.js"></script>
|
||||
|
|
@ -91,4 +92,7 @@
|
|||
<script src="js/battle-searchresults.js?"></script>
|
||||
<script src="js/panel-teambuilder-team.js?"></script>
|
||||
|
||||
<script src="https://play.pokemonshowdown.com/data/pokedex-mini.js"></script>
|
||||
<script src="https://play.pokemonshowdown.com/data/pokedex-mini-bw.js"></script>
|
||||
|
||||
</body></html>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user