Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 36 additions & 119 deletions src/battle-scene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { activeOverrides } from "#app/overrides";
import type { Phase } from "#app/phase";
import { PhaseManager } from "#app/phase-manager";
import { SceneBase } from "#app/scene-base";
import { IV_MAX, IV_MIN, LEVEL_CAP_SCALE_FACTOR } from "#constants/game-constants";
import { LEVEL_CAP_SCALE_FACTOR } from "#constants/game-constants";
import {
ME_ANTI_VARIANCE_WEIGHT_MODIFIER,
ME_AVERAGE_ENCOUNTERS_PER_RUN_TARGET,
Expand All @@ -31,7 +31,7 @@ import { pokemonPreEvolutions } from "#data/pokemon-pre-evolutions";
import type { PokemonSpecies } from "#data/pokemon-species";
import { resetStarterColors, starterColors } from "#data/starter-colors";
import { getTypeRgb } from "#data/type";
import { type Variant, variantData } from "#data/variant";
import { variantData } from "#data/variant";
import type { AchvCategory } from "#enums/achv-category";
import { BattleType } from "#enums/battle-type";
import { BattlerIndex, type FieldBattlerIndex } from "#enums/battler-index";
Expand All @@ -55,15 +55,14 @@ import { SpeciesId } from "#enums/species-id";
import { StatusEffect } from "#enums/status-effect";
import { SwitchType } from "#enums/switch-type";
import { TextStyle } from "#enums/text-style";
import type { TrainerSlot } from "#enums/trainer-slot";
import { TrainerVariant } from "#enums/trainer-variant";
import type { UiWindowStyle } from "#enums/ui-window-style";
import { NewArenaEvent } from "#events/battle-scene";
import { Arena, ArenaBase, getBgTerrainColorRatioForBiome } from "#field/arena";
import { DamageNumberHandler } from "#field/damage-number-handler";
import { EnemyPokemon } from "#field/enemy-pokemon";
import { EnemyPokemon, type EnemyPokemonOptions } from "#field/enemy-pokemon";
import { PlayerPokemon } from "#field/player-pokemon";
import type { Pokemon } from "#field/pokemon";
import type { Pokemon, PokemonOptions } from "#field/pokemon";
import { PokemonSpriteTeraSparkleHandler } from "#field/pokemon-sprite-tera-sparkle-handler";
import { Trainer } from "#field/trainer";
import { SpeciesFormChangeManualTrigger } from "#form-change-triggers/species-form-change-manual-trigger";
Expand Down Expand Up @@ -111,7 +110,6 @@ import { SpritePipeline } from "#pipelines/sprite";
import { type Achievement, achvs } from "#system/achievements";
import { GameData } from "#system/game-data";
import { initGameSpeed } from "#system/game-speed";
import type { PokemonData } from "#system/pokemon-data";
import { settings } from "#system/settings-manager";
import type { TrainerData } from "#system/trainer-data";
import { type Voucher, vouchers } from "#system/voucher";
Expand All @@ -131,7 +129,7 @@ import { addTextObject } from "#ui/text-utils";
import { UI } from "#ui/ui";
import { setDocumentUiTheme, updateWindowStyle } from "#ui/ui-theme";
import { loadCommonAnimAssets } from "#utils/anim-utils";
import { BooleanHolder, enumValueToKey, fixedNumber, isBetween, NumberHolder, ValueHolder } from "#utils/common-utils";
import { BooleanHolder, enumValueToKey, fixedNumber, NumberHolder, ValueHolder } from "#utils/common-utils";
import { getModifierType } from "#utils/modifier-type-utils";
import { loadMoveAnimAssets } from "#utils/move-anim-utils";
import { getPokemonSpecies } from "#utils/pokemon-utils";
Expand Down Expand Up @@ -907,128 +905,50 @@ export class BattleScene extends SceneBase {
return findInParty(this.getPlayerParty()) || findInParty(this.getEnemyParty());
}

addPlayerPokemon(
public addPlayerPokemon(
species: PokemonSpecies,
level: number,
abilityIndex?: number,
formIndex?: number,
gender?: Gender,
shiny?: boolean,
variant?: Variant,
ivs?: number[],
nature?: Nature,
dataSource?: Pokemon | PokemonData,
options: PokemonOptions | Pokemon = {},
postProcess?: (playerPokemon: PlayerPokemon) => void,
): PlayerPokemon {
const pokemon = new PlayerPokemon(
species,
level,
abilityIndex,
formIndex,
gender,
shiny,
variant,
ivs,
nature,
dataSource,
);
const pokemon = new PlayerPokemon(species, level, options);
if (postProcess) {
postProcess(pokemon);
}

if (activeOverrides.IVS_OVERRIDE === null) {
// do nothing
} else if (Array.isArray(activeOverrides.IVS_OVERRIDE)) {
if (activeOverrides.IVS_OVERRIDE.length !== 6) {
throw new Error("The Player IVs override must be an array of length 6 or a number!");
}
if (activeOverrides.IVS_OVERRIDE.some((value) => !isBetween(value, IV_MIN, IV_MAX))) {
throw new Error(`All IVs in the player IV override must be between ${IV_MIN} and ${IV_MAX}!`);
}
pokemon.ivs = activeOverrides.IVS_OVERRIDE;
} else {
if (!isBetween(activeOverrides.IVS_OVERRIDE, IV_MIN, IV_MAX)) {
throw new Error(`The Player IV override must be a value between ${IV_MIN} and ${IV_MAX}!`);
}
pokemon.ivs = new Array(6).fill(activeOverrides.IVS_OVERRIDE);
}

if (activeOverrides.NATURE_OVERRIDE !== null) {
pokemon.nature = activeOverrides.NATURE_OVERRIDE;
}

pokemon.init();
return pokemon;
}

addEnemyPokemon(
species: PokemonSpecies,
level: number,
trainerSlot: TrainerSlot,
boss: boolean = false,
shinyLock: boolean = false,
dataSource?: PokemonData,
options: EnemyPokemonOptions | Pokemon = {},
postProcess?: (enemyPokemon: EnemyPokemon) => void,
): EnemyPokemon {
if (activeOverrides.ENEMY_LEVEL_OVERRIDE > 0) {
level = activeOverrides.ENEMY_LEVEL_OVERRIDE;
}
if (activeOverrides.ENEMY_SPECIES_OVERRIDE) {
species = getPokemonSpecies(activeOverrides.ENEMY_SPECIES_OVERRIDE);

// The fact that a Pokemon is a boss or not can change based on its Species and level
boss = this.getEncounterBossSegments(this.currentBattle.waveIndex, level, species) > 1;

// Ensure the gender from the data source is valid for the overridden species
if (dataSource?.gender != null) {
if (dataSource.gender === Gender.GENDERLESS && species.malePercent != null) {
dataSource.gender = species.malePercent > 0 ? Gender.MALE : Gender.FEMALE;
} else if (dataSource.gender !== Gender.GENDERLESS && species.malePercent == null) {
dataSource.gender = Gender.GENDERLESS;
}
}
}

const pokemon = new EnemyPokemon(species, level, trainerSlot, boss, shinyLock, dataSource);

if (boss && !dataSource) {
const pokemon = new EnemyPokemon(species, level, options);

if (pokemon.boss && options.ivs == null) {
/*
* Boss Pokemon with random IVs have their IVs rolled a second time.
* Their final IV for each stat is the weighted average of the two rolls,
* with the higher roll at 75% weighting.
*
* With the additional roll, Bosses' IVs are higher on average (~23 compared to ~15)
* and have less variance.
*/
const secondaryIvs = pokemon.generateIvs();

for (let s = 0; s < pokemon.ivs.length; s++) {
pokemon.ivs[s] = Math.round(
Phaser.Math.Linear(
Math.min(pokemon.ivs[s], secondaryIvs[s]),
Math.max(pokemon.ivs[s], secondaryIvs[s]),
0.75,
),
);
const ivMin = Math.min(pokemon.ivs[s], secondaryIvs[s]);
const ivMax = Math.max(pokemon.ivs[s], secondaryIvs[s]);
pokemon.ivs[s] = Math.round(0.75 * ivMax + 0.25 * ivMin);
}
}
if (postProcess) {
postProcess(pokemon);
}

if (activeOverrides.ENEMY_IVS_OVERRIDE === null) {
// do nothing
} else if (Array.isArray(activeOverrides.ENEMY_IVS_OVERRIDE)) {
if (activeOverrides.ENEMY_IVS_OVERRIDE.length !== 6) {
throw new Error("The Enemy IVs override must be an array of length 6 or a number!");
}
if (activeOverrides.ENEMY_IVS_OVERRIDE.some((value) => !isBetween(value, IV_MIN, IV_MAX))) {
throw new Error(`All IVs in the enemy IV override must be between ${IV_MIN} and ${IV_MAX}!`);
}
pokemon.ivs = activeOverrides.ENEMY_IVS_OVERRIDE;
} else {
if (!isBetween(activeOverrides.ENEMY_IVS_OVERRIDE, IV_MIN, IV_MAX)) {
throw new Error(`The Enemy IV override must be a value between ${IV_MIN} and ${IV_MAX}!`);
}
pokemon.ivs = new Array(6).fill(activeOverrides.ENEMY_IVS_OVERRIDE);
}

if (activeOverrides.ENEMY_NATURE_OVERRIDE !== null) {
pokemon.nature = activeOverrides.ENEMY_NATURE_OVERRIDE;
}

pokemon.init();
return pokemon;
}
Expand Down Expand Up @@ -1627,19 +1547,16 @@ export class BattleScene extends SceneBase {
species?: PokemonSpecies,
forceBoss: boolean = false,
): number {
if (activeOverrides.ENEMY_HEALTH_SEGMENTS_OVERRIDE > 1) {
return activeOverrides.ENEMY_HEALTH_SEGMENTS_OVERRIDE;
}
if (activeOverrides.ENEMY_HEALTH_SEGMENTS_OVERRIDE === 1) {
// The rest of the code expects to be returned 0 and not 1 if the enemy is not a boss
return 0;
const override = activeOverrides.ENEMY_HEALTH_SEGMENTS_OVERRIDE;
if (override != null) {
return Math.max(0, override);
}

if (this.gameMode.isDaily && this.gameMode.isWaveFinal(waveIndex)) {
return 5;
}

let isBoss: boolean | undefined;
let isBoss: boolean = false;
if (forceBoss || species?.isLegendLike()) {
isBoss = true;
} else {
Expand All @@ -1654,17 +1571,17 @@ export class BattleScene extends SceneBase {
return 0;
}

let ret: number = 2;
let segments: number = 2;

if (level >= 100) {
ret++;
segments++;
}
if (species && species.baseTotal >= 670) {
ret++;
segments++;
}
ret += Math.floor(waveIndex / 250);
segments += Math.floor(waveIndex / 250);

return ret;
return segments;
}

trySpreadPokerus(): void {
Expand Down Expand Up @@ -1960,7 +1877,7 @@ export class BattleScene extends SceneBase {
this.findModifiers((m) => m.isPokemonHeldItemModifier() && m.pokemonId === enemy.id, false).map(
(m) => (scoreIncrease *= (m as PokemonHeldItemModifier).getScoreMultiplier()),
);
if (enemy.isBoss()) {
if (enemy.boss) {
scoreIncrease *= Math.sqrt(enemy.bossSegments);
}
this.currentBattle.battleScore += Math.ceil(scoreIncrease);
Expand Down Expand Up @@ -2346,7 +2263,7 @@ export class BattleScene extends SceneBase {
});
} else {
const isBoss =
enemyPokemon.isBoss()
enemyPokemon.boss
|| (this.currentBattle.battleType === BattleType.TRAINER && !!this.currentBattle.trainer?.config.isBoss);
let upgradeChance = 32;
if (isBoss) {
Expand Down Expand Up @@ -2752,7 +2669,7 @@ export class BattleScene extends SceneBase {
* @param pokemon The (enemy) pokemon
*/
initFinalBossPhaseTwo(pokemon: Pokemon): void {
if (pokemon.isEnemy() && pokemon.isBoss() && !pokemon.formIndex && pokemon.bossSegmentIndex < 1) {
if (pokemon.isEnemy() && pokemon.boss && !pokemon.formIndex && pokemon.bossSegmentIndex < 1) {
this.audioManager.fadeOutBgm(fixedNumber(2000), false);
this.ui.showDialogue(classicFinalBossDialogue.firstStageWin, pokemon.species.name, () => {
const finalBossMBH = getModifierType(modifierTypes.MINI_BLACK_HOLE).newModifier(
Expand Down Expand Up @@ -3006,7 +2923,7 @@ export class BattleScene extends SceneBase {
if (
this.currentBattle.battleType !== BattleType.WILD
|| !pokemon.isEnemy()
|| pokemon.isBoss()
|| pokemon.boss
|| pokemon.level > source.level
) {
return false;
Expand Down
2 changes: 1 addition & 1 deletion src/battle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ export class Battle {
partyMemberTurnMultiplier /= 1.5;
}
for (const p of globalScene.getEnemyParty()) {
if (p.isBoss()) {
if (p.boss) {
partyMemberTurnMultiplier *= p.bossSegments / 1.5 / globalScene.getEnemyParty().length;
}
}
Expand Down
13 changes: 1 addition & 12 deletions src/data/daily-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,7 @@ function getDailyRunStarter(starterSpeciesForm: PokemonSpeciesForm, startingLeve
? (starterSpeciesForm as PokemonSpecies)
: getPokemonSpecies(starterSpeciesForm.speciesId);
const formIndex = starterSpeciesForm.type === "PokemonSpecies" ? undefined : starterSpeciesForm.formIndex;
const pokemon = new PlayerPokemon(
starterSpecies,
startingLevel,
undefined,
formIndex,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
);
const pokemon = new PlayerPokemon(starterSpecies, startingLevel, { formIndex });
const starter: StarterConfig = {
species: starterSpecies,
dexAttr: pokemon.getDexAttr(),
Expand Down
5 changes: 4 additions & 1 deletion src/data/egg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,10 @@ export class Egg {
abilityIndex = 2;
}

ret = globalScene.addPlayerPokemon(pokemonSpecies, 1, abilityIndex, undefined, undefined, false);
ret = globalScene.addPlayerPokemon(pokemonSpecies, 1, {
abilityIndex,
shiny: false,
});
ret.shiny = this._isShiny;
ret.variant = this._variantTier;

Expand Down
2 changes: 1 addition & 1 deletion src/data/moves/move-attrs/add-substitute-attr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class AddSubstituteAttr extends MoveEffectAttr {
}

override getUserBenefitScore(user: Pokemon, _target: Pokemon, _move: Move): number {
if (user.isBoss()) {
if (user.boss) {
return -10;
}
return 5;
Expand Down
2 changes: 1 addition & 1 deletion src/data/moves/move-attrs/half-max-hp-recoil-attr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class HalfMaxHpRecoilAttr extends MoveEffectAttr {
}

override getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
if (user.isBoss()) {
if (user.boss) {
return -10;
}
return Math.ceil(
Expand Down
6 changes: 3 additions & 3 deletions src/data/moves/move-attrs/revival-blessing-attr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
if (user.isEnemy()) {
// If used by an enemy trainer with at least one fainted non-boss Pokemon, this
// revives one of said Pokemon selected at random.
const faintedPokemon = globalScene.getEnemyParty().filter((p) => p.isFainted() && !p.isBoss());
const faintedPokemon = globalScene.getEnemyParty().filter((p) => p.isFainted() && !p.boss);
const pokemon = faintedPokemon[user.randSeedInt(faintedPokemon.length)];
const slotIndex = globalScene.getEnemyParty().findIndex((p) => pokemon.id === p.id);
const { currentBattle, phaseManager } = globalScene;
Expand Down Expand Up @@ -60,11 +60,11 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
override getCondition(): MoveConditionFunc {
return (user, _target, _move) =>
(user.isPlayer() && globalScene.getPlayerParty().some((p) => p.isFainted()))
|| (user.isEnemy() && user.hasTrainer() && globalScene.getEnemyParty().some((p) => p.isFainted() && !p.isBoss()));
|| (user.isEnemy() && user.hasTrainer() && globalScene.getEnemyParty().some((p) => p.isFainted() && !p.boss));
}

override getUserBenefitScore(user: Pokemon, _target: Pokemon, _move: Move): number {
if (user.hasTrainer() && globalScene.getEnemyParty().some((p) => p.isFainted() && !p.isBoss())) {
if (user.hasTrainer() && globalScene.getEnemyParty().some((p) => p.isFainted() && !p.boss)) {
return 20;
}

Expand Down
2 changes: 1 addition & 1 deletion src/data/moves/move-attrs/sacrificial-attr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class SacrificialAttr extends MoveEffectAttr {
}

override getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
if (user.isBoss()) {
if (user.boss) {
return -20;
}
return Math.ceil(((1 - user.getHpRatio()) * 10 - 10) * (target.getAttackTypeEffectiveness(move.type, user) - 0.5));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PokeballType } from "#enums/pokeball-type";
import { SpeciesId } from "#enums/species-id";
import { type BattleStat, Stat } from "#enums/stat";
import { TrainerSlot } from "#enums/trainer-slot";
import { EnemyPokemon } from "#field/enemy-pokemon";
import type { Pokemon } from "#field/pokemon";
import { PokemonMove } from "#field/pokemon-move";
Expand Down Expand Up @@ -376,7 +375,9 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
// Let it have the food
// Greedent joins the team, level equal to 2 below highest party member (shiny locked)
const level = getHighestLevelPlayerPokemon(false, true).level - 2;
const greedent = new EnemyPokemon(getPokemonSpecies(SpeciesId.GREEDENT), level, TrainerSlot.NONE, false, true);
const greedent = new EnemyPokemon(getPokemonSpecies(SpeciesId.GREEDENT), level, {
shiny: false,
});
greedent.setMoveset(MoveId.THRASH, MoveId.BODY_PRESS, MoveId.STUFF_CHEEKS, MoveId.SLACK_OFF);
greedent.passive = true;

Expand Down
Loading