diff --git a/data/scripts.js b/data/scripts.js index 78db6cf27b..7527cbf59f 100644 --- a/data/scripts.js +++ b/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) {