package com.sneed.pkrandom.romhandlers;
/*----------------------------------------------------------------------------*/
/*-- AbstractRomHandler.java - a base class for all rom handlers which --*/
/*-- implements the majority of the actual --*/
/*-- randomizer logic by building on the base --*/
/*-- getters & setters provided by each concrete --*/
/*-- handler. --*/
/*-- --*/
/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/
/*-- Originally part of "Universal Pokemon Randomizer" by sneed --*/
/*-- Pokemon and any associated names and the like are --*/
/*-- trademark and (C) Nintendo 1996-2020. --*/
/*-- --*/
/*-- The custom code written here is licensed under the terms of the GPL: --*/
/*-- --*/
/*-- This program is free software: you can redistribute it and/or modify --*/
/*-- it under the terms of the GNU General Public License as published by --*/
/*-- the Free Software Foundation, either version 3 of the License, or --*/
/*-- (at your option) any later version. --*/
/*-- --*/
/*-- This program is distributed in the hope that it will be useful, --*/
/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/
/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/
/*-- GNU General Public License for more details. --*/
/*-- --*/
/*-- You should have received a copy of the GNU General Public License --*/
/*-- along with this program. If not, see . --*/
/*----------------------------------------------------------------------------*/
import java.io.PrintStream;
import java.util.*;
import java.util.stream.Collectors;
import com.sneed.pkrandom.*;
import com.sneed.pkrandom.constants.*;
import com.sneed.pkrandom.exceptions.RandomizationException;
import com.sneed.pkrandom.pokemon.*;
public abstract class AbstractRomHandler implements RomHandler {
private boolean restrictionsSet;
protected List mainPokemonList;
protected List mainPokemonListInclFormes;
private List altFormesList;
private List megaEvolutionsList;
private List noLegendaryList, onlyLegendaryList, ultraBeastList;
private List noLegendaryListInclFormes, onlyLegendaryListInclFormes;
private List noLegendaryAltsList, onlyLegendaryAltsList;
private List pickedStarters;
protected final Random random;
private final Random cosmeticRandom;
protected PrintStream logStream;
private List alreadyPicked = new ArrayList<>();
private Map placementHistory = new HashMap<>();
private Map itemPlacementHistory = new HashMap<>();
private int fullyEvolvedRandomSeed;
boolean isORAS = false;
boolean isSM = false;
int perfectAccuracy = 100;
/* Constructor */
public AbstractRomHandler(Random random, PrintStream logStream) {
this.random = random;
this.cosmeticRandom = RandomSource.cosmeticInstance();
this.fullyEvolvedRandomSeed = -1;
this.logStream = logStream;
}
/*
* Public Methods, implemented here for all gens. Unlikely to be overridden.
*/
public void setLog(PrintStream logStream) {
this.logStream = logStream;
}
public void setPokemonPool(Settings settings) {
GenRestrictions restrictions = null;
if (settings != null) {
restrictions = settings.getCurrentRestrictions();
// restrictions should already be null if "Limit Pokemon" is disabled, but this is a safeguard
if (!settings.isLimitPokemon()) {
restrictions = null;
}
}
restrictionsSet = true;
mainPokemonList = this.allPokemonWithoutNull();
mainPokemonListInclFormes = this.allPokemonInclFormesWithoutNull();
altFormesList = this.getAltFormes();
megaEvolutionsList = this.getMegaEvolutions();
if (restrictions != null) {
mainPokemonList = new ArrayList<>();
mainPokemonListInclFormes = new ArrayList<>();
megaEvolutionsList = new ArrayList<>();
List allPokemon = this.getPokemon();
if (restrictions.allow_gen1) {
addPokesFromRange(mainPokemonList, allPokemon, Species.bulbasaur, Species.mew);
}
if (restrictions.allow_gen2 && allPokemon.size() > Gen2Constants.pokemonCount) {
addPokesFromRange(mainPokemonList, allPokemon, Species.chikorita, Species.celebi);
}
if (restrictions.allow_gen3 && allPokemon.size() > Gen3Constants.pokemonCount) {
addPokesFromRange(mainPokemonList, allPokemon, Species.treecko, Species.deoxys);
}
if (restrictions.allow_gen4 && allPokemon.size() > Gen4Constants.pokemonCount) {
addPokesFromRange(mainPokemonList, allPokemon, Species.turtwig, Species.arceus);
}
if (restrictions.allow_gen5 && allPokemon.size() > Gen5Constants.pokemonCount) {
addPokesFromRange(mainPokemonList, allPokemon, Species.victini, Species.genesect);
}
if (restrictions.allow_gen6 && allPokemon.size() > Gen6Constants.pokemonCount) {
addPokesFromRange(mainPokemonList, allPokemon, Species.chespin, Species.volcanion);
}
int maxGen7SpeciesID = isSM ? Species.marshadow : Species.zeraora;
if (restrictions.allow_gen7 && allPokemon.size() > maxGen7SpeciesID) {
addPokesFromRange(mainPokemonList, allPokemon, Species.rowlet, maxGen7SpeciesID);
}
// If the user specified it, add all the evolutionary relatives for everything in the mainPokemonList
if (restrictions.allow_evolutionary_relatives) {
addEvolutionaryRelatives(mainPokemonList);
}
// Now that mainPokemonList has all the selected Pokemon, update mainPokemonListInclFormes too
addAllPokesInclFormes(mainPokemonList, mainPokemonListInclFormes);
// Populate megaEvolutionsList with all of the mega evolutions that exist in the pool
List allMegaEvolutions = this.getMegaEvolutions();
for (MegaEvolution megaEvo : allMegaEvolutions) {
if (mainPokemonListInclFormes.contains(megaEvo.to)) {
megaEvolutionsList.add(megaEvo);
}
}
}
noLegendaryList = new ArrayList<>();
noLegendaryListInclFormes = new ArrayList<>();
onlyLegendaryList = new ArrayList<>();
onlyLegendaryListInclFormes = new ArrayList<>();
noLegendaryAltsList = new ArrayList<>();
onlyLegendaryAltsList = new ArrayList<>();
ultraBeastList = new ArrayList<>();
for (Pokemon p : mainPokemonList) {
if (p.isLegendary()) {
onlyLegendaryList.add(p);
} else if (p.isUltraBeast()) {
ultraBeastList.add(p);
} else {
noLegendaryList.add(p);
}
}
for (Pokemon p : mainPokemonListInclFormes) {
if (p.isLegendary()) {
onlyLegendaryListInclFormes.add(p);
} else if (!ultraBeastList.contains(p)) {
noLegendaryListInclFormes.add(p);
}
}
for (Pokemon f : altFormesList) {
if (f.isLegendary()) {
onlyLegendaryAltsList.add(f);
} else {
noLegendaryAltsList.add(f);
}
}
}
private void addPokesFromRange(List pokemonPool, List allPokemon, int range_min, int range_max) {
for (int i = range_min; i <= range_max; i++) {
if (!pokemonPool.contains(allPokemon.get(i))) {
pokemonPool.add(allPokemon.get(i));
}
}
}
private void addEvolutionaryRelatives(List pokemonPool) {
Set newPokemon = new TreeSet<>();
for (Pokemon pk : pokemonPool) {
List evolutionaryRelatives = getEvolutionaryRelatives(pk);
for (Pokemon relative : evolutionaryRelatives) {
if (!pokemonPool.contains(relative) && !newPokemon.contains(relative)) {
newPokemon.add(relative);
}
}
}
pokemonPool.addAll(newPokemon);
}
private void addAllPokesInclFormes(List pokemonPool, List pokemonPoolInclFormes) {
List altFormes = this.getAltFormes();
for (int i = 0; i < pokemonPool.size(); i++) {
Pokemon currentPokemon = pokemonPool.get(i);
if (!pokemonPoolInclFormes.contains(currentPokemon)) {
pokemonPoolInclFormes.add(currentPokemon);
}
for (int j = 0; j < altFormes.size(); j++) {
Pokemon potentialAltForme = altFormes.get(j);
if (potentialAltForme.baseForme != null && potentialAltForme.baseForme.number == currentPokemon.number) {
pokemonPoolInclFormes.add(potentialAltForme);
}
}
}
}
@Override
public void shufflePokemonStats(Settings settings) {
boolean evolutionSanity = settings.isBaseStatsFollowEvolutions();
boolean megaEvolutionSanity = settings.isBaseStatsFollowMegaEvolutions();
if (evolutionSanity) {
copyUpEvolutionsHelper(pk -> pk.shuffleStats(AbstractRomHandler.this.random),
(evFrom, evTo, toMonIsFinalEvo) -> evTo.copyShuffledStatsUpEvolution(evFrom)
);
} else {
List allPokes = this.getPokemonInclFormes();
for (Pokemon pk : allPokes) {
if (pk != null) {
pk.shuffleStats(this.random);
}
}
}
List allPokes = this.getPokemonInclFormes();
for (Pokemon pk : allPokes) {
if (pk != null && pk.actuallyCosmetic) {
pk.copyBaseFormeBaseStats(pk.baseForme);
}
}
if (megaEvolutionSanity) {
List allMegaEvos = getMegaEvolutions();
for (MegaEvolution megaEvo: allMegaEvos) {
if (megaEvo.from.megaEvolutionsFrom.size() > 1) continue;
megaEvo.to.copyShuffledStatsUpEvolution(megaEvo.from);
}
}
}
@Override
public void randomizePokemonStats(Settings settings) {
boolean evolutionSanity = settings.isBaseStatsFollowEvolutions();
boolean megaEvolutionSanity = settings.isBaseStatsFollowMegaEvolutions();
boolean assignEvoStatsRandomly = settings.isAssignEvoStatsRandomly();
if (evolutionSanity) {
if (assignEvoStatsRandomly) {
copyUpEvolutionsHelper(pk -> pk.randomizeStatsWithinBST(AbstractRomHandler.this.random),
(evFrom, evTo, toMonIsFinalEvo) -> evTo.assignNewStatsForEvolution(evFrom, this.random),
(evFrom, evTo, toMonIsFinalEvo) -> evTo.assignNewStatsForEvolution(evFrom, this.random),
true
);
} else {
copyUpEvolutionsHelper(pk -> pk.randomizeStatsWithinBST(AbstractRomHandler.this.random),
(evFrom, evTo, toMonIsFinalEvo) -> evTo.copyRandomizedStatsUpEvolution(evFrom),
(evFrom, evTo, toMonIsFinalEvo) -> evTo.assignNewStatsForEvolution(evFrom, this.random),
true
);
}
} else {
List allPokes = this.getPokemonInclFormes();
for (Pokemon pk : allPokes) {
if (pk != null) {
pk.randomizeStatsWithinBST(this.random);
}
}
}
List allPokes = this.getPokemonInclFormes();
for (Pokemon pk : allPokes) {
if (pk != null && pk.actuallyCosmetic) {
pk.copyBaseFormeBaseStats(pk.baseForme);
}
}
if (megaEvolutionSanity) {
List allMegaEvos = getMegaEvolutions();
for (MegaEvolution megaEvo: allMegaEvos) {
if (megaEvo.from.megaEvolutionsFrom.size() > 1 || assignEvoStatsRandomly) {
megaEvo.to.assignNewStatsForEvolution(megaEvo.from, this.random);
} else {
megaEvo.to.copyRandomizedStatsUpEvolution(megaEvo.from);
}
}
}
}
@Override
public void updatePokemonStats(Settings settings) {
int generation = settings.getUpdateBaseStatsToGeneration();
List pokes = getPokemonInclFormes();
for (int gen = 6; gen <= generation; gen++) {
Map statChanges = getUpdatedPokemonStats(gen);
for (int i = 1; i < pokes.size(); i++) {
StatChange changedStats = statChanges.get(i);
if (changedStats != null) {
int statNum = 0;
if ((changedStats.stat & Stat.HP.val) != 0) {
pokes.get(i).hp = changedStats.values[statNum];
statNum++;
}
if ((changedStats.stat & Stat.ATK.val) != 0) {
pokes.get(i).attack = changedStats.values[statNum];
statNum++;
}
if ((changedStats.stat & Stat.DEF.val) != 0) {
pokes.get(i).defense = changedStats.values[statNum];
statNum++;
}
if ((changedStats.stat & Stat.SPATK.val) != 0) {
if (generationOfPokemon() != 1) {
pokes.get(i).spatk = changedStats.values[statNum];
}
statNum++;
}
if ((changedStats.stat & Stat.SPDEF.val) != 0) {
if (generationOfPokemon() != 1) {
pokes.get(i).spdef = changedStats.values[statNum];
}
statNum++;
}
if ((changedStats.stat & Stat.SPEED.val) != 0) {
pokes.get(i).speed = changedStats.values[statNum];
statNum++;
}
if ((changedStats.stat & Stat.SPECIAL.val) != 0) {
pokes.get(i).special = changedStats.values[statNum];
}
}
}
}
}
public Pokemon randomPokemon() {
checkPokemonRestrictions();
return mainPokemonList.get(this.random.nextInt(mainPokemonList.size()));
}
@Override
public Pokemon randomPokemonInclFormes() {
checkPokemonRestrictions();
return mainPokemonListInclFormes.get(this.random.nextInt(mainPokemonListInclFormes.size()));
}
@Override
public Pokemon randomNonLegendaryPokemon() {
checkPokemonRestrictions();
return noLegendaryList.get(this.random.nextInt(noLegendaryList.size()));
}
private Pokemon randomNonLegendaryPokemonInclFormes() {
checkPokemonRestrictions();
return noLegendaryListInclFormes.get(this.random.nextInt(noLegendaryListInclFormes.size()));
}
@Override
public Pokemon randomLegendaryPokemon() {
checkPokemonRestrictions();
return onlyLegendaryList.get(this.random.nextInt(onlyLegendaryList.size()));
}
private List twoEvoPokes;
@Override
public Pokemon random2EvosPokemon(boolean allowAltFormes) {
if (twoEvoPokes == null) {
// Prepare the list
twoEvoPokes = new ArrayList<>();
List allPokes =
allowAltFormes ?
this.getPokemonInclFormes()
.stream()
.filter(pk -> pk == null || !pk.actuallyCosmetic)
.collect(Collectors.toList()) :
this.getPokemon();
for (Pokemon pk : allPokes) {
if (pk != null && pk.evolutionsTo.size() == 0 && pk.evolutionsFrom.size() > 0) {
// Potential candidate
for (Evolution ev : pk.evolutionsFrom) {
// If any of the targets here evolve, the original
// Pokemon has 2+ stages.
if (ev.to.evolutionsFrom.size() > 0) {
twoEvoPokes.add(pk);
break;
}
}
}
}
}
return twoEvoPokes.get(this.random.nextInt(twoEvoPokes.size()));
}
@Override
public Type randomType() {
Type t = Type.randomType(this.random);
while (!typeInGame(t)) {
t = Type.randomType(this.random);
}
return t;
}
@Override
public void randomizePokemonTypes(Settings settings) {
boolean evolutionSanity = settings.getTypesMod() == Settings.TypesMod.RANDOM_FOLLOW_EVOLUTIONS;
boolean megaEvolutionSanity = settings.isTypesFollowMegaEvolutions();
boolean dualTypeOnly = settings.isDualTypeOnly();
List allPokes = this.getPokemonInclFormes();
if (evolutionSanity) {
// Type randomization with evolution sanity
copyUpEvolutionsHelper(pk -> {
// Step 1: Basic or Excluded From Copying Pokemon
// A Basic/EFC pokemon has a 35% chance of a second type if
// it has an evolution that copies type/stats, a 50% chance
// otherwise
pk.primaryType = randomType();
pk.secondaryType = null;
if (pk.evolutionsFrom.size() == 1 && pk.evolutionsFrom.get(0).carryStats) {
if (AbstractRomHandler.this.random.nextDouble() < 0.35 || dualTypeOnly) {
pk.secondaryType = randomType();
while (pk.secondaryType == pk.primaryType) {
pk.secondaryType = randomType();
}
}
} else {
if (AbstractRomHandler.this.random.nextDouble() < 0.5 || dualTypeOnly) {
pk.secondaryType = randomType();
while (pk.secondaryType == pk.primaryType) {
pk.secondaryType = randomType();
}
}
}
}, (evFrom, evTo, toMonIsFinalEvo) -> {
evTo.primaryType = evFrom.primaryType;
evTo.secondaryType = evFrom.secondaryType;
if (evTo.secondaryType == null) {
double chance = toMonIsFinalEvo ? 0.25 : 0.15;
if (AbstractRomHandler.this.random.nextDouble() < chance || dualTypeOnly) {
evTo.secondaryType = randomType();
while (evTo.secondaryType == evTo.primaryType) {
evTo.secondaryType = randomType();
}
}
}
});
} else {
// Entirely random types
for (Pokemon pkmn : allPokes) {
if (pkmn != null) {
pkmn.primaryType = randomType();
pkmn.secondaryType = null;
if (this.random.nextDouble() < 0.5||settings.isDualTypeOnly()) {
pkmn.secondaryType = randomType();
while (pkmn.secondaryType == pkmn.primaryType) {
pkmn.secondaryType = randomType();
}
}
}
}
}
for (Pokemon pk : allPokes) {
if (pk != null && pk.actuallyCosmetic) {
pk.primaryType = pk.baseForme.primaryType;
pk.secondaryType = pk.baseForme.secondaryType;
}
}
if (megaEvolutionSanity) {
List allMegaEvos = getMegaEvolutions();
for (MegaEvolution megaEvo: allMegaEvos) {
if (megaEvo.from.megaEvolutionsFrom.size() > 1) continue;
megaEvo.to.primaryType = megaEvo.from.primaryType;
megaEvo.to.secondaryType = megaEvo.from.secondaryType;
if (megaEvo.to.secondaryType == null) {
if (this.random.nextDouble() < 0.25) {
megaEvo.to.secondaryType = randomType();
while (megaEvo.to.secondaryType == megaEvo.to.primaryType) {
megaEvo.to.secondaryType = randomType();
}
}
}
}
}
}
@Override
public void randomizeAbilities(Settings settings) {
boolean evolutionSanity = settings.isAbilitiesFollowEvolutions();
boolean allowWonderGuard = settings.isAllowWonderGuard();
boolean banTrappingAbilities = settings.isBanTrappingAbilities();
boolean banNegativeAbilities = settings.isBanNegativeAbilities();
boolean banBadAbilities = settings.isBanBadAbilities();
boolean megaEvolutionSanity = settings.isAbilitiesFollowMegaEvolutions();
boolean weighDuplicatesTogether = settings.isWeighDuplicateAbilitiesTogether();
boolean ensureTwoAbilities = settings.isEnsureTwoAbilities();
boolean doubleBattleMode = settings.isDoubleBattleMode();
// Abilities don't exist in some games...
if (this.abilitiesPerPokemon() == 0) {
return;
}
final boolean hasDWAbilities = (this.abilitiesPerPokemon() == 3);
final List bannedAbilities = this.getUselessAbilities();
if (!allowWonderGuard) {
bannedAbilities.add(Abilities.wonderGuard);
}
if (banTrappingAbilities) {
bannedAbilities.addAll(GlobalConstants.battleTrappingAbilities);
}
if (banNegativeAbilities) {
bannedAbilities.addAll(GlobalConstants.negativeAbilities);
}
if (banBadAbilities) {
bannedAbilities.addAll(GlobalConstants.badAbilities);
if (!doubleBattleMode) {
bannedAbilities.addAll(GlobalConstants.doubleBattleAbilities);
}
}
if (weighDuplicatesTogether) {
bannedAbilities.addAll(GlobalConstants.duplicateAbilities);
if (generationOfPokemon() == 3) {
bannedAbilities.add(Gen3Constants.airLockIndex); // Special case for Air Lock in Gen 3
}
}
final int maxAbility = this.highestAbilityIndex();
if (evolutionSanity) {
// copy abilities straight up evolution lines
// still keep WG as an exception, though
copyUpEvolutionsHelper(pk -> {
if (pk.ability1 != Abilities.wonderGuard
&& pk.ability2 != Abilities.wonderGuard
&& pk.ability3 != Abilities.wonderGuard) {
// Pick first ability
pk.ability1 = pickRandomAbility(maxAbility, bannedAbilities, weighDuplicatesTogether);
// Second ability?
if (ensureTwoAbilities || AbstractRomHandler.this.random.nextDouble() < 0.5) {
// Yes, second ability
pk.ability2 = pickRandomAbility(maxAbility, bannedAbilities, weighDuplicatesTogether,
pk.ability1);
} else {
// Nope
pk.ability2 = 0;
}
// Third ability?
if (hasDWAbilities) {
pk.ability3 = pickRandomAbility(maxAbility, bannedAbilities, weighDuplicatesTogether,
pk.ability1, pk.ability2);
}
}
}, (evFrom, evTo, toMonIsFinalEvo) -> {
if (evTo.ability1 != Abilities.wonderGuard
&& evTo.ability2 != Abilities.wonderGuard
&& evTo.ability3 != Abilities.wonderGuard) {
evTo.ability1 = evFrom.ability1;
evTo.ability2 = evFrom.ability2;
evTo.ability3 = evFrom.ability3;
}
});
} else {
List allPokes = this.getPokemonInclFormes();
for (Pokemon pk : allPokes) {
if (pk == null) {
continue;
}
// Don't remove WG if already in place.
if (pk.ability1 != Abilities.wonderGuard
&& pk.ability2 != Abilities.wonderGuard
&& pk.ability3 != Abilities.wonderGuard) {
// Pick first ability
pk.ability1 = this.pickRandomAbility(maxAbility, bannedAbilities, weighDuplicatesTogether);
// Second ability?
if (ensureTwoAbilities || this.random.nextDouble() < 0.5) {
// Yes, second ability
pk.ability2 = this.pickRandomAbility(maxAbility, bannedAbilities, weighDuplicatesTogether,
pk.ability1);
} else {
// Nope
pk.ability2 = 0;
}
// Third ability?
if (hasDWAbilities) {
pk.ability3 = pickRandomAbility(maxAbility, bannedAbilities, weighDuplicatesTogether,
pk.ability1, pk.ability2);
}
}
}
}
List allPokes = this.getPokemonInclFormes();
for (Pokemon pk : allPokes) {
if (pk != null && pk.actuallyCosmetic) {
pk.copyBaseFormeAbilities(pk.baseForme);
}
}
if (megaEvolutionSanity) {
List allMegaEvos = this.getMegaEvolutions();
for (MegaEvolution megaEvo: allMegaEvos) {
if (megaEvo.from.megaEvolutionsFrom.size() > 1) continue;
megaEvo.to.ability1 = megaEvo.from.ability1;
megaEvo.to.ability2 = megaEvo.from.ability2;
megaEvo.to.ability3 = megaEvo.from.ability3;
}
}
}
private int pickRandomAbilityVariation(int selectedAbility, int... alreadySetAbilities) {
int newAbility = selectedAbility;
while (true) {
Map> abilityVariations = getAbilityVariations();
for (int baseAbility: abilityVariations.keySet()) {
if (selectedAbility == baseAbility) {
List variationsForThisAbility = abilityVariations.get(selectedAbility);
newAbility = variationsForThisAbility.get(this.random.nextInt(variationsForThisAbility.size()));
break;
}
}
boolean repeat = false;
for (int alreadySetAbility : alreadySetAbilities) {
if (alreadySetAbility == newAbility) {
repeat = true;
break;
}
}
if (!repeat) {
break;
}
}
return newAbility;
}
private int pickRandomAbility(int maxAbility, List bannedAbilities, boolean useVariations,
int... alreadySetAbilities) {
int newAbility;
while (true) {
newAbility = this.random.nextInt(maxAbility) + 1;
if (bannedAbilities.contains(newAbility)) {
continue;
}
boolean repeat = false;
for (int alreadySetAbility : alreadySetAbilities) {
if (alreadySetAbility == newAbility) {
repeat = true;
break;
}
}
if (!repeat) {
if (useVariations) {
newAbility = pickRandomAbilityVariation(newAbility, alreadySetAbilities);
}
break;
}
}
return newAbility;
}
@Override
public void randomEncounters(Settings settings) {
boolean useTimeOfDay = settings.isUseTimeBasedEncounters();
boolean catchEmAll = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.CATCH_EM_ALL;
boolean typeThemed = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.TYPE_THEME_AREAS;
boolean usePowerLevels = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.SIMILAR_STRENGTH;
boolean noLegendaries = settings.isBlockWildLegendaries();
boolean balanceShakingGrass = settings.isBalanceShakingGrass();
int levelModifier = settings.isWildLevelsModified() ? settings.getWildLevelModifier() : 0;
boolean allowAltFormes = settings.isAllowWildAltFormes();
boolean banIrregularAltFormes = settings.isBanIrregularAltFormes();
boolean abilitiesAreRandomized = settings.getAbilitiesMod() == Settings.AbilitiesMod.RANDOMIZE;
List currentEncounters = this.getEncounters(useTimeOfDay);
if (isORAS) {
List collapsedEncounters = collapseAreasORAS(currentEncounters);
area1to1EncountersImpl(collapsedEncounters, settings);
enhanceRandomEncountersORAS(collapsedEncounters, settings);
setEncounters(useTimeOfDay, currentEncounters);
return;
}
checkPokemonRestrictions();
// New: randomize the order encounter sets are randomized in.
// Leads to less predictable results for various modifiers.
// Need to keep the original ordering around for saving though.
List scrambledEncounters = new ArrayList<>(currentEncounters);
Collections.shuffle(scrambledEncounters, this.random);
List banned = this.bannedForWildEncounters();
banned.addAll(this.getBannedFormesForPlayerPokemon());
if (!abilitiesAreRandomized) {
List abilityDependentFormes = getAbilityDependentFormes();
banned.addAll(abilityDependentFormes);
}
if (banIrregularAltFormes) {
banned.addAll(getIrregularFormes());
}
// Assume EITHER catch em all OR type themed OR match strength for now
if (catchEmAll) {
List allPokes;
if (allowAltFormes) {
allPokes = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes) : new ArrayList<>(
mainPokemonListInclFormes);
allPokes.removeIf(o -> ((Pokemon) o).actuallyCosmetic);
} else {
allPokes = noLegendaries ? new ArrayList<>(noLegendaryList) : new ArrayList<>(
mainPokemonList);
}
allPokes.removeAll(banned);
for (EncounterSet area : scrambledEncounters) {
List pickablePokemon = allPokes;
if (area.bannedPokemon.size() > 0) {
pickablePokemon = new ArrayList<>(allPokes);
pickablePokemon.removeAll(area.bannedPokemon);
}
for (Encounter enc : area.encounters) {
// In Catch 'Em All mode, don't randomize encounters for Pokemon that are banned for
// wild encounters. Otherwise, it may be impossible to obtain this Pokemon unless it
// randomly appears as a static or unless it becomes a random evolution.
if (banned.contains(enc.pokemon)) {
continue;
}
// Pick a random pokemon
if (pickablePokemon.size() == 0) {
// Only banned pokes are left, ignore them and pick
// something else for now.
List tempPickable;
if (allowAltFormes) {
tempPickable = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes) : new ArrayList<>(
mainPokemonListInclFormes);
tempPickable.removeIf(o -> ((Pokemon) o).actuallyCosmetic);
} else {
tempPickable = noLegendaries ? new ArrayList<>(noLegendaryList) : new ArrayList<>(
mainPokemonList);
}
tempPickable.removeAll(banned);
tempPickable.removeAll(area.bannedPokemon);
if (tempPickable.size() == 0) {
throw new RandomizationException("ERROR: Couldn't replace a wild Pokemon!");
}
int picked = this.random.nextInt(tempPickable.size());
enc.pokemon = tempPickable.get(picked);
setFormeForEncounter(enc, enc.pokemon);
} else {
// Picked this Pokemon, remove it
int picked = this.random.nextInt(pickablePokemon.size());
enc.pokemon = pickablePokemon.get(picked);
pickablePokemon.remove(picked);
if (allPokes != pickablePokemon) {
allPokes.remove(enc.pokemon);
}
setFormeForEncounter(enc, enc.pokemon);
if (allPokes.size() == 0) {
// Start again
if (allowAltFormes) {
allPokes.addAll(noLegendaries ? noLegendaryListInclFormes : mainPokemonListInclFormes);
allPokes.removeIf(o -> ((Pokemon) o).actuallyCosmetic);
} else {
allPokes.addAll(noLegendaries ? noLegendaryList : mainPokemonList);
}
allPokes.removeAll(banned);
if (pickablePokemon != allPokes) {
pickablePokemon.addAll(allPokes);
pickablePokemon.removeAll(area.bannedPokemon);
}
}
}
}
}
} else if (typeThemed) {
Map> cachedPokeLists = new TreeMap<>();
for (EncounterSet area : scrambledEncounters) {
List possiblePokemon = null;
int iterLoops = 0;
while (possiblePokemon == null && iterLoops < 10000) {
Type areaTheme = randomType();
if (!cachedPokeLists.containsKey(areaTheme)) {
List pType = allowAltFormes ? pokemonOfTypeInclFormes(areaTheme, noLegendaries) :
pokemonOfType(areaTheme, noLegendaries);
pType.removeAll(banned);
cachedPokeLists.put(areaTheme, pType);
}
possiblePokemon = cachedPokeLists.get(areaTheme);
if (area.bannedPokemon.size() > 0) {
possiblePokemon = new ArrayList<>(possiblePokemon);
possiblePokemon.removeAll(area.bannedPokemon);
}
if (possiblePokemon.size() == 0) {
// Can't use this type for this area
possiblePokemon = null;
}
iterLoops++;
}
if (possiblePokemon == null) {
throw new RandomizationException("Could not randomize an area in a reasonable amount of attempts.");
}
for (Encounter enc : area.encounters) {
// Pick a random themed pokemon
enc.pokemon = possiblePokemon.get(this.random.nextInt(possiblePokemon.size()));
while (enc.pokemon.actuallyCosmetic) {
enc.pokemon = possiblePokemon.get(this.random.nextInt(possiblePokemon.size()));
}
setFormeForEncounter(enc, enc.pokemon);
}
}
} else if (usePowerLevels) {
List allowedPokes;
if (allowAltFormes) {
allowedPokes = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes)
: new ArrayList<>(mainPokemonListInclFormes);
} else {
allowedPokes = noLegendaries ? new ArrayList<>(noLegendaryList)
: new ArrayList<>(mainPokemonList);
}
allowedPokes.removeAll(banned);
for (EncounterSet area : scrambledEncounters) {
List localAllowed = allowedPokes;
if (area.bannedPokemon.size() > 0) {
localAllowed = new ArrayList<>(allowedPokes);
localAllowed.removeAll(area.bannedPokemon);
}
for (Encounter enc : area.encounters) {
if (balanceShakingGrass) {
if (area.displayName.contains("Shaking")) {
enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, (enc.level + enc.maxLevel) / 2);
while (enc.pokemon.actuallyCosmetic) {
enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, (enc.level + enc.maxLevel) / 2);
}
setFormeForEncounter(enc, enc.pokemon);
} else {
enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, 100);
while (enc.pokemon.actuallyCosmetic) {
enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, 100);
}
setFormeForEncounter(enc, enc.pokemon);
}
} else {
enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, 100);
while (enc.pokemon.actuallyCosmetic) {
enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, 100);
}
setFormeForEncounter(enc, enc.pokemon);
}
}
}
} else {
// Entirely random
for (EncounterSet area : scrambledEncounters) {
for (Encounter enc : area.encounters) {
enc.pokemon = pickEntirelyRandomPokemon(allowAltFormes, noLegendaries, area, banned);
setFormeForEncounter(enc, enc.pokemon);
}
}
}
if (levelModifier != 0) {
for (EncounterSet area : currentEncounters) {
for (Encounter enc : area.encounters) {
enc.level = Math.min(100, (int) Math.round(enc.level * (1 + levelModifier / 100.0)));
enc.maxLevel = Math.min(100, (int) Math.round(enc.maxLevel * (1 + levelModifier / 100.0)));
}
}
}
setEncounters(useTimeOfDay, currentEncounters);
}
@Override
public void area1to1Encounters(Settings settings) {
boolean useTimeOfDay = settings.isUseTimeBasedEncounters();
List currentEncounters = this.getEncounters(useTimeOfDay);
if (isORAS) {
List collapsedEncounters = collapseAreasORAS(currentEncounters);
area1to1EncountersImpl(collapsedEncounters, settings);
setEncounters(useTimeOfDay, currentEncounters);
return;
} else {
area1to1EncountersImpl(currentEncounters, settings);
setEncounters(useTimeOfDay, currentEncounters);
}
}
private void area1to1EncountersImpl(List currentEncounters, Settings settings) {
boolean catchEmAll = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.CATCH_EM_ALL;
boolean typeThemed = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.TYPE_THEME_AREAS;
boolean usePowerLevels = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.SIMILAR_STRENGTH;
boolean noLegendaries = settings.isBlockWildLegendaries();
int levelModifier = settings.isWildLevelsModified() ? settings.getWildLevelModifier() : 0;
boolean allowAltFormes = settings.isAllowWildAltFormes();
boolean banIrregularAltFormes = settings.isBanIrregularAltFormes();
boolean abilitiesAreRandomized = settings.getAbilitiesMod() == Settings.AbilitiesMod.RANDOMIZE;
checkPokemonRestrictions();
List banned = this.bannedForWildEncounters();
banned.addAll(this.getBannedFormesForPlayerPokemon());
if (!abilitiesAreRandomized) {
List abilityDependentFormes = getAbilityDependentFormes();
banned.addAll(abilityDependentFormes);
}
if (banIrregularAltFormes) {
banned.addAll(getIrregularFormes());
}
// New: randomize the order encounter sets are randomized in.
// Leads to less predictable results for various modifiers.
// Need to keep the original ordering around for saving though.
List scrambledEncounters = new ArrayList<>(currentEncounters);
Collections.shuffle(scrambledEncounters, this.random);
// Assume EITHER catch em all OR type themed for now
if (catchEmAll) {
List allPokes;
if (allowAltFormes) {
allPokes = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes) : new ArrayList<>(
mainPokemonListInclFormes);
allPokes.removeIf(o -> ((Pokemon) o).actuallyCosmetic);
} else {
allPokes = noLegendaries ? new ArrayList<>(noLegendaryList) : new ArrayList<>(
mainPokemonList);
}
allPokes.removeAll(banned);
for (EncounterSet area : scrambledEncounters) {
Set inArea = pokemonInArea(area);
// Build area map using catch em all
Map areaMap = new TreeMap<>();
List pickablePokemon = allPokes;
if (area.bannedPokemon.size() > 0) {
pickablePokemon = new ArrayList<>(allPokes);
pickablePokemon.removeAll(area.bannedPokemon);
}
for (Pokemon areaPk : inArea) {
if (pickablePokemon.size() == 0) {
// No more pickable pokes left, take a random one
List tempPickable;
if (allowAltFormes) {
tempPickable = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes) : new ArrayList<>(
mainPokemonListInclFormes);
tempPickable.removeIf(o -> ((Pokemon) o).actuallyCosmetic);
} else {
tempPickable = noLegendaries ? new ArrayList<>(noLegendaryList) : new ArrayList<>(
mainPokemonList);
}
tempPickable.removeAll(banned);
tempPickable.removeAll(area.bannedPokemon);
if (tempPickable.size() == 0) {
throw new RandomizationException("ERROR: Couldn't replace a wild Pokemon!");
}
int picked = this.random.nextInt(tempPickable.size());
Pokemon pickedMN = tempPickable.get(picked);
areaMap.put(areaPk, pickedMN);
} else {
int picked = this.random.nextInt(allPokes.size());
Pokemon pickedMN = allPokes.get(picked);
areaMap.put(areaPk, pickedMN);
pickablePokemon.remove(pickedMN);
if (allPokes != pickablePokemon) {
allPokes.remove(pickedMN);
}
if (allPokes.size() == 0) {
// Start again
if (allowAltFormes) {
allPokes.addAll(noLegendaries ? noLegendaryListInclFormes : mainPokemonListInclFormes);
allPokes.removeIf(o -> ((Pokemon) o).actuallyCosmetic);
} else {
allPokes.addAll(noLegendaries ? noLegendaryList : mainPokemonList);
}
allPokes.removeAll(banned);
if (pickablePokemon != allPokes) {
pickablePokemon.addAll(allPokes);
pickablePokemon.removeAll(area.bannedPokemon);
}
}
}
}
for (Encounter enc : area.encounters) {
// In Catch 'Em All mode, don't randomize encounters for Pokemon that are banned for
// wild encounters. Otherwise, it may be impossible to obtain this Pokemon unless it
// randomly appears as a static or unless it becomes a random evolution.
if (banned.contains(enc.pokemon)) {
continue;
}
// Apply the map
enc.pokemon = areaMap.get(enc.pokemon);
setFormeForEncounter(enc, enc.pokemon);
}
}
} else if (typeThemed) {
Map> cachedPokeLists = new TreeMap<>();
for (EncounterSet area : scrambledEncounters) {
// Poke-set
Set inArea = pokemonInArea(area);
List possiblePokemon = null;
int iterLoops = 0;
while (possiblePokemon == null && iterLoops < 10000) {
Type areaTheme = randomType();
if (!cachedPokeLists.containsKey(areaTheme)) {
List pType = allowAltFormes ? pokemonOfTypeInclFormes(areaTheme, noLegendaries) :
pokemonOfType(areaTheme, noLegendaries);
pType.removeAll(banned);
cachedPokeLists.put(areaTheme, pType);
}
possiblePokemon = new ArrayList<>(cachedPokeLists.get(areaTheme));
if (area.bannedPokemon.size() > 0) {
possiblePokemon.removeAll(area.bannedPokemon);
}
if (possiblePokemon.size() < inArea.size()) {
// Can't use this type for this area
possiblePokemon = null;
}
iterLoops++;
}
if (possiblePokemon == null) {
throw new RandomizationException("Could not randomize an area in a reasonable amount of attempts.");
}
// Build area map using type theme.
Map areaMap = new TreeMap<>();
for (Pokemon areaPk : inArea) {
int picked = this.random.nextInt(possiblePokemon.size());
Pokemon pickedMN = possiblePokemon.get(picked);
while (pickedMN.actuallyCosmetic) {
picked = this.random.nextInt(possiblePokemon.size());
pickedMN = possiblePokemon.get(picked);
}
areaMap.put(areaPk, pickedMN);
possiblePokemon.remove(picked);
}
for (Encounter enc : area.encounters) {
// Apply the map
enc.pokemon = areaMap.get(enc.pokemon);
setFormeForEncounter(enc, enc.pokemon);
}
}
} else if (usePowerLevels) {
List allowedPokes;
if (allowAltFormes) {
allowedPokes = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes)
: new ArrayList<>(mainPokemonListInclFormes);
} else {
allowedPokes = noLegendaries ? new ArrayList<>(noLegendaryList)
: new ArrayList<>(mainPokemonList);
}
allowedPokes.removeAll(banned);
for (EncounterSet area : scrambledEncounters) {
// Poke-set
Set inArea = pokemonInArea(area);
// Build area map using randoms
Map areaMap = new TreeMap<>();
List usedPks = new ArrayList<>();
List localAllowed = allowedPokes;
if (area.bannedPokemon.size() > 0) {
localAllowed = new ArrayList<>(allowedPokes);
localAllowed.removeAll(area.bannedPokemon);
}
for (Pokemon areaPk : inArea) {
Pokemon picked = pickWildPowerLvlReplacement(localAllowed, areaPk, false, usedPks, 100);
while (picked.actuallyCosmetic) {
picked = pickWildPowerLvlReplacement(localAllowed, areaPk, false, usedPks, 100);
}
areaMap.put(areaPk, picked);
usedPks.add(picked);
}
for (Encounter enc : area.encounters) {
// Apply the map
enc.pokemon = areaMap.get(enc.pokemon);
setFormeForEncounter(enc, enc.pokemon);
}
}
} else {
// Entirely random
for (EncounterSet area : scrambledEncounters) {
// Poke-set
Set inArea = pokemonInArea(area);
// Build area map using randoms
Map areaMap = new TreeMap<>();
for (Pokemon areaPk : inArea) {
Pokemon picked = pickEntirelyRandomPokemon(allowAltFormes, noLegendaries, area, banned);
while (areaMap.containsValue(picked)) {
picked = pickEntirelyRandomPokemon(allowAltFormes, noLegendaries, area, banned);
}
areaMap.put(areaPk, picked);
}
for (Encounter enc : area.encounters) {
// Apply the map
enc.pokemon = areaMap.get(enc.pokemon);
setFormeForEncounter(enc, enc.pokemon);
}
}
}
if (levelModifier != 0) {
for (EncounterSet area : currentEncounters) {
for (Encounter enc : area.encounters) {
enc.level = Math.min(100, (int) Math.round(enc.level * (1 + levelModifier / 100.0)));
enc.maxLevel = Math.min(100, (int) Math.round(enc.maxLevel * (1 + levelModifier / 100.0)));
}
}
}
}
@Override
public void game1to1Encounters(Settings settings) {
boolean useTimeOfDay = settings.isUseTimeBasedEncounters();
boolean usePowerLevels = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.SIMILAR_STRENGTH;
boolean noLegendaries = settings.isBlockWildLegendaries();
int levelModifier = settings.isWildLevelsModified() ? settings.getWildLevelModifier() : 0;
boolean allowAltFormes = settings.isAllowWildAltFormes();
boolean banIrregularAltFormes = settings.isBanIrregularAltFormes();
boolean abilitiesAreRandomized = settings.getAbilitiesMod() == Settings.AbilitiesMod.RANDOMIZE;
checkPokemonRestrictions();
// Build the full 1-to-1 map
Map translateMap = new TreeMap<>();
List remainingLeft = allPokemonInclFormesWithoutNull();
remainingLeft.removeIf(o -> ((Pokemon) o).actuallyCosmetic);
List remainingRight;
if (allowAltFormes) {
remainingRight = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes)
: new ArrayList<>(mainPokemonListInclFormes);
remainingRight.removeIf(o -> ((Pokemon) o).actuallyCosmetic);
} else {
remainingRight = noLegendaries ? new ArrayList<>(noLegendaryList)
: new ArrayList<>(mainPokemonList);
}
List banned = this.bannedForWildEncounters();
banned.addAll(this.getBannedFormesForPlayerPokemon());
if (!abilitiesAreRandomized) {
List abilityDependentFormes = getAbilityDependentFormes();
banned.addAll(abilityDependentFormes);
}
if (banIrregularAltFormes) {
banned.addAll(getIrregularFormes());
}
// Banned pokemon should be mapped to themselves
for (Pokemon bannedPK : banned) {
translateMap.put(bannedPK, bannedPK);
remainingLeft.remove(bannedPK);
remainingRight.remove(bannedPK);
}
while (!remainingLeft.isEmpty()) {
if (usePowerLevels) {
int pickedLeft = this.random.nextInt(remainingLeft.size());
Pokemon pickedLeftP = remainingLeft.remove(pickedLeft);
Pokemon pickedRightP;
if (remainingRight.size() == 1) {
// pick this (it may or may not be the same poke)
pickedRightP = remainingRight.get(0);
} else {
// pick on power level with the current one blocked
pickedRightP = pickWildPowerLvlReplacement(remainingRight, pickedLeftP, true, null, 100);
}
remainingRight.remove(pickedRightP);
translateMap.put(pickedLeftP, pickedRightP);
} else {
int pickedLeft = this.random.nextInt(remainingLeft.size());
int pickedRight = this.random.nextInt(remainingRight.size());
Pokemon pickedLeftP = remainingLeft.remove(pickedLeft);
Pokemon pickedRightP = remainingRight.get(pickedRight);
while (pickedLeftP.number == pickedRightP.number && remainingRight.size() != 1) {
// Reroll for a different pokemon if at all possible
pickedRight = this.random.nextInt(remainingRight.size());
pickedRightP = remainingRight.get(pickedRight);
}
remainingRight.remove(pickedRight);
translateMap.put(pickedLeftP, pickedRightP);
}
if (remainingRight.size() == 0) {
// restart
if (allowAltFormes) {
remainingRight.addAll(noLegendaries ? noLegendaryListInclFormes : mainPokemonListInclFormes);
remainingRight.removeIf(o -> ((Pokemon) o).actuallyCosmetic);
} else {
remainingRight.addAll(noLegendaries ? noLegendaryList : mainPokemonList);
}
remainingRight.removeAll(banned);
}
}
// Map remaining to themselves just in case
List allPokes = allPokemonInclFormesWithoutNull();
for (Pokemon poke : allPokes) {
if (!translateMap.containsKey(poke)) {
translateMap.put(poke, poke);
}
}
List currentEncounters = this.getEncounters(useTimeOfDay);
for (EncounterSet area : currentEncounters) {
for (Encounter enc : area.encounters) {
// Apply the map
enc.pokemon = translateMap.get(enc.pokemon);
if (area.bannedPokemon.contains(enc.pokemon)) {
// Ignore the map and put a random non-banned poke
List tempPickable;
if (allowAltFormes) {
tempPickable = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes)
: new ArrayList<>(mainPokemonListInclFormes);
tempPickable.removeIf(o -> ((Pokemon) o).actuallyCosmetic);
} else {
tempPickable = noLegendaries ? new ArrayList<>(noLegendaryList)
: new ArrayList<>(mainPokemonList);
}
tempPickable.removeAll(banned);
tempPickable.removeAll(area.bannedPokemon);
if (tempPickable.size() == 0) {
throw new RandomizationException("ERROR: Couldn't replace a wild Pokemon!");
}
if (usePowerLevels) {
enc.pokemon = pickWildPowerLvlReplacement(tempPickable, enc.pokemon, false, null, 100);
} else {
int picked = this.random.nextInt(tempPickable.size());
enc.pokemon = tempPickable.get(picked);
}
}
setFormeForEncounter(enc, enc.pokemon);
}
}
if (levelModifier != 0) {
for (EncounterSet area : currentEncounters) {
for (Encounter enc : area.encounters) {
enc.level = Math.min(100, (int) Math.round(enc.level * (1 + levelModifier / 100.0)));
enc.maxLevel = Math.min(100, (int) Math.round(enc.maxLevel * (1 + levelModifier / 100.0)));
}
}
}
setEncounters(useTimeOfDay, currentEncounters);
}
@Override
public void onlyChangeWildLevels(Settings settings) {
int levelModifier = settings.getWildLevelModifier();
List currentEncounters = this.getEncounters(true);
if (levelModifier != 0) {
for (EncounterSet area : currentEncounters) {
for (Encounter enc : area.encounters) {
enc.level = Math.min(100, (int) Math.round(enc.level * (1 + levelModifier / 100.0)));
enc.maxLevel = Math.min(100, (int) Math.round(enc.maxLevel * (1 + levelModifier / 100.0)));
}
}
setEncounters(true, currentEncounters);
}
}
private void enhanceRandomEncountersORAS(List collapsedEncounters, Settings settings) {
boolean catchEmAll = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.CATCH_EM_ALL;
boolean typeThemed = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.TYPE_THEME_AREAS;
boolean usePowerLevels = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.SIMILAR_STRENGTH;
boolean noLegendaries = settings.isBlockWildLegendaries();
boolean allowAltFormes = settings.isAllowWildAltFormes();
boolean banIrregularAltFormes = settings.isBanIrregularAltFormes();
boolean abilitiesAreRandomized = settings.getAbilitiesMod() == Settings.AbilitiesMod.RANDOMIZE;
List banned = this.bannedForWildEncounters();
if (!abilitiesAreRandomized) {
List abilityDependentFormes = getAbilityDependentFormes();
banned.addAll(abilityDependentFormes);
}
if (banIrregularAltFormes) {
banned.addAll(getIrregularFormes());
}
Map> zonesToEncounters = mapZonesToEncounters(collapsedEncounters);
Map> cachedPokeLists = new TreeMap<>();
for (List encountersInZone : zonesToEncounters.values()) {
int currentAreaIndex = -1;
List nonRockSmashAreas = new ArrayList<>();
Map> areasAndEncountersToRandomize = new TreeMap<>();
// Since Rock Smash Pokemon do not show up on DexNav, they can be fully randomized
for (EncounterSet area : encountersInZone) {
if (area.displayName.contains("Rock Smash")) {
// Assume EITHER catch em all OR type themed OR match strength for now
if (catchEmAll) {
for (Encounter enc : area.encounters) {
boolean shouldRandomize = doesAnotherEncounterWithSamePokemonExistInArea(enc, area);
if (shouldRandomize) {
enc.pokemon = pickEntirelyRandomPokemon(allowAltFormes, noLegendaries, area, banned);
setFormeForEncounter(enc, enc.pokemon);
}
}
} else if (typeThemed) {
List possiblePokemon = null;
int iterLoops = 0;
while (possiblePokemon == null && iterLoops < 10000) {
Type areaTheme = randomType();
if (!cachedPokeLists.containsKey(areaTheme)) {
List pType = allowAltFormes ? pokemonOfTypeInclFormes(areaTheme, noLegendaries) :
pokemonOfType(areaTheme, noLegendaries);
pType.removeAll(banned);
cachedPokeLists.put(areaTheme, pType);
}
possiblePokemon = cachedPokeLists.get(areaTheme);
if (area.bannedPokemon.size() > 0) {
possiblePokemon = new ArrayList<>(possiblePokemon);
possiblePokemon.removeAll(area.bannedPokemon);
}
if (possiblePokemon.size() == 0) {
// Can't use this type for this area
possiblePokemon = null;
}
iterLoops++;
}
if (possiblePokemon == null) {
throw new RandomizationException("Could not randomize an area in a reasonable amount of attempts.");
}
for (Encounter enc : area.encounters) {
// Pick a random themed pokemon
enc.pokemon = possiblePokemon.get(this.random.nextInt(possiblePokemon.size()));
while (enc.pokemon.actuallyCosmetic) {
enc.pokemon = possiblePokemon.get(this.random.nextInt(possiblePokemon.size()));
}
setFormeForEncounter(enc, enc.pokemon);
}
} else if (usePowerLevels) {
List allowedPokes;
if (allowAltFormes) {
allowedPokes = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes)
: new ArrayList<>(mainPokemonListInclFormes);
} else {
allowedPokes = noLegendaries ? new ArrayList<>(noLegendaryList)
: new ArrayList<>(mainPokemonList);
}
allowedPokes.removeAll(banned);
List localAllowed = allowedPokes;
if (area.bannedPokemon.size() > 0) {
localAllowed = new ArrayList<>(allowedPokes);
localAllowed.removeAll(area.bannedPokemon);
}
for (Encounter enc : area.encounters) {
enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, 100);
while (enc.pokemon.actuallyCosmetic) {
enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, 100);
}
setFormeForEncounter(enc, enc.pokemon);
}
} else {
// Entirely random
for (Encounter enc : area.encounters) {
enc.pokemon = pickEntirelyRandomPokemon(allowAltFormes, noLegendaries, area, banned);
setFormeForEncounter(enc, enc.pokemon);
}
}
} else {
currentAreaIndex++;
nonRockSmashAreas.add(area);
List encounterIndices = new ArrayList<>();
for (int i = 0; i < area.encounters.size(); i++) {
encounterIndices.add(i);
}
areasAndEncountersToRandomize.put(currentAreaIndex, encounterIndices);
}
}
// Now, randomize non-Rock Smash Pokemon until we hit the threshold for DexNav
int crashThreshold = computeDexNavCrashThreshold(encountersInZone);
while (crashThreshold < 18 && areasAndEncountersToRandomize.size() > 0) {
Set areaIndices = areasAndEncountersToRandomize.keySet();
int areaIndex = areaIndices.stream().skip(this.random.nextInt(areaIndices.size())).findFirst().orElse(-1);
List encounterIndices = areasAndEncountersToRandomize.get(areaIndex);
int indexInListOfEncounterIndices = this.random.nextInt(encounterIndices.size());
int randomEncounterIndex = encounterIndices.get(indexInListOfEncounterIndices);
EncounterSet area = nonRockSmashAreas.get(areaIndex);
Encounter enc = area.encounters.get(randomEncounterIndex);
// Assume EITHER catch em all OR type themed OR match strength for now
if (catchEmAll) {
boolean shouldRandomize = doesAnotherEncounterWithSamePokemonExistInArea(enc, area);
if (shouldRandomize) {
enc.pokemon = pickEntirelyRandomPokemon(allowAltFormes, noLegendaries, area, banned);
setFormeForEncounter(enc, enc.pokemon);
}
} else if (typeThemed) {
List possiblePokemon = null;
Type areaTheme = getTypeForArea(area);
if (!cachedPokeLists.containsKey(areaTheme)) {
List pType = allowAltFormes ? pokemonOfTypeInclFormes(areaTheme, noLegendaries) :
pokemonOfType(areaTheme, noLegendaries);
pType.removeAll(banned);
cachedPokeLists.put(areaTheme, pType);
}
possiblePokemon = cachedPokeLists.get(areaTheme);
if (area.bannedPokemon.size() > 0) {
possiblePokemon = new ArrayList<>(possiblePokemon);
possiblePokemon.removeAll(area.bannedPokemon);
}
if (possiblePokemon.size() == 0) {
// Can't use this type for this area
throw new RandomizationException("Could not find a possible Pokemon of the correct type.");
}
// Pick a random themed pokemon
enc.pokemon = possiblePokemon.get(this.random.nextInt(possiblePokemon.size()));
while (enc.pokemon.actuallyCosmetic) {
enc.pokemon = possiblePokemon.get(this.random.nextInt(possiblePokemon.size()));
}
setFormeForEncounter(enc, enc.pokemon);
} else if (usePowerLevels) {
List allowedPokes;
if (allowAltFormes) {
allowedPokes = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes)
: new ArrayList<>(mainPokemonListInclFormes);
} else {
allowedPokes = noLegendaries ? new ArrayList<>(noLegendaryList)
: new ArrayList<>(mainPokemonList);
}
allowedPokes.removeAll(banned);
List localAllowed = allowedPokes;
if (area.bannedPokemon.size() > 0) {
localAllowed = new ArrayList<>(allowedPokes);
localAllowed.removeAll(area.bannedPokemon);
}
enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, 100);
while (enc.pokemon.actuallyCosmetic) {
enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, 100);
}
setFormeForEncounter(enc, enc.pokemon);
} else {
// Entirely random
enc.pokemon = pickEntirelyRandomPokemon(allowAltFormes, noLegendaries, area, banned);
setFormeForEncounter(enc, enc.pokemon);
}
crashThreshold = computeDexNavCrashThreshold(encountersInZone);
encounterIndices.remove(indexInListOfEncounterIndices);
if (encounterIndices.size() == 0) {
areasAndEncountersToRandomize.remove(areaIndex);
}
}
}
}
private Type getTypeForArea(EncounterSet area) {
Pokemon firstPokemon = area.encounters.get(0).pokemon;
if (area.encounters.get(0).formeNumber != 0) {
firstPokemon = getAltFormeOfPokemon(firstPokemon, area.encounters.get(0).formeNumber);
}
Type primaryType = firstPokemon.primaryType;
int primaryCount = 1;
Type secondaryType = null;
int secondaryCount = 0;
if (firstPokemon.secondaryType != null) {
secondaryType = firstPokemon.secondaryType;
secondaryCount = 1;
}
for (int i = 1; i < area.encounters.size(); i++) {
Pokemon pokemon = area.encounters.get(i).pokemon;
if (area.encounters.get(i).formeNumber != 0) {
pokemon = getAltFormeOfPokemon(pokemon, area.encounters.get(i).formeNumber);
}
if (pokemon.primaryType == primaryType || pokemon.secondaryType == primaryType) {
primaryCount++;
}
if (pokemon.primaryType == secondaryType || pokemon.secondaryType == secondaryType) {
secondaryCount++;
}
}
return primaryCount > secondaryCount ? primaryType : secondaryType;
}
private boolean doesAnotherEncounterWithSamePokemonExistInArea(Encounter enc, EncounterSet area) {
for (Encounter encounterToCheck : area.encounters) {
if (enc != encounterToCheck && enc.pokemon == encounterToCheck.pokemon) {
return true;
}
}
return false;
}
private List collapseAreasORAS(List currentEncounters) {
List output = new ArrayList<>();
Map> zonesToEncounters = mapZonesToEncounters(currentEncounters);
for (Integer zone : zonesToEncounters.keySet()) {
List encountersInZone = zonesToEncounters.get(zone);
int crashThreshold = computeDexNavCrashThreshold(encountersInZone);
if (crashThreshold <= 18) {
output.addAll(encountersInZone);
continue;
}
// Naive Area 1-to-1 randomization will crash the game, so let's start collapsing areas to prevent this.
// Start with combining all the fishing rod encounters, since it's a little less noticeable when they've
// been collapsed.
List collapsedEncounters = new ArrayList<>(encountersInZone);
EncounterSet rodGroup = new EncounterSet();
rodGroup.offset = zone;
rodGroup.displayName = "Rod Group";
for (EncounterSet area : encountersInZone) {
if (area.displayName.contains("Old Rod") || area.displayName.contains("Good Rod") || area.displayName.contains("Super Rod")) {
collapsedEncounters.remove(area);
rodGroup.encounters.addAll(area.encounters);
}
}
if (rodGroup.encounters.size() > 0) {
collapsedEncounters.add(rodGroup);
}
crashThreshold = computeDexNavCrashThreshold(collapsedEncounters);
if (crashThreshold <= 18) {
output.addAll(collapsedEncounters);
continue;
}
// Even after combining all the fishing rod encounters, we're still not below the threshold to prevent
// DexNav from crashing the game. Combine all the grass encounters now to drop us below the threshold;
// we've combined everything that DexNav normally combines, so at this point, we're *guaranteed* not
// to crash the game.
EncounterSet grassGroup = new EncounterSet();
grassGroup.offset = zone;
grassGroup.displayName = "Grass Group";
for (EncounterSet area : encountersInZone) {
if (area.displayName.contains("Grass/Cave") || area.displayName.contains("Long Grass") || area.displayName.contains("Horde")) {
collapsedEncounters.remove(area);
grassGroup.encounters.addAll(area.encounters);
}
}
if (grassGroup.encounters.size() > 0) {
collapsedEncounters.add(grassGroup);
}
output.addAll(collapsedEncounters);
}
return output;
}
private int computeDexNavCrashThreshold(List encountersInZone) {
int crashThreshold = 0;
for (EncounterSet area : encountersInZone) {
if (area.displayName.contains("Rock Smash")) {
continue; // Rock Smash Pokemon don't display on DexNav
}
Set uniquePokemonInArea = new HashSet<>();
for (Encounter enc : area.encounters) {
if (enc.pokemon.baseForme != null) { // DexNav treats different forms as one Pokemon
uniquePokemonInArea.add(enc.pokemon.baseForme);
} else {
uniquePokemonInArea.add(enc.pokemon);
}
}
crashThreshold += uniquePokemonInArea.size();
}
return crashThreshold;
}
private void setEvoChainAsIllegal(Pokemon newPK, List illegalList, boolean willForceEvolve) {
// set pre-evos as illegal
setIllegalPreEvos(newPK, illegalList);
// if the placed Pokemon will be forced fully evolved, set its evolutions as illegal
if (willForceEvolve) {
setIllegalEvos(newPK, illegalList);
}
}
private void setIllegalPreEvos(Pokemon pk, List illegalList) {
for (Evolution evo: pk.evolutionsTo) {
pk = evo.from;
illegalList.add(pk);
setIllegalPreEvos(pk, illegalList);
}
}
private void setIllegalEvos(Pokemon pk, List illegalList) {
for (Evolution evo: pk.evolutionsFrom) {
pk = evo.to;
illegalList.add(pk);
setIllegalEvos(pk, illegalList);
}
}
private List getFinalEvos(Pokemon pk) {
List finalEvos = new ArrayList<>();
traverseEvolutions(pk, finalEvos);
return finalEvos;
}
private void traverseEvolutions(Pokemon pk, List finalEvos) {
if (!pk.evolutionsFrom.isEmpty()) {
for (Evolution evo: pk.evolutionsFrom) {
pk = evo.to;
traverseEvolutions(pk, finalEvos);
}
} else {
finalEvos.add(pk);
}
}
private void setFormeForTrainerPokemon(TrainerPokemon tp, Pokemon pk) {
boolean checkCosmetics = true;
tp.formeSuffix = "";
tp.forme = 0;
if (pk.formeNumber > 0) {
tp.forme = pk.formeNumber;
tp.formeSuffix = pk.formeSuffix;
tp.pokemon = pk.baseForme;
checkCosmetics = false;
}
if (checkCosmetics && tp.pokemon.cosmeticForms > 0) {
tp.forme = tp.pokemon.getCosmeticFormNumber(this.random.nextInt(tp.pokemon.cosmeticForms));
} else if (!checkCosmetics && pk.cosmeticForms > 0) {
tp.forme += pk.getCosmeticFormNumber(this.random.nextInt(pk.cosmeticForms));
}
}
private void applyLevelModifierToTrainerPokemon(Trainer trainer, int levelModifier) {
if (levelModifier != 0) {
for (TrainerPokemon tp : trainer.pokemon) {
tp.level = Math.min(100, (int) Math.round(tp.level * (1 + levelModifier / 100.0)));
}
}
}
@Override
public void randomizeTrainerPokes(Settings settings) {
boolean usePowerLevels = settings.isTrainersUsePokemonOfSimilarStrength();
boolean weightByFrequency = settings.isTrainersMatchTypingDistribution();
boolean noLegendaries = settings.isTrainersBlockLegendaries();
boolean noEarlyWonderGuard = settings.isTrainersBlockEarlyWonderGuard();
int levelModifier = settings.isTrainersLevelModified() ? settings.getTrainersLevelModifier() : 0;
boolean isTypeThemed = settings.getTrainersMod() == Settings.TrainersMod.TYPE_THEMED;
boolean isTypeThemedEliteFourGymOnly = settings.getTrainersMod() == Settings.TrainersMod.TYPE_THEMED_ELITE4_GYMS;
boolean distributionSetting = settings.getTrainersMod() == Settings.TrainersMod.DISTRIBUTED;
boolean mainPlaythroughSetting = settings.getTrainersMod() == Settings.TrainersMod.MAINPLAYTHROUGH;
boolean includeFormes = settings.isAllowTrainerAlternateFormes();
boolean banIrregularAltFormes = settings.isBanIrregularAltFormes();
boolean swapMegaEvos = settings.isSwapTrainerMegaEvos();
boolean shinyChance = settings.isShinyChance();
boolean abilitiesAreRandomized = settings.getAbilitiesMod() == Settings.AbilitiesMod.RANDOMIZE;
int eliteFourUniquePokemonNumber = settings.getEliteFourUniquePokemonNumber();
boolean forceFullyEvolved = settings.isTrainersForceFullyEvolved();
int forceFullyEvolvedLevel = settings.getTrainersForceFullyEvolvedLevel();
boolean forceChallengeMode = (settings.getCurrentMiscTweaks() & MiscTweak.FORCE_CHALLENGE_MODE.getValue()) > 0;
boolean rivalCarriesStarter = settings.isRivalCarriesStarterThroughout();
checkPokemonRestrictions();
// Set up Pokemon pool
cachedReplacementLists = new TreeMap<>();
cachedAllList = noLegendaries ? new ArrayList<>(noLegendaryList) : new ArrayList<>(
mainPokemonList);
if (includeFormes) {
if (noLegendaries) {
cachedAllList.addAll(noLegendaryAltsList);
} else {
cachedAllList.addAll(altFormesList);
}
}
cachedAllList =
cachedAllList
.stream()
.filter(pk -> !pk.actuallyCosmetic)
.collect(Collectors.toList());
List banned = this.getBannedFormesForTrainerPokemon();
if (!abilitiesAreRandomized) {
List abilityDependentFormes = getAbilityDependentFormes();
banned.addAll(abilityDependentFormes);
}
if (banIrregularAltFormes) {
banned.addAll(getIrregularFormes());
}
cachedAllList.removeAll(banned);
List currentTrainers = this.getTrainers();
// Type Themed related
Map trainerTypes = new TreeMap<>();
Set usedUberTypes = new TreeSet<>();
if (isTypeThemed || isTypeThemedEliteFourGymOnly) {
typeWeightings = new TreeMap<>();
totalTypeWeighting = 0;
// Construct groupings for types
// Anything starting with GYM or ELITE or CHAMPION is a group
Map> groups = new TreeMap<>();
for (Trainer t : currentTrainers) {
if (t.tag != null && t.tag.equals("IRIVAL")) {
// This is the first rival in Yellow. His Pokemon is used to determine the non-player
// starter, so we can't change it here. Just skip it.
continue;
}
String group = t.tag == null ? "" : t.tag;
if (group.contains("-")) {
group = group.substring(0, group.indexOf('-'));
}
if (group.startsWith("GYM") || group.startsWith("ELITE") ||
((group.startsWith("CHAMPION") || group.startsWith("THEMED")) && !isTypeThemedEliteFourGymOnly)) {
// Yep this is a group
if (!groups.containsKey(group)) {
groups.put(group, new ArrayList<>());
}
groups.get(group).add(t);
} else if (group.startsWith("GIO")) {
// Giovanni has same grouping as his gym, gym 8
if (!groups.containsKey("GYM8")) {
groups.put("GYM8", new ArrayList<>());
}
groups.get("GYM8").add(t);
}
}
// Give a type to each group
// Gym & elite types have to be unique
// So do uber types, including the type we pick for champion
Set usedGymTypes = new TreeSet<>();
Set usedEliteTypes = new TreeSet<>();
for (String group : groups.keySet()) {
List trainersInGroup = groups.get(group);
// Shuffle ordering within group to promote randomness
Collections.shuffle(trainersInGroup, random);
Type typeForGroup = pickType(weightByFrequency, noLegendaries, includeFormes);
if (group.startsWith("GYM")) {
while (usedGymTypes.contains(typeForGroup)) {
typeForGroup = pickType(weightByFrequency, noLegendaries, includeFormes);
}
usedGymTypes.add(typeForGroup);
}
if (group.startsWith("ELITE")) {
while (usedEliteTypes.contains(typeForGroup)) {
typeForGroup = pickType(weightByFrequency, noLegendaries, includeFormes);
}
usedEliteTypes.add(typeForGroup);
}
if (group.equals("CHAMPION")) {
usedUberTypes.add(typeForGroup);
}
for (Trainer t : trainersInGroup) {
trainerTypes.put(t, typeForGroup);
}
}
}
// Randomize the order trainers are randomized in.
// Leads to less predictable results for various modifiers.
// Need to keep the original ordering around for saving though.
List scrambledTrainers = new ArrayList<>(currentTrainers);
Collections.shuffle(scrambledTrainers, this.random);
// Elite Four Unique Pokemon related
boolean eliteFourUniquePokemon = eliteFourUniquePokemonNumber > 0;
List illegalIfEvolvedList = new ArrayList<>();
List bannedFromUniqueList = new ArrayList<>();
boolean illegalEvoChains = false;
List eliteFourIndices = getEliteFourTrainers(forceChallengeMode);
if (eliteFourUniquePokemon) {
// Sort Elite Four Trainers to the start of the list
scrambledTrainers.sort((t1, t2) ->
Boolean.compare(eliteFourIndices.contains(currentTrainers.indexOf(t2)+1),eliteFourIndices.contains(currentTrainers.indexOf(t1)+1)));
illegalEvoChains = forceFullyEvolved;
if (rivalCarriesStarter) {
List starterList = getStarters().subList(0,3);
for (Pokemon starter: starterList) {
// If rival/friend carries starter, the starters cannot be set as unique
bannedFromUniqueList.add(starter);
setEvoChainAsIllegal(starter, bannedFromUniqueList, true);
// If the final boss is a rival/friend, the fully evolved starters will be unique
if (hasRivalFinalBattle()) {
cachedAllList.removeAll(getFinalEvos(starter));
if (illegalEvoChains) {
illegalIfEvolvedList.add(starter);
setEvoChainAsIllegal(starter, illegalIfEvolvedList, true);
}
}
}
}
}
List mainPlaythroughTrainers = getMainPlaythroughTrainers();
// Randomize Trainer Pokemon
// The result after this is done will not be final if "Force Fully Evolved" or "Rival Carries Starter"
// are used, as they are applied later
for (Trainer t : scrambledTrainers) {
applyLevelModifierToTrainerPokemon(t, levelModifier);
if (t.tag != null && t.tag.equals("IRIVAL")) {
// This is the first rival in Yellow. His Pokemon is used to determine the non-player
// starter, so we can't change it here. Just skip it.
continue;
}
// If type themed, give a type to each unassigned trainer
Type typeForTrainer = trainerTypes.get(t);
if (typeForTrainer == null && isTypeThemed) {
typeForTrainer = pickType(weightByFrequency, noLegendaries, includeFormes);
// Ubers: can't have the same type as each other
if (t.tag != null && t.tag.equals("UBER")) {
while (usedUberTypes.contains(typeForTrainer)) {
typeForTrainer = pickType(weightByFrequency, noLegendaries, includeFormes);
}
usedUberTypes.add(typeForTrainer);
}
}
List evolvesIntoTheWrongType = new ArrayList<>();
if (typeForTrainer != null) {
List pokemonOfType = includeFormes ? pokemonOfTypeInclFormes(typeForTrainer, noLegendaries) :
pokemonOfType(typeForTrainer, noLegendaries);
for (Pokemon pk : pokemonOfType) {
if (!pokemonOfType.contains(fullyEvolve(pk, t.index))) {
evolvesIntoTheWrongType.add(pk);
}
}
}
List trainerPokemonList = new ArrayList<>(t.pokemon);
// Elite Four Unique Pokemon related
boolean eliteFourTrackPokemon = false;
boolean eliteFourRival = false;
if (eliteFourUniquePokemon && eliteFourIndices.contains(t.index)) {
eliteFourTrackPokemon = true;
// Sort Pokemon list back to front, and then put highest level Pokemon first
// (Only while randomizing, does not affect order in game)
Collections.reverse(trainerPokemonList);
trainerPokemonList.sort((tp1, tp2) -> Integer.compare(tp2.level, tp1.level));
if (rivalCarriesStarter && (t.tag.contains("RIVAL") || t.tag.contains("FRIEND"))) {
eliteFourRival = true;
}
}
for (TrainerPokemon tp : trainerPokemonList) {
boolean swapThisMegaEvo = swapMegaEvos && tp.canMegaEvolve();
boolean wgAllowed = (!noEarlyWonderGuard) || tp.level >= 20;
boolean eliteFourSetUniquePokemon =
eliteFourTrackPokemon && eliteFourUniquePokemonNumber > trainerPokemonList.indexOf(tp);
boolean willForceEvolve = forceFullyEvolved && tp.level >= forceFullyEvolvedLevel;
Pokemon oldPK = tp.pokemon;
if (tp.forme > 0) {
oldPK = getAltFormeOfPokemon(oldPK, tp.forme);
}
bannedList = new ArrayList<>();
bannedList.addAll(usedAsUniqueList);
if (illegalEvoChains && willForceEvolve) {
bannedList.addAll(illegalIfEvolvedList);
}
if (eliteFourSetUniquePokemon) {
bannedList.addAll(bannedFromUniqueList);
}
if (willForceEvolve) {
bannedList.addAll(evolvesIntoTheWrongType);
}
Pokemon newPK = pickTrainerPokeReplacement(
oldPK,
usePowerLevels,
typeForTrainer,
noLegendaries,
wgAllowed,
distributionSetting || (mainPlaythroughSetting && mainPlaythroughTrainers.contains(t.index)),
swapThisMegaEvo,
abilitiesAreRandomized,
includeFormes,
banIrregularAltFormes
);
// Chosen Pokemon is locked in past here
if (distributionSetting || (mainPlaythroughSetting && mainPlaythroughTrainers.contains(t.index))) {
setPlacementHistory(newPK);
}
tp.pokemon = newPK;
setFormeForTrainerPokemon(tp, newPK);
tp.abilitySlot = getRandomAbilitySlot(newPK);
tp.resetMoves = true;
if (!eliteFourRival) {
if (eliteFourSetUniquePokemon) {
List actualPKList;
if (willForceEvolve) {
actualPKList = getFinalEvos(newPK);
} else {
actualPKList = new ArrayList<>();
actualPKList.add(newPK);
}
// If the unique Pokemon will evolve, we have to set all its potential evolutions as unique
for (Pokemon actualPK: actualPKList) {
usedAsUniqueList.add(actualPK);
if (illegalEvoChains) {
setEvoChainAsIllegal(actualPK, illegalIfEvolvedList, willForceEvolve);
}
}
}
if (eliteFourTrackPokemon) {
bannedFromUniqueList.add(newPK);
if (illegalEvoChains) {
setEvoChainAsIllegal(newPK, bannedFromUniqueList, willForceEvolve);
}
}
} else {
// If the champion is a rival, the first Pokemon will be skipped - it's already
// set as unique since it's a starter
eliteFourRival = false;
}
if (swapThisMegaEvo) {
tp.heldItem = newPK
.megaEvolutionsFrom
.get(this.random.nextInt(newPK.megaEvolutionsFrom.size()))
.argument;
}
if (shinyChance) {
if (this.random.nextInt(256) == 0) {
tp.IVs |= (1 << 30);
}
}
}
}
// Save it all up
this.setTrainers(currentTrainers, false);
}
@Override
public void randomizeTrainerHeldItems(Settings settings) {
boolean giveToBossPokemon = settings.isRandomizeHeldItemsForBossTrainerPokemon();
boolean giveToImportantPokemon = settings.isRandomizeHeldItemsForImportantTrainerPokemon();
boolean giveToRegularPokemon = settings.isRandomizeHeldItemsForRegularTrainerPokemon();
boolean highestLevelOnly = settings.isHighestLevelGetsItemsForTrainers();
List moves = this.getMoves();
Map> movesets = this.getMovesLearnt();
List currentTrainers = this.getTrainers();
for (Trainer t : currentTrainers) {
if (trainerShouldNotGetBuffs(t)) {
continue;
}
if (!giveToRegularPokemon && (!t.isImportant() && !t.isBoss())) {
continue;
}
if (!giveToImportantPokemon && t.isImportant()) {
continue;
}
if (!giveToBossPokemon && t.isBoss()) {
continue;
}
t.setPokemonHaveItems(true);
if (highestLevelOnly) {
int maxLevel = -1;
TrainerPokemon highestLevelPoke = null;
for (TrainerPokemon tp : t.pokemon) {
if (tp.level > maxLevel) {
highestLevelPoke = tp;
maxLevel = tp.level;
}
}
if (highestLevelPoke == null) {
continue; // should never happen - trainer had zero pokes
}
int[] moveset = highestLevelPoke.resetMoves ?
RomFunctions.getMovesAtLevel(getAltFormeOfPokemon(
highestLevelPoke.pokemon, highestLevelPoke.forme).number,
movesets,
highestLevelPoke.level) :
highestLevelPoke.moves;
randomizeHeldItem(highestLevelPoke, settings, moves, moveset);
} else {
for (TrainerPokemon tp : t.pokemon) {
int[] moveset = tp.resetMoves ?
RomFunctions.getMovesAtLevel(getAltFormeOfPokemon(
tp.pokemon, tp.forme).number,
movesets,
tp.level) :
tp.moves;
randomizeHeldItem(tp, settings, moves, moveset);
if (t.requiresUniqueHeldItems) {
while (!t.pokemonHaveUniqueHeldItems()) {
randomizeHeldItem(tp, settings, moves, moveset);
}
}
}
}
}
this.setTrainers(currentTrainers, false);
}
private void randomizeHeldItem(TrainerPokemon tp, Settings settings, List moves, int[] moveset) {
boolean sensibleItemsOnly = settings.isSensibleItemsOnlyForTrainers();
boolean consumableItemsOnly = settings.isConsumableItemsOnlyForTrainers();
boolean swapMegaEvolutions = settings.isSwapTrainerMegaEvos();
if (tp.hasZCrystal) {
return; // Don't overwrite existing Z Crystals.
}
if (tp.hasMegaStone && swapMegaEvolutions) {
return; // Don't overwrite mega stones if another setting handled that.
}
List toChooseFrom;
if (sensibleItemsOnly) {
toChooseFrom = getSensibleHeldItemsFor(tp, consumableItemsOnly, moves, moveset);
} else if (consumableItemsOnly) {
toChooseFrom = getAllConsumableHeldItems();
} else {
toChooseFrom = getAllHeldItems();
}
tp.heldItem = toChooseFrom.get(random.nextInt(toChooseFrom.size()));
}
@Override
public void rivalCarriesStarter() {
checkPokemonRestrictions();
List currentTrainers = this.getTrainers();
rivalCarriesStarterUpdate(currentTrainers, "RIVAL", isORAS ? 0 : 1);
rivalCarriesStarterUpdate(currentTrainers, "FRIEND", 2);
this.setTrainers(currentTrainers, false);
}
@Override
public boolean hasRivalFinalBattle() {
return false;
}
@Override
public void forceFullyEvolvedTrainerPokes(Settings settings) {
int minLevel = settings.getTrainersForceFullyEvolvedLevel();
checkPokemonRestrictions();
List currentTrainers = this.getTrainers();
for (Trainer t : currentTrainers) {
for (TrainerPokemon tp : t.pokemon) {
if (tp.level >= minLevel) {
Pokemon newPokemon = fullyEvolve(tp.pokemon, t.index);
if (newPokemon != tp.pokemon) {
tp.pokemon = newPokemon;
setFormeForTrainerPokemon(tp, newPokemon);
tp.abilitySlot = getValidAbilitySlotFromOriginal(newPokemon, tp.abilitySlot);
tp.resetMoves = true;
}
}
}
}
this.setTrainers(currentTrainers, false);
}
@Override
public void onlyChangeTrainerLevels(Settings settings) {
int levelModifier = settings.getTrainersLevelModifier();
List currentTrainers = this.getTrainers();
for (Trainer t: currentTrainers) {
applyLevelModifierToTrainerPokemon(t, levelModifier);
}
this.setTrainers(currentTrainers, false);
}
@Override
public void addTrainerPokemon(Settings settings) {
int additionalNormal = settings.getAdditionalRegularTrainerPokemon();
int additionalImportant = settings.getAdditionalImportantTrainerPokemon();
int additionalBoss = settings.getAdditionalBossTrainerPokemon();
List currentTrainers = this.getTrainers();
for (Trainer t: currentTrainers) {
int additional;
if (t.isBoss()) {
additional = additionalBoss;
} else if (t.isImportant()) {
if (t.skipImportant()) continue;
additional = additionalImportant;
} else {
additional = additionalNormal;
}
if (additional == 0) {
continue;
}
int lowest = 100;
List potentialPokes = new ArrayList<>();
// First pass: find lowest level
for (TrainerPokemon tpk: t.pokemon) {
if (tpk.level < lowest) {
lowest = tpk.level;
}
}
// Second pass: find all Pokemon at lowest level
for (TrainerPokemon tpk: t.pokemon) {
if (tpk.level == lowest) {
potentialPokes.add(tpk);
}
}
// If a trainer can appear in a Multi Battle (i.e., a Double Battle where the enemy consists
// of two independent trainers), we want to be aware of that so we don't give them a team of
// six Pokemon and have a 6v12 battle
int maxPokemon = t.multiBattleStatus != Trainer.MultiBattleStatus.NEVER ? 3 : 6;
for (int i = 0; i < additional; i++) {
if (t.pokemon.size() >= maxPokemon) break;
// We want to preserve the original last Pokemon because the order is sometimes used to
// determine the rival's starter
int secondToLastIndex = t.pokemon.size() - 1;
TrainerPokemon newPokemon = potentialPokes.get(i % potentialPokes.size()).copy();
// Clear out the held item because we only want one Pokemon with a mega stone if we're
// swapping mega evolvables
newPokemon.heldItem = 0;
t.pokemon.add(secondToLastIndex, newPokemon);
}
}
this.setTrainers(currentTrainers, false);
}
@Override
public void doubleBattleMode() {
List currentTrainers = this.getTrainers();
for (Trainer t: currentTrainers) {
if (t.pokemon.size() != 1 || t.multiBattleStatus == Trainer.MultiBattleStatus.ALWAYS || this.trainerShouldNotGetBuffs(t)) {
continue;
}
t.pokemon.add(t.pokemon.get(0).copy());
}
this.setTrainers(currentTrainers, true);
}
private Map> allLevelUpMoves;
private Map> allEggMoves;
private Map allTMCompat, allTutorCompat;
private List allTMMoves, allTutorMoves;
@Override
public List getMoveSelectionPoolAtLevel(TrainerPokemon tp, boolean cyclicEvolutions) {
List moves = getMoves();
double eggMoveProbability = 0.1;
double preEvoMoveProbability = 0.5;
double tmMoveProbability = 0.6;
double tutorMoveProbability = 0.6;
if (allLevelUpMoves == null) {
allLevelUpMoves = getMovesLearnt();
}
if (allEggMoves == null) {
allEggMoves = getEggMoves();
}
if (allTMCompat == null) {
allTMCompat = getTMHMCompatibility();
}
if (allTMMoves == null) {
allTMMoves = getTMMoves();
}
if (allTutorCompat == null && hasMoveTutors()) {
allTutorCompat = getMoveTutorCompatibility();
}
if (allTutorMoves == null) {
allTutorMoves = getMoveTutorMoves();
}
// Level-up Moves
List moveSelectionPoolAtLevel = allLevelUpMoves.get(getAltFormeOfPokemon(tp.pokemon, tp.forme).number)
.stream()
.filter(ml -> (ml.level <= tp.level && ml.level != 0) || (ml.level == 0 && tp.level >= 30))
.map(ml -> moves.get(ml.move))
.distinct()
.collect(Collectors.toList());
// Pre-Evo Moves
if (!cyclicEvolutions) {
Pokemon preEvo;
if (altFormesCanHaveDifferentEvolutions()) {
preEvo = getAltFormeOfPokemon(tp.pokemon, tp.forme);
} else {
preEvo = tp.pokemon;
}
while (!preEvo.evolutionsTo.isEmpty()) {
preEvo = preEvo.evolutionsTo.get(0).from;
moveSelectionPoolAtLevel.addAll(allLevelUpMoves.get(preEvo.number)
.stream()
.filter(ml -> ml.level <= tp.level)
.filter(ml -> this.random.nextDouble() < preEvoMoveProbability)
.map(ml -> moves.get(ml.move))
.distinct()
.collect(Collectors.toList()));
}
}
// TM Moves
boolean[] tmCompat = allTMCompat.get(getAltFormeOfPokemon(tp.pokemon, tp.forme));
for (int tmMove: allTMMoves) {
if (tmCompat[allTMMoves.indexOf(tmMove) + 1]) {
Move thisMove = moves.get(tmMove);
if (thisMove.power > 1 && tp.level * 3 > thisMove.power * thisMove.hitCount &&
this.random.nextDouble() < tmMoveProbability) {
moveSelectionPoolAtLevel.add(thisMove);
} else if ((thisMove.power <= 1 && this.random.nextInt(100) < tp.level) ||
this.random.nextInt(200) < tp.level) {
moveSelectionPoolAtLevel.add(thisMove);
}
}
}
// Move Tutor Moves
if (hasMoveTutors()) {
boolean[] tutorCompat = allTutorCompat.get(getAltFormeOfPokemon(tp.pokemon, tp.forme));
for (int tutorMove: allTutorMoves) {
if (tutorCompat[allTutorMoves.indexOf(tutorMove) + 1]) {
Move thisMove = moves.get(tutorMove);
if (thisMove.power > 1 && tp.level * 3 > thisMove.power * thisMove.hitCount &&
this.random.nextDouble() < tutorMoveProbability) {
moveSelectionPoolAtLevel.add(thisMove);
} else if ((thisMove.power <= 1 && this.random.nextInt(100) < tp.level) ||
this.random.nextInt(200) < tp.level) {
moveSelectionPoolAtLevel.add(thisMove);
}
}
}
}
// Egg Moves
if (!cyclicEvolutions) {
Pokemon firstEvo;
if (altFormesCanHaveDifferentEvolutions()) {
firstEvo = getAltFormeOfPokemon(tp.pokemon, tp.forme);
} else {
firstEvo = tp.pokemon;
}
while (!firstEvo.evolutionsTo.isEmpty()) {
firstEvo = firstEvo.evolutionsTo.get(0).from;
}
if (allEggMoves.get(firstEvo.number) != null) {
moveSelectionPoolAtLevel.addAll(allEggMoves.get(firstEvo.number)
.stream()
.filter(egm -> this.random.nextDouble() < eggMoveProbability)
.map(moves::get)
.collect(Collectors.toList()));
}
}
return moveSelectionPoolAtLevel.stream().distinct().collect(Collectors.toList());
}
@Override
public void pickTrainerMovesets(Settings settings) {
boolean isCyclicEvolutions = settings.getEvolutionsMod() == Settings.EvolutionsMod.RANDOM_EVERY_LEVEL;
boolean doubleBattleMode = settings.isDoubleBattleMode();
List trainers = getTrainers();
for (Trainer t: trainers) {
t.setPokemonHaveCustomMoves(true);
for (TrainerPokemon tp: t.pokemon) {
tp.resetMoves = false;
List movesAtLevel = getMoveSelectionPoolAtLevel(tp, isCyclicEvolutions);
movesAtLevel = trimMoveList(tp, movesAtLevel, doubleBattleMode);
if (movesAtLevel.isEmpty()) {
continue;
}
double trainerTypeModifier = 1;
if (t.isImportant()) {
trainerTypeModifier = 1.5;
} else if (t.isBoss()) {
trainerTypeModifier = 2;
}
double movePoolSizeModifier = movesAtLevel.size() / 10.0;
double bonusModifier = trainerTypeModifier * movePoolSizeModifier;
double atkSpatkRatioModifier = 0.75;
double stabMoveBias = 0.25 * bonusModifier;
double hardAbilityMoveBias = 1 * bonusModifier;
double softAbilityMoveBias = 0.5 * bonusModifier;
double statBias = 0.5 * bonusModifier;
double softMoveBias = 0.25 * bonusModifier;
double hardMoveBias = 1 * bonusModifier;
double softMoveAntiBias = 0.5;
// Add bias for STAB
Pokemon pk = getAltFormeOfPokemon(tp.pokemon, tp.forme);
List stabMoves = new ArrayList<>(movesAtLevel)
.stream()
.filter(mv -> mv.type == pk.primaryType && mv.category != MoveCategory.STATUS)
.collect(Collectors.toList());
Collections.shuffle(stabMoves, this.random);
for (int i = 0; i < stabMoveBias * stabMoves.size(); i++) {
int j = i % stabMoves.size();
movesAtLevel.add(stabMoves.get(j));
}
if (pk.secondaryType != null) {
stabMoves = new ArrayList<>(movesAtLevel)
.stream()
.filter(mv -> mv.type == pk.secondaryType && mv.category != MoveCategory.STATUS)
.collect(Collectors.toList());
Collections.shuffle(stabMoves, this.random);
for (int i = 0; i < stabMoveBias * stabMoves.size(); i++) {
int j = i % stabMoves.size();
movesAtLevel.add(stabMoves.get(j));
}
}
// Hard ability/move synergy
List abilityMoveSynergyList = MoveSynergy.getHardAbilityMoveSynergy(
getAbilityForTrainerPokemon(tp),
pk.primaryType,
pk.secondaryType,
movesAtLevel,
generationOfPokemon(),
perfectAccuracy);
Collections.shuffle(abilityMoveSynergyList, this.random);
for (int i = 0; i < hardAbilityMoveBias * abilityMoveSynergyList.size(); i++) {
int j = i % abilityMoveSynergyList.size();
movesAtLevel.add(abilityMoveSynergyList.get(j));
}
// Soft ability/move synergy
List softAbilityMoveSynergyList = MoveSynergy.getSoftAbilityMoveSynergy(
getAbilityForTrainerPokemon(tp),
movesAtLevel,
pk.primaryType,
pk.secondaryType);
Collections.shuffle(softAbilityMoveSynergyList, this.random);
for (int i = 0; i < softAbilityMoveBias * softAbilityMoveSynergyList.size(); i++) {
int j = i % softAbilityMoveSynergyList.size();
movesAtLevel.add(softAbilityMoveSynergyList.get(j));
}
// Soft ability/move anti-synergy
List softAbilityMoveAntiSynergyList = MoveSynergy.getSoftAbilityMoveAntiSynergy(
getAbilityForTrainerPokemon(tp), movesAtLevel);
List withoutSoftAntiSynergy = new ArrayList<>(movesAtLevel);
for (Move mv: softAbilityMoveAntiSynergyList) {
withoutSoftAntiSynergy.remove(mv);
}
if (withoutSoftAntiSynergy.size() > 0) {
movesAtLevel = withoutSoftAntiSynergy;
}
List distinctMoveList = movesAtLevel.stream().distinct().collect(Collectors.toList());
int movesLeft = distinctMoveList.size();
if (movesLeft <= 4) {
for (int i = 0; i < 4; i++) {
if (i < movesLeft) {
tp.moves[i] = distinctMoveList.get(i).number;
} else {
tp.moves[i] = 0;
}
}
continue;
}
// Stat/move synergy
List statSynergyList = MoveSynergy.getStatMoveSynergy(pk, movesAtLevel);
Collections.shuffle(statSynergyList, this.random);
for (int i = 0; i < statBias * statSynergyList.size(); i++) {
int j = i % statSynergyList.size();
movesAtLevel.add(statSynergyList.get(j));
}
// Stat/move anti-synergy
List statAntiSynergyList = MoveSynergy.getStatMoveAntiSynergy(pk, movesAtLevel);
List withoutStatAntiSynergy = new ArrayList<>(movesAtLevel);
for (Move mv: statAntiSynergyList) {
withoutStatAntiSynergy.remove(mv);
}
if (withoutStatAntiSynergy.size() > 0) {
movesAtLevel = withoutStatAntiSynergy;
}
distinctMoveList = movesAtLevel.stream().distinct().collect(Collectors.toList());
movesLeft = distinctMoveList.size();
if (movesLeft <= 4) {
for (int i = 0; i < 4; i++) {
if (i < movesLeft) {
tp.moves[i] = distinctMoveList.get(i).number;
} else {
tp.moves[i] = 0;
}
}
continue;
}
// Add bias for atk/spatk ratio
double atkSpatkRatio = (double)pk.attack / (double)pk.spatk;
switch(getAbilityForTrainerPokemon(tp)) {
case Abilities.hugePower:
case Abilities.purePower:
atkSpatkRatio *= 2;
break;
case Abilities.hustle:
case Abilities.gorillaTactics:
atkSpatkRatio *= 1.5;
break;
case Abilities.moxie:
atkSpatkRatio *= 1.1;
break;
case Abilities.soulHeart:
atkSpatkRatio *= 0.9;
break;
}
List physicalMoves = new ArrayList<>(movesAtLevel)
.stream()
.filter(mv -> mv.category == MoveCategory.PHYSICAL)
.collect(Collectors.toList());
List specialMoves = new ArrayList<>(movesAtLevel)
.stream()
.filter(mv -> mv.category == MoveCategory.SPECIAL)
.collect(Collectors.toList());
if (atkSpatkRatio < 1 && specialMoves.size() > 0) {
atkSpatkRatio = 1 / atkSpatkRatio;
double acceptedRatio = atkSpatkRatioModifier * atkSpatkRatio;
int additionalMoves = (int)(physicalMoves.size() * acceptedRatio) - specialMoves.size();
for (int i = 0; i < additionalMoves; i++) {
Move mv = specialMoves.get(this.random.nextInt(specialMoves.size()));
movesAtLevel.add(mv);
}
} else if (physicalMoves.size() > 0) {
double acceptedRatio = atkSpatkRatioModifier * atkSpatkRatio;
int additionalMoves = (int)(specialMoves.size() * acceptedRatio) - physicalMoves.size();
for (int i = 0; i < additionalMoves; i++) {
Move mv = physicalMoves.get(this.random.nextInt(physicalMoves.size()));
movesAtLevel.add(mv);
}
}
// Pick moves
List pickedMoves = new ArrayList<>();
for (int i = 1; i <= 4; i++) {
Move move;
List pickFrom;
if (i == 1) {
pickFrom = movesAtLevel
.stream()
.filter(mv -> mv.isGoodDamaging(perfectAccuracy))
.collect(Collectors.toList());
if (pickFrom.isEmpty()) {
pickFrom = movesAtLevel;
}
} else {
pickFrom = movesAtLevel;
}
if (i == 4) {
List requiresOtherMove = movesAtLevel
.stream()
.filter(mv -> GlobalConstants.requiresOtherMove.contains(mv.number))
.distinct()
.collect(Collectors.toList());
for (Move dependentMove: requiresOtherMove) {
boolean hasRequiredMove = false;
for (Move requiredMove: MoveSynergy.requiresOtherMove(dependentMove, movesAtLevel)) {
if (pickedMoves.contains(requiredMove)) {
hasRequiredMove = true;
break;
}
}
if (!hasRequiredMove) {
movesAtLevel.removeAll(Collections.singletonList(dependentMove));
}
}
}
move = pickFrom.get(this.random.nextInt(pickFrom.size()));
pickedMoves.add(move);
if (i == 4) {
break;
}
movesAtLevel.removeAll(Collections.singletonList(move));
movesAtLevel.removeAll(MoveSynergy.getHardMoveAntiSynergy(move, movesAtLevel));
distinctMoveList = movesAtLevel.stream().distinct().collect(Collectors.toList());
movesLeft = distinctMoveList.size();
if (movesLeft <= (4 - i)) {
pickedMoves.addAll(distinctMoveList);
break;
}
List hardMoveSynergyList = MoveSynergy.getMoveSynergy(
move,
movesAtLevel,
generationOfPokemon());
Collections.shuffle(hardMoveSynergyList, this.random);
for (int j = 0; j < hardMoveBias * hardMoveSynergyList.size(); j++) {
int k = j % hardMoveSynergyList.size();
movesAtLevel.add(hardMoveSynergyList.get(k));
}
List softMoveSynergyList = MoveSynergy.getSoftMoveSynergy(
move,
movesAtLevel,
generationOfPokemon(),
isEffectivenessUpdated());
Collections.shuffle(softMoveSynergyList, this.random);
for (int j = 0; j < softMoveBias * softMoveSynergyList.size(); j++) {
int k = j % softMoveSynergyList.size();
movesAtLevel.add(softMoveSynergyList.get(k));
}
List softMoveAntiSynergyList = MoveSynergy.getSoftMoveAntiSynergy(move, movesAtLevel);
Collections.shuffle(softMoveAntiSynergyList, this.random);
for (int j = 0; j < softMoveAntiBias * softMoveAntiSynergyList.size(); j++) {
distinctMoveList = movesAtLevel.stream().distinct().collect(Collectors.toList());
if (distinctMoveList.size() <= (4 - i)) {
break;
}
int k = j % softMoveAntiSynergyList.size();
movesAtLevel.remove(softMoveAntiSynergyList.get(k));
}
distinctMoveList = movesAtLevel.stream().distinct().collect(Collectors.toList());
movesLeft = distinctMoveList.size();
if (movesLeft <= (4 - i)) {
pickedMoves.addAll(distinctMoveList);
break;
}
}
int movesPicked = pickedMoves.size();
for (int i = 0; i < 4; i++) {
if (i < movesPicked) {
tp.moves[i] = pickedMoves.get(i).number;
} else {
tp.moves[i] = 0;
}
}
}
}
setTrainers(trainers, false);
}
private List trimMoveList(TrainerPokemon tp, List movesAtLevel, boolean doubleBattleMode) {
int movesLeft = movesAtLevel.size();
if (movesLeft <= 4) {
for (int i = 0; i < 4; i++) {
if (i < movesLeft) {
tp.moves[i] = movesAtLevel.get(i).number;
} else {
tp.moves[i] = 0;
}
}
return new ArrayList<>();
}
movesAtLevel = movesAtLevel
.stream()
.filter(mv -> !GlobalConstants.uselessMoves.contains(mv.number) &&
(doubleBattleMode || !GlobalConstants.doubleBattleMoves.contains(mv.number)))
.collect(Collectors.toList());
movesLeft = movesAtLevel.size();
if (movesLeft <= 4) {
for (int i = 0; i < 4; i++) {
if (i < movesLeft) {
tp.moves[i] = movesAtLevel.get(i).number;
} else {
tp.moves[i] = 0;
}
}
return new ArrayList<>();
}
List obsoletedMoves = getObsoleteMoves(movesAtLevel);
// Remove obsoleted moves
movesAtLevel.removeAll(obsoletedMoves);
movesLeft = movesAtLevel.size();
if (movesLeft <= 4) {
for (int i = 0; i < 4; i++) {
if (i < movesLeft) {
tp.moves[i] = movesAtLevel.get(i).number;
} else {
tp.moves[i] = 0;
}
}
return new ArrayList<>();
}
List requiresOtherMove = movesAtLevel
.stream()
.filter(mv -> GlobalConstants.requiresOtherMove.contains(mv.number))
.collect(Collectors.toList());
for (Move dependentMove: requiresOtherMove) {
if (MoveSynergy.requiresOtherMove(dependentMove, movesAtLevel).isEmpty()) {
movesAtLevel.remove(dependentMove);
}
}
movesLeft = movesAtLevel.size();
if (movesLeft <= 4) {
for (int i = 0; i < 4; i++) {
if (i < movesLeft) {
tp.moves[i] = movesAtLevel.get(i).number;
} else {
tp.moves[i] = 0;
}
}
return new ArrayList<>();
}
// Remove hard ability anti-synergy moves
List withoutHardAntiSynergy = new ArrayList<>(movesAtLevel);
withoutHardAntiSynergy.removeAll(MoveSynergy.getHardAbilityMoveAntiSynergy(
getAbilityForTrainerPokemon(tp),
movesAtLevel));
if (withoutHardAntiSynergy.size() > 0) {
movesAtLevel = withoutHardAntiSynergy;
}
movesLeft = movesAtLevel.size();
if (movesLeft <= 4) {
for (int i = 0; i < 4; i++) {
if (i < movesLeft) {
tp.moves[i] = movesAtLevel.get(i).number;
} else {
tp.moves[i] = 0;
}
}
return new ArrayList<>();
}
return movesAtLevel;
}
private List getObsoleteMoves(List movesAtLevel) {
List obsoletedMoves = new ArrayList<>();
for (Move mv: movesAtLevel) {
if (GlobalConstants.cannotObsoleteMoves.contains(mv.number)) {
continue;
}
if (mv.power > 0) {
List obsoleteThis = movesAtLevel
.stream()
.filter(mv2 -> !GlobalConstants.cannotBeObsoletedMoves.contains(mv2.number) &&
mv.type == mv2.type &&
((((mv.statChangeMoveType == mv2.statChangeMoveType &&
mv.statChanges[0].equals(mv2.statChanges[0])) ||
(mv2.statChangeMoveType == StatChangeMoveType.NONE_OR_UNKNOWN &&
mv.hasBeneficialStatChange())) &&
mv.absorbPercent >= mv2.absorbPercent &&
!mv.isChargeMove &&
!mv.isRechargeMove) ||
mv2.power * mv2.hitCount <= 30) &&
mv.hitratio >= mv2.hitratio &&
mv.category == mv2.category &&
mv.priority >= mv2.priority &&
mv2.power > 0 &&
mv.power * mv.hitCount > mv2.power * mv2.hitCount)
.collect(Collectors.toList());
for (Move obsoleted: obsoleteThis) {
//System.out.println(obsoleted.name + " obsoleted by " + mv.name);
}
obsoletedMoves.addAll(obsoleteThis);
} else if (mv.statChangeMoveType == StatChangeMoveType.NO_DAMAGE_USER ||
mv.statChangeMoveType == StatChangeMoveType.NO_DAMAGE_TARGET) {
List obsoleteThis = new ArrayList<>();
List statChanges1 = new ArrayList<>();
for (Move.StatChange sc: mv.statChanges) {
if (sc.type != StatChangeType.NONE) {
statChanges1.add(sc);
}
}
for (Move mv2: movesAtLevel
.stream()
.filter(otherMv -> !otherMv.equals(mv) &&
otherMv.power <= 0 &&
otherMv.statChangeMoveType == mv.statChangeMoveType &&
(otherMv.statusType == mv.statusType ||
otherMv.statusType == StatusType.NONE))
.collect(Collectors.toList())) {
List statChanges2 = new ArrayList<>();
for (Move.StatChange sc: mv2.statChanges) {
if (sc.type != StatChangeType.NONE) {
statChanges2.add(sc);
}
}
if (statChanges2.size() > statChanges1.size()) {
continue;
}
List statChanges1Filtered = statChanges1
.stream()
.filter(sc -> !statChanges2.contains(sc))
.collect(Collectors.toList());
statChanges2.removeAll(statChanges1);
if (!statChanges1Filtered.isEmpty() && statChanges2.isEmpty()) {
if (!GlobalConstants.cannotBeObsoletedMoves.contains(mv2.number)) {
obsoleteThis.add(mv2);
}
continue;
}
if (statChanges1Filtered.isEmpty() && statChanges2.isEmpty()) {
continue;
}
boolean maybeBetter = false;
for (Move.StatChange sc1: statChanges1Filtered) {
boolean canStillBeBetter = false;
for (Move.StatChange sc2: statChanges2) {
if (sc1.type == sc2.type) {
canStillBeBetter = true;
if ((mv.statChangeMoveType == StatChangeMoveType.NO_DAMAGE_USER && sc1.stages > sc2.stages) ||
(mv.statChangeMoveType == StatChangeMoveType.NO_DAMAGE_TARGET && sc1.stages < sc2.stages)) {
maybeBetter = true;
} else {
canStillBeBetter = false;
}
}
}
if (!canStillBeBetter) {
maybeBetter = false;
break;
}
}
if (maybeBetter) {
if (!GlobalConstants.cannotBeObsoletedMoves.contains(mv2.number)) {
obsoleteThis.add(mv2);
}
}
}
for (Move obsoleted: obsoleteThis) {
//System.out.println(obsoleted.name + " obsoleted by " + mv.name);
}
obsoletedMoves.addAll(obsoleteThis);
}
}
return obsoletedMoves.stream().distinct().collect(Collectors.toList());
}
private boolean trainerShouldNotGetBuffs(Trainer t) {
return t.tag != null && (t.tag.startsWith("RIVAL1-") || t.tag.startsWith("FRIEND1-") || t.tag.endsWith("NOTSTRONG"));
}
public int getRandomAbilitySlot(Pokemon pokemon) {
if (abilitiesPerPokemon() == 0) {
return 0;
}
List abilitiesList = Arrays.asList(pokemon.ability1, pokemon.ability2, pokemon.ability3);
int slot = random.nextInt(this.abilitiesPerPokemon());
while (abilitiesList.get(slot) == 0) {
slot = random.nextInt(this.abilitiesPerPokemon());
}
return slot + 1;
}
public int getValidAbilitySlotFromOriginal(Pokemon pokemon, int originalAbilitySlot) {
// This is used in cases where one Trainer Pokemon evolves into another. If the unevolved Pokemon
// is using slot 2, but the evolved Pokemon doesn't actually have a second ability, then we
// want the evolved Pokemon to use slot 1 for safety's sake.
if (originalAbilitySlot == 2 && pokemon.ability2 == 0) {
return 1;
}
return originalAbilitySlot;
}
// MOVE DATA
// All randomizers don't touch move ID 165 (Struggle)
// They also have other exclusions where necessary to stop things glitching.
@Override
public void randomizeMovePowers() {
List moves = this.getMoves();
for (Move mv : moves) {
if (mv != null && mv.internalId != Moves.struggle && mv.power >= 10) {
// "Generic" damaging move to randomize power
if (random.nextInt(3) != 2) {
// "Regular" move
mv.power = random.nextInt(11) * 5 + 50; // 50 ... 100
} else {
// "Extreme" move
mv.power = random.nextInt(27) * 5 + 20; // 20 ... 150
}
// Tiny chance for massive power jumps
for (int i = 0; i < 2; i++) {
if (random.nextInt(100) == 0) {
mv.power += 50;
}
}
if (mv.hitCount != 1) {
// Divide randomized power by average hit count, round to
// nearest 5
mv.power = (int) (Math.round(mv.power / mv.hitCount / 5) * 5);
if (mv.power == 0) {
mv.power = 5;
}
}
}
}
}
@Override
public void randomizeMovePPs() {
List moves = this.getMoves();
for (Move mv : moves) {
if (mv != null && mv.internalId != Moves.struggle) {
if (random.nextInt(3) != 2) {
// "average" PP: 15-25
mv.pp = random.nextInt(3) * 5 + 15;
} else {
// "extreme" PP: 5-40
mv.pp = random.nextInt(8) * 5 + 5;
}
}
}
}
@Override
public void randomizeMoveAccuracies() {
List moves = this.getMoves();
for (Move mv : moves) {
if (mv != null && mv.internalId != Moves.struggle && mv.hitratio >= 5) {
// "Sane" accuracy randomization
// Broken into three tiers based on original accuracy
// Designed to limit the chances of 100% accurate OHKO moves and
// keep a decent base of 100% accurate regular moves.
if (mv.hitratio <= 50) {
// lowest tier (acc <= 50)
// new accuracy = rand(20...50) inclusive
// with a 10% chance to increase by 50%
mv.hitratio = random.nextInt(7) * 5 + 20;
if (random.nextInt(10) == 0) {
mv.hitratio = (mv.hitratio * 3 / 2) / 5 * 5;
}
} else if (mv.hitratio < 90) {
// middle tier (50 < acc < 90)
// count down from 100% to 20% in 5% increments with 20%
// chance to "stop" and use the current accuracy at each
// increment
// gives decent-but-not-100% accuracy most of the time
mv.hitratio = 100;
while (mv.hitratio > 20) {
if (random.nextInt(10) < 2) {
break;
}
mv.hitratio -= 5;
}
} else {
// highest tier (90 <= acc <= 100)
// count down from 100% to 20% in 5% increments with 40%
// chance to "stop" and use the current accuracy at each
// increment
// gives high accuracy most of the time
mv.hitratio = 100;
while (mv.hitratio > 20) {
if (random.nextInt(10) < 4) {
break;
}
mv.hitratio -= 5;
}
}
}
}
}
@Override
public void randomizeMoveTypes() {
List moves = this.getMoves();
for (Move mv : moves) {
if (mv != null && mv.internalId != Moves.struggle && mv.type != null) {
mv.type = randomType();
}
}
}
@Override
public void randomizeMoveCategory() {
if (!this.hasPhysicalSpecialSplit()) {
return;
}
List moves = this.getMoves();
for (Move mv : moves) {
if (mv != null && mv.internalId != Moves.struggle && mv.category != MoveCategory.STATUS) {
if (random.nextInt(2) == 0) {
mv.category = (mv.category == MoveCategory.PHYSICAL) ? MoveCategory.SPECIAL : MoveCategory.PHYSICAL;
}
}
}
}
@Override
public void updateMoves(Settings settings) {
int generation = settings.getUpdateMovesToGeneration();
List moves = this.getMoves();
if (generation >= 2 && generationOfPokemon() < 2) {
// gen1
// Karate Chop => FIGHTING (gen1)
updateMoveType(moves, Moves.karateChop, Type.FIGHTING);
// Gust => FLYING (gen1)
updateMoveType(moves, Moves.gust, Type.FLYING);
// Wing Attack => 60 power (gen1)
updateMovePower(moves, Moves.wingAttack, 60);
// Whirlwind => 100 accuracy (gen1)
updateMoveAccuracy(moves, Moves.whirlwind, 100);
// Sand Attack => GROUND (gen1)
updateMoveType(moves, Moves.sandAttack, Type.GROUND);
// Double-Edge => 120 power (gen1)
updateMovePower(moves, Moves.doubleEdge, 120);
// Move 44, Bite, becomes dark (but doesn't exist anyway)
// Blizzard => 70% accuracy (gen1)
updateMoveAccuracy(moves, Moves.blizzard, 70);
// Rock Throw => 90% accuracy (gen1)
updateMoveAccuracy(moves, Moves.rockThrow, 90);
// Hypnosis => 60% accuracy (gen1)
updateMoveAccuracy(moves, Moves.hypnosis, 60);
// SelfDestruct => 200power (gen1)
updateMovePower(moves, Moves.selfDestruct, 200);
// Explosion => 250 power (gen1)
updateMovePower(moves, Moves.explosion, 250);
// Dig => 60 power (gen1)
updateMovePower(moves, Moves.dig, 60);
}
if (generation >= 3 && generationOfPokemon() < 3) {
// Razor Wind => 100% accuracy (gen1/2)
updateMoveAccuracy(moves, Moves.razorWind, 100);
// Move 67, Low Kick, has weight-based power in gen3+
// Low Kick => 100% accuracy (gen1)
updateMoveAccuracy(moves, Moves.lowKick, 100);
}
if (generation >= 4 && generationOfPokemon() < 4) {
// Fly => 90 power (gen1/2/3)
updateMovePower(moves, Moves.fly, 90);
// Vine Whip => 15 pp (gen1/2/3)
updateMovePP(moves, Moves.vineWhip, 15);
// Absorb => 25pp (gen1/2/3)
updateMovePP(moves, Moves.absorb, 25);
// Mega Drain => 15pp (gen1/2/3)
updateMovePP(moves, Moves.megaDrain, 15);
// Dig => 80 power (gen1/2/3)
updateMovePower(moves, Moves.dig, 80);
// Recover => 10pp (gen1/2/3)
updateMovePP(moves, Moves.recover, 10);
// Flash => 100% acc (gen1/2/3)
updateMoveAccuracy(moves, Moves.flash, 100);
// Petal Dance => 90 power (gen1/2/3)
updateMovePower(moves, Moves.petalDance, 90);
// Disable => 100% accuracy (gen1-4)
updateMoveAccuracy(moves, Moves.disable, 80);
// Jump Kick => 85 power
updateMovePower(moves, Moves.jumpKick, 85);
// Hi Jump Kick => 100 power
updateMovePower(moves, Moves.highJumpKick, 100);
if (generationOfPokemon() >= 2) {
// Zap Cannon => 120 power (gen2-3)
updateMovePower(moves, Moves.zapCannon, 120);
// Outrage => 120 power (gen2-3)
updateMovePower(moves, Moves.outrage, 120);
updateMovePP(moves, Moves.outrage, 10);
// Giga Drain => 10pp (gen2-3)
updateMovePP(moves, Moves.gigaDrain, 10);
// Rock Smash => 40 power (gen2-3)
updateMovePower(moves, Moves.rockSmash, 40);
}
if (generationOfPokemon() == 3) {
// Stockpile => 20 pp
updateMovePP(moves, Moves.stockpile, 20);
// Dive => 80 power
updateMovePower(moves, Moves.dive, 80);
// Leaf Blade => 90 power
updateMovePower(moves, Moves.leafBlade, 90);
}
}
if (generation >= 5 && generationOfPokemon() < 5) {
// Bind => 85% accuracy (gen1-4)
updateMoveAccuracy(moves, Moves.bind, 85);
// Jump Kick => 10 pp, 100 power (gen1-4)
updateMovePP(moves, Moves.jumpKick, 10);
updateMovePower(moves, Moves.jumpKick, 100);
// Tackle => 50 power, 100% accuracy , gen1-4
updateMovePower(moves, Moves.tackle, 50);
updateMoveAccuracy(moves, Moves.tackle, 100);
// Wrap => 90% accuracy (gen1-4)
updateMoveAccuracy(moves, Moves.wrap, 90);
// Thrash => 120 power, 10pp (gen1-4)
updateMovePP(moves, Moves.thrash, 10);
updateMovePower(moves, Moves.thrash, 120);
// Disable => 100% accuracy (gen1-4)
updateMoveAccuracy(moves, Moves.disable, 100);
// Petal Dance => 120power, 10pp (gen1-4)
updateMovePP(moves, Moves.petalDance, 10);
updateMovePower(moves, Moves.petalDance, 120);
// Fire Spin => 35 power, 85% acc (gen1-4)
updateMoveAccuracy(moves, Moves.fireSpin, 85);
updateMovePower(moves, Moves.fireSpin, 35);
// Toxic => 90% accuracy (gen1-4)
updateMoveAccuracy(moves, Moves.toxic, 90);
// Clamp => 15pp, 85% acc (gen1-4)
updateMoveAccuracy(moves, Moves.clamp, 85);
updateMovePP(moves, Moves.clamp, 15);
// HJKick => 130 power, 10pp (gen1-4)
updateMovePP(moves, Moves.highJumpKick, 10);
updateMovePower(moves, Moves.highJumpKick, 130);
// Glare => 90% acc (gen1-4)
updateMoveAccuracy(moves, Moves.glare, 90);
// Poison Gas => 80% acc (gen1-4)
updateMoveAccuracy(moves, Moves.poisonGas, 80);
// Crabhammer => 90% acc (gen1-4)
updateMoveAccuracy(moves, Moves.crabhammer, 90);
if (generationOfPokemon() >= 2) {
// Curse => GHOST (gen2-4)
updateMoveType(moves, Moves.curse, Type.GHOST);
// Cotton Spore => 100% acc (gen2-4)
updateMoveAccuracy(moves, Moves.cottonSpore, 100);
// Scary Face => 100% acc (gen2-4)
updateMoveAccuracy(moves, Moves.scaryFace, 100);
// Bone Rush => 90% acc (gen2-4)
updateMoveAccuracy(moves, Moves.boneRush, 90);
// Giga Drain => 75 power (gen2-4)
updateMovePower(moves, Moves.gigaDrain, 75);
// Fury Cutter => 20 power (gen2-4)
updateMovePower(moves, Moves.furyCutter, 20);
// Future Sight => 10 pp, 100 power, 100% acc (gen2-4)
updateMovePP(moves, Moves.futureSight, 10);
updateMovePower(moves, Moves.futureSight, 100);
updateMoveAccuracy(moves, Moves.futureSight, 100);
// Whirlpool => 35 pow, 85% acc (gen2-4)
updateMovePower(moves, Moves.whirlpool, 35);
updateMoveAccuracy(moves, Moves.whirlpool, 85);
}
if (generationOfPokemon() >= 3) {
// Uproar => 90 power (gen3-4)
updateMovePower(moves, Moves.uproar, 90);
// Sand Tomb => 35 pow, 85% acc (gen3-4)
updateMovePower(moves, Moves.sandTomb, 35);
updateMoveAccuracy(moves, Moves.sandTomb, 85);
// Bullet Seed => 25 power (gen3-4)
updateMovePower(moves, Moves.bulletSeed, 25);
// Icicle Spear => 25 power (gen3-4)
updateMovePower(moves, Moves.icicleSpear, 25);
// Covet => 60 power (gen3-4)
updateMovePower(moves, Moves.covet, 60);
// Rock Blast => 90% acc (gen3-4)
updateMoveAccuracy(moves, Moves.rockBlast, 90);
// Doom Desire => 140 pow, 100% acc, gen3-4
updateMovePower(moves, Moves.doomDesire, 140);
updateMoveAccuracy(moves, Moves.doomDesire, 100);
}
if (generationOfPokemon() == 4) {
// Feint => 30 pow
updateMovePower(moves, Moves.feint, 30);
// Last Resort => 140 pow
updateMovePower(moves, Moves.lastResort, 140);
// Drain Punch => 10 pp, 75 pow
updateMovePP(moves, Moves.drainPunch, 10);
updateMovePower(moves, Moves.drainPunch, 75);
// Magma Storm => 75% acc
updateMoveAccuracy(moves, Moves.magmaStorm, 75);
}
}
if (generation >= 6 && generationOfPokemon() < 6) {
// gen 1
// Swords Dance 20 PP
updateMovePP(moves, Moves.swordsDance, 20);
// Whirlwind can't miss
updateMoveAccuracy(moves, Moves.whirlwind, perfectAccuracy);
// Vine Whip 25 PP, 45 Power
updateMovePP(moves, Moves.vineWhip, 25);
updateMovePower(moves, Moves.vineWhip, 45);
// Pin Missile 25 Power, 95% Accuracy
updateMovePower(moves, Moves.pinMissile, 25);
updateMoveAccuracy(moves, Moves.pinMissile, 95);
// Flamethrower 90 Power
updateMovePower(moves, Moves.flamethrower, 90);
// Hydro Pump 110 Power
updateMovePower(moves, Moves.hydroPump, 110);
// Surf 90 Power
updateMovePower(moves, Moves.surf, 90);
// Ice Beam 90 Power
updateMovePower(moves, Moves.iceBeam, 90);
// Blizzard 110 Power
updateMovePower(moves, Moves.blizzard, 110);
// Growth 20 PP
updateMovePP(moves, Moves.growth, 20);
// Thunderbolt 90 Power
updateMovePower(moves, Moves.thunderbolt, 90);
// Thunder 110 Power
updateMovePower(moves, Moves.thunder, 110);
// Minimize 10 PP
updateMovePP(moves, Moves.minimize, 10);
// Barrier 20 PP
updateMovePP(moves, Moves.barrier, 20);
// Lick 30 Power
updateMovePower(moves, Moves.lick, 30);
// Smog 30 Power
updateMovePower(moves, Moves.smog, 30);
// Fire Blast 110 Power
updateMovePower(moves, Moves.fireBlast, 110);
// Skull Bash 10 PP, 130 Power
updateMovePP(moves, Moves.skullBash, 10);
updateMovePower(moves, Moves.skullBash, 130);
// Glare 100% Accuracy
updateMoveAccuracy(moves, Moves.glare, 100);
// Poison Gas 90% Accuracy
updateMoveAccuracy(moves, Moves.poisonGas, 90);
// Bubble 40 Power
updateMovePower(moves, Moves.bubble, 40);
// Psywave 100% Accuracy
updateMoveAccuracy(moves, Moves.psywave, 100);
// Acid Armor 20 PP
updateMovePP(moves, Moves.acidArmor, 20);
// Crabhammer 100 Power
updateMovePower(moves, Moves.crabhammer, 100);
if (generationOfPokemon() >= 2) {
// Thief 25 PP, 60 Power
updateMovePP(moves, Moves.thief, 25);
updateMovePower(moves, Moves.thief, 60);
// Snore 50 Power
updateMovePower(moves, Moves.snore, 50);
// Fury Cutter 40 Power
updateMovePower(moves, Moves.furyCutter, 40);
// Future Sight 120 Power
updateMovePower(moves, Moves.futureSight, 120);
}
if (generationOfPokemon() >= 3) {
// Heat Wave 95 Power
updateMovePower(moves, Moves.heatWave, 95);
// Will-o-Wisp 85% Accuracy
updateMoveAccuracy(moves, Moves.willOWisp, 85);
// Smellingsalt 70 Power
updateMovePower(moves, Moves.smellingSalts, 70);
// Knock off 65 Power
updateMovePower(moves, Moves.knockOff, 65);
// Meteor Mash 90 Power, 90% Accuracy
updateMovePower(moves, Moves.meteorMash, 90);
updateMoveAccuracy(moves, Moves.meteorMash, 90);
// Air Cutter 60 Power
updateMovePower(moves, Moves.airCutter, 60);
// Overheat 130 Power
updateMovePower(moves, Moves.overheat, 130);
// Rock Tomb 15 PP, 60 Power, 95% Accuracy
updateMovePP(moves, Moves.rockTomb, 15);
updateMovePower(moves, Moves.rockTomb, 60);
updateMoveAccuracy(moves, Moves.rockTomb, 95);
// Extrasensory 20 PP
updateMovePP(moves, Moves.extrasensory, 20);
// Muddy Water 90 Power
updateMovePower(moves, Moves.muddyWater, 90);
// Covet 25 PP
updateMovePP(moves, Moves.covet, 25);
}
if (generationOfPokemon() >= 4) {
// Wake-Up Slap 70 Power
updateMovePower(moves, Moves.wakeUpSlap, 70);
// Tailwind 15 PP
updateMovePP(moves, Moves.tailwind, 15);
// Assurance 60 Power
updateMovePower(moves, Moves.assurance, 60);
// Psycho Shift 100% Accuracy
updateMoveAccuracy(moves, Moves.psychoShift, 100);
// Aura Sphere 80 Power
updateMovePower(moves, Moves.auraSphere, 80);
// Air Slash 15 PP
updateMovePP(moves, Moves.airSlash, 15);
// Dragon Pulse 85 Power
updateMovePower(moves, Moves.dragonPulse, 85);
// Power Gem 80 Power
updateMovePower(moves, Moves.powerGem, 80);
// Energy Ball 90 Power
updateMovePower(moves, Moves.energyBall, 90);
// Draco Meteor 130 Power
updateMovePower(moves, Moves.dracoMeteor, 130);
// Leaf Storm 130 Power
updateMovePower(moves, Moves.leafStorm, 130);
// Gunk Shot 80% Accuracy
updateMoveAccuracy(moves, Moves.gunkShot, 80);
// Chatter 65 Power
updateMovePower(moves, Moves.chatter, 65);
// Magma Storm 100 Power
updateMovePower(moves, Moves.magmaStorm, 100);
}
if (generationOfPokemon() == 5) {
// Synchronoise 120 Power
updateMovePower(moves, Moves.synchronoise, 120);
// Low Sweep 65 Power
updateMovePower(moves, Moves.lowSweep, 65);
// Hex 65 Power
updateMovePower(moves, Moves.hex, 65);
// Incinerate 60 Power
updateMovePower(moves, Moves.incinerate, 60);
// Pledges 80 Power
updateMovePower(moves, Moves.waterPledge, 80);
updateMovePower(moves, Moves.firePledge, 80);
updateMovePower(moves, Moves.grassPledge, 80);
// Struggle Bug 50 Power
updateMovePower(moves, Moves.struggleBug, 50);
// Frost Breath and Storm Throw 45 Power
// Crits are 2x in these games, so we need to multiply BP by 3/4
// Storm Throw was also updated to have a base BP of 60
updateMovePower(moves, Moves.frostBreath, 45);
updateMovePower(moves, Moves.stormThrow, 45);
// Sacred Sword 15 PP
updateMovePP(moves, Moves.sacredSword, 15);
// Hurricane 110 Power
updateMovePower(moves, Moves.hurricane, 110);
// Techno Blast 120 Power
updateMovePower(moves, Moves.technoBlast, 120);
}
}
if (generation >= 7 && generationOfPokemon() < 7) {
// Leech Life 80 Power, 10 PP
updateMovePower(moves, Moves.leechLife, 80);
updateMovePP(moves, Moves.leechLife, 10);
// Submission 20 PP
updateMovePP(moves, Moves.submission, 20);
// Tackle 40 Power
updateMovePower(moves, Moves.tackle, 40);
// Thunder Wave 90% Accuracy
updateMoveAccuracy(moves, Moves.thunderWave, 90);
if (generationOfPokemon() >= 2) {
// Swagger 85% Accuracy
updateMoveAccuracy(moves, Moves.swagger, 85);
}
if (generationOfPokemon() >= 3) {
// Knock Off 20 PP
updateMovePP(moves, Moves.knockOff, 20);
}
if (generationOfPokemon() >= 4) {
// Dark Void 50% Accuracy
updateMoveAccuracy(moves, Moves.darkVoid, 50);
// Sucker Punch 70 Power
updateMovePower(moves, Moves.suckerPunch, 70);
}
if (generationOfPokemon() == 6) {
// Aromatic Mist can't miss
updateMoveAccuracy(moves, Moves.aromaticMist, perfectAccuracy);
// Fell Stinger 50 Power
updateMovePower(moves, Moves.fellStinger, 50);
// Flying Press 100 Power
updateMovePower(moves, Moves.flyingPress, 100);
// Mat Block 10 PP
updateMovePP(moves, Moves.matBlock, 10);
// Mystical Fire 75 Power
updateMovePower(moves, Moves.mysticalFire, 75);
// Parabolic Charge 65 Power
updateMovePower(moves, Moves.parabolicCharge, 65);
// Topsy-Turvy can't miss
updateMoveAccuracy(moves, Moves.topsyTurvy, perfectAccuracy);
// Water Shuriken Special
updateMoveCategory(moves, Moves.waterShuriken, MoveCategory.SPECIAL);
}
}
if (generation >= 8 && generationOfPokemon() < 8) {
if (generationOfPokemon() >= 2) {
// Rapid Spin 50 Power
updateMovePower(moves, Moves.rapidSpin, 50);
}
if (generationOfPokemon() == 7) {
// Multi-Attack 120 Power
updateMovePower(moves, Moves.multiAttack, 120);
}
}
if (generation >= 9 && generationOfPokemon() < 9) {
// Gen 1
// Recover 5 PP
updateMovePP(moves, Moves.recover, 5);
// Soft-Boiled 5 PP
updateMovePP(moves, Moves.softBoiled, 5);
// Rest 5 PP
updateMovePP(moves, Moves.rest, 5);
if (generationOfPokemon() >= 2) {
// Milk Drink 5 PP
updateMovePP(moves, Moves.milkDrink, 5);
}
if (generationOfPokemon() >= 3) {
// Slack Off 5 PP
updateMovePP(moves, Moves.slackOff, 5);
}
if (generationOfPokemon() >= 4) {
// Roost 5 PP
updateMovePP(moves, Moves.roost, 5);
}
if (generationOfPokemon() >= 7) {
// Shore Up 5 PP
updateMovePP(moves, Moves.shoreUp, 5);
}
if (generationOfPokemon() >= 8) {
// Grassy Glide 60 Power
updateMovePower(moves, Moves.grassyGlide, 60);
// Wicked Blow 75 Power
updateMovePower(moves, Moves.wickedBlow, 75);
// Glacial Lance 120 Power
updateMovePower(moves, Moves.glacialLance, 120);
}
}
}
private Map moveUpdates;
@Override
public void initMoveUpdates() {
moveUpdates = new TreeMap<>();
}
@Override
public Map getMoveUpdates() {
return moveUpdates;
}
@Override
public void randomizeMovesLearnt(Settings settings) {
boolean typeThemed = settings.getMovesetsMod() == Settings.MovesetsMod.RANDOM_PREFER_SAME_TYPE;
boolean noBroken = settings.isBlockBrokenMovesetMoves();
boolean forceStartingMoves = supportsFourStartingMoves() && settings.isStartWithGuaranteedMoves();
int forceStartingMoveCount = settings.getGuaranteedMoveCount();
double goodDamagingPercentage =
settings.isMovesetsForceGoodDamaging() ? settings.getMovesetsGoodDamagingPercent() / 100.0 : 0;
boolean evolutionMovesForAll = settings.isEvolutionMovesForAll();
// Get current sets
Map> movesets = this.getMovesLearnt();
// Build sets of moves
List validMoves = new ArrayList<>();
List validDamagingMoves = new ArrayList<>();
Map> validTypeMoves = new HashMap<>();
Map> validTypeDamagingMoves = new HashMap<>();
createSetsOfMoves(noBroken, validMoves, validDamagingMoves, validTypeMoves, validTypeDamagingMoves);
for (Integer pkmnNum : movesets.keySet()) {
List learnt = new ArrayList<>();
List moves = movesets.get(pkmnNum);
int lv1AttackingMove = 0;
Pokemon pkmn = findPokemonInPoolWithSpeciesID(mainPokemonListInclFormes, pkmnNum);
if (pkmn == null) {
continue;
}
double atkSpAtkRatio = pkmn.getAttackSpecialAttackRatio();
// 4 starting moves?
if (forceStartingMoves) {
int lv1count = 0;
for (MoveLearnt ml : moves) {
if (ml.level == 1) {
lv1count++;
}
}
if (lv1count < forceStartingMoveCount) {
for (int i = 0; i < forceStartingMoveCount - lv1count; i++) {
MoveLearnt fakeLv1 = new MoveLearnt();
fakeLv1.level = 1;
fakeLv1.move = 0;
moves.add(0, fakeLv1);
}
}
}
if (evolutionMovesForAll) {
if (moves.get(0).level != 0) {
MoveLearnt fakeEvoMove = new MoveLearnt();
fakeEvoMove.level = 0;
fakeEvoMove.move = 0;
moves.add(0, fakeEvoMove);
}
}
if (pkmn.actuallyCosmetic) {
for (int i = 0; i < moves.size(); i++) {
moves.get(i).move = movesets.get(pkmn.baseForme.number).get(i).move;
}
continue;
}
// Find last lv1 move
// lv1index ends up as the index of the first non-lv1 move
int lv1index = moves.get(0).level == 1 ? 0 : 1; // Evolution move handling (level 0 = evo move)
while (lv1index < moves.size() && moves.get(lv1index).level == 1) {
lv1index++;
}
// last lv1 move is 1 before lv1index
if (lv1index != 0) {
lv1index--;
}
// Force a certain amount of good damaging moves depending on the percentage
int goodDamagingLeft = (int)Math.round(goodDamagingPercentage * moves.size());
// Replace moves as needed
for (int i = 0; i < moves.size(); i++) {
// should this move be forced damaging?
boolean attemptDamaging = i == lv1index || goodDamagingLeft > 0;
// type themed?
Type typeOfMove = null;
if (typeThemed) {
double picked = random.nextDouble();
if ((pkmn.primaryType == Type.NORMAL && pkmn.secondaryType != null) ||
(pkmn.secondaryType == Type.NORMAL)) {
Type otherType = pkmn.primaryType == Type.NORMAL ? pkmn.secondaryType : pkmn.primaryType;
// Normal/OTHER: 10% normal, 30% other, 60% random
if (picked < 0.1) {
typeOfMove = Type.NORMAL;
} else if (picked < 0.4) {
typeOfMove = otherType;
}
// else random
} else if (pkmn.secondaryType != null) {
// Primary/Secondary: 20% primary, 20% secondary, 60% random
if (picked < 0.2) {
typeOfMove = pkmn.primaryType;
} else if (picked < 0.4) {
typeOfMove = pkmn.secondaryType;
}
// else random
} else {
// Primary/None: 40% primary, 60% random
if (picked < 0.4) {
typeOfMove = pkmn.primaryType;
}
// else random
}
}
// select a list to pick a move from that has at least one free
List pickList = validMoves;
if (attemptDamaging) {
if (typeOfMove != null) {
if (validTypeDamagingMoves.containsKey(typeOfMove)
&& checkForUnusedMove(validTypeDamagingMoves.get(typeOfMove), learnt)) {
pickList = validTypeDamagingMoves.get(typeOfMove);
} else if (checkForUnusedMove(validDamagingMoves, learnt)) {
pickList = validDamagingMoves;
}
} else if (checkForUnusedMove(validDamagingMoves, learnt)) {
pickList = validDamagingMoves;
}
MoveCategory forcedCategory = random.nextDouble() < atkSpAtkRatio ? MoveCategory.PHYSICAL : MoveCategory.SPECIAL;
List filteredList = pickList.stream().filter(mv -> mv.category == forcedCategory).collect(Collectors.toList());
if (!filteredList.isEmpty() && checkForUnusedMove(filteredList, learnt)) {
pickList = filteredList;
}
} else if (typeOfMove != null) {
if (validTypeMoves.containsKey(typeOfMove)
&& checkForUnusedMove(validTypeMoves.get(typeOfMove), learnt)) {
pickList = validTypeMoves.get(typeOfMove);
}
}
// now pick a move until we get a valid one
Move mv = pickList.get(random.nextInt(pickList.size()));
while (learnt.contains(mv.number)) {
mv = pickList.get(random.nextInt(pickList.size()));
}
if (i == lv1index) {
lv1AttackingMove = mv.number;
} else {
goodDamagingLeft--;
}
learnt.add(mv.number);
}
Collections.shuffle(learnt, random);
if (learnt.get(lv1index) != lv1AttackingMove) {
for (int i = 0; i < learnt.size(); i++) {
if (learnt.get(i) == lv1AttackingMove) {
learnt.set(i, learnt.get(lv1index));
learnt.set(lv1index, lv1AttackingMove);
break;
}
}
}
// write all moves for the pokemon
for (int i = 0; i < learnt.size(); i++) {
moves.get(i).move = learnt.get(i);
if (i == lv1index) {
// just in case, set this to lv1
moves.get(i).level = 1;
}
}
}
// Done, save
this.setMovesLearnt(movesets);
}
@Override
public void randomizeEggMoves(Settings settings) {
boolean typeThemed = settings.getMovesetsMod() == Settings.MovesetsMod.RANDOM_PREFER_SAME_TYPE;
boolean noBroken = settings.isBlockBrokenMovesetMoves();
double goodDamagingPercentage =
settings.isMovesetsForceGoodDamaging() ? settings.getMovesetsGoodDamagingPercent() / 100.0 : 0;
// Get current sets
Map> movesets = this.getEggMoves();
// Build sets of moves
List validMoves = new ArrayList<>();
List validDamagingMoves = new ArrayList<>();
Map> validTypeMoves = new HashMap<>();
Map> validTypeDamagingMoves = new HashMap<>();
createSetsOfMoves(noBroken, validMoves, validDamagingMoves, validTypeMoves, validTypeDamagingMoves);
for (Integer pkmnNum : movesets.keySet()) {
List learnt = new ArrayList<>();
List moves = movesets.get(pkmnNum);
Pokemon pkmn = findPokemonInPoolWithSpeciesID(mainPokemonListInclFormes, pkmnNum);
if (pkmn == null) {
continue;
}
double atkSpAtkRatio = pkmn.getAttackSpecialAttackRatio();
if (pkmn.actuallyCosmetic) {
for (int i = 0; i < moves.size(); i++) {
moves.set(i, movesets.get(pkmn.baseForme.number).get(i));
}
continue;
}
// Force a certain amount of good damaging moves depending on the percentage
int goodDamagingLeft = (int)Math.round(goodDamagingPercentage * moves.size());
// Replace moves as needed
for (int i = 0; i < moves.size(); i++) {
// should this move be forced damaging?
boolean attemptDamaging = goodDamagingLeft > 0;
// type themed?
Type typeOfMove = null;
if (typeThemed) {
double picked = random.nextDouble();
if ((pkmn.primaryType == Type.NORMAL && pkmn.secondaryType != null) ||
(pkmn.secondaryType == Type.NORMAL)) {
Type otherType = pkmn.primaryType == Type.NORMAL ? pkmn.secondaryType : pkmn.primaryType;
// Normal/OTHER: 10% normal, 30% other, 60% random
if (picked < 0.1) {
typeOfMove = Type.NORMAL;
} else if (picked < 0.4) {
typeOfMove = otherType;
}
// else random
} else if (pkmn.secondaryType != null) {
// Primary/Secondary: 20% primary, 20% secondary, 60% random
if (picked < 0.2) {
typeOfMove = pkmn.primaryType;
} else if (picked < 0.4) {
typeOfMove = pkmn.secondaryType;
}
// else random
} else {
// Primary/None: 40% primary, 60% random
if (picked < 0.4) {
typeOfMove = pkmn.primaryType;
}
// else random
}
}
// select a list to pick a move from that has at least one free
List pickList = validMoves;
if (attemptDamaging) {
if (typeOfMove != null) {
if (validTypeDamagingMoves.containsKey(typeOfMove)
&& checkForUnusedMove(validTypeDamagingMoves.get(typeOfMove), learnt)) {
pickList = validTypeDamagingMoves.get(typeOfMove);
} else if (checkForUnusedMove(validDamagingMoves, learnt)) {
pickList = validDamagingMoves;
}
} else if (checkForUnusedMove(validDamagingMoves, learnt)) {
pickList = validDamagingMoves;
}
MoveCategory forcedCategory = random.nextDouble() < atkSpAtkRatio ? MoveCategory.PHYSICAL : MoveCategory.SPECIAL;
List filteredList = pickList.stream().filter(mv -> mv.category == forcedCategory).collect(Collectors.toList());
if (!filteredList.isEmpty() && checkForUnusedMove(filteredList, learnt)) {
pickList = filteredList;
}
} else if (typeOfMove != null) {
if (validTypeMoves.containsKey(typeOfMove)
&& checkForUnusedMove(validTypeMoves.get(typeOfMove), learnt)) {
pickList = validTypeMoves.get(typeOfMove);
}
}
// now pick a move until we get a valid one
Move mv = pickList.get(random.nextInt(pickList.size()));
while (learnt.contains(mv.number)) {
mv = pickList.get(random.nextInt(pickList.size()));
}
goodDamagingLeft--;
learnt.add(mv.number);
}
// write all moves for the pokemon
Collections.shuffle(learnt, random);
for (int i = 0; i < learnt.size(); i++) {
moves.set(i, learnt.get(i));
}
}
// Done, save
this.setEggMoves(movesets);
}
private void createSetsOfMoves(boolean noBroken, List validMoves, List validDamagingMoves,
Map> validTypeMoves, Map> validTypeDamagingMoves) {
List allMoves = this.getMoves();
List hms = this.getHMMoves();
Set allBanned = new HashSet(noBroken ? this.getGameBreakingMoves() : Collections.EMPTY_SET);
allBanned.addAll(hms);
allBanned.addAll(this.getMovesBannedFromLevelup());
allBanned.addAll(GlobalConstants.zMoves);
allBanned.addAll(this.getIllegalMoves());
for (Move mv : allMoves) {
if (mv != null && !GlobalConstants.bannedRandomMoves[mv.number] && !allBanned.contains(mv.number)) {
validMoves.add(mv);
if (mv.type != null) {
if (!validTypeMoves.containsKey(mv.type)) {
validTypeMoves.put(mv.type, new ArrayList<>());
}
validTypeMoves.get(mv.type).add(mv);
}
if (!GlobalConstants.bannedForDamagingMove[mv.number]) {
if (mv.isGoodDamaging(perfectAccuracy)) {
validDamagingMoves.add(mv);
if (mv.type != null) {
if (!validTypeDamagingMoves.containsKey(mv.type)) {
validTypeDamagingMoves.put(mv.type, new ArrayList<>());
}
validTypeDamagingMoves.get(mv.type).add(mv);
}
}
}
}
}
Map avgTypePowers = new TreeMap<>();
double totalAvgPower = 0;
for (Type type: validTypeMoves.keySet()) {
List typeMoves = validTypeMoves.get(type);
int attackingSum = 0;
for (Move typeMove: typeMoves) {
if (typeMove.power > 0) {
attackingSum += (typeMove.power * typeMove.hitCount);
}
}
double avgTypePower = (double)attackingSum / (double)typeMoves.size();
avgTypePowers.put(type, avgTypePower);
totalAvgPower += (avgTypePower);
}
totalAvgPower /= (double)validTypeMoves.keySet().size();
// Want the average power of each type to be within 25% both directions
double minAvg = totalAvgPower * 0.75;
double maxAvg = totalAvgPower * 1.25;
// Add extra moves to type lists outside of the range to balance the average power of each type
for (Type type: avgTypePowers.keySet()) {
double avgPowerForType = avgTypePowers.get(type);
List typeMoves = validTypeMoves.get(type);
List alreadyPicked = new ArrayList<>();
int iterLoops = 0;
while (avgPowerForType < minAvg && iterLoops < 10000) {
final double finalAvgPowerForType = avgPowerForType;
List strongerThanAvgTypeMoves = typeMoves
.stream()
.filter(mv -> mv.power * mv.hitCount > finalAvgPowerForType)
.collect(Collectors.toList());
if (strongerThanAvgTypeMoves.isEmpty()) break;
if (alreadyPicked.containsAll(strongerThanAvgTypeMoves)) {
alreadyPicked = new ArrayList<>();
} else {
strongerThanAvgTypeMoves.removeAll(alreadyPicked);
}
Move extraMove = strongerThanAvgTypeMoves.get(random.nextInt(strongerThanAvgTypeMoves.size()));
avgPowerForType = (avgPowerForType * typeMoves.size() + extraMove.power * extraMove.hitCount)
/ (typeMoves.size() + 1);
typeMoves.add(extraMove);
alreadyPicked.add(extraMove);
iterLoops++;
}
iterLoops = 0;
while (avgPowerForType > maxAvg && iterLoops < 10000) {
final double finalAvgPowerForType = avgPowerForType;
List weakerThanAvgTypeMoves = typeMoves
.stream()
.filter(mv -> mv.power * mv.hitCount < finalAvgPowerForType)
.collect(Collectors.toList());
if (weakerThanAvgTypeMoves.isEmpty()) break;
if (alreadyPicked.containsAll(weakerThanAvgTypeMoves)) {
alreadyPicked = new ArrayList<>();
} else {
weakerThanAvgTypeMoves.removeAll(alreadyPicked);
}
Move extraMove = weakerThanAvgTypeMoves.get(random.nextInt(weakerThanAvgTypeMoves.size()));
avgPowerForType = (avgPowerForType * typeMoves.size() + extraMove.power * extraMove.hitCount)
/ (typeMoves.size() + 1);
typeMoves.add(extraMove);
alreadyPicked.add(extraMove);
iterLoops++;
}
}
}
@Override
public void orderDamagingMovesByDamage() {
Map> movesets = this.getMovesLearnt();
List allMoves = this.getMoves();
for (Integer pkmn : movesets.keySet()) {
List moves = movesets.get(pkmn);
// Build up a list of damaging moves and their positions
List damagingMoveIndices = new ArrayList<>();
List damagingMoves = new ArrayList<>();
for (int i = 0; i < moves.size(); i++) {
if (moves.get(i).level == 0) continue; // Don't reorder evolution move
Move mv = allMoves.get(moves.get(i).move);
if (mv.power > 1) {
// considered a damaging move for this purpose
damagingMoveIndices.add(i);
damagingMoves.add(mv);
}
}
// Ties should be sorted randomly, so shuffle the list first.
Collections.shuffle(damagingMoves, random);
// Sort the damaging moves by power
damagingMoves.sort(Comparator.comparingDouble(m -> m.power * m.hitCount));
// Reassign damaging moves in the ordered positions
for (int i = 0; i < damagingMoves.size(); i++) {
moves.get(damagingMoveIndices.get(i)).move = damagingMoves.get(i).number;
}
}
// Done, save
this.setMovesLearnt(movesets);
}
@Override
public void metronomeOnlyMode() {
// movesets
Map> movesets = this.getMovesLearnt();
MoveLearnt metronomeML = new MoveLearnt();
metronomeML.level = 1;
metronomeML.move = Moves.metronome;
for (List ms : movesets.values()) {
if (ms != null && ms.size() > 0) {
ms.clear();
ms.add(metronomeML);
}
}
this.setMovesLearnt(movesets);
// trainers
// run this to remove all custom non-Metronome moves
List trainers = this.getTrainers();
for (Trainer t : trainers) {
for (TrainerPokemon tpk : t.pokemon) {
tpk.resetMoves = true;
}
}
this.setTrainers(trainers, false);
// tms
List tmMoves = this.getTMMoves();
for (int i = 0; i < tmMoves.size(); i++) {
tmMoves.set(i, Moves.metronome);
}
this.setTMMoves(tmMoves);
// movetutors
if (this.hasMoveTutors()) {
List mtMoves = this.getMoveTutorMoves();
for (int i = 0; i < mtMoves.size(); i++) {
mtMoves.set(i, Moves.metronome);
}
this.setMoveTutorMoves(mtMoves);
}
// move tweaks
List moveData = this.getMoves();
Move metronome = moveData.get(Moves.metronome);
metronome.pp = 40;
List hms = this.getHMMoves();
for (int hm : hms) {
Move thisHM = moveData.get(hm);
thisHM.pp = 0;
}
}
@Override
public void customStarters(Settings settings) {
boolean abilitiesUnchanged = settings.getAbilitiesMod() == Settings.AbilitiesMod.UNCHANGED;
int[] customStarters = settings.getCustomStarters();
boolean allowAltFormes = settings.isAllowStarterAltFormes();
boolean banIrregularAltFormes = settings.isBanIrregularAltFormes();
List romPokemon = getPokemonInclFormes()
.stream()
.filter(pk -> pk == null || !pk.actuallyCosmetic)
.collect(Collectors.toList());
List banned = getBannedFormesForPlayerPokemon();
pickedStarters = new ArrayList<>();
if (abilitiesUnchanged) {
List abilityDependentFormes = getAbilityDependentFormes();
banned.addAll(abilityDependentFormes);
}
if (banIrregularAltFormes) {
banned.addAll(getIrregularFormes());
}
// loop to add chosen pokemon to banned, preventing it from being a random option.
for (int i = 0; i < customStarters.length; i = i + 1){
if (!(customStarters[i] - 1 == 0)){
banned.add(romPokemon.get(customStarters[i] - 1));
}
}
if (customStarters[0] - 1 == 0){
Pokemon pkmn = allowAltFormes ? randomPokemonInclFormes() : randomPokemon();
while (pickedStarters.contains(pkmn) || banned.contains(pkmn) || pkmn.actuallyCosmetic) {
pkmn = allowAltFormes ? randomPokemonInclFormes() : randomPokemon();
}
pickedStarters.add(pkmn);
} else {
Pokemon pkmn1 = romPokemon.get(customStarters[0] - 1);
pickedStarters.add(pkmn1);
}
if (customStarters[1] - 1 == 0){
Pokemon pkmn = allowAltFormes ? randomPokemonInclFormes() : randomPokemon();
while (pickedStarters.contains(pkmn) || banned.contains(pkmn) || pkmn.actuallyCosmetic) {
pkmn = allowAltFormes ? randomPokemonInclFormes() : randomPokemon();
}
pickedStarters.add(pkmn);
} else {
Pokemon pkmn2 = romPokemon.get(customStarters[1] - 1);
pickedStarters.add(pkmn2);
}
if (isYellow()) {
setStarters(pickedStarters);
} else {
if (customStarters[2] - 1 == 0){
Pokemon pkmn = allowAltFormes ? randomPokemonInclFormes() : randomPokemon();
while (pickedStarters.contains(pkmn) || banned.contains(pkmn) || pkmn.actuallyCosmetic) {
pkmn = allowAltFormes ? randomPokemonInclFormes() : randomPokemon();
}
pickedStarters.add(pkmn);
} else {
Pokemon pkmn3 = romPokemon.get(customStarters[2] - 1);
pickedStarters.add(pkmn3);
}
if (starterCount() > 3) {
for (int i = 3; i < starterCount(); i++) {
Pokemon pkmn = random2EvosPokemon(allowAltFormes);
while (pickedStarters.contains(pkmn)) {
pkmn = random2EvosPokemon(allowAltFormes);
}
pickedStarters.add(pkmn);
}
setStarters(pickedStarters);
} else {
setStarters(pickedStarters);
}
}
}
@Override
public void randomizeStarters(Settings settings) {
boolean abilitiesUnchanged = settings.getAbilitiesMod() == Settings.AbilitiesMod.UNCHANGED;
boolean allowAltFormes = settings.isAllowStarterAltFormes();
boolean banIrregularAltFormes = settings.isBanIrregularAltFormes();
int starterCount = starterCount();
pickedStarters = new ArrayList<>();
List banned = getBannedFormesForPlayerPokemon();
if (abilitiesUnchanged) {
List abilityDependentFormes = getAbilityDependentFormes();
banned.addAll(abilityDependentFormes);
}
if (banIrregularAltFormes) {
banned.addAll(getIrregularFormes());
}
for (int i = 0; i < starterCount; i++) {
Pokemon pkmn = allowAltFormes ? randomPokemonInclFormes() : randomPokemon();
while (pickedStarters.contains(pkmn) || banned.contains(pkmn) || pkmn.actuallyCosmetic) {
pkmn = allowAltFormes ? randomPokemonInclFormes() : randomPokemon();
}
pickedStarters.add(pkmn);
}
setStarters(pickedStarters);
}
@Override
public void randomizeBasicTwoEvosStarters(Settings settings) {
boolean abilitiesUnchanged = settings.getAbilitiesMod() == Settings.AbilitiesMod.UNCHANGED;
boolean allowAltFormes = settings.isAllowStarterAltFormes();
boolean banIrregularAltFormes = settings.isBanIrregularAltFormes();
int starterCount = starterCount();
pickedStarters = new ArrayList<>();
List banned = getBannedFormesForPlayerPokemon();
if (abilitiesUnchanged) {
List abilityDependentFormes = getAbilityDependentFormes();
banned.addAll(abilityDependentFormes);
}
if (banIrregularAltFormes) {
banned.addAll(getIrregularFormes());
}
for (int i = 0; i < starterCount; i++) {
Pokemon pkmn = random2EvosPokemon(allowAltFormes);
while (pickedStarters.contains(pkmn) || banned.contains(pkmn)) {
pkmn = random2EvosPokemon(allowAltFormes);
}
pickedStarters.add(pkmn);
}
setStarters(pickedStarters);
}
@Override
public List getPickedStarters() {
return pickedStarters;
}
@Override
public void randomizeStaticPokemon(Settings settings) {
boolean swapLegendaries = settings.getStaticPokemonMod() == Settings.StaticPokemonMod.RANDOM_MATCHING;
boolean similarStrength = settings.getStaticPokemonMod() == Settings.StaticPokemonMod.SIMILAR_STRENGTH;
boolean limitMainGameLegendaries = settings.isLimitMainGameLegendaries();
boolean limit600 = settings.isLimit600();
boolean allowAltFormes = settings.isAllowStaticAltFormes();
boolean banIrregularAltFormes = settings.isBanIrregularAltFormes();
boolean swapMegaEvos = settings.isSwapStaticMegaEvos();
boolean abilitiesAreRandomized = settings.getAbilitiesMod() == Settings.AbilitiesMod.RANDOMIZE;
int levelModifier = settings.isStaticLevelModified() ? settings.getStaticLevelModifier() : 0;
boolean correctStaticMusic = settings.isCorrectStaticMusic();
// Load
checkPokemonRestrictions();
List currentStaticPokemon = this.getStaticPokemon();
List replacements = new ArrayList<>();
List banned = this.bannedForStaticPokemon();
banned.addAll(this.getBannedFormesForPlayerPokemon());
if (!abilitiesAreRandomized) {
List abilityDependentFormes = getAbilityDependentFormes();
banned.addAll(abilityDependentFormes);
}
if (banIrregularAltFormes) {
banned.addAll(getIrregularFormes());
}
boolean reallySwapMegaEvos = forceSwapStaticMegaEvos() || swapMegaEvos;
Map specialMusicStaticChanges = new HashMap<>();
List changeMusicStatics = new ArrayList<>();
if (correctStaticMusic) {
changeMusicStatics = getSpecialMusicStatics();
}
if (swapLegendaries) {
List