Redo moveset generation

This commit is contained in:
Xavion3 2024-05-06 23:32:38 +10:00
parent f92b563baa
commit 3008776170

View File

@ -16,7 +16,7 @@ import { Gender } from '../data/gender';
import { initMoveAnim, loadMoveAnimAssets } from '../data/battle-anims'; import { initMoveAnim, loadMoveAnimAssets } from '../data/battle-anims';
import { Status, StatusEffect, getRandomStatus } from '../data/status-effect'; import { Status, StatusEffect, getRandomStatus } from '../data/status-effect';
import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from '../data/pokemon-evolutions'; import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from '../data/pokemon-evolutions';
import { reverseCompatibleTms, tmSpecies } from '../data/tms'; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from '../data/tms';
import { DamagePhase, FaintPhase, LearnMovePhase, ObtainStatusEffectPhase, StatChangePhase, SwitchSummonPhase } from '../phases'; import { DamagePhase, FaintPhase, LearnMovePhase, ObtainStatusEffectPhase, StatChangePhase, SwitchSummonPhase } from '../phases';
import { BattleStat } from '../data/battle-stat'; import { BattleStat } from '../data/battle-stat';
import { BattlerTag, BattlerTagLapseType, EncoreTag, HelpingHandTag, HighestStatBoostTag, TypeBoostTag, getBattlerTag } from '../data/battler-tags'; import { BattlerTag, BattlerTagLapseType, EncoreTag, HelpingHandTag, HighestStatBoostTag, TypeBoostTag, getBattlerTag } from '../data/battler-tags';
@ -46,6 +46,8 @@ import { TrainerSlot } from '../data/trainer-config';
import { ABILITY_OVERRIDE, MOVE_OVERRIDE, MOVE_OVERRIDE_2, OPP_ABILITY_OVERRIDE, OPP_MOVE_OVERRIDE, OPP_MOVE_OVERRIDE_2, OPP_SHINY_OVERRIDE, OPP_VARIANT_OVERRIDE } from '../overrides'; import { ABILITY_OVERRIDE, MOVE_OVERRIDE, MOVE_OVERRIDE_2, OPP_ABILITY_OVERRIDE, OPP_MOVE_OVERRIDE, OPP_MOVE_OVERRIDE_2, OPP_SHINY_OVERRIDE, OPP_VARIANT_OVERRIDE } from '../overrides';
import { BerryType } from '../data/berry'; import { BerryType } from '../data/berry';
import i18next from '../plugins/i18n'; import i18next from '../plugins/i18n';
import { speciesEggMoves } from '../data/egg-moves';
import { ModifierTier } from '../modifier/modifier-tier';
export enum FieldPosition { export enum FieldPosition {
CENTER, CENTER,
@ -1110,7 +1112,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
generateAndPopulateMoveset(): void { generateAndPopulateMoveset(): void {
this.moveset = []; this.moveset = [];
let movePool: Moves[] = []; let movePool: [Moves, number][] = [];
const allLevelMoves = this.getLevelMoves(1, true, true); const allLevelMoves = this.getLevelMoves(1, true, true);
if (!allLevelMoves) { if (!allLevelMoves) {
console.log(this.species.speciesId, 'ERROR'); console.log(this.species.speciesId, 'ERROR');
@ -1121,38 +1123,114 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const levelMove = allLevelMoves[m]; const levelMove = allLevelMoves[m];
if (this.level < levelMove[0]) if (this.level < levelMove[0])
break; break;
if (movePool.indexOf(levelMove[1]) === -1) { let weight = levelMove[0];
if (!allMoves[levelMove[1]].name.endsWith(' (N)')) if (weight === 0) // Evo Moves
movePool.push(levelMove[1]); weight = 50;
else if (weight === 1 && allMoves[levelMove[1]].power >= 80) // Assume level 1 moves with 80+ BP are "move reminder" moves and bump their weight
movePool.unshift(levelMove[1]); weight = 40;
if (allMoves[levelMove[1]].name.endsWith(' (N)'))
weight /= 100; // Unimplemented level up moves are possible to generate, but 1% of their normal chance.
if (!movePool.some(m => m[0] === levelMove[1]))
movePool.push([levelMove[1], weight]);
}
if (this.isBoss()) // Bosses never get self ko moves
movePool = movePool.filter(m => !allMoves[m[0]].getAttrs(SacrificialAttr).length);
if (this.hasTrainer()) {
const tms = Object.keys(tmSpecies);
for (let tm of tms) {
const moveId = parseInt(tm) as Moves;
let compatible = false;
for (let p of tmSpecies[tm]) {
if (Array.isArray(p)) {
if (p[0] === this.species.speciesId || (this.fusionSpecies && p[0] === this.fusionSpecies.speciesId) && p.slice(1).indexOf(this.species.forms[this.formIndex]) > -1) {
compatible = true;
break;
}
} else if (p === this.species.speciesId || (this.fusionSpecies && p === this.fusionSpecies.speciesId)) {
compatible = true;
break;
}
}
if (compatible && !movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(' (N)')) {
if (tmPoolTiers[moveId] === ModifierTier.COMMON && this.level >= 15)
movePool.push([moveId, 4]);
else if (tmPoolTiers[moveId] === ModifierTier.GREAT && this.level >= 30)
movePool.push([moveId, 8]);
else if (tmPoolTiers[moveId] === ModifierTier.ULTRA && this.level >= 50)
movePool.push([moveId, 14]);
}
}
if (this.level >= 25) { // No egg moves below level 25
for (let i = 0; i < 3; i++) {
const moveId = speciesEggMoves[this.species.getRootSpeciesId()][i];
if (!movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(' (N)'))
movePool.push([moveId, Math.min(this.level * 0.5, 40)]);
}
const moveId = speciesEggMoves[this.species.getRootSpeciesId()][3];
if (this.level >= 60 && !movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(' (N)')) // No rare egg moves before level 60
movePool.push([moveId, Math.min(this.level * 0.2, 20)]);
if (this.fusionSpecies) {
for (let i = 0; i < 3; i++) {
const moveId = speciesEggMoves[this.fusionSpecies.getRootSpeciesId()][i];
if (!movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(' (N)'))
movePool.push([moveId, Math.min(this.level * 0.5, 40)]);
}
const moveId = speciesEggMoves[this.fusionSpecies.getRootSpeciesId()][3];
if (this.level >= 60 && !movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(' (N)')) // No rare egg moves before level 60
movePool.push([moveId, Math.min(this.level * 0.2, 20)]);
}
} }
} }
let weightMultiplier = 0.9; // The higher this is the more the game weights towards higher level moves. At 0 all moves are equal weight.
if (this.hasTrainer())
weightMultiplier += 0.7;
if (this.isBoss()) if (this.isBoss())
movePool = movePool.filter(m => !allMoves[m].getAttrs(SacrificialAttr).length); weightMultiplier += 0.4;
movePool.reverse(); const baseWeights: [Moves, number][] = movePool.map(m => [m[0], Math.ceil(Math.pow(m[1], weightMultiplier)*100)]);
const attackMovePool = movePool.filter(m => { if (this.hasTrainer() || this.isBoss()) { // Trainers and bosses always force a stab move
const move = allMoves[m]; const stabMovePool = baseWeights.filter(m => allMoves[m[0]].category !== MoveCategory.STATUS && this.isOfType(allMoves[m[0]].type));
return move.category !== MoveCategory.STATUS;
});
const easeType = this.hasTrainer() || this.isBoss() ? this.hasTrainer() && this.isBoss() ? 'Quart.easeIn' : 'Cubic.easeIn' : 'Sine.easeIn'; if (stabMovePool.length) {
const totalWeight = stabMovePool.reduce((v, m) => v + m[1], 0);
if (attackMovePool.length) { let rand = Utils.randSeedInt(totalWeight);
const randomAttackMove = Utils.randSeedEasedWeightedItem(attackMovePool, easeType); let index = 0;
this.moveset.push(new PokemonMove(randomAttackMove, 0, 0)); while (rand > stabMovePool[index][1])
console.log(allMoves[randomAttackMove]); rand -= stabMovePool[index++][1];
movePool.splice(movePool.findIndex(m => m === randomAttackMove), 1); this.moveset.push(new PokemonMove(stabMovePool[index][0], 0, 0));
}
} else { // Normal wild pokemon just force a random damaging move
const attackMovePool = baseWeights.filter(m => allMoves[m[0]].category !== MoveCategory.STATUS);
if (attackMovePool.length) {
const totalWeight = attackMovePool.reduce((v, m) => v + m[1], 0);
let rand = Utils.randSeedInt(totalWeight);
let index = 0;
while (rand > attackMovePool[index][1])
rand -= attackMovePool[index++][1];
this.moveset.push(new PokemonMove(attackMovePool[index][0], 0, 0));
}
} }
while (movePool.length && this.moveset.length < 4) { while (movePool.length > 1 && this.moveset.length < 4) {
const randomMove = Utils.randSeedEasedWeightedItem(movePool, easeType); if (this.hasTrainer()) {
this.moveset.push(new PokemonMove(randomMove, 0, 0)); // Sqrt the weight of any damaging moves with overlapping types
console.log(allMoves[randomMove]); // Other damaging moves 2x weight if 0-1 damaging moves, 0.5x if 2, 0.125x if 3. These weights double if STAB.
movePool.splice(movePool.indexOf(randomMove), 1); // Status moves remain unchanged on weight
movePool = baseWeights.filter(m => !this.moveset.some(mo => m[0] === mo.moveId)).map(m => [m[0], this.moveset.some(mo => mo.getMove().category !== MoveCategory.STATUS && mo.getMove().type === allMoves[m[0]].type) ? Math.ceil(Math.sqrt(m[1])) : allMoves[m[0]].category !== MoveCategory.STATUS ? Math.ceil(m[1]/Math.max(Math.pow(4, this.moveset.filter(mo => mo.getMove().power > 1).length)/8,0.5) * (this.isOfType(allMoves[m[0]].type) ? 2 : 1)) : m[1]]);
} else { // Non-trainer pokemon just use normal weights
movePool = baseWeights.filter(m => !this.moveset.some(mo => m[0] === mo.moveId));
}
const totalWeight = movePool.reduce((v, m) => v + m[1], 0);
let rand = Utils.randSeedInt(totalWeight);
let index = 0;
while (rand > movePool[index][1])
rand -= movePool[index++][1];
this.moveset.push(new PokemonMove(movePool[index][0], 0, 0));
} }
this.scene.triggerPokemonFormChange(this, SpeciesFormChangeMoveLearnedTrigger); this.scene.triggerPokemonFormChange(this, SpeciesFormChangeMoveLearnedTrigger);