Improve performance and debuggability of random team makers

- 30% more op/s for randomTeam, randomDoublesTeam and randomCCTeam
- 300% more op/s for randomHackmonsCCTeam
- Implement helper method `sampleNoReplace`: takes an array parameter, removes a random element and returns it.
- Random team makers now follow the game RNG. Therefore, it's now possible to reconstruct the teambuilding process for bad sets, given that the starting seed is known.
This commit is contained in:
Ivo Julca 2015-02-14 23:13:12 -05:00
parent 845e681e85
commit 1deedc5952

View File

@ -592,6 +592,16 @@ exports.BattleScripts = {
if (!isValid) selectedAbilities.push(selectedAbility);
return isValid;
},
sampleNoReplace: function (list) {
var length = list.length;
var index = this.random(length);
var element = list[index];
for (var nextIndex = index + 1; nextIndex < length; index += 1, nextIndex += 1) {
list[index] = list[nextIndex];
}
list.pop();
return element;
},
hasMegaEvo: function (template) {
if (template.otherFormes) {
var forme = this.getTemplate(template.otherFormes[0]);
@ -617,31 +627,36 @@ exports.BattleScripts = {
}
},
randomCCTeam: function (side) {
var teamdexno = [];
var team = [];
//pick six random pokmeon--no repeats, even among formes
//also need to either normalize for formes or select formes at random
//unreleased are okay. No CAP for now, but maybe at some later date
var natures = Object.keys(this.data.Natures);
var items = Object.keys(this.data.Items);
var hasDexNumber = {};
var formes = [[], [], [], [], [], []];
// pick six random pokemon--no repeats, even among formes
// also need to either normalize for formes or select formes at random
// unreleased are okay. No CAP for now, but maybe at some later date
var num;
for (var i = 0; i < 6; i++) {
while (true) {
var x = Math.floor(Math.random() * 721) + 1;
if (teamdexno.indexOf(x) === -1) {
teamdexno.push(x);
break;
}
do {
num = this.random(721) + 1;
} while (num in hasDexNumber);
hasDexNumber[num] = i;
}
for (var id in this.data.Pokedex) {
if (!(this.data.Pokedex[id].num in hasDexNumber)) continue;
var template = this.getTemplate(id);
if (template.learnset && template.species !== 'Pichu-Spiky-eared') {
formes[hasDexNumber[template.num]].push(template.species);
}
}
for (var i = 0; i < 6; i++) {
//choose forme
var formes = [];
for (var j in this.data.Pokedex) {
if (this.data.Pokedex[j].num === teamdexno[i] && this.getTemplate(this.data.Pokedex[j].species).learnset && this.data.Pokedex[j].species !== 'Pichu-Spiky-eared') {
formes.push(this.data.Pokedex[j].species);
}
}
var poke = formes.sample();
var poke = formes[i][this.random(formes[i].length)];
var template = this.getTemplate(poke);
//level balance--calculate directly from stats rather than using some silly lookup table
@ -682,59 +697,47 @@ exports.BattleScripts = {
if (template.abilities['H']) {
abilities.push(template.abilities['H']);
}
var ability = abilities.sample();
var ability = abilities[this.random(abilities.length)];
//random nature
var nature = ["Adamant", "Bashful", "Bold", "Brave", "Calm", "Careful", "Docile", "Gentle", "Hardy", "Hasty", "Impish", "Jolly", "Lax", "Lonely", "Mild", "Modest", "Naive", "Naughty", "Quiet", "Quirky", "Rash", "Relaxed", "Sassy", "Serious", "Timid"].sample();
var nature = natures[this.random(natures.length)];
//random item--I guess if it's in items.js, it's okay
var item = Object.keys(this.data.Items).sample();
//random item
var item = '';
//since we're selecting forme at random, we gotta make sure forme/item combo is correct
if (template.requiredItem) {
item = template.requiredItem;
} else {
item = items[this.random(items.length)];
}
if (this.getItem(item).megaStone) {
// we'll exclude mega stones for now
item = Object.keys(this.data.Items).sample();
item = items[this.random(items.length)];
}
while ((poke === 'Arceus' && item.indexOf("plate") > -1) || (poke === 'Giratina' && item === 'griseousorb')) {
item = Object.keys(this.data.Items).sample();
//since we're selecting forme at random, we gotta make sure forme/item combo is correct
while (poke === 'Arceus' && item.substr(-5) !== 'plate' || poke === 'Giratina' && item === 'griseousorb') {
item = items[this.random(items.length)];
}
//random IVs
var ivs = {
hp: Math.floor(Math.random() * 32),
atk: Math.floor(Math.random() * 32),
def: Math.floor(Math.random() * 32),
spa: Math.floor(Math.random() * 32),
spd: Math.floor(Math.random() * 32),
spe: Math.floor(Math.random() * 32)
};
var ivs = {hp: this.random(32), atk: this.random(32), def: this.random(32), spa: this.random(32), spd: this.random(32), spe: this.random(32)};
//random EVs
var evs = {
hp: 0,
atk: 0,
def: 0,
spa: 0,
spd: 0,
spe: 0
};
var evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
var s = ["hp", "atk", "def", "spa", "spd", "spe"];
var evpool = 510;
do {
var x = s.sample();
var y = Math.floor(Math.random() * Math.min(256 - evs[x], evpool + 1));
var x = s[this.random(s.length)];
var y = this.random(Math.min(256 - evs[x], evpool + 1));
evs[x] += y;
evpool -= y;
} while (evpool > 0);
//random happiness--useless, since return/frustration is currently a "cheat"
var happiness = Math.floor(Math.random() * 256);
var happiness = this.random(256);
//random shininess?
var shiny = (Math.random() * 1024 <= 1);
var shiny = !this.random(1024);
//four random unique moves from movepool. don't worry about "attacking" or "viable"
var moves;
@ -747,7 +750,7 @@ exports.BattleScripts = {
if (pool.length <= 4) {
moves = pool;
} else {
moves = pool.sample(4);
moves = [this.sampleNoReplace(pool), this.sampleNoReplace(pool), this.sampleNoReplace(pool), this.sampleNoReplace(pool)];
}
team.push({
@ -769,76 +772,80 @@ exports.BattleScripts = {
},
randomHackmonsCCTeam: function (side) {
var team = [];
var teamdexno = [];
var items = Object.keys(this.data.Items).randomize();
var abilities = Object.keys(this.data.Abilities).exclude('mountaineer', 'rebound', 'persistent').randomize();
var moves = Object.keys(this.data.Movedex).exclude(/hiddenpower./, 'struggle', 'paleowave', 'shadowstrike', 'magikarpsrevenge').randomize();
// Pick six random unique Pokmeon
var itemPool = Object.keys(this.data.Items);
var abilityPool = Object.keys(this.data.Abilities);
var movePool = Object.keys(this.data.Movedex);
var naturePool = Object.keys(this.data.Natures);
var hasDexNumber = {};
var formes = [[], [], [], [], [], []];
// pick six random pokemon--no repeats, even among formes
// also need to either normalize for formes or select formes at random
// unreleased are okay. No CAP for now, but maybe at some later date
var num;
for (var i = 0; i < 6; i++) {
while (true) {
var x = Math.floor(Math.random() * 721) + 1;
if (teamdexno.indexOf(x) === -1) {
teamdexno.push(x);
break;
}
do {
num = this.random(721) + 1;
} while (num in hasDexNumber);
hasDexNumber[num] = i;
}
for (var id in this.data.Pokedex) {
if (!(this.data.Pokedex[id].num in hasDexNumber)) continue;
var template = this.getTemplate(id);
if (template.learnset && template.species !== 'Pichu-Spiky-eared') {
formes[hasDexNumber[template.num]].push(template.species);
}
}
for (var i = 0; i < 6; i++) {
// Choose forme
var formes = [];
for (var j in this.data.Pokedex) {
if (this.data.Pokedex[j].num === teamdexno[i] && this.data.Pokedex[j].species !== 'Pichu-Spiky-eared') {
formes.push(this.data.Pokedex[j].species);
}
}
var pokemon = formes.sample();
var pokemon = formes[i][this.random(formes[i].length)];
var template = this.getTemplate(pokemon);
// Random unique item
var item = items[0];
items.shift();
var item = '';
do {
item = this.sampleNoReplace(itemPool);
} while (this.data.Items[item].isNonstandard);
// Genesect forms are a sprite difference based on its Drives
if (template.species.substr(0, 9) === 'Genesect-' && item !== toId(template.requiredItem)) pokemon = 'Genesect';
// Random unique ability
var ability = abilities[0];
abilities.shift();
var ability = '';
do {
ability = this.sampleNoReplace(abilityPool);
} while (this.data.Abilities[ability].isNonstandard);
// Random unique moves
var m = moves.splice(0, 4);
var m = [];
while (true) {
var moveid = this.sampleNoReplace(movePool);
if (!this.data.Movedex[moveid].isNonstandard && (moveid === 'hiddenpower' || moveid.substr(0, 11) !== 'hiddenpower')) {
if (m.push(moveid) >= 4) break;
}
}
// Random EVs
var evs = {
hp: 0,
atk: 0,
def: 0,
spa: 0,
spd: 0,
spe: 0
};
var evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
var s = ['hp', 'atk', 'def', 'spa', 'spd', 'spe'];
var evpool = 510;
do {
var x = s.sample();
var y = Math.floor(Math.random() * Math.min(256 - evs[x], evpool + 1));
var x = s[this.random(s.length)];
var y = this.random(Math.min(256 - evs[x], evpool + 1));
evs[x] += y;
evpool -= y;
} while (evpool > 0);
// Random IVs
var ivs = {
hp: Math.floor(Math.random() * 32),
atk: Math.floor(Math.random() * 32),
def: Math.floor(Math.random() * 32),
spa: Math.floor(Math.random() * 32),
spd: Math.floor(Math.random() * 32),
spe: Math.floor(Math.random() * 32)
};
var ivs = {hp: this.random(32), atk: this.random(32), def: this.random(32), spa: this.random(32), spd: this.random(32), spe: this.random(32)};
// Random nature
var nature = ['Adamant', 'Bashful', 'Bold', 'Brave', 'Calm', 'Careful', 'Docile', 'Gentle', 'Hardy', 'Hasty', 'Impish', 'Jolly', 'Lax', 'Lonely', 'Mild', 'Modest', 'Naive', 'Naughty', 'Quiet', 'Quirky', 'Rash', 'Relaxed', 'Sassy', 'Serious', 'Timid'].sample();
var nature = naturePool[this.random(naturePool.length)];
// Level balance
var mbstmin = 1307;
@ -862,10 +869,10 @@ exports.BattleScripts = {
}
// Random happiness
var happiness = Math.floor(Math.random() * 256);
var happiness = this.random(256);
// Random shininess
var shiny = (Math.random() * 1024 <= 1);
var shiny = !this.random(1024);
team.push({
name: pokemon,
@ -900,13 +907,13 @@ exports.BattleScripts = {
// Decide if the Pokemon can mega evolve early, so viable moves for the mega can be generated
if (!noMega && this.hasMegaEvo(template)) {
// If there's more than one mega evolution, randomly pick one
template = this.getTemplate(template.otherFormes[(template.otherFormes[1]) ? Math.round(Math.random()) : 0]);
template = this.getTemplate(template.otherFormes[this.random(template.otherFormes.length)]);
}
if (template.otherFormes && this.getTemplate(template.otherFormes[0]).forme === 'Primal' && Math.random() >= 0.5) {
if (template.otherFormes && this.getTemplate(template.otherFormes[0]).isPrimal && this.random(2)) {
template = this.getTemplate(template.otherFormes[0]);
}
var moveKeys = (template.randomBattleMoves || Object.keys(template.learnset)).randomize();
var movePool = (template.randomBattleMoves ? template.randomBattleMoves.slice() : Object.keys(template.learnset));
var moves = [];
var ability = '';
var item = '';
@ -968,13 +975,11 @@ exports.BattleScripts = {
var damagingMoves, damagingMoveIndex, hasMove, counter, setupType;
var j = 0;
hasMove = {};
do {
// Choose next 4 moves from learnset/viable moves and add them to moves list:
while (moves.length < 4 && j < moveKeys.length) {
var moveid = toId(moveKeys[j]);
j++;
while (moves.length < 4 && movePool.length) {
var moveid = this.sampleNoReplace(movePool);
if (moveid.substr(0, 11) === 'hiddenpower') {
if (hasMove['hiddenpower']) continue;
hasMove['hiddenpower'] = true;
@ -1342,7 +1347,7 @@ exports.BattleScripts = {
}
// Remove rejected moves from the move list.
if (rejected && j < moveKeys.length) {
if (rejected && movePool.length) {
moves.splice(k, 1);
break;
}
@ -1355,11 +1360,11 @@ exports.BattleScripts = {
}
}
}
if (j < moveKeys.length && moves.length === 4 && !hasMove['judgment']) {
if (movePool.length && moves.length === 4 && !hasMove['judgment']) {
// Move post-processing:
if (damagingMoves.length === 0) {
// A set shouldn't have no attacking moves
moves.splice(Math.floor(Math.random() * moves.length), 1);
moves.splice(this.random(moves.length), 1);
} else if (damagingMoves.length === 1) {
// Night Shade, Seismic Toss, etc. don't count:
if (!damagingMoves[0].damage) {
@ -1368,7 +1373,7 @@ exports.BattleScripts = {
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'] && Math.random() * 2 > 1) replace = true;
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;
@ -1398,7 +1403,7 @@ exports.BattleScripts = {
if (rejectCombo) {
if (typeCombo === 'Electric/Ice' || typeCombo === 'Fighting/Ghost') rejectCombo = false;
}
if (rejectCombo) moves.splice(Math.floor(Math.random() * moves.length), 1);
if (rejectCombo) moves.splice(this.random(moves.length), 1);
} else {
// If you have three or more attacks, and none of them are STAB, reject one of them at random.
var isStab = false;
@ -1408,10 +1413,10 @@ exports.BattleScripts = {
break;
}
}
if (!isStab) moves.splice(Math.floor(Math.random() * moves.length), 1);
if (!isStab) moves.splice(this.random(moves.length), 1);
}
}
} while (moves.length < 4 && j < moveKeys.length);
} while (moves.length < 4 && movePool.length);
// any moveset modification goes here
//moves[0] = 'safeguard';
@ -1461,9 +1466,9 @@ exports.BattleScripts = {
var ability = ability0.name;
if (abilities[1]) {
if (ability0.rating <= ability1.rating) {
if (Math.random() * 2 < 1) ability = ability1.name;
if (this.random(2)) ability = ability1.name;
} else if (ability0.rating - 0.6 <= ability1.rating) {
if (Math.random() * 3 < 1) ability = ability1.name;
if (!this.random(3)) ability = ability1.name;
}
var rejectAbility = false;
@ -1529,7 +1534,7 @@ exports.BattleScripts = {
} else if (template.id === 'combee') {
// Combee always gets Hustle but its only physical move is Endeavor, which loses accuracy
ability = 'Honey Gather';
} else if (template.id === 'lopunny' && hasMove['switcheroo'] && Math.random() * 3 > 1) {
} else if (template.id === 'lopunny' && hasMove['switcheroo'] && this.random(3)) {
ability = 'Klutz';
} else if (template.id === 'mawilemega') {
// Mega Mawile only needs Intimidate for a starting ability
@ -1576,10 +1581,10 @@ exports.BattleScripts = {
// To perma-taunt a Pokemon by giving it Assault Vest
item = 'Assault Vest';
} else if (hasMove['trick'] || hasMove['switcheroo']) {
var randomNum = Math.random() * 2;
if (counter.Physical >= 3 && (template.baseStats.spe >= 95 || randomNum > 1)) {
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 > 1)) {
} else if (counter.Special >= 3 && (template.baseStats.spe >= 95 || randomNum)) {
item = 'Choice Specs';
} else {
item = 'Choice Scarf';
@ -1653,9 +1658,9 @@ exports.BattleScripts = {
// 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 = template.baseStats.spe > 82 && template.baseStats.spe < 109 && !counter['priority'] && Math.random() * 3 > 1 ? 'Choice Scarf' : 'Choice Band';
item = template.baseStats.spe > 82 && template.baseStats.spe < 109 && !counter['priority'] && this.random(3) ? 'Choice Scarf' : 'Choice Band';
} else if (counter.Special >= 4) {
item = template.baseStats.spe > 82 && template.baseStats.spe < 109 && !counter['priority'] && Math.random() * 3 > 1 ? 'Choice Scarf' : 'Choice Specs';
item = template.baseStats.spe > 82 && template.baseStats.spe < 109 && !counter['priority'] && this.random(3) ? 'Choice Scarf' : 'Choice Specs';
} else if (this.getEffectiveness('Ground', template) >= 2 && !hasType['Poison'] && ability !== 'Levitate' && !hasMove['magnetrise']) {
item = 'Air Balloon';
} else if ((hasMove['eruption'] || hasMove['waterspout']) && !counter['Status']) {
@ -1777,24 +1782,27 @@ exports.BattleScripts = {
ivs: ivs,
item: item,
level: level,
shiny: (Math.random() * 1024 <= 1)
shiny: !this.random(1024)
};
},
randomTeam: function (side) {
var keys = [];
var pokemonLeft = 0;
var pokemon = [];
for (var i in this.data.FormatsData) {
var template = this.getTemplate(i);
if (this.data.FormatsData[i].randomBattleMoves && !this.data.FormatsData[i].isNonstandard && !(template.tier in {'LC':1, 'LC Uber':1, 'NFE':1}) && (template.forme.substr(0, 4) !== 'Mega') && template.forme !== 'Primal') {
keys.push(i);
var excludedTiers = {'LC':1, 'LC Uber':1, 'NFE':1};
var allowedNFE = {'Chansey':1, 'Doublade':1, 'Pikachu':1, 'Porygon2':1, 'Scyther':1};
var pokemonPool = [];
for (var id in this.data.FormatsData) {
var template = this.getTemplate(id);
if (!excludedTiers[template.tier] && !template.isMega && !template.isPrimal && !template.isNonstandard && template.randomBattleMoves) {
pokemonPool.push(id);
}
}
keys = keys.randomize();
// PotD stuff
var potd = {};
if ('Rule:potd' in this.getBanlistTable(this.getFormat())) {
var potd;
if (Config.potd && 'Rule:potd' in this.getBanlistTable(this.getFormat())) {
potd = this.getTemplate(Config.potd);
}
@ -1805,49 +1813,71 @@ exports.BattleScripts = {
var puCount = 0;
var megaCount = 0;
for (var i = 0; i < keys.length && pokemonLeft < 6; i++) {
var template = this.getTemplate(keys[i]);
if (!template || !template.name || !template.types) continue;
var tier = template.tier;
// PUs/Ubers are limited to 2 but have a 20% chance of being added anyway.
if (tier === 'PU' && puCount > 1 && Math.random() * 5 > 1) continue;
if (tier === 'Uber' && uberCount > 1 && Math.random() * 5 > 1) continue;
while (pokemonPool.length && pokemonLeft < 6) {
var template = this.getTemplate(this.sampleNoReplace(pokemonPool));
if (!template.exists) continue;
// CAPs have 20% the normal rate
if (tier === 'CAP' && Math.random() * 5 > 1) continue;
// Unreleased Pokemon have 20% the normal rate
if (tier === 'Unreleased' && Math.random() * 5 > 1) continue;
// Arceus formes have 1/18 the normal rate each (so Arceus as a whole has a normal rate)
if (keys[i].substr(0, 6) === 'arceus' && Math.random() * 18 > 1) continue;
// Basculin formes have 1/2 the normal rate each (so Basculin as a whole has a normal rate)
if (keys[i].substr(0, 8) === 'basculin' && Math.random() * 2 > 1) continue;
// Genesect formes have 1/5 the normal rate each (so Genesect as a whole has a normal rate)
if (keys[i].substr(0, 8) === 'genesect' && Math.random() * 5 > 1) continue;
// Gourgeist formes have 1/4 the normal rate each (so Gourgeist as a whole has a normal rate)
if (keys[i].substr(0, 9) === 'gourgeist' && Math.random() * 4 > 1) continue;
// Cosplay Pikachu formes have 20% the normal rate (1/30 the normal rate each)
if (keys[i].substr(0, 7) === 'pikachu' && template.species !== 'Pikachu' && Math.random() * 30 > 1) continue;
// Regular Pikachu has 80% the normal rate (so Pikachu as a whole has a normal rate)
if (template.species === 'Pikachu' && Math.random() * 5 <= 1) continue;
// Not available on XY
// Limit to one of each species (Species Clause)
if (baseFormes[template.baseSpecies]) continue;
// Not available on ORAS
if (template.species === 'Pichu-Spiky-eared') continue;
// Only certain NFE Pokemon are allowed
if (template.evos.length && !(template.species in {'Chansey':1, 'Doublade':1, 'Pikachu':1, 'Porygon2':1, 'Scyther':1})) continue;
if (template.evos.length && !allowedNFE[template.species]) continue;
var tier = template.tier;
switch (tier) {
case 'PU':
// PUs are limited to 2 but have a 20% chance of being added anyway.
if (puCount > 1 && this.random(5) >= 1) continue;
break;
case 'Uber':
// Ubers are limited to 2 but have a 20% chance of being added anyway.
if (uberCount > 1 && this.random(5) >= 1) continue;
break;
case 'CAP':
// CAPs have 20% the normal rate
if (this.random(5) >= 1) continue;
break;
case 'Unreleased':
// Unreleased Pokémon 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(18) >= 1) continue;
break;
case 'Basculin':
if (this.random(2) >= 1) continue;
break;
case 'Genesect':
if (this.random(5) >= 1) continue;
break;
case 'Gourgeist':
if (this.random(4) >= 1) continue;
break;
case 'Pikachu':
// Cosplay Pikachu formes have 20% the normal rate (1/30 the normal rate each)
if (template.species !== 'Pikachu' && this.random(30) >= 1) continue;
}
// 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 && Math.random() * 5 > 1) {
if (typeCount[types[t]] > 1 && this.random(5) >= 1) {
skip = true;
break;
}
}
if (skip) continue;
if (potd && potd.name && potd.types) {
if (potd && potd.exists) {
// The Pokemon of the Day belongs in slot 2
if (i === 1) {
if (pokemon.length === 1) {
template = potd;
if (template.species === 'Magikarp') {
template.randomBattleMoves = ['magikarpsrevenge', 'splash', 'bounce'];
@ -1859,7 +1889,7 @@ exports.BattleScripts = {
}
}
var set = this.randomSet(template, i, megaCount);
var set = this.randomSet(template, pokemon.length, megaCount);
// Illusion shouldn't be on the last pokemon of the team
if (set.ability === 'Illusion' && pokemonLeft > 4) continue;
@ -1872,20 +1902,18 @@ exports.BattleScripts = {
}
if (typeCombo in typeComboCount) continue;
// Limit the number of Megas to one, just like in-game
var forme = template.otherFormes ? this.getTemplate(template.otherFormes[0]) : 0;
// Limit the number of Megas to one
var forme = template.otherFormes && this.getTemplate(template.otherFormes[0]);
var isMegaSet = this.getItem(set.item).megaStone || (forme && forme.isMega && forme.requiredMove && set.moves.indexOf(toId(forme.requiredMove)) >= 0);
if (isMegaSet && megaCount > 0) continue;
// Limit to one of each species (Species Clause)
if (baseFormes[template.baseSpecies]) continue;
baseFormes[template.baseSpecies] = 1;
// 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++;
// Now that our Pokemon has passed all checks, we can increment the type counter
// Increment type counters
for (var t = 0; t < types.length; t++) {
if (types[t] in typeCount) {
typeCount[types[t]]++;
@ -1895,84 +1923,114 @@ exports.BattleScripts = {
}
typeComboCount[typeCombo] = 1;
// Increment Uber/NU and mega counter
// Increment Uber/NU counters
if (tier === 'Uber') {
uberCount++;
} else if (tier === 'PU') {
puCount++;
}
// Increment mega and base species counters
if (isMegaSet) megaCount++;
baseFormes[template.baseSpecies] = 1;
}
return pokemon;
},
randomDoublesTeam: function (side) {
var keys = [];
var pokemonLeft = 0;
var pokemon = [];
for (var i in this.data.FormatsData) {
var template = this.getTemplate(i);
if (this.data.FormatsData[i].randomBattleMoves && !this.data.FormatsData[i].isNonstandard && !(template.tier in {'LC':1, 'LC Uber':1, 'NFE':1}) && (template.forme.substr(0, 4) !== 'Mega') && template.forme !== 'Primal') {
keys.push(i);
var excludedTiers = {'LC':1, 'LC Uber':1, 'NFE':1};
var allowedNFE = {'Chansey':1, 'Doublade':1, 'Pikachu':1, 'Porygon2':1, 'Scyther':1};
var pokemonPool = [];
for (var id in this.data.FormatsData) {
var template = this.getTemplate(id);
if (!excludedTiers[template.tier] && !template.isMega && !template.isPrimal && !template.isNonstandard && template.randomBattleMoves) {
pokemonPool.push(id);
}
}
keys = keys.randomize();
// PotD stuff
var potd = {};
if ('Rule:potd' in this.getBanlistTable(this.getFormat())) {
var potd;
if (Config.potd && 'Rule:potd' in this.getBanlistTable(this.getFormat())) {
potd = this.getTemplate(Config.potd);
}
var typeCount = {};
var typeComboCount = {};
var baseFormes = {};
var uberCount = 0;
var puCount = 0;
var megaCount = 0;
for (var i = 0; i < keys.length && pokemonLeft < 6; i++) {
var template = this.getTemplate(keys[i]);
if (!template || !template.name || !template.types) continue;
var tier = template.tier;
// Unreleased Pokemon have 20% the normal rate
if (tier === 'Unreleased' && Math.random() * 5 > 1) continue;
// Arceus formes have 1/18 the normal rate each (so Arceus as a whole has a normal rate)
if (keys[i].substr(0, 6) === 'arceus' && Math.random() * 18 > 1) continue;
// Basculin formes have 1/2 the normal rate each (so Basculin as a whole has a normal rate)
if (keys[i].substr(0, 8) === 'basculin' && Math.random() * 2 > 1) continue;
// Genesect formes have 1/5 the normal rate each (so Genesect as a whole has a normal rate)
if (keys[i].substr(0, 8) === 'genesect' && Math.random() * 5 > 1) continue;
// Gourgeist formes have 1/4 the normal rate each (so Gourgeist as a whole has a normal rate)
if (keys[i].substr(0, 9) === 'gourgeist' && Math.random() * 4 > 1) continue;
// Cosplay Pikachu formes have 20% the normal rate (1/30 the normal rate each)
if (keys[i].substr(0, 7) === 'pikachu' && template.species !== 'Pikachu' && Math.random() * 30 > 1) continue;
// Regular Pikachu has 80% the normal rate (so Pikachu as a whole has a normal rate)
if (template.species === 'Pikachu' && Math.random() * 5 <= 1) continue;
// Not available on XY
while (pokemonPool.length && pokemonLeft < 6) {
var template = this.getTemplate(this.sampleNoReplace(pokemonPool));
if (!template.exists) continue;
// Limit to one of each species (Species Clause)
if (baseFormes[template.baseSpecies]) continue;
// Not available on ORAS
if (template.species === 'Pichu-Spiky-eared') continue;
// Only certain NFE Pokemon are allowed
if (template.evos.length && !(template.species in {'Chansey':1, 'Doublade':1, 'Pikachu':1, 'Porygon2':1, 'Scyther':1})) continue;
if (template.evos.length && !allowedNFE[template.species]) continue;
var tier = template.tier;
switch (tier) {
case 'CAP':
// CAPs have 20% the normal rate
if (this.random(5) >= 1) continue;
break;
case 'Unreleased':
// Unreleased Pokémon 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(18) >= 1) continue;
break;
case 'Basculin':
if (this.random(2) >= 1) continue;
break;
case 'Genesect':
if (this.random(5) >= 1) continue;
break;
case 'Gourgeist':
if (this.random(4) >= 1) continue;
break;
case 'Pikachu':
// Cosplay Pikachu formes have 20% the normal rate (1/30 the normal rate each)
if (template.species !== 'Pikachu' && this.random(30) >= 1) continue;
}
// 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 && Math.random() * 5 > 1) {
if (typeCount[types[t]] > 1 && this.random(5) >= 1) {
skip = true;
break;
}
}
if (skip) continue;
// More potd stuff
if (potd && potd.name && potd.types) {
if (potd && potd.exists) {
// The Pokemon of the Day belongs in slot 3
if (i === 2) {
if (pokemon.length === 2) {
template = potd;
} else if (template.species === potd.species) {
continue; // No, thanks, I've already got one
}
}
var set = this.randomDoublesSet(template, megaCount);
var set = this.randomDoublesSet(template, pokemon.length, megaCount);
// Illusion shouldn't be on the last pokemon of the team
if (set.ability === 'Illusion' && pokemonLeft > 4) continue;
// Limit 1 of any type combination
var typeCombo = types.join();
@ -1982,20 +2040,18 @@ exports.BattleScripts = {
}
if (typeCombo in typeComboCount) continue;
// Limit the number of Megas to one, just like in-game
var forme = template.otherFormes ? this.getTemplate(template.otherFormes[0]) : 0;
// Limit the number of Megas to one
var forme = template.otherFormes && this.getTemplate(template.otherFormes[0]);
var isMegaSet = this.getItem(set.item).megaStone || (forme && forme.isMega && forme.requiredMove && set.moves.indexOf(toId(forme.requiredMove)) >= 0);
if (isMegaSet && megaCount > 0) continue;
// Limit to one of each species (Species Clause)
if (baseFormes[template.baseSpecies]) continue;
baseFormes[template.baseSpecies] = 1;
// 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++;
// Now that our Pokemon has passed all checks, we can increment the type counter
// Increment type counters
for (var t = 0; t < types.length; t++) {
if (types[t] in typeCount) {
typeCount[types[t]]++;
@ -2005,8 +2061,16 @@ exports.BattleScripts = {
}
typeComboCount[typeCombo] = 1;
// Increment mega counter
// Increment Uber/NU counters
if (tier === 'Uber') {
uberCount++;
} else if (tier === 'PU') {
puCount++;
}
// Increment mega and base species counters
if (isMegaSet) megaCount++;
baseFormes[template.baseSpecies] = 1;
}
return pokemon;
},
@ -2025,13 +2089,15 @@ exports.BattleScripts = {
// Decide if the Pokemon can mega evolve early, so viable moves for the mega can be generated
if (!noMega && this.hasMegaEvo(template)) {
// If there's more than one mega evolution, randomly pick one
template = this.getTemplate(template.otherFormes[(template.otherFormes[1]) ? Math.round(Math.random()) : 0]);
template = this.getTemplate(template.otherFormes[this.random(template.otherFormes.length)]);
}
if (template.otherFormes && this.getTemplate(template.otherFormes[0]).forme === 'Primal' && Math.random() >= 0.5) {
if (template.otherFormes && this.getTemplate(template.otherFormes[0]).isPrimal && this.random(2)) {
template = this.getTemplate(template.otherFormes[0]);
}
var moveKeys = (template.randomDoubleBattleMoves || template.randomBattleMoves || Object.keys(template.learnset)).randomize();
var movePool = (template.randomDoubleBattleMoves || template.randomBattleMoves);
movePool = movePool ? movePool.slice() : Object.keys(template.learnset);
var moves = [];
var ability = '';
var item = '';
@ -2090,12 +2156,10 @@ exports.BattleScripts = {
var damagingMoves, damagingMoveIndex, hasMove, counter, setupType;
hasMove = {};
var j = 0;
do {
// Choose next 4 moves from learnset/viable moves and add them to moves list:
while (moves.length < 4 && j < moveKeys.length) {
var moveid = toId(moveKeys[j]);
j++;
while (moves.length < 4 && movePool.length) {
var moveid = toId(this.sampleNoReplace(movePool));
if (moveid.substr(0, 11) === 'hiddenpower') {
if (hasMove['hiddenpower']) continue;
hasMove['hiddenpower'] = true;
@ -2429,7 +2493,7 @@ exports.BattleScripts = {
}
// Remove rejected moves from the move list.
if (rejected && j < moveKeys.length) {
if (rejected && movePool.length) {
moves.splice(k, 1);
break;
}
@ -2442,11 +2506,11 @@ exports.BattleScripts = {
}
}
}
if (j < moveKeys.length && moves.length === 4 && !hasMove['judgment']) {
if (movePool.length && moves.length === 4 && !hasMove['judgment']) {
// Move post-processing:
if (damagingMoves.length === 0) {
// A set shouldn't have no attacking moves
moves.splice(Math.floor(Math.random() * moves.length), 1);
moves.splice(this.random(moves.length), 1);
} else if (damagingMoves.length === 1) {
// Night Shade, Seismic Toss, etc. don't count:
if (!damagingMoves[0].damage) {
@ -2455,7 +2519,7 @@ exports.BattleScripts = {
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'] && Math.random() * 2 > 1) replace = true;
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;
@ -2485,7 +2549,7 @@ exports.BattleScripts = {
if (rejectCombo) {
if (typeCombo === 'Electric/Ice' || typeCombo === 'Fighting/Ghost') rejectCombo = false;
}
if (rejectCombo) moves.splice(Math.floor(Math.random() * moves.length), 1);
if (rejectCombo) moves.splice(this.random(moves.length), 1);
} else {
// If you have three or more attacks, and none of them are STAB, reject one of them at random.
var isStab = false;
@ -2495,10 +2559,10 @@ exports.BattleScripts = {
break;
}
}
if (!isStab) moves.splice(Math.floor(Math.random() * moves.length), 1);
if (!isStab) moves.splice(this.random(moves.length), 1);
}
}
} while (moves.length < 4 && j < moveKeys.length);
} while (moves.length < 4 && movePool.length);
// If Hidden Power has been removed, reset the IVs
if (!hasMove['hiddenpower']) {
@ -2520,9 +2584,9 @@ exports.BattleScripts = {
var ability = ability0.name;
if (abilities[1]) {
if (ability0.rating <= ability1.rating) {
if (Math.random() * 2 < 1) ability = ability1.name;
if (this.random(2)) ability = ability1.name;
} else if (ability0.rating - 0.6 <= ability1.rating) {
if (Math.random() * 3 < 1) ability = ability1.name;
if (!this.random(3)) ability = ability1.name;
}
var rejectAbility = false;
@ -2628,10 +2692,10 @@ exports.BattleScripts = {
} else if (hasMove['trick'] && hasMove['gyroball']) {
item = 'Iron Ball';
} else if (hasMove['trick'] || hasMove['switcheroo']) {
var randomNum = Math.random() * 2;
if (counter.Physical >= 3 && (template.baseStats.spe >= 95 || randomNum > 1)) {
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 > 1)) {
} else if (counter.Special >= 3 && (template.baseStats.spe >= 95 || randomNum)) {
item = 'Choice Specs';
} else {
item = 'Choice Scarf';
@ -2769,7 +2833,7 @@ exports.BattleScripts = {
ivs: ivs,
item: item,
level: level,
shiny: (Math.random() * (template.id === 'missingno' ? 4 : 1024) <= 1)
shiny: !this.random(template.id === 'missingno' ? 4 : 1024)
};
},
randomSeasonalMulanTeam: function (side) {