Improve EV guesser

"Suggested spread" is now renamed "Guessed spread", to make it clearer
that it's not exactly a suggestion, more of a guess of what might be a
good spread.

Speed EVs are now suggested a lot more often, with the new basic
threshold at 80+ Spe instead of 95+. (71+ Spe is still enough to get
Spe EVs on a non-bulky stat spread.)

EV spreads are now guessed based on post-Mega stats (so e.g. Mega
Metagross gets Spe EVs).

In addition, Speed EVs are now suggested for support spreads above 110
base Spe (mostly for support Starmie sets).

I wouldn't call the EV guesser _good_ by any means, but I still
strongly believe it's better than nothing.
This commit is contained in:
Guangcong Luo
2019-03-04 10:40:34 -06:00
parent 331a129644
commit 9c3a9addcd
2 changed files with 61 additions and 27 deletions

View File

@@ -1815,9 +1815,9 @@
var guessedEVs = guess.evs;
var guessedPlus = guess.plusStat;
var guessedMinus = guess.minusStat;
buf += '<p class="suggested"><small>Suggested spread:';
buf += '<p class="suggested"><small>Guessed spread:';
if (role === '?') {
buf += ' (Please choose 4 moves to get a suggested spread) (<a target="_blank" href="' + this.smogdexLink(template) + '">Smogon&nbsp;analysis</a>)</small></p>';
buf += ' (Please choose 4 moves to get a guessed spread) (<a target="_blank" href="' + this.smogdexLink(template) + '">Smogon&nbsp;analysis</a>)</small></p>';
} else {
buf += ' </small><button name="setStatFormGuesses">' + role + ': ';
for (var i in BattleStatNames) {
@@ -1829,7 +1829,7 @@
if (guessedPlus && guessedMinus) buf += ' (+' + BattleStatNames[guessedPlus] + ', -' + BattleStatNames[guessedMinus] + ')';
else buf = buf.slice(0, -3);
buf += '</button><small> (<a target="_blank" href="' + this.smogdexLink(template) + '">Smogon&nbsp;analysis</a>)</small></p>';
//buf += ' <small>(' + role + ' | bulk: phys ' + Math.round(guess.moveCount.physicalBulk/1000) + ' + spec ' + Math.round(guess.moveCount.specialBulk/1000) + ' = ' + Math.round(guess.moveCount.bulk/1000) + ')</small>';
// buf += ' <small>(' + role + ' | bulk: phys ' + Math.round(guess.moveCount.physicalBulk/1000) + ' + spec ' + Math.round(guess.moveCount.specialBulk/1000) + ' = ' + Math.round(guess.moveCount.bulk/1000) + ')</small>';
}
if (setGuessed) {

View File

@@ -1585,7 +1585,12 @@ class BattleStatGuesser {
constructor(formatid: ID) {
this.formatid = formatid;
this.dex = formatid ? Dex.mod(formatid.slice(0, 4) as ID) : Dex;
this.ignoreEVLimits = (this.dex.gen < 3 || this.formatid.endsWith('hackmons') || this.formatid === 'gen7metronomebattle' || this.formatid.endsWith('norestrictions'));
this.ignoreEVLimits = (
this.dex.gen < 3 ||
this.formatid.endsWith('hackmons') ||
this.formatid === 'gen7metronomebattle' ||
this.formatid.endsWith('norestrictions')
);
this.supportsEVs = !this.formatid.startsWith('gen7letsgo');
this.supportsAVs = !this.supportsEVs && this.formatid.endsWith('norestrictions');
}
@@ -1625,26 +1630,35 @@ class BattleStatGuesser {
'physicalBulk': 0,
};
let hasMove: {[moveid: string]: 1} = {};
let template = this.dex.getTemplate(set.species || set.name);
let stats = template.baseStats;
if (!stats) return '?';
let itemid = toId(set.item);
let item = this.dex.getItem(itemid);
let abilityid = toId(set.ability);
if (set.moves.length < 4 && template.id !== 'unown' && template.id !== 'ditto' && this.dex.gen > 2 && this.formatid !== 'gen7metronomebattle') return '?';
let template = this.dex.getTemplate(set.species || set.name);
if (item.megaEvolves === template.species) template = this.dex.getTemplate(item.megaStone);
if (!template.exists) return '?';
let stats = template.baseStats;
if (set.moves.length < 1) return '?';
let needsFourMoves = !['unown', 'ditto'].includes(template.id);
let moveids = set.moves.map(toId);
if (moveids.includes('lastresort' as ID)) needsFourMoves = false;
if (set.moves.length < 4 && needsFourMoves && this.formatid !== 'gen7metronomebattle') {
return '?';
}
for (let i = 0, len = set.moves.length; i < len; i++) {
let move = Dex.getMove(set.moves[i]);
hasMove[move.id] = 1;
if (move.category === 'Status') {
if (move.id === 'batonpass' || move.id === 'healingwish' || move.id === 'lunardance') {
if (['batonpass', 'healingwish', 'lunardance'].includes(move.id)) {
moveCount['Support']++;
} else if (move.id === 'metronome' || move.id === 'assist' || move.id === 'copycat' || move.id === 'mefirst') {
} else if (['metronome', 'assist', 'copycat', 'mefirst'].includes(move.id)) {
moveCount['Physical'] += 0.5;
moveCount['Special'] += 0.5;
} else if (move.id === 'naturepower') {
moveCount['Special']++;
} else if (move.id === 'protect' || move.id === 'detect' || move.id === 'spikyshield' || move.id === 'kingsshield') {
} else if (['protect', 'detect', 'spikyshield', 'kingsshield'].includes(move.id)) {
moveCount['Stall']++;
} else if (move.id === 'wish') {
moveCount['Restoration']++;
@@ -1654,23 +1668,27 @@ class BattleStatGuesser {
moveCount['Restoration']++;
moveCount['Stall']++;
} else if (move.target === 'self') {
if (move.id === 'agility' || move.id === 'rockpolish' || move.id === 'shellsmash' || move.id === 'growth' || move.id === 'workup') {
if (['agility', 'rockpolish', 'shellsmash', 'growth', 'workup'].includes(move.id)) {
moveCount['PhysicalSetup']++;
moveCount['SpecialSetup']++;
} else if (move.id === 'dragondance' || move.id === 'swordsdance' || move.id === 'coil' || move.id === 'bulkup' || move.id === 'curse' || move.id === 'bellydrum') {
} else if (['dragondance', 'swordsdance', 'coil', 'bulkup', 'curse', 'bellydrum'].includes(move.id)) {
moveCount['PhysicalSetup']++;
} else if (move.id === 'nastyplot' || move.id === 'tailglow' || move.id === 'quiverdance' || move.id === 'calmmind' || move.id === 'geomancy') {
} else if (['nastyplot', 'tailglow', 'quiverdance', 'calmmind', 'geomancy'].includes(move.id)) {
moveCount['SpecialSetup']++;
}
if (move.id === 'substitute') moveCount['Stall']++;
moveCount['Setup']++;
} else {
if (move.id === 'toxic' || move.id === 'leechseed' || move.id === 'willowisp') moveCount['Stall']++;
if (['toxic', 'leechseed', 'willowisp'].includes(move.id)) {
moveCount['Stall']++;
}
moveCount['Support']++;
}
} else if (move.id === 'counter' || move.id === 'endeavor' || move.id === 'metalburst' || move.id === 'mirrorcoat' || move.id === 'rapidspin') {
} else if (['counter', 'endeavor', 'metalburst', 'mirrorcoat', 'rapidspin'].includes(move.id)) {
moveCount['Support']++;
} else if (move.id === 'nightshade' || move.id === 'seismictoss' || move.id === 'psywave' || move.id === 'superfang' || move.id === 'naturesmadness' || move.id === 'foulplay' || move.id === 'endeavor' || move.id === 'finalgambit') {
} else if ([
'nightshade', 'seismictoss', 'psywave', 'superfang', 'naturesmadness', 'foulplay', 'endeavor', 'finalgambit',
].includes(move.id)) {
moveCount['Offense']++;
} else if (move.id === 'fellstinger') {
moveCount['PhysicalSetup']++;
@@ -1678,8 +1696,12 @@ class BattleStatGuesser {
} else {
moveCount[move.category]++;
moveCount['Offense']++;
if (move.id === 'knockoff') moveCount['Support']++;
if (move.id === 'scald' || move.id === 'voltswitch' || move.id === 'uturn') moveCount[move.category] -= 0.2;
if (move.id === 'knockoff') {
moveCount['Support']++;
}
if (['scald', 'voltswitch', 'uturn'].includes(move.id)) {
moveCount[move.category] -= 0.2;
}
}
}
if (hasMove['batonpass']) moveCount['Support'] += moveCount['Setup'];
@@ -1690,7 +1712,7 @@ class BattleStatGuesser {
if (hasMove['dragondance'] || hasMove['quiverdance']) moveCount['Ultrafast'] = 1;
let isFast = (stats.spe > 95);
let isFast = (stats.spe >= 80);
let physicalBulk = (stats.hp + 75) * (stats.def + 87);
let specialBulk = (stats.hp + 75) * (stats.spd + 87);
@@ -1711,7 +1733,9 @@ class BattleStatGuesser {
specialBulk *= 1.3;
moveCount['SpecialStall']++;
}
if (abilityid === 'sandstream' && (template.types[0] === 'Rock' || template.types[1] === 'Rock')) specialBulk *= 1.5;
if (abilityid === 'sandstream' && template.types.includes('Rock')) {
specialBulk *= 1.5;
}
if (hasMove['bellydrum']) {
physicalBulk *= 0.6;
@@ -1768,7 +1792,9 @@ class BattleStatGuesser {
physicalBulk *= 1.5;
specialBulk *= 1.5;
}
if (itemid === 'assaultvest') specialBulk *= 1.5;
if (itemid === 'assaultvest') {
specialBulk *= 1.5;
}
let bulk = physicalBulk + specialBulk;
if (bulk < 46000 && stats.spe >= 70) isFast = true;
@@ -1777,7 +1803,10 @@ class BattleStatGuesser {
moveCount['physicalBulk'] = physicalBulk;
moveCount['specialBulk'] = specialBulk;
if (hasMove['agility'] || hasMove['dragondance'] || hasMove['quiverdance'] || hasMove['rockpolish'] || hasMove['shellsmash'] || hasMove['flamecharge']) {
if (
hasMove['agility'] || hasMove['dragondance'] || hasMove['quiverdance'] ||
hasMove['rockpolish'] || hasMove['shellsmash'] || hasMove['flamecharge']
) {
isFast = true;
} else if (abilityid === 'unburden' || abilityid === 'speedboost' || abilityid === 'motordrive') {
isFast = true;
@@ -1788,7 +1817,9 @@ class BattleStatGuesser {
} else if (itemid === 'salacberry') {
isFast = true;
}
if (hasMove['agility'] || hasMove['shellsmash'] || hasMove['autotomize'] || hasMove['shiftgear'] || hasMove['rockpolish']) moveCount['Ultrafast'] = 2;
if (hasMove['agility'] || hasMove['shellsmash'] || hasMove['autotomize'] || hasMove['shiftgear'] || hasMove['rockpolish']) {
moveCount['Ultrafast'] = 2;
}
moveCount['Fast'] = isFast ? 1 : 0;
this.moveCount = moveCount;
@@ -1814,6 +1845,8 @@ class BattleStatGuesser {
if (template.id === 'unown') return 'Fast Special Sweeper';
if (stats.spe > 110 && abilityid !== 'prankster') return 'Fast Bulky Support';
if (moveCount['PhysicalStall'] && moveCount['Restoration']) {
return 'Specially Defensive';
}
@@ -1881,6 +1914,7 @@ class BattleStatGuesser {
}
guessEVs(set: PokemonSet, role: string): Partial<StatsTable> & {plusStat?: StatName | '', minusStat?: StatName | ''} {
if (!set) return {};
if (role === '?') return {};
let template = this.dex.getTemplate(set.species || set.name);
let stats = template.baseStats;
@@ -1906,13 +1940,13 @@ class BattleStatGuesser {
'Bulky Special Sweeper': ['spa', 'hp'],
'Fast Bulky Support': ['spe', 'hp'],
'Physically Defensive': ['def', 'hp'],
'Specially Defensive': ['spd', 'hp']
'Specially Defensive': ['spd', 'hp'],
};
plusStat = statChart[role][0];
if (role === 'Fast Bulky Support') moveCount['Ultrafast'] = 0;
if (plusStat === 'spe') {
if (statChart[role][1] === 'atk' || statChart[role][1] == 'spa') {
if (statChart[role][1] === 'atk' || statChart[role][1] === 'spa') {
plusStat = statChart[role][1];
} else if (moveCount['Physical'] >= 3) {
plusStat = 'atk';
@@ -2031,7 +2065,7 @@ class BattleStatGuesser {
secondaryStat = 'atk';
} else if (!evs['spa'] && moveCount['SpecialAttack'] >= 1) {
secondaryStat = 'spa';
} else if (stats.hp == 1 && !evs['def']) {
} else if (stats.hp === 1 && !evs['def']) {
secondaryStat = 'def';
} else if (stats.def === stats.spd && !evs['spd']) {
secondaryStat = 'spd';