mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-05-06 13:47:24 -05:00
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:
parent
845e681e85
commit
1deedc5952
504
data/scripts.js
504
data/scripts.js
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user