pokemon-showdown/mods/gen5/scripts.js
Ivo Julca 6d189221c0 Make BW random team builder fully deterministic
This was supposed to be done in baadd0a, but somehow most Math.random() calls remained unnoticed.
2015-07-16 12:36:05 -05:00

922 lines
32 KiB
JavaScript

exports.BattleScripts = {
gen: 5,
randomSet: function (template, slot) {
if (slot === undefined) slot = 1;
template = this.getTemplate(template);
var name = template.name;
if (!template.exists || (!template.randomBattleMoves && !template.learnset)) {
// GET IT? UNOWN? BECAUSE WE CAN'T TELL WHAT THE POKEMON IS
template = this.getTemplate('unown');
var stack = 'Template incompatible with random battles: ' + name;
var fakeErr = {stack: stack};
require('./../../crashlogger.js')(fakeErr, 'The randbat set generator');
}
var movePool = (template.randomBattleMoves ? template.randomBattleMoves.slice() : Object.keys(template.learnset));
var moves = [];
var ability = '';
var item = '';
var evs = {
hp: 85,
atk: 85,
def: 85,
spa: 85,
spd: 85,
spe: 85
};
var ivs = {
hp: 31,
atk: 31,
def: 31,
spa: 31,
spd: 31,
spe: 31
};
var hasStab = {};
hasStab[template.types[0]] = true;
var hasType = {};
hasType[template.types[0]] = true;
if (template.types[1]) {
hasStab[template.types[1]] = true;
hasType[template.types[1]] = true;
}
var damagingMoves = [];
var damagingMoveIndex = {};
var hasMove = {};
var counter = {};
var setupType = '';
do {
// Choose next 4 moves from learnset/viable moves and add them to moves list:
while (moves.length < 4 && movePool.length) {
var moveid = this.sampleNoReplace(movePool);
if (moveid.substr(0, 11) === 'hiddenpower') {
if (!hasMove['hiddenpower']) {
hasMove['hiddenpower'] = true;
} else {
continue;
}
}
moves.push(moveid);
}
damagingMoves = [];
damagingMoveIndex = {};
hasMove = {};
counter = {
Physical: 0, Special: 0, Status: 0, damage: 0,
technician: 0, skilllink: 0, contrary: 0, sheerforce: 0, ironfist: 0, adaptability: 0, hustle: 0,
blaze: 0, overgrow: 0, swarm: 0, torrent: 0,
recoil: 0, inaccurate: 0,
physicalsetup: 0, specialsetup: 0, mixedsetup: 0
};
// Iterate through all moves we've chosen so far and keep track of what they do:
for (var k = 0; k < moves.length; k++) {
var move = this.getMove(moves[k]);
var moveid = move.id;
// Keep track of all moves we have:
hasMove[moveid] = true;
if (move.damage || move.damageCallback) {
// Moves that do a set amount of damage:
counter['damage']++;
damagingMoves.push(move);
damagingMoveIndex[moveid] = k;
} else {
// Are Physical/Special/Status moves:
counter[move.category]++;
}
// Moves that have a low base power:
if (move.basePower && move.basePower <= 60) {
counter['technician']++;
}
// Moves that hit multiple times:
if (move.multihit && move.multihit[1] === 5) {
counter['skilllink']++;
}
// Punching moves:
if (move.flags['punch']) {
counter['ironfist']++;
}
// Recoil:
if (move.recoil) {
counter['recoil']++;
}
// Moves which have a base power:
if (move.basePower || move.basePowerCallback) {
if (hasType[move.type]) {
counter['adaptability']++;
// STAB:
// Power Gem, Bounce, Aeroblast aren't considered STABs.
// If they're in the Pokémon's movepool and are STAB, consider the Pokémon not to have that type as a STAB.
if (moveid === 'aeroblast' || moveid === 'powergem' || moveid === 'bounce') hasStab[move.type] = false;
}
if (move.category === 'Physical') counter['hustle']++;
if (move.type === 'Fire') counter['blaze']++;
if (move.type === 'Grass') counter['overgrow']++;
if (move.type === 'Bug') counter['swarm']++;
if (move.type === 'Water') counter['torrent']++;
// Make sure not to count Knock Off, Rapid Spin, etc.
if (move.basePower > 20 || move.multihit || move.basePowerCallback) {
damagingMoves.push(move);
damagingMoveIndex[moveid] = k;
}
}
// Moves with secondary effects:
if (move.secondary) {
if (move.secondary.chance < 50) {
counter['sheerforce'] -= 5;
} else {
counter['sheerforce']++;
}
}
// Moves with low accuracy:
if (move.accuracy && move.accuracy !== true && move.accuracy < 90) {
counter['inaccurate']++;
}
// Moves which drop stats:
var ContraryMove = {
leafstorm: 1, overheat: 1, closecombat: 1, superpower: 1, vcreate: 1
};
if (ContraryMove[moveid]) {
counter['contrary']++;
}
// Moves that boost Attack:
var PhysicalSetup = {
swordsdance:1, dragondance:1, coil:1, bulkup:1, curse:1, bellydrum:1, shiftgear:1, honeclaws:1, howl:1
};
// Moves which boost Special Attack:
var SpecialSetup = {
nastyplot:1, tailglow:1, quiverdance:1, calmmind:1, chargebeam:1
};
// Moves which boost Attack AND Special Attack:
var MixedSetup = {
growth:1, workup:1, shellsmash:1
};
if (PhysicalSetup[moveid]) {
counter['physicalsetup']++;
}
if (SpecialSetup[moveid]) {
counter['specialsetup']++;
}
if (MixedSetup[moveid]) {
counter['mixedsetup']++;
}
}
// Choose a setup type:
if (counter['mixedsetup']) {
setupType = 'Mixed';
} else if (counter['specialsetup']) {
setupType = 'Special';
} else if (counter['physicalsetup']) {
setupType = 'Physical';
}
// Iterate through the moves again, this time to cull them:
for (var k = 0; k < moves.length; k++) {
var moveid = moves[k];
var move = this.getMove(moveid);
var rejected = false;
var isSetup = false;
switch (moveid) {
// not very useful without their supporting moves
case 'sleeptalk':
if (!hasMove['rest']) rejected = true;
break;
case 'endure':
if (!hasMove['flail'] && !hasMove['endeavor'] && !hasMove['reversal']) rejected = true;
break;
case 'focuspunch':
if (hasMove['sleeptalk'] || !hasMove['substitute']) rejected = true;
break;
case 'storedpower':
if (!hasMove['cosmicpower'] && !setupType) rejected = true;
break;
case 'batonpass':
if (!setupType && !hasMove['substitute'] && !hasMove['cosmicpower']) rejected = true;
break;
// we only need to set up once
case 'swordsdance': case 'dragondance': case 'coil': case 'curse': case 'bulkup': case 'bellydrum':
if (counter.Physical < 2 && !hasMove['batonpass']) rejected = true;
if (setupType !== 'Physical' || counter['physicalsetup'] > 1) rejected = true;
isSetup = true;
break;
case 'nastyplot': case 'tailglow': case 'quiverdance': case 'calmmind':
if (counter.Special < 2 && !hasMove['batonpass']) rejected = true;
if (setupType !== 'Special' || counter['specialsetup'] > 1) rejected = true;
isSetup = true;
break;
case 'shellsmash': case 'growth': case 'workup':
if (counter.Physical + counter.Special < 2 && !hasMove['batonpass']) rejected = true;
if (setupType !== 'Mixed' || counter['mixedsetup'] > 1) rejected = true;
isSetup = true;
break;
// bad after setup
case 'seismictoss': case 'nightshade': case 'superfang':
if (setupType) rejected = true;
break;
case 'knockoff': case 'perishsong': case 'magiccoat': case 'spikes':
if (setupType) rejected = true;
break;
case 'uturn': case 'voltswitch':
if (setupType || hasMove['agility'] || hasMove['rockpolish'] || hasMove['magnetrise']) rejected = true;
break;
case 'relicsong':
if (setupType) rejected = true;
break;
case 'pursuit': case 'protect': case 'haze': case 'stealthrock':
if (setupType || (hasMove['rest'] && hasMove['sleeptalk'])) rejected = true;
break;
case 'trick': case 'switcheroo':
if (setupType || (hasMove['rest'] && hasMove['sleeptalk']) || hasMove['trickroom'] || hasMove['reflect'] || hasMove['lightscreen'] || hasMove['batonpass']) rejected = true;
break;
case 'dragontail': case 'circlethrow':
if (hasMove['agility'] || hasMove['rockpolish']) rejected = true;
if (hasMove['whirlwind'] || hasMove['roar'] || hasMove['encore']) rejected = true;
break;
// bit redundant to have both
// Attacks:
case 'flamethrower': case 'fierydance':
if (hasMove['lavaplume'] || hasMove['overheat'] || hasMove['fireblast'] || hasMove['blueflare']) rejected = true;
break;
case 'overheat':
if (setupType === 'Special' || hasMove['fireblast']) rejected = true;
break;
case 'icebeam':
if (hasMove['blizzard']) rejected = true;
break;
case 'surf':
if (hasMove['scald'] || hasMove['hydropump']) rejected = true;
break;
case 'hydropump':
if (hasMove['razorshell'] || hasMove['scald']) rejected = true;
break;
case 'waterfall':
if (hasMove['aquatail']) rejected = true;
break;
case 'airslash':
if (hasMove['hurricane']) rejected = true;
break;
case 'bravebird': case 'pluck': case 'drillpeck':
if (hasMove['acrobatics']) rejected = true;
break;
case 'solarbeam':
if ((!hasMove['sunnyday'] && template.species !== 'Ninetales') || hasMove['gigadrain'] || hasMove['leafstorm']) rejected = true;
break;
case 'gigadrain':
if ((!setupType && hasMove['leafstorm']) || hasMove['petaldance']) rejected = true;
break;
case 'leafstorm':
if (setupType && hasMove['gigadrain']) rejected = true;
break;
case 'weatherball':
if (!hasMove['sunnyday']) rejected = true;
break;
case 'firepunch':
if (hasMove['flareblitz']) rejected = true;
break;
case 'bugbite':
if (hasMove['uturn']) rejected = true;
break;
case 'crosschop': case 'highjumpkick':
if (hasMove['closecombat']) rejected = true;
break;
case 'drainpunch':
if (hasMove['closecombat'] || hasMove['highjumpkick'] || hasMove['crosschop']) rejected = true;
break;
case 'thunderbolt':
if (hasMove['discharge'] || hasMove['voltswitch'] || hasMove['thunder']) rejected = true;
break;
case 'discharge': case 'thunder':
if (hasMove['voltswitch']) rejected = true;
break;
case 'rockslide': case 'rockblast':
if (hasMove['stoneedge'] || hasMove['headsmash']) rejected = true;
break;
case 'stoneedge':
if (hasMove['headsmash']) rejected = true;
break;
case 'bonemerang': case 'earthpower':
if (hasMove['earthquake']) rejected = true;
break;
case 'dragonclaw':
if (hasMove['outrage'] || hasMove['dragontail']) rejected = true;
break;
case 'ancientpower':
if (hasMove['paleowave']) rejected = true;
break;
case 'dragonpulse':
if (hasMove['dracometeor']) rejected = true;
break;
case 'return':
if (hasMove['bodyslam'] || hasMove['facade'] || hasMove['doubleedge'] || hasMove['tailslap']) rejected = true;
break;
case 'poisonjab':
if (hasMove['gunkshot']) rejected = true;
break;
case 'psychic':
if (hasMove['psyshock']) rejected = true;
break;
case 'fusionbolt':
if (setupType && hasMove['boltstrike']) rejected = true;
break;
case 'boltstrike':
if (!setupType && hasMove['fusionbolt']) rejected = true;
break;
// Status:
case 'rest':
if (hasMove['painsplit'] || hasMove['wish'] || hasMove['recover'] || hasMove['moonlight'] || hasMove['synthesis']) rejected = true;
break;
case 'softboiled': case 'roost':
if (hasMove['wish'] || hasMove['recover']) rejected = true;
break;
case 'perishsong':
if (hasMove['roar'] || hasMove['whirlwind'] || hasMove['haze']) rejected = true;
break;
case 'roar':
// Whirlwind outclasses Roar because Soundproof
if (hasMove['whirlwind'] || hasMove['dragontail'] || hasMove['haze'] || hasMove['circlethrow']) rejected = true;
break;
case 'substitute':
if (hasMove['uturn'] || hasMove['voltswitch'] || hasMove['pursuit']) rejected = true;
break;
case 'fakeout':
if (hasMove['trick'] || hasMove['switcheroo']) rejected = true;
break;
case 'encore':
if (hasMove['rest'] && hasMove['sleeptalk']) rejected = true;
if (hasMove['whirlwind'] || hasMove['dragontail'] || hasMove['roar'] || hasMove['circlethrow']) rejected = true;
break;
case 'suckerpunch':
if (hasMove['rest'] && hasMove['sleeptalk']) rejected = true;
break;
case 'cottonguard':
if (hasMove['reflect']) rejected = true;
break;
case 'lightscreen':
if (hasMove['calmmind']) rejected = true;
break;
case 'rockpolish': case 'agility': case 'autotomize':
if (!setupType && !hasMove['batonpass'] && hasMove['thunderwave']) rejected = true;
if ((hasMove['stealthrock'] || hasMove['spikes'] || hasMove['toxicspikes']) && !hasMove['batonpass']) rejected = true;
break;
case 'thunderwave':
if (setupType && (hasMove['rockpolish'] || hasMove['agility'])) rejected = true;
if (hasMove['discharge'] || hasMove['trickroom']) rejected = true;
if (hasMove['rest'] && hasMove['sleeptalk']) rejected = true;
break;
case 'lavaplume':
if (hasMove['willowisp']) rejected = true;
break;
}
// These moves can be used even if we aren't setting up to use them:
var SetupException = {
overheat:1, dracometeor:1, leafstorm:1,
voltswitch:1, uturn:1,
suckerpunch:1, extremespeed:1
};
if (move.category === 'Special' && setupType === 'Physical' && !SetupException[move.id]) {
rejected = true;
}
if (move.category === 'Physical' && setupType === 'Special' && !SetupException[move.id]) {
rejected = true;
}
// This move doesn't satisfy our setup requirements:
if (setupType === 'Physical' && move.category !== 'Physical' && counter['Physical'] < 2) {
rejected = true;
}
if (setupType === 'Special' && move.category !== 'Special' && counter['Special'] < 2) {
rejected = true;
}
// Remove rejected moves from the move list.
if (rejected && movePool.length) {
moves.splice(k, 1);
break;
}
// handle HP IVs
if (move.id === 'hiddenpower') {
var HPivs = this.getType(move.type).HPivs;
for (var iv in HPivs) {
ivs[iv] = HPivs[iv];
}
}
}
if (movePool.length && moves.length === 4) {
// Move post-processing:
if (damagingMoves.length === 0) {
// Have a 60% chance of rejecting one move at random:
if (this.random(5) <= 2) this.sampleNoReplace(moves);
} else if (damagingMoves.length === 1) {
// Night Shade, Seismic Toss, etc. don't count:
if (!damagingMoves[0].damage) {
var damagingid = damagingMoves[0].id;
var damagingType = damagingMoves[0].type;
var replace = false;
if (damagingid === 'suckerpunch' || damagingid === 'counter' || damagingid === 'mirrorcoat') {
// A player shouldn't be forced to rely upon the opponent attacking them to do damage.
if (!hasMove['encore'] && this.random(2)) replace = true;
} else if (damagingid === 'focuspunch') {
// Focus Punch is a bad idea without a sub:
if (!hasMove['substitute']) replace = true;
} else if (damagingid.substr(0, 11) === 'hiddenpower' && damagingType === 'Ice') {
// Mono-HP-Ice is never acceptable.
replace = true;
} else {
// If you have one attack, and it's not STAB, Ice, Fire, or Ground, reject it.
// Mono-Ice/Ground/Fire is only acceptable if the Pokémon's STABs are one of: Poison, Psychic, Steel, Normal, Grass.
if (!hasStab[damagingType]) {
if (damagingType === 'Ice' || damagingType === 'Fire' || damagingType === 'Ground') {
if (!hasStab['Poison'] && !hasStab['Psychic'] && !hasStab['Steel'] && !hasStab['Normal'] && !hasStab['Grass']) {
replace = true;
}
} else {
replace = true;
}
}
}
if (replace) moves.splice(damagingMoveIndex[damagingid], 1);
}
} else if (damagingMoves.length === 2) {
// If you have two attacks, neither is STAB, and the combo isn't Ice/Electric, Ghost/Fighting, or Dark/Fighting, reject one of them at random.
var type1 = damagingMoves[0].type, type2 = damagingMoves[1].type;
var typeCombo = [type1, type2].sort().join('/');
var rejectCombo = true;
if (!(type1 in hasStab) && !(type2 in hasStab)) {
if (typeCombo === 'Electric/Ice' || typeCombo === 'Fighting/Ghost' || typeCombo === 'Dark/Fighting') rejectCombo = false;
} else {
rejectCombo = false;
}
if (rejectCombo) this.sampleNoReplace(moves);
} else {
// If you have three or more attacks, and none of them are STAB, reject one of them at random.
var isStab = false;
for (var l = 0; l < damagingMoves.length; l++) {
if (hasStab[damagingMoves[l].type]) {
isStab = true;
break;
}
}
if (!isStab) this.sampleNoReplace(moves);
}
}
} while (moves.length < 4 && movePool.length);
// any moveset modification goes here
//moves[0] = 'Safeguard';
var abilities = [template.abilities['0']];
if (template.abilities['1']) {
abilities.push(template.abilities['1']);
}
if (template.abilities['H']) {
abilities.push(template.abilities['H']);
}
abilities.sort(function (a, b) {
return this.getAbility(b).rating - this.getAbility(a).rating;
}.bind(this));
var ability0 = this.getAbility(abilities[0]);
var ability1 = this.getAbility(abilities[1]);
var ability = ability0.name;
if (abilities[1]) {
if (ability0.rating <= ability1.rating) {
if (this.random(2)) {
ability = ability1.name;
}
} else if (ability0.rating - 0.6 <= ability1.rating) {
if (!this.random(3)) {
ability = ability1.name;
}
}
var rejectAbility = false;
if (ability === 'Blaze' && !counter['blaze']) {
rejectAbility = true;
}
if (ability === 'Overgrow' && !counter['overgrow']) {
rejectAbility = true;
}
if (ability === 'Swarm' && !counter['swarm']) {
rejectAbility = true;
}
if (ability === 'Torrent' && !counter['torrent']) {
rejectAbility = true;
}
if (ability === 'Contrary' && !counter['contrary']) {
rejectAbility = true;
}
if (ability === 'Technician' && !counter['technician']) {
rejectAbility = true;
}
if (ability === 'Skill Link' && !counter['skilllink']) {
rejectAbility = true;
}
if (ability === 'Iron Fist' && !counter['ironfist']) {
rejectAbility = true;
}
if (ability === 'Adaptability' && !counter['adaptability']) {
rejectAbility = true;
}
if ((ability === 'Rock Head' || ability === 'Reckless') && !counter['recoil']) {
rejectAbility = true;
}
if ((ability === 'No Guard' || ability === 'Compoundeyes') && !counter['inaccurate']) {
rejectAbility = true;
}
if ((ability === 'Sheer Force' || ability === 'Serene Grace') && !counter['sheerforce']) {
rejectAbility = true;
}
if (ability === 'Hustle' && !counter['hustle']) {
rejectAbility = true;
}
if (ability === 'Simple' && !setupType && !hasMove['flamecharge'] && !hasMove['stockpile']) {
rejectAbility = true;
}
if (ability === 'Prankster' && !counter['Status']) {
rejectAbility = true;
}
if (ability === 'Defiant' && !counter['Physical'] && !hasMove['batonpass']) {
rejectAbility = true;
}
// below 2 checks should be modified, when it becomes possible, to check if the team contains rain or sun
if (ability === 'Swift Swim' && !hasMove['raindance']) {
rejectAbility = true;
}
if (ability === 'Chlorophyll' && !hasMove['sunnyday']) {
rejectAbility = true;
}
if (ability === 'Moody' && template.id !== 'bidoof') {
rejectAbility = true;
}
if (ability === 'Lightning Rod' && template.types.indexOf('Ground') >= 0) {
rejectAbility = true;
}
if (rejectAbility) {
if (ability === ability1.name) { // or not
ability = ability0.name;
} else if (ability1.rating > 0) { // only switch if the alternative doesn't suck
ability = ability1.name;
}
}
if ((abilities[0] === 'Guts' || abilities[1] === 'Guts' || abilities[2] === 'Guts') && ability !== 'Quick Feet' && hasMove['facade']) {
ability = 'Guts';
}
if ((abilities[0] === 'Swift Swim' || abilities[1] === 'Swift Swim' || abilities[2] === 'Swift Swim') && hasMove['raindance']) {
ability = 'Swift Swim';
}
if ((abilities[0] === 'Chlorophyll' || abilities[1] === 'Chlorophyll' || abilities[2] === 'Chlorophyll') && ability !== 'Solar Power' && hasMove['sunnyday']) {
ability = 'Chlorophyll';
}
if (template.id === 'combee') {
// it always gets Hustle but its only physical move is Endeavor, which loses accuracy
ability = 'Honey Gather';
}
}
if (hasMove['gyroball']) {
ivs.spe = 0;
//evs.atk += evs.spe;
evs.spe = 0;
} else if (hasMove['trickroom']) {
ivs.spe = 0;
//evs.hp += evs.spe;
evs.spe = 0;
}
item = 'Leftovers';
if (template.requiredItem) {
item = template.requiredItem;
} else if (template.species === 'Rotom-Fan') {
// this is just to amuse myself
item = 'Air Balloon';
} else if (template.species === 'Delibird') {
// to go along with the Christmas Delibird set
item = 'Leftovers';
// First, the extra high-priority items
} else if (ability === 'Imposter') {
item = 'Choice Scarf';
} else if (hasMove["magikarpsrevenge"]) {
item = 'Choice Band';
} else if (ability === 'Wonder Guard') {
item = 'Focus Sash';
} else if (template.species === 'Unown') {
item = 'Choice Specs';
} else if ((template.species === 'Wynaut' || template.species === 'Wobbuffet') && hasMove['destinybond'] && this.random(2)) {
item = 'Custap Berry';
} else if (hasMove['trick'] && hasMove['gyroball'] && (ability === 'Levitate' || hasType['Flying'])) {
item = 'Macho Brace';
} else if (hasMove['trick'] && hasMove['gyroball']) {
item = 'Iron Ball';
} else if (hasMove['trick'] || hasMove['switcheroo']) {
var randomNum = this.random(2);
if (counter.Physical >= 3 && (template.baseStats.spe >= 95 || randomNum)) {
item = 'Choice Band';
} else if (counter.Special >= 3 && (template.baseStats.spe >= 95 || randomNum)) {
item = 'Choice Specs';
} else {
item = 'Choice Scarf';
}
} else if (hasMove['rest'] && !hasMove['sleeptalk'] && ability !== 'Natural Cure' && ability !== 'Shed Skin') {
item = 'Chesto Berry';
} else if (hasMove['naturalgift']) {
item = 'Liechi Berry';
} else if (ability === 'Harvest') {
item = 'Sitrus Berry';
} else if (template.species === 'Cubone' || template.species === 'Marowak') {
item = 'Thick Club';
} else if (template.species === 'Pikachu') {
item = 'Light Ball';
} else if (template.species === 'Clamperl') {
item = 'DeepSeaTooth';
} else if (hasMove['reflect'] && hasMove['lightscreen']) {
item = 'Light Clay';
} else if (hasMove['acrobatics']) {
item = 'Flying Gem';
} else if (hasMove['shellsmash']) {
item = 'White Herb';
} else if (hasMove['facade'] || ability === 'Poison Heal' || ability === 'Toxic Boost') {
item = 'Toxic Orb';
} else if (hasMove['raindance']) {
item = 'Damp Rock';
} else if (hasMove['sunnyday']) {
item = 'Heat Rock';
} else if (hasMove['sandstorm']) { // lol
item = 'Smooth Rock';
} else if (hasMove['hail']) { // lol
item = 'Icy Rock';
} else if (ability === 'Magic Guard' && hasMove['psychoshift']) {
item = 'Flame Orb';
} else if (ability === 'Sheer Force' || ability === 'Magic Guard') {
item = 'Life Orb';
} else if (ability === 'Unburden' && (counter['Physical'] || counter['Special'])) {
// Give Unburden mons a random Gem of the type of one of their damaging moves
var eligibleTypes = [];
for (var i = 0; i < moves.length; i++) {
var move = this.getMove(moves[i]);
if (!move.basePower && !move.basePowerCallback) continue;
eligibleTypes.push(move.type);
}
item = eligibleTypes[this.random(eligibleTypes.length)] + ' Gem';
} else if (ability === 'Guts') {
if (hasMove['drainpunch']) {
item = 'Flame Orb';
} else {
item = 'Toxic Orb';
}
if ((hasMove['return'] || hasMove['hyperfang']) && !hasMove['facade']) {
// lol no
for (var j = 0; j < moves.length; j++) {
if (moves[j] === 'Return' || moves[j] === 'HyperFang') {
moves[j] = 'Facade';
break;
}
}
}
} else if (ability === 'Marvel Scale' && hasMove['psychoshift']) {
item = 'Flame Orb';
} else if (hasMove['reflect'] || hasMove['lightscreen']) {
// less priority than if you'd had both
item = 'Light Clay';
} else if (counter.Physical >= 4 && !hasMove['fakeout'] && !hasMove['suckerpunch'] && !hasMove['flamecharge'] && !hasMove['rapidspin']) {
item = this.random(3) ? 'Choice Band' : 'Expert Belt';
} else if (counter.Special >= 4) {
item = this.random(3) ? 'Choice Specs' : 'Expert Belt';
} else if (this.getEffectiveness('Ground', template) >= 2 && ability !== 'Levitate' && !hasMove['magnetrise']) {
item = 'Air Balloon';
} else if ((hasMove['eruption'] || hasMove['waterspout']) && !counter['Status']) {
item = 'Choice Scarf';
} else if (hasMove['substitute'] && hasMove['reversal']) {
var eligibleTypes = [];
for (var i = 0; i < moves.length; i++) {
var move = this.getMove(moves[i]);
if (!move.basePower && !move.basePowerCallback) continue;
eligibleTypes.push(move.type);
}
item = eligibleTypes[this.random(eligibleTypes.length)] + ' Gem';
} else if (hasMove['substitute'] || hasMove['detect'] || hasMove['protect'] || ability === 'Moody') {
item = 'Leftovers';
} else if ((hasMove['flail'] || hasMove['reversal']) && !hasMove['endure'] && ability !== 'Sturdy') {
item = 'Focus Sash';
} else if (ability === 'Iron Barbs') {
// only Iron Barbs for now
item = 'Rocky Helmet';
} else if ((template.baseStats.hp + 75) * (template.baseStats.def + template.baseStats.spd + 175) > 60000 || template.species === 'Skarmory' || template.species === 'Forretress') {
// skarmory and forretress get exceptions for their typing
item = 'Leftovers';
} else if (counter.Physical + counter.Special >= 3 && setupType) {
item = 'Life Orb';
} else if (counter.Special >= 3 && setupType) {
item = 'Life Orb';
} else if (counter.Physical + counter.Special >= 4) {
item = 'Expert Belt';
} else if (slot === 0 && ability !== 'Sturdy' && !counter['recoil']) {
item = 'Focus Sash';
} else if (hasMove['outrage']) {
item = 'Lum Berry';
// this is the "REALLY can't think of a good item" cutoff
// why not always Leftovers? Because it's boring. :P
} else if (hasType['Flying'] || ability === 'Levitate') {
item = 'Leftovers';
} else if (this.getEffectiveness('Ground', template) >= 1 && ability !== 'Levitate' && !hasMove['magnetrise']) {
item = 'Air Balloon';
} else if (hasType['Poison']) {
item = 'Black Sludge';
} else if (counter.Status <= 1) {
item = 'Life Orb';
} else {
item = 'Leftovers';
}
if (item === 'Leftovers' && hasType['Poison']) {
item = 'Black Sludge';
}
// 95-86-82-78-74-70
var levelScale = {
LC: 95,
NFE: 90,
'LC Uber': 86,
NU: 86,
BL3: 84,
RU: 82,
BL2: 80,
UU: 78,
BL: 76,
OU: 74,
CAP: 74,
Unreleased: 74,
Uber: 70
};
var customScale = {
// Really bad Pokemon and jokemons
Azurill: 99, Burmy: 99, Cascoon: 99, Caterpie: 99, Cleffa: 99, Combee: 99, Feebas: 99, Igglybuff: 99, Happiny: 99, Hoppip: 99,
Kakuna: 99, Kricketot: 99, Ledyba: 99, Magikarp: 99, Metapod: 99, Pichu: 99, Ralts: 99, Sentret: 99, Shedinja: 99,
Silcoon: 99, Slakoth: 99, Sunkern: 99, Tynamo: 99, Tyrogue: 99, Unown: 99, Weedle: 99, Wurmple: 99, Zigzagoon: 99,
Clefairy: 95, Delibird: 95, "Farfetch'd": 95, Jigglypuff: 95, Kirlia: 95, Ledian: 95, Luvdisc: 95, Marill: 95, Skiploom: 95,
Pachirisu: 90,
// Eviolite
Ferroseed: 95, Misdreavus: 95, Munchlax: 95, Murkrow: 95, Natu: 95,
Gligar: 90, Metang: 90, Monferno: 90, Roselia: 90, Seadra: 90, Togetic: 90, Wartortle: 90, Whirlipede: 90,
Dusclops: 84, Porygon2: 82, Chansey: 78,
// Weather or teammate dependent
Snover: 95, Vulpix: 95, Excadrill: 78, Ninetales: 78, Tentacruel: 78, Toxicroak: 78, Venusaur: 78, "Tornadus-Therian": 74,
// Holistic judgment
Carvanha: 90, Blaziken: 74, "Deoxys-Defense": 74, "Deoxys-Speed": 74, Garchomp: 74, Thundurus: 74
};
var level = levelScale[template.tier] || 90;
if (customScale[template.name]) level = customScale[template.name];
if (template.name === 'Chandelure' && ability === 'Shadow Tag') level = 70;
if (template.name === 'Serperior' && ability === 'Contrary') level = 74;
if (template.name === 'Magikarp' && hasMove['magikarpsrevenge']) level = 85;
if (template.name === 'Spinda' && ability !== 'Contrary') level = 95;
return {
name: name,
moves: moves,
ability: ability,
evs: evs,
ivs: ivs,
item: item,
level: level,
shiny: !this.random(1024)
};
},
randomTeam: function (side) {
var pokemonLeft = 0;
var pokemon = [];
var pokemonPool = [];
for (var id in this.data.FormatsData) {
var template = this.getTemplate(id);
if (template.gen >= this.gen || !template.randomBattleMoves) continue;
pokemonPool.push(id);
}
// PotD stuff
var potd;
if (Config.potd && 'Rule:potd' in this.getBanlistTable(this.getFormat())) {
potd = this.getTemplate(Config.potd);
}
var typeCount = {};
var typeComboCount = {};
var uberCount = 0;
var nuCount = 0;
while (pokemonPool.length && pokemonLeft < 6) {
var template = this.getTemplate(this.sampleNoReplace(pokemonPool));
if (!template.exists) continue;
// Not available on BW
if (template.species === 'Pichu-Spiky-eared') continue;
var tier = template.tier;
// This tries to limit the amount of Ubers and NUs on one team to promote "fun":
// LC Pokemon have a hard limit in place at 2; NFEs/NUs/Ubers are also limited to 2 but have a 20% chance of being added anyway.
// LC/NFE/NU Pokemon all share a counter (so having one of each would make the counter 3), while Ubers have a counter of their own.
switch (tier) {
case 'LC':
if (nuCount > 1) continue;
break;
case 'NFE': case 'NU':
if (nuCount > 1 && this.random(5) >= 1) continue;
break;
case 'Uber':
if (uberCount > 1 && this.random(5) >= 1) continue;
break;
case 'CAP':
// CAPs have 20% the normal rate
if (this.random(5) >= 1) continue;
}
// Adjust rate for species with multiple formes
switch (template.baseSpecies) {
case 'Arceus':
if (this.random(17) >= 1) continue;
break;
case 'Basculin':
if (this.random(2) >= 1) continue;
break;
}
// Limit 2 of any type
var types = template.types;
var skip = false;
for (var t = 0; t < types.length; t++) {
if (typeCount[types[t]] > 1 && this.random(5) >= 1) {
skip = true;
break;
}
}
if (skip) continue;
if (potd && potd.exists) {
// The Pokemon of the Day belongs in slot 2
if (pokemon.length === 1) {
template = potd;
if (template.species === 'Magikarp') {
template.randomBattleMoves = ['magikarpsrevenge', 'splash', 'bounce'];
} else if (template.species === 'Delibird') {
template.randomBattleMoves = ['present', 'bestow'];
}
} else if (template.species === potd.species) {
continue; // No, thanks, I've already got one
}
}
var set = this.randomSet(template, pokemon.length);
// Limit 1 of any type combination
var typeCombo = types.join();
if (set.ability === 'Drought' || set.ability === 'Drizzle') {
// Drought and Drizzle don't count towards the type combo limit
typeCombo = set.ability;
}
if (typeCombo in typeComboCount) continue;
// Okay, the set passes, add it to our team
pokemon.push(set);
// Now that our Pokemon has passed all checks, we can increment our counters
pokemonLeft++;
// Increment type counters
for (var t = 0; t < types.length; t++) {
if (types[t] in typeCount) {
typeCount[types[t]]++;
} else {
typeCount[types[t]] = 1;
}
}
typeComboCount[typeCombo] = 1;
// Increment Uber/NU counters
if (tier === 'Uber') {
uberCount++;
} else if (tier === 'NU' || tier === 'NFE' || tier === 'LC') {
nuCount++;
}
}
return pokemon;
}
};