Preact: Support Pokemon Champions + Remove IV inputs in Champions (#2676)

This commit is contained in:
shrianshChari
2026-05-22 02:31:47 -07:00
committed by GitHub
parent 0162aaf3fe
commit 2f4196b15d
5 changed files with 167 additions and 143 deletions

View File

@@ -2449,135 +2449,137 @@
}
buf += '</div>';
if (this.curTeam.gen > 2) {
buf += '<div class="col ivcol"><div><strong>IVs</strong></div>';
if (!set.ivs) set.ivs = {};
for (var i in stats) {
if (set.ivs[i] === undefined || isNaN(set.ivs[i])) set.ivs[i] = 31;
var val = '' + (set.ivs[i]);
buf += '<div><input type="number" name="iv-' + i + '" value="' + BattleLog.escapeHTML(val) + '" class="textbox inputform numform" min="' + (usesStatPoints ? 31 : 0) + '" max="31" step="1"' + (usesStatPoints ? ' disabled' : '') + ' /></div>';
if (!usesStatPoints) {
if (this.curTeam.gen > 2) {
buf += '<div class="col ivcol"><div><strong>IVs</strong></div>';
if (!set.ivs) set.ivs = {};
for (var i in stats) {
if (set.ivs[i] === undefined || isNaN(set.ivs[i])) set.ivs[i] = 31;
var val = '' + (set.ivs[i]);
buf += '<div><input type="number" name="iv-' + i + '" value="' + BattleLog.escapeHTML(val) + '" class="textbox inputform numform" min="' + (usesStatPoints ? 31 : 0) + '" max="31" step="1"' + (usesStatPoints ? ' disabled' : '') + ' /></div>';
}
var hpType = '';
if (set.moves) {
for (var i = 0; i < set.moves.length; i++) {
var moveid = toID(set.moves[i]);
if (moveid.slice(0, 11) === 'hiddenpower') {
hpType = moveid.slice(11);
}
}
}
if (hpType && !this.canHyperTrain(set)) {
var hpIVs;
switch (hpType) {
case 'dark':
hpIVs = ['111111']; break;
case 'dragon':
hpIVs = ['011111', '101111', '110111']; break;
case 'ice':
hpIVs = ['010111', '100111', '111110']; break;
case 'psychic':
hpIVs = ['011110', '101110', '110110']; break;
case 'electric':
hpIVs = ['010110', '100110', '111011']; break;
case 'grass':
hpIVs = ['011011', '101011', '110011']; break;
case 'water':
hpIVs = ['100011', '111010']; break;
case 'fire':
hpIVs = ['101010', '110010']; break;
case 'steel':
hpIVs = ['100010', '111101']; break;
case 'ghost':
hpIVs = ['101101', '110101']; break;
case 'bug':
hpIVs = ['100101', '111100', '101100']; break;
case 'rock':
hpIVs = ['001100', '110100', '100100']; break;
case 'ground':
hpIVs = ['000100', '111001', '101001']; break;
case 'poison':
hpIVs = ['001001', '110001', '100001']; break;
case 'flying':
hpIVs = ['000001', '111000', '101000']; break;
case 'fighting':
hpIVs = ['001000', '110000', '100000']; break;
}
buf += '<div style="margin-left:-80px;text-align:right"><select name="ivspread" class="button">';
buf += '<option value="" selected>HP ' + hpType.charAt(0).toUpperCase() + hpType.slice(1) + ' IVs</option>';
var minStat = this.curTeam.gen >= 6 ? 0 : 2;
buf += '<optgroup label="min Atk">';
for (var i = 0; i < hpIVs.length; i++) {
var spread = '';
for (var j = 0; j < 6; j++) {
if (j) spread += '/';
spread += (j === 1 ? minStat : 30) + parseInt(hpIVs[i].charAt(j), 10);
}
buf += '<option value="' + spread + '">' + spread + '</option>';
}
buf += '</optgroup>';
buf += '<optgroup label="min Atk, min Spe">';
for (var i = 0; i < hpIVs.length; i++) {
var spread = '';
for (var j = 0; j < 6; j++) {
if (j) spread += '/';
spread += (j === 5 || j === 1 ? minStat : 30) + parseInt(hpIVs[i].charAt(j), 10);
}
buf += '<option value="' + spread + '">' + spread + '</option>';
}
buf += '</optgroup>';
buf += '<optgroup label="max all">';
for (var i = 0; i < hpIVs.length; i++) {
var spread = '';
for (var j = 0; j < 6; j++) {
if (j) spread += '/';
spread += 30 + parseInt(hpIVs[i].charAt(j), 10);
}
buf += '<option value="' + spread + '">' + spread + '</option>';
}
buf += '</optgroup>';
buf += '<optgroup label="min Spe">';
for (var i = 0; i < hpIVs.length; i++) {
var spread = '';
for (var j = 0; j < 6; j++) {
if (j) spread += '/';
spread += (j === 5 ? minStat : 30) + parseInt(hpIVs[i].charAt(j), 10);
}
buf += '<option value="' + spread + '">' + spread + '</option>';
}
buf += '</optgroup>';
buf += '</select></div>';
} else if (!usesStatPoints) {
buf += '<div style="margin-left:-80px;text-align:right"><select name="ivspread" class="button">';
buf += '<option value="" selected>IV spreads</option>';
buf += '<optgroup label="min Atk">';
buf += '<option value="31/0/31/31/31/31">31/0/31/31/31/31</option>';
buf += '</optgroup>';
buf += '<optgroup label="min Atk, min Spe">';
buf += '<option value="31/0/31/31/31/0">31/0/31/31/31/0</option>';
buf += '</optgroup>';
buf += '<optgroup label="max all">';
buf += '<option value="31/31/31/31/31/31">31/31/31/31/31/31</option>';
buf += '</optgroup>';
buf += '<optgroup label="min Spe">';
buf += '<option value="31/31/31/31/31/0">31/31/31/31/31/0</option>';
buf += '</optgroup>';
buf += '</select></div>';
}
buf += '</div>';
} else {
buf += '<div class="col ivcol"><div><strong>DVs</strong></div>';
if (!set.ivs) set.ivs = {};
for (var i in stats) {
if (set.ivs[i] === undefined || isNaN(set.ivs[i])) set.ivs[i] = 31;
var val = '' + Math.floor(set.ivs[i] / 2);
buf += '<div><input type="number" name="iv-' + i + '" value="' + BattleLog.escapeHTML(val) + '" class="textbox inputform numform" min="0" max="15" step="1" /></div>';
}
buf += '</div>';
}
var hpType = '';
if (set.moves) {
for (var i = 0; i < set.moves.length; i++) {
var moveid = toID(set.moves[i]);
if (moveid.slice(0, 11) === 'hiddenpower') {
hpType = moveid.slice(11);
}
}
}
if (hpType && !this.canHyperTrain(set)) {
var hpIVs;
switch (hpType) {
case 'dark':
hpIVs = ['111111']; break;
case 'dragon':
hpIVs = ['011111', '101111', '110111']; break;
case 'ice':
hpIVs = ['010111', '100111', '111110']; break;
case 'psychic':
hpIVs = ['011110', '101110', '110110']; break;
case 'electric':
hpIVs = ['010110', '100110', '111011']; break;
case 'grass':
hpIVs = ['011011', '101011', '110011']; break;
case 'water':
hpIVs = ['100011', '111010']; break;
case 'fire':
hpIVs = ['101010', '110010']; break;
case 'steel':
hpIVs = ['100010', '111101']; break;
case 'ghost':
hpIVs = ['101101', '110101']; break;
case 'bug':
hpIVs = ['100101', '111100', '101100']; break;
case 'rock':
hpIVs = ['001100', '110100', '100100']; break;
case 'ground':
hpIVs = ['000100', '111001', '101001']; break;
case 'poison':
hpIVs = ['001001', '110001', '100001']; break;
case 'flying':
hpIVs = ['000001', '111000', '101000']; break;
case 'fighting':
hpIVs = ['001000', '110000', '100000']; break;
}
buf += '<div style="margin-left:-80px;text-align:right"><select name="ivspread" class="button">';
buf += '<option value="" selected>HP ' + hpType.charAt(0).toUpperCase() + hpType.slice(1) + ' IVs</option>';
var minStat = this.curTeam.gen >= 6 ? 0 : 2;
buf += '<optgroup label="min Atk">';
for (var i = 0; i < hpIVs.length; i++) {
var spread = '';
for (var j = 0; j < 6; j++) {
if (j) spread += '/';
spread += (j === 1 ? minStat : 30) + parseInt(hpIVs[i].charAt(j), 10);
}
buf += '<option value="' + spread + '">' + spread + '</option>';
}
buf += '</optgroup>';
buf += '<optgroup label="min Atk, min Spe">';
for (var i = 0; i < hpIVs.length; i++) {
var spread = '';
for (var j = 0; j < 6; j++) {
if (j) spread += '/';
spread += (j === 5 || j === 1 ? minStat : 30) + parseInt(hpIVs[i].charAt(j), 10);
}
buf += '<option value="' + spread + '">' + spread + '</option>';
}
buf += '</optgroup>';
buf += '<optgroup label="max all">';
for (var i = 0; i < hpIVs.length; i++) {
var spread = '';
for (var j = 0; j < 6; j++) {
if (j) spread += '/';
spread += 30 + parseInt(hpIVs[i].charAt(j), 10);
}
buf += '<option value="' + spread + '">' + spread + '</option>';
}
buf += '</optgroup>';
buf += '<optgroup label="min Spe">';
for (var i = 0; i < hpIVs.length; i++) {
var spread = '';
for (var j = 0; j < 6; j++) {
if (j) spread += '/';
spread += (j === 5 ? minStat : 30) + parseInt(hpIVs[i].charAt(j), 10);
}
buf += '<option value="' + spread + '">' + spread + '</option>';
}
buf += '</optgroup>';
buf += '</select></div>';
} else if (!usesStatPoints) {
buf += '<div style="margin-left:-80px;text-align:right"><select name="ivspread" class="button">';
buf += '<option value="" selected>IV spreads</option>';
buf += '<optgroup label="min Atk">';
buf += '<option value="31/0/31/31/31/31">31/0/31/31/31/31</option>';
buf += '</optgroup>';
buf += '<optgroup label="min Atk, min Spe">';
buf += '<option value="31/0/31/31/31/0">31/0/31/31/31/0</option>';
buf += '</optgroup>';
buf += '<optgroup label="max all">';
buf += '<option value="31/31/31/31/31/31">31/31/31/31/31/31</option>';
buf += '</optgroup>';
buf += '<optgroup label="min Spe">';
buf += '<option value="31/31/31/31/31/0">31/31/31/31/31/0</option>';
buf += '</optgroup>';
buf += '</select></div>';
}
buf += '</div>';
} else {
buf += '<div class="col ivcol"><div><strong>DVs</strong></div>';
if (!set.ivs) set.ivs = {};
for (var i in stats) {
if (set.ivs[i] === undefined || isNaN(set.ivs[i])) set.ivs[i] = 31;
var val = '' + Math.floor(set.ivs[i] / 2);
buf += '<div><input type="number" name="iv-' + i + '" value="' + BattleLog.escapeHTML(val) + '" class="textbox inputform numform" min="0" max="15" step="1" /></div>';
}
buf += '</div>';
}
buf += '<div class="col statscol"><div></div>';

View File

@@ -243,7 +243,7 @@ export class PSSearchResults extends preact.Component<{
let pp = (move.pp === 1 || move.noPPBoosts ? move.pp : move.pp * 8 / 5);
if (search.dex.gen < 3) pp = Math.min(61, pp);
if (search.dex.modid === 'champions') {
pp = move.pp > 20 ? 20 : pp;
pp = move.pp > 20 ? 20 : move.pp;
if (!move.noPPBoosts) pp = (pp / 5 + 1) * 4;
}
return <li class="result"><a

View File

@@ -66,6 +66,7 @@ export class TeamEditorState extends PSModel {
isLetsGo = false;
isNatDex = false;
isBDSP = false;
isChampions = false;
formeLegality: 'normal' | 'hackmons' | 'custom' = 'normal';
abilityLegality: 'normal' | 'hackmons' = 'normal';
defaultLevel = 100;
@@ -98,6 +99,7 @@ export class TeamEditorState extends PSModel {
this.isLetsGo = formatid.includes('letsgo');
this.isNatDex = formatid.includes('nationaldex') || formatid.includes('natdex');
this.isBDSP = formatid.includes('bdsp');
this.isChampions = formatid.includes('champions');
if (formatid.includes('almostanyability') || formatid.includes('aaa')) {
this.abilityLegality = 'hackmons';
} else {
@@ -663,7 +665,8 @@ export class TeamEditorState extends PSModel {
}
}
getStat(stat: StatName, set: Dex.PokemonSet, ivOverride: number, evOverride?: number, natureOverride?: number) {
const supportsEVs = !this.isLetsGo;
const usesStatPoints = this.isChampions;
const supportsEVs = !this.isLetsGo && !usesStatPoints;
const supportsAVs = !supportsEVs;
// do this after setting set.evs because it's assumed to exist
@@ -1677,7 +1680,7 @@ class TeamTextbox extends preact.Component<{
<span class="detailcell">
<label>Shiny</label>{set.shiny ? 'Yes' : '\u2014'}
</span>
{editor.gen === 9 ? (
{editor.gen === 9 && !editor.isChampions ? (
<span class="detailcell">
<label>Tera</label><PSIcon type={set.teraType || species.requiredTeraType || species.types[0]} />
</span>
@@ -2001,7 +2004,7 @@ class TeamWizard extends preact.Component<{
<strong class="label">Shiny</strong> {}
{set.shiny ? <img src={`${Dex.resourcePrefix}sprites/misc/shiny.png`} width={22} height={22} alt="Yes" /> : '\u2014'}
</span>}
{editor.gen === 9 && <span class="detailcell">
{editor.gen === 9 && !editor.isChampions && <span class="detailcell">
<strong class="label">Tera</strong> {}
<PSIcon type={set.teraType || species.requiredTeraType || species.types[0]} />
</span>}
@@ -2480,6 +2483,7 @@ class StatForm extends preact.Component<{
const hpIVdata = hpType && !editor.canHyperTrain(set) && editor.getHPIVs(hpType) || null;
const autoSpread = set.ivs && editor.defaultIVs(set, false);
const autoSpreadValue = autoSpread && Object.values(autoSpread).join('/');
if (editor.isChampions) return null;
if (!hpIVdata) {
return <select name="ivspread" class="button" onChange={this.changeIVSpread}>
<option value="" selected>IV spreads</option>
@@ -2836,8 +2840,9 @@ class StatForm extends preact.Component<{
};
maxEVs() {
const editor = this.props.editor;
const useEVs = !editor.isLetsGo && editor.gen >= 3;
return useEVs ? 510 : Infinity;
const usesStatPoints = editor.isChampions;
const useEVs = !editor.isLetsGo && editor.gen >= 3 && !usesStatPoints;
return usesStatPoints ? 66 : useEVs ? 510 : Infinity;
}
override render() {
const { editor, set } = this.props;
@@ -2847,9 +2852,10 @@ class StatForm extends preact.Component<{
const nature = BattleNatures[set.nature || 'Serious'];
const useEVs = !editor.isLetsGo;
const usesStatPoints = editor.isChampions;
const useEVs = !editor.isLetsGo && !usesStatPoints;
// const useAVs = !useEVs && team.format.endsWith('norestrictions');
const maxEV = useEVs ? 252 : 200;
const maxEV = usesStatPoints ? 32 : useEVs ? 252 : 200;
const stepEV = useEVs ? 4 : 1;
const defaultEV = useEVs && editor.gen <= 2 && !set.evs ? maxEV : 0;
const useIVs = editor.gen > 2;
@@ -2893,9 +2899,9 @@ class StatForm extends preact.Component<{
<th>{/* Stat name */}</th>
<th>Base</th>
<th class="setstatbar">{/* Stat bar */}</th>
<th>{useEVs ? 'EVs' : 'AVs'}</th>
<th>{useEVs ? 'EVs' : usesStatPoints ? 'Points' : 'AVs'}</th>
<th>{/* EV slider */}</th>
<th>{useIVs ? 'IVs' : 'DVs'}</th>
{!editor.isChampions && <th>{useIVs ? 'IVs' : 'DVs'}</th>}
<th>{/* Final stat */}</th>
</tr>
{stats.map(([statID, statName, stat]) => <tr>
@@ -2912,10 +2918,11 @@ class StatForm extends preact.Component<{
type="range" class="evslider" tabIndex={-1} aria-hidden
onInput={this.changeEV} onChange={this.changeEV}
/></td>
<td><input
{!editor.isChampions && <td><input
name={`iv-${statID}`} min={0} max={useIVs ? 31 : 15} placeholder={`${defaultIVs[statID]}`} style="width:40px"
type="number" inputMode="numeric" class="textbox default-placeholder" onInput={this.changeIV} onChange={this.changeIV}
/></td>
type="number" inputMode="numeric" class="textbox default-placeholder" onInput={this.changeIV}
onChange={this.changeIV} disabled={usesStatPoints}
/></td>}
<td style="text-align:right"><strong>{stat}</strong></td>
</tr>)}
<tr>
@@ -3075,7 +3082,7 @@ class DetailsForm extends preact.Component<{
name="level" value={set.level ?? ''} placeholder={`${editor.defaultLevel}`}
type="number" inputMode="numeric" min="1" max="100" step="1"
class="textbox inputform numform default-placeholder" style="width: 50px"
onInput={this.changeLevel} onChange={this.changeLevel}
onInput={this.changeLevel} onChange={this.changeLevel} disabled={editor.isChampions}
/></label><small>(You probably want to change the team's levels by changing the format, not here)</small></p>
{editor.gen > 1 && (<>
<p><div class="label">Shiny: <div class="labeled">
@@ -3151,7 +3158,7 @@ class DetailsForm extends preact.Component<{
))}
</select></label>
</p>}
{editor.gen === 9 && <p>
{editor.gen === 9 && !editor.isChampions && <p>
<label class="label" title="Tera Type">
Tera Type: {}
{species.requiredTeraType && editor.formeLegality === 'normal' ? (

View File

@@ -250,6 +250,7 @@ export class MainMenuRoom extends PSRoom {
let partner = false;
let bestOfDefault = false;
let teraPreviewDefault = false;
let itemClauseDefault = false;
let team: 'preset' | null = null;
let teambuilderLevel: number | null = null;
let lastCommaIndex = name.lastIndexOf(',');
@@ -264,6 +265,7 @@ export class MainMenuRoom extends PSRoom {
if (code & 32) partner = true;
if (code & 64) bestOfDefault = true;
if (code & 128) teraPreviewDefault = true;
if (code & 256) itemClauseDefault = true;
} else {
// Backwards compatibility: late 0.9.0 -> 0.10.0
if (name.substr(name.length - 2) === ',#') { // preset teams
@@ -327,6 +329,7 @@ export class MainMenuRoom extends PSRoom {
tournamentShow,
bestOfDefault,
teraPreviewDefault,
itemClauseDefault,
rated: searchShow && id.substr(4, 7) !== 'unrated',
teambuilderLevel,
partner,
@@ -825,6 +828,7 @@ export class TeamForm extends preact.Component<{
format = '';
teraPreview = false;
bestOf = false;
itemClause = false;
changeFormat = (ev: Event) => {
this.format = (ev.target as HTMLButtonElement).value;
};
@@ -851,6 +855,10 @@ export class TeamForm extends preact.Component<{
const value = this.base?.querySelector<HTMLInputElement>('input[name=bestofvalue]')?.value;
format = `${format}${hasCustomRules ? `, Best of = ${value!}` : `@@@ Best of = ${value!}`}`;
}
if (this.itemClause) {
const hasCustomRules = format.includes('@@@');
format = `${format}${hasCustomRules ? ', Item Clause = 1' : '@@@ Item Clause = 1'}`;
}
PS.teams.loadTeam(team).then(() => {
(validate === 'validate' ? this.props.onValidate : this.props.onSubmit)?.(ev, format, team);
});
@@ -860,6 +868,7 @@ export class TeamForm extends preact.Component<{
const rule = (ev.target as HTMLInputElement)?.name;
if (rule === 'terapreview') this.teraPreview = checked;
if (rule === 'bestof') this.bestOf = checked;
if (rule === 'itemclause=1') this.itemClause = checked;
};
handleClick = (ev: Event) => {
let target = ev.target as HTMLButtonElement | null;
@@ -927,6 +936,11 @@ export class TeamForm extends preact.Component<{
name="bestofvalue" type="number" min="3" max="9" step="2" value="3" style="width: 28px; vertical-align: initial;"
/>
</abbr></label></p>}
{this.props.selectType === 'challenge' &&
window.BattleFormats[formatId]?.itemClauseDefault && <p>
<label class="checkbox">
<input type="checkbox" name="itemclause" onChange={this.toggleCustomRule} />
<abbr title="Start a battle with Item Clause">Item Clause</abbr></label></p>}
<p>{this.props.children}</p>
</form>;
}

View File

@@ -349,6 +349,7 @@ export interface FormatData {
tournamentShow?: boolean;
bestOfDefault?: boolean;
teraPreviewDefault?: boolean;
itemClauseDefault?: boolean;
rated: boolean;
teambuilderLevel?: number | null;
partner?: boolean;