[Refactor] use typescript `strict-null` (#3259)

* TS: enable strict-null

* fix battle-scene.ts

* fix voucher.ts

* adapt more files to strict-null

* adapt more files to strict-null ( 2)

* adapt ability.ts to strict-null

* adapt `arena.ts` to strict-null

* adapt TagAddedEvent constructor to strict-null

* adapt phases.ts.to strict-null

* adapt status-effect.ts to strict-null

* adapt `account.ts` to strict-null

* adapt `configHandler.ts` to strict-null

* adapt `ability.ts` to strict-null

* adapt `biomes.ts` to strict-null

* adapt `challenge.ts` to strict-null

* adapt `daily-run.ts` to strict-null

* adapt `nature.ts` to strict-null

* adapt `pokemon-forms.ts` to strict-null

* adapt `tainer-names.ts` to strict-null

* adapt `types.ts` to strict-null

* adapt `weather.ts` to strict-null

* adapt `egg-hatch-phase.ts` to strict-null

* adapt `evolution-phase.ts` to strict-null

* adapt `pokemon-sprite-sparkle-handler.ts` to strict-null

* adapt `evolution-phase.ts` to strict-null

* adapt `game-mode.ts` to strict-null

* adapt `utils.ts` to strict-null

* adapt `voucher-ui-handler.ts` to strict-null

* adapt `src/ui/unavailable-modal-ui-handler.ts` to strict-null

* adapt `src/ui/ui.ts` to strict-null

* adapt `src/ui/ui-theme.ts` to strict-null

* adapt `src/ui/title-ui-handler.ts` to strict-null

* adapt `src/ui/time-of-day-widget.ts` to strict-null

* adapt `src/ui/text.ts` to strict-null

* adapt `src/ui/target-select-ui-handler.ts` to strict-null

* adapt `src/ui/settings/settings-keyboard-ui-handler.ts` to strict-null

* adapt more files to strict-null (3)

* adapt more files to strict-null (4)

* adapt more files (mostly tests) to strict-null (5)

* adapt more files to strict-null (6)

* adapt more files to strict-null (7)

* Update `src/data/pokemon-evolutions.ts` for strict-null

Partial update `src/data/pokemon-species.ts` for strict-null

* adapt more files to strict-null (8)

* adapt more files to strict-null (9)

* Strict some more nulls (still a few errors remaining)

* adapt rest of the files to strict-null (9)

* fix tests (check for null instead of undefined)

* repalce a lot of `??` with bangs

And added TODO notice as usual

* fix more tests

* all tests pass now

* fix broken game-loop after trainer battle

add some console.warn for missing cases and falling back to default

* remove guessed fallback from utils.rgbHexToRgba

* add TODO for this.currentBattle = null

* adjust   getPokemonById() return to include `null`

* fix compilation errors

* add test for pokemon.trySetStatus

* `chanceMultiplier` shouldn't be optional

* allow `null` for currentPhase

* adjust hasExpSprite logic for no keymatch found

* reduce bang usage in account.updateUserInfo()

* fix new strict-null issues after merge

* fix `strict-null` issues in dropdown.ts

and sand_spit.test.ts

* fix egg-gacha

* adapt gul_missile.test.ts to strict-null

* fix move.ts strict-null

* fix i18n.ts strict-null

* fix strict-null issues

* fix baton_pass test

after accidentially breaking it

* chore: fix compiler errors

* revert accidential changes in baton_pass.test.ts

---------

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
flx-sta 2024-08-07 09:23:12 -07:00 committed by GitHub
parent 555d33c16c
commit a07d2c57a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
204 changed files with 2217 additions and 1956 deletions

3
src/@types/common.ts Normal file
View File

@ -0,0 +1,3 @@
import BattleScene from "#app/battle-scene.js";
export type ConditionFn = (scene: BattleScene, args?: any[]) => boolean;

View File

@ -8,7 +8,7 @@ export interface UserInfo {
googleId: string; googleId: string;
} }
export let loggedInUser: UserInfo = null; export let loggedInUser: UserInfo | null = null;
// This is a random string that is used to identify the client session - unique per session (tab or window) so that the game will only save on the one that the server is expecting // This is a random string that is used to identify the client session - unique per session (tab or window) so that the game will only save on the one that the server is expecting
export const clientSessionId = Utils.randomString(32); export const clientSessionId = Utils.randomString(32);
@ -30,11 +30,13 @@ export function updateUserInfo(): Promise<[boolean, integer]> {
loggedInUser.lastSessionSlot = lastSessionSlot; loggedInUser.lastSessionSlot = lastSessionSlot;
// Migrate old data from before the username was appended // Migrate old data from before the username was appended
[ "data", "sessionData", "sessionData1", "sessionData2", "sessionData3", "sessionData4" ].map(d => { [ "data", "sessionData", "sessionData1", "sessionData2", "sessionData3", "sessionData4" ].map(d => {
if (localStorage.hasOwnProperty(d)) { const lsItem = localStorage.getItem(d);
if (localStorage.hasOwnProperty(`${d}_${loggedInUser.username}`)) { if (lsItem && !!loggedInUser?.username) {
localStorage.setItem(`${d}_${loggedInUser.username}_bak`, localStorage.getItem(`${d}_${loggedInUser.username}`)); const lsUserItem = localStorage.getItem(`${d}_${loggedInUser.username}`);
if (lsUserItem) {
localStorage.setItem(`${d}_${loggedInUser.username}_bak`, lsUserItem);
} }
localStorage.setItem(`${d}_${loggedInUser.username}`, localStorage.getItem(d)); localStorage.setItem(`${d}_${loggedInUser.username}`, lsItem);
localStorage.removeItem(d); localStorage.removeItem(d);
} }
}); });

View File

@ -106,8 +106,8 @@ export default class BattleScene extends SceneBase {
public inputController: InputsController; public inputController: InputsController;
public uiInputs: UiInputs; public uiInputs: UiInputs;
public sessionPlayTime: integer = null; public sessionPlayTime: integer | null = null;
public lastSavePlayTime: integer = null; public lastSavePlayTime: integer | null = null;
public masterVolume: number = 0.5; public masterVolume: number = 0.5;
public bgmVolume: number = 1; public bgmVolume: number = 1;
public seVolume: number = 1; public seVolume: number = 1;
@ -192,8 +192,8 @@ export default class BattleScene extends SceneBase {
private phaseQueuePrependSpliceIndex: integer; private phaseQueuePrependSpliceIndex: integer;
private nextCommandPhaseQueue: Phase[]; private nextCommandPhaseQueue: Phase[];
private currentPhase: Phase; private currentPhase: Phase | null;
private standbyPhase: Phase; private standbyPhase: Phase | null;
public field: Phaser.GameObjects.Container; public field: Phaser.GameObjects.Container;
public fieldUI: Phaser.GameObjects.Container; public fieldUI: Phaser.GameObjects.Container;
public charSprite: CharSprite; public charSprite: CharSprite;
@ -213,7 +213,7 @@ export default class BattleScene extends SceneBase {
public score: integer; public score: integer;
public lockModifierTiers: boolean; public lockModifierTiers: boolean;
public trainer: Phaser.GameObjects.Sprite; public trainer: Phaser.GameObjects.Sprite;
public lastEnemyTrainer: Trainer; public lastEnemyTrainer: Trainer | null;
public currentBattle: Battle; public currentBattle: Battle;
public pokeballCounts: PokeballCounts; public pokeballCounts: PokeballCounts;
public money: integer; public money: integer;
@ -251,7 +251,7 @@ export default class BattleScene extends SceneBase {
public spritePipeline: SpritePipeline; public spritePipeline: SpritePipeline;
private bgm: AnySound; private bgm: AnySound;
private bgmResumeTimer: Phaser.Time.TimerEvent; private bgmResumeTimer: Phaser.Time.TimerEvent | null;
private bgmCache: Set<string> = new Set(); private bgmCache: Set<string> = new Set();
private playTimeTimer: Phaser.Time.TimerEvent; private playTimeTimer: Phaser.Time.TimerEvent;
@ -700,7 +700,11 @@ export default class BattleScene extends SceneBase {
hasExpSprite(key: string): boolean { hasExpSprite(key: string): boolean {
const keyMatch = /^pkmn__?(back__)?(shiny__)?(female__)?(\d+)(\-.*?)?(?:_[1-3])?$/g.exec(key); const keyMatch = /^pkmn__?(back__)?(shiny__)?(female__)?(\d+)(\-.*?)?(?:_[1-3])?$/g.exec(key);
let k = keyMatch[4]; if (!keyMatch) {
return false;
}
let k = keyMatch[4]!;
if (keyMatch[2]) { if (keyMatch[2]) {
k += "s"; k += "s";
} }
@ -723,7 +727,7 @@ export default class BattleScene extends SceneBase {
return this.party; return this.party;
} }
getPlayerPokemon(): PlayerPokemon { getPlayerPokemon(): PlayerPokemon | undefined {
return this.getPlayerField().find(p => p.isActive()); return this.getPlayerField().find(p => p.isActive());
} }
@ -740,7 +744,7 @@ export default class BattleScene extends SceneBase {
return this.currentBattle?.enemyParty || []; return this.currentBattle?.enemyParty || [];
} }
getEnemyPokemon(): EnemyPokemon { getEnemyPokemon(): EnemyPokemon | undefined {
return this.getEnemyField().find(p => p.isActive()); return this.getEnemyField().find(p => p.isActive());
} }
@ -782,12 +786,12 @@ export default class BattleScene extends SceneBase {
return activeOnly ? this.infoToggles.filter(t => t?.isActive()) : this.infoToggles; return activeOnly ? this.infoToggles.filter(t => t?.isActive()) : this.infoToggles;
} }
getPokemonById(pokemonId: integer): Pokemon { getPokemonById(pokemonId: integer): Pokemon | null {
const findInParty = (party: Pokemon[]) => party.find(p => p.id === pokemonId); const findInParty = (party: Pokemon[]) => party.find(p => p.id === pokemonId);
return findInParty(this.getParty()) || findInParty(this.getEnemyParty()); return (findInParty(this.getParty()) || findInParty(this.getEnemyParty())) ?? null;
} }
addPlayerPokemon(species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData, postProcess?: (playerPokemon: PlayerPokemon) => void): PlayerPokemon { addPlayerPokemon(species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData, postProcess?: (playerPokemon: PlayerPokemon) => void): PlayerPokemon {
const pokemon = new PlayerPokemon(this, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource); const pokemon = new PlayerPokemon(this, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource);
if (postProcess) { if (postProcess) {
postProcess(pokemon); postProcess(pokemon);
@ -957,7 +961,8 @@ export default class BattleScene extends SceneBase {
p.destroy(); p.destroy();
} }
this.currentBattle = null; //@ts-ignore - allowing `null` for currentBattle causes a lot of trouble
this.currentBattle = null; // TODO: resolve ts-ignore
this.biomeWaveText.setText(startingWave.toString()); this.biomeWaveText.setText(startingWave.toString());
this.biomeWaveText.setVisible(false); this.biomeWaveText.setVisible(false);
@ -1021,14 +1026,14 @@ export default class BattleScene extends SceneBase {
} }
} }
newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean): Battle { newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean): Battle | null {
const _startingWave = Overrides.STARTING_WAVE_OVERRIDE || startingWave; const _startingWave = Overrides.STARTING_WAVE_OVERRIDE || startingWave;
const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (_startingWave - 1)) + 1); const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (_startingWave - 1)) + 1);
let newDouble: boolean; let newDouble: boolean | undefined;
let newBattleType: BattleType; let newBattleType: BattleType;
let newTrainer: Trainer; let newTrainer: Trainer | undefined;
let battleConfig: FixedBattleConfig = null; let battleConfig: FixedBattleConfig | null = null;
this.resetSeed(newWaveIndex); this.resetSeed(newWaveIndex);
@ -1038,7 +1043,7 @@ export default class BattleScene extends SceneBase {
battleConfig = this.gameMode.getFixedBattle(newWaveIndex); battleConfig = this.gameMode.getFixedBattle(newWaveIndex);
newDouble = battleConfig.double; newDouble = battleConfig.double;
newBattleType = battleConfig.battleType; newBattleType = battleConfig.battleType;
this.executeWithSeedOffset(() => newTrainer = battleConfig.getTrainer(this), (battleConfig.seedOffsetWaveIndex || newWaveIndex) << 8); this.executeWithSeedOffset(() => newTrainer = battleConfig?.getTrainer(this), (battleConfig.seedOffsetWaveIndex || newWaveIndex) << 8);
if (newTrainer) { if (newTrainer) {
this.field.add(newTrainer); this.field.add(newTrainer);
} }
@ -1078,7 +1083,7 @@ export default class BattleScene extends SceneBase {
playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, doubleChance)); playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, doubleChance));
newDouble = !Utils.randSeedInt(doubleChance.value); newDouble = !Utils.randSeedInt(doubleChance.value);
} else if (newBattleType === BattleType.TRAINER) { } else if (newBattleType === BattleType.TRAINER) {
newDouble = newTrainer.variant === TrainerVariant.DOUBLE; newDouble = newTrainer?.variant === TrainerVariant.DOUBLE;
} }
} else if (!battleConfig) { } else if (!battleConfig) {
newDouble = !!double; newDouble = !!double;
@ -1309,7 +1314,7 @@ export default class BattleScene extends SceneBase {
return 5; return 5;
} }
let isBoss: boolean; let isBoss: boolean | undefined;
if (forceBoss || (species && (species.subLegendary || species.legendary || species.mythical))) { if (forceBoss || (species && (species.subLegendary || species.legendary || species.mythical))) {
isBoss = true; isBoss = true;
} else { } else {
@ -1608,7 +1613,7 @@ export default class BattleScene extends SceneBase {
randomSpecies(waveIndex: integer, level: integer, fromArenaPool?: boolean, speciesFilter?: PokemonSpeciesFilter, filterAllEvolutions?: boolean): PokemonSpecies { randomSpecies(waveIndex: integer, level: integer, fromArenaPool?: boolean, speciesFilter?: PokemonSpeciesFilter, filterAllEvolutions?: boolean): PokemonSpecies {
if (fromArenaPool) { if (fromArenaPool) {
return this.arena.randomSpecies(waveIndex, level,null , getPartyLuckValue(this.party)); return this.arena.randomSpecies(waveIndex, level, undefined , getPartyLuckValue(this.party));
} }
const filteredSpecies = speciesFilter ? [...new Set(allSpecies.filter(s => s.isCatchable()).filter(speciesFilter).map(s => { const filteredSpecies = speciesFilter ? [...new Set(allSpecies.filter(s => s.isCatchable()).filter(speciesFilter).map(s => {
if (!filterAllEvolutions) { if (!filterAllEvolutions) {
@ -1966,11 +1971,11 @@ export default class BattleScene extends SceneBase {
} }
/* Phase Functions */ /* Phase Functions */
getCurrentPhase(): Phase { getCurrentPhase(): Phase | null {
return this.currentPhase; return this.currentPhase;
} }
getStandbyPhase(): Phase { getStandbyPhase(): Phase | null {
return this.standbyPhase; return this.standbyPhase;
} }
@ -2048,7 +2053,10 @@ export default class BattleScene extends SceneBase {
} }
if (this.phaseQueuePrepend.length) { if (this.phaseQueuePrepend.length) {
while (this.phaseQueuePrepend.length) { while (this.phaseQueuePrepend.length) {
this.phaseQueue.unshift(this.phaseQueuePrepend.pop()); const poppedPhase = this.phaseQueuePrepend.pop();
if (poppedPhase) {
this.phaseQueue.unshift(poppedPhase);
}
} }
} }
if (!this.phaseQueue.length) { if (!this.phaseQueue.length) {
@ -2056,23 +2064,26 @@ export default class BattleScene extends SceneBase {
// Clear the conditionalQueue if there are no phases left in the phaseQueue // Clear the conditionalQueue if there are no phases left in the phaseQueue
this.conditionalQueue = []; this.conditionalQueue = [];
} }
this.currentPhase = this.phaseQueue.shift();
this.currentPhase = this.phaseQueue.shift() ?? null;
// Check if there are any conditional phases queued // Check if there are any conditional phases queued
if (this.conditionalQueue?.length) { if (this.conditionalQueue?.length) {
// Retrieve the first conditional phase from the queue // Retrieve the first conditional phase from the queue
const conditionalPhase = this.conditionalQueue.shift(); const conditionalPhase = this.conditionalQueue.shift();
// Evaluate the condition associated with the phase // Evaluate the condition associated with the phase
if (conditionalPhase[0]()) { if (conditionalPhase?.[0]()) {
// If the condition is met, add the phase to the phase queue // If the condition is met, add the phase to the phase queue
this.pushPhase(conditionalPhase[1]); this.pushPhase(conditionalPhase[1]);
} else { } else if (conditionalPhase) {
// If the condition is not met, re-add the phase back to the front of the conditional queue // If the condition is not met, re-add the phase back to the front of the conditional queue
this.conditionalQueue.unshift(conditionalPhase); this.conditionalQueue.unshift(conditionalPhase);
} else {
console.warn("condition phase is undefined/null!", conditionalPhase);
} }
} }
this.currentPhase.start(); this.currentPhase?.start();
} }
overridePhase(phase: Phase): boolean { overridePhase(phase: Phase): boolean {
@ -2087,7 +2098,7 @@ export default class BattleScene extends SceneBase {
return true; return true;
} }
findPhase(phaseFilter: (phase: Phase) => boolean): Phase { findPhase(phaseFilter: (phase: Phase) => boolean): Phase | undefined {
return this.phaseQueue.find(phaseFilter); return this.phaseQueue.find(phaseFilter);
} }
@ -2146,7 +2157,7 @@ export default class BattleScene extends SceneBase {
* @param promptDelay optional param for MessagePhase constructor * @param promptDelay optional param for MessagePhase constructor
* @param defer boolean for which queue to add it to, false -> add to PhaseQueuePrepend, true -> nextCommandPhaseQueue * @param defer boolean for which queue to add it to, false -> add to PhaseQueuePrepend, true -> nextCommandPhaseQueue
*/ */
queueMessage(message: string, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer, defer?: boolean) { queueMessage(message: string, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null, defer?: boolean | null) {
const phase = new MessagePhase(this, message, callbackDelay, prompt, promptDelay); const phase = new MessagePhase(this, message, callbackDelay, prompt, promptDelay);
if (!defer) { if (!defer) {
// adds to the end of PhaseQueuePrepend // adds to the end of PhaseQueuePrepend
@ -2182,7 +2193,10 @@ export default class BattleScene extends SceneBase {
return Math.floor(moneyValue / 10) * 10; return Math.floor(moneyValue / 10) * 10;
} }
addModifier(modifier: Modifier, ignoreUpdate?: boolean, playSound?: boolean, virtual?: boolean, instant?: boolean): Promise<boolean> { addModifier(modifier: Modifier | null, ignoreUpdate?: boolean, playSound?: boolean, virtual?: boolean, instant?: boolean): Promise<boolean> {
if (!modifier) {
return Promise.resolve(false);
}
return new Promise(resolve => { return new Promise(resolve => {
let success = false; let success = false;
const soundName = modifier.type.soundName; const soundName = modifier.type.soundName;
@ -2202,7 +2216,7 @@ export default class BattleScene extends SceneBase {
} }
} else if (!virtual) { } else if (!virtual) {
const defaultModifierType = getDefaultModifierTypeForTier(modifier.type.tier); const defaultModifierType = getDefaultModifierTypeForTier(modifier.type.tier);
this.queueMessage(`The stack for this item is full.\n You will receive ${defaultModifierType.name} instead.`, null, true); this.queueMessage(`The stack for this item is full.\n You will receive ${defaultModifierType.name} instead.`, undefined, true);
return this.addModifier(defaultModifierType.newModifier(), ignoreUpdate, playSound, false, instant).then(success => resolve(success)); return this.addModifier(defaultModifierType.newModifier(), ignoreUpdate, playSound, false, instant).then(success => resolve(success));
} }
@ -2302,7 +2316,7 @@ export default class BattleScene extends SceneBase {
return new Promise(resolve => { return new Promise(resolve => {
const source = itemModifier.pokemonId ? itemModifier.getPokemon(target.scene) : null; const source = itemModifier.pokemonId ? itemModifier.getPokemon(target.scene) : null;
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
Utils.executeIf(source && source.isPlayer() !== target.isPlayer(), () => applyAbAttrs(BlockItemTheftAbAttr, source, cancelled)).then(() => { Utils.executeIf(!!source && source.isPlayer() !== target.isPlayer(), () => applyAbAttrs(BlockItemTheftAbAttr, source! /* checked in condition*/, cancelled)).then(() => {
if (cancelled.value) { if (cancelled.value) {
return resolve(false); return resolve(false);
} }
@ -2383,7 +2397,7 @@ export default class BattleScene extends SceneBase {
} }
party.forEach((enemyPokemon: EnemyPokemon, i: integer) => { party.forEach((enemyPokemon: EnemyPokemon, i: integer) => {
const isBoss = enemyPokemon.isBoss() || (this.currentBattle.battleType === BattleType.TRAINER && this.currentBattle.trainer.config.isBoss); const isBoss = enemyPokemon.isBoss() || (this.currentBattle.battleType === BattleType.TRAINER && this.currentBattle.trainer?.config.isBoss);
let upgradeChance = 32; let upgradeChance = 32;
if (isBoss) { if (isBoss) {
upgradeChance /= 2; upgradeChance /= 2;
@ -2391,9 +2405,9 @@ export default class BattleScene extends SceneBase {
if (isFinalBoss) { if (isFinalBoss) {
upgradeChance /= 8; upgradeChance /= 8;
} }
const modifierChance = this.gameMode.getEnemyModifierChance(isBoss); const modifierChance = this.gameMode.getEnemyModifierChance(isBoss!); // TODO: is this bang correct?
let pokemonModifierChance = modifierChance; let pokemonModifierChance = modifierChance;
if (this.currentBattle.battleType === BattleType.TRAINER) if (this.currentBattle.battleType === BattleType.TRAINER && this.currentBattle.trainer)
pokemonModifierChance = Math.ceil(pokemonModifierChance * this.currentBattle.trainer.getPartyMemberModifierChanceMultiplier(i)); // eslint-disable-line pokemonModifierChance = Math.ceil(pokemonModifierChance * this.currentBattle.trainer.getPartyMemberModifierChanceMultiplier(i)); // eslint-disable-line
let count = 0; let count = 0;
for (let c = 0; c < chances; c++) { for (let c = 0; c < chances; c++) {
@ -2512,7 +2526,7 @@ export default class BattleScene extends SceneBase {
return (player ? this.modifiers : this.enemyModifiers).filter(m => (modifierFilter as ModifierPredicate)(m)); return (player ? this.modifiers : this.enemyModifiers).filter(m => (modifierFilter as ModifierPredicate)(m));
} }
findModifier(modifierFilter: ModifierPredicate, player: boolean = true): PersistentModifier { findModifier(modifierFilter: ModifierPredicate, player: boolean = true): PersistentModifier | undefined {
return (player ? this.modifiers : this.enemyModifiers).find(m => (modifierFilter as ModifierPredicate)(m)); return (player ? this.modifiers : this.enemyModifiers).find(m => (modifierFilter as ModifierPredicate)(m));
} }
@ -2548,7 +2562,7 @@ export default class BattleScene extends SceneBase {
return appliedModifiers; return appliedModifiers;
} }
applyModifier(modifierType: Constructor<Modifier>, player: boolean = true, ...args: any[]): PersistentModifier { applyModifier(modifierType: Constructor<Modifier>, player: boolean = true, ...args: any[]): PersistentModifier | null {
const modifiers = (player ? this.modifiers : this.enemyModifiers).filter(m => m instanceof modifierType && m.shouldApply(args)); const modifiers = (player ? this.modifiers : this.enemyModifiers).filter(m => m instanceof modifierType && m.shouldApply(args));
for (const modifier of modifiers) { for (const modifier of modifiers) {
if (modifier.apply(args)) { if (modifier.apply(args)) {
@ -2635,7 +2649,7 @@ export default class BattleScene extends SceneBase {
initFinalBossPhaseTwo(pokemon: Pokemon): void { initFinalBossPhaseTwo(pokemon: Pokemon): void {
if (pokemon instanceof EnemyPokemon && pokemon.isBoss() && !pokemon.formIndex && pokemon.bossSegmentIndex < 1) { if (pokemon instanceof EnemyPokemon && pokemon.isBoss() && !pokemon.formIndex && pokemon.bossSegmentIndex < 1) {
this.fadeOutBgm(Utils.fixedInt(2000), false); this.fadeOutBgm(Utils.fixedInt(2000), false);
this.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].firstStageWin, pokemon.species.name, null, () => { this.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].firstStageWin, pokemon.species.name, undefined, () => {
this.addEnemyModifier(getModifierType(modifierTypes.MINI_BLACK_HOLE).newModifier(pokemon) as PersistentModifier, false, true); this.addEnemyModifier(getModifierType(modifierTypes.MINI_BLACK_HOLE).newModifier(pokemon) as PersistentModifier, false, true);
pokemon.generateAndPopulateMoveset(1); pokemon.generateAndPopulateMoveset(1);
this.setFieldScale(0.75); this.setFieldScale(0.75);

View File

@ -39,7 +39,7 @@ export interface TurnCommand {
} }
interface TurnCommands { interface TurnCommands {
[key: integer]: TurnCommand [key: integer]: TurnCommand | null
} }
export default class Battle { export default class Battle {
@ -47,8 +47,8 @@ export default class Battle {
public waveIndex: integer; public waveIndex: integer;
public battleType: BattleType; public battleType: BattleType;
public battleSpec: BattleSpec; public battleSpec: BattleSpec;
public trainer: Trainer; public trainer: Trainer | null;
public enemyLevels: integer[]; public enemyLevels: integer[] | undefined;
public enemyParty: EnemyPokemon[]; public enemyParty: EnemyPokemon[];
public seenEnemyPartyMemberIds: Set<integer>; public seenEnemyPartyMemberIds: Set<integer>;
public double: boolean; public double: boolean;
@ -62,26 +62,26 @@ export default class Battle {
public escapeAttempts: integer; public escapeAttempts: integer;
public lastMove: Moves; public lastMove: Moves;
public battleSeed: string; public battleSeed: string;
private battleSeedState: string; private battleSeedState: string | null;
public moneyScattered: number; public moneyScattered: number;
public lastUsedPokeball: PokeballType; public lastUsedPokeball: PokeballType | null;
public playerFaints: number; // The amount of times pokemon on the players side have fainted public playerFaints: number; // The amount of times pokemon on the players side have fainted
public enemyFaints: number; // The amount of times pokemon on the enemies side have fainted public enemyFaints: number; // The amount of times pokemon on the enemies side have fainted
private rngCounter: integer = 0; private rngCounter: integer = 0;
constructor(gameMode: GameMode, waveIndex: integer, battleType: BattleType, trainer: Trainer, double: boolean) { constructor(gameMode: GameMode, waveIndex: integer, battleType: BattleType, trainer?: Trainer, double?: boolean) {
this.gameMode = gameMode; this.gameMode = gameMode;
this.waveIndex = waveIndex; this.waveIndex = waveIndex;
this.battleType = battleType; this.battleType = battleType;
this.trainer = trainer; this.trainer = trainer!; //TODO: is this bang correct?
this.initBattleSpec(); this.initBattleSpec();
this.enemyLevels = battleType !== BattleType.TRAINER this.enemyLevels = battleType !== BattleType.TRAINER
? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave()) ? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave())
: trainer.getPartyLevels(this.waveIndex); : trainer?.getPartyLevels(this.waveIndex);
this.enemyParty = []; this.enemyParty = [];
this.seenEnemyPartyMemberIds = new Set<integer>(); this.seenEnemyPartyMemberIds = new Set<integer>();
this.double = double; this.double = double!; //TODO: is this bang correct?
this.enemySwitchCounter = 0; this.enemySwitchCounter = 0;
this.turn = 0; this.turn = 0;
this.playerParticipantIds = new Set<integer>(); this.playerParticipantIds = new Set<integer>();
@ -159,6 +159,7 @@ export default class Battle {
addPostBattleLoot(enemyPokemon: EnemyPokemon): void { addPostBattleLoot(enemyPokemon: EnemyPokemon): void {
this.postBattleLoot.push(...enemyPokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferrable, false).map(i => { this.postBattleLoot.push(...enemyPokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferrable, false).map(i => {
const ret = i as PokemonHeldItemModifier; const ret = i as PokemonHeldItemModifier;
//@ts-ignore - this is awful to fix/change
ret.pokemonId = null; ret.pokemonId = null;
return ret; return ret;
})); }));
@ -177,7 +178,7 @@ export default class Battle {
const userLocale = navigator.language || "en-US"; const userLocale = navigator.language || "en-US";
const formattedMoneyAmount = moneyAmount.value.toLocaleString(userLocale); const formattedMoneyAmount = moneyAmount.value.toLocaleString(userLocale);
const message = i18next.t("battle:moneyPickedUp", { moneyAmount: formattedMoneyAmount }); const message = i18next.t("battle:moneyPickedUp", { moneyAmount: formattedMoneyAmount });
scene.queueMessage(message, null, true); scene.queueMessage(message, undefined, true);
scene.currentBattle.moneyScattered = 0; scene.currentBattle.moneyScattered = 0;
} }
@ -200,16 +201,16 @@ export default class Battle {
scene.updateScoreText(); scene.updateScoreText();
} }
getBgmOverride(scene: BattleScene): string { getBgmOverride(scene: BattleScene): string | null {
const battlers = this.enemyParty.slice(0, this.getBattlerCount()); const battlers = this.enemyParty.slice(0, this.getBattlerCount());
if (this.battleType === BattleType.TRAINER) { if (this.battleType === BattleType.TRAINER) {
if (!this.started && this.trainer.config.encounterBgm && this.trainer.getEncounterMessages()?.length) { if (!this.started && this.trainer?.config.encounterBgm && this.trainer?.getEncounterMessages()?.length) {
return `encounter_${this.trainer.getEncounterBgm()}`; return `encounter_${this.trainer?.getEncounterBgm()}`;
} }
if (scene.musicPreference === 0) { if (scene.musicPreference === 0) {
return this.trainer.getBattleBgm(); return this.trainer?.getBattleBgm()!; // TODO: is this bang correct?
} else { } else {
return this.trainer.getMixedBattleBgm(); return this.trainer?.getMixedBattleBgm()!; // TODO: is this bang correct?
} }
} else if (this.gameMode.isClassic && this.waveIndex > 195 && this.battleSpec !== BattleSpec.FINAL_BOSS) { } else if (this.gameMode.isClassic && this.waveIndex > 195 && this.battleSpec !== BattleSpec.FINAL_BOSS) {
return "end_summit"; return "end_summit";
@ -382,7 +383,7 @@ export default class Battle {
export class FixedBattle extends Battle { export class FixedBattle extends Battle {
constructor(scene: BattleScene, waveIndex: integer, config: FixedBattleConfig) { constructor(scene: BattleScene, waveIndex: integer, config: FixedBattleConfig) {
super(scene.gameMode, waveIndex, config.battleType, config.battleType === BattleType.TRAINER ? config.getTrainer(scene) : null, config.double); super(scene.gameMode, waveIndex, config.battleType, config.battleType === BattleType.TRAINER ? config.getTrainer(scene) : undefined, config.double);
if (config.getEnemyParty) { if (config.getEnemyParty) {
this.enemyParty = config.getEnemyParty(scene); this.enemyParty = config.getEnemyParty(scene);
} }

View File

@ -20,7 +20,7 @@ export function getKeyWithKeycode(config, keycode) {
*/ */
export function getSettingNameWithKeycode(config, keycode) { export function getSettingNameWithKeycode(config, keycode) {
const key = getKeyWithKeycode(config, keycode); const key = getKeyWithKeycode(config, keycode);
return config.custom[key]; return key ? config.custom[key] : null;
} }
/** /**
@ -32,7 +32,7 @@ export function getSettingNameWithKeycode(config, keycode) {
*/ */
export function getIconWithKeycode(config, keycode) { export function getIconWithKeycode(config, keycode) {
const key = getKeyWithKeycode(config, keycode); const key = getKeyWithKeycode(config, keycode);
return config.icons[key]; return key ? config.icons[key] : null;
} }
/** /**
@ -122,15 +122,21 @@ export function assign(config, settingNameTarget, keycode): boolean {
// if it was already bound, we delete the bind // if it was already bound, we delete the bind
if (previousSettingName) { if (previousSettingName) {
const previousKey = getKeyWithSettingName(config, previousSettingName); const previousKey = getKeyWithSettingName(config, previousSettingName);
if (previousKey) {
config.custom[previousKey] = -1; config.custom[previousKey] = -1;
} }
}
// then, we need to delete the current key for this settingName // then, we need to delete the current key for this settingName
const currentKey = getKeyWithSettingName(config, settingNameTarget); const currentKey = getKeyWithSettingName(config, settingNameTarget);
if (currentKey) {
config.custom[currentKey] = -1; config.custom[currentKey] = -1;
}
// then, the new key is assigned to the new settingName // then, the new key is assigned to the new settingName
const newKey = getKeyWithKeycode(config, keycode); const newKey = getKeyWithKeycode(config, keycode);
if (newKey) {
config.custom[newKey] = settingNameTarget; config.custom[newKey] = settingNameTarget;
}
return true; return true;
} }
@ -145,8 +151,12 @@ export function swap(config, settingNameTarget, keycode) {
const new_key = getKeyWithKeycode(config, keycode); const new_key = getKeyWithKeycode(config, keycode);
const new_settingName = getSettingNameWithKey(config, new_key); const new_settingName = getSettingNameWithKey(config, new_key);
if (prev_key) {
config.custom[prev_key] = new_settingName; config.custom[prev_key] = new_settingName;
}
if (new_key) {
config.custom[new_key] = prev_settingName; config.custom[new_key] = prev_settingName;
}
return true; return true;
} }
@ -161,7 +171,9 @@ export function deleteBind(config, settingName) {
if (config.blacklist.includes(key)) { if (config.blacklist.includes(key)) {
return false; return false;
} }
if (key) {
config.custom[key] = -1; config.custom[key] = -1;
}
return true; return true;
} }

View File

@ -120,7 +120,7 @@ export class Ability implements Localizable {
type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => boolean | Promise<boolean>; type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => boolean | Promise<boolean>;
type AbAttrCondition = (pokemon: Pokemon) => boolean; type AbAttrCondition = (pokemon: Pokemon) => boolean;
type PokemonAttackCondition = (user: Pokemon, target: Pokemon, move: Move) => boolean; type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean; type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
type PokemonStatChangeCondition = (target: Pokemon, statsChanged: BattleStat[], levels: integer) => boolean; type PokemonStatChangeCondition = (target: Pokemon, statsChanged: BattleStat[], levels: integer) => boolean;
@ -132,11 +132,11 @@ export abstract class AbAttr {
this.showAbility = showAbility; this.showAbility = showAbility;
} }
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> { apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder | null, args: any[]): boolean | Promise<boolean> {
return false; return false;
} }
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null {
return null; return null;
} }
@ -226,7 +226,7 @@ export class PostBattleInitStatChangeAbAttr extends PostBattleInitAbAttr {
} }
for (const statChangePhase of statChangePhases) { for (const statChangePhase of statChangePhases) {
if (!this.selfTarget && !statChangePhase.getPokemon().summonData) { if (!this.selfTarget && !statChangePhase.getPokemon()?.summonData) {
pokemon.scene.pushPhase(statChangePhase); pokemon.scene.pushPhase(statChangePhase);
} else { // TODO: This causes the ability bar to be shown at the wrong time } else { // TODO: This causes the ability bar to be shown at the wrong time
pokemon.scene.unshiftPhase(statChangePhase); pokemon.scene.unshiftPhase(statChangePhase);
@ -240,7 +240,7 @@ export class PostBattleInitStatChangeAbAttr extends PostBattleInitAbAttr {
type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: Move) => boolean; type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: Move) => boolean;
export class PreDefendAbAttr extends AbAttr { export class PreDefendAbAttr extends AbAttr {
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> { applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, args: any[]): boolean | Promise<boolean> {
return false; return false;
} }
} }
@ -352,14 +352,14 @@ export class PreDefendMoveDamageToOneAbAttr extends ReceivedMoveDamageMultiplier
* @see {@linkcode getCondition} * @see {@linkcode getCondition}
*/ */
export class TypeImmunityAbAttr extends PreDefendAbAttr { export class TypeImmunityAbAttr extends PreDefendAbAttr {
private immuneType: Type; private immuneType: Type | null;
private condition: AbAttrCondition; private condition: AbAttrCondition | null;
constructor(immuneType: Type, condition?: AbAttrCondition) { constructor(immuneType: Type | null, condition?: AbAttrCondition) {
super(); super();
this.immuneType = immuneType; this.immuneType = immuneType;
this.condition = condition; this.condition = condition!; // TODO: is this bang correct?
} }
/** /**
@ -386,7 +386,7 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr {
return false; return false;
} }
getCondition(): AbAttrCondition { override getCondition(): AbAttrCondition | null {
return this.condition; return this.condition;
} }
} }
@ -491,7 +491,7 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
} }
export class PostDefendAbAttr extends AbAttr { export class PostDefendAbAttr extends AbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise<boolean> {
return false; return false;
} }
} }
@ -881,7 +881,7 @@ export class EffectSporeAbAttr extends PostDefendContactApplyStatusEffectAbAttr
export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr { export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
private chance: integer; private chance: integer;
private tagType: BattlerTagType; private tagType: BattlerTagType;
private turnCount: integer; private turnCount: integer | undefined;
constructor(chance: integer, tagType: BattlerTagType, turnCount?: integer) { constructor(chance: integer, tagType: BattlerTagType, turnCount?: integer) {
super(); super();
@ -918,7 +918,7 @@ export class PostDefendCritStatChangeAbAttr extends PostDefendAbAttr {
} }
getCondition(): AbAttrCondition { getCondition(): AbAttrCondition {
return (pokemon: Pokemon) => pokemon.turnData.attacksReceived.length && pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1].critical; return (pokemon: Pokemon) => pokemon.turnData.attacksReceived.length !== 0 && pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1].critical;
} }
} }
@ -1109,7 +1109,7 @@ export class PostStatChangeStatChangeAbAttr extends PostStatChangeAbAttr {
} }
export class PreAttackAbAttr extends AbAttr { export class PreAttackAbAttr extends AbAttr {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> { applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon | null, move: Move, args: any[]): boolean | Promise<boolean> {
return false; return false;
} }
} }
@ -1122,7 +1122,7 @@ export class PreAttackAbAttr extends AbAttr {
export class MoveEffectChanceMultiplierAbAttr extends AbAttr { export class MoveEffectChanceMultiplierAbAttr extends AbAttr {
private chanceMultiplier: number; private chanceMultiplier: number;
constructor(chanceMultiplier?: number) { constructor(chanceMultiplier: number) {
super(true); super(true);
this.chanceMultiplier = chanceMultiplier; this.chanceMultiplier = chanceMultiplier;
} }
@ -1503,7 +1503,7 @@ export class FieldMovePowerBoostAbAttr extends AbAttr {
this.powerMultiplier = powerMultiplier; this.powerMultiplier = powerMultiplier;
} }
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { applyPreAttack(pokemon: Pokemon | null, passive: boolean | null, defender: Pokemon | null, move: Move, args: any[]): boolean {
if (this.condition(pokemon, defender, move)) { if (this.condition(pokemon, defender, move)) {
(args[0] as Utils.NumberHolder).value *= this.powerMultiplier; (args[0] as Utils.NumberHolder).value *= this.powerMultiplier;
@ -1557,14 +1557,14 @@ export class AllyMoveCategoryPowerBoostAbAttr extends FieldMovePowerBoostAbAttr
export class BattleStatMultiplierAbAttr extends AbAttr { export class BattleStatMultiplierAbAttr extends AbAttr {
private battleStat: BattleStat; private battleStat: BattleStat;
private multiplier: number; private multiplier: number;
private condition: PokemonAttackCondition; private condition: PokemonAttackCondition | null;
constructor(battleStat: BattleStat, multiplier: number, condition?: PokemonAttackCondition) { constructor(battleStat: BattleStat, multiplier: number, condition?: PokemonAttackCondition) {
super(false); super(false);
this.battleStat = battleStat; this.battleStat = battleStat;
this.multiplier = multiplier; this.multiplier = multiplier;
this.condition = condition; this.condition = condition!; // TODO: is this bang correct?
} }
applyBattleStat(pokemon: Pokemon, passive: boolean, battleStat: BattleStat, statValue: Utils.NumberHolder, args: any[]): boolean | Promise<boolean> { applyBattleStat(pokemon: Pokemon, passive: boolean, battleStat: BattleStat, statValue: Utils.NumberHolder, args: any[]): boolean | Promise<boolean> {
@ -1593,7 +1593,7 @@ export class PostAttackAbAttr extends AbAttr {
* applying the effect of any inherited class. This can be changed by providing a different {@link attackCondition} to the constructor. See {@link ConfusionOnStatusEffectAbAttr} * applying the effect of any inherited class. This can be changed by providing a different {@link attackCondition} to the constructor. See {@link ConfusionOnStatusEffectAbAttr}
* for an example of an effect that does not require a damaging move. * for an example of an effect that does not require a damaging move.
*/ */
applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> { applyPostAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise<boolean> {
// When attackRequired is true, we require the move to be an attack move and to deal damage before checking secondary requirements. // When attackRequired is true, we require the move to be an attack move and to deal damage before checking secondary requirements.
// If attackRequired is false, we always defer to the secondary requirements. // If attackRequired is false, we always defer to the secondary requirements.
if (this.attackCondition(pokemon, defender, move)) { if (this.attackCondition(pokemon, defender, move)) {
@ -1606,18 +1606,18 @@ export class PostAttackAbAttr extends AbAttr {
/** /**
* This method is only called after {@link applyPostAttack} has already been applied. Use this for handling checks specific to the ability in question. * This method is only called after {@link applyPostAttack} has already been applied. Use this for handling checks specific to the ability in question.
*/ */
applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> { applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise<boolean> {
return false; return false;
} }
} }
export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
private stealCondition: PokemonAttackCondition; private stealCondition: PokemonAttackCondition | null;
constructor(stealCondition?: PokemonAttackCondition) { constructor(stealCondition?: PokemonAttackCondition) {
super(); super();
this.stealCondition = stealCondition; this.stealCondition = stealCondition!; // TODO: is this bang correct?
} }
applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> { applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
@ -1701,12 +1701,12 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
} }
export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
private condition: PokemonDefendCondition; private condition: PokemonDefendCondition | null;
constructor(condition?: PokemonDefendCondition) { constructor(condition?: PokemonDefendCondition) {
super(); super();
this.condition = condition; this.condition = condition!; // TODO: is this bang correct?
} }
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
@ -2197,17 +2197,17 @@ export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr {
} }
if ( if (
target.getAbility().hasAttr(UncopiableAbilityAbAttr) && target!.getAbility().hasAttr(UncopiableAbilityAbAttr) &&
// Wonder Guard is normally uncopiable so has the attribute, but Trace specifically can copy it // Wonder Guard is normally uncopiable so has the attribute, but Trace specifically can copy it
!(pokemon.hasAbility(Abilities.TRACE) && target.getAbility().id === Abilities.WONDER_GUARD) !(pokemon.hasAbility(Abilities.TRACE) && target!.getAbility().id === Abilities.WONDER_GUARD)
) { ) {
return false; return false;
} }
this.target = target; this.target = target!;
this.targetAbilityName = allAbilities[target.getAbility().id].name; this.targetAbilityName = allAbilities[target!.getAbility().id].name;
pokemon.summonData.ability = target.getAbility().id; pokemon.summonData.ability = target!.getAbility().id;
setAbilityRevealed(target); setAbilityRevealed(target!);
pokemon.updateInfo(); pokemon.updateInfo();
return true; return true;
@ -2254,7 +2254,7 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt
} }
for (const pokemon of allowedParty) { for (const pokemon of allowedParty) {
if (this.statusEffect.includes(pokemon.status?.effect)) { if (pokemon.status && this.statusEffect.includes(pokemon.status.effect)) {
pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)));
pokemon.resetStatus(false); pokemon.resetStatus(false);
pokemon.updateInfo(); pokemon.updateInfo();
@ -2310,6 +2310,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
target = targets[0]; target = targets[0];
} }
target = target!; // compiler doesn't know its guranteed to be defined
pokemon.summonData.speciesForm = target.getSpeciesForm(); pokemon.summonData.speciesForm = target.getSpeciesForm();
pokemon.summonData.fusionSpeciesForm = target.getFusionSpeciesForm(); pokemon.summonData.fusionSpeciesForm = target.getFusionSpeciesForm();
pokemon.summonData.ability = target.getAbility().id; pokemon.summonData.ability = target.getAbility().id;
@ -2317,7 +2318,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
pokemon.summonData.fusionGender = target.getFusionGender(); pokemon.summonData.fusionGender = target.getFusionGender();
pokemon.summonData.stats = [ pokemon.stats[Stat.HP] ].concat(target.stats.slice(1)); pokemon.summonData.stats = [ pokemon.stats[Stat.HP] ].concat(target.stats.slice(1));
pokemon.summonData.battleStats = target.summonData.battleStats.slice(0); pokemon.summonData.battleStats = target.summonData.battleStats.slice(0);
pokemon.summonData.moveset = target.getMoveset().map(m => new PokemonMove(m.moveId, m.ppUsed, m.ppUp)); pokemon.summonData.moveset = target.getMoveset().map(m => new PokemonMove(m!.moveId, m!.ppUsed, m!.ppUp)); // TODO: are those bangs correct?
pokemon.summonData.types = target.getTypes(); pokemon.summonData.types = target.getTypes();
pokemon.scene.playSound("PRSFX- Transform"); pokemon.scene.playSound("PRSFX- Transform");
@ -2364,7 +2365,7 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr {
* @returns {boolean} Returns true if the weather clears, otherwise false. * @returns {boolean} Returns true if the weather clears, otherwise false.
*/ */
applyPreSwitchOut(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise<boolean> { applyPreSwitchOut(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise<boolean> {
const weatherType = pokemon.scene.arena.weather.weatherType; const weatherType = pokemon.scene.arena.weather?.weatherType;
let turnOffWeather = false; let turnOffWeather = false;
// Clear weather only if user's ability matches the weather and no other pokemon has the ability. // Clear weather only if user's ability matches the weather and no other pokemon has the ability.
@ -2445,18 +2446,18 @@ export class PreSwitchOutFormChangeAbAttr extends PreSwitchOutAbAttr {
} }
export class PreStatChangeAbAttr extends AbAttr { export class PreStatChangeAbAttr extends AbAttr {
applyPreStatChange(pokemon: Pokemon, passive: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> { applyPreStatChange(pokemon: Pokemon | null, passive: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
return false; return false;
} }
} }
export class ProtectStatAbAttr extends PreStatChangeAbAttr { export class ProtectStatAbAttr extends PreStatChangeAbAttr {
private protectedStat: BattleStat; private protectedStat: BattleStat | null;
constructor(protectedStat?: BattleStat) { constructor(protectedStat?: BattleStat) {
super(); super();
this.protectedStat = protectedStat; this.protectedStat = protectedStat!; // TODO: is this bang correct?
} }
applyPreStatChange(pokemon: Pokemon, passive: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreStatChange(pokemon: Pokemon, passive: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean {
@ -2472,7 +2473,7 @@ export class ProtectStatAbAttr extends PreStatChangeAbAttr {
return i18next.t("abilityTriggers:protectStat", { return i18next.t("abilityTriggers:protectStat", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
abilityName, abilityName,
statName: this.protectedStat !== undefined ? getBattleStatName(this.protectedStat) : i18next.t("battle:stats") statName: this.protectedStat ? getBattleStatName(this.protectedStat) : i18next.t("battle:stats")
}); });
} }
} }
@ -2512,7 +2513,7 @@ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr {
} }
export class PreSetStatusAbAttr extends AbAttr { export class PreSetStatusAbAttr extends AbAttr {
applyPreSetStatus(pokemon: Pokemon, passive: boolean, effect: StatusEffect, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> { applyPreSetStatus(pokemon: Pokemon, passive: boolean, effect: StatusEffect | undefined, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
return false; return false;
} }
} }
@ -2723,7 +2724,7 @@ export class BlockStatusDamageAbAttr extends AbAttr {
* @returns Returns true if status damage is blocked * @returns Returns true if status damage is blocked
*/ */
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.effects.includes(pokemon.status?.effect)) { if (pokemon.status && this.effects.includes(pokemon.status.effect)) {
cancelled.value = true; cancelled.value = true;
return true; return true;
} }
@ -2762,7 +2763,7 @@ export class IncrementMovePriorityAbAttr extends AbAttr {
export class IgnoreContactAbAttr extends AbAttr { } export class IgnoreContactAbAttr extends AbAttr { }
export class PreWeatherEffectAbAttr extends AbAttr { export class PreWeatherEffectAbAttr extends AbAttr {
applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> { applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, weather: Weather | null, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
return false; return false;
} }
} }
@ -2793,7 +2794,7 @@ export class SuppressWeatherEffectAbAttr extends PreWeatherEffectAbAttr {
constructor(affectsImmutable?: boolean) { constructor(affectsImmutable?: boolean) {
super(); super();
this.affectsImmutable = affectsImmutable; this.affectsImmutable = affectsImmutable!; // TODO: is this bang correct?
} }
applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean {
@ -2844,7 +2845,7 @@ function getWeatherCondition(...weatherTypes: WeatherType[]): AbAttrCondition {
return false; return false;
} }
const weatherType = pokemon.scene.arena.weather?.weatherType; const weatherType = pokemon.scene.arena.weather?.weatherType;
return weatherType && weatherTypes.indexOf(weatherType) > -1; return !!weatherType && weatherTypes.indexOf(weatherType) > -1;
}; };
} }
@ -2853,15 +2854,15 @@ function getAnticipationCondition(): AbAttrCondition {
for (const opponent of pokemon.getOpponents()) { for (const opponent of pokemon.getOpponents()) {
for (const move of opponent.moveset) { for (const move of opponent.moveset) {
// move is super effective // move is super effective
if (move.getMove() instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.getMove().type, opponent, true) >= 2) { if (move!.getMove() instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move!.getMove().type, opponent, true) >= 2) { // TODO: is this bang correct?
return true; return true;
} }
// move is a OHKO // move is a OHKO
if (move.getMove().hasAttr(OneHitKOAttr)) { if (move!.getMove().hasAttr(OneHitKOAttr)) { // TODO: is this bang correct?
return true; return true;
} }
// edge case for hidden power, type is computed // edge case for hidden power, type is computed
if (move.getMove().id === Moves.HIDDEN_POWER) { if (move!.getMove().id === Moves.HIDDEN_POWER) { // TODO: is this bang correct?
const iv_val = Math.floor(((opponent.ivs[Stat.HP] & 1) const iv_val = Math.floor(((opponent.ivs[Stat.HP] & 1)
+(opponent.ivs[Stat.ATK] & 1) * 2 +(opponent.ivs[Stat.ATK] & 1) * 2
+(opponent.ivs[Stat.DEF] & 1) * 4 +(opponent.ivs[Stat.DEF] & 1) * 4
@ -2909,21 +2910,21 @@ export class ForewarnAbAttr extends PostSummonAbAttr {
let movePower = 0; let movePower = 0;
for (const opponent of pokemon.getOpponents()) { for (const opponent of pokemon.getOpponents()) {
for (const move of opponent.moveset) { for (const move of opponent.moveset) {
if (move.getMove() instanceof StatusMove) { if (move!.getMove() instanceof StatusMove) { // TODO: is this bang correct?
movePower = 1; movePower = 1;
} else if (move.getMove().hasAttr(OneHitKOAttr)) { } else if (move!.getMove().hasAttr(OneHitKOAttr)) { // TODO: is this bang correct?
movePower = 150; movePower = 150;
} else if (move.getMove().id === Moves.COUNTER || move.getMove().id === Moves.MIRROR_COAT || move.getMove().id === Moves.METAL_BURST) { } else if (move!.getMove().id === Moves.COUNTER || move!.getMove().id === Moves.MIRROR_COAT || move!.getMove().id === Moves.METAL_BURST) { // TODO: are those bangs correct?
movePower = 120; movePower = 120;
} else if (move.getMove().power === -1) { } else if (move!.getMove().power === -1) { // TODO: is this bang correct?
movePower = 80; movePower = 80;
} else { } else {
movePower = move.getMove().power; movePower = move!.getMove().power; // TODO: is this bang correct?
} }
if (movePower > maxPowerSeen) { if (movePower > maxPowerSeen) {
maxPowerSeen = movePower; maxPowerSeen = movePower;
maxMove = move.getName(); maxMove = move!.getName(); // TODO: is this bang correct?
} }
} }
} }
@ -2984,7 +2985,7 @@ export class PostWeatherLapseAbAttr extends AbAttr {
this.weatherTypes = weatherTypes; this.weatherTypes = weatherTypes;
} }
applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, weather: Weather, args: any[]): boolean | Promise<boolean> { applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, weather: Weather | null, args: any[]): boolean | Promise<boolean> {
return false; return false;
} }
@ -3067,7 +3068,7 @@ export class PostTerrainChangeAddBattlerTagAttr extends PostTerrainChangeAbAttr
function getTerrainCondition(...terrainTypes: TerrainType[]): AbAttrCondition { function getTerrainCondition(...terrainTypes: TerrainType[]): AbAttrCondition {
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const terrainType = pokemon.scene.arena.terrain?.terrainType; const terrainType = pokemon.scene.arena.terrain?.terrainType;
return terrainType && terrainTypes.indexOf(terrainType) > -1; return !!terrainType && terrainTypes.indexOf(terrainType) > -1;
}; };
} }
@ -3099,7 +3100,7 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr {
* @returns Returns true if healed from status, false if not * @returns Returns true if healed from status, false if not
*/ */
applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise<boolean> { applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise<boolean> {
if (this.effects.includes(pokemon.status?.effect)) { if (pokemon.status && this.effects.includes(pokemon.status.effect)) {
if (!pokemon.isFullHp()) { if (!pokemon.isFullHp()) {
const scene = pokemon.scene; const scene = pokemon.scene;
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
@ -3335,7 +3336,7 @@ export class FetchBallAbAttr extends PostTurnAbAttr {
*/ */
applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean { applyPostTurn(pokemon: Pokemon, passive: boolean, args: any[]): boolean {
const lastUsed = pokemon.scene.currentBattle.lastUsedPokeball; const lastUsed = pokemon.scene.currentBattle.lastUsedPokeball;
if (lastUsed !== null && pokemon.isPlayer) { if (lastUsed !== null && !!pokemon.isPlayer) {
pokemon.scene.pokeballCounts[lastUsed]++; pokemon.scene.pokeballCounts[lastUsed]++;
pokemon.scene.currentBattle.lastUsedPokeball = null; pokemon.scene.currentBattle.lastUsedPokeball = null;
pokemon.scene.queueMessage(i18next.t("abilityTriggers:fetchBall", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokeballName: getPokeballName(lastUsed) })); pokemon.scene.queueMessage(i18next.t("abilityTriggers:fetchBall", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokeballName: getPokeballName(lastUsed) }));
@ -3616,7 +3617,8 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr {
const postBattleLoot = pokemon.scene.currentBattle.postBattleLoot; const postBattleLoot = pokemon.scene.currentBattle.postBattleLoot;
if (postBattleLoot.length) { if (postBattleLoot.length) {
const randItem = Utils.randSeedItem(postBattleLoot); const randItem = Utils.randSeedItem(postBattleLoot);
if (pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, true, 1, true)) { //@ts-ignore - TODO see below
if (pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, true, 1, true)) { // TODO: fix. This is a promise!?
postBattleLoot.splice(postBattleLoot.indexOf(randItem), 1); postBattleLoot.splice(postBattleLoot.indexOf(randItem), 1);
pokemon.scene.queueMessage(i18next.t("abilityTriggers:postBattleLoot", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), itemName: randItem.type.name })); pokemon.scene.queueMessage(i18next.t("abilityTriggers:postBattleLoot", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), itemName: randItem.type.name }));
return true; return true;
@ -3648,7 +3650,7 @@ export class PostFaintClearWeatherAbAttr extends PostFaintAbAttr {
* @returns {boolean} Returns true if the weather clears, otherwise false. * @returns {boolean} Returns true if the weather clears, otherwise false.
*/ */
applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { applyPostFaint(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
const weatherType = pokemon.scene.arena.weather.weatherType; const weatherType = pokemon.scene.arena.weather?.weatherType;
let turnOffWeather = false; let turnOffWeather = false;
// Clear weather only if user's ability matches the weather and no other pokemon has the ability. // Clear weather only if user's ability matches the weather and no other pokemon has the ability.
@ -4120,7 +4122,7 @@ export class BypassSpeedChanceAbAttr extends AbAttr {
const turnCommand = const turnCommand =
pokemon.scene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; pokemon.scene.currentBattle.turnCommands[pokemon.getBattlerIndex()];
const isCommandFight = turnCommand?.command === Command.FIGHT; const isCommandFight = turnCommand?.command === Command.FIGHT;
const move = allMoves[turnCommand.move?.move]; const move = turnCommand?.move?.move ?allMoves[turnCommand.move.move] : null;
const isDamageMove = move?.category === MoveCategory.PHYSICAL || move?.category === MoveCategory.SPECIAL; const isDamageMove = move?.category === MoveCategory.PHYSICAL || move?.category === MoveCategory.SPECIAL;
if (isCommandFight && isDamageMove) { if (isCommandFight && isDamageMove) {
@ -4139,14 +4141,14 @@ export class BypassSpeedChanceAbAttr extends AbAttr {
async function applyAbAttrsInternal<TAttr extends AbAttr>( async function applyAbAttrsInternal<TAttr extends AbAttr>(
attrType: Constructor<TAttr>, attrType: Constructor<TAttr>,
pokemon: Pokemon, pokemon: Pokemon | null,
applyFunc: AbAttrApplyFunc<TAttr>, applyFunc: AbAttrApplyFunc<TAttr>,
args: any[], args: any[],
showAbilityInstant: boolean = false, showAbilityInstant: boolean = false,
quiet: boolean = false, quiet: boolean = false,
) { ) {
for (const passive of [false, true]) { for (const passive of [false, true]) {
if (!pokemon.canApplyAbility(passive)) { if (!pokemon?.canApplyAbility(passive)) {
continue; continue;
} }
@ -4194,7 +4196,7 @@ async function applyAbAttrsInternal<TAttr extends AbAttr>(
} }
} }
export function applyAbAttrs(attrType: Constructor<AbAttr>, pokemon: Pokemon, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> { export function applyAbAttrs(attrType: Constructor<AbAttr>, pokemon: Pokemon, cancelled: Utils.BooleanHolder | null, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<AbAttr>(attrType, pokemon, (attr, passive) => attr.apply(pokemon, passive, cancelled, args), args); return applyAbAttrsInternal<AbAttr>(attrType, pokemon, (attr, passive) => attr.apply(pokemon, passive, cancelled, args), args);
} }
@ -4204,13 +4206,13 @@ export function applyPostBattleInitAbAttrs(attrType: Constructor<PostBattleInitA
} }
export function applyPreDefendAbAttrs(attrType: Constructor<PreDefendAbAttr>, export function applyPreDefendAbAttrs(attrType: Constructor<PreDefendAbAttr>,
pokemon: Pokemon, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> { pokemon: Pokemon, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, ...args: any[]): Promise<void> {
const simulated = args.length > 1 && args[1]; const simulated = args.length > 1 && args[1];
return applyAbAttrsInternal<PreDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, attacker, move, cancelled, args), args, false, simulated); return applyAbAttrsInternal<PreDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, attacker, move, cancelled, args), args, false, simulated);
} }
export function applyPostDefendAbAttrs(attrType: Constructor<PostDefendAbAttr>, export function applyPostDefendAbAttrs(attrType: Constructor<PostDefendAbAttr>,
pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise<void> { pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult | null, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostDefend(pokemon, passive, attacker, move, hitResult, args), args); return applyAbAttrsInternal<PostDefendAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostDefend(pokemon, passive, attacker, move, hitResult, args), args);
} }
@ -4240,12 +4242,12 @@ export function applyFieldBattleStatMultiplierAbAttrs(attrType: Constructor<Fiel
} }
export function applyPreAttackAbAttrs(attrType: Constructor<PreAttackAbAttr>, export function applyPreAttackAbAttrs(attrType: Constructor<PreAttackAbAttr>,
pokemon: Pokemon, defender: Pokemon, move: Move, ...args: any[]): Promise<void> { pokemon: Pokemon, defender: Pokemon | null, move: Move, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreAttack(pokemon, passive, defender, move, args), args); return applyAbAttrsInternal<PreAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreAttack(pokemon, passive, defender, move, args), args);
} }
export function applyPostAttackAbAttrs(attrType: Constructor<PostAttackAbAttr>, export function applyPostAttackAbAttrs(attrType: Constructor<PostAttackAbAttr>,
pokemon: Pokemon, defender: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise<void> { pokemon: Pokemon, defender: Pokemon, move: Move, hitResult: HitResult | null, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostAttack(pokemon, passive, defender, move, hitResult, args), args); return applyAbAttrsInternal<PostAttackAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostAttack(pokemon, passive, defender, move, hitResult, args), args);
} }
@ -4270,7 +4272,7 @@ export function applyPreSwitchOutAbAttrs(attrType: Constructor<PreSwitchOutAbAtt
} }
export function applyPreStatChangeAbAttrs(attrType: Constructor<PreStatChangeAbAttr>, export function applyPreStatChangeAbAttrs(attrType: Constructor<PreStatChangeAbAttr>,
pokemon: Pokemon, stat: BattleStat, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> { pokemon: Pokemon | null, stat: BattleStat, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreStatChangeAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreStatChange(pokemon, passive, stat, cancelled, args), args); return applyAbAttrsInternal<PreStatChangeAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreStatChange(pokemon, passive, stat, cancelled, args), args);
} }
@ -4280,7 +4282,7 @@ export function applyPostStatChangeAbAttrs(attrType: Constructor<PostStatChangeA
} }
export function applyPreSetStatusAbAttrs(attrType: Constructor<PreSetStatusAbAttr>, export function applyPreSetStatusAbAttrs(attrType: Constructor<PreSetStatusAbAttr>,
pokemon: Pokemon, effect: StatusEffect, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> { pokemon: Pokemon, effect: StatusEffect | undefined, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
const simulated = args.length > 1 && args[1]; const simulated = args.length > 1 && args[1];
return applyAbAttrsInternal<PreSetStatusAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreSetStatus(pokemon, passive, effect, cancelled, args), args, false, !simulated); return applyAbAttrsInternal<PreSetStatusAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreSetStatus(pokemon, passive, effect, cancelled, args), args, false, !simulated);
} }
@ -4291,7 +4293,7 @@ export function applyPreApplyBattlerTagAbAttrs(attrType: Constructor<PreApplyBat
} }
export function applyPreWeatherEffectAbAttrs(attrType: Constructor<PreWeatherEffectAbAttr>, export function applyPreWeatherEffectAbAttrs(attrType: Constructor<PreWeatherEffectAbAttr>,
pokemon: Pokemon, weather: Weather, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> { pokemon: Pokemon, weather: Weather | null, cancelled: Utils.BooleanHolder, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreWeatherDamageAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreWeatherEffect(pokemon, passive, weather, cancelled, args), args, true); return applyAbAttrsInternal<PreWeatherDamageAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreWeatherEffect(pokemon, passive, weather, cancelled, args), args, true);
} }
@ -4306,7 +4308,7 @@ export function applyPostWeatherChangeAbAttrs(attrType: Constructor<PostWeatherC
} }
export function applyPostWeatherLapseAbAttrs(attrType: Constructor<PostWeatherLapseAbAttr>, export function applyPostWeatherLapseAbAttrs(attrType: Constructor<PostWeatherLapseAbAttr>,
pokemon: Pokemon, weather: Weather, ...args: any[]): Promise<void> { pokemon: Pokemon, weather: Weather | null, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostWeatherLapseAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostWeatherLapse(pokemon, passive, weather, args), args); return applyAbAttrsInternal<PostWeatherLapseAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostWeatherLapse(pokemon, passive, weather, args), args);
} }
@ -4595,8 +4597,8 @@ export function initAbilities() {
.attr(TypeImmunityStatChangeAbAttr, Type.ELECTRIC, BattleStat.SPD, 1) .attr(TypeImmunityStatChangeAbAttr, Type.ELECTRIC, BattleStat.SPD, 1)
.ignorable(), .ignorable(),
new Ability(Abilities.RIVALRY, 4) new Ability(Abilities.RIVALRY, 4)
.attr(MovePowerBoostAbAttr, (user, target, move) => user.gender !== Gender.GENDERLESS && target.gender !== Gender.GENDERLESS && user.gender === target.gender, 1.25, true) .attr(MovePowerBoostAbAttr, (user, target, move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender === target?.gender, 1.25, true)
.attr(MovePowerBoostAbAttr, (user, target, move) => user.gender !== Gender.GENDERLESS && target.gender !== Gender.GENDERLESS && user.gender !== target.gender, 0.75), .attr(MovePowerBoostAbAttr, (user, target, move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender !== target?.gender, 0.75),
new Ability(Abilities.STEADFAST, 4) new Ability(Abilities.STEADFAST, 4)
.attr(FlinchStatChangeAbAttr, BattleStat.SPD, 1), .attr(FlinchStatChangeAbAttr, BattleStat.SPD, 1),
new Ability(Abilities.SNOW_CLOAK, 4) new Ability(Abilities.SNOW_CLOAK, 4)
@ -4686,7 +4688,8 @@ export function initAbilities() {
.attr(IgnoreOpponentStatChangesAbAttr) .attr(IgnoreOpponentStatChangesAbAttr)
.ignorable(), .ignorable(),
new Ability(Abilities.TINTED_LENS, 4) new Ability(Abilities.TINTED_LENS, 4)
.attr(DamageBoostAbAttr, 2, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) <= 0.5), //@ts-ignore
.attr(DamageBoostAbAttr, 2, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) <= 0.5), // TODO: fix TS issues
new Ability(Abilities.FILTER, 4) new Ability(Abilities.FILTER, 4)
.attr(ReceivedMoveDamageMultiplierAbAttr,(target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75) .attr(ReceivedMoveDamageMultiplierAbAttr,(target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75)
.ignorable(), .ignorable(),
@ -4768,9 +4771,9 @@ export function initAbilities() {
.attr(ReceivedMoveDamageMultiplierAbAttr,(target, user, move) => target.isFullHp(), 0.5) .attr(ReceivedMoveDamageMultiplierAbAttr,(target, user, move) => target.isFullHp(), 0.5)
.ignorable(), .ignorable(),
new Ability(Abilities.TOXIC_BOOST, 5) new Ability(Abilities.TOXIC_BOOST, 5)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.category === MoveCategory.PHYSICAL && (user.status?.effect === StatusEffect.POISON || user.status?.effect === StatusEffect.TOXIC), 1.5), .attr(MovePowerBoostAbAttr, (user, target, move) => move.category === MoveCategory.PHYSICAL && (user?.status?.effect === StatusEffect.POISON || user?.status?.effect === StatusEffect.TOXIC), 1.5),
new Ability(Abilities.FLARE_BOOST, 5) new Ability(Abilities.FLARE_BOOST, 5)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.category === MoveCategory.SPECIAL && user.status?.effect === StatusEffect.BURN, 1.5), .attr(MovePowerBoostAbAttr, (user, target, move) => move.category === MoveCategory.SPECIAL && user?.status?.effect === StatusEffect.BURN, 1.5),
new Ability(Abilities.HARVEST, 5) new Ability(Abilities.HARVEST, 5)
.attr( .attr(
PostTurnLootAbAttr, PostTurnLootAbAttr,
@ -4803,7 +4806,8 @@ export function initAbilities() {
.attr(WonderSkinAbAttr) .attr(WonderSkinAbAttr)
.ignorable(), .ignorable(),
new Ability(Abilities.ANALYTIC, 5) new Ability(Abilities.ANALYTIC, 5)
.attr(MovePowerBoostAbAttr, (user, target, move) => !!target.getLastXMoves(1).find(m => m.turn === target.scene.currentBattle.turn) || user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command !== Command.FIGHT, 1.3), //@ts-ignore
.attr(MovePowerBoostAbAttr, (user, target, move) => !!target?.getLastXMoves(1).find(m => m.turn === target?.scene.currentBattle.turn) || user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command !== Command.FIGHT, 1.3), // TODO fix TS issues
new Ability(Abilities.ILLUSION, 5) new Ability(Abilities.ILLUSION, 5)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
@ -4953,7 +4957,7 @@ export function initAbilities() {
new Ability(Abilities.WATER_COMPACTION, 7) new Ability(Abilities.WATER_COMPACTION, 7)
.attr(PostDefendStatChangeAbAttr, (target, user, move) => move.type === Type.WATER && move.category !== MoveCategory.STATUS, BattleStat.DEF, 2), .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.type === Type.WATER && move.category !== MoveCategory.STATUS, BattleStat.DEF, 2),
new Ability(Abilities.MERCILESS, 7) new Ability(Abilities.MERCILESS, 7)
.attr(ConditionalCritAbAttr, (user, target, move) => target.status?.effect === StatusEffect.TOXIC || target.status?.effect === StatusEffect.POISON), .attr(ConditionalCritAbAttr, (user, target, move) => target?.status?.effect === StatusEffect.TOXIC || target?.status?.effect === StatusEffect.POISON),
new Ability(Abilities.SHIELDS_DOWN, 7) new Ability(Abilities.SHIELDS_DOWN, 7)
.attr(PostBattleInitFormChangeAbAttr, () => 0) .attr(PostBattleInitFormChangeAbAttr, () => 0)
.attr(PostSummonFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0)) .attr(PostSummonFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0))
@ -4965,7 +4969,8 @@ export function initAbilities() {
.bypassFaint() .bypassFaint()
.partial(), .partial(),
new Ability(Abilities.STAKEOUT, 7) new Ability(Abilities.STAKEOUT, 7)
.attr(MovePowerBoostAbAttr, (user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.POKEMON, 2), //@ts-ignore
.attr(MovePowerBoostAbAttr, (user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.POKEMON, 2), // TODO: fix TS issues
new Ability(Abilities.WATER_BUBBLE, 7) new Ability(Abilities.WATER_BUBBLE, 7)
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5) .attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5)
.attr(MoveTypePowerBoostAbAttr, Type.WATER, 2) .attr(MoveTypePowerBoostAbAttr, Type.WATER, 2)
@ -5105,7 +5110,8 @@ export function initAbilities() {
new Ability(Abilities.PRISM_ARMOR, 7) new Ability(Abilities.PRISM_ARMOR, 7)
.attr(ReceivedMoveDamageMultiplierAbAttr,(target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75), .attr(ReceivedMoveDamageMultiplierAbAttr,(target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75),
new Ability(Abilities.NEUROFORCE, 7) new Ability(Abilities.NEUROFORCE, 7)
.attr(MovePowerBoostAbAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 1.25), //@ts-ignore
.attr(MovePowerBoostAbAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 1.25), // TODO: fix TS issues
new Ability(Abilities.INTREPID_SWORD, 8) new Ability(Abilities.INTREPID_SWORD, 8)
.attr(PostSummonStatChangeAbAttr, BattleStat.ATK, 1, true) .attr(PostSummonStatChangeAbAttr, BattleStat.ATK, 1, true)
.condition(getOncePerBattleCondition(Abilities.INTREPID_SWORD)), .condition(getOncePerBattleCondition(Abilities.INTREPID_SWORD)),
@ -5192,8 +5198,10 @@ export function initAbilities() {
.attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) .attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
.ignorable(), .ignorable(),
new Ability(Abilities.HUNGER_SWITCH, 8) new Ability(Abilities.HUNGER_SWITCH, 8)
.attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 0 : 1) //@ts-ignore
.attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 1 : 0) .attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 0 : 1) // TODO: fix ts-ignore
//@ts-ignore
.attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 1 : 0) // TODO: fix ts-ignore
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr) .attr(NoTransformAbilityAbAttr)

View File

@ -25,12 +25,12 @@ export enum ArenaTagSide {
export abstract class ArenaTag { export abstract class ArenaTag {
public tagType: ArenaTagType; public tagType: ArenaTagType;
public turnCount: integer; public turnCount: integer;
public sourceMove: Moves; public sourceMove?: Moves;
public sourceId: integer; public sourceId?: integer;
public side: ArenaTagSide; public side: ArenaTagSide;
constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId?: integer, side: ArenaTagSide = ArenaTagSide.BOTH) { constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId?: integer, side: ArenaTagSide = ArenaTagSide.BOTH) {
this.tagType = tagType; this.tagType = tagType;
this.turnCount = turnCount; this.turnCount = turnCount;
this.sourceMove = sourceMove; this.sourceMove = sourceMove;
@ -56,7 +56,7 @@ export abstract class ArenaTag {
return this.turnCount < 1 || !!(--this.turnCount); return this.turnCount < 1 || !!(--this.turnCount);
} }
getMoveName(): string { getMoveName(): string | null {
return this.sourceMove return this.sourceMove
? allMoves[this.sourceMove].name ? allMoves[this.sourceMove].name
: null; : null;
@ -75,9 +75,14 @@ export class MistTag extends ArenaTag {
onAdd(arena: Arena, quiet: boolean = false): void { onAdd(arena: Arena, quiet: boolean = false): void {
super.onAdd(arena); super.onAdd(arena);
if (this.sourceId) {
const source = arena.scene.getPokemonById(this.sourceId); const source = arena.scene.getPokemonById(this.sourceId);
if (!quiet) {
if (!quiet && source) {
arena.scene.queueMessage(i18next.t("arenaTag:mistOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) })); arena.scene.queueMessage(i18next.t("arenaTag:mistOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) }));
} else if (!quiet) {
console.warn("Failed to get source for MistTag onAdd");
}
} }
} }
@ -280,8 +285,14 @@ class MatBlockTag extends ConditionalProtectTag {
} }
onAdd(arena: Arena) { onAdd(arena: Arena) {
if (this.sourceId) {
const source = arena.scene.getPokemonById(this.sourceId); const source = arena.scene.getPokemonById(this.sourceId);
if (source) {
arena.scene.queueMessage(i18next.t("arenaTag:matBlockOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) })); arena.scene.queueMessage(i18next.t("arenaTag:matBlockOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) }));
} else {
console.warn("Failed to get source for MatBlockTag onAdd");
}
}
} }
} }
@ -328,9 +339,9 @@ export class NoCritTag extends ArenaTag {
/** Queues a message upon removing this effect from the field */ /** Queues a message upon removing this effect from the field */
onRemove(arena: Arena): void { onRemove(arena: Arena): void {
const source = arena.scene.getPokemonById(this.sourceId); const source = arena.scene.getPokemonById(this.sourceId!); // TODO: is this bang correct?
arena.scene.queueMessage(i18next.t("arenaTag:noCritOnRemove", { arena.scene.queueMessage(i18next.t("arenaTag:noCritOnRemove", {
pokemonNameWithAffix: getPokemonNameWithAffix(source), pokemonNameWithAffix: getPokemonNameWithAffix(source ?? undefined),
moveName: this.getMoveName() moveName: this.getMoveName()
})); }));
} }
@ -350,10 +361,16 @@ class WishTag extends ArenaTag {
} }
onAdd(arena: Arena): void { onAdd(arena: Arena): void {
if (this.sourceId) {
const user = arena.scene.getPokemonById(this.sourceId); const user = arena.scene.getPokemonById(this.sourceId);
if (user) {
this.battlerIndex = user.getBattlerIndex(); this.battlerIndex = user.getBattlerIndex();
this.triggerMessage = i18next.t("arenaTag:wishTagOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user) }); this.triggerMessage = i18next.t("arenaTag:wishTagOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user) });
this.healHp = Math.max(Math.floor(user.getMaxHp() / 2), 1); this.healHp = Math.max(Math.floor(user.getMaxHp() / 2), 1);
} else {
console.warn("Failed to get source for WishTag onAdd");
}
}
} }
onRemove(arena: Arena): void { onRemove(arena: Arena): void {
@ -494,8 +511,8 @@ class SpikesTag extends ArenaTrapTag {
onAdd(arena: Arena, quiet: boolean = false): void { onAdd(arena: Arena, quiet: boolean = false): void {
super.onAdd(arena); super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId); const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null;
if (!quiet) { if (!quiet && source) {
arena.scene.queueMessage(i18next.t("arenaTag:spikesOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() })); arena.scene.queueMessage(i18next.t("arenaTag:spikesOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() }));
} }
} }
@ -539,8 +556,8 @@ class ToxicSpikesTag extends ArenaTrapTag {
onAdd(arena: Arena, quiet: boolean = false): void { onAdd(arena: Arena, quiet: boolean = false): void {
super.onAdd(arena); super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId); const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null;
if (!quiet) { if (!quiet && source) {
arena.scene.queueMessage(i18next.t("arenaTag:toxicSpikesOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() })); arena.scene.queueMessage(i18next.t("arenaTag:toxicSpikesOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() }));
} }
} }
@ -589,7 +606,7 @@ class ToxicSpikesTag extends ArenaTrapTag {
class DelayedAttackTag extends ArenaTag { class DelayedAttackTag extends ArenaTag {
public targetIndex: BattlerIndex; public targetIndex: BattlerIndex;
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, targetIndex: BattlerIndex) { constructor(tagType: ArenaTagType, sourceMove: Moves | undefined, sourceId: integer, targetIndex: BattlerIndex) {
super(tagType, 3, sourceMove, sourceId); super(tagType, 3, sourceMove, sourceId);
this.targetIndex = targetIndex; this.targetIndex = targetIndex;
@ -599,7 +616,7 @@ class DelayedAttackTag extends ArenaTag {
const ret = super.lapse(arena); const ret = super.lapse(arena);
if (!ret) { if (!ret) {
arena.scene.unshiftPhase(new MoveEffectPhase(arena.scene, this.sourceId, [ this.targetIndex ], new PokemonMove(this.sourceMove, 0, 0, true))); arena.scene.unshiftPhase(new MoveEffectPhase(arena.scene, this.sourceId!, [ this.targetIndex ], new PokemonMove(this.sourceMove!, 0, 0, true))); // TODO: are those bangs correct?
} }
return ret; return ret;
@ -621,8 +638,8 @@ class StealthRockTag extends ArenaTrapTag {
onAdd(arena: Arena, quiet: boolean = false): void { onAdd(arena: Arena, quiet: boolean = false): void {
super.onAdd(arena); super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId); const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null;
if (!quiet) { if (!quiet && source) {
arena.scene.queueMessage(i18next.t("arenaTag:stealthRockOnAdd", { opponentDesc: source.getOpponentDescriptor() })); arena.scene.queueMessage(i18next.t("arenaTag:stealthRockOnAdd", { opponentDesc: source.getOpponentDescriptor() }));
} }
} }
@ -630,7 +647,7 @@ class StealthRockTag extends ArenaTrapTag {
getDamageHpRatio(pokemon: Pokemon): number { getDamageHpRatio(pokemon: Pokemon): number {
const effectiveness = pokemon.getAttackTypeEffectiveness(Type.ROCK, undefined, true); const effectiveness = pokemon.getAttackTypeEffectiveness(Type.ROCK, undefined, true);
let damageHpRatio: number; let damageHpRatio: number = 0;
switch (effectiveness) { switch (effectiveness) {
case 0: case 0:
@ -696,8 +713,8 @@ class StickyWebTag extends ArenaTrapTag {
onAdd(arena: Arena, quiet: boolean = false): void { onAdd(arena: Arena, quiet: boolean = false): void {
super.onAdd(arena); super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId); const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null;
if (!quiet) { if (!quiet && source) {
arena.scene.queueMessage(i18next.t("arenaTag:stickyWebOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() })); arena.scene.queueMessage(i18next.t("arenaTag:stickyWebOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() }));
} }
} }
@ -735,7 +752,10 @@ export class TrickRoomTag extends ArenaTag {
} }
onAdd(arena: Arena): void { onAdd(arena: Arena): void {
arena.scene.queueMessage(i18next.t("arenaTag:trickRoomOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(arena.scene.getPokemonById(this.sourceId)) })); const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null;
if (source) {
arena.scene.queueMessage(i18next.t("arenaTag:trickRoomOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) }));
}
} }
onRemove(arena: Arena): void { onRemove(arena: Arena): void {
@ -782,8 +802,8 @@ class TailwindTag extends ArenaTag {
arena.scene.queueMessage(i18next.t(`arenaTag:tailwindOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); arena.scene.queueMessage(i18next.t(`arenaTag:tailwindOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
} }
const source = arena.scene.getPokemonById(this.sourceId); const source = arena.scene.getPokemonById(this.sourceId!); //TODO: this bang is questionable!
const party = source.isPlayer() ? source.scene.getPlayerField() : source.scene.getEnemyField(); const party = (source?.isPlayer() ? source.scene.getPlayerField() : source?.scene.getEnemyField()) ?? [];
for (const pokemon of party) { for (const pokemon of party) {
// Apply the CHARGED tag to party members with the WIND_POWER ability // Apply the CHARGED tag to party members with the WIND_POWER ability
@ -824,7 +844,7 @@ class HappyHourTag extends ArenaTag {
} }
} }
export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag { export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null {
switch (tagType) { switch (tagType) {
case ArenaTagType.MIST: case ArenaTagType.MIST:
return new MistTag(turnCount, sourceId, side); return new MistTag(turnCount, sourceId, side);
@ -837,7 +857,7 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
case ArenaTagType.CRAFTY_SHIELD: case ArenaTagType.CRAFTY_SHIELD:
return new CraftyShieldTag(sourceId, side); return new CraftyShieldTag(sourceId, side);
case ArenaTagType.NO_CRIT: case ArenaTagType.NO_CRIT:
return new NoCritTag(turnCount, sourceMove, sourceId, side); return new NoCritTag(turnCount, sourceMove!, sourceId, side); // TODO: is this bang correct?
case ArenaTagType.MUD_SPORT: case ArenaTagType.MUD_SPORT:
return new MudSportTag(turnCount, sourceId); return new MudSportTag(turnCount, sourceId);
case ArenaTagType.WATER_SPORT: case ArenaTagType.WATER_SPORT:
@ -848,7 +868,7 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
return new ToxicSpikesTag(sourceId, side); return new ToxicSpikesTag(sourceId, side);
case ArenaTagType.FUTURE_SIGHT: case ArenaTagType.FUTURE_SIGHT:
case ArenaTagType.DOOM_DESIRE: case ArenaTagType.DOOM_DESIRE:
return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex); return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex!); // TODO:questionable bang
case ArenaTagType.WISH: case ArenaTagType.WISH:
return new WishTag(turnCount, sourceId, side); return new WishTag(turnCount, sourceId, side);
case ArenaTagType.STEALTH_ROCK: case ArenaTagType.STEALTH_ROCK:
@ -869,5 +889,7 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
return new TailwindTag(turnCount, sourceId, side); return new TailwindTag(turnCount, sourceId, side);
case ArenaTagType.HAPPY_HOUR: case ArenaTagType.HAPPY_HOUR:
return new HappyHourTag(turnCount, sourceId, side); return new HappyHourTag(turnCount, sourceId, side);
default:
return null;
} }
} }

View File

@ -128,7 +128,7 @@ export class AnimConfig {
for (const fte of Object.keys(frameTimedEvents)) { for (const fte of Object.keys(frameTimedEvents)) {
const timedEvents: AnimTimedEvent[] = []; const timedEvents: AnimTimedEvent[] = [];
for (const te of frameTimedEvents[fte]) { for (const te of frameTimedEvents[fte]) {
let timedEvent: AnimTimedEvent; let timedEvent: AnimTimedEvent | undefined;
switch (te.eventType) { switch (te.eventType) {
case "AnimTimedSoundEvent": case "AnimTimedSoundEvent":
timedEvent = new AnimTimedSoundEvent(te.frameIndex, te.resourceName, te); timedEvent = new AnimTimedSoundEvent(te.frameIndex, te.resourceName, te);
@ -140,7 +140,8 @@ export class AnimConfig {
timedEvent = new AnimTimedUpdateBgEvent(te.frameIndex, te.resourceName, te); timedEvent = new AnimTimedUpdateBgEvent(te.frameIndex, te.resourceName, te);
break; break;
} }
timedEvents.push(timedEvent);
timedEvent && timedEvents.push(timedEvent);
} }
this.frameTimedEvents.set(parseInt(fte), timedEvents); this.frameTimedEvents.set(parseInt(fte), timedEvents);
} }
@ -330,7 +331,7 @@ class AnimTimedSoundEvent extends AnimTimedEvent {
} }
return Math.ceil((scene.sound.get(this.resourceName).totalDuration * 1000) / 33.33); return Math.ceil((scene.sound.get(this.resourceName).totalDuration * 1000) / 33.33);
} else { } else {
return Math.ceil((battleAnim.user.cry(soundConfig).totalDuration * 1000) / 33.33); return Math.ceil((battleAnim.user!.cry(soundConfig).totalDuration * 1000) / 33.33); // TODO: is the bang behind user correct?
} }
} }
@ -441,15 +442,15 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
} }
} }
export const moveAnims = new Map<Moves, AnimConfig | [AnimConfig, AnimConfig]>(); export const moveAnims = new Map<Moves, AnimConfig | [AnimConfig, AnimConfig] | null>();
export const chargeAnims = new Map<ChargeAnim, AnimConfig | [AnimConfig, AnimConfig]>(); export const chargeAnims = new Map<ChargeAnim, AnimConfig | [AnimConfig, AnimConfig] | null>();
export const commonAnims = new Map<CommonAnim, AnimConfig>(); export const commonAnims = new Map<CommonAnim, AnimConfig>();
export function initCommonAnims(scene: BattleScene): Promise<void> { export function initCommonAnims(scene: BattleScene): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
const commonAnimNames = Utils.getEnumKeys(CommonAnim); const commonAnimNames = Utils.getEnumKeys(CommonAnim);
const commonAnimIds = Utils.getEnumValues(CommonAnim); const commonAnimIds = Utils.getEnumValues(CommonAnim);
const commonAnimFetches = []; const commonAnimFetches: Promise<Map<CommonAnim, AnimConfig>>[] = [];
for (let ca = 0; ca < commonAnimIds.length; ca++) { for (let ca = 0; ca < commonAnimIds.length; ca++) {
const commonAnimId = commonAnimIds[ca]; const commonAnimId = commonAnimIds[ca];
commonAnimFetches.push(scene.cachedFetch(`./battle-anims/common-${commonAnimNames[ca].toLowerCase().replace(/\_/g, "-")}.json`) commonAnimFetches.push(scene.cachedFetch(`./battle-anims/common-${commonAnimNames[ca].toLowerCase().replace(/\_/g, "-")}.json`)
@ -572,7 +573,7 @@ export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLo
const chargeAttr = allMoves[moveId].getAttrs(ChargeAttr)[0] || allMoves[moveId].getAttrs(DelayedAttackAttr)[0]; const chargeAttr = allMoves[moveId].getAttrs(ChargeAttr)[0] || allMoves[moveId].getAttrs(DelayedAttackAttr)[0];
if (chargeAttr) { if (chargeAttr) {
const moveChargeAnims = chargeAnims.get(chargeAttr.chargeAnim); const moveChargeAnims = chargeAnims.get(chargeAttr.chargeAnim);
moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims[0]); moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims![0]); // TODO: is the bang correct?
if (Array.isArray(moveChargeAnims)) { if (Array.isArray(moveChargeAnims)) {
moveAnimations.push(moveChargeAnims[1]); moveAnimations.push(moveChargeAnims[1]);
} }
@ -668,21 +669,21 @@ interface SpriteCache {
} }
export abstract class BattleAnim { export abstract class BattleAnim {
public user: Pokemon; public user: Pokemon | null;
public target: Pokemon; public target: Pokemon | null;
public sprites: Phaser.GameObjects.Sprite[]; public sprites: Phaser.GameObjects.Sprite[];
public bgSprite: Phaser.GameObjects.TileSprite | Phaser.GameObjects.Rectangle; public bgSprite: Phaser.GameObjects.TileSprite | Phaser.GameObjects.Rectangle;
private srcLine: number[]; private srcLine: number[];
private dstLine: number[]; private dstLine: number[];
constructor(user: Pokemon, target: Pokemon) { constructor(user?: Pokemon, target?: Pokemon) {
this.user = user; this.user = user!; // TODO: is this bang correct?
this.target = target; this.target = target!; // TODO: is this bang correct?
this.sprites = []; this.sprites = [];
} }
abstract getAnim(): AnimConfig; abstract getAnim(): AnimConfig | null;
abstract isOppAnim(): boolean; abstract isOppAnim(): boolean;
@ -705,12 +706,12 @@ export abstract class BattleAnim {
const user = !isOppAnim ? this.user : this.target; const user = !isOppAnim ? this.user : this.target;
const target = !isOppAnim ? this.target : this.user; const target = !isOppAnim ? this.target : this.user;
const userInitialX = user.x; const userInitialX = user!.x; // TODO: is this bang correct?
const userInitialY = user.y; const userInitialY = user!.y; // TODO: is this bang correct?
const userHalfHeight = user.getSprite().displayHeight / 2; const userHalfHeight = user!.getSprite().displayHeight! / 2; // TODO: is this bang correct?
const targetInitialX = target.x; const targetInitialX = target!.x; // TODO: is this bang correct?
const targetInitialY = target.y; const targetInitialY = target!.y; // TODO: is this bang correct?
const targetHalfHeight = target.getSprite().displayHeight / 2; const targetHalfHeight = target!.getSprite().displayHeight! / 2; // TODO: is this bang correct?
let g = 0; let g = 0;
let u = 0; let u = 0;
@ -742,7 +743,7 @@ export abstract class BattleAnim {
} }
const angle = -frame.angle; const angle = -frame.angle;
const key = frame.target === AnimFrameTarget.GRAPHIC ? g++ : frame.target === AnimFrameTarget.USER ? u++ : t++; const key = frame.target === AnimFrameTarget.GRAPHIC ? g++ : frame.target === AnimFrameTarget.USER ? u++ : t++;
ret.get(frame.target).set(key, { x: x, y: y, scaleX: scaleX, scaleY: scaleY, angle: angle }); ret.get(frame.target)!.set(key, { x: x, y: y, scaleX: scaleX, scaleY: scaleY, angle: angle }); // TODO: is the bang correct?
} }
return ret; return ret;
@ -750,10 +751,10 @@ export abstract class BattleAnim {
play(scene: BattleScene, callback?: Function) { play(scene: BattleScene, callback?: Function) {
const isOppAnim = this.isOppAnim(); const isOppAnim = this.isOppAnim();
const user = !isOppAnim ? this.user : this.target; const user = !isOppAnim ? this.user! : this.target!; // TODO: are those bangs correct?
const target = !isOppAnim ? this.target : this.user; const target = !isOppAnim ? this.target : this.user;
if (!target.isOnField()) { if (!target?.isOnField()) {
if (callback) { if (callback) {
callback(); callback();
} }
@ -781,7 +782,7 @@ export abstract class BattleAnim {
targetSprite.setAlpha(1); targetSprite.setAlpha(1);
targetSprite.pipelineData["tone"] = [ 0.0, 0.0, 0.0, 0.0 ]; targetSprite.pipelineData["tone"] = [ 0.0, 0.0, 0.0, 0.0 ];
targetSprite.setAngle(0); targetSprite.setAngle(0);
if (!this.isHideUser()) { if (!this.isHideUser() && userSprite) {
userSprite.setVisible(true); userSprite.setVisible(true);
} }
if (!this.isHideTarget() && (targetSprite !== userSprite || !this.isHideUser())) { if (!this.isHideTarget() && (targetSprite !== userSprite || !this.isHideUser())) {
@ -814,20 +815,20 @@ export abstract class BattleAnim {
this.srcLine = [ userFocusX, userFocusY, targetFocusX, targetFocusY ]; this.srcLine = [ userFocusX, userFocusY, targetFocusX, targetFocusY ];
this.dstLine = [ userInitialX, userInitialY, targetInitialX, targetInitialY ]; this.dstLine = [ userInitialX, userInitialY, targetInitialX, targetInitialY ];
let r = anim.frames.length; let r = anim!.frames.length; // TODO: is this bang correct?
let f = 0; let f = 0;
scene.tweens.addCounter({ scene.tweens.addCounter({
duration: Utils.getFrameMs(3), duration: Utils.getFrameMs(3),
repeat: anim.frames.length, repeat: anim!.frames.length, // TODO: is this bang correct?
onRepeat: () => { onRepeat: () => {
if (!f) { if (!f) {
userSprite.setVisible(false); userSprite.setVisible(false);
targetSprite.setVisible(false); targetSprite.setVisible(false);
} }
const spriteFrames = anim.frames[f]; const spriteFrames = anim!.frames[f]; // TODO: is the bang correcT?
const frameData = this.getGraphicFrameData(scene, anim.frames[f]); const frameData = this.getGraphicFrameData(scene, anim!.frames[f]); // TODO: is the bang correct?
let u = 0; let u = 0;
let t = 0; let t = 0;
let g = 0; let g = 0;
@ -840,9 +841,9 @@ export abstract class BattleAnim {
const sprites = spriteCache[isUser ? AnimFrameTarget.USER : AnimFrameTarget.TARGET]; const sprites = spriteCache[isUser ? AnimFrameTarget.USER : AnimFrameTarget.TARGET];
const spriteSource = isUser ? userSprite : targetSprite; const spriteSource = isUser ? userSprite : targetSprite;
if ((isUser ? u : t) === sprites.length) { if ((isUser ? u : t) === sprites.length) {
const sprite = scene.addPokemonSprite(isUser ? user : target, 0, 0, spriteSource.texture, spriteSource.frame.name, true); const sprite = scene.addPokemonSprite(isUser ? user! : target, 0, 0, spriteSource!.texture, spriteSource!.frame.name, true); // TODO: are those bangs correct?
[ "spriteColors", "fusionSpriteColors" ].map(k => sprite.pipelineData[k] = (isUser ? user : target).getSprite().pipelineData[k]); [ "spriteColors", "fusionSpriteColors" ].map(k => sprite.pipelineData[k] = (isUser ? user! : target).getSprite().pipelineData[k]); // TODO: are those bangs correct?
sprite.setPipelineData("spriteKey", (isUser ? user : target).getBattleSpriteKey()); sprite.setPipelineData("spriteKey", (isUser ? user! : target).getBattleSpriteKey());
sprite.setPipelineData("shiny", (isUser ? user : target).shiny); sprite.setPipelineData("shiny", (isUser ? user : target).shiny);
sprite.setPipelineData("variant", (isUser ? user : target).variant); sprite.setPipelineData("variant", (isUser ? user : target).variant);
sprite.setPipelineData("ignoreFieldPos", true); sprite.setPipelineData("ignoreFieldPos", true);
@ -853,7 +854,7 @@ export abstract class BattleAnim {
const spriteIndex = isUser ? u++ : t++; const spriteIndex = isUser ? u++ : t++;
const pokemonSprite = sprites[spriteIndex]; const pokemonSprite = sprites[spriteIndex];
const graphicFrameData = frameData.get(frame.target).get(spriteIndex); const graphicFrameData = frameData.get(frame.target)!.get(spriteIndex)!; // TODO: are the bangs correct?
pokemonSprite.setPosition(graphicFrameData.x, graphicFrameData.y - ((spriteSource.height / 2) * (spriteSource.parentContainer.scale - 1))); pokemonSprite.setPosition(graphicFrameData.x, graphicFrameData.y - ((spriteSource.height / 2) * (spriteSource.parentContainer.scale - 1)));
pokemonSprite.setAngle(graphicFrameData.angle); pokemonSprite.setAngle(graphicFrameData.angle);
@ -868,7 +869,7 @@ export abstract class BattleAnim {
} else { } else {
const sprites = spriteCache[AnimFrameTarget.GRAPHIC]; const sprites = spriteCache[AnimFrameTarget.GRAPHIC];
if (g === sprites.length) { if (g === sprites.length) {
const newSprite: Phaser.GameObjects.Sprite = scene.addFieldSprite(0, 0, anim.graphic, 1); const newSprite: Phaser.GameObjects.Sprite = scene.addFieldSprite(0, 0, anim!.graphic, 1); // TODO: is the bang correct?
sprites.push(newSprite); sprites.push(newSprite);
scene.field.add(newSprite); scene.field.add(newSprite);
spritePriorities.push(1); spritePriorities.push(1);
@ -881,7 +882,7 @@ export abstract class BattleAnim {
const setSpritePriority = (priority: integer) => { const setSpritePriority = (priority: integer) => {
switch (priority) { switch (priority) {
case 0: case 0:
scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, scene.getEnemyPokemon() || scene.getPlayerPokemon()); scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, scene.getEnemyPokemon() || scene.getPlayerPokemon()!); // TODO: is this bang correct?
break; break;
case 1: case 1:
scene.field.moveTo(moveSprite, scene.field.getAll().length - 1); scene.field.moveTo(moveSprite, scene.field.getAll().length - 1);
@ -892,11 +893,11 @@ export abstract class BattleAnim {
if (this.bgSprite) { if (this.bgSprite) {
scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite); scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite);
} else { } else {
scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user); scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct?
} }
break; break;
case AnimFocus.TARGET: case AnimFocus.TARGET:
scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target); scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct?
break; break;
default: default:
setSpritePriority(1); setSpritePriority(1);
@ -906,10 +907,10 @@ export abstract class BattleAnim {
case 3: case 3:
switch (frame.focus) { switch (frame.focus) {
case AnimFocus.USER: case AnimFocus.USER:
scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user); scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct?
break; break;
case AnimFocus.TARGET: case AnimFocus.TARGET:
scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target); scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct?
break; break;
default: default:
setSpritePriority(1); setSpritePriority(1);
@ -925,7 +926,7 @@ export abstract class BattleAnim {
moveSprite.setFrame(frame.graphicFrame); moveSprite.setFrame(frame.graphicFrame);
//console.log(AnimFocus[frame.focus]); //console.log(AnimFocus[frame.focus]);
const graphicFrameData = frameData.get(frame.target).get(graphicIndex); const graphicFrameData = frameData.get(frame.target)!.get(graphicIndex)!; // TODO: are those bangs correct?
moveSprite.setPosition(graphicFrameData.x, graphicFrameData.y); moveSprite.setPosition(graphicFrameData.x, graphicFrameData.y);
moveSprite.setAngle(graphicFrameData.angle); moveSprite.setAngle(graphicFrameData.angle);
moveSprite.setScale(graphicFrameData.scaleX, graphicFrameData.scaleY); moveSprite.setScale(graphicFrameData.scaleX, graphicFrameData.scaleY);
@ -935,8 +936,8 @@ export abstract class BattleAnim {
moveSprite.setBlendMode(frame.blendType === AnimBlendType.NORMAL ? Phaser.BlendModes.NORMAL : frame.blendType === AnimBlendType.ADD ? Phaser.BlendModes.ADD : Phaser.BlendModes.DIFFERENCE); moveSprite.setBlendMode(frame.blendType === AnimBlendType.NORMAL ? Phaser.BlendModes.NORMAL : frame.blendType === AnimBlendType.ADD ? Phaser.BlendModes.ADD : Phaser.BlendModes.DIFFERENCE);
} }
} }
if (anim.frameTimedEvents.has(f)) { if (anim?.frameTimedEvents.has(f)) {
for (const event of anim.frameTimedEvents.get(f)) { for (const event of anim.frameTimedEvents.get(f)!) { // TODO: is this bang correct?
r = Math.max((anim.frames.length - f) + event.execute(scene, this), r); r = Math.max((anim.frames.length - f) + event.execute(scene, this), r);
} }
} }
@ -980,16 +981,16 @@ export abstract class BattleAnim {
} }
export class CommonBattleAnim extends BattleAnim { export class CommonBattleAnim extends BattleAnim {
public commonAnim: CommonAnim; public commonAnim: CommonAnim | null;
constructor(commonAnim: CommonAnim, user: Pokemon, target?: Pokemon) { constructor(commonAnim: CommonAnim | null, user: Pokemon, target?: Pokemon) {
super(user, target || user); super(user, target || user);
this.commonAnim = commonAnim; this.commonAnim = commonAnim;
} }
getAnim(): AnimConfig { getAnim(): AnimConfig | null {
return commonAnims.get(this.commonAnim); return this.commonAnim ? commonAnims.get(this.commonAnim)! : null; // TODO: is this bang correct?
} }
isOppAnim(): boolean { isOppAnim(): boolean {
@ -1009,11 +1010,11 @@ export class MoveAnim extends BattleAnim {
getAnim(): AnimConfig { getAnim(): AnimConfig {
return moveAnims.get(this.move) instanceof AnimConfig return moveAnims.get(this.move) instanceof AnimConfig
? moveAnims.get(this.move) as AnimConfig ? moveAnims.get(this.move) as AnimConfig
: moveAnims.get(this.move)[this.user.isPlayer() ? 0 : 1] as AnimConfig; : moveAnims.get(this.move)![this.user?.isPlayer() ? 0 : 1] as AnimConfig; // TODO: is this bang correct?
} }
isOppAnim(): boolean { isOppAnim(): boolean {
return !this.user.isPlayer() && Array.isArray(moveAnims.get(this.move)); return !this.user?.isPlayer() && Array.isArray(moveAnims.get(this.move));
} }
protected isHideUser(): boolean { protected isHideUser(): boolean {
@ -1035,13 +1036,13 @@ export class MoveChargeAnim extends MoveAnim {
} }
isOppAnim(): boolean { isOppAnim(): boolean {
return !this.user.isPlayer() && Array.isArray(chargeAnims.get(this.chargeAnim)); return !this.user?.isPlayer() && Array.isArray(chargeAnims.get(this.chargeAnim));
} }
getAnim(): AnimConfig { getAnim(): AnimConfig {
return chargeAnims.get(this.chargeAnim) instanceof AnimConfig return chargeAnims.get(this.chargeAnim) instanceof AnimConfig
? chargeAnims.get(this.chargeAnim) as AnimConfig ? chargeAnims.get(this.chargeAnim) as AnimConfig
: chargeAnims.get(this.chargeAnim)[this.user.isPlayer() ? 0 : 1] as AnimConfig; : chargeAnims.get(this.chargeAnim)![this.user?.isPlayer() ? 0 : 1] as AnimConfig; // TODO: is this bang correct?
} }
} }
@ -1059,19 +1060,19 @@ export async function populateAnims() {
moveNameToId[moveName] = move; moveNameToId[moveName] = move;
} }
const seNames = [];//(await fs.readdir('./public/audio/se/battle_anims/')).map(se => se.toString()); const seNames: string[] = [];//(await fs.readdir('./public/audio/se/battle_anims/')).map(se => se.toString());
const animsData = [];//battleAnimRawData.split('!ruby/array:PBAnimation').slice(1); const animsData : any[] = [];//battleAnimRawData.split('!ruby/array:PBAnimation').slice(1); // TODO: add a proper type
for (let a = 0; a < animsData.length; a++) { for (let a = 0; a < animsData.length; a++) {
const fields = animsData[a].split("@").slice(1); const fields = animsData[a].split("@").slice(1);
const nameField = fields.find(f => f.startsWith("name: ")); const nameField = fields.find(f => f.startsWith("name: "));
let isOppMove: boolean; let isOppMove: boolean | undefined;
let commonAnimId: CommonAnim; let commonAnimId: CommonAnim | undefined;
let chargeAnimId: ChargeAnim; let chargeAnimId: ChargeAnim | undefined;
if (!nameField.startsWith("name: Move:") && !(isOppMove = nameField.startsWith("name: OppMove:"))) { if (!nameField.startsWith("name: Move:") && !(isOppMove = nameField.startsWith("name: OppMove:"))) {
const nameMatch = commonNamePattern.exec(nameField); const nameMatch = commonNamePattern.exec(nameField)!; // TODO: is this bang correct?
const name = nameMatch[2].toLowerCase(); const name = nameMatch[2].toLowerCase();
if (commonAnimMatchNames.indexOf(name) > -1) { if (commonAnimMatchNames.indexOf(name) > -1) {
commonAnimId = commonAnimIds[commonAnimMatchNames.indexOf(name)]; commonAnimId = commonAnimIds[commonAnimMatchNames.indexOf(name)];
@ -1128,14 +1129,14 @@ export async function populateAnims() {
for (let t = 0; t < timingEntries.length; t++) { for (let t = 0; t < timingEntries.length; t++) {
const timingData = timingEntries[t].replace(/\n/g, " ").replace(/[ ]{2,}/g, " ").replace(/[a-z]+: ! '', /ig, "").replace(/name: (.*?),/, "name: \"$1\",") const timingData = timingEntries[t].replace(/\n/g, " ").replace(/[ ]{2,}/g, " ").replace(/[a-z]+: ! '', /ig, "").replace(/name: (.*?),/, "name: \"$1\",")
.replace(/flashColor: !ruby\/object:Color { alpha: ([\d\.]+), blue: ([\d\.]+), green: ([\d\.]+), red: ([\d\.]+)}/, "flashRed: $4, flashGreen: $3, flashBlue: $2, flashAlpha: $1"); .replace(/flashColor: !ruby\/object:Color { alpha: ([\d\.]+), blue: ([\d\.]+), green: ([\d\.]+), red: ([\d\.]+)}/, "flashRed: $4, flashGreen: $3, flashBlue: $2, flashAlpha: $1");
const frameIndex = parseInt(/frame: (\d+)/.exec(timingData)[1]); const frameIndex = parseInt(/frame: (\d+)/.exec(timingData)![1]); // TODO: is the bang correct?
let resourceName = /name: "(.*?)"/.exec(timingData)[1].replace("''", ""); let resourceName = /name: "(.*?)"/.exec(timingData)![1].replace("''", ""); // TODO: is the bang correct?
const timingType = parseInt(/timingType: (\d)/.exec(timingData)[1]); const timingType = parseInt(/timingType: (\d)/.exec(timingData)![1]); // TODO: is the bang correct?
let timedEvent: AnimTimedEvent; let timedEvent: AnimTimedEvent | undefined;
switch (timingType) { switch (timingType) {
case 0: case 0:
if (resourceName && resourceName.indexOf(".") === -1) { if (resourceName && resourceName.indexOf(".") === -1) {
let ext: string; let ext: string | undefined;
[ "wav", "mp3", "m4a" ].every(e => { [ "wav", "mp3", "m4a" ].every(e => {
if (seNames.indexOf(`${resourceName}.${e}`) > -1) { if (seNames.indexOf(`${resourceName}.${e}`) > -1) {
ext = e; ext = e;
@ -1162,7 +1163,7 @@ export async function populateAnims() {
} }
const propPattern = /([a-z]+): (.*?)(?:,|\})/ig; const propPattern = /([a-z]+): (.*?)(?:,|\})/ig;
let propMatch: RegExpExecArray; let propMatch: RegExpExecArray;
while ((propMatch = propPattern.exec(timingData))) { while ((propMatch = propPattern.exec(timingData)!)) { // TODO: is this bang correct?
const prop = propMatch[1]; const prop = propMatch[1];
let value: any = propMatch[2]; let value: any = propMatch[2];
switch (prop) { switch (prop) {
@ -1194,7 +1195,7 @@ export async function populateAnims() {
if (!anim.frameTimedEvents.has(frameIndex)) { if (!anim.frameTimedEvents.has(frameIndex)) {
anim.frameTimedEvents.set(frameIndex, []); anim.frameTimedEvents.set(frameIndex, []);
} }
anim.frameTimedEvents.get(frameIndex).push(timedEvent); anim.frameTimedEvents.get(frameIndex)!.push(timedEvent); // TODO: is this bang correct?
} }
break; break;
case "position": case "position":

View File

@ -36,11 +36,11 @@ export class BattlerTag {
public sourceMove: Moves; public sourceMove: Moves;
public sourceId?: number; public sourceId?: number;
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType | BattlerTagLapseType[], turnCount: number, sourceMove: Moves, sourceId?: number) { constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType | BattlerTagLapseType[], turnCount: number, sourceMove?: Moves, sourceId?: number) {
this.tagType = tagType; this.tagType = tagType;
this.lapseTypes = Array.isArray(lapseType) ? lapseType : [ lapseType ]; this.lapseTypes = Array.isArray(lapseType) ? lapseType : [ lapseType ];
this.turnCount = turnCount; this.turnCount = turnCount;
this.sourceMove = sourceMove; this.sourceMove = sourceMove!; // TODO: is this bang correct?
this.sourceId = sourceId; this.sourceId = sourceId;
} }
@ -66,7 +66,7 @@ export class BattlerTag {
return false; return false;
} }
getMoveName(): string { getMoveName(): string | null {
return this.sourceMove return this.sourceMove
? allMoves[this.sourceMove].name ? allMoves[this.sourceMove].name
: null; : null;
@ -299,12 +299,12 @@ export class DestinyBondTag extends BattlerTag {
if (lapseType !== BattlerTagLapseType.CUSTOM) { if (lapseType !== BattlerTagLapseType.CUSTOM) {
return super.lapse(pokemon, lapseType); return super.lapse(pokemon, lapseType);
} }
const source = pokemon.scene.getPokemonById(this.sourceId); const source = this.sourceId ? pokemon.scene.getPokemonById(this.sourceId) : null;
if (!source.isFainted()) { if (!source?.isFainted()) {
return true; return true;
} }
if (source.getAlly() === pokemon) { if (source?.getAlly() === pokemon) {
return false; return false;
} }
@ -330,7 +330,19 @@ export class InfatuatedTag extends BattlerTag {
} }
canAdd(pokemon: Pokemon): boolean { canAdd(pokemon: Pokemon): boolean {
return pokemon.isOppositeGender(pokemon.scene.getPokemonById(this.sourceId)); if (this.sourceId) {
const pkm = pokemon.scene.getPokemonById(this.sourceId);
if (pkm) {
return pokemon.isOppositeGender(pkm);
} else {
console.warn("canAdd: this.sourceId is not a valid pokemon id!", this.sourceId);
return false;
}
} else {
console.warn("canAdd: this.sourceId is undefined");
return false;
}
} }
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
@ -339,7 +351,7 @@ export class InfatuatedTag extends BattlerTag {
pokemon.scene.queueMessage( pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsInfatuatedOnAdd", { i18next.t("battle:battlerTagsInfatuatedOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId)) sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined) // TODO: is that bang correct?
}) })
); );
} }
@ -357,7 +369,7 @@ export class InfatuatedTag extends BattlerTag {
pokemon.scene.queueMessage( pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsInfatuatedLapse", { i18next.t("battle:battlerTagsInfatuatedLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId)) sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined) // TODO: is that bang correct?
}) })
); );
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT)); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT));
@ -410,7 +422,7 @@ export class SeedTag extends BattlerTag {
super.onAdd(pokemon); super.onAdd(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsSeededOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.scene.queueMessage(i18next.t("battle:battlerTagsSeededOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId).getBattlerIndex(); this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId!)!.getBattlerIndex(); // TODO: are those bangs correct?
} }
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -556,11 +568,11 @@ export class EncoreTag extends BattlerTag {
const movePhase = pokemon.scene.findPhase(m => m instanceof MovePhase && m.pokemon === pokemon); const movePhase = pokemon.scene.findPhase(m => m instanceof MovePhase && m.pokemon === pokemon);
if (movePhase) { if (movePhase) {
const movesetMove = pokemon.getMoveset().find(m => m.moveId === this.moveId); const movesetMove = pokemon.getMoveset().find(m => m!.moveId === this.moveId); // TODO: is this bang correct?
if (movesetMove) { if (movesetMove) {
const lastMove = pokemon.getLastXMoves(1)[0]; const lastMove = pokemon.getLastXMoves(1)[0];
pokemon.scene.tryReplacePhase((m => m instanceof MovePhase && m.pokemon === pokemon), pokemon.scene.tryReplacePhase((m => m instanceof MovePhase && m.pokemon === pokemon),
new MovePhase(pokemon.scene, pokemon, lastMove.targets, movesetMove)); new MovePhase(pokemon.scene, pokemon, lastMove.targets!, movesetMove)); // TODO: is this bang correct?
} }
} }
} }
@ -580,7 +592,7 @@ export class HelpingHandTag extends BattlerTag {
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
pokemon.scene.queueMessage( pokemon.scene.queueMessage(
i18next.t("battle:battlerTagsHelpingHandOnAdd", { i18next.t("battle:battlerTagsHelpingHandOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId)), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
pokemonName: getPokemonNameWithAffix(pokemon) pokemonName: getPokemonNameWithAffix(pokemon)
}) })
); );
@ -800,7 +812,7 @@ export class BindTag extends DamagingTrapTag {
getTrapMessage(pokemon: Pokemon): string { getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsBindOnTrap", { return i18next.t("battle:battlerTagsBindOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId)), sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
moveName: this.getMoveName() moveName: this.getMoveName()
}); });
} }
@ -814,7 +826,7 @@ export class WrapTag extends DamagingTrapTag {
getTrapMessage(pokemon: Pokemon): string { getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsWrapOnTrap", { return i18next.t("battle:battlerTagsWrapOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId)) sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
}); });
} }
} }
@ -848,7 +860,7 @@ export class ClampTag extends DamagingTrapTag {
getTrapMessage(pokemon: Pokemon): string { getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsClampOnTrap", { return i18next.t("battle:battlerTagsClampOnTrap", {
sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId)), sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
pokemonName: getPokemonNameWithAffix(pokemon), pokemonName: getPokemonNameWithAffix(pokemon),
}); });
} }
@ -895,7 +907,7 @@ export class ThunderCageTag extends DamagingTrapTag {
getTrapMessage(pokemon: Pokemon): string { getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsThunderCageOnTrap", { return i18next.t("battle:battlerTagsThunderCageOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId)) sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
}); });
} }
} }
@ -908,7 +920,7 @@ export class InfestationTag extends DamagingTrapTag {
getTrapMessage(pokemon: Pokemon): string { getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battle:battlerTagsInfestationOnTrap", { return i18next.t("battle:battlerTagsInfestationOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId)) sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
}); });
} }
} }
@ -1242,6 +1254,7 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
return highestValue; return highestValue;
}, 0); }, 0);
highestStat = highestStat!; // tell TS compiler it's defined!
this.stat = highestStat; this.stat = highestStat;
switch (this.stat) { switch (this.stat) {
@ -1427,7 +1440,7 @@ export class SaltCuredTag extends BattlerTag {
super.onAdd(pokemon); super.onAdd(pokemon);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsSaltCuredOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.scene.queueMessage(i18next.t("battle:battlerTagsSaltCuredOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId).getBattlerIndex(); this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId!)!.getBattlerIndex(); // TODO: are those bangs correct?
} }
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -1474,7 +1487,7 @@ export class CursedTag extends BattlerTag {
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); super.onAdd(pokemon);
this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId).getBattlerIndex(); this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId!)!.getBattlerIndex(); // TODO: are those bangs correct?
} }
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {

View File

@ -54,7 +54,7 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25); const threshold = new Utils.NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, threshold); applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, threshold);
return !!pokemon.getMoveset().find(m => !m.getPpRatio()); return !!pokemon.getMoveset().find(m => !m?.getPpRatio());
}; };
} }
} }
@ -120,10 +120,10 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
if (pokemon.battleData) { if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType); pokemon.battleData.berriesEaten.push(berryType);
} }
const ppRestoreMove = pokemon.getMoveset().find(m => !m.getPpRatio()) ? pokemon.getMoveset().find(m => !m.getPpRatio()) : pokemon.getMoveset().find(m => m.getPpRatio() < 1); const ppRestoreMove = pokemon.getMoveset().find(m => !m?.getPpRatio()) ? pokemon.getMoveset().find(m => !m?.getPpRatio()) : pokemon.getMoveset().find(m => m!.getPpRatio() < 1); // TODO: is this bang correct?
if (ppRestoreMove !== undefined) { if (ppRestoreMove !== undefined) {
ppRestoreMove.ppUsed = Math.max(ppRestoreMove.ppUsed - 10, 0); ppRestoreMove!.ppUsed = Math.max(ppRestoreMove!.ppUsed - 10, 0);
pokemon.scene.queueMessage(i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: ppRestoreMove.getName(), berryName: getBerryName(berryType) })); pokemon.scene.queueMessage(i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: ppRestoreMove!.getName(), berryName: getBerryName(berryType) }));
} }
}; };
} }

View File

@ -7705,7 +7705,7 @@ export function initBiomes() {
? pokemonEvolutions[speciesId] ? pokemonEvolutions[speciesId]
: []; : [];
if (!biomeEntries.filter(b => b[0] !== Biome.END).length && !speciesEvolutions.filter(es => !!((pokemonBiomes.find(p => p[0] === es.speciesId))[3] as any[]).filter(b => b[0] !== Biome.END).length).length) { if (!biomeEntries.filter(b => b[0] !== Biome.END).length && !speciesEvolutions.filter(es => !!((pokemonBiomes.find(p => p[0] === es.speciesId)!)[3] as any[]).filter(b => b[0] !== Biome.END).length).length) { // TODO: is the bang on the `find()` correct?
uncatchableSpecies.push(speciesId); uncatchableSpecies.push(speciesId);
} }

View File

@ -412,7 +412,7 @@ export class SingleGenerationChallenge extends Challenge {
const speciesToCheck = [pokemon.speciesId]; const speciesToCheck = [pokemon.speciesId];
while (speciesToCheck.length) { while (speciesToCheck.length) {
const checking = speciesToCheck.pop(); const checking = speciesToCheck.pop();
if (pokemonEvolutions.hasOwnProperty(checking) && checkPokemonEvolutions) { if (checking && pokemonEvolutions.hasOwnProperty(checking) && checkPokemonEvolutions) {
pokemonEvolutions[checking].forEach(e => { pokemonEvolutions[checking].forEach(e => {
speciesToCheck.push(e.speciesId); speciesToCheck.push(e.speciesId);
generations.push(getPokemonSpecies(e.speciesId).generation); generations.push(getPokemonSpecies(e.speciesId).generation);
@ -430,7 +430,7 @@ export class SingleGenerationChallenge extends Challenge {
applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean { applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean {
const baseGeneration = pokemon.species.speciesId === Species.VICTINI ? 5 : getPokemonSpecies(pokemon.species.speciesId).generation; const baseGeneration = pokemon.species.speciesId === Species.VICTINI ? 5 : getPokemonSpecies(pokemon.species.speciesId).generation;
const fusionGeneration = pokemon.isFusion() ? pokemon.fusionSpecies.speciesId === Species.VICTINI ? 5 : getPokemonSpecies(pokemon.fusionSpecies.speciesId).generation : 0; const fusionGeneration = pokemon.isFusion() ? pokemon.fusionSpecies?.speciesId === Species.VICTINI ? 5 : getPokemonSpecies(pokemon.fusionSpecies!.speciesId).generation : 0; // TODO: is the bang on fusionSpecies correct?
if (pokemon.isPlayer() && (baseGeneration !== this.value || (pokemon.isFusion() && fusionGeneration !== this.value))) { if (pokemon.isPlayer() && (baseGeneration !== this.value || (pokemon.isFusion() && fusionGeneration !== this.value))) {
valid.value = false; valid.value = false;
return true; return true;
@ -542,13 +542,13 @@ export class SingleTypeChallenge extends Challenge {
const speciesToCheck = [pokemon.speciesId]; const speciesToCheck = [pokemon.speciesId];
while (speciesToCheck.length) { while (speciesToCheck.length) {
const checking = speciesToCheck.pop(); const checking = speciesToCheck.pop();
if (pokemonEvolutions.hasOwnProperty(checking) && checkPokemonEvolutions) { if (checking && pokemonEvolutions.hasOwnProperty(checking) && checkPokemonEvolutions) {
pokemonEvolutions[checking].forEach(e => { pokemonEvolutions[checking].forEach(e => {
speciesToCheck.push(e.speciesId); speciesToCheck.push(e.speciesId);
types.push(getPokemonSpecies(e.speciesId).type1, getPokemonSpecies(e.speciesId).type2); types.push(getPokemonSpecies(e.speciesId).type1, getPokemonSpecies(e.speciesId).type2);
}); });
} }
if (pokemonFormChanges.hasOwnProperty(checking) && checkPokemonForms) { if (checking && pokemonFormChanges.hasOwnProperty(checking) && checkPokemonForms) {
pokemonFormChanges[checking].forEach(f1 => { pokemonFormChanges[checking].forEach(f1 => {
getPokemonSpecies(checking).forms.forEach(f2 => { getPokemonSpecies(checking).forms.forEach(f2 => {
if (f1.formKey === f2.formKey) { if (f1.formKey === f2.formKey) {
@ -568,10 +568,11 @@ export class SingleTypeChallenge extends Challenge {
applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean { applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean {
if (pokemon.isPlayer() && !pokemon.isOfType(this.value - 1, false, false, true) if (pokemon.isPlayer() && !pokemon.isOfType(this.value - 1, false, false, true)
&& !SingleTypeChallenge.TYPE_OVERRIDES.some(o => o.type === (this.value - 1) && (pokemon.isFusion() && o.fusion ? pokemon.fusionSpecies : pokemon.species).speciesId === o.species)) { && !SingleTypeChallenge.TYPE_OVERRIDES.some(o => o.type === (this.value - 1) && (pokemon.isFusion() && o.fusion ? pokemon.fusionSpecies! : pokemon.species).speciesId === o.species)) { // TODO: is the bang on fusionSpecies correct?
valid.value = false; valid.value = false;
return true; return true;
} }
return false;
} }
/** /**

View File

@ -11,15 +11,15 @@ export interface DailyRunConfig {
starters: Starter; starters: Starter;
} }
export function fetchDailyRunSeed(): Promise<string> { export function fetchDailyRunSeed(): Promise<string | null> {
return new Promise<string>((resolve, reject) => { return new Promise<string | null>((resolve, reject) => {
Utils.apiFetch("daily/seed").then(response => { Utils.apiFetch("daily/seed").then(response => {
if (!response.ok) { if (!response.ok) {
resolve(null); resolve(null);
return; return;
} }
return response.text(); return response.text();
}).then(seed => resolve(seed)) }).then(seed => resolve(seed!)) // TODO: is this bang correct?
.catch(err => reject(err)); .catch(err => reject(err));
}); });
} }

View File

@ -140,30 +140,30 @@ export class Egg {
constructor(eggOptions?: IEggOptions) { constructor(eggOptions?: IEggOptions) {
//if (eggOptions.tier && eggOptions.species) throw Error("Error egg can't have species and tier as option. only choose one of them.") //if (eggOptions.tier && eggOptions.species) throw Error("Error egg can't have species and tier as option. only choose one of them.")
this._sourceType = eggOptions.sourceType ?? undefined; this._sourceType = eggOptions?.sourceType!; // TODO: is this bang correct?
// Ensure _sourceType is defined before invoking rollEggTier(), as it is referenced // Ensure _sourceType is defined before invoking rollEggTier(), as it is referenced
this._tier = eggOptions.tier ?? (Overrides.EGG_TIER_OVERRIDE ?? this.rollEggTier()); this._tier = eggOptions?.tier ?? (Overrides.EGG_TIER_OVERRIDE ?? this.rollEggTier());
// If egg was pulled, check if egg pity needs to override the egg tier // If egg was pulled, check if egg pity needs to override the egg tier
if (eggOptions.pulled) { if (eggOptions?.pulled) {
// Needs this._tier and this._sourceType to work // Needs this._tier and this._sourceType to work
this.checkForPityTierOverrides(eggOptions.scene); this.checkForPityTierOverrides(eggOptions.scene!); // TODO: is this bang correct?
} }
this._id = eggOptions.id ?? Utils.randInt(EGG_SEED, EGG_SEED * this._tier); this._id = eggOptions?.id ?? Utils.randInt(EGG_SEED, EGG_SEED * this._tier);
this._sourceType = eggOptions.sourceType ?? undefined; this._sourceType = eggOptions?.sourceType ?? undefined;
this._hatchWaves = eggOptions.hatchWaves ?? this.getEggTierDefaultHatchWaves(); this._hatchWaves = eggOptions?.hatchWaves ?? this.getEggTierDefaultHatchWaves();
this._timestamp = eggOptions.timestamp ?? new Date().getTime(); this._timestamp = eggOptions?.timestamp ?? new Date().getTime();
// First roll shiny and variant so we can filter if species with an variant exist // First roll shiny and variant so we can filter if species with an variant exist
this._isShiny = eggOptions.isShiny ?? (Overrides.EGG_SHINY_OVERRIDE || this.rollShiny()); this._isShiny = eggOptions?.isShiny ?? (Overrides.EGG_SHINY_OVERRIDE || this.rollShiny());
this._variantTier = eggOptions.variantTier ?? (Overrides.EGG_VARIANT_OVERRIDE ?? this.rollVariant()); this._variantTier = eggOptions?.variantTier ?? (Overrides.EGG_VARIANT_OVERRIDE ?? this.rollVariant());
this._species = eggOptions.species ?? this.rollSpecies(eggOptions.scene); this._species = eggOptions?.species ?? this.rollSpecies(eggOptions!.scene!)!; // TODO: Are those bangs correct?
this._overrideHiddenAbility = eggOptions.overrideHiddenAbility ?? false; this._overrideHiddenAbility = eggOptions?.overrideHiddenAbility ?? false;
// Override egg tier and hatchwaves if species was given // Override egg tier and hatchwaves if species was given
if (eggOptions.species) { if (eggOptions?.species) {
this._tier = this.getEggTierFromSpeciesStarterValue(); this._tier = this.getEggTierFromSpeciesStarterValue();
this._hatchWaves = eggOptions.hatchWaves ?? this.getEggTierDefaultHatchWaves(); this._hatchWaves = eggOptions.hatchWaves ?? this.getEggTierDefaultHatchWaves();
} }
@ -174,10 +174,10 @@ export class Egg {
this._variantTier = VariantTier.COMMON; this._variantTier = VariantTier.COMMON;
} }
// Needs this._tier so it needs to be generated afer the tier override if bought from same species // Needs this._tier so it needs to be generated afer the tier override if bought from same species
this._eggMoveIndex = eggOptions.eggMoveIndex ?? this.rollEggMoveIndex(); this._eggMoveIndex = eggOptions?.eggMoveIndex ?? this.rollEggMoveIndex();
if (eggOptions.pulled) { if (eggOptions?.pulled) {
this.increasePullStatistic(eggOptions.scene); this.increasePullStatistic(eggOptions.scene!); // TODO: is this bang correct?
this.addEggToGameData(eggOptions.scene); this.addEggToGameData(eggOptions.scene!); // TODO: is this bang correct?
} }
} }
@ -202,7 +202,7 @@ export class Egg {
// Legacy egg wants to hatch. Generate missing properties // Legacy egg wants to hatch. Generate missing properties
if (!this._species) { if (!this._species) {
this._isShiny = this.rollShiny(); this._isShiny = this.rollShiny();
this._species = this.rollSpecies(scene); this._species = this.rollSpecies(scene!)!; // TODO: are these bangs correct?
} }
let pokemonSpecies = getPokemonSpecies(this._species); let pokemonSpecies = getPokemonSpecies(this._species);
@ -213,7 +213,7 @@ export class Egg {
// Sets the hidden ability if a hidden ability exists and the override is set // Sets the hidden ability if a hidden ability exists and the override is set
// or if the same species egg hits the chance // or if the same species egg hits the chance
let abilityIndex = undefined; let abilityIndex: number | undefined = undefined;
if (pokemonSpecies.abilityHidden && (this._overrideHiddenAbility if (pokemonSpecies.abilityHidden && (this._overrideHiddenAbility
|| (this._sourceType === EggSourceType.SAME_SPECIES_EGG && !Utils.randSeedInt(SAME_SPECIES_EGG_HA_RATE)))) { || (this._sourceType === EggSourceType.SAME_SPECIES_EGG && !Utils.randSeedInt(SAME_SPECIES_EGG_HA_RATE)))) {
abilityIndex = 2; abilityIndex = 2;
@ -277,6 +277,9 @@ export class Egg {
return i18next.t("egg:gachaTypeShiny"); return i18next.t("egg:gachaTypeShiny");
case EggSourceType.GACHA_MOVE: case EggSourceType.GACHA_MOVE:
return i18next.t("egg:gachaTypeMove"); return i18next.t("egg:gachaTypeMove");
default:
console.warn("getEggTypeDescriptor case not defined. Returning default empty string");
return "";
} }
} }
@ -326,9 +329,9 @@ export class Egg {
return tierValue >= 52 + tierValueOffset ? EggTier.COMMON : tierValue >= 8 + tierValueOffset ? EggTier.GREAT : tierValue >= 1 + tierValueOffset ? EggTier.ULTRA : EggTier.MASTER; return tierValue >= 52 + tierValueOffset ? EggTier.COMMON : tierValue >= 8 + tierValueOffset ? EggTier.GREAT : tierValue >= 1 + tierValueOffset ? EggTier.ULTRA : EggTier.MASTER;
} }
private rollSpecies(scene: BattleScene): Species { private rollSpecies(scene: BattleScene): Species | null {
if (!scene) { if (!scene) {
return undefined; return null;
} }
/** /**
* Manaphy eggs have a 1/8 chance of being Manaphy and 7/8 chance of being Phione * Manaphy eggs have a 1/8 chance of being Manaphy and 7/8 chance of being Phione
@ -400,7 +403,7 @@ export class Egg {
* and being the same each time * and being the same each time
*/ */
let totalWeight = 0; let totalWeight = 0;
const speciesWeights = []; const speciesWeights : number[] = [];
for (const speciesId of speciesPool) { for (const speciesId of speciesPool) {
let weight = Math.floor((((maxStarterValue - speciesStarters[speciesId]) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100); let weight = Math.floor((((maxStarterValue - speciesStarters[speciesId]) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100);
const species = getPokemonSpecies(speciesId); const species = getPokemonSpecies(speciesId);
@ -420,6 +423,7 @@ export class Egg {
break; break;
} }
} }
species = species!; // tell TS compiled it's defined now!
if (!!scene.gameData.dexData[species].caughtAttr || scene.gameData.eggs.some(e => e.species === species)) { if (!!scene.gameData.dexData[species].caughtAttr || scene.gameData.eggs.some(e => e.species === species)) {
scene.gameData.unlockPity[this.tier] = Math.min(scene.gameData.unlockPity[this.tier] + 1, 10); scene.gameData.unlockPity[this.tier] = Math.min(scene.gameData.unlockPity[this.tier] + 1, 10);
@ -517,6 +521,8 @@ export class Egg {
if (speciesStartValue >= 8) { if (speciesStartValue >= 8) {
return EggTier.MASTER; return EggTier.MASTER;
} }
return EggTier.COMMON;
} }
//// ////
@ -541,6 +547,7 @@ export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timesta
scene.executeWithSeedOffset(() => { scene.executeWithSeedOffset(() => {
ret = Phaser.Math.RND.shuffle(legendarySpecies)[index]; ret = Phaser.Math.RND.shuffle(legendarySpecies)[index];
}, offset, EGG_SEED.toString()); }, offset, EGG_SEED.toString());
ret = ret!; // tell TS compiler it's
return ret; return ret;
} }

View File

@ -180,7 +180,7 @@ export default class Move implements Localizable {
* @returns the first {@linkcode MoveAttr} element in attrs that makes the input function return true * @returns the first {@linkcode MoveAttr} element in attrs that makes the input function return true
*/ */
findAttr(attrPredicate: (attr: MoveAttr) => boolean): MoveAttr { findAttr(attrPredicate: (attr: MoveAttr) => boolean): MoveAttr {
return this.attrs.find(attrPredicate); return this.attrs.find(attrPredicate)!; // TODO: is the bang correct?
} }
/** /**
@ -361,7 +361,7 @@ export default class Move implements Localizable {
* @param makesContact The value (boolean) to set the flag to * @param makesContact The value (boolean) to set the flag to
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
makesContact(makesContact?: boolean): this { makesContact(makesContact: boolean = true): this { // TODO: is true the correct default?
this.setFlag(MoveFlags.MAKES_CONTACT, makesContact); this.setFlag(MoveFlags.MAKES_CONTACT, makesContact);
return this; return this;
} }
@ -372,7 +372,7 @@ export default class Move implements Localizable {
* example: @see {@linkcode Moves.CURSE} * example: @see {@linkcode Moves.CURSE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
ignoresProtect(ignoresProtect?: boolean): this { ignoresProtect(ignoresProtect: boolean = true): this { // TODO: is `true` the correct default?
this.setFlag(MoveFlags.IGNORE_PROTECT, ignoresProtect); this.setFlag(MoveFlags.IGNORE_PROTECT, ignoresProtect);
return this; return this;
} }
@ -383,7 +383,7 @@ export default class Move implements Localizable {
* example: @see {@linkcode Moves.NATURE_POWER} * example: @see {@linkcode Moves.NATURE_POWER}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
ignoresVirtual(ignoresVirtual?: boolean): this { ignoresVirtual(ignoresVirtual: boolean = true): this { // TODO: is `true` the correct default?
this.setFlag(MoveFlags.IGNORE_VIRTUAL, ignoresVirtual); this.setFlag(MoveFlags.IGNORE_VIRTUAL, ignoresVirtual);
return this; return this;
} }
@ -394,7 +394,7 @@ export default class Move implements Localizable {
* example: @see {@linkcode Moves.UPROAR} * example: @see {@linkcode Moves.UPROAR}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
soundBased(soundBased?: boolean): this { soundBased(soundBased: boolean = true): this { // TODO: is `true` the correct default?
this.setFlag(MoveFlags.SOUND_BASED, soundBased); this.setFlag(MoveFlags.SOUND_BASED, soundBased);
return this; return this;
} }
@ -405,7 +405,7 @@ export default class Move implements Localizable {
* example: @see {@linkcode Moves.TELEPORT} * example: @see {@linkcode Moves.TELEPORT}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
hidesUser(hidesUser?: boolean): this { hidesUser(hidesUser: boolean = true): this { // TODO: is `true` the correct default?
this.setFlag(MoveFlags.HIDE_USER, hidesUser); this.setFlag(MoveFlags.HIDE_USER, hidesUser);
return this; return this;
} }
@ -416,7 +416,7 @@ export default class Move implements Localizable {
* example: @see {@linkcode Moves.WHIRLWIND} * example: @see {@linkcode Moves.WHIRLWIND}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
hidesTarget(hidesTarget?: boolean): this { hidesTarget(hidesTarget: boolean = true): this { // TODO: is `true` the correct default?
this.setFlag(MoveFlags.HIDE_TARGET, hidesTarget); this.setFlag(MoveFlags.HIDE_TARGET, hidesTarget);
return this; return this;
} }
@ -427,7 +427,7 @@ export default class Move implements Localizable {
* example: @see {@linkcode Moves.BITE} * example: @see {@linkcode Moves.BITE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
bitingMove(bitingMove?: boolean): this { bitingMove(bitingMove: boolean = true): this { // TODO: is `true` the correct default?
this.setFlag(MoveFlags.BITING_MOVE, bitingMove); this.setFlag(MoveFlags.BITING_MOVE, bitingMove);
return this; return this;
} }
@ -438,7 +438,7 @@ export default class Move implements Localizable {
* example: @see {@linkcode Moves.WATER_PULSE} * example: @see {@linkcode Moves.WATER_PULSE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
pulseMove(pulseMove?: boolean): this { pulseMove(pulseMove: boolean = true): this { // TODO: is `true` the correct default?
this.setFlag(MoveFlags.PULSE_MOVE, pulseMove); this.setFlag(MoveFlags.PULSE_MOVE, pulseMove);
return this; return this;
} }
@ -449,7 +449,7 @@ export default class Move implements Localizable {
* example: @see {@linkcode Moves.DRAIN_PUNCH} * example: @see {@linkcode Moves.DRAIN_PUNCH}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
punchingMove(punchingMove?: boolean): this { punchingMove(punchingMove: boolean = true): this { // TODO: is `true` the correct default?
this.setFlag(MoveFlags.PUNCHING_MOVE, punchingMove); this.setFlag(MoveFlags.PUNCHING_MOVE, punchingMove);
return this; return this;
} }
@ -460,7 +460,7 @@ export default class Move implements Localizable {
* example: @see {@linkcode Moves.X_SCISSOR} * example: @see {@linkcode Moves.X_SCISSOR}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
slicingMove(slicingMove?: boolean): this { slicingMove(slicingMove: boolean = true): this { // TODO: is `true` the correct default?
this.setFlag(MoveFlags.SLICING_MOVE, slicingMove); this.setFlag(MoveFlags.SLICING_MOVE, slicingMove);
return this; return this;
} }
@ -471,7 +471,7 @@ export default class Move implements Localizable {
* @param recklessMove The value to set the flag to * @param recklessMove The value to set the flag to
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
recklessMove(recklessMove?: boolean): this { recklessMove(recklessMove: boolean = true): this { // TODO: is `true` the correct default?
this.setFlag(MoveFlags.RECKLESS_MOVE, recklessMove); this.setFlag(MoveFlags.RECKLESS_MOVE, recklessMove);
return this; return this;
} }
@ -482,7 +482,7 @@ export default class Move implements Localizable {
* example: @see {@linkcode Moves.ELECTRO_BALL} * example: @see {@linkcode Moves.ELECTRO_BALL}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
ballBombMove(ballBombMove?: boolean): this { ballBombMove(ballBombMove: boolean = true): this { // TODO: is `true` the correct default?
this.setFlag(MoveFlags.BALLBOMB_MOVE, ballBombMove); this.setFlag(MoveFlags.BALLBOMB_MOVE, ballBombMove);
return this; return this;
} }
@ -493,7 +493,7 @@ export default class Move implements Localizable {
* example: @see {@linkcode Moves.STUN_SPORE} * example: @see {@linkcode Moves.STUN_SPORE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
powderMove(powderMove?: boolean): this { powderMove(powderMove: boolean = true): this { // TODO: is `true` the correct default?
this.setFlag(MoveFlags.POWDER_MOVE, powderMove); this.setFlag(MoveFlags.POWDER_MOVE, powderMove);
return this; return this;
} }
@ -504,7 +504,7 @@ export default class Move implements Localizable {
* example: @see {@linkcode Moves.PETAL_DANCE} * example: @see {@linkcode Moves.PETAL_DANCE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
danceMove(danceMove?: boolean): this { danceMove(danceMove: boolean = true): this { // TODO: is `true` the correct default?
this.setFlag(MoveFlags.DANCE_MOVE, danceMove); this.setFlag(MoveFlags.DANCE_MOVE, danceMove);
return this; return this;
} }
@ -515,7 +515,7 @@ export default class Move implements Localizable {
* example: @see {@linkcode Moves.HURRICANE} * example: @see {@linkcode Moves.HURRICANE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
windMove(windMove?: boolean): this { windMove(windMove: boolean = true): this { // TODO: is `true` the correct default?
this.setFlag(MoveFlags.WIND_MOVE, windMove); this.setFlag(MoveFlags.WIND_MOVE, windMove);
return this; return this;
} }
@ -526,7 +526,7 @@ export default class Move implements Localizable {
* example: @see {@linkcode Moves.ABSORB} * example: @see {@linkcode Moves.ABSORB}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
triageMove(triageMove?: boolean): this { triageMove(triageMove: boolean = true): this { // TODO: is `true` the correct default?
this.setFlag(MoveFlags.TRIAGE_MOVE, triageMove); this.setFlag(MoveFlags.TRIAGE_MOVE, triageMove);
return this; return this;
} }
@ -537,7 +537,7 @@ export default class Move implements Localizable {
* example: @see {@linkcode Moves.SUNSTEEL_STRIKE} * example: @see {@linkcode Moves.SUNSTEEL_STRIKE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
ignoresAbilities(ignoresAbilities?: boolean): this { ignoresAbilities(ignoresAbilities: boolean = true): this { // TODO: is `true` the correct default?
this.setFlag(MoveFlags.IGNORE_ABILITIES, ignoresAbilities); this.setFlag(MoveFlags.IGNORE_ABILITIES, ignoresAbilities);
return this; return this;
} }
@ -548,7 +548,7 @@ export default class Move implements Localizable {
* example: @see {@linkcode Moves.TRIPLE_AXEL} * example: @see {@linkcode Moves.TRIPLE_AXEL}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
checkAllHits(checkAllHits?: boolean): this { checkAllHits(checkAllHits: boolean = true): this { // TODO: is `true` the correct default?
this.setFlag(MoveFlags.CHECK_ALL_HITS, checkAllHits); this.setFlag(MoveFlags.CHECK_ALL_HITS, checkAllHits);
return this; return this;
} }
@ -559,7 +559,7 @@ export default class Move implements Localizable {
* example: @see {@linkcode Moves.METAL_BURST} * example: @see {@linkcode Moves.METAL_BURST}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
redirectCounter(redirectCounter?: boolean): this { redirectCounter(redirectCounter: boolean = true): this { // TODO: is `true` the correct default?
this.setFlag(MoveFlags.REDIRECT_COUNTER, redirectCounter); this.setFlag(MoveFlags.REDIRECT_COUNTER, redirectCounter);
return this; return this;
} }
@ -571,7 +571,7 @@ export default class Move implements Localizable {
* @param target {@linkcode Pokemon} the Pokemon receiving the move * @param target {@linkcode Pokemon} the Pokemon receiving the move
* @returns boolean * @returns boolean
*/ */
checkFlag(flag: MoveFlags, user: Pokemon, target: Pokemon): boolean { checkFlag(flag: MoveFlags, user: Pokemon, target: Pokemon | null): boolean {
// special cases below, eg: if the move flag is MAKES_CONTACT, and the user pokemon has an ability that ignores contact (like "Long Reach"), then overrides and move does not make contact // special cases below, eg: if the move flag is MAKES_CONTACT, and the user pokemon has an ability that ignores contact (like "Long Reach"), then overrides and move does not make contact
switch (flag) { switch (flag) {
case MoveFlags.MAKES_CONTACT: case MoveFlags.MAKES_CONTACT:
@ -870,7 +870,7 @@ export abstract class MoveAttr {
* @param args Set of unique arguments needed by this attribute * @param args Set of unique arguments needed by this attribute
* @returns true if application of the ability succeeds * @returns true if application of the ability succeeds
*/ */
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> { apply(user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): boolean | Promise<boolean> {
return true; return true;
} }
@ -878,7 +878,7 @@ export abstract class MoveAttr {
* @virtual * @virtual
* @returns the {@linkcode MoveCondition} or {@linkcode MoveConditionFunc} for this {@linkcode Move} * @returns the {@linkcode MoveCondition} or {@linkcode MoveConditionFunc} for this {@linkcode Move}
*/ */
getCondition(): MoveCondition | MoveConditionFunc { getCondition(): MoveCondition | MoveConditionFunc | null {
return null; return null;
} }
@ -954,14 +954,14 @@ export class MoveEffectAttr extends MoveAttr {
* @param args Set of unique arguments needed by this attribute * @param args Set of unique arguments needed by this attribute
* @returns true if basic application of the ability attribute should be possible * @returns true if basic application of the ability attribute should be possible
*/ */
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) { canApply(user: Pokemon, target: Pokemon, move: Move, args?: any[]) {
return !! (this.selfTarget ? user.hp && !user.getTag(BattlerTagType.FRENZY) : target.hp) return !! (this.selfTarget ? user.hp && !user.getTag(BattlerTagType.FRENZY) : target.hp)
&& (this.selfTarget || !target.getTag(BattlerTagType.PROTECTED) || && (this.selfTarget || !target.getTag(BattlerTagType.PROTECTED) ||
move.checkFlag(MoveFlags.IGNORE_PROTECT, user, target)); move.checkFlag(MoveFlags.IGNORE_PROTECT, user, target));
} }
/** Applies move effects so long as they are able based on {@linkcode canApply} */ /** Applies move effects so long as they are able based on {@linkcode canApply} */
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> { apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean | Promise<boolean> {
return this.canApply(user, target, move, args); return this.canApply(user, target, move, args);
} }
@ -1408,10 +1408,10 @@ export class PartyStatusCureAttr extends MoveEffectAttr {
/** Skips mons with this ability, ie. Soundproof */ /** Skips mons with this ability, ie. Soundproof */
private abilityCondition: Abilities; private abilityCondition: Abilities;
constructor(message: string, abilityCondition: Abilities) { constructor(message: string | null, abilityCondition: Abilities) {
super(); super();
this.message = message; this.message = message!; // TODO: is this bang correct?
this.abilityCondition = abilityCondition; this.abilityCondition = abilityCondition;
} }
@ -1428,6 +1428,7 @@ export class PartyStatusCureAttr extends MoveEffectAttr {
return false; return false;
} }
this.addPartyCurePhase(user); this.addPartyCurePhase(user);
return true;
} }
addPartyCurePhase(user: Pokemon) { addPartyCurePhase(user: Pokemon) {
@ -1583,16 +1584,16 @@ export class SandHealAttr extends WeatherHealAttr {
*/ */
export class BoostHealAttr extends HealAttr { export class BoostHealAttr extends HealAttr {
/** Healing received when {@linkcode condition} is false */ /** Healing received when {@linkcode condition} is false */
private normalHealRatio?: number; private normalHealRatio: number;
/** Healing received when {@linkcode condition} is true */ /** Healing received when {@linkcode condition} is true */
private boostedHealRatio?: number; private boostedHealRatio: number;
/** The lambda expression to check against when boosting the healing value */ /** The lambda expression to check against when boosting the healing value */
private condition?: MoveConditionFunc; private condition?: MoveConditionFunc;
constructor(normalHealRatio?: number, boostedHealRatio?: number, showAnim?: boolean, selfTarget?: boolean, condition?: MoveConditionFunc) { constructor(normalHealRatio?: number, boostedHealRatio?: number, showAnim?: boolean, selfTarget?: boolean, condition?: MoveConditionFunc) {
super(normalHealRatio, showAnim, selfTarget); super(normalHealRatio, showAnim, selfTarget);
this.normalHealRatio = normalHealRatio; this.normalHealRatio = normalHealRatio!; // TODO: is this bang correct?
this.boostedHealRatio = boostedHealRatio; this.boostedHealRatio = boostedHealRatio!; // TODO: is this bang correct?
this.condition = condition; this.condition = condition;
} }
@ -1604,7 +1605,7 @@ export class BoostHealAttr extends HealAttr {
* @returns true if the move was successful * @returns true if the move was successful
*/ */
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const healRatio = this.condition(user, target, move) ? this.boostedHealRatio : this.normalHealRatio; const healRatio: number = (this.condition ? this.condition(user, target, move) : false) ? this.boostedHealRatio : this.normalHealRatio;
this.addHealPhase(target, healRatio); this.addHealPhase(target, healRatio);
return true; return true;
} }
@ -1643,13 +1644,13 @@ export class HealOnAllyAttr extends HealAttr {
export class HitHealAttr extends MoveEffectAttr { export class HitHealAttr extends MoveEffectAttr {
private healRatio: number; private healRatio: number;
private message: string; private message: string;
private healStat: Stat; private healStat: Stat | null;
constructor(healRatio?: number, healStat?: Stat) { constructor(healRatio?: number | null, healStat?: Stat) {
super(true, MoveEffectTrigger.HIT); super(true, MoveEffectTrigger.HIT);
this.healRatio = healRatio || 0.5; this.healRatio = healRatio!; // TODO: is this bang correct?
this.healStat = healStat || null; this.healStat = healStat!; // TODO: is this bang correct?
} }
/** /**
* Heals the user the determined amount and possibly displays a message about regaining health. * Heals the user the determined amount and possibly displays a message about regaining health.
@ -1665,7 +1666,7 @@ export class HitHealAttr extends MoveEffectAttr {
let healAmount = 0; let healAmount = 0;
let message = ""; let message = "";
const reverseDrain = target.hasAbilityWithAttr(ReverseDrainAbAttr, false); const reverseDrain = target.hasAbilityWithAttr(ReverseDrainAbAttr, false);
if (this.healStat) { if (this.healStat !== null) {
// Strength Sap formula // Strength Sap formula
healAmount = target.getBattleStat(this.healStat); healAmount = target.getBattleStat(this.healStat);
message = i18next.t("battle:drainMessage", {pokemonName: getPokemonNameWithAffix(target)}); message = i18next.t("battle:drainMessage", {pokemonName: getPokemonNameWithAffix(target)});
@ -1677,11 +1678,11 @@ export class HitHealAttr extends MoveEffectAttr {
if (reverseDrain) { if (reverseDrain) {
if (user.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { if (user.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
healAmount = 0; healAmount = 0;
message = null; message = "";
} else { } else {
user.turnData.damageTaken += healAmount; user.turnData.damageTaken += healAmount;
healAmount = healAmount * -1; healAmount = healAmount * -1;
message = null; message = "";
} }
} }
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(), healAmount, message, false, true)); user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(), healAmount, message, false, true));
@ -1850,14 +1851,14 @@ export class WaterShurikenMultiHitTypeAttr extends ChangeMultiHitTypeAttr {
export class StatusEffectAttr extends MoveEffectAttr { export class StatusEffectAttr extends MoveEffectAttr {
public effect: StatusEffect; public effect: StatusEffect;
public cureTurn: integer; public cureTurn: integer | null;
public overrideStatus: boolean; public overrideStatus: boolean;
constructor(effect: StatusEffect, selfTarget?: boolean, cureTurn?: integer, overrideStatus?: boolean) { constructor(effect: StatusEffect, selfTarget?: boolean, cureTurn?: integer, overrideStatus?: boolean) {
super(selfTarget, MoveEffectTrigger.HIT); super(selfTarget, MoveEffectTrigger.HIT);
this.effect = effect; this.effect = effect;
this.cureTurn = cureTurn; this.cureTurn = cureTurn!; // TODO: is this bang correct?
this.overrideStatus = !!overrideStatus; this.overrideStatus = !!overrideStatus;
} }
@ -1875,7 +1876,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
} }
if ((!pokemon.status || (pokemon.status.effect === this.effect && moveChance < 0)) if ((!pokemon.status || (pokemon.status.effect === this.effect && moveChance < 0))
&& pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) { && pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) {
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null,this.effect); applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, this.effect);
return true; return true;
} }
} }
@ -1914,12 +1915,13 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const statusToApply: StatusEffect = user.status?.effect ?? (user.hasAbility(Abilities.COMATOSE) ? StatusEffect.SLEEP : undefined); const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(Abilities.COMATOSE) ? StatusEffect.SLEEP : undefined);
if (target.status) { if (target.status) {
return false; return false;
} }
if (!target.status || (target.status.effect === statusToApply && move.chance < 0)) { //@ts-ignore - how can target.status.effect be checked when we return `false` before when it's defined?
if (!target.status || (target.status.effect === statusToApply && move.chance < 0)) { // TODO: resolve ts-ignore
const statusAfflictResult = target.trySetStatus(statusToApply, true, user); const statusAfflictResult = target.trySetStatus(statusToApply, true, user);
if (statusAfflictResult) { if (statusAfflictResult) {
if (user.status) { if (user.status) {
@ -1960,7 +1962,7 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
const heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferrable); const heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferrable);
if (heldItems.length) { if (heldItems.length) {
const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD; const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD;
const highestItemTier = heldItems.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier, highestTier), 0); const highestItemTier = heldItems.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is the bang after tier correct?
const tierHeldItems = heldItems.filter(m => m.type.getOrInferTier(poolType) === highestItemTier); const tierHeldItems = heldItems.filter(m => m.type.getOrInferTier(poolType) === highestItemTier);
const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length)]; const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length)];
user.scene.tryTransferHeldItemModifier(stolenItem, user, false).then(success => { user.scene.tryTransferHeldItemModifier(stolenItem, user, false).then(success => {
@ -2073,10 +2075,9 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
* Attribute that causes targets of the move to eat a berry. Used for Teatime, Stuff Cheeks * Attribute that causes targets of the move to eat a berry. Used for Teatime, Stuff Cheeks
*/ */
export class EatBerryAttr extends MoveEffectAttr { export class EatBerryAttr extends MoveEffectAttr {
protected chosenBerry: BerryModifier; protected chosenBerry: BerryModifier | undefined;
constructor() { constructor() {
super(true, MoveEffectTrigger.HIT); super(true, MoveEffectTrigger.HIT);
this.chosenBerry = undefined;
} }
/** /**
* Causes the target to eat a berry. * Causes the target to eat a berry.
@ -2111,16 +2112,16 @@ export class EatBerryAttr extends MoveEffectAttr {
} }
reduceBerryModifier(target: Pokemon) { reduceBerryModifier(target: Pokemon) {
if (this.chosenBerry.stackCount === 1) { if (this.chosenBerry?.stackCount === 1) {
target.scene.removeModifier(this.chosenBerry, !target.isPlayer()); target.scene.removeModifier(this.chosenBerry, !target.isPlayer());
} else { } else if (this.chosenBerry !== undefined && this.chosenBerry.stackCount > 1) {
this.chosenBerry.stackCount--; this.chosenBerry.stackCount--;
} }
target.scene.updateModifiers(target.isPlayer()); target.scene.updateModifiers(target.isPlayer());
} }
eatBerry(consumer: Pokemon) { eatBerry(consumer: Pokemon) {
getBerryEffectFunc(this.chosenBerry.berryType)(consumer); // consumer eats the berry getBerryEffectFunc(this.chosenBerry!.berryType)(consumer); // consumer eats the berry
applyAbAttrs(HealFromBerryUseAbAttr, consumer, new Utils.BooleanHolder(false)); applyAbAttrs(HealFromBerryUseAbAttr, consumer, new Utils.BooleanHolder(false));
} }
} }
@ -2351,20 +2352,20 @@ export class OverrideMoveEffectAttr extends MoveAttr {
export class ChargeAttr extends OverrideMoveEffectAttr { export class ChargeAttr extends OverrideMoveEffectAttr {
public chargeAnim: ChargeAnim; public chargeAnim: ChargeAnim;
private chargeText: string; private chargeText: string;
private tagType: BattlerTagType; private tagType: BattlerTagType | null;
private chargeEffect: boolean; private chargeEffect: boolean;
public sameTurn: boolean; public sameTurn: boolean;
public followUpPriority: integer; public followUpPriority: integer | null;
constructor(chargeAnim: ChargeAnim, chargeText: string, tagType?: BattlerTagType, chargeEffect: boolean = false, sameTurn: boolean = false, followUpPriority?: integer) { constructor(chargeAnim: ChargeAnim, chargeText: string, tagType?: BattlerTagType | null, chargeEffect: boolean = false, sameTurn: boolean = false, followUpPriority?: integer) {
super(); super();
this.chargeAnim = chargeAnim; this.chargeAnim = chargeAnim;
this.chargeText = chargeText; this.chargeText = chargeText;
this.tagType = tagType; this.tagType = tagType!; // TODO: is this bang correct?
this.chargeEffect = chargeEffect; this.chargeEffect = chargeEffect;
this.sameTurn = sameTurn; this.sameTurn = sameTurn;
this.followUpPriority = followUpPriority; this.followUpPriority = followUpPriority!; // TODO: is this bang correct?
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
@ -2383,11 +2384,11 @@ export class ChargeAttr extends OverrideMoveEffectAttr {
user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER }); user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER });
user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true }); user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true });
if (this.sameTurn) { if (this.sameTurn) {
let movesetMove = user.moveset.find(m => m.moveId === move.id); let movesetMove = user.moveset.find(m => m?.moveId === move.id);
if (!movesetMove) { // account for any move that calls a ChargeAttr move when the ChargeAttr move does not exist in moveset if (!movesetMove) { // account for any move that calls a ChargeAttr move when the ChargeAttr move does not exist in moveset
movesetMove = new PokemonMove(move.id, 0, 0, true); movesetMove = new PokemonMove(move.id, 0, 0, true);
} }
user.scene.pushMovePhase(new MovePhase(user.scene, user, [ target.getBattlerIndex() ], movesetMove, true), this.followUpPriority); user.scene.pushMovePhase(new MovePhase(user.scene, user, [ target.getBattlerIndex() ], movesetMove, true), this.followUpPriority!); // TODO: is this bang correct?
} }
user.addTag(BattlerTagType.CHARGING, 1, move.id, user.id); user.addTag(BattlerTagType.CHARGING, 1, move.id, user.id);
resolve(true); resolve(true);
@ -2399,7 +2400,7 @@ export class ChargeAttr extends OverrideMoveEffectAttr {
}); });
} }
usedChargeEffect(user: Pokemon, target: Pokemon, move: Move): boolean { usedChargeEffect(user: Pokemon, target: Pokemon | null, move: Move): boolean {
if (!this.chargeEffect) { if (!this.chargeEffect) {
return false; return false;
} }
@ -2488,7 +2489,7 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr {
resolve(true); resolve(true);
}); });
} else { } else {
user.scene.ui.showText(i18next.t("moveTriggers:tookMoveAttack", {pokemonName: getPokemonNameWithAffix(user.scene.getPokemonById(target.id)), moveName: move.name}), null, () => resolve(true)); user.scene.ui.showText(i18next.t("moveTriggers:tookMoveAttack", {pokemonName: getPokemonNameWithAffix(user.scene.getPokemonById(target.id) ?? undefined), moveName: move.name}), null, () => resolve(true));
} }
}); });
} }
@ -2497,20 +2498,20 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr {
export class StatChangeAttr extends MoveEffectAttr { export class StatChangeAttr extends MoveEffectAttr {
public stats: BattleStat[]; public stats: BattleStat[];
public levels: integer; public levels: integer;
private condition: MoveConditionFunc; private condition: MoveConditionFunc | null;
private showMessage: boolean; private showMessage: boolean;
constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean, condition?: MoveConditionFunc, showMessage: boolean = true, firstHitOnly: boolean = false, moveEffectTrigger: MoveEffectTrigger = MoveEffectTrigger.HIT, firstTargetOnly: boolean = false) { constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean, condition?: MoveConditionFunc | null, showMessage: boolean = true, firstHitOnly: boolean = false, moveEffectTrigger: MoveEffectTrigger = MoveEffectTrigger.HIT, firstTargetOnly: boolean = false) {
super(selfTarget, moveEffectTrigger, firstHitOnly, false, firstTargetOnly); super(selfTarget, moveEffectTrigger, firstHitOnly, false, firstTargetOnly);
this.stats = typeof(stats) === "number" this.stats = typeof(stats) === "number"
? [ stats as BattleStat ] ? [ stats as BattleStat ]
: stats as BattleStat[]; : stats as BattleStat[];
this.levels = levels; this.levels = levels;
this.condition = condition || null; this.condition = condition!; // TODO: is this bang correct?
this.showMessage = showMessage; this.showMessage = showMessage;
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> { apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean | Promise<boolean> {
if (!super.apply(user, target, move, args) || (this.condition && !this.condition(user, target, move))) { if (!super.apply(user, target, move, args) || (this.condition && !this.condition(user, target, move))) {
return false; return false;
} }
@ -2574,7 +2575,7 @@ export class StatChangeAttr extends MoveEffectAttr {
export class PostVictoryStatChangeAttr extends MoveAttr { export class PostVictoryStatChangeAttr extends MoveAttr {
private stats: BattleStat[]; private stats: BattleStat[];
private levels: integer; private levels: integer;
private condition: MoveConditionFunc; private condition: MoveConditionFunc | null;
private showMessage: boolean; private showMessage: boolean;
constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean, condition?: MoveConditionFunc, showMessage: boolean = true, firstHitOnly: boolean = false) { constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean, condition?: MoveConditionFunc, showMessage: boolean = true, firstHitOnly: boolean = false) {
@ -2583,7 +2584,7 @@ export class PostVictoryStatChangeAttr extends MoveAttr {
? [ stats as BattleStat ] ? [ stats as BattleStat ]
: stats as BattleStat[]; : stats as BattleStat[];
this.levels = levels; this.levels = levels;
this.condition = condition || null; this.condition = condition!; // TODO: is this bang correct?
this.showMessage = showMessage; this.showMessage = showMessage;
} }
applyPostVictory(user: Pokemon, target: Pokemon, move: Move): void { applyPostVictory(user: Pokemon, target: Pokemon, move: Move): void {
@ -2591,7 +2592,7 @@ export class PostVictoryStatChangeAttr extends MoveAttr {
return; return;
} }
const statChangeAttr = new StatChangeAttr(this.stats, this.levels, this.showMessage); const statChangeAttr = new StatChangeAttr(this.stats, this.levels, this.showMessage);
statChangeAttr.apply(user, target, move, undefined); statChangeAttr.apply(user, target, move);
} }
} }
@ -2751,7 +2752,7 @@ export class HpSplitAttr extends MoveEffectAttr {
return resolve(false); return resolve(false);
} }
const infoUpdates = []; const infoUpdates: Promise<void>[] = [];
const hpValue = Math.floor((target.hp + user.hp) / 2); const hpValue = Math.floor((target.hp + user.hp) / 2);
if (user.hp < hpValue) { if (user.hp < hpValue) {
@ -2803,7 +2804,7 @@ export class LessPPMorePowerAttr extends VariablePowerAttr {
*/ */
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const ppMax = move.pp; const ppMax = move.pp;
const ppUsed = user.moveset.find((m) => m.moveId === move.id).ppUsed; const ppUsed = user.moveset.find((m) => m?.moveId === move.id)?.ppUsed!; // TODO: is the bang correct?
let ppRemains = ppMax - ppUsed; let ppRemains = ppMax - ppUsed;
/** Reduce to 0 to avoid negative numbers if user has 1PP before attack and target has Ability.PRESSURE */ /** Reduce to 0 to avoid negative numbers if user has 1PP before attack and target has Ability.PRESSURE */
@ -2870,6 +2871,7 @@ const beatUpFunc = (user: Pokemon, allyIndex: number): number => {
} }
return (pokemon.species.getBaseStat(Stat.ATK) / 10) + 5; return (pokemon.species.getBaseStat(Stat.ATK) / 10) + 5;
} }
return 0;
}; };
export class BeatUpAttr extends VariablePowerAttr { export class BeatUpAttr extends VariablePowerAttr {
@ -2896,7 +2898,7 @@ export class BeatUpAttr extends VariablePowerAttr {
} }
const doublePowerChanceMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => { const doublePowerChanceMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => {
let message: string = null; let message: string = "";
user.scene.executeWithSeedOffset(() => { user.scene.executeWithSeedOffset(() => {
const rand = Utils.randSeedInt(100); const rand = Utils.randSeedInt(100);
if (rand < move.chance) { if (rand < move.chance) {
@ -2910,7 +2912,7 @@ export class DoublePowerChanceAttr extends VariablePowerAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
let rand: integer; let rand: integer;
user.scene.executeWithSeedOffset(() => rand = Utils.randSeedInt(100), user.scene.currentBattle.turn << 6, user.scene.waveSeed); user.scene.executeWithSeedOffset(() => rand = Utils.randSeedInt(100), user.scene.currentBattle.turn << 6, user.scene.waveSeed);
if (rand < move.chance) { if (rand! < move.chance) {
const power = args[0] as Utils.NumberHolder; const power = args[0] as Utils.NumberHolder;
power.value *= 2; power.value *= 2;
return true; return true;
@ -2926,9 +2928,9 @@ export abstract class ConsecutiveUsePowerMultiplierAttr extends MovePowerMultipl
const moveHistory = user.getLastXMoves(limit + 1).slice(1); const moveHistory = user.getLastXMoves(limit + 1).slice(1);
let count = 0; let count = 0;
let turnMove: TurnMove; let turnMove: TurnMove | undefined;
while (((turnMove = moveHistory.shift())?.move === move.id || (comboMoves.length && comboMoves.includes(turnMove?.move))) && (!resetOnFail || turnMove.result === MoveResult.SUCCESS)) { while (((turnMove = moveHistory.shift())?.move === move.id || (comboMoves.length && comboMoves.includes(turnMove?.move!))) && (!resetOnFail || turnMove?.result === MoveResult.SUCCESS)) { // TODO: is this bang correct?
if (count < (limit - 1)) { if (count < (limit - 1)) {
count++; count++;
} else if (resetOnLimit) { } else if (resetOnLimit) {
@ -3181,7 +3183,7 @@ const magnitudeMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => {
message = i18next.t("moveTriggers:magnitudeMessage", {magnitude: m + 4}); message = i18next.t("moveTriggers:magnitudeMessage", {magnitude: m + 4});
}, user.scene.currentBattle.turn << 6, user.scene.waveSeed); }, user.scene.currentBattle.turn << 6, user.scene.waveSeed);
return message; return message!;
}; };
export class MagnitudePowerAttr extends VariablePowerAttr { export class MagnitudePowerAttr extends VariablePowerAttr {
@ -3197,7 +3199,7 @@ export class MagnitudePowerAttr extends VariablePowerAttr {
let m = 0; let m = 0;
for (; m < magnitudeThresholds.length; m++) { for (; m < magnitudeThresholds.length; m++) {
if (rand < magnitudeThresholds[m]) { if (rand! < magnitudeThresholds[m]) {
break; break;
} }
} }
@ -3360,7 +3362,7 @@ export class SpitUpPowerAttr extends VariablePowerAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const stockpilingTag = user.getTag(StockpilingTag); const stockpilingTag = user.getTag(StockpilingTag);
if (stockpilingTag?.stockpiledCount > 0) { if (stockpilingTag !== null && stockpilingTag.stockpiledCount > 0) {
const power = args[0] as Utils.IntegerHolder; const power = args[0] as Utils.IntegerHolder;
power.value = this.multiplier * stockpilingTag.stockpiledCount; power.value = this.multiplier * stockpilingTag.stockpiledCount;
return true; return true;
@ -3378,7 +3380,7 @@ export class SwallowHealAttr extends HealAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const stockpilingTag = user.getTag(StockpilingTag); const stockpilingTag = user.getTag(StockpilingTag);
if (stockpilingTag?.stockpiledCount > 0) { if (stockpilingTag !== null && stockpilingTag?.stockpiledCount > 0) {
const stockpiled = stockpilingTag.stockpiledCount; const stockpiled = stockpilingTag.stockpiledCount;
let healRatio: number; let healRatio: number;
@ -3400,7 +3402,10 @@ export class SwallowHealAttr extends HealAttr {
} }
} }
const hasStockpileStacksCondition: MoveConditionFunc = (user) => user.getTag(StockpilingTag)?.stockpiledCount > 0; const hasStockpileStacksCondition: MoveConditionFunc = (user) => {
const hasStockpilingTag = user.getTag(StockpilingTag);
return !!hasStockpilingTag && hasStockpilingTag.stockpiledCount > 0;
};
/** /**
* Attribute used for multi-hit moves that increase power in increments of the * Attribute used for multi-hit moves that increase power in increments of the
@ -3471,13 +3476,13 @@ export class LastMoveDoublePowerAttr extends VariablePowerAttr {
const enemy = user.getOpponent(0); const enemy = user.getOpponent(0);
const pokemonActed: Pokemon[] = []; const pokemonActed: Pokemon[] = [];
if (enemy.turnData.acted) { if (enemy?.turnData.acted) {
pokemonActed.push(enemy); pokemonActed.push(enemy);
} }
if (user.scene.currentBattle.double) { if (user.scene.currentBattle.double) {
const userAlly = user.getAlly(); const userAlly = user.getAlly();
const enemyAlly = enemy.getAlly(); const enemyAlly = enemy?.getAlly();
if (userAlly && userAlly.turnData.acted) { if (userAlly && userAlly.turnData.acted) {
pokemonActed.push(userAlly); pokemonActed.push(userAlly);
@ -3751,7 +3756,7 @@ export class VariableMoveTypeAttr extends MoveAttr {
export class FormChangeItemTypeAttr extends VariableMoveTypeAttr { export class FormChangeItemTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.ARCEUS) || [user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.SILVALLY)) { if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.ARCEUS) || [user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.SILVALLY)) {
const form = user.species.speciesId === Species.ARCEUS || user.species.speciesId === Species.SILVALLY ? user.formIndex : user.fusionSpecies.formIndex; const form = user.species.speciesId === Species.ARCEUS || user.species.speciesId === Species.SILVALLY ? user.formIndex : user.fusionSpecies?.formIndex!; // TODO: is this bang correct?
move.type = Type[Type[form]]; move.type = Type[Type[form]];
return true; return true;
@ -3764,7 +3769,7 @@ export class FormChangeItemTypeAttr extends VariableMoveTypeAttr {
export class TechnoBlastTypeAttr extends VariableMoveTypeAttr { export class TechnoBlastTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.GENESECT)) { if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.GENESECT)) {
const form = user.species.speciesId === Species.GENESECT ? user.formIndex : user.fusionSpecies.formIndex; const form = user.species.speciesId === Species.GENESECT ? user.formIndex : user.fusionSpecies?.formIndex;
switch (form) { switch (form) {
case 1: // Shock Drive case 1: // Shock Drive
@ -3793,7 +3798,7 @@ export class TechnoBlastTypeAttr extends VariableMoveTypeAttr {
export class AuraWheelTypeAttr extends VariableMoveTypeAttr { export class AuraWheelTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.MORPEKO)) { if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.MORPEKO)) {
const form = user.species.speciesId === Species.MORPEKO ? user.formIndex : user.fusionSpecies.formIndex; const form = user.species.speciesId === Species.MORPEKO ? user.formIndex : user.fusionSpecies?.formIndex;
switch (form) { switch (form) {
case 1: // Hangry Mode case 1: // Hangry Mode
@ -3813,7 +3818,7 @@ export class AuraWheelTypeAttr extends VariableMoveTypeAttr {
export class RagingBullTypeAttr extends VariableMoveTypeAttr { export class RagingBullTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.PALDEA_TAUROS)) { if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.PALDEA_TAUROS)) {
const form = user.species.speciesId === Species.PALDEA_TAUROS ? user.formIndex : user.fusionSpecies.formIndex; const form = user.species.speciesId === Species.PALDEA_TAUROS ? user.formIndex : user.fusionSpecies?.formIndex;
switch (form) { switch (form) {
case 1: // Blaze breed case 1: // Blaze breed
@ -3836,7 +3841,7 @@ export class RagingBullTypeAttr extends VariableMoveTypeAttr {
export class IvyCudgelTypeAttr extends VariableMoveTypeAttr { export class IvyCudgelTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.OGERPON)) { if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.OGERPON)) {
const form = user.species.speciesId === Species.OGERPON ? user.formIndex : user.fusionSpecies.formIndex; const form = user.species.speciesId === Species.OGERPON ? user.formIndex : user.fusionSpecies?.formIndex;
switch (form) { switch (form) {
case 1: // Wellspring Mask case 1: // Wellspring Mask
@ -4125,23 +4130,23 @@ export class DisableMoveAttr extends MoveEffectAttr {
} }
const moveQueue = target.getLastXMoves(); const moveQueue = target.getLastXMoves();
let turnMove: TurnMove; let turnMove: TurnMove | undefined;
while (moveQueue.length) { while (moveQueue.length) {
turnMove = moveQueue.shift(); turnMove = moveQueue.shift();
if (turnMove.virtual) { if (turnMove?.virtual) {
continue; continue;
} }
const moveIndex = target.getMoveset().findIndex(m => m.moveId === turnMove.move); const moveIndex = target.getMoveset().findIndex(m => m?.moveId === turnMove?.move);
if (moveIndex === -1) { if (moveIndex === -1) {
return false; return false;
} }
const disabledMove = target.getMoveset()[moveIndex]; const disabledMove = target.getMoveset()[moveIndex];
target.summonData.disabledMove = disabledMove.moveId; target.summonData.disabledMove = disabledMove?.moveId!; // TODO: is this bang correct?
target.summonData.disabledTurns = 4; target.summonData.disabledTurns = 4;
user.scene.queueMessage(i18next.t("abilityTriggers:postDefendMoveDisable", { pokemonNameWithAffix: getPokemonNameWithAffix(target), moveName: disabledMove.getName()})); user.scene.queueMessage(i18next.t("abilityTriggers:postDefendMoveDisable", { pokemonNameWithAffix: getPokemonNameWithAffix(target), moveName: disabledMove?.getName()}));
return true; return true;
} }
@ -4150,26 +4155,28 @@ export class DisableMoveAttr extends MoveEffectAttr {
} }
getCondition(): MoveConditionFunc { getCondition(): MoveConditionFunc {
return (user, target, move) => { return (user, target, move): boolean => { // TODO: Not sure what to do here
if (target.summonData.disabledMove || target.isMax()) { if (target.summonData.disabledMove || target.isMax()) {
return false; return false;
} }
const moveQueue = target.getLastXMoves(); const moveQueue = target.getLastXMoves();
let turnMove: TurnMove; let turnMove: TurnMove | undefined;
while (moveQueue.length) { while (moveQueue.length) {
turnMove = moveQueue.shift(); turnMove = moveQueue.shift();
if (turnMove.virtual) { if (turnMove?.virtual) {
continue; continue;
} }
const move = target.getMoveset().find(m => m.moveId === turnMove.move); const move = target.getMoveset().find(m => m?.moveId === turnMove?.move);
if (!move) { if (!move) {
continue; continue;
} }
return true; return true;
} }
return false;
}; };
} }
@ -4242,13 +4249,13 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
return false; return false;
} }
getCondition(): MoveConditionFunc { getCondition(): MoveConditionFunc | null {
return this.failOnOverlap return this.failOnOverlap
? (user, target, move) => !(this.selfTarget ? user : target).getTag(this.tagType) ? (user, target, move) => !(this.selfTarget ? user : target).getTag(this.tagType)
: null; : null;
} }
getTagTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { getTagTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer | void {
switch (this.tagType) { switch (this.tagType) {
case BattlerTagType.RECHARGING: case BattlerTagType.RECHARGING:
case BattlerTagType.PERISH_SONG: case BattlerTagType.PERISH_SONG:
@ -4296,7 +4303,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
if (moveChance < 0) { if (moveChance < 0) {
moveChance = 100; moveChance = 100;
} }
return Math.floor(this.getTagTargetBenefitScore(user, target, move) * (moveChance / 100)); return Math.floor(this.getTagTargetBenefitScore(user, target, move)! * (moveChance / 100)); // TODO: is the bang correct?
} }
} }
@ -4444,11 +4451,11 @@ export class ProtectAttr extends AddBattlerTagAttr {
return ((user, target, move): boolean => { return ((user, target, move): boolean => {
let timesUsed = 0; let timesUsed = 0;
const moveHistory = user.getLastXMoves(); const moveHistory = user.getLastXMoves();
let turnMove: TurnMove; let turnMove: TurnMove | undefined;
while (moveHistory.length) { while (moveHistory.length) {
turnMove = moveHistory.shift(); turnMove = moveHistory.shift();
if (!allMoves[turnMove.move].hasAttr(ProtectAttr) || turnMove.result !== MoveResult.SUCCESS) { if (!allMoves[turnMove?.move!].hasAttr(ProtectAttr) || turnMove?.result !== MoveResult.SUCCESS) { // TODO: is the bang correct?
break; break;
} }
timesUsed++; timesUsed++;
@ -4521,11 +4528,11 @@ export class AddArenaTagAttr extends MoveEffectAttr {
private failOnOverlap: boolean; private failOnOverlap: boolean;
public selfSideTarget: boolean; public selfSideTarget: boolean;
constructor(tagType: ArenaTagType, turnCount?: integer, failOnOverlap: boolean = false, selfSideTarget: boolean = false) { constructor(tagType: ArenaTagType, turnCount?: integer | null, failOnOverlap: boolean = false, selfSideTarget: boolean = false) {
super(true, MoveEffectTrigger.POST_APPLY); super(true, MoveEffectTrigger.POST_APPLY);
this.tagType = tagType; this.tagType = tagType;
this.turnCount = turnCount; this.turnCount = turnCount!; // TODO: is the bang correct?
this.failOnOverlap = failOnOverlap; this.failOnOverlap = failOnOverlap;
this.selfSideTarget = selfSideTarget; this.selfSideTarget = selfSideTarget;
} }
@ -4543,7 +4550,7 @@ export class AddArenaTagAttr extends MoveEffectAttr {
return false; return false;
} }
getCondition(): MoveConditionFunc { getCondition(): MoveConditionFunc | null {
return this.failOnOverlap return this.failOnOverlap
? (user, target, move) => !user.scene.arena.getTagOnSide(this.tagType, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY) ? (user, target, move) => !user.scene.arena.getTagOnSide(this.tagType, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)
: null; : null;
@ -4716,13 +4723,13 @@ export class SwapArenaTagsAttr extends MoveEffectAttr {
if (tagPlayerTemp) { if (tagPlayerTemp) {
for (const swapTagsType of tagPlayerTemp) { for (const swapTagsType of tagPlayerTemp) {
user.scene.arena.removeTagOnSide(swapTagsType.tagType, ArenaTagSide.PLAYER, true); user.scene.arena.removeTagOnSide(swapTagsType.tagType, ArenaTagSide.PLAYER, true);
user.scene.arena.addTag(swapTagsType.tagType, swapTagsType.turnCount, swapTagsType.sourceMove, swapTagsType.sourceId, ArenaTagSide.ENEMY, true); user.scene.arena.addTag(swapTagsType.tagType, swapTagsType.turnCount, swapTagsType.sourceMove, swapTagsType.sourceId!, ArenaTagSide.ENEMY, true); // TODO: is the bang correct?
} }
} }
if (tagEnemyTemp) { if (tagEnemyTemp) {
for (const swapTagsType of tagEnemyTemp) { for (const swapTagsType of tagEnemyTemp) {
user.scene.arena.removeTagOnSide(swapTagsType.tagType, ArenaTagSide.ENEMY, true); user.scene.arena.removeTagOnSide(swapTagsType.tagType, ArenaTagSide.ENEMY, true);
user.scene.arena.addTag(swapTagsType.tagType, swapTagsType.turnCount, swapTagsType.sourceMove, swapTagsType.sourceId, ArenaTagSide.PLAYER, true); user.scene.arena.addTag(swapTagsType.tagType, swapTagsType.turnCount, swapTagsType.sourceMove, swapTagsType.sourceId!, ArenaTagSide.PLAYER, true); // TODO: is the bang correct?
} }
} }
@ -4836,7 +4843,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
if (switchOutTarget.hp > 0) { if (switchOutTarget.hp > 0) {
// for opponent switching out // for opponent switching out
user.scene.prependToPhase(new SwitchSummonPhase(user.scene, switchOutTarget.getFieldIndex(), user.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot), false, this.batonPass, false), MoveEndPhase); user.scene.prependToPhase(new SwitchSummonPhase(user.scene, switchOutTarget.getFieldIndex(), (user.scene.currentBattle.trainer ? user.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0), false, this.batonPass, false), MoveEndPhase);
} }
} else { } else {
// Switch out logic for everything else // Switch out logic for everything else
@ -4926,7 +4933,7 @@ export class RemoveTypeAttr extends MoveEffectAttr {
return false; return false;
} }
if (user.isTerastallized && user.getTeraType() === this.removedType) { // active tera types cannot be removed if (user.isTerastallized() && user.getTeraType() === this.removedType) { // active tera types cannot be removed
return false; return false;
} }
@ -5048,7 +5055,7 @@ export class FirstMoveTypeAttr extends MoveEffectAttr {
return false; return false;
} }
const firstMoveType = target.getMoveset()[0].getMove().type; const firstMoveType = target.getMoveset()[0]?.getMove().type!; // TODO: is this bang correct?
user.summonData.types = [ firstMoveType ]; user.summonData.types = [ firstMoveType ];
user.scene.queueMessage(i18next.t("battle:transformedIntoType", {pokemonName: getPokemonNameWithAffix(user), type: i18next.t(`pokemonInfo:Type.${Type[firstMoveType]}`)})); user.scene.queueMessage(i18next.t("battle:transformedIntoType", {pokemonName: getPokemonNameWithAffix(user), type: i18next.t(`pokemonInfo:Type.${Type[firstMoveType]}`)}));
@ -5057,21 +5064,21 @@ export class FirstMoveTypeAttr extends MoveEffectAttr {
} }
export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr { export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr {
private enemyMoveset: boolean; private enemyMoveset: boolean | null;
constructor(enemyMoveset?: boolean) { constructor(enemyMoveset?: boolean) {
super(); super();
this.enemyMoveset = enemyMoveset; this.enemyMoveset = enemyMoveset!; // TODO: is this bang correct?
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const moveset = (!this.enemyMoveset ? user : target).getMoveset(); const moveset = (!this.enemyMoveset ? user : target).getMoveset();
const moves = moveset.filter(m => !m.getMove().hasFlag(MoveFlags.IGNORE_VIRTUAL)); const moves = moveset.filter(m => !m?.getMove().hasFlag(MoveFlags.IGNORE_VIRTUAL));
if (moves.length) { if (moves.length) {
const move = moves[user.randSeedInt(moves.length)]; const move = moves[user.randSeedInt(moves.length)];
const moveIndex = moveset.findIndex(m => m.moveId === move.moveId); const moveIndex = moveset.findIndex(m => m?.moveId === move?.moveId);
const moveTargets = getMoveTargets(user, move.moveId); const moveTargets = getMoveTargets(user, move?.moveId!); // TODO: is this bang correct?
if (!moveTargets.targets.length) { if (!moveTargets.targets.length) {
return false; return false;
} }
@ -5092,8 +5099,8 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr {
} }
} }
const targets = selectTargets; const targets = selectTargets;
user.getMoveQueue().push({ move: move.moveId, targets: targets, ignorePP: true }); user.getMoveQueue().push({ move: move?.moveId!, targets: targets, ignorePP: true }); // TODO: is this bang correct?
user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, moveset[moveIndex], true)); user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, moveset[moveIndex]!, true)); // There's a PR to re-do the move(s) that use this Attr, gonna put `!` for now
return true; return true;
} }
@ -5335,12 +5342,12 @@ export class ReducePpMoveAttr extends MoveEffectAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
// Null checks can be skipped due to condition function // Null checks can be skipped due to condition function
const lastMove = target.getLastXMoves().find(() => true); const lastMove = target.getLastXMoves().find(() => true);
const movesetMove = target.getMoveset().find(m => m.moveId === lastMove.move); const movesetMove = target.getMoveset().find(m => m?.moveId === lastMove?.move);
const lastPpUsed = movesetMove.ppUsed; const lastPpUsed = movesetMove?.ppUsed!; // TODO: is the bang correct?
movesetMove.ppUsed = Math.min(movesetMove.ppUsed + this.reduction, movesetMove.getMovePp()); movesetMove!.ppUsed = Math.min((movesetMove?.ppUsed!) + this.reduction, movesetMove?.getMovePp()!); // TODO: is the bang correct?
const message = i18next.t("battle:ppReduced", {targetName: getPokemonNameWithAffix(target), moveName: movesetMove.getName(), reduction: movesetMove.ppUsed - lastPpUsed}); const message = i18next.t("battle:ppReduced", {targetName: getPokemonNameWithAffix(target), moveName: movesetMove?.getName(), reduction: (movesetMove?.ppUsed!) - lastPpUsed}); // TODO: is the bang correct?
user.scene.eventTarget.dispatchEvent(new MoveUsedEvent(target?.id, movesetMove.getMove(), movesetMove.ppUsed)); user.scene.eventTarget.dispatchEvent(new MoveUsedEvent(target?.id, movesetMove?.getMove()!, movesetMove?.ppUsed!)); // TODO: are these bangs correct?
user.scene.queueMessage(message); user.scene.queueMessage(message);
return true; return true;
@ -5350,7 +5357,7 @@ export class ReducePpMoveAttr extends MoveEffectAttr {
return (user, target, move) => { return (user, target, move) => {
const lastMove = target.getLastXMoves().find(() => true); const lastMove = target.getLastXMoves().find(() => true);
if (lastMove) { if (lastMove) {
const movesetMove = target.getMoveset().find(m => m.moveId === lastMove.move); const movesetMove = target.getMoveset().find(m => m?.moveId === lastMove.move);
return !!movesetMove?.getPpRatio(); return !!movesetMove?.getPpRatio();
} }
return false; return false;
@ -5360,7 +5367,7 @@ export class ReducePpMoveAttr extends MoveEffectAttr {
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
const lastMove = target.getLastXMoves().find(() => true); const lastMove = target.getLastXMoves().find(() => true);
if (lastMove) { if (lastMove) {
const movesetMove = target.getMoveset().find(m => m.moveId === lastMove.move); const movesetMove = target.getMoveset().find(m => m?.moveId === lastMove.move);
if (movesetMove) { if (movesetMove) {
const maxPp = movesetMove.getMovePp(); const maxPp = movesetMove.getMovePp();
const ppLeft = maxPp - movesetMove.ppUsed; const ppLeft = maxPp - movesetMove.ppUsed;
@ -5397,7 +5404,7 @@ export class AttackReducePpMoveAttr extends ReducePpMoveAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const lastMove = target.getLastXMoves().find(() => true); const lastMove = target.getLastXMoves().find(() => true);
if (lastMove) { if (lastMove) {
const movesetMove = target.getMoveset().find(m => m.moveId === lastMove.move); const movesetMove = target.getMoveset().find(m => m?.moveId === lastMove.move);
if (Boolean(movesetMove?.getPpRatio())) { if (Boolean(movesetMove?.getPpRatio())) {
super.apply(user, target, move, args); super.apply(user, target, move, args);
} }
@ -5443,7 +5450,7 @@ export class MovesetCopyMoveAttr extends OverrideMoveEffectAttr {
const copiedMove = allMoves[targetMoves[0].move]; const copiedMove = allMoves[targetMoves[0].move];
const thisMoveIndex = user.getMoveset().findIndex(m => m.moveId === move.id); const thisMoveIndex = user.getMoveset().findIndex(m => m?.moveId === move.id);
if (thisMoveIndex === -1) { if (thisMoveIndex === -1) {
return false; return false;
@ -5494,7 +5501,7 @@ export class SketchAttr extends MoveEffectAttr {
} }
const sketchedMove = allMoves[targetMove.move]; const sketchedMove = allMoves[targetMove.move];
const sketchIndex = user.getMoveset().findIndex(m => m.moveId === move.id); const sketchIndex = user.getMoveset().findIndex(m => m?.moveId === move.id);
if (sketchIndex === -1) { if (sketchIndex === -1) {
return false; return false;
} }
@ -5533,7 +5540,7 @@ export class SketchAttr extends MoveEffectAttr {
return false; return false;
} }
if (user.getMoveset().find(m => m.moveId === targetMove.move)) { if (user.getMoveset().find(m => m?.moveId === targetMove.move)) {
return false; return false;
} }
@ -5722,7 +5729,7 @@ export class TransformAttr extends MoveEffectAttr {
user.summonData.fusionGender = target.getFusionGender(); user.summonData.fusionGender = target.getFusionGender();
user.summonData.stats = [ user.stats[Stat.HP] ].concat(target.stats.slice(1)); user.summonData.stats = [ user.stats[Stat.HP] ].concat(target.stats.slice(1));
user.summonData.battleStats = target.summonData.battleStats.slice(0); user.summonData.battleStats = target.summonData.battleStats.slice(0);
user.summonData.moveset = target.getMoveset().map(m => new PokemonMove(m.moveId, m.ppUsed, m.ppUp)); user.summonData.moveset = target.getMoveset().map(m => new PokemonMove(m?.moveId!, m?.ppUsed, m?.ppUp)); // TODO: is this bang correct?
user.summonData.types = target.getTypes(); user.summonData.types = target.getTypes();
user.scene.queueMessage(i18next.t("moveTriggers:transformedIntoTarget", {pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target)})); user.scene.queueMessage(i18next.t("moveTriggers:transformedIntoTarget", {pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target)}));
@ -5790,7 +5797,7 @@ export class LastResortAttr extends MoveAttr {
getCondition(): MoveConditionFunc { getCondition(): MoveConditionFunc {
return (user: Pokemon, target: Pokemon, move: Move) => { return (user: Pokemon, target: Pokemon, move: Move) => {
const uniqueUsedMoveIds = new Set<Moves>(); const uniqueUsedMoveIds = new Set<Moves>();
const movesetMoveIds = user.getMoveset().map(m => m.moveId); const movesetMoveIds = user.getMoveset().map(m => m?.moveId);
user.getMoveHistory().map(m => { user.getMoveHistory().map(m => {
if (m.move !== move.id && movesetMoveIds.find(mm => mm === m.move)) { if (m.move !== move.id && movesetMoveIds.find(mm => mm === m.move)) {
uniqueUsedMoveIds.add(m.move); uniqueUsedMoveIds.add(m.move);
@ -5864,7 +5871,7 @@ const targetSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target
export type MoveAttrFilter = (attr: MoveAttr) => boolean; export type MoveAttrFilter = (attr: MoveAttr) => boolean;
function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<void> { function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
const attrPromises: Promise<boolean>[] = []; const attrPromises: Promise<boolean>[] = [];
const moveAttrs = move.attrs.filter(a => attrFilter(a)); const moveAttrs = move.attrs.filter(a => attrFilter(a));
@ -5878,11 +5885,11 @@ function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon, targe
}); });
} }
export function applyMoveAttrs(attrType: Constructor<MoveAttr>, user: Pokemon, target: Pokemon, move: Move, ...args: any[]): Promise<void> { export function applyMoveAttrs(attrType: Constructor<MoveAttr>, user: Pokemon | null, target: Pokemon | null, move: Move, ...args: any[]): Promise<void> {
return applyMoveAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args); return applyMoveAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args);
} }
export function applyFilteredMoveAttrs(attrFilter: MoveAttrFilter, user: Pokemon, target: Pokemon, move: Move, ...args: any[]): Promise<void> { export function applyFilteredMoveAttrs(attrFilter: MoveAttrFilter, user: Pokemon, target: Pokemon | null, move: Move, ...args: any[]): Promise<void> {
return applyMoveAttrsInternal(attrFilter, user, target, move, args); return applyMoveAttrsInternal(attrFilter, user, target, move, args);
} }
@ -6889,7 +6896,7 @@ export function initMoves() {
.unimplemented(), .unimplemented(),
new SelfStatusMove(Moves.REFRESH, Type.NORMAL, -1, 20, -1, 0, 3) new SelfStatusMove(Moves.REFRESH, Type.NORMAL, -1, 20, -1, 0, 3)
.attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN) .attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN)
.condition((user, target, move) => user.status && (user.status.effect === StatusEffect.PARALYSIS || user.status.effect === StatusEffect.POISON || user.status.effect === StatusEffect.TOXIC || user.status.effect === StatusEffect.BURN)), .condition((user, target, move) => !!user.status && (user.status.effect === StatusEffect.PARALYSIS || user.status.effect === StatusEffect.POISON || user.status.effect === StatusEffect.TOXIC || user.status.effect === StatusEffect.BURN)),
new SelfStatusMove(Moves.GRUDGE, Type.GHOST, -1, 5, -1, 0, 3) new SelfStatusMove(Moves.GRUDGE, Type.GHOST, -1, 5, -1, 0, 3)
.unimplemented(), .unimplemented(),
new SelfStatusMove(Moves.SNATCH, Type.DARK, -1, 10, -1, 4, 3) new SelfStatusMove(Moves.SNATCH, Type.DARK, -1, 10, -1, 4, 3)
@ -6952,7 +6959,7 @@ export function initMoves() {
.attr(FlinchAttr), .attr(FlinchAttr),
new AttackMove(Moves.WEATHER_BALL, Type.NORMAL, MoveCategory.SPECIAL, 50, 100, 10, -1, 0, 3) new AttackMove(Moves.WEATHER_BALL, Type.NORMAL, MoveCategory.SPECIAL, 50, 100, 10, -1, 0, 3)
.attr(WeatherBallTypeAttr) .attr(WeatherBallTypeAttr)
.attr(MovePowerMultiplierAttr, (user, target, move) => [WeatherType.SUNNY, WeatherType.RAIN, WeatherType.SANDSTORM, WeatherType.HAIL, WeatherType.SNOW, WeatherType.FOG, WeatherType.HEAVY_RAIN, WeatherType.HARSH_SUN].includes(user.scene.arena.weather?.weatherType) && !user.scene.arena.weather?.isEffectSuppressed(user.scene) ? 2 : 1) .attr(MovePowerMultiplierAttr, (user, target, move) => [WeatherType.SUNNY, WeatherType.RAIN, WeatherType.SANDSTORM, WeatherType.HAIL, WeatherType.SNOW, WeatherType.FOG, WeatherType.HEAVY_RAIN, WeatherType.HARSH_SUN].includes(user.scene.arena.weather?.weatherType!) && !user.scene.arena.weather?.isEffectSuppressed(user.scene) ? 2 : 1) // TODO: is this bang correct?
.ballBombMove(), .ballBombMove(),
new StatusMove(Moves.AROMATHERAPY, Type.GRASS, -1, 5, -1, 0, 3) new StatusMove(Moves.AROMATHERAPY, Type.GRASS, -1, 5, -1, 0, 3)
.attr(PartyStatusCureAttr, i18next.t("moveTriggers:soothingAromaWaftedThroughArea"), Abilities.SAP_SIPPER) .attr(PartyStatusCureAttr, i18next.t("moveTriggers:soothingAromaWaftedThroughArea"), Abilities.SAP_SIPPER)
@ -7120,7 +7127,7 @@ export function initMoves() {
new AttackMove(Moves.CLOSE_COMBAT, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 4) new AttackMove(Moves.CLOSE_COMBAT, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 4)
.attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true), .attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true),
new AttackMove(Moves.PAYBACK, Type.DARK, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 4) new AttackMove(Moves.PAYBACK, Type.DARK, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 4)
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getLastXMoves(1).find(m => m.turn === target.scene.currentBattle.turn) || user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.BALL ? 2 : 1), .attr(MovePowerMultiplierAttr, (user, target, move) => target.getLastXMoves(1).find(m => m.turn === target.scene.currentBattle.turn) || user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.BALL ? 2 : 1),
new AttackMove(Moves.ASSURANCE, Type.DARK, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 4) new AttackMove(Moves.ASSURANCE, Type.DARK, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 4)
.attr(MovePowerMultiplierAttr, (user, target, move) => target.turnData.damageTaken > 0 ? 2 : 1), .attr(MovePowerMultiplierAttr, (user, target, move) => target.turnData.damageTaken > 0 ? 2 : 1),
new StatusMove(Moves.EMBARGO, Type.DARK, 100, 15, -1, 0, 4) new StatusMove(Moves.EMBARGO, Type.DARK, 100, 15, -1, 0, 4)
@ -7135,7 +7142,7 @@ export function initMoves() {
if (user.status?.effect && isNonVolatileStatusEffect(user.status.effect)) { if (user.status?.effect && isNonVolatileStatusEffect(user.status.effect)) {
statusToApply = user.status.effect; statusToApply = user.status.effect;
} }
return statusToApply && target.canSetStatus(statusToApply, false, false, user); return !!statusToApply && target.canSetStatus(statusToApply, false, false, user);
} }
), ),
new AttackMove(Moves.TRUMP_CARD, Type.NORMAL, MoveCategory.SPECIAL, -1, -1, 5, -1, 0, 4) new AttackMove(Moves.TRUMP_CARD, Type.NORMAL, MoveCategory.SPECIAL, -1, -1, 5, -1, 0, 4)
@ -7173,7 +7180,7 @@ export function initMoves() {
new StatusMove(Moves.WORRY_SEED, Type.GRASS, 100, 10, -1, 0, 4) new StatusMove(Moves.WORRY_SEED, Type.GRASS, 100, 10, -1, 0, 4)
.attr(AbilityChangeAttr, Abilities.INSOMNIA), .attr(AbilityChangeAttr, Abilities.INSOMNIA),
new AttackMove(Moves.SUCKER_PUNCH, Type.DARK, MoveCategory.PHYSICAL, 70, 100, 5, -1, 1, 4) new AttackMove(Moves.SUCKER_PUNCH, Type.DARK, MoveCategory.PHYSICAL, 70, 100, 5, -1, 1, 4)
.condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()].move.move].category !== MoveCategory.STATUS), .condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].category !== MoveCategory.STATUS), // TODO: is this bang correct?
new StatusMove(Moves.TOXIC_SPIKES, Type.POISON, -1, 20, -1, 0, 4) new StatusMove(Moves.TOXIC_SPIKES, Type.POISON, -1, 20, -1, 0, 4)
.attr(AddArenaTrapTagAttr, ArenaTagType.TOXIC_SPIKES) .attr(AddArenaTrapTagAttr, ArenaTagType.TOXIC_SPIKES)
.target(MoveTarget.ENEMY_SIDE), .target(MoveTarget.ENEMY_SIDE),
@ -8016,7 +8023,7 @@ export function initMoves() {
new AttackMove(Moves.SMART_STRIKE, Type.STEEL, MoveCategory.PHYSICAL, 70, -1, 10, -1, 0, 7), new AttackMove(Moves.SMART_STRIKE, Type.STEEL, MoveCategory.PHYSICAL, 70, -1, 10, -1, 0, 7),
new StatusMove(Moves.PURIFY, Type.POISON, -1, 20, -1, 0, 7) new StatusMove(Moves.PURIFY, Type.POISON, -1, 20, -1, 0, 7)
.condition( .condition(
(user: Pokemon, target: Pokemon, move: Move) => isNonVolatileStatusEffect(target.status?.effect)) (user: Pokemon, target: Pokemon, move: Move) => isNonVolatileStatusEffect(target.status?.effect!)) // TODO: is this bang correct?
.attr(HealAttr, 0.5) .attr(HealAttr, 0.5)
.attr(HealStatusEffectAttr, false, ...getNonVolatileStatusEffects()) .attr(HealStatusEffectAttr, false, ...getNonVolatileStatusEffects())
.triageMove(), .triageMove(),
@ -8738,7 +8745,7 @@ export function initMoves() {
.slicingMove(), .slicingMove(),
new AttackMove(Moves.HYDRO_STEAM, Type.WATER, MoveCategory.SPECIAL, 80, 100, 15, -1, 0, 9) new AttackMove(Moves.HYDRO_STEAM, Type.WATER, MoveCategory.SPECIAL, 80, 100, 15, -1, 0, 9)
.attr(IgnoreWeatherTypeDebuffAttr, WeatherType.SUNNY) .attr(IgnoreWeatherTypeDebuffAttr, WeatherType.SUNNY)
.attr(MovePowerMultiplierAttr, (user, target, move) => [WeatherType.SUNNY, WeatherType.HARSH_SUN].includes(user.scene.arena.weather?.weatherType) && !user.scene.arena.weather?.isEffectSuppressed(user.scene) ? 1.5 : 1), .attr(MovePowerMultiplierAttr, (user, target, move) => [WeatherType.SUNNY, WeatherType.HARSH_SUN].includes(user.scene.arena.weather?.weatherType!) && !user.scene.arena.weather?.isEffectSuppressed(user.scene) ? 1.5 : 1), // TODO: is this bang correct?
new AttackMove(Moves.RUINATION, Type.DARK, MoveCategory.SPECIAL, -1, 90, 10, -1, 0, 9) new AttackMove(Moves.RUINATION, Type.DARK, MoveCategory.SPECIAL, -1, 90, 10, -1, 0, 9)
.attr(TargetHalfHpDamageAttr), .attr(TargetHalfHpDamageAttr),
new AttackMove(Moves.COLLISION_COURSE, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9) new AttackMove(Moves.COLLISION_COURSE, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9)
@ -8846,7 +8853,7 @@ export function initMoves() {
new SelfStatusMove(Moves.BURNING_BULWARK, Type.FIRE, -1, 10, -1, 4, 9) new SelfStatusMove(Moves.BURNING_BULWARK, Type.FIRE, -1, 10, -1, 4, 9)
.attr(ProtectAttr, BattlerTagType.BURNING_BULWARK), .attr(ProtectAttr, BattlerTagType.BURNING_BULWARK),
new AttackMove(Moves.THUNDERCLAP, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 5, -1, 1, 9) new AttackMove(Moves.THUNDERCLAP, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 5, -1, 1, 9)
.condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()].move.move].category !== MoveCategory.STATUS), .condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].category !== MoveCategory.STATUS), // TODO: is this bang correct?
new AttackMove(Moves.MIGHTY_CLEAVE, Type.ROCK, MoveCategory.PHYSICAL, 95, 100, 5, -1, 0, 9) new AttackMove(Moves.MIGHTY_CLEAVE, Type.ROCK, MoveCategory.PHYSICAL, 95, 100, 5, -1, 0, 9)
.slicingMove() .slicingMove()
.ignoresProtect(), .ignoresProtect(),
@ -8873,7 +8880,7 @@ export function initMoves() {
.partial(), .partial(),
new AttackMove(Moves.UPPER_HAND, Type.FIGHTING, MoveCategory.PHYSICAL, 65, 100, 15, 100, 3, 9) new AttackMove(Moves.UPPER_HAND, Type.FIGHTING, MoveCategory.PHYSICAL, 65, 100, 15, 100, 3, 9)
.attr(FlinchAttr) .attr(FlinchAttr)
.condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()].move.move].category !== MoveCategory.STATUS && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()].move.move].priority > 0 ) .condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].category !== MoveCategory.STATUS && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].priority > 0 ) // TODO: is this bang correct?
//TODO: Should also apply when target move priority increased by ability ex. gale wings //TODO: Should also apply when target move priority increased by ability ex. gale wings
.partial(), .partial(),
new AttackMove(Moves.MALIGNANT_CHAIN, Type.POISON, MoveCategory.SPECIAL, 100, 100, 5, 50, 0, 9) new AttackMove(Moves.MALIGNANT_CHAIN, Type.POISON, MoveCategory.SPECIAL, 100, 100, 5, 50, 0, 9)

View File

@ -15,8 +15,8 @@ export function getNatureName(nature: Nature, includeStatEffects: boolean = fals
} }
if (includeStatEffects) { if (includeStatEffects) {
const stats = Utils.getEnumValues(Stat).slice(1); const stats = Utils.getEnumValues(Stat).slice(1);
let increasedStat: Stat = null; let increasedStat: Stat | null = null;
let decreasedStat: Stat = null; let decreasedStat: Stat | null = null;
for (const stat of stats) { for (const stat of stats) {
const multiplier = getNatureStatMultiplier(nature, stat); const multiplier = getNatureStatMultiplier(nature, stat);
if (multiplier > 1) { if (multiplier > 1) {

View File

@ -59,26 +59,26 @@ export type EvolutionConditionEnforceFunc = (p: Pokemon) => void;
export class SpeciesFormEvolution { export class SpeciesFormEvolution {
public speciesId: Species; public speciesId: Species;
public preFormKey: string; public preFormKey: string | null;
public evoFormKey: string; public evoFormKey: string | null;
public level: integer; public level: integer;
public item: EvolutionItem; public item: EvolutionItem | null;
public condition: SpeciesEvolutionCondition; public condition: SpeciesEvolutionCondition | null;
public wildDelay: SpeciesWildEvolutionDelay; public wildDelay: SpeciesWildEvolutionDelay | null;
constructor(speciesId: Species, preFormKey: string, evoFormKey: string, level: integer, item: EvolutionItem, condition: SpeciesEvolutionCondition, wildDelay?: SpeciesWildEvolutionDelay) { constructor(speciesId: Species, preFormKey: string | null, evoFormKey: string | null, level: integer, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay | null) {
this.speciesId = speciesId; this.speciesId = speciesId;
this.preFormKey = preFormKey; this.preFormKey = preFormKey;
this.evoFormKey = evoFormKey; this.evoFormKey = evoFormKey;
this.level = level; this.level = level;
this.item = item || EvolutionItem.NONE; this.item = item || EvolutionItem.NONE;
this.condition = condition; this.condition = condition;
this.wildDelay = wildDelay || SpeciesWildEvolutionDelay.NONE; this.wildDelay = wildDelay!; // TODO: is this bang correct?
} }
} }
export class SpeciesEvolution extends SpeciesFormEvolution { export class SpeciesEvolution extends SpeciesFormEvolution {
constructor(speciesId: Species, level: integer, item: EvolutionItem, condition: SpeciesEvolutionCondition, wildDelay?: SpeciesWildEvolutionDelay) { constructor(speciesId: Species, level: integer, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay | null) {
super(speciesId, null, null, level, item, condition, wildDelay); super(speciesId, null, null, level, item, condition, wildDelay);
} }
} }
@ -95,7 +95,7 @@ export class FusionSpeciesFormEvolution extends SpeciesFormEvolution {
export class SpeciesEvolutionCondition { export class SpeciesEvolutionCondition {
public predicate: EvolutionConditionPredicate; public predicate: EvolutionConditionPredicate;
public enforceFunc: EvolutionConditionEnforceFunc; public enforceFunc: EvolutionConditionEnforceFunc | undefined;
constructor(predicate: EvolutionConditionPredicate, enforceFunc?: EvolutionConditionEnforceFunc) { constructor(predicate: EvolutionConditionPredicate, enforceFunc?: EvolutionConditionEnforceFunc) {
this.predicate = predicate; this.predicate = predicate;
@ -1264,17 +1264,17 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.EXEGGUTOR, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.EXEGGUTOR, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG)
], ],
[Species.TANGELA]: [ [Species.TANGELA]: [
new SpeciesEvolution(Species.TANGROWTH, 34, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.ANCIENT_POWER).length > 0), SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.TANGROWTH, 34, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.ANCIENT_POWER).length > 0), SpeciesWildEvolutionDelay.LONG)
], ],
[Species.LICKITUNG]: [ [Species.LICKITUNG]: [
new SpeciesEvolution(Species.LICKILICKY, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.ROLLOUT).length > 0), SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.LICKILICKY, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.ROLLOUT).length > 0), SpeciesWildEvolutionDelay.LONG)
], ],
[Species.STARYU]: [ [Species.STARYU]: [
new SpeciesEvolution(Species.STARMIE, 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.STARMIE, 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG)
], ],
[Species.EEVEE]: [ [Species.EEVEE]: [
new SpeciesFormEvolution(Species.SYLVEON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(70, p => !!p.getMoveset().find(m => m.getMove().type === Type.FAIRY)), SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(Species.SYLVEON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(70, p => !!p.getMoveset().find(m => m?.getMove().type === Type.FAIRY)), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.SYLVEON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(70, p => !!p.getMoveset().find(m => m.getMove().type === Type.FAIRY)), SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(Species.SYLVEON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(70, p => !!p.getMoveset().find(m => m?.getMove().type === Type.FAIRY)), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ESPEON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(70, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(Species.ESPEON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(70, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.ESPEON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(70, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(Species.ESPEON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(70, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG),
new SpeciesFormEvolution(Species.UMBREON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(70, p => p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(Species.UMBREON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(70, p => p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.LONG),
@ -1294,13 +1294,13 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.TOGEKISS, 1, EvolutionItem.SHINY_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG) new SpeciesEvolution(Species.TOGEKISS, 1, EvolutionItem.SHINY_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG)
], ],
[Species.AIPOM]: [ [Species.AIPOM]: [
new SpeciesEvolution(Species.AMBIPOM, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.DOUBLE_HIT).length > 0), SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.AMBIPOM, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.DOUBLE_HIT).length > 0), SpeciesWildEvolutionDelay.LONG)
], ],
[Species.SUNKERN]: [ [Species.SUNKERN]: [
new SpeciesEvolution(Species.SUNFLORA, 1, EvolutionItem.SUN_STONE, null, SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.SUNFLORA, 1, EvolutionItem.SUN_STONE, null, SpeciesWildEvolutionDelay.LONG)
], ],
[Species.YANMA]: [ [Species.YANMA]: [
new SpeciesEvolution(Species.YANMEGA, 33, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.ANCIENT_POWER).length > 0), SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.YANMEGA, 33, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.ANCIENT_POWER).length > 0), SpeciesWildEvolutionDelay.LONG)
], ],
[Species.MURKROW]: [ [Species.MURKROW]: [
new SpeciesEvolution(Species.HONCHKROW, 1, EvolutionItem.DUSK_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG) new SpeciesEvolution(Species.HONCHKROW, 1, EvolutionItem.DUSK_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG)
@ -1309,17 +1309,17 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.MISMAGIUS, 1, EvolutionItem.DUSK_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG) new SpeciesEvolution(Species.MISMAGIUS, 1, EvolutionItem.DUSK_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG)
], ],
[Species.GIRAFARIG]: [ [Species.GIRAFARIG]: [
new SpeciesEvolution(Species.FARIGIRAF, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.TWIN_BEAM).length > 0), SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.FARIGIRAF, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.TWIN_BEAM).length > 0), SpeciesWildEvolutionDelay.LONG)
], ],
[Species.DUNSPARCE]: [ [Species.DUNSPARCE]: [
new SpeciesFormEvolution(Species.DUDUNSPARCE, "", "three-segment", 32, null, new SpeciesEvolutionCondition(p => { new SpeciesFormEvolution(Species.DUDUNSPARCE, "", "three-segment", 32, null, new SpeciesEvolutionCondition(p => {
let ret = false; let ret = false;
if (p.moveset.filter(m => m.moveId === Moves.HYPER_DRILL).length > 0) { if (p.moveset.filter(m => m?.moveId === Moves.HYPER_DRILL).length > 0) {
p.scene.executeWithSeedOffset(() => ret = !Utils.randSeedInt(4), p.id); p.scene.executeWithSeedOffset(() => ret = !Utils.randSeedInt(4), p.id);
} }
return ret; return ret;
}), SpeciesWildEvolutionDelay.LONG), }), SpeciesWildEvolutionDelay.LONG),
new SpeciesEvolution(Species.DUDUNSPARCE, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.HYPER_DRILL).length > 0), SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.DUDUNSPARCE, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.HYPER_DRILL).length > 0), SpeciesWildEvolutionDelay.LONG)
], ],
[Species.GLIGAR]: [ [Species.GLIGAR]: [
new SpeciesEvolution(Species.GLISCOR, 1, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT /* Razor fang at night*/), SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.GLISCOR, 1, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT /* Razor fang at night*/), SpeciesWildEvolutionDelay.LONG)
@ -1331,10 +1331,10 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.URSALUNA, 1, EvolutionItem.PEAT_BLOCK, null, SpeciesWildEvolutionDelay.VERY_LONG) //Ursaring does not evolve into Bloodmoon Ursaluna new SpeciesEvolution(Species.URSALUNA, 1, EvolutionItem.PEAT_BLOCK, null, SpeciesWildEvolutionDelay.VERY_LONG) //Ursaring does not evolve into Bloodmoon Ursaluna
], ],
[Species.PILOSWINE]: [ [Species.PILOSWINE]: [
new SpeciesEvolution(Species.MAMOSWINE, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.ANCIENT_POWER).length > 0), SpeciesWildEvolutionDelay.VERY_LONG) new SpeciesEvolution(Species.MAMOSWINE, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.ANCIENT_POWER).length > 0), SpeciesWildEvolutionDelay.VERY_LONG)
], ],
[Species.STANTLER]: [ [Species.STANTLER]: [
new SpeciesEvolution(Species.WYRDEER, 25, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.PSYSHIELD_BASH).length > 0), SpeciesWildEvolutionDelay.VERY_LONG) new SpeciesEvolution(Species.WYRDEER, 25, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.PSYSHIELD_BASH).length > 0), SpeciesWildEvolutionDelay.VERY_LONG)
], ],
[Species.LOMBRE]: [ [Species.LOMBRE]: [
new SpeciesEvolution(Species.LUDICOLO, 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.LUDICOLO, 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG)
@ -1352,11 +1352,11 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.ROSERADE, 1, EvolutionItem.SHINY_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG) new SpeciesEvolution(Species.ROSERADE, 1, EvolutionItem.SHINY_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG)
], ],
[Species.BONSLY]: [ [Species.BONSLY]: [
new SpeciesEvolution(Species.SUDOWOODO, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.MIMIC).length > 0), SpeciesWildEvolutionDelay.MEDIUM) new SpeciesEvolution(Species.SUDOWOODO, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0), SpeciesWildEvolutionDelay.MEDIUM)
], ],
[Species.MIME_JR]: [ [Species.MIME_JR]: [
new SpeciesEvolution(Species.GALAR_MR_MIME, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.MIMIC).length > 0 && (p.scene.arena.biomeType === Biome.ICE_CAVE || p.scene.arena.biomeType === Biome.SNOWY_FOREST)), SpeciesWildEvolutionDelay.MEDIUM), new SpeciesEvolution(Species.GALAR_MR_MIME, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0 && (p.scene.arena.biomeType === Biome.ICE_CAVE || p.scene.arena.biomeType === Biome.SNOWY_FOREST)), SpeciesWildEvolutionDelay.MEDIUM),
new SpeciesEvolution(Species.MR_MIME, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.MIMIC).length > 0), SpeciesWildEvolutionDelay.MEDIUM) new SpeciesEvolution(Species.MR_MIME, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0), SpeciesWildEvolutionDelay.MEDIUM)
], ],
[Species.PANSAGE]: [ [Species.PANSAGE]: [
new SpeciesEvolution(Species.SIMISAGE, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.SIMISAGE, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG)
@ -1411,10 +1411,10 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesFormEvolution(Species.LYCANROC, "", "midnight", 25, null, new SpeciesEvolutionCondition(p => (p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT) && (p.formIndex === 0)), null) new SpeciesFormEvolution(Species.LYCANROC, "", "midnight", 25, null, new SpeciesEvolutionCondition(p => (p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT) && (p.formIndex === 0)), null)
], ],
[Species.STEENEE]: [ [Species.STEENEE]: [
new SpeciesEvolution(Species.TSAREENA, 28, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.STOMP).length > 0), SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.TSAREENA, 28, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.STOMP).length > 0), SpeciesWildEvolutionDelay.LONG)
], ],
[Species.POIPOLE]: [ [Species.POIPOLE]: [
new SpeciesEvolution(Species.NAGANADEL, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.DRAGON_PULSE).length > 0), SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.NAGANADEL, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.DRAGON_PULSE).length > 0), SpeciesWildEvolutionDelay.LONG)
], ],
[Species.ALOLA_SANDSHREW]: [ [Species.ALOLA_SANDSHREW]: [
new SpeciesEvolution(Species.ALOLA_SANDSLASH, 1, EvolutionItem.ICE_STONE, null, SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.ALOLA_SANDSLASH, 1, EvolutionItem.ICE_STONE, null, SpeciesWildEvolutionDelay.LONG)
@ -1428,7 +1428,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.APPLETUN, 1, EvolutionItem.SWEET_APPLE, null, SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.APPLETUN, 1, EvolutionItem.SWEET_APPLE, null, SpeciesWildEvolutionDelay.LONG)
], ],
[Species.CLOBBOPUS]: [ [Species.CLOBBOPUS]: [
new SpeciesEvolution(Species.GRAPPLOCT, 35, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.TAUNT).length > 0), SpeciesWildEvolutionDelay.MEDIUM) new SpeciesEvolution(Species.GRAPPLOCT, 35, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.TAUNT).length > 0), SpeciesWildEvolutionDelay.MEDIUM)
], ],
[Species.SINISTEA]: [ [Species.SINISTEA]: [
new SpeciesFormEvolution(Species.POLTEAGEIST, "phony", "phony", 1, EvolutionItem.CRACKED_POT, null, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(Species.POLTEAGEIST, "phony", "phony", 1, EvolutionItem.CRACKED_POT, null, SpeciesWildEvolutionDelay.LONG),
@ -1462,7 +1462,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.HISUI_ELECTRODE, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.HISUI_ELECTRODE, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG)
], ],
[Species.HISUI_QWILFISH]: [ [Species.HISUI_QWILFISH]: [
new SpeciesEvolution(Species.OVERQWIL, 28, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.BARB_BARRAGE).length > 0), SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.OVERQWIL, 28, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.BARB_BARRAGE).length > 0), SpeciesWildEvolutionDelay.LONG)
], ],
[Species.HISUI_SNEASEL]: [ [Species.HISUI_SNEASEL]: [
new SpeciesEvolution(Species.SNEASLER, 1, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAY /* Razor claw at day*/), SpeciesWildEvolutionDelay.LONG) new SpeciesEvolution(Species.SNEASLER, 1, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAY /* Razor claw at day*/), SpeciesWildEvolutionDelay.LONG)
@ -1485,7 +1485,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesFormEvolution(Species.SINISTCHA, "artisan", "masterpiece", 1, EvolutionItem.MASTERPIECE_TEACUP, null, SpeciesWildEvolutionDelay.LONG) new SpeciesFormEvolution(Species.SINISTCHA, "artisan", "masterpiece", 1, EvolutionItem.MASTERPIECE_TEACUP, null, SpeciesWildEvolutionDelay.LONG)
], ],
[Species.DIPPLIN]: [ [Species.DIPPLIN]: [
new SpeciesEvolution(Species.HYDRAPPLE, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.DRAGON_CHEER).length > 0), SpeciesWildEvolutionDelay.VERY_LONG) new SpeciesEvolution(Species.HYDRAPPLE, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.DRAGON_CHEER).length > 0), SpeciesWildEvolutionDelay.VERY_LONG)
], ],
[Species.KADABRA]: [ [Species.KADABRA]: [
new SpeciesEvolution(Species.ALAKAZAM, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) new SpeciesEvolution(Species.ALAKAZAM, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG)
@ -1501,7 +1501,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
], ],
[Species.ONIX]: [ [Species.ONIX]: [
new SpeciesEvolution(Species.STEELIX, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition( new SpeciesEvolution(Species.STEELIX, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(
p => p.moveset.filter(m => m.getMove().type === Type.STEEL).length > 0), p => p.moveset.filter(m => m?.getMove().type === Type.STEEL).length > 0),
SpeciesWildEvolutionDelay.VERY_LONG) SpeciesWildEvolutionDelay.VERY_LONG)
], ],
[Species.RHYDON]: [ [Species.RHYDON]: [
@ -1512,7 +1512,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
], ],
[Species.SCYTHER]: [ [Species.SCYTHER]: [
new SpeciesEvolution(Species.SCIZOR, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition( new SpeciesEvolution(Species.SCIZOR, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(
p => p.moveset.filter(m => m.getMove().type === Type.STEEL).length > 0), p => p.moveset.filter(m => m?.getMove().type === Type.STEEL).length > 0),
SpeciesWildEvolutionDelay.VERY_LONG), SpeciesWildEvolutionDelay.VERY_LONG),
new SpeciesEvolution(Species.KLEAVOR, 1, EvolutionItem.BLACK_AUGURITE, null, SpeciesWildEvolutionDelay.VERY_LONG) new SpeciesEvolution(Species.KLEAVOR, 1, EvolutionItem.BLACK_AUGURITE, null, SpeciesWildEvolutionDelay.VERY_LONG)
], ],
@ -1566,7 +1566,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.ALOLA_GOLEM, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) new SpeciesEvolution(Species.ALOLA_GOLEM, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG)
], ],
[Species.PRIMEAPE]: [ [Species.PRIMEAPE]: [
new SpeciesEvolution(Species.ANNIHILAPE, 35, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m.moveId === Moves.RAGE_FIST).length > 0), SpeciesWildEvolutionDelay.VERY_LONG) new SpeciesEvolution(Species.ANNIHILAPE, 35, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.RAGE_FIST).length > 0), SpeciesWildEvolutionDelay.VERY_LONG)
], ],
[Species.GOLBAT]: [ [Species.GOLBAT]: [
new SpeciesEvolution(Species.CROBAT, 1, null, new SpeciesFriendshipEvolutionCondition(110), SpeciesWildEvolutionDelay.VERY_LONG) new SpeciesEvolution(Species.CROBAT, 1, null, new SpeciesFriendshipEvolutionCondition(110), SpeciesWildEvolutionDelay.VERY_LONG)

View File

@ -181,7 +181,7 @@ export class SpeciesFormChange {
return true; return true;
} }
findTrigger(triggerType: Constructor<SpeciesFormChangeTrigger>): SpeciesFormChangeTrigger { findTrigger(triggerType: Constructor<SpeciesFormChangeTrigger>): SpeciesFormChangeTrigger | null {
if (!this.trigger.hasTriggerType(triggerType)) { if (!this.trigger.hasTriggerType(triggerType)) {
return null; return null;
} }
@ -189,7 +189,7 @@ export class SpeciesFormChange {
const trigger = this.trigger; const trigger = this.trigger;
if (trigger instanceof SpeciesFormChangeCompoundTrigger) { if (trigger instanceof SpeciesFormChangeCompoundTrigger) {
return trigger.triggers.find(t => t.hasTriggerType(triggerType)); return trigger.triggers.find(t => t.hasTriggerType(triggerType))!; // TODO: is this bang correct?
} }
return trigger; return trigger;
@ -198,11 +198,11 @@ export class SpeciesFormChange {
export class SpeciesFormChangeCondition { export class SpeciesFormChangeCondition {
public predicate: SpeciesFormChangeConditionPredicate; public predicate: SpeciesFormChangeConditionPredicate;
public enforceFunc: SpeciesFormChangeConditionEnforceFunc; public enforceFunc: SpeciesFormChangeConditionEnforceFunc | null;
constructor(predicate: SpeciesFormChangeConditionPredicate, enforceFunc?: SpeciesFormChangeConditionEnforceFunc) { constructor(predicate: SpeciesFormChangeConditionPredicate, enforceFunc?: SpeciesFormChangeConditionEnforceFunc) {
this.predicate = predicate; this.predicate = predicate;
this.enforceFunc = enforceFunc; this.enforceFunc = enforceFunc!; // TODO: is this bang correct?
} }
} }
@ -314,7 +314,7 @@ export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigge
} }
canChange(pokemon: Pokemon): boolean { canChange(pokemon: Pokemon): boolean {
return (!!pokemon.moveset.filter(m => m.moveId === this.move).length) === this.known; return (!!pokemon.moveset.filter(m => m?.moveId === this.move).length) === this.known;
} }
} }
@ -332,7 +332,7 @@ export abstract class SpeciesFormChangeMoveTrigger extends SpeciesFormChangeTrig
export class SpeciesFormChangePreMoveTrigger extends SpeciesFormChangeMoveTrigger { export class SpeciesFormChangePreMoveTrigger extends SpeciesFormChangeMoveTrigger {
canChange(pokemon: Pokemon): boolean { canChange(pokemon: Pokemon): boolean {
const command = pokemon.scene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; const command = pokemon.scene.currentBattle.turnCommands[pokemon.getBattlerIndex()];
return command?.move && this.movePredicate(command.move.move) === this.used; return !!command?.move && this.movePredicate(command.move.move) === this.used;
} }
} }

View File

@ -28,21 +28,21 @@ export enum Region {
PALDEA PALDEA
} }
export function getPokemonSpecies(species: Species): PokemonSpecies { export function getPokemonSpecies(species: Species | Species[]): PokemonSpecies {
// If a special pool (named trainers) is used here it CAN happen that they have a array as species (which means choose one of those two). So we catch that with this code block // If a special pool (named trainers) is used here it CAN happen that they have a array as species (which means choose one of those two). So we catch that with this code block
if (Array.isArray(species)) { if (Array.isArray(species)) {
// Pick a random species from the list // Pick a random species from the list
species = species[Math.floor(Math.random() * species.length)]; species = species[Math.floor(Math.random() * species.length)];
} }
if (species >= 2000) { if (species >= 2000) {
return allSpecies.find(s => s.speciesId === species); return allSpecies.find(s => s.speciesId === species)!; // TODO: is this bang correct?
} }
return allSpecies[species - 1]; return allSpecies[species - 1];
} }
export function getPokemonSpeciesForm(species: Species, formIndex: integer): PokemonSpeciesForm { export function getPokemonSpeciesForm(species: Species, formIndex: integer): PokemonSpeciesForm {
const retSpecies: PokemonSpecies = species >= 2000 const retSpecies: PokemonSpecies = species >= 2000
? allSpecies.find(s => s.speciesId === species) ? allSpecies.find(s => s.speciesId === species)! // TODO: is the bang correct?
: allSpecies[species - 1]; : allSpecies[species - 1];
if (formIndex < retSpecies.forms?.length) { if (formIndex < retSpecies.forms?.length) {
return retSpecies.forms[formIndex]; return retSpecies.forms[formIndex];
@ -97,7 +97,7 @@ export function getFusedSpeciesName(speciesAName: string, speciesBName: string):
fragB = fragB.slice(1); fragB = fragB.slice(1);
} else { } else {
const newCharMatch = new RegExp(`[^${lastCharA}]`).exec(fragB); const newCharMatch = new RegExp(`[^${lastCharA}]`).exec(fragB);
if (newCharMatch?.index > 0) { if (newCharMatch?.index !== undefined && newCharMatch.index > 0) {
fragB = fragB.slice(newCharMatch.index); fragB = fragB.slice(newCharMatch.index);
} }
} }
@ -125,7 +125,7 @@ export abstract class PokemonSpeciesForm {
public formIndex: integer; public formIndex: integer;
public generation: integer; public generation: integer;
public type1: Type; public type1: Type;
public type2: Type; public type2: Type | null;
public height: number; public height: number;
public weight: number; public weight: number;
public ability1: Abilities; public ability1: Abilities;
@ -139,7 +139,7 @@ export abstract class PokemonSpeciesForm {
public genderDiffs: boolean; public genderDiffs: boolean;
public isStarterSelectable: boolean; public isStarterSelectable: boolean;
constructor(type1: Type, type2: Type, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities, constructor(type1: Type, type2: Type | null, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities,
baseTotal: integer, baseHp: integer, baseAtk: integer, baseDef: integer, baseSpatk: integer, baseSpdef: integer, baseSpd: integer, baseTotal: integer, baseHp: integer, baseAtk: integer, baseDef: integer, baseSpatk: integer, baseSpdef: integer, baseSpd: integer,
catchRate: integer, baseFriendship: integer, baseExp: integer, genderDiffs: boolean, isStarterSelectable: boolean) { catchRate: integer, baseFriendship: integer, baseExp: integer, genderDiffs: boolean, isStarterSelectable: boolean) {
this.type1 = type1; this.type1 = type1;
@ -267,7 +267,7 @@ export abstract class PokemonSpeciesForm {
return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`; return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`;
} }
getSpriteId(female: boolean, formIndex?: integer, shiny?: boolean, variant?: integer, back?: boolean): string { getSpriteId(female: boolean, formIndex?: integer, shiny?: boolean, variant: integer = 0, back?: boolean): string {
if (formIndex === undefined || this instanceof PokemonForm) { if (formIndex === undefined || this instanceof PokemonForm) {
formIndex = this.formIndex; formIndex = this.formIndex;
} }
@ -281,7 +281,7 @@ export abstract class PokemonSpeciesForm {
`${back ? "back__" : ""}${baseSpriteKey}`.split("__").map(p => config ? config = config[p] : null); `${back ? "back__" : ""}${baseSpriteKey}`.split("__").map(p => config ? config = config[p] : null);
const variantSet = config as VariantSet; const variantSet = config as VariantSet;
return `${back ? "back__" : ""}${shiny && (!variantSet || (!variant && !variantSet[variant || 0])) ? "shiny__" : ""}${baseSpriteKey}${shiny && variantSet && variantSet[variant || 0] === 2 ? `_${variant + 1}` : ""}`; return `${back ? "back__" : ""}${shiny && (!variantSet || (!variant && !variantSet[variant || 0])) ? "shiny__" : ""}${baseSpriteKey}${shiny && variantSet && variantSet[variant] === 2 ? `_${variant + 1}` : ""}`;
} }
getSpriteKey(female: boolean, formIndex?: integer, shiny?: boolean, variant?: integer): string { getSpriteKey(female: boolean, formIndex?: integer, shiny?: boolean, variant?: integer): string {
@ -297,10 +297,10 @@ export abstract class PokemonSpeciesForm {
* @returns species id if no additional forms, index with formkey if a pokemon with a form * @returns species id if no additional forms, index with formkey if a pokemon with a form
*/ */
getVariantDataIndex(formIndex?: integer) { getVariantDataIndex(formIndex?: integer) {
let formkey = null; let formkey: string | null = null;
let variantDataIndex: integer|string = this.speciesId; let variantDataIndex: integer | string = this.speciesId;
const species = getPokemonSpecies(this.speciesId); const species = getPokemonSpecies(this.speciesId);
if (species.forms.length > 0) { if (species.forms.length > 0 && formIndex !== undefined) {
formkey = species.forms[formIndex]?.formSpriteKey; formkey = species.forms[formIndex]?.formSpriteKey;
if (formkey) { if (formkey) {
variantDataIndex = `${this.speciesId}-${formkey}`; variantDataIndex = `${this.speciesId}-${formkey}`;
@ -311,7 +311,7 @@ export abstract class PokemonSpeciesForm {
getIconAtlasKey(formIndex?: integer, shiny?: boolean, variant?: integer): string { getIconAtlasKey(formIndex?: integer, shiny?: boolean, variant?: integer): string {
const variantDataIndex = this.getVariantDataIndex(formIndex); const variantDataIndex = this.getVariantDataIndex(formIndex);
const isVariant = shiny && variantData[variantDataIndex] && variantData[variantDataIndex][variant]; const isVariant = shiny && variantData[variantDataIndex] && (variant !== undefined && variantData[variantDataIndex][variant]);
return `pokemon_icons_${this.generation}${isVariant ? "v" : ""}`; return `pokemon_icons_${this.generation}${isVariant ? "v" : ""}`;
} }
@ -324,7 +324,7 @@ export abstract class PokemonSpeciesForm {
let ret = this.speciesId.toString(); let ret = this.speciesId.toString();
const isVariant = shiny && variantData[variantDataIndex] && variantData[variantDataIndex][variant]; const isVariant = shiny && variantData[variantDataIndex] && (variant !== undefined && variantData[variantDataIndex][variant]);
if (shiny && !isVariant) { if (shiny && !isVariant) {
ret += "s"; ret += "s";
@ -382,7 +382,7 @@ export abstract class PokemonSpeciesForm {
let ret = speciesId.toString(); let ret = speciesId.toString();
const forms = getPokemonSpecies(speciesId).forms; const forms = getPokemonSpecies(speciesId).forms;
if (forms.length) { if (forms.length) {
if (formIndex >= forms.length) { if (formIndex !== undefined && formIndex >= forms.length) {
console.warn(`Attempted accessing form with index ${formIndex} of species ${getPokemonSpecies(speciesId).getName()} with only ${forms.length || 0} forms`); console.warn(`Attempted accessing form with index ${formIndex} of species ${getPokemonSpecies(speciesId).getName()} with only ${forms.length || 0} forms`);
formIndex = Math.min(formIndex, forms.length - 1); formIndex = Math.min(formIndex, forms.length - 1);
} }
@ -478,7 +478,7 @@ export abstract class PokemonSpeciesForm {
let config = variantData; let config = variantData;
spritePath.split("/").map(p => config ? config = config[p] : null); spritePath.split("/").map(p => config ? config = config[p] : null);
const variantSet = config as VariantSet; const variantSet = config as VariantSet;
if (variantSet && variantSet[variant] === 1) { if (variantSet && (variant !== undefined && variantSet[variant] === 1)) {
const populateVariantColors = (key: string): Promise<void> => { const populateVariantColors = (key: string): Promise<void> => {
return new Promise(resolve => { return new Promise(resolve => {
if (variantColorCache.hasOwnProperty(key)) { if (variantColorCache.hasOwnProperty(key)) {
@ -507,7 +507,7 @@ export abstract class PokemonSpeciesForm {
cry(scene: BattleScene, soundConfig?: Phaser.Types.Sound.SoundConfig, ignorePlay?: boolean): AnySound { cry(scene: BattleScene, soundConfig?: Phaser.Types.Sound.SoundConfig, ignorePlay?: boolean): AnySound {
const cryKey = this.getCryKey(this.formIndex); const cryKey = this.getCryKey(this.formIndex);
let cry = scene.sound.get(cryKey) as AnySound; let cry: AnySound | null = scene.sound.get(cryKey) as AnySound;
if (cry?.pendingRemove) { if (cry?.pendingRemove) {
cry = null; cry = null;
} }
@ -532,10 +532,12 @@ export abstract class PokemonSpeciesForm {
const frame = sourceFrame; const frame = sourceFrame;
canvas.width = frame.width; canvas.width = frame.width;
canvas.height = frame.height; canvas.height = frame.height;
context.drawImage(sourceImage, frame.cutX, frame.cutY, frame.width, frame.height, 0, 0, frame.width, frame.height); context?.drawImage(sourceImage, frame.cutX, frame.cutY, frame.width, frame.height, 0, 0, frame.width, frame.height);
const imageData = context.getImageData(frame.cutX, frame.cutY, frame.width, frame.height); const imageData = context?.getImageData(frame.cutX, frame.cutY, frame.width, frame.height);
const pixelData = imageData.data; const pixelData = imageData?.data;
const pixelColors: number[] = [];
if (pixelData?.length !== undefined) {
for (let i = 0; i < pixelData.length; i += 4) { for (let i = 0; i < pixelData.length; i += 4) {
if (pixelData[i + 3]) { if (pixelData[i + 3]) {
const pixel = pixelData.slice(i, i + 4); const pixel = pixelData.slice(i, i + 4);
@ -546,7 +548,6 @@ export abstract class PokemonSpeciesForm {
} }
} }
const pixelColors = [];
for (let i = 0; i < pixelData.length; i += 4) { for (let i = 0; i < pixelData.length; i += 4) {
const total = pixelData.slice(i, i + 3).reduce((total: integer, value: integer) => total + value, 0); const total = pixelData.slice(i, i + 3).reduce((total: integer, value: integer) => total + value, 0);
if (!total) { if (!total) {
@ -554,8 +555,9 @@ export abstract class PokemonSpeciesForm {
} }
pixelColors.push(argbFromRgba({ r: pixelData[i], g: pixelData[i + 1], b: pixelData[i + 2], a: pixelData[i + 3] })); pixelColors.push(argbFromRgba({ r: pixelData[i], g: pixelData[i + 1], b: pixelData[i + 2], a: pixelData[i + 3] }));
} }
}
let paletteColors: Map<number, number>; let paletteColors: Map<number, number> = new Map();
const originalRandom = Math.random; const originalRandom = Math.random;
Math.random = () => Phaser.Math.RND.realInRange(0, 1); Math.random = () => Phaser.Math.RND.realInRange(0, 1);
@ -577,15 +579,15 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
public mythical: boolean; public mythical: boolean;
public species: string; public species: string;
public growthRate: GrowthRate; public growthRate: GrowthRate;
public malePercent: number; public malePercent: number | null;
public genderDiffs: boolean; public genderDiffs: boolean;
public canChangeForm: boolean; public canChangeForm: boolean;
public forms: PokemonForm[]; public forms: PokemonForm[];
constructor(id: Species, generation: integer, subLegendary: boolean, legendary: boolean, mythical: boolean, species: string, constructor(id: Species, generation: integer, subLegendary: boolean, legendary: boolean, mythical: boolean, species: string,
type1: Type, type2: Type, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities, type1: Type, type2: Type | null, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities,
baseTotal: integer, baseHp: integer, baseAtk: integer, baseDef: integer, baseSpatk: integer, baseSpdef: integer, baseSpd: integer, baseTotal: integer, baseHp: integer, baseAtk: integer, baseDef: integer, baseSpatk: integer, baseSpdef: integer, baseSpd: integer,
catchRate: integer, baseFriendship: integer, baseExp: integer, growthRate: GrowthRate, malePercent: number, catchRate: integer, baseFriendship: integer, baseExp: integer, growthRate: GrowthRate, malePercent: number | null,
genderDiffs: boolean, canChangeForm?: boolean, ...forms: PokemonForm[]) { genderDiffs: boolean, canChangeForm?: boolean, ...forms: PokemonForm[]) {
super(type1, type2, height, weight, ability1, ability2, abilityHidden, baseTotal, baseHp, baseAtk, baseDef, baseSpatk, baseSpdef, baseSpd, super(type1, type2, height, weight, ability1, ability2, abilityHidden, baseTotal, baseHp, baseAtk, baseDef, baseSpatk, baseSpdef, baseSpd,
catchRate, baseFriendship, baseExp, genderDiffs, false); catchRate, baseFriendship, baseExp, genderDiffs, false);
@ -614,7 +616,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
getName(formIndex?: integer): string { getName(formIndex?: integer): string {
if (formIndex !== undefined && this.forms.length) { if (formIndex !== undefined && this.forms.length) {
const form = this.forms[formIndex]; const form = this.forms[formIndex];
let key: string; let key: string | null;
switch (form.formKey) { switch (form.formKey) {
case SpeciesFormKey.MEGA: case SpeciesFormKey.MEGA:
case SpeciesFormKey.PRIMAL: case SpeciesFormKey.PRIMAL:
@ -626,6 +628,8 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
default: default:
if (form.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1) { if (form.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1) {
key = "gigantamax"; key = "gigantamax";
} else {
key = null;
} }
} }
@ -713,11 +717,11 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
evolutionChance = Math.min(minChance + easeInFunc(Math.min(level - ev.level, maxLevelDiff) / maxLevelDiff) * (1 - minChance), 1); evolutionChance = Math.min(minChance + easeInFunc(Math.min(level - ev.level, maxLevelDiff) / maxLevelDiff) * (1 - minChance), 1);
} }
} else { } else {
const preferredMinLevel = Math.max((ev.level - 1) + ev.wildDelay * this.getStrengthLevelDiff(strength), 1); const preferredMinLevel = Math.max((ev.level - 1) + (ev.wildDelay!) * this.getStrengthLevelDiff(strength), 1); // TODO: is the bang correct?
let evolutionLevel = Math.max(ev.level > 1 ? ev.level : Math.floor(preferredMinLevel / 2), 1); let evolutionLevel = Math.max(ev.level > 1 ? ev.level : Math.floor(preferredMinLevel / 2), 1);
if (ev.level <= 1 && pokemonPrevolutions.hasOwnProperty(this.speciesId)) { if (ev.level <= 1 && pokemonPrevolutions.hasOwnProperty(this.speciesId)) {
const prevolutionLevel = pokemonEvolutions[pokemonPrevolutions[this.speciesId]].find(ev => ev.speciesId === this.speciesId).level; const prevolutionLevel = pokemonEvolutions[pokemonPrevolutions[this.speciesId]].find(ev => ev.speciesId === this.speciesId)!.level; // TODO: is the bang correct?
if (prevolutionLevel > 1) { if (prevolutionLevel > 1) {
evolutionLevel = prevolutionLevel; evolutionLevel = prevolutionLevel;
} }
@ -750,15 +754,15 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
for (const weight of evolutionPool.keys()) { for (const weight of evolutionPool.keys()) {
if (randValue < weight) { if (randValue < weight) {
return getPokemonSpecies(evolutionPool.get(weight)).getSpeciesForLevel(level, true, forTrainer, strength); return getPokemonSpecies(evolutionPool.get(weight)!).getSpeciesForLevel(level, true, forTrainer, strength); // TODO: is the bang correct?
} }
} }
return this.speciesId; return this.speciesId;
} }
getEvolutionLevels() { getEvolutionLevels(): [Species, integer][] {
const evolutionLevels = []; const evolutionLevels: [Species, integer][] = [];
//console.log(Species[this.speciesId], pokemonEvolutions[this.speciesId]) //console.log(Species[this.speciesId], pokemonEvolutions[this.speciesId])
@ -778,8 +782,8 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
return evolutionLevels; return evolutionLevels;
} }
getPrevolutionLevels() { getPrevolutionLevels(): [Species, integer][] {
const prevolutionLevels = []; const prevolutionLevels: [Species, integer][] = [];
const allEvolvingPokemon = Object.keys(pokemonEvolutions); const allEvolvingPokemon = Object.keys(pokemonEvolutions);
for (const p of allEvolvingPokemon) { for (const p of allEvolvingPokemon) {
@ -801,18 +805,18 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
// This could definitely be written better and more accurate to the getSpeciesForLevel logic, but it is only for generating movesets for evolved Pokemon // This could definitely be written better and more accurate to the getSpeciesForLevel logic, but it is only for generating movesets for evolved Pokemon
getSimulatedEvolutionChain(currentLevel: integer, forTrainer: boolean = false, isBoss: boolean = false, player: boolean = false): [Species, integer][] { getSimulatedEvolutionChain(currentLevel: integer, forTrainer: boolean = false, isBoss: boolean = false, player: boolean = false): [Species, integer][] {
const ret = []; const ret: [Species, integer][] = [];
if (pokemonPrevolutions.hasOwnProperty(this.speciesId)) { if (pokemonPrevolutions.hasOwnProperty(this.speciesId)) {
const prevolutionLevels = this.getPrevolutionLevels().reverse(); const prevolutionLevels = this.getPrevolutionLevels().reverse();
const levelDiff = player ? 0 : forTrainer || isBoss ? forTrainer && isBoss ? 2.5 : 5 : 10; const levelDiff = player ? 0 : forTrainer || isBoss ? forTrainer && isBoss ? 2.5 : 5 : 10;
ret.push([ prevolutionLevels[0][0], 1 ]); ret.push([ prevolutionLevels[0][0], 1 ]);
for (let l = 1; l < prevolutionLevels.length; l++) { for (let l = 1; l < prevolutionLevels.length; l++) {
const evolution = pokemonEvolutions[prevolutionLevels[l - 1][0]].find(e => e.speciesId === prevolutionLevels[l][0]); const evolution = pokemonEvolutions[prevolutionLevels[l - 1][0]].find(e => e.speciesId === prevolutionLevels[l][0]);
ret.push([ prevolutionLevels[l][0], Math.min(Math.max(evolution.level + Math.round(Utils.randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max(evolution.wildDelay, 0.5) * 5) - 1, 2, evolution.level), currentLevel - 1) ]); ret.push([ prevolutionLevels[l][0], Math.min(Math.max((evolution?.level!) + Math.round(Utils.randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max((evolution?.wildDelay!), 0.5) * 5) - 1, 2, (evolution?.level!)), currentLevel - 1) ]); // TODO: are those bangs correct?
} }
const lastPrevolutionLevel = ret[prevolutionLevels.length - 1][1]; const lastPrevolutionLevel = ret[prevolutionLevels.length - 1][1];
const evolution = pokemonEvolutions[prevolutionLevels[prevolutionLevels.length - 1][0]].find(e => e.speciesId === this.speciesId); const evolution = pokemonEvolutions[prevolutionLevels[prevolutionLevels.length - 1][0]].find(e => e.speciesId === this.speciesId);
ret.push([ this.speciesId, Math.min(Math.max(lastPrevolutionLevel + Math.round(Utils.randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max(evolution.wildDelay, 0.5) * 5), lastPrevolutionLevel + 1, evolution.level), currentLevel) ]); ret.push([ this.speciesId, Math.min(Math.max(lastPrevolutionLevel + Math.round(Utils.randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max((evolution?.wildDelay!), 0.5) * 5), lastPrevolutionLevel + 1, (evolution?.level!)), currentLevel) ]); // TODO: are those bangs correct?
} else { } else {
ret.push([ this.speciesId, 1 ]); ret.push([ this.speciesId, 1 ]);
} }
@ -853,7 +857,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
} }
getFormSpriteKey(formIndex?: integer) { getFormSpriteKey(formIndex?: integer) {
if (this.forms.length && formIndex >= this.forms.length) { if (this.forms.length && (formIndex !== undefined && formIndex >= this.forms.length)) {
console.warn(`Attempted accessing form with index ${formIndex} of species ${this.getName()} with only ${this.forms.length || 0} forms`); console.warn(`Attempted accessing form with index ${formIndex} of species ${this.getName()} with only ${this.forms.length || 0} forms`);
formIndex = Math.min(formIndex, this.forms.length - 1); formIndex = Math.min(formIndex, this.forms.length - 1);
} }
@ -866,14 +870,14 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
export class PokemonForm extends PokemonSpeciesForm { export class PokemonForm extends PokemonSpeciesForm {
public formName: string; public formName: string;
public formKey: string; public formKey: string;
public formSpriteKey: string; public formSpriteKey: string | null;
// This is a collection of form keys that have in-run form changes, but should still be separately selectable from the start screen // This is a collection of form keys that have in-run form changes, but should still be separately selectable from the start screen
private starterSelectableKeys: string[] = ["10", "50", "10-pc", "50-pc", "red", "orange", "yellow", "green", "blue", "indigo", "violet"]; private starterSelectableKeys: string[] = ["10", "50", "10-pc", "50-pc", "red", "orange", "yellow", "green", "blue", "indigo", "violet"];
constructor(formName: string, formKey: string, type1: Type, type2: Type, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities, constructor(formName: string, formKey: string, type1: Type, type2: Type | null, height: number, weight: number, ability1: Abilities, ability2: Abilities, abilityHidden: Abilities,
baseTotal: integer, baseHp: integer, baseAtk: integer, baseDef: integer, baseSpatk: integer, baseSpdef: integer, baseSpd: integer, baseTotal: integer, baseHp: integer, baseAtk: integer, baseDef: integer, baseSpatk: integer, baseSpdef: integer, baseSpd: integer,
catchRate: integer, baseFriendship: integer, baseExp: integer, genderDiffs?: boolean, formSpriteKey?: string, isStarterSelectable?: boolean, ) { catchRate: integer, baseFriendship: integer, baseExp: integer, genderDiffs?: boolean, formSpriteKey?: string | null, isStarterSelectable?: boolean, ) {
super(type1, type2, height, weight, ability1, ability2, abilityHidden, baseTotal, baseHp, baseAtk, baseDef, baseSpatk, baseSpdef, baseSpd, super(type1, type2, height, weight, ability1, ability2, abilityHidden, baseTotal, baseHp, baseAtk, baseDef, baseSpatk, baseSpdef, baseSpd,
catchRate, baseFriendship, baseExp, !!genderDiffs, (!!isStarterSelectable || !formKey)); catchRate, baseFriendship, baseExp, !!genderDiffs, (!!isStarterSelectable || !formKey));
this.formName = formName; this.formName = formName;

View File

@ -7,12 +7,12 @@ export { StatusEffect };
export class Status { export class Status {
public effect: StatusEffect; public effect: StatusEffect;
public turnCount: integer; public turnCount: integer;
public cureTurn: integer; public cureTurn: integer | null;
constructor(effect: StatusEffect, turnCount: integer = 0, cureTurn?: integer) { constructor(effect: StatusEffect, turnCount: integer = 0, cureTurn?: integer) {
this.effect = effect; this.effect = effect;
this.turnCount = turnCount === undefined ? 0 : turnCount; this.turnCount = turnCount === undefined ? 0 : turnCount;
this.cureTurn = cureTurn; this.cureTurn = cureTurn!; // TODO: is this bang correct?
} }
incrementTurn(): void { incrementTurn(): void {
@ -24,7 +24,7 @@ export class Status {
} }
} }
function getStatusEffectMessageKey(statusEffect: StatusEffect): string { function getStatusEffectMessageKey(statusEffect: StatusEffect | undefined): string {
switch (statusEffect) { switch (statusEffect) {
case StatusEffect.POISON: case StatusEffect.POISON:
return "statusEffect:poison"; return "statusEffect:poison";
@ -43,7 +43,7 @@ function getStatusEffectMessageKey(statusEffect: StatusEffect): string {
} }
} }
export function getStatusEffectObtainText(statusEffect: StatusEffect, pokemonNameWithAffix: string, sourceText?: string): string { export function getStatusEffectObtainText(statusEffect: StatusEffect | undefined, pokemonNameWithAffix: string, sourceText?: string): string {
if (!sourceText) { if (!sourceText) {
const i18nKey = `${getStatusEffectMessageKey(statusEffect)}.obtain`as ParseKeys; const i18nKey = `${getStatusEffectMessageKey(statusEffect)}.obtain`as ParseKeys;
return i18next.t(i18nKey, { pokemonNameWithAffix: pokemonNameWithAffix }); return i18next.t(i18nKey, { pokemonNameWithAffix: pokemonNameWithAffix });

View File

@ -288,7 +288,7 @@ export class TrainerConfig {
* @param trainerTypeToDeriveFrom - The trainer type to derive from. (If null, the this.trainerType property will be used.) * @param trainerTypeToDeriveFrom - The trainer type to derive from. (If null, the this.trainerType property will be used.)
* @returns {TrainerType} - The derived trainer type. * @returns {TrainerType} - The derived trainer type.
*/ */
getDerivedType(trainerTypeToDeriveFrom: TrainerType = null): TrainerType { getDerivedType(trainerTypeToDeriveFrom: TrainerType | null = null): TrainerType {
let trainerType = trainerTypeToDeriveFrom ? trainerTypeToDeriveFrom : this.trainerType; let trainerType = trainerTypeToDeriveFrom ? trainerTypeToDeriveFrom : this.trainerType;
switch (trainerType) { switch (trainerType) {
case TrainerType.RIVAL_2: case TrainerType.RIVAL_2:
@ -361,7 +361,7 @@ export class TrainerConfig {
this.nameFemale = i18next.t("trainerNames:rival_female"); this.nameFemale = i18next.t("trainerNames:rival_female");
} else { } else {
// Otherwise, assign the provided female name. // Otherwise, assign the provided female name.
this.nameFemale = nameFemale; this.nameFemale = nameFemale!; // TODO: is this bang correct?
} }
// Indicate that this trainer configuration includes genders. // Indicate that this trainer configuration includes genders.
@ -587,6 +587,9 @@ export class TrainerConfig {
}; };
} }
} }
console.warn(`Evil team admin for ${team} not found. Returning empty species pools.`);
return [];
} }
/** /**
@ -715,7 +718,7 @@ export class TrainerConfig {
this.setVictoryBgm("victory_gym"); this.setVictoryBgm("victory_gym");
this.setGenModifiersFunc(party => { this.setGenModifiersFunc(party => {
const waveIndex = party[0].scene.currentBattle.waveIndex; const waveIndex = party[0].scene.currentBattle.waveIndex;
return getRandomTeraModifiers(party, waveIndex >= 100 ? 1 : 0, specialtyTypes.length ? specialtyTypes : null); return getRandomTeraModifiers(party, waveIndex >= 100 ? 1 : 0, specialtyTypes.length ? specialtyTypes : undefined);
}); });
return this; return this;
@ -749,7 +752,7 @@ export class TrainerConfig {
// Set species filter and specialty types if provided, otherwise filter by base total. // Set species filter and specialty types if provided, otherwise filter by base total.
if (specialtyTypes.length) { if (specialtyTypes.length) {
this.setSpeciesFilter(p => specialtyTypes.find(t => p.isOfType(t)) && p.baseTotal >= 450); this.setSpeciesFilter(p => specialtyTypes.some(t => p.isOfType(t)) && p.baseTotal >= 450);
this.setSpecialtyTypes(...specialtyTypes); this.setSpecialtyTypes(...specialtyTypes);
} else { } else {
this.setSpeciesFilter(p => p.baseTotal >= 450); this.setSpeciesFilter(p => p.baseTotal >= 450);
@ -772,7 +775,7 @@ export class TrainerConfig {
this.setHasVoucher(true); this.setHasVoucher(true);
this.setBattleBgm("battle_unova_elite"); this.setBattleBgm("battle_unova_elite");
this.setVictoryBgm("victory_gym"); this.setVictoryBgm("victory_gym");
this.setGenModifiersFunc(party => getRandomTeraModifiers(party, 2, specialtyTypes.length ? specialtyTypes : null)); this.setGenModifiersFunc(party => getRandomTeraModifiers(party, 2, specialtyTypes.length ? specialtyTypes : undefined));
return this; return this;
} }
@ -897,7 +900,7 @@ export class TrainerConfig {
start: 1, start: 1,
end: 128 end: 128
}) })
: null; : "";
console.warn = originalWarn; console.warn = originalWarn;
if (!(scene.anims.exists(trainerKey))) { if (!(scene.anims.exists(trainerKey))) {
scene.anims.create({ scene.anims.create({
@ -983,7 +986,7 @@ function getRandomTeraModifiers(party: EnemyPokemon[], count: integer, types?: T
for (let t = 0; t < Math.min(count, party.length); t++) { for (let t = 0; t < Math.min(count, party.length); t++) {
const randomIndex = Utils.randSeedItem(partyMemberIndexes); const randomIndex = Utils.randSeedItem(partyMemberIndexes);
partyMemberIndexes.splice(partyMemberIndexes.indexOf(randomIndex), 1); partyMemberIndexes.splice(partyMemberIndexes.indexOf(randomIndex), 1);
ret.push(modifierTypes.TERA_SHARD().generateType(null, [Utils.randSeedItem(types ? types : party[randomIndex].getTypes())]).withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(party[randomIndex]) as PersistentModifier); ret.push(modifierTypes.TERA_SHARD().generateType([], [Utils.randSeedItem(types ? types : party[randomIndex].getTypes())])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(party[randomIndex]) as PersistentModifier); // TODO: is the bang correct?
} }
return ret; return ret;
} }
@ -1594,7 +1597,7 @@ export const trainerConfigs: TrainerConfigs = {
.setSpeciesFilter(species => species.baseTotal >= 540) .setSpeciesFilter(species => species.baseTotal >= 540)
.setGenModifiersFunc(party => { .setGenModifiersFunc(party => {
const starter = party[0]; const starter = party[0];
return [modifierTypes.TERA_SHARD().generateType(null, [starter.species.type1]).withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier]; return [modifierTypes.TERA_SHARD().generateType([], [starter.species.type1])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier]; // TODO: is the bang correct?
}), }),
[TrainerType.RIVAL_5]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_5) [TrainerType.RIVAL_5]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_5)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL], TrainerSlot.TRAINER, true, .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL], TrainerSlot.TRAINER, true,
@ -1610,7 +1613,7 @@ export const trainerConfigs: TrainerConfigs = {
})) }))
.setGenModifiersFunc(party => { .setGenModifiersFunc(party => {
const starter = party[0]; const starter = party[0];
return [modifierTypes.TERA_SHARD().generateType(null, [starter.species.type1]).withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier]; return [modifierTypes.TERA_SHARD().generateType([], [starter.species.type1])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier]; //TODO: is the bang correct?
}), }),
[TrainerType.RIVAL_6]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(3).setEncounterBgm("final").setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_6) [TrainerType.RIVAL_6]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(3).setEncounterBgm("final").setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_6)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL], TrainerSlot.TRAINER, true, .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL], TrainerSlot.TRAINER, true,
@ -1636,7 +1639,7 @@ export const trainerConfigs: TrainerConfigs = {
})) }))
.setGenModifiersFunc(party => { .setGenModifiersFunc(party => {
const starter = party[0]; const starter = party[0];
return [modifierTypes.TERA_SHARD().generateType(null, [starter.species.type1]).withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier]; return [modifierTypes.TERA_SHARD().generateType([], [starter.species.type1])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier]; // TODO: is the bang correct?
}), }),
[TrainerType.ROCKET_BOSS_GIOVANNI_1]: new TrainerConfig(t = TrainerType.ROCKET_BOSS_GIOVANNI_1).setName("Giovanni").initForEvilTeamLeader("Rocket Boss", []).setMixedBattleBgm("battle_rocket_boss").setVictoryBgm("victory_team_plasma") [TrainerType.ROCKET_BOSS_GIOVANNI_1]: new TrainerConfig(t = TrainerType.ROCKET_BOSS_GIOVANNI_1).setName("Giovanni").initForEvilTeamLeader("Rocket Boss", []).setMixedBattleBgm("battle_rocket_boss").setVictoryBgm("victory_team_plasma")

View File

@ -3,7 +3,7 @@ import * as Utils from "../utils";
class TrainerNameConfig { class TrainerNameConfig {
public urls: string[]; public urls: string[];
public femaleUrls: string[]; public femaleUrls: string[] | null;
constructor(type: TrainerType, ...urls: string[]) { constructor(type: TrainerType, ...urls: string[]) {
this.urls = urls.length ? urls : [ Utils.toReadableString(TrainerType[type]).replace(/ /g, "_") ]; this.urls = urls.length ? urls : [ Utils.toReadableString(TrainerType[type]).replace(/ /g, "_") ];
@ -136,8 +136,11 @@ function fetchAndPopulateTrainerNames(url: string, parser: DOMParser, trainerNam
.then(html => { .then(html => {
console.log(url); console.log(url);
const htmlDoc = parser.parseFromString(html, "text/html"); const htmlDoc = parser.parseFromString(html, "text/html");
const trainerListHeader = htmlDoc.querySelector("#Trainer_list").parentElement; const trainerListHeader = htmlDoc.querySelector("#Trainer_list")?.parentElement;
const elements = [...trainerListHeader.parentElement.childNodes]; if (!trainerListHeader) {
return [];
}
const elements = [...(trainerListHeader?.parentElement?.childNodes ?? [])];
const startChildIndex = elements.indexOf(trainerListHeader); const startChildIndex = elements.indexOf(trainerListHeader);
const endChildIndex = elements.findIndex(h => h.nodeName === "H2" && elements.indexOf(h) > startChildIndex); const endChildIndex = elements.findIndex(h => h.nodeName === "H2" && elements.indexOf(h) > startChildIndex);
const tables = elements.filter(t => { const tables = elements.filter(t => {
@ -152,6 +155,9 @@ function fetchAndPopulateTrainerNames(url: string, parser: DOMParser, trainerNam
const trainerRows = [...table.querySelectorAll("tr:not(:first-child)")].filter(r => r.children.length === 9); const trainerRows = [...table.querySelectorAll("tr:not(:first-child)")].filter(r => r.children.length === 9);
for (const row of trainerRows) { for (const row of trainerRows) {
const nameCell = row.firstElementChild; const nameCell = row.firstElementChild;
if (!nameCell) {
continue;
}
const content = nameCell.innerHTML; const content = nameCell.innerHTML;
if (content.indexOf(" <a ") > -1) { if (content.indexOf(" <a ") > -1) {
const female = /♀/.test(content); const female = /♀/.test(content);

View File

@ -499,6 +499,8 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer):
case Type.STELLAR: case Type.STELLAR:
return 1; return 1;
} }
return 0;
} }
/** /**

View File

@ -103,7 +103,7 @@ export class Weather {
const field = scene.getField(true); const field = scene.getField(true);
for (const pokemon of field) { for (const pokemon of field) {
let suppressWeatherEffectAbAttr = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr)[0]; let suppressWeatherEffectAbAttr: SuppressWeatherEffectAbAttr | null = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr)[0];
if (!suppressWeatherEffectAbAttr) { if (!suppressWeatherEffectAbAttr) {
suppressWeatherEffectAbAttr = pokemon.hasPassive() ? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr)[0] : null; suppressWeatherEffectAbAttr = pokemon.hasPassive() ? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr)[0] : null;
} }
@ -116,7 +116,7 @@ export class Weather {
} }
} }
export function getWeatherStartMessage(weatherType: WeatherType): string { export function getWeatherStartMessage(weatherType: WeatherType): string | null {
switch (weatherType) { switch (weatherType) {
case WeatherType.SUNNY: case WeatherType.SUNNY:
return i18next.t("weather:sunnyStartMessage"); return i18next.t("weather:sunnyStartMessage");
@ -141,7 +141,7 @@ export function getWeatherStartMessage(weatherType: WeatherType): string {
return null; return null;
} }
export function getWeatherLapseMessage(weatherType: WeatherType): string { export function getWeatherLapseMessage(weatherType: WeatherType): string | null {
switch (weatherType) { switch (weatherType) {
case WeatherType.SUNNY: case WeatherType.SUNNY:
return i18next.t("weather:sunnyLapseMessage"); return i18next.t("weather:sunnyLapseMessage");
@ -166,7 +166,7 @@ export function getWeatherLapseMessage(weatherType: WeatherType): string {
return null; return null;
} }
export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokemon): string { export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokemon): string | null {
switch (weatherType) { switch (weatherType) {
case WeatherType.SANDSTORM: case WeatherType.SANDSTORM:
return i18next.t("weather:sandstormDamageMessage", {pokemonNameWithAffix: getPokemonNameWithAffix(pokemon)}); return i18next.t("weather:sandstormDamageMessage", {pokemonNameWithAffix: getPokemonNameWithAffix(pokemon)});
@ -177,7 +177,7 @@ export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokem
return null; return null;
} }
export function getWeatherClearMessage(weatherType: WeatherType): string { export function getWeatherClearMessage(weatherType: WeatherType): string | null {
switch (weatherType) { switch (weatherType) {
case WeatherType.SUNNY: case WeatherType.SUNNY:
return i18next.t("weather:sunnyClearMessage"); return i18next.t("weather:sunnyClearMessage");
@ -202,7 +202,7 @@ export function getWeatherClearMessage(weatherType: WeatherType): string {
return null; return null;
} }
export function getTerrainStartMessage(terrainType: TerrainType): string { export function getTerrainStartMessage(terrainType: TerrainType): string | null {
switch (terrainType) { switch (terrainType) {
case TerrainType.MISTY: case TerrainType.MISTY:
return i18next.t("terrain:mistyStartMessage"); return i18next.t("terrain:mistyStartMessage");
@ -212,10 +212,13 @@ export function getTerrainStartMessage(terrainType: TerrainType): string {
return i18next.t("terrain:grassyStartMessage"); return i18next.t("terrain:grassyStartMessage");
case TerrainType.PSYCHIC: case TerrainType.PSYCHIC:
return i18next.t("terrain:psychicStartMessage"); return i18next.t("terrain:psychicStartMessage");
default:
console.warn("getTerrainStartMessage not defined. Using default null");
return null;
} }
} }
export function getTerrainClearMessage(terrainType: TerrainType): string { export function getTerrainClearMessage(terrainType: TerrainType): string | null {
switch (terrainType) { switch (terrainType) {
case TerrainType.MISTY: case TerrainType.MISTY:
return i18next.t("terrain:mistyClearMessage"); return i18next.t("terrain:mistyClearMessage");
@ -225,6 +228,9 @@ export function getTerrainClearMessage(terrainType: TerrainType): string {
return i18next.t("terrain:grassyClearMessage"); return i18next.t("terrain:grassyClearMessage");
case TerrainType.PSYCHIC: case TerrainType.PSYCHIC:
return i18next.t("terrain:psychicClearMessage"); return i18next.t("terrain:psychicClearMessage");
default:
console.warn("getTerrainClearMessage not defined. Using default null");
return null;
} }
} }

View File

@ -84,7 +84,7 @@ export class EggHatchPhase extends Phase {
this.scene.gameData.eggs.splice(eggIndex, 1); this.scene.gameData.eggs.splice(eggIndex, 1);
this.scene.fadeOutBgm(null, false); this.scene.fadeOutBgm(undefined, false);
this.eggHatchHandler = this.scene.ui.getHandler() as EggHatchSceneHandler; this.eggHatchHandler = this.scene.ui.getHandler() as EggHatchSceneHandler;
@ -234,8 +234,8 @@ export class EggHatchPhase extends Phase {
ease: "Sine.easeInOut", ease: "Sine.easeInOut",
duration: 250, duration: 250,
onComplete: () => { onComplete: () => {
count++; count!++;
if (count < repeatCount) { if (count! < repeatCount!) { // we know they are defined
return this.doEggShake(intensity, repeatCount, count).then(() => resolve()); return this.doEggShake(intensity, repeatCount, count).then(() => resolve());
} }
this.scene.tweens.add({ this.scene.tweens.add({
@ -347,7 +347,7 @@ export class EggHatchPhase extends Phase {
this.scene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs); this.scene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs);
this.scene.gameData.setPokemonCaught(this.pokemon, true, true).then(() => { this.scene.gameData.setPokemonCaught(this.pokemon, true, true).then(() => {
this.scene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex).then(() => { this.scene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex).then(() => {
this.scene.ui.showText(null, 0); this.scene.ui.showText("", 0);
this.end(); this.end();
}); });
}); });
@ -447,6 +447,6 @@ export class EggHatchPhase extends Phase {
}, this.egg.id, EGG_SEED.toString()); }, this.egg.id, EGG_SEED.toString());
return ret; return ret!;
} }
} }

View File

@ -81,8 +81,8 @@ export class TagAddedEvent extends ArenaEvent {
this.arenaTagType = arenaTagType; this.arenaTagType = arenaTagType;
this.arenaTagSide = arenaTagSide; this.arenaTagSide = arenaTagSide;
this.arenaTagLayers = arenaTagLayers; this.arenaTagLayers = arenaTagLayers!; // TODO: is this bang correct?
this.arenaTagMaxLayers = arenaTagMaxLayers; this.arenaTagMaxLayers = arenaTagMaxLayers!; // TODO: is this bang correct?
} }
} }
/** /**

View File

@ -16,7 +16,7 @@ export class EvolutionPhase extends Phase {
protected pokemon: PlayerPokemon; protected pokemon: PlayerPokemon;
protected lastLevel: integer; protected lastLevel: integer;
private evolution: SpeciesFormEvolution; private evolution: SpeciesFormEvolution | null;
protected evolutionContainer: Phaser.GameObjects.Container; protected evolutionContainer: Phaser.GameObjects.Container;
protected evolutionBaseBg: Phaser.GameObjects.Image; protected evolutionBaseBg: Phaser.GameObjects.Image;
@ -28,7 +28,7 @@ export class EvolutionPhase extends Phase {
protected pokemonEvoSprite: Phaser.GameObjects.Sprite; protected pokemonEvoSprite: Phaser.GameObjects.Sprite;
protected pokemonEvoTintSprite: Phaser.GameObjects.Sprite; protected pokemonEvoTintSprite: Phaser.GameObjects.Sprite;
constructor(scene: BattleScene, pokemon: PlayerPokemon, evolution: SpeciesFormEvolution, lastLevel: integer) { constructor(scene: BattleScene, pokemon: PlayerPokemon, evolution: SpeciesFormEvolution | null, lastLevel: integer) {
super(scene); super(scene);
this.pokemon = pokemon; this.pokemon = pokemon;
@ -53,7 +53,7 @@ export class EvolutionPhase extends Phase {
return this.end(); return this.end();
} }
this.scene.fadeOutBgm(null, false); this.scene.fadeOutBgm(undefined, false);
const evolutionHandler = this.scene.ui.getHandler() as EvolutionSceneHandler; const evolutionHandler = this.scene.ui.getHandler() as EvolutionSceneHandler;
@ -195,7 +195,7 @@ export class EvolutionPhase extends Phase {
this.scene.ui.showText(i18next.t("menu:stoppedEvolving", { pokemonName: preName }), null, () => { this.scene.ui.showText(i18next.t("menu:stoppedEvolving", { pokemonName: preName }), null, () => {
this.scene.ui.showText(i18next.t("menu:pauseEvolutionsQuestion", { pokemonName: preName }), null, () => { this.scene.ui.showText(i18next.t("menu:pauseEvolutionsQuestion", { pokemonName: preName }), null, () => {
const end = () => { const end = () => {
this.scene.ui.showText(null, 0); this.scene.ui.showText("", 0);
this.scene.playBgm(); this.scene.playBgm();
evolvedPokemon.destroy(); evolvedPokemon.destroy();
this.end(); this.end();

View File

@ -25,8 +25,8 @@ import { TrainerType } from "#enums/trainer-type";
export class Arena { export class Arena {
public scene: BattleScene; public scene: BattleScene;
public biomeType: Biome; public biomeType: Biome;
public weather: Weather; public weather: Weather | null;
public terrain: Terrain; public terrain: Terrain | null;
public tags: ArenaTag[]; public tags: ArenaTag[];
public bgm: string; public bgm: string;
public ignoreAbilities: boolean; public ignoreAbilities: boolean;
@ -121,7 +121,7 @@ export class Arena {
} }
} }
ret = getPokemonSpecies(species); ret = getPokemonSpecies(species!);
if (ret.subLegendary || ret.legendary || ret.mythical) { if (ret.subLegendary || ret.legendary || ret.mythical) {
switch (true) { switch (true) {
@ -292,7 +292,7 @@ export class Arena {
trySetWeatherOverride(weather: WeatherType): boolean { trySetWeatherOverride(weather: WeatherType): boolean {
this.weather = new Weather(weather, 0); this.weather = new Weather(weather, 0);
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1))); this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1)));
this.scene.queueMessage(getWeatherStartMessage(weather)); this.scene.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct?
return true; return true;
} }
@ -314,13 +314,13 @@ export class Arena {
const oldWeatherType = this.weather?.weatherType || WeatherType.NONE; const oldWeatherType = this.weather?.weatherType || WeatherType.NONE;
this.weather = weather ? new Weather(weather, hasPokemonSource ? 5 : 0) : null; this.weather = weather ? new Weather(weather, hasPokemonSource ? 5 : 0) : null;
this.eventTarget.dispatchEvent(new WeatherChangedEvent(oldWeatherType, this.weather?.weatherType, this.weather?.turnsLeft)); this.eventTarget.dispatchEvent(new WeatherChangedEvent(oldWeatherType, this.weather?.weatherType!, this.weather?.turnsLeft!)); // TODO: is this bang correct?
if (this.weather) { if (this.weather) {
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1))); this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1)));
this.scene.queueMessage(getWeatherStartMessage(weather)); this.scene.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct?
} else { } else {
this.scene.queueMessage(getWeatherClearMessage(oldWeatherType)); this.scene.queueMessage(getWeatherClearMessage(oldWeatherType)!); // TODO: is this bang correct?
} }
this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => { this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => {
@ -339,15 +339,15 @@ export class Arena {
const oldTerrainType = this.terrain?.terrainType || TerrainType.NONE; const oldTerrainType = this.terrain?.terrainType || TerrainType.NONE;
this.terrain = terrain ? new Terrain(terrain, hasPokemonSource ? 5 : 0) : null; this.terrain = terrain ? new Terrain(terrain, hasPokemonSource ? 5 : 0) : null;
this.eventTarget.dispatchEvent(new TerrainChangedEvent(oldTerrainType,this.terrain?.terrainType, this.terrain?.turnsLeft)); this.eventTarget.dispatchEvent(new TerrainChangedEvent(oldTerrainType,this.terrain?.terrainType!, this.terrain?.turnsLeft!)); // TODO: are those bangs correct?
if (this.terrain) { if (this.terrain) {
if (!ignoreAnim) { if (!ignoreAnim) {
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.MISTY_TERRAIN + (terrain - 1))); this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.MISTY_TERRAIN + (terrain - 1)));
} }
this.scene.queueMessage(getTerrainStartMessage(terrain)); this.scene.queueMessage(getTerrainStartMessage(terrain)!); // TODO: is this bang correct?
} else { } else {
this.scene.queueMessage(getTerrainClearMessage(oldTerrainType)); this.scene.queueMessage(getTerrainClearMessage(oldTerrainType)!); // TODO: is this bang correct?
} }
this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => { this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => {
@ -554,7 +554,7 @@ export class Arena {
this.applyTagsForSide(tagType, ArenaTagSide.BOTH, ...args); this.applyTagsForSide(tagType, ArenaTagSide.BOTH, ...args);
} }
addTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide = ArenaTagSide.BOTH, quiet: boolean = false, targetIndex?: BattlerIndex): boolean { addTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId: integer, side: ArenaTagSide = ArenaTagSide.BOTH, quiet: boolean = false, targetIndex?: BattlerIndex): boolean {
const existingTag = this.getTagOnSide(tagType, side); const existingTag = this.getTagOnSide(tagType, side);
if (existingTag) { if (existingTag) {
existingTag.onOverlap(this); existingTag.onOverlap(this);
@ -568,21 +568,23 @@ export class Arena {
} }
const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side); const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side);
if (newTag) {
this.tags.push(newTag); this.tags.push(newTag);
newTag.onAdd(this, quiet); newTag.onAdd(this, quiet);
const { layers = 0, maxLayers = 0 } = newTag instanceof ArenaTrapTag ? newTag : {}; const { layers = 0, maxLayers = 0 } = newTag instanceof ArenaTrapTag ? newTag : {};
this.eventTarget.dispatchEvent(new TagAddedEvent(newTag.tagType, newTag.side, newTag.turnCount, layers, maxLayers)); this.eventTarget.dispatchEvent(new TagAddedEvent(newTag.tagType, newTag.side, newTag.turnCount, layers, maxLayers));
}
return true; return true;
} }
getTag(tagType: ArenaTagType | Constructor<ArenaTag>): ArenaTag { getTag(tagType: ArenaTagType | Constructor<ArenaTag>): ArenaTag | undefined {
return this.getTagOnSide(tagType, ArenaTagSide.BOTH); return this.getTagOnSide(tagType, ArenaTagSide.BOTH);
} }
getTagOnSide(tagType: ArenaTagType | Constructor<ArenaTag>, side: ArenaTagSide): ArenaTag { getTagOnSide(tagType: ArenaTagType | Constructor<ArenaTag>, side: ArenaTagSide): ArenaTag | undefined {
return typeof(tagType) === "string" return typeof(tagType) === "string"
? this.tags.find(t => t.tagType === tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side)) ? this.tags.find(t => t.tagType === tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side))
: this.tags.find(t => t instanceof tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side)); : this.tags.find(t => t instanceof tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side));
@ -724,6 +726,9 @@ export class Arena {
return 0.000; return 0.000;
case Biome.SNOWY_FOREST: case Biome.SNOWY_FOREST:
return 3.047; return 3.047;
default:
console.warn(`missing bgm loop-point for biome "${Biome[this.biomeType]}" (=${this.biomeType})`);
return 0;
} }
} }
} }
@ -777,12 +782,12 @@ export class ArenaBase extends Phaser.GameObjects.Container {
this.player = player; this.player = player;
this.base = scene.addFieldSprite(0, 0, "plains_a", null, 1); this.base = scene.addFieldSprite(0, 0, "plains_a", undefined, 1);
this.base.setOrigin(0, 0); this.base.setOrigin(0, 0);
this.props = !player ? this.props = !player ?
new Array(3).fill(null).map(() => { new Array(3).fill(null).map(() => {
const ret = scene.addFieldSprite(0, 0, "plains_b", null, 1); const ret = scene.addFieldSprite(0, 0, "plains_b", undefined, 1);
ret.setOrigin(0, 0); ret.setOrigin(0, 0);
ret.setVisible(false); ret.setVisible(false);
return ret; return ret;

View File

@ -3,6 +3,8 @@ import Pokemon, { DamageResult, HitResult } from "./pokemon";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "../battle";
type TextAndShadowArr = [ string | null, string | null ];
export default class DamageNumberHandler { export default class DamageNumberHandler {
private damageNumbers: Map<BattlerIndex, Phaser.GameObjects.Text[]>; private damageNumbers: Map<BattlerIndex, Phaser.GameObjects.Text[]>;
@ -24,7 +26,7 @@ export default class DamageNumberHandler {
damageNumber.setOrigin(0.5, 1); damageNumber.setOrigin(0.5, 1);
damageNumber.setScale(baseScale); damageNumber.setScale(baseScale);
let [ textColor, shadowColor ] = [ null, null ]; let [ textColor, shadowColor ] : TextAndShadowArr = [ null, null ];
switch (result) { switch (result) {
case HitResult.SUPER_EFFECTIVE: case HitResult.SUPER_EFFECTIVE:
@ -62,12 +64,12 @@ export default class DamageNumberHandler {
this.damageNumbers.set(battlerIndex, []); this.damageNumbers.set(battlerIndex, []);
} }
const yOffset = this.damageNumbers.get(battlerIndex).length * -10; const yOffset = this.damageNumbers.get(battlerIndex)!.length * -10;
if (yOffset) { if (yOffset) {
damageNumber.y += yOffset; damageNumber.y += yOffset;
} }
this.damageNumbers.get(battlerIndex).push(damageNumber); this.damageNumbers.get(battlerIndex)!.push(damageNumber);
if (scene.damageNumbersMode === 1) { if (scene.damageNumbersMode === 1) {
scene.tweens.add({ scene.tweens.add({
@ -83,7 +85,7 @@ export default class DamageNumberHandler {
alpha: 0, alpha: 0,
ease: "Sine.easeIn", ease: "Sine.easeIn",
onComplete: () => { onComplete: () => {
this.damageNumbers.get(battlerIndex).splice(this.damageNumbers.get(battlerIndex).indexOf(damageNumber), 1); this.damageNumbers.get(battlerIndex)!.splice(this.damageNumbers.get(battlerIndex)!.indexOf(damageNumber), 1);
damageNumber.destroy(true); damageNumber.destroy(true);
} }
}); });
@ -167,7 +169,7 @@ export default class DamageNumberHandler {
delay: Utils.fixedInt(500), delay: Utils.fixedInt(500),
alpha: 0, alpha: 0,
onComplete: () => { onComplete: () => {
this.damageNumbers.get(battlerIndex).splice(this.damageNumbers.get(battlerIndex).indexOf(damageNumber), 1); this.damageNumbers.get(battlerIndex)!.splice(this.damageNumbers.get(battlerIndex)!.indexOf(damageNumber), 1);
damageNumber.destroy(true); damageNumber.destroy(true);
} }
} }

View File

@ -35,7 +35,7 @@ export default class PokemonSpriteSparkleHandler {
const ratioX = s.width / width; const ratioX = s.width / width;
const ratioY = s.height / height; const ratioY = s.height / height;
const pixel = texture.manager.getPixel(pixelX, pixelY, texture.key, "__BASE"); const pixel = texture.manager.getPixel(pixelX, pixelY, texture.key, "__BASE");
if (pixel.alpha) { if (pixel?.alpha) {
const [ xOffset, yOffset ] = [ -s.originX * s.width, -s.originY * s.height]; const [ xOffset, yOffset ] = [ -s.originX * s.width, -s.originY * s.height];
const sparkle = (s.scene as BattleScene).addFieldSprite(((pokemon?.x || 0) + s.x + pixelX * ratioX + xOffset), ((pokemon?.y || 0) + s.y + pixelY * ratioY + yOffset), "tera_sparkle"); const sparkle = (s.scene as BattleScene).addFieldSprite(((pokemon?.x || 0) + s.x + pixelX * ratioX + xOffset), ((pokemon?.y || 0) + s.y + pixelY * ratioY + yOffset), "tera_sparkle");
sparkle.pipelineData["ignoreTimeTint"] = s.pipelineData["ignoreTimeTint"]; sparkle.pipelineData["ignoreTimeTint"] = s.pipelineData["ignoreTimeTint"];

View File

@ -79,8 +79,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public ivs: integer[]; public ivs: integer[];
public nature: Nature; public nature: Nature;
public natureOverride: Nature | -1; public natureOverride: Nature | -1;
public moveset: PokemonMove[]; public moveset: (PokemonMove | null)[];
public status: Status; public status: Status | null;
public friendship: integer; public friendship: integer;
public metLevel: integer; public metLevel: integer;
public metBiome: Biome | -1; public metBiome: Biome | -1;
@ -89,7 +89,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public pauseEvolutions: boolean; public pauseEvolutions: boolean;
public pokerus: boolean; public pokerus: boolean;
public fusionSpecies: PokemonSpecies; public fusionSpecies: PokemonSpecies | null;
public fusionFormIndex: integer; public fusionFormIndex: integer;
public fusionAbilityIndex: integer; public fusionAbilityIndex: integer;
public fusionShiny: boolean; public fusionShiny: boolean;
@ -97,7 +97,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public fusionGender: Gender; public fusionGender: Gender;
public fusionLuck: integer; public fusionLuck: integer;
private summonDataPrimer: PokemonSummonData; private summonDataPrimer: PokemonSummonData | null;
public summonData: PokemonSummonData; public summonData: PokemonSummonData;
public battleData: PokemonBattleData; public battleData: PokemonBattleData;
@ -107,7 +107,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public fieldPosition: FieldPosition; public fieldPosition: FieldPosition;
public maskEnabled: boolean; public maskEnabled: boolean;
public maskSprite: Phaser.GameObjects.Sprite; public maskSprite: Phaser.GameObjects.Sprite | null;
private shinySparkle: Phaser.GameObjects.Sprite; private shinySparkle: Phaser.GameObjects.Sprite;
@ -169,7 +169,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.nickname = dataSource.nickname; this.nickname = dataSource.nickname;
this.natureOverride = dataSource.natureOverride !== undefined ? dataSource.natureOverride : -1; this.natureOverride = dataSource.natureOverride !== undefined ? dataSource.natureOverride : -1;
this.moveset = dataSource.moveset; this.moveset = dataSource.moveset;
this.status = dataSource.status; this.status = dataSource.status!; // TODO: is this bang correct?
this.friendship = dataSource.friendship !== undefined ? dataSource.friendship : this.species.baseFriendship; this.friendship = dataSource.friendship !== undefined ? dataSource.friendship : this.species.baseFriendship;
this.metLevel = dataSource.metLevel || 5; this.metLevel = dataSource.metLevel || 5;
this.luck = dataSource.luck; this.luck = dataSource.luck;
@ -177,7 +177,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.metSpecies = dataSource.metSpecies ?? (this.metBiome !== -1 ? this.species.speciesId : this.species.getRootSpeciesId(true)); this.metSpecies = dataSource.metSpecies ?? (this.metBiome !== -1 ? this.species.speciesId : this.species.getRootSpeciesId(true));
this.pauseEvolutions = dataSource.pauseEvolutions; this.pauseEvolutions = dataSource.pauseEvolutions;
this.pokerus = !!dataSource.pokerus; this.pokerus = !!dataSource.pokerus;
this.fusionSpecies = dataSource.fusionSpecies instanceof PokemonSpecies ? dataSource.fusionSpecies : getPokemonSpecies(dataSource.fusionSpecies); this.fusionSpecies = dataSource.fusionSpecies instanceof PokemonSpecies ? dataSource.fusionSpecies : dataSource.fusionSpecies ? getPokemonSpecies(dataSource.fusionSpecies) : null;
this.fusionFormIndex = dataSource.fusionFormIndex; this.fusionFormIndex = dataSource.fusionFormIndex;
this.fusionAbilityIndex = dataSource.fusionAbilityIndex; this.fusionAbilityIndex = dataSource.fusionAbilityIndex;
this.fusionShiny = dataSource.fusionShiny; this.fusionShiny = dataSource.fusionShiny;
@ -350,7 +350,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
loadAssets(ignoreOverride: boolean = true): Promise<void> { loadAssets(ignoreOverride: boolean = true): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
const moveIds = this.getMoveset().map(m => m.getMove().id); const moveIds = this.getMoveset().map(m => m!.getMove().id); // TODO: is this bang correct?
Promise.allSettled(moveIds.map(m => initMoveAnim(this.scene, m))) Promise.allSettled(moveIds.map(m => initMoveAnim(this.scene, m)))
.then(() => { .then(() => {
loadMoveAnimAssets(this.scene, moveIds); loadMoveAnimAssets(this.scene, moveIds);
@ -438,7 +438,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.species.forms[this.formIndex].formKey; return this.species.forms[this.formIndex].formKey;
} }
getFusionFormKey(): string { getFusionFormKey(): string | null {
if (!this.fusionSpecies) { if (!this.fusionSpecies) {
return null; return null;
} }
@ -527,7 +527,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.summonData.fusionSpeciesForm; return this.summonData.fusionSpeciesForm;
} }
if (!this.fusionSpecies?.forms?.length || this.fusionFormIndex >= this.fusionSpecies?.forms.length) { if (!this.fusionSpecies?.forms?.length || this.fusionFormIndex >= this.fusionSpecies?.forms.length) {
return this.fusionSpecies; //@ts-ignore
return this.fusionSpecies; // TODO: I don't even know how to fix this... A complete cluster of classes involved + null
} }
return this.fusionSpecies?.forms[this.fusionFormIndex]; return this.fusionSpecies?.forms[this.fusionFormIndex];
} }
@ -536,7 +537,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.getAt(0) as Phaser.GameObjects.Sprite; return this.getAt(0) as Phaser.GameObjects.Sprite;
} }
getTintSprite(): Phaser.GameObjects.Sprite { getTintSprite(): Phaser.GameObjects.Sprite | null {
return !this.maskEnabled return !this.maskEnabled
? this.getAt(1) as Phaser.GameObjects.Sprite ? this.getAt(1) as Phaser.GameObjects.Sprite
: this.maskSprite; : this.maskSprite;
@ -562,7 +563,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
updateSpritePipelineData(): void { updateSpritePipelineData(): void {
[ this.getSprite(), this.getTintSprite() ].map(s => s.pipelineData["teraColor"] = getTypeRgb(this.getTeraType())); [ this.getSprite(), this.getTintSprite() ].filter(s => !!s).map(s => s.pipelineData["teraColor"] = getTypeRgb(this.getTeraType()));
this.updateInfo(true); this.updateInfo(true);
} }
@ -610,7 +611,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
playAnim(): void { playAnim(): void {
this.tryPlaySprite(this.getSprite(), this.getTintSprite(), this.getBattleSpriteKey()); this.tryPlaySprite(this.getSprite(), this.getTintSprite()!, this.getBattleSpriteKey()); // TODO: is the bag correct?
} }
getFieldPositionOffset(): [ number, number ] { getFieldPositionOffset(): [ number, number ] {
@ -870,7 +871,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
abstract isBoss(): boolean; abstract isBoss(): boolean;
getMoveset(ignoreOverride?: boolean): PokemonMove[] { getMoveset(ignoreOverride?: boolean): (PokemonMove | null)[] {
const ret = !ignoreOverride && this.summonData?.moveset const ret = !ignoreOverride && this.summonData?.moveset
? this.summonData.moveset ? this.summonData.moveset
: this.moveset; : this.moveset;
@ -921,7 +922,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (this.metBiome === -1) { if (this.metBiome === -1) {
levelMoves = this.getUnlockedEggMoves().concat(levelMoves); levelMoves = this.getUnlockedEggMoves().concat(levelMoves);
} }
return levelMoves.filter(lm => !this.moveset.some(m => m.moveId === lm)); return levelMoves.filter(lm => !this.moveset.some(m => m?.moveId === lm));
} }
/** /**
@ -932,7 +933,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @returns array of {@linkcode Type} * @returns array of {@linkcode Type}
*/ */
getTypes(includeTeraType = false, forDefend: boolean = false, ignoreOverride?: boolean): Type[] { getTypes(includeTeraType = false, forDefend: boolean = false, ignoreOverride?: boolean): Type[] {
const types = []; const types : Type[] = [];
if (includeTeraType) { if (includeTeraType) {
const teraType = this.getTeraType(); const teraType = this.getTeraType();
@ -942,7 +943,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
if (!types.length || !includeTeraType) { if (!types.length || !includeTeraType) {
if (!ignoreOverride && this.summonData?.types) { if (!ignoreOverride && this.summonData?.types && this.summonData.types.length !== 0) {
this.summonData.types.forEach(t => types.push(t)); this.summonData.types.forEach(t => types.push(t));
} else { } else {
const speciesForm = this.getSpeciesForm(ignoreOverride); const speciesForm = this.getSpeciesForm(ignoreOverride);
@ -1115,7 +1116,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return false; return false;
} }
} }
return (this.hp || ability.isBypassFaint) && !ability.conditions.find(condition => !condition(this)); return (!!this.hp || ability.isBypassFaint) && !ability.conditions.find(condition => !condition(this));
} }
/** /**
@ -1242,7 +1243,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
? moveOrType ? moveOrType
: undefined; : undefined;
const moveType = (moveOrType instanceof Move) const moveType = (moveOrType instanceof Move)
? move.type ? move!.type // TODO: is this bang correct?
: moveOrType; : moveOrType;
if (moveType === Type.STELLAR) { if (moveType === Type.STELLAR) {
@ -1285,7 +1286,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
} }
return multiplier; return multiplier as TypeDamageMultiplier;
} }
/** /**
@ -1327,7 +1328,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return (atkScore + defScore) * hpDiffRatio; return (atkScore + defScore) * hpDiffRatio;
} }
getEvolution(): SpeciesFormEvolution { getEvolution(): SpeciesFormEvolution | null {
if (pokemonEvolutions.hasOwnProperty(this.species.speciesId)) { if (pokemonEvolutions.hasOwnProperty(this.species.speciesId)) {
const evolutions = pokemonEvolutions[this.species.speciesId]; const evolutions = pokemonEvolutions[this.species.speciesId];
for (const e of evolutions) { for (const e of evolutions) {
@ -1339,7 +1340,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
} }
if (this.isFusion() && pokemonEvolutions.hasOwnProperty(this.fusionSpecies.speciesId)) { if (this.isFusion() && this.fusionSpecies && pokemonEvolutions.hasOwnProperty(this.fusionSpecies.speciesId)) {
const fusionEvolutions = pokemonEvolutions[this.fusionSpecies.speciesId].map(e => new FusionSpeciesFormEvolution(this.species.speciesId, e)); const fusionEvolutions = pokemonEvolutions[this.fusionSpecies.speciesId].map(e => new FusionSpeciesFormEvolution(this.species.speciesId, e));
for (const fe of fusionEvolutions) { for (const fe of fusionEvolutions) {
if (!fe.item && this.level >= fe.level && (!fe.preFormKey || this.getFusionFormKey() === fe.preFormKey)) { if (!fe.item && this.level >= fe.level && (!fe.preFormKey || this.getFusionFormKey() === fe.preFormKey)) {
@ -1549,7 +1550,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
clearFusionSpecies(): void { clearFusionSpecies(): void {
this.fusionSpecies = undefined; this.fusionSpecies = null;
this.fusionFormIndex = 0; this.fusionFormIndex = 0;
this.fusionAbilityIndex = 0; this.fusionAbilityIndex = 0;
this.fusionShiny = false; this.fusionShiny = false;
@ -1709,9 +1710,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// Sqrt the weight of any damaging moves with overlapping types. This is about a 0.05 - 0.1 multiplier. // Sqrt the weight of any damaging moves with overlapping types. This is about a 0.05 - 0.1 multiplier.
// Other damaging moves 2x weight if 0-1 damaging moves, 0.5x if 2, 0.125x if 3. These weights double if STAB. // Other damaging moves 2x weight if 0-1 damaging moves, 0.5x if 2, 0.125x if 3. These weights double if STAB.
// Status moves remain unchanged on weight, this encourages 1-2 // Status moves remain unchanged on weight, this encourages 1-2
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]]); 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]]); // TODO: is this bang correct?
} else { // Non-trainer pokemon just use normal weights } else { // Non-trainer pokemon just use normal weights
movePool = baseWeights.filter(m => !this.moveset.some(mo => m[0] === mo.moveId)); movePool = baseWeights.filter(m => !this.moveset.some(mo => m[0] === mo?.moveId));
} }
const totalWeight = movePool.reduce((v, m) => v + m[1], 0); const totalWeight = movePool.reduce((v, m) => v + m[1], 0);
let rand = Utils.randSeedInt(totalWeight); let rand = Utils.randSeedInt(totalWeight);
@ -1729,7 +1730,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const move = this.getMoveset().length > moveIndex const move = this.getMoveset().length > moveIndex
? this.getMoveset()[moveIndex] ? this.getMoveset()[moveIndex]
: null; : null;
return move?.isUsable(this, ignorePp); return move?.isUsable(this, ignorePp)!; // TODO: is this bang correct?
} }
showInfo(): void { showInfo(): void {
@ -1812,7 +1813,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.levelExp = this.exp - getLevelTotalExp(this.level, this.species.growthRate); this.levelExp = this.exp - getLevelTotalExp(this.level, this.species.growthRate);
} }
getOpponent(targetIndex: integer): Pokemon { getOpponent(targetIndex: integer): Pokemon | null {
const ret = this.getOpponents()[targetIndex]; const ret = this.getOpponents()[targetIndex];
if (ret.summonData) { if (ret.summonData) {
return ret; return ret;
@ -1977,7 +1978,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.scene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critLevel); this.scene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critLevel);
this.scene.applyModifiers(TempBattleStatBoosterModifier, source.isPlayer(), TempBattleStat.CRIT, critLevel); this.scene.applyModifiers(TempBattleStatBoosterModifier, source.isPlayer(), TempBattleStat.CRIT, critLevel);
const bonusCrit = new Utils.BooleanHolder(false); const bonusCrit = new Utils.BooleanHolder(false);
if (applyAbAttrs(BonusCritAbAttr, source, null, bonusCrit)) { //@ts-ignore
if (applyAbAttrs(BonusCritAbAttr, source, null, bonusCrit)) { // TODO: resolve ts-ignore. This is a promise. Checking a promise is bogus.
if (bonusCrit.value) { if (bonusCrit.value) {
critLevel.value += 1; critLevel.value += 1;
} }
@ -2000,7 +2002,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
isCritical = false; isCritical = false;
} }
} }
const sourceAtk = new Utils.IntegerHolder(source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, this, null, isCritical)); const sourceAtk = new Utils.IntegerHolder(source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, isCritical));
const targetDef = new Utils.IntegerHolder(this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, isCritical)); const targetDef = new Utils.IntegerHolder(this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, isCritical));
const criticalMultiplier = new Utils.NumberHolder(isCritical ? 1.5 : 1); const criticalMultiplier = new Utils.NumberHolder(isCritical ? 1.5 : 1);
applyAbAttrs(MultCritAbAttr, source, null, criticalMultiplier); applyAbAttrs(MultCritAbAttr, source, null, criticalMultiplier);
@ -2088,6 +2090,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
isCritical = false; isCritical = false;
result = HitResult.EFFECTIVE; result = HitResult.EFFECTIVE;
} }
result = result!; // telling TS compiler that result is defined!
if (!result) { if (!result) {
if (!typeMultiplier.value) { if (!typeMultiplier.value) {
@ -2192,7 +2195,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
if (damage) { if (damage) {
const attacker = this.scene.getPokemonById(source.id); const attacker = this.scene.getPokemonById(source.id)!; // TODO: is this bang correct?
destinyTag?.lapse(attacker, BattlerTagLapseType.CUSTOM); destinyTag?.lapse(attacker, BattlerTagLapseType.CUSTOM);
} }
} }
@ -2292,7 +2295,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
isMax(): boolean { isMax(): boolean {
const maxForms = [SpeciesFormKey.GIGANTAMAX, SpeciesFormKey.GIGANTAMAX_RAPID, SpeciesFormKey.GIGANTAMAX_SINGLE, SpeciesFormKey.ETERNAMAX] as string[]; const maxForms = [SpeciesFormKey.GIGANTAMAX, SpeciesFormKey.GIGANTAMAX_RAPID, SpeciesFormKey.GIGANTAMAX_SINGLE, SpeciesFormKey.ETERNAMAX] as string[];
return maxForms.includes(this.getFormKey()) || maxForms.includes(this.getFusionFormKey()); return maxForms.includes(this.getFormKey()) || (!!this.getFusionFormKey() && maxForms.includes(this.getFusionFormKey()!));
} }
addTag(tagType: BattlerTagType, turnCount: integer = 0, sourceMove?: Moves, sourceId?: integer): boolean { addTag(tagType: BattlerTagType, turnCount: integer = 0, sourceMove?: Moves, sourceId?: integer): boolean {
@ -2302,7 +2305,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return false; return false;
} }
const newTag = getBattlerTag(tagType, turnCount, sourceMove, sourceId); const newTag = getBattlerTag(tagType, turnCount, sourceMove!, sourceId!); // TODO: are the bangs correct?
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
applyPreApplyBattlerTagAbAttrs(BattlerTagImmunityAbAttr, this, newTag, cancelled); applyPreApplyBattlerTagAbAttrs(BattlerTagImmunityAbAttr, this, newTag, cancelled);
@ -2321,18 +2324,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
/** @overload */ /** @overload */
getTag(tagType: BattlerTagType): BattlerTag; getTag(tagType: BattlerTagType): BattlerTag | null;
/** @overload */ /** @overload */
getTag<T extends BattlerTag>(tagType: Constructor<T>): T; getTag<T extends BattlerTag>(tagType: Constructor<T>): T | null;
getTag(tagType: BattlerTagType | Constructor<BattlerTag>): BattlerTag { getTag(tagType: BattlerTagType | Constructor<BattlerTag>): BattlerTag | null {
if (!this.summonData) { if (!this.summonData) {
return null; return null;
} }
return tagType instanceof Function return (tagType instanceof Function
? this.summonData.tags.find(t => t instanceof tagType) ? this.summonData.tags.find(t => t instanceof tagType)
: this.summonData.tags.find(t => t.tagType === tagType); : this.summonData.tags.find(t => t.tagType === tagType)
)!; // TODO: is this bang correct?
} }
findTag(tagFilter: ((tag: BattlerTag) => boolean)) { findTag(tagFilter: ((tag: BattlerTag) => boolean)) {
@ -2436,7 +2440,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.getMoveHistory().push(turnMove); this.getMoveHistory().push(turnMove);
} }
getLastXMoves(turnCount?: integer): TurnMove[] { getLastXMoves(turnCount: integer = 0): TurnMove[] {
const moveHistory = this.getMoveHistory(); const moveHistory = this.getMoveHistory();
return moveHistory.slice(turnCount >= 0 ? Math.max(moveHistory.length - (turnCount || 1), 0) : 0, moveHistory.length).reverse(); return moveHistory.slice(turnCount >= 0 ? Math.max(moveHistory.length - (turnCount || 1), 0) : 0, moveHistory.length).reverse();
} }
@ -2514,9 +2518,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
let frameThreshold: number; let frameThreshold: number;
sprite.anims.pause(); sprite.anims.pause();
tintSprite.anims.pause(); tintSprite?.anims.pause();
let faintCryTimer = this.scene.time.addEvent({ let faintCryTimer : Phaser.Time.TimerEvent | null = this.scene.time.addEvent({
delay: Utils.fixedInt(delay), delay: Utils.fixedInt(delay),
repeat: -1, repeat: -1,
callback: () => { callback: () => {
@ -2526,7 +2530,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
while (frameProgress > frameThreshold) { while (frameProgress > frameThreshold) {
if (sprite.anims.duration) { if (sprite.anims.duration) {
sprite.anims.nextFrame(); sprite.anims.nextFrame();
tintSprite.anims.nextFrame(); tintSprite?.anims.nextFrame();
} }
frameProgress -= frameThreshold; frameProgress -= frameThreshold;
} }
@ -2534,7 +2538,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
rate *= 0.99; rate *= 0.99;
cry.setRate(rate); cry.setRate(rate);
} else { } else {
faintCryTimer.destroy(); faintCryTimer?.destroy();
faintCryTimer = null; faintCryTimer = null;
if (callback) { if (callback) {
callback(); callback();
@ -2593,9 +2597,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
let frameThreshold: number; let frameThreshold: number;
sprite.anims.pause(); sprite.anims.pause();
tintSprite.anims.pause(); tintSprite?.anims.pause();
let faintCryTimer = this.scene.time.addEvent({ let faintCryTimer: Phaser.Time.TimerEvent | null = this.scene.time.addEvent({
delay: Utils.fixedInt(delay), delay: Utils.fixedInt(delay),
repeat: -1, repeat: -1,
callback: () => { callback: () => {
@ -2605,7 +2609,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
while (frameProgress > frameThreshold) { while (frameProgress > frameThreshold) {
if (sprite.anims.duration) { if (sprite.anims.duration) {
sprite.anims.nextFrame(); sprite.anims.nextFrame();
tintSprite.anims.nextFrame(); tintSprite?.anims.nextFrame();
} }
frameProgress -= frameThreshold; frameProgress -= frameThreshold;
} }
@ -2622,7 +2626,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
fusionCry.setRate(rate); fusionCry.setRate(rate);
} }
if ((!cry || cry.pendingRemove) && (!fusionCry || fusionCry.pendingRemove)) { if ((!cry || cry.pendingRemove) && (!fusionCry || fusionCry.pendingRemove)) {
faintCryTimer.destroy(); faintCryTimer?.destroy();
faintCryTimer = null; faintCryTimer = null;
if (callback) { if (callback) {
callback(); callback();
@ -2653,7 +2657,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.gender !== Gender.GENDERLESS && pokemon.gender === (this.gender === Gender.MALE ? Gender.FEMALE : Gender.MALE); return this.gender !== Gender.GENDERLESS && pokemon.gender === (this.gender === Gender.MALE ? Gender.FEMALE : Gender.MALE);
} }
canSetStatus(effect: StatusEffect, quiet: boolean = false, overrideStatus: boolean = false, sourcePokemon: Pokemon = null): boolean { canSetStatus(effect: StatusEffect | undefined, quiet: boolean = false, overrideStatus: boolean = false, sourcePokemon: Pokemon | null = null): boolean {
if (effect !== StatusEffect.FAINT) { if (effect !== StatusEffect.FAINT) {
if (overrideStatus ? this.status?.effect === effect : this.status) { if (overrideStatus ? this.status?.effect === effect : this.status) {
return false; return false;
@ -2704,7 +2708,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
break; break;
case StatusEffect.FREEZE: case StatusEffect.FREEZE:
if (this.isOfType(Type.ICE) || [WeatherType.SUNNY, WeatherType.HARSH_SUN].includes(this.scene?.arena.weather?.weatherType)) { if (this.isOfType(Type.ICE) || (this.scene?.arena?.weather?.weatherType &&[WeatherType.SUNNY, WeatherType.HARSH_SUN].includes(this.scene.arena.weather.weatherType))) {
return false; return false;
} }
break; break;
@ -2728,7 +2732,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return true; return true;
} }
trySetStatus(effect: StatusEffect, asPhase: boolean = false, sourcePokemon: Pokemon = null, cureTurn: integer = 0, sourceText: string = null): boolean { trySetStatus(effect: StatusEffect | undefined, asPhase: boolean = false, sourcePokemon: Pokemon | null = null, cureTurn: integer | null = 0, sourceText: string | null = null): boolean {
if (!this.canSetStatus(effect, asPhase, false, sourcePokemon)) { if (!this.canSetStatus(effect, asPhase, false, sourcePokemon)) {
return false; return false;
} }
@ -2742,7 +2746,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
if (asPhase) { if (asPhase) {
this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, cureTurn, sourceText, sourcePokemon)); this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, cureTurn, sourceText!, sourcePokemon!)); // TODO: are these bangs correct?
return true; return true;
} }
@ -2770,6 +2774,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
} }
statusCureTurn = statusCureTurn!; // tell TS compiler it's defined
effect = effect!; // If `effect` is undefined then `trySetStatus()` will have already returned early via the `canSetStatus()` call
this.status = new Status(effect, 0, statusCureTurn?.value); this.status = new Status(effect, 0, statusCureTurn?.value);
if (effect !== StatusEffect.FAINT) { if (effect !== StatusEffect.FAINT) {
@ -2790,7 +2796,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (!revive && lastStatus === StatusEffect.FAINT) { if (!revive && lastStatus === StatusEffect.FAINT) {
return; return;
} }
this.status = undefined; this.status = null;
if (lastStatus === StatusEffect.SLEEP) { if (lastStatus === StatusEffect.SLEEP) {
this.setFrameRate(12); this.setFrameRate(12);
if (this.getTag(BattlerTagType.NIGHTMARE)) { if (this.getTag(BattlerTagType.NIGHTMARE)) {
@ -2858,16 +2864,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
setFrameRate(frameRate: integer) { setFrameRate(frameRate: integer) {
this.scene.anims.get(this.getBattleSpriteKey()).frameRate = frameRate; this.scene.anims.get(this.getBattleSpriteKey()).frameRate = frameRate;
this.getSprite().play(this.getBattleSpriteKey()); this.getSprite().play(this.getBattleSpriteKey());
this.getTintSprite().play(this.getBattleSpriteKey()); this.getTintSprite()?.play(this.getBattleSpriteKey());
} }
tint(color: number, alpha?: number, duration?: integer, ease?: string) { tint(color: number, alpha?: number, duration?: integer, ease?: string) {
const tintSprite = this.getTintSprite(); const tintSprite = this.getTintSprite();
tintSprite.setTintFill(color); tintSprite?.setTintFill(color);
tintSprite.setVisible(true); tintSprite?.setVisible(true);
if (duration) { if (duration) {
tintSprite.setAlpha(0); tintSprite?.setAlpha(0);
this.scene.tweens.add({ this.scene.tweens.add({
targets: tintSprite, targets: tintSprite,
@ -2876,7 +2882,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
ease: ease || "Linear" ease: ease || "Linear"
}); });
} else { } else {
tintSprite.setAlpha(alpha); tintSprite?.setAlpha(alpha);
} }
} }
@ -2890,32 +2896,32 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
duration: duration, duration: duration,
ease: ease || "Linear", ease: ease || "Linear",
onComplete: () => { onComplete: () => {
tintSprite.setVisible(false); tintSprite?.setVisible(false);
tintSprite.setAlpha(1); tintSprite?.setAlpha(1);
} }
}); });
} else { } else {
tintSprite.setVisible(false); tintSprite?.setVisible(false);
tintSprite.setAlpha(1); tintSprite?.setAlpha(1);
} }
} }
enableMask() { enableMask() {
if (!this.maskEnabled) { if (!this.maskEnabled) {
this.maskSprite = this.getTintSprite(); this.maskSprite = this.getTintSprite();
this.maskSprite.setVisible(true); this.maskSprite?.setVisible(true);
this.maskSprite.setPosition(this.x * this.parentContainer.scale + this.parentContainer.x, this.maskSprite?.setPosition(this.x * this.parentContainer.scale + this.parentContainer.x,
this.y * this.parentContainer.scale + this.parentContainer.y); this.y * this.parentContainer.scale + this.parentContainer.y);
this.maskSprite.setScale(this.getSpriteScale() * this.parentContainer.scale); this.maskSprite?.setScale(this.getSpriteScale() * this.parentContainer.scale);
this.maskEnabled = true; this.maskEnabled = true;
} }
} }
disableMask() { disableMask() {
if (this.maskEnabled) { if (this.maskEnabled) {
this.maskSprite.setVisible(false); this.maskSprite?.setVisible(false);
this.maskSprite.setPosition(0, 0); this.maskSprite?.setPosition(0, 0);
this.maskSprite.setScale(this.getSpriteScale()); this.maskSprite?.setScale(this.getSpriteScale());
this.maskSprite = null; this.maskSprite = null;
this.maskEnabled = false; this.maskEnabled = false;
} }
@ -2930,7 +2936,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
updateFusionPalette(ignoreOveride?: boolean): void { updateFusionPalette(ignoreOveride?: boolean): void {
if (!this.getFusionSpeciesForm(ignoreOveride)) { if (!this.getFusionSpeciesForm(ignoreOveride)) {
[ this.getSprite(), this.getTintSprite() ].map(s => { [ this.getSprite(), this.getTintSprite() ].filter(s => !!s).map(s => {
s.pipelineData[`spriteColors${ignoreOveride && this.summonData?.speciesForm ? "Base" : ""}`] = []; s.pipelineData[`spriteColors${ignoreOveride && this.summonData?.speciesForm ? "Base" : ""}`] = [];
s.pipelineData[`fusionSpriteColors${ignoreOveride && this.summonData?.speciesForm ? "Base" : ""}`] = []; s.pipelineData[`fusionSpriteColors${ignoreOveride && this.summonData?.speciesForm ? "Base" : ""}`] = [];
}); });
@ -2966,9 +2972,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const frame = [ sourceFrame, sourceBackFrame, fusionFrame, fusionBackFrame ][c]; const frame = [ sourceFrame, sourceBackFrame, fusionFrame, fusionBackFrame ][c];
canv.width = frame.width; canv.width = frame.width;
canv.height = frame.height; canv.height = frame.height;
if (context) {
context.drawImage([ sourceImage, sourceBackImage, fusionImage, fusionBackImage ][c], frame.cutX, frame.cutY, frame.width, frame.height, 0, 0, frame.width, frame.height); context.drawImage([ sourceImage, sourceBackImage, fusionImage, fusionBackImage ][c], frame.cutX, frame.cutY, frame.width, frame.height, 0, 0, frame.width, frame.height);
const imageData = context.getImageData(frame.cutX, frame.cutY, frame.width, frame.height); const imageData = context.getImageData(frame.cutX, frame.cutY, frame.width, frame.height);
pixelData.push(imageData.data); pixelData.push(imageData.data);
}
}); });
for (let f = 0; f < 2; f++) { for (let f = 0; f < 2; f++) {
@ -2988,9 +2997,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const color = Utils.rgbaToInt([r, g, b, a]); const color = Utils.rgbaToInt([r, g, b, a]);
if (variantColorSet.has(color)) { if (variantColorSet.has(color)) {
const mappedPixel = variantColorSet.get(color); const mappedPixel = variantColorSet.get(color);
if (mappedPixel) {
[ r, g, b, a ] = mappedPixel; [ r, g, b, a ] = mappedPixel;
} }
} }
}
if (!spriteColors.find(c => c[0] === r && c[1] === g && c[2] === b)) { if (!spriteColors.find(c => c[0] === r && c[1] === g && c[2] === b)) {
spriteColors.push([ r, g, b, a ]); spriteColors.push([ r, g, b, a ]);
} }
@ -3000,7 +3011,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const fusionSpriteColors = JSON.parse(JSON.stringify(spriteColors)); const fusionSpriteColors = JSON.parse(JSON.stringify(spriteColors));
const pixelColors = []; const pixelColors: number[] = [];
for (let f = 0; f < 2; f++) { for (let f = 0; f < 2; f++) {
for (let i = 0; i < pixelData[f].length; i += 4) { for (let i = 0; i < pixelData[f].length; i += 4) {
const total = pixelData[f].slice(i, i + 3).reduce((total: integer, value: integer) => total + value, 0); const total = pixelData[f].slice(i, i + 3).reduce((total: integer, value: integer) => total + value, 0);
@ -3011,7 +3022,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
} }
const fusionPixelColors = []; const fusionPixelColors : number[] = [];
for (let f = 0; f < 2; f++) { for (let f = 0; f < 2; f++) {
const variantColors = variantColorCache[!f ? fusionSpriteKey : fusionBackSpriteKey]; const variantColors = variantColorCache[!f ? fusionSpriteKey : fusionBackSpriteKey];
const variantColorSet = new Map<integer, integer[]>(); const variantColorSet = new Map<integer, integer[]>();
@ -3030,9 +3041,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const color = Utils.rgbaToInt([r, g, b, a]); const color = Utils.rgbaToInt([r, g, b, a]);
if (variantColorSet.has(color)) { if (variantColorSet.has(color)) {
const mappedPixel = variantColorSet.get(color); const mappedPixel = variantColorSet.get(color);
if (mappedPixel) {
[ r, g, b, a ] = mappedPixel; [ r, g, b, a ] = mappedPixel;
} }
} }
}
fusionPixelColors.push(argbFromRgba({ r, g, b, a })); fusionPixelColors.push(argbFromRgba({ r, g, b, a }));
} }
} }
@ -3050,9 +3063,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
Math.random = originalRandom; Math.random = originalRandom;
paletteColors = paletteColors!; // tell TS compiler that paletteColors is defined!
fusionPaletteColors = fusionPaletteColors!; // TS compiler that fusionPaletteColors is defined!
const [ palette, fusionPalette ] = [ paletteColors, fusionPaletteColors ] const [ palette, fusionPalette ] = [ paletteColors, fusionPaletteColors ]
.map(paletteColors => { .map(paletteColors => {
let keys = Array.from(paletteColors.keys()).sort((a: integer, b: integer) => paletteColors.get(a) < paletteColors.get(b) ? 1 : -1); let keys = Array.from(paletteColors.keys()).sort((a: integer, b: integer) => paletteColors.get(a)! < paletteColors.get(b)! ? 1 : -1);
let rgbaColors: Map<number, integer[]>; let rgbaColors: Map<number, integer[]>;
let hsvColors: Map<number, number[]>; let hsvColors: Map<number, number[]>;
@ -3065,19 +3080,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
map.set(k, Object.values(rgbaFromArgb(k))); return map; map.set(k, Object.values(rgbaFromArgb(k))); return map;
}, new Map<number, integer[]>()); }, new Map<number, integer[]>());
hsvColors = Array.from(rgbaColors.keys()).reduce((map: Map<number, number[]>, k: number) => { hsvColors = Array.from(rgbaColors.keys()).reduce((map: Map<number, number[]>, k: number) => {
const rgb = rgbaColors.get(k).slice(0, 3); const rgb = rgbaColors.get(k)!.slice(0, 3);
map.set(k, Utils.rgbToHsv(rgb[0], rgb[1], rgb[2])); map.set(k, Utils.rgbToHsv(rgb[0], rgb[1], rgb[2]));
return map; return map;
}, new Map<number, number[]>()); }, new Map<number, number[]>());
for (let c = keys.length - 1; c >= 0; c--) { for (let c = keys.length - 1; c >= 0; c--) {
const hsv = hsvColors.get(keys[c]); const hsv = hsvColors.get(keys[c])!;
for (let c2 = 0; c2 < c; c2++) { for (let c2 = 0; c2 < c; c2++) {
const hsv2 = hsvColors.get(keys[c2]); const hsv2 = hsvColors.get(keys[c2])!;
const diff = Math.abs(hsv[0] - hsv2[0]); const diff = Math.abs(hsv[0] - hsv2[0]);
if (diff < 30 || diff >= 330) { if (diff < 30 || diff >= 330) {
if (mappedColors.has(keys[c])) { if (mappedColors.has(keys[c])) {
mappedColors.get(keys[c]).push(keys[c2]); mappedColors.get(keys[c])!.push(keys[c2]);
} else { } else {
mappedColors.set(keys[c], [ keys[c2] ]); mappedColors.set(keys[c], [ keys[c2] ]);
} }
@ -3087,10 +3102,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
mappedColors.forEach((values: integer[], key: integer) => { mappedColors.forEach((values: integer[], key: integer) => {
const keyColor = rgbaColors.get(key); const keyColor = rgbaColors.get(key)!;
const valueColors = values.map(v => rgbaColors.get(v)); const valueColors = values.map(v => rgbaColors.get(v)!);
const color = keyColor.slice(0); const color = keyColor.slice(0);
let count = paletteColors.get(key); let count = paletteColors.get(key)!;
for (const value of values) { for (const value of values) {
const valueCount = paletteColors.get(value); const valueCount = paletteColors.get(value);
if (!valueCount) { if (!valueCount) {
@ -3100,10 +3115,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
for (let c = 0; c < 3; c++) { for (let c = 0; c < 3; c++) {
color[c] *= (paletteColors.get(key) / count); color[c] *= (paletteColors.get(key)! / count);
values.forEach((value: integer, i: integer) => { values.forEach((value: integer, i: integer) => {
if (paletteColors.has(value)) { if (paletteColors.has(value)) {
const valueCount = paletteColors.get(value); const valueCount = paletteColors.get(value)!;
color[c] += valueColors[i][c] * (valueCount / count); color[c] += valueColors[i][c] * (valueCount / count);
} }
}); });
@ -3121,7 +3136,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
paletteColors.set(argbFromRgba({ r: color[0], g: color[1], b: color[2], a: color[3] }), count); paletteColors.set(argbFromRgba({ r: color[0], g: color[1], b: color[2], a: color[3] }), count);
}); });
keys = Array.from(paletteColors.keys()).sort((a: integer, b: integer) => paletteColors.get(a) < paletteColors.get(b) ? 1 : -1); keys = Array.from(paletteColors.keys()).sort((a: integer, b: integer) => paletteColors.get(a)! < paletteColors.get(b)! ? 1 : -1);
} while (mappedColors.size); } while (mappedColors.size);
return keys.map(c => Object.values(rgbaFromArgb(c))); return keys.map(c => Object.values(rgbaFromArgb(c)));
@ -3152,7 +3167,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
} }
[ this.getSprite(), this.getTintSprite() ].map(s => { [ this.getSprite(), this.getTintSprite() ].filter(s => !!s).map(s => {
s.pipelineData[`spriteColors${ignoreOveride && this.summonData?.speciesForm ? "Base" : ""}`] = spriteColors; s.pipelineData[`spriteColors${ignoreOveride && this.summonData?.speciesForm ? "Base" : ""}`] = spriteColors;
s.pipelineData[`fusionSpriteColors${ignoreOveride && this.summonData?.speciesForm ? "Base" : ""}`] = fusionSpriteColors; s.pipelineData[`fusionSpriteColors${ignoreOveride && this.summonData?.speciesForm ? "Base" : ""}`] = fusionSpriteColors;
}); });
@ -3216,7 +3231,7 @@ export default interface Pokemon {
export class PlayerPokemon extends Pokemon { export class PlayerPokemon extends Pokemon {
public compatibleTms: Moves[]; public compatibleTms: Moves[];
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender: Gender, shiny: boolean, variant: Variant, ivs: integer[], nature: Nature, dataSource: Pokemon | PokemonData) { constructor(scene: BattleScene, species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData) {
super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource); super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource);
if (Overrides.STATUS_OVERRIDE) { if (Overrides.STATUS_OVERRIDE) {
@ -3320,11 +3335,11 @@ export class PlayerPokemon extends Pokemon {
addFriendship(friendship: integer): void { addFriendship(friendship: integer): void {
const starterSpeciesId = this.species.getRootSpeciesId(); const starterSpeciesId = this.species.getRootSpeciesId();
const fusionStarterSpeciesId = this.isFusion() ? this.fusionSpecies.getRootSpeciesId() : 0; const fusionStarterSpeciesId = this.isFusion() && this.fusionSpecies ? this.fusionSpecies.getRootSpeciesId() : 0;
const starterData = [ const starterData = [
this.scene.gameData.starterData[starterSpeciesId], this.scene.gameData.starterData[starterSpeciesId],
fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null
].filter(d => d); ].filter(d => !!d);
const amount = new Utils.IntegerHolder(friendship); const amount = new Utils.IntegerHolder(friendship);
const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? 2 : 1) / (fusionStarterSpeciesId ? 2 : 1))); const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? 2 : 1) / (fusionStarterSpeciesId ? 2 : 1)));
if (amount.value > 0) { if (amount.value > 0) {
@ -3388,7 +3403,10 @@ export class PlayerPokemon extends Pokemon {
}); });
} }
getPossibleEvolution(evolution: SpeciesFormEvolution): Promise<Pokemon> { getPossibleEvolution(evolution: SpeciesFormEvolution | null): Promise<Pokemon> {
if (!evolution) {
return new Promise(resolve => resolve(this));
}
return new Promise(resolve => { return new Promise(resolve => {
const evolutionSpecies = getPokemonSpecies(evolution.speciesId); const evolutionSpecies = getPokemonSpecies(evolution.speciesId);
const isFusion = evolution instanceof FusionSpeciesFormEvolution; const isFusion = evolution instanceof FusionSpeciesFormEvolution;
@ -3409,7 +3427,10 @@ export class PlayerPokemon extends Pokemon {
}); });
} }
evolve(evolution: SpeciesFormEvolution, preEvolution: PokemonSpeciesForm): Promise<void> { evolve(evolution: SpeciesFormEvolution | null, preEvolution: PokemonSpeciesForm): Promise<void> {
if (!evolution) {
return new Promise(resolve => resolve());
}
return new Promise(resolve => { return new Promise(resolve => {
this.pauseEvolutions = false; this.pauseEvolutions = false;
// Handles Nincada evolving into Ninjask + Shedinja // Handles Nincada evolving into Ninjask + Shedinja
@ -3421,7 +3442,7 @@ export class PlayerPokemon extends Pokemon {
this.fusionSpecies = getPokemonSpecies(evolution.speciesId); this.fusionSpecies = getPokemonSpecies(evolution.speciesId);
} }
if (evolution.preFormKey !== null) { if (evolution.preFormKey !== null) {
const formIndex = Math.max((!isFusion ? this.species : this.fusionSpecies).forms.findIndex(f => f.formKey === evolution.evoFormKey), 0); const formIndex = Math.max((!isFusion || !this.fusionSpecies ? this.species : this.fusionSpecies).forms.findIndex(f => f.formKey === evolution.evoFormKey), 0);
if (!isFusion) { if (!isFusion) {
this.formIndex = formIndex; this.formIndex = formIndex;
} else { } else {
@ -3477,10 +3498,10 @@ export class PlayerPokemon extends Pokemon {
const isFusion = evolution instanceof FusionSpeciesFormEvolution; const isFusion = evolution instanceof FusionSpeciesFormEvolution;
const evoSpecies = (!isFusion ? this.species : this.fusionSpecies); const evoSpecies = (!isFusion ? this.species : this.fusionSpecies);
if (evoSpecies.speciesId === Species.NINCADA && evolution.speciesId === Species.NINJASK) { if (evoSpecies?.speciesId === Species.NINCADA && evolution.speciesId === Species.NINJASK) {
const newEvolution = pokemonEvolutions[evoSpecies.speciesId][1]; const newEvolution = pokemonEvolutions[evoSpecies.speciesId][1];
if (newEvolution.condition.predicate(this)) { if (newEvolution.condition?.predicate(this)) {
const newPokemon = this.scene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, undefined, this.shiny, this.variant, this.ivs, this.nature); const newPokemon = this.scene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, undefined, this.shiny, this.variant, this.ivs, this.nature);
newPokemon.natureOverride = this.natureOverride; newPokemon.natureOverride = this.natureOverride;
newPokemon.passive = this.passive; newPokemon.passive = this.passive;
@ -3576,7 +3597,7 @@ export class PlayerPokemon extends Pokemon {
if (!this.isFainted()) { if (!this.isFainted()) {
// If this Pokemon hasn't fainted, make sure the HP wasn't set over the new maximum // If this Pokemon hasn't fainted, make sure the HP wasn't set over the new maximum
this.hp = Math.min(this.hp, this.stats[Stat.HP]); this.hp = Math.min(this.hp, this.stats[Stat.HP]);
this.status = getRandomStatus(this.status, pokemon.status); // Get a random valid status between the two this.status = getRandomStatus(this.status!, pokemon.status!); // Get a random valid status between the two // TODO: are the bangs correct?
} else if (!pokemon.isFainted()) { } else if (!pokemon.isFainted()) {
// If this Pokemon fainted but the other hasn't, make sure the HP wasn't set to zero // If this Pokemon fainted but the other hasn't, make sure the HP wasn't set to zero
this.hp = Math.max(this.hp, 1); this.hp = Math.max(this.hp, 1);
@ -3601,7 +3622,7 @@ export class PlayerPokemon extends Pokemon {
this.scene.removePartyMemberModifiers(fusedPartyMemberIndex); this.scene.removePartyMemberModifiers(fusedPartyMemberIndex);
this.scene.getParty().splice(fusedPartyMemberIndex, 1)[0]; this.scene.getParty().splice(fusedPartyMemberIndex, 1)[0];
const newPartyMemberIndex = this.scene.getParty().indexOf(this); const newPartyMemberIndex = this.scene.getParty().indexOf(this);
pokemon.getMoveset(true).map(m => this.scene.unshiftPhase(new LearnMovePhase(this.scene, newPartyMemberIndex, m.getMove().id))); pokemon.getMoveset(true).map(m => this.scene.unshiftPhase(new LearnMovePhase(this.scene, newPartyMemberIndex, m!.getMove().id))); // TODO: is the bang correct?
pokemon.destroy(); pokemon.destroy();
this.updateFusionPalette(); this.updateFusionPalette();
resolve(); resolve();
@ -3621,9 +3642,9 @@ export class PlayerPokemon extends Pokemon {
/** Returns a deep copy of this Pokemon's moveset array */ /** Returns a deep copy of this Pokemon's moveset array */
copyMoveset(): PokemonMove[] { copyMoveset(): PokemonMove[] {
const newMoveset = []; const newMoveset : PokemonMove[] = [];
this.moveset.forEach(move => this.moveset.forEach(move =>
newMoveset.push(new PokemonMove(move.moveId, 0, move.ppUp, move.virtual))); newMoveset.push(new PokemonMove(move!.moveId, 0, move!.ppUp, move!.virtual))); // TODO: are those bangs correct?
return newMoveset; return newMoveset;
} }
@ -3637,9 +3658,9 @@ export class EnemyPokemon extends Pokemon {
/** To indicate of the instance was populated with a dataSource -> e.g. loaded & populated from session data */ /** To indicate of the instance was populated with a dataSource -> e.g. loaded & populated from session data */
public readonly isPopulatedFromDataSource: boolean; public readonly isPopulatedFromDataSource: boolean;
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean, dataSource: PokemonData) { constructor(scene: BattleScene, species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean, dataSource?: PokemonData) {
super(scene, 236, 84, species, level, dataSource?.abilityIndex, dataSource?.formIndex, super(scene, 236, 84, species, level, dataSource?.abilityIndex, dataSource?.formIndex,
dataSource?.gender, dataSource ? dataSource.shiny : false, dataSource ? dataSource.variant : undefined, null, dataSource ? dataSource.nature : undefined, dataSource); dataSource?.gender, dataSource ? dataSource.shiny : false, dataSource ? dataSource.variant : undefined, undefined, dataSource ? dataSource.nature : undefined, dataSource);
this.trainerSlot = trainerSlot; this.trainerSlot = trainerSlot;
this.isPopulatedFromDataSource = !!dataSource; // if a dataSource is provided, then it was populated from dataSource this.isPopulatedFromDataSource = !!dataSource; // if a dataSource is provided, then it was populated from dataSource
@ -3672,7 +3693,7 @@ export class EnemyPokemon extends Pokemon {
let speciesId = species.speciesId; let speciesId = species.speciesId;
while ((prevolution = pokemonPrevolutions[speciesId])) { while ((prevolution = pokemonPrevolutions[speciesId])) {
const evolution = pokemonEvolutions[prevolution].find(pe => pe.speciesId === speciesId && (!pe.evoFormKey || pe.evoFormKey === this.getFormKey())); const evolution = pokemonEvolutions[prevolution].find(pe => pe.speciesId === speciesId && (!pe.evoFormKey || pe.evoFormKey === this.getFormKey()));
if (evolution.condition?.enforceFunc) { if (evolution?.condition?.enforceFunc) {
evolution.condition.enforceFunc(this); evolution.condition.enforceFunc(this);
} }
speciesId = prevolution; speciesId = prevolution;
@ -3748,7 +3769,7 @@ export class EnemyPokemon extends Pokemon {
getNextMove(): QueuedMove { getNextMove(): QueuedMove {
// If this Pokemon has a move already queued, return it. // If this Pokemon has a move already queued, return it.
const queuedMove = this.getMoveQueue().length const queuedMove = this.getMoveQueue().length
? this.getMoveset().find(m => m.moveId === this.getMoveQueue()[0].move) ? this.getMoveset().find(m => m?.moveId === this.getMoveQueue()[0].move)
: null; : null;
if (queuedMove) { if (queuedMove) {
if (queuedMove.isUsable(this, this.getMoveQueue()[0].ignorePP)) { if (queuedMove.isUsable(this, this.getMoveQueue()[0].ignorePP)) {
@ -3760,24 +3781,24 @@ export class EnemyPokemon extends Pokemon {
} }
// Filter out any moves this Pokemon cannot use // Filter out any moves this Pokemon cannot use
const movePool = this.getMoveset().filter(m => m.isUsable(this)); const movePool = this.getMoveset().filter(m => m?.isUsable(this));
// If no moves are left, use Struggle. Otherwise, continue with move selection // If no moves are left, use Struggle. Otherwise, continue with move selection
if (movePool.length) { if (movePool.length) {
// If there's only 1 move in the move pool, use it. // If there's only 1 move in the move pool, use it.
if (movePool.length === 1) { if (movePool.length === 1) {
return { move: movePool[0].moveId, targets: this.getNextTargets(movePool[0].moveId) }; return { move: movePool[0]!.moveId, targets: this.getNextTargets(movePool[0]!.moveId) }; // TODO: are the bangs correct?
} }
// If a move is forced because of Encore, use it. // If a move is forced because of Encore, use it.
const encoreTag = this.getTag(EncoreTag) as EncoreTag; const encoreTag = this.getTag(EncoreTag) as EncoreTag;
if (encoreTag) { if (encoreTag) {
const encoreMove = movePool.find(m => m.moveId === encoreTag.moveId); const encoreMove = movePool.find(m => m?.moveId === encoreTag.moveId);
if (encoreMove) { if (encoreMove) {
return { move: encoreMove.moveId, targets: this.getNextTargets(encoreMove.moveId) }; return { move: encoreMove.moveId, targets: this.getNextTargets(encoreMove.moveId) };
} }
} }
switch (this.aiType) { switch (this.aiType) {
case AiType.RANDOM: // No enemy should spawn with this AI type in-game case AiType.RANDOM: // No enemy should spawn with this AI type in-game
const moveId = movePool[this.scene.randBattleSeedInt(movePool.length)].moveId; const moveId = movePool[this.scene.randBattleSeedInt(movePool.length)]!.moveId; // TODO: is the bang correct?
return { move: moveId, targets: this.getNextTargets(moveId) }; return { move: moveId, targets: this.getNextTargets(moveId) };
case AiType.SMART_RANDOM: case AiType.SMART_RANDOM:
case AiType.SMART: case AiType.SMART:
@ -3787,9 +3808,9 @@ export class EnemyPokemon extends Pokemon {
* For more information on how benefit scores are calculated, see `docs/enemy-ai.md`. * For more information on how benefit scores are calculated, see `docs/enemy-ai.md`.
*/ */
const moveScores = movePool.map(() => 0); const moveScores = movePool.map(() => 0);
const moveTargets = Object.fromEntries(movePool.map(m => [ m.moveId, this.getNextTargets(m.moveId) ])); const moveTargets = Object.fromEntries(movePool.map(m => [ m!.moveId, this.getNextTargets(m!.moveId) ])); // TODO: are those bangs correct?
for (const m in movePool) { for (const m in movePool) {
const pokemonMove = movePool[m]; const pokemonMove = movePool[m]!; // TODO: is the bang correct?
const move = pokemonMove.getMove(); const move = pokemonMove.getMove();
let moveScore = moveScores[m]; let moveScore = moveScores[m];
@ -3870,8 +3891,8 @@ export class EnemyPokemon extends Pokemon {
r++; r++;
} }
} }
console.log(movePool.map(m => m.getName()), moveScores, r, sortedMovePool.map(m => m.getName())); console.log(movePool.map(m => m!.getName()), moveScores, r, sortedMovePool.map(m => m!.getName())); // TODO: are those bangs correct?
return { move: sortedMovePool[r].moveId, targets: moveTargets[sortedMovePool[r].moveId] }; return { move: sortedMovePool[r]!.moveId, targets: moveTargets[sortedMovePool[r]!.moveId] };
} }
} }
@ -3934,7 +3955,7 @@ export class EnemyPokemon extends Pokemon {
} }
const thresholds: integer[] = []; const thresholds: integer[] = [];
let totalWeight: integer; let totalWeight: integer = 0;
targetWeights.reduce((total: integer, w: integer) => { targetWeights.reduce((total: integer, w: integer) => {
total += w; total += w;
thresholds.push(total); thresholds.push(total);
@ -3948,7 +3969,7 @@ export class EnemyPokemon extends Pokemon {
* is greater than that random number. * is greater than that random number.
*/ */
const randValue = this.scene.randBattleSeedInt(totalWeight); const randValue = this.scene.randBattleSeedInt(totalWeight);
let targetIndex: integer; let targetIndex: integer = 0;
thresholds.every((t, i) => { thresholds.every((t, i) => {
if (randValue >= t) { if (randValue >= t) {
@ -4124,7 +4145,7 @@ export class EnemyPokemon extends Pokemon {
addToParty(pokeballType: PokeballType) { addToParty(pokeballType: PokeballType) {
const party = this.scene.getParty(); const party = this.scene.getParty();
let ret: PlayerPokemon = null; let ret: PlayerPokemon | null = null;
if (party.length < 6) { if (party.length < 6) {
this.pokeball = pokeballType; this.pokeball = pokeballType;
@ -4173,15 +4194,15 @@ export class PokemonSummonData {
public abilitySuppressed: boolean = false; public abilitySuppressed: boolean = false;
public abilitiesApplied: Abilities[] = []; public abilitiesApplied: Abilities[] = [];
public speciesForm: PokemonSpeciesForm; public speciesForm: PokemonSpeciesForm | null;
public fusionSpeciesForm: PokemonSpeciesForm; public fusionSpeciesForm: PokemonSpeciesForm;
public ability: Abilities = Abilities.NONE; public ability: Abilities = Abilities.NONE;
public gender: Gender; public gender: Gender;
public fusionGender: Gender; public fusionGender: Gender;
public stats: integer[]; public stats: integer[];
public moveset: PokemonMove[]; public moveset: (PokemonMove | null)[];
// If not initialized this value will not be populated from save data. // If not initialized this value will not be populated from save data.
public types: Type[] = null; public types: Type[] = [];
} }
export class PokemonBattleData { export class PokemonBattleData {

View File

@ -208,7 +208,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
} }
getPartyLevels(waveIndex: integer): integer[] { getPartyLevels(waveIndex: integer): integer[] {
const ret = []; const ret: number[] = [];
const partyTemplate = this.getPartyTemplate(); const partyTemplate = this.getPartyTemplate();
const difficultyWaveIndex = this.scene.gameMode.getWaveForDifficulty(waveIndex); const difficultyWaveIndex = this.scene.gameMode.getWaveForDifficulty(waveIndex);
@ -257,7 +257,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
genPartyMember(index: integer): EnemyPokemon { genPartyMember(index: integer): EnemyPokemon {
const battle = this.scene.currentBattle; const battle = this.scene.currentBattle;
const level = battle.enemyLevels[index]; const level = battle.enemyLevels?.[index]!; // TODO: is this bang correct?
let ret: EnemyPokemon; let ret: EnemyPokemon;
@ -290,7 +290,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
} }
// Create an empty species pool (which will be set to one of the species pools based on the index) // Create an empty species pool (which will be set to one of the species pools based on the index)
let newSpeciesPool = []; let newSpeciesPool: Species[] = [];
let useNewSpeciesPool = false; let useNewSpeciesPool = false;
// If we are in a double battle of named trainers, we need to use alternate species pools (generate half the party from each trainer) // If we are in a double battle of named trainers, we need to use alternate species pools (generate half the party from each trainer)
@ -315,7 +315,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
return !species.some(s => AlreadyUsedSpecies.includes(s)); return !species.some(s => AlreadyUsedSpecies.includes(s));
} }
return !AlreadyUsedSpecies.includes(species); return !AlreadyUsedSpecies.includes(species);
}); }).flat();
// Filter out the species that are already in the enemy party from the partner trainer species pool // Filter out the species that are already in the enemy party from the partner trainer species pool
const speciesPoolPartnerFiltered = speciesPoolPartner.filter(species => { const speciesPoolPartnerFiltered = speciesPoolPartner.filter(species => {
@ -324,7 +324,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
return !species.some(s => AlreadyUsedSpecies.includes(s)); return !species.some(s => AlreadyUsedSpecies.includes(s));
} }
return !AlreadyUsedSpecies.includes(species); return !AlreadyUsedSpecies.includes(species);
}); }).flat();
// If the index is even, use the species pool for the main trainer (that way he only uses his own pokemon in battle) // If the index is even, use the species pool for the main trainer (that way he only uses his own pokemon in battle)
@ -370,7 +370,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
ret = this.scene.addEnemyPokemon(species, level, !this.isDouble() || !(index % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); ret = this.scene.addEnemyPokemon(species, level, !this.isDouble() || !(index % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER);
}, this.config.hasStaticParty ? this.config.getDerivedType() + ((index + 1) << 8) : this.scene.currentBattle.waveIndex + (this.config.getDerivedType() << 10) + (((!this.config.useSameSeedForAllMembers ? index : 0) + 1) << 8)); }, this.config.hasStaticParty ? this.config.getDerivedType() + ((index + 1) << 8) : this.scene.currentBattle.waveIndex + (this.config.getDerivedType() << 10) + (((!this.config.useSameSeedForAllMembers ? index : 0) + 1) << 8));
return ret; return ret!; // TODO: is this bang correct?
} }
@ -481,7 +481,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
if (maxScorePartyMemberIndexes.length > 1) { if (maxScorePartyMemberIndexes.length > 1) {
let rand: integer; let rand: integer;
this.scene.executeWithSeedOffset(() => rand = Utils.randSeedInt(maxScorePartyMemberIndexes.length), this.scene.currentBattle.turn << 2); this.scene.executeWithSeedOffset(() => rand = Utils.randSeedInt(maxScorePartyMemberIndexes.length), this.scene.currentBattle.turn << 2);
return maxScorePartyMemberIndexes[rand]; return maxScorePartyMemberIndexes[rand!];
} }
return maxScorePartyMemberIndexes[0]; return maxScorePartyMemberIndexes[0];
@ -499,6 +499,9 @@ export default class Trainer extends Phaser.GameObjects.Container {
return 0.45; return 0.45;
case PartyMemberStrength.STRONGER: case PartyMemberStrength.STRONGER:
return 0.375; return 0.375;
default:
console.warn("getPartyMemberModifierChanceMultiplier not defined. Using default 0");
return 0;
} }
} }

View File

@ -167,7 +167,7 @@ export class GameMode implements GameModeConfig {
} }
} }
getOverrideSpecies(waveIndex: integer): PokemonSpecies { getOverrideSpecies(waveIndex: integer): PokemonSpecies | null {
if (this.isDaily && this.isWaveFinal(waveIndex)) { if (this.isDaily && this.isWaveFinal(waveIndex)) {
const allFinalBossSpecies = allSpecies.filter(s => (s.subLegendary || s.legendary || s.mythical) const allFinalBossSpecies = allSpecies.filter(s => (s.subLegendary || s.legendary || s.mythical)
&& s.baseTotal >= 600 && s.speciesId !== Species.ETERNATUS && s.speciesId !== Species.ARCEUS); && s.baseTotal >= 600 && s.speciesId !== Species.ETERNATUS && s.speciesId !== Species.ARCEUS);
@ -210,7 +210,7 @@ export class GameMode implements GameModeConfig {
* @returns true if waveIndex is a multiple of 50 in Endless * @returns true if waveIndex is a multiple of 50 in Endless
*/ */
isEndlessBoss(waveIndex: integer): boolean { isEndlessBoss(waveIndex: integer): boolean {
return waveIndex % 50 && return !!(waveIndex % 50) &&
(this.modeId === GameModes.ENDLESS || this.modeId === GameModes.SPLICED_ENDLESS); (this.modeId === GameModes.ENDLESS || this.modeId === GameModes.SPLICED_ENDLESS);
} }
@ -267,6 +267,8 @@ export class GameMode implements GameModeConfig {
return 5000; return 5000;
case GameModes.DAILY: case GameModes.DAILY:
return 2500; return 2500;
default:
return 0;
} }
} }

View File

@ -154,7 +154,7 @@ export class InputsController {
}); });
if (typeof this.scene.input.gamepad !== "undefined") { if (typeof this.scene.input.gamepad !== "undefined") {
this.scene.input.gamepad.on("connected", function (thisGamepad) { this.scene.input.gamepad?.on("connected", function (thisGamepad) {
if (!thisGamepad) { if (!thisGamepad) {
return; return;
} }
@ -163,23 +163,23 @@ export class InputsController {
this.onReconnect(thisGamepad); this.onReconnect(thisGamepad);
}, this); }, this);
this.scene.input.gamepad.on("disconnected", function (thisGamepad) { this.scene.input.gamepad?.on("disconnected", function (thisGamepad) {
this.onDisconnect(thisGamepad); // when a gamepad is disconnected this.onDisconnect(thisGamepad); // when a gamepad is disconnected
}, this); }, this);
// Check to see if the gamepad has already been setup by the browser // Check to see if the gamepad has already been setup by the browser
this.scene.input.gamepad.refreshPads(); this.scene.input.gamepad?.refreshPads();
if (this.scene.input.gamepad.total) { if (this.scene.input.gamepad?.total) {
this.refreshGamepads(); this.refreshGamepads();
for (const thisGamepad of this.gamepads) { for (const thisGamepad of this.gamepads) {
this.scene.input.gamepad.emit("connected", thisGamepad); this.scene.input.gamepad.emit("connected", thisGamepad);
} }
} }
this.scene.input.gamepad.on("down", this.gamepadButtonDown, this); this.scene.input.gamepad?.on("down", this.gamepadButtonDown, this);
this.scene.input.gamepad.on("up", this.gamepadButtonUp, this); this.scene.input.gamepad?.on("up", this.gamepadButtonUp, this);
this.scene.input.keyboard.on("keydown", this.keyboardKeyDown, this); this.scene.input.keyboard?.on("keydown", this.keyboardKeyDown, this);
this.scene.input.keyboard.on("keyup", this.keyboardKeyUp, this); this.scene.input.keyboard?.on("keyup", this.keyboardKeyUp, this);
} }
this.touchControls = new TouchControl(this.scene); this.touchControls = new TouchControl(this.scene);
} }
@ -338,9 +338,9 @@ export class InputsController {
*/ */
refreshGamepads(): void { refreshGamepads(): void {
// Sometimes, gamepads are undefined. For some reason. // Sometimes, gamepads are undefined. For some reason.
this.gamepads = this.scene.input.gamepad.gamepads.filter(function (el) { this.gamepads = this.scene.input.gamepad?.gamepads.filter(function (el) {
return el !== null; return el !== null;
}); })!; // TODO: is this bang correct?
for (const [index, thisGamepad] of this.gamepads.entries()) { for (const [index, thisGamepad] of this.gamepads.entries()) {
thisGamepad.index = index; // Overwrite the gamepad index, in case we had undefined gamepads earlier thisGamepad.index = index; // Overwrite the gamepad index, in case we had undefined gamepads earlier

View File

@ -453,8 +453,8 @@ export class LoadingScene extends SceneBase {
// videos do not need to be preloaded // videos do not need to be preloaded
intro.loadURL("images/intro_dark.mp4", true); intro.loadURL("images/intro_dark.mp4", true);
if (mobile) { if (mobile) {
intro.video.setAttribute("webkit-playsinline", "webkit-playsinline"); intro.video?.setAttribute("webkit-playsinline", "webkit-playsinline");
intro.video.setAttribute("playsinline", "playsinline"); intro.video?.setAttribute("playsinline", "playsinline");
} }
intro.play(); intro.play();
}); });

View File

@ -7,7 +7,10 @@ import i18next from "i18next";
* @param pokemon {@linkcode Pokemon} name and battle context will be retrieved from this instance * @param pokemon {@linkcode Pokemon} name and battle context will be retrieved from this instance
* @returns {string} ex: "Wild Gengar", "Ectoplasma sauvage" * @returns {string} ex: "Wild Gengar", "Ectoplasma sauvage"
*/ */
export function getPokemonNameWithAffix(pokemon: Pokemon): string { export function getPokemonNameWithAffix(pokemon: Pokemon | undefined): string {
if (!pokemon) {
return "Missigno";
} // TODO: little easter-egg, lol
switch (pokemon.scene.currentBattle.battleSpec) { switch (pokemon.scene.currentBattle.battleSpec) {
case BattleSpec.DEFAULT: case BattleSpec.DEFAULT:
return !pokemon.isPlayer() return !pokemon.isPlayer()

View File

@ -51,13 +51,13 @@ export class ModifierType {
public group: string; public group: string;
public soundName: string; public soundName: string;
public tier: ModifierTier; public tier: ModifierTier;
protected newModifierFunc: NewModifierFunc; protected newModifierFunc: NewModifierFunc | null;
constructor(localeKey: string, iconImage: string, newModifierFunc: NewModifierFunc, group?: string, soundName?: string) { constructor(localeKey: string | null, iconImage: string | null, newModifierFunc: NewModifierFunc | null, group?: string, soundName?: string) {
this.localeKey = localeKey; this.localeKey = localeKey!; // TODO: is this bang correct?
this.iconImage = iconImage; this.iconImage = iconImage!; // TODO: is this bang correct?
this.group = group || ""; this.group = group!; // TODO: is this bang correct?
this.soundName = soundName || "restore"; this.soundName = soundName ?? "restore";
this.newModifierFunc = newModifierFunc; this.newModifierFunc = newModifierFunc;
} }
@ -73,7 +73,7 @@ export class ModifierType {
this.tier = tier; this.tier = tier;
} }
getOrInferTier(poolType: ModifierPoolType = ModifierPoolType.PLAYER): ModifierTier { getOrInferTier(poolType: ModifierPoolType = ModifierPoolType.PLAYER): ModifierTier | null {
if (this.tier) { if (this.tier) {
return this.tier; return this.tier;
} }
@ -111,16 +111,16 @@ export class ModifierType {
} }
withIdFromFunc(func: ModifierTypeFunc): ModifierType { withIdFromFunc(func: ModifierTypeFunc): ModifierType {
this.id = Object.keys(modifierTypes).find(k => modifierTypes[k] === func); this.id = Object.keys(modifierTypes).find(k => modifierTypes[k] === func)!; // TODO: is this bang correct?
return this; return this;
} }
newModifier(...args: any[]): Modifier { newModifier(...args: any[]): Modifier | null {
return this.newModifierFunc(this, args); return this.newModifierFunc && this.newModifierFunc(this, args);
} }
} }
type ModifierTypeGeneratorFunc = (party: Pokemon[], pregenArgs?: any[]) => ModifierType; type ModifierTypeGeneratorFunc = (party: Pokemon[], pregenArgs?: any[]) => ModifierType | null;
export class ModifierTypeGenerator extends ModifierType { export class ModifierTypeGenerator extends ModifierType {
private genTypeFunc: ModifierTypeGeneratorFunc; private genTypeFunc: ModifierTypeGeneratorFunc;
@ -197,7 +197,7 @@ class AddVoucherModifierType extends ModifierType {
} }
export class PokemonModifierType extends ModifierType { export class PokemonModifierType extends ModifierType {
public selectFilter: PokemonSelectFilter; public selectFilter: PokemonSelectFilter | undefined;
constructor(localeKey: string, iconImage: string, newModifierFunc: NewModifierFunc, selectFilter?: PokemonSelectFilter, group?: string, soundName?: string) { constructor(localeKey: string, iconImage: string, newModifierFunc: NewModifierFunc, selectFilter?: PokemonSelectFilter, group?: string, soundName?: string) {
super(localeKey, iconImage, newModifierFunc, group, soundName); super(localeKey, iconImage, newModifierFunc, group, soundName);
@ -298,7 +298,7 @@ export class PokemonStatusHealModifierType extends PokemonModifierType {
} }
export abstract class PokemonMoveModifierType extends PokemonModifierType { export abstract class PokemonMoveModifierType extends PokemonModifierType {
public moveSelectFilter: PokemonMoveSelectFilter; public moveSelectFilter: PokemonMoveSelectFilter | undefined;
constructor(localeKey: string, iconImage: string, newModifierFunc: NewModifierFunc, selectFilter?: PokemonSelectFilter, moveSelectFilter?: PokemonMoveSelectFilter, group?: string) { constructor(localeKey: string, iconImage: string, newModifierFunc: NewModifierFunc, selectFilter?: PokemonSelectFilter, moveSelectFilter?: PokemonMoveSelectFilter, group?: string) {
super(localeKey, iconImage, newModifierFunc, selectFilter, group); super(localeKey, iconImage, newModifierFunc, selectFilter, group);
@ -338,7 +338,7 @@ export class PokemonAllMovePpRestoreModifierType extends PokemonModifierType {
constructor(localeKey: string, iconImage: string, restorePoints: integer) { constructor(localeKey: string, iconImage: string, restorePoints: integer) {
super(localeKey, iconImage, (_type, args) => new Modifiers.PokemonAllMovePpRestoreModifier(this, (args[0] as PlayerPokemon).id, this.restorePoints), super(localeKey, iconImage, (_type, args) => new Modifiers.PokemonAllMovePpRestoreModifier(this, (args[0] as PlayerPokemon).id, this.restorePoints),
(pokemon: PlayerPokemon) => { (pokemon: PlayerPokemon) => {
if (!pokemon.getMoveset().filter(m => m.ppUsed).length) { if (!pokemon.getMoveset().filter(m => m?.ppUsed).length) {
return PartyUiHandler.NoEffectMessage; return PartyUiHandler.NoEffectMessage;
} }
return null; return null;
@ -518,7 +518,7 @@ export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType i
public boostPercent: integer; public boostPercent: integer;
constructor(moveType: Type, boostPercent: integer) { constructor(moveType: Type, boostPercent: integer) {
super("", `${getAttackTypeBoosterItemName(moveType).replace(/[ \-]/g, "_").toLowerCase()}`, super("", `${getAttackTypeBoosterItemName(moveType)?.replace(/[ \-]/g, "_").toLowerCase()}`,
(_type, args) => new Modifiers.AttackTypeBoosterModifier(this, (args[0] as Pokemon).id, moveType, boostPercent)); (_type, args) => new Modifiers.AttackTypeBoosterModifier(this, (args[0] as Pokemon).id, moveType, boostPercent));
this.moveType = moveType; this.moveType = moveType;
@ -526,7 +526,7 @@ export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType i
} }
get name(): string { get name(): string {
return i18next.t(`modifierType:AttackTypeBoosterItem.${getAttackTypeBoosterItemName(this.moveType).replace(/[ \-]/g, "_").toLowerCase()}`); return i18next.t(`modifierType:AttackTypeBoosterItem.${getAttackTypeBoosterItemName(this.moveType)?.replace(/[ \-]/g, "_").toLowerCase()}`);
} }
getDescription(scene: BattleScene): string { getDescription(scene: BattleScene): string {
@ -638,7 +638,7 @@ class AllPokemonFullHpRestoreModifierType extends ModifierType {
constructor(localeKey: string, iconImage: string, descriptionKey?: string, newModifierFunc?: NewModifierFunc) { constructor(localeKey: string, iconImage: string, descriptionKey?: string, newModifierFunc?: NewModifierFunc) {
super(localeKey, iconImage, newModifierFunc || ((_type, _args) => new Modifiers.PokemonHpRestoreModifier(this, -1, 0, 100, false))); super(localeKey, iconImage, newModifierFunc || ((_type, _args) => new Modifiers.PokemonHpRestoreModifier(this, -1, 0, 100, false)));
this.descriptionKey = descriptionKey; this.descriptionKey = descriptionKey!; // TODO: is this bang correct?
} }
getDescription(scene: BattleScene): string { getDescription(scene: BattleScene): string {
@ -773,7 +773,7 @@ export class EvolutionItemModifierType extends PokemonModifierType implements Ge
if (pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) && pokemonEvolutions[pokemon.species.speciesId].filter(e => e.item === this.evolutionItem if (pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) && pokemonEvolutions[pokemon.species.speciesId].filter(e => e.item === this.evolutionItem
&& (!e.condition || e.condition.predicate(pokemon))).length && (pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX)) { && (!e.condition || e.condition.predicate(pokemon))).length && (pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX)) {
return null; return null;
} else if (pokemon.isFusion() && pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) && pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter(e => e.item === this.evolutionItem } else if (pokemon.isFusion() && pokemon.fusionSpecies && pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) && pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter(e => e.item === this.evolutionItem
&& (!e.condition || e.condition.predicate(pokemon))).length && (pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX)) { && (!e.condition || e.condition.predicate(pokemon))).length && (pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX)) {
return null; return null;
} }
@ -859,7 +859,7 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator {
return new AttackTypeBoosterModifierType(pregenArgs[0] as Type, 20); return new AttackTypeBoosterModifierType(pregenArgs[0] as Type, 20);
} }
const attackMoveTypes = party.map(p => p.getMoveset().map(m => m.getMove()).filter(m => m instanceof AttackMove).map(m => m.type)).flat(); const attackMoveTypes = party.map(p => p.getMoveset().map(m => m?.getMove()).filter(m => m instanceof AttackMove).map(m => m.type)).flat();
if (!attackMoveTypes.length) { if (!attackMoveTypes.length) {
return null; return null;
} }
@ -868,8 +868,8 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator {
let totalWeight = 0; let totalWeight = 0;
for (const t of attackMoveTypes) { for (const t of attackMoveTypes) {
if (attackMoveTypeWeights.has(t)) { if (attackMoveTypeWeights.has(t)) {
if (attackMoveTypeWeights.get(t) < 3) { if (attackMoveTypeWeights.get(t)! < 3) { // attackMoveTypeWeights.has(t) was checked before
attackMoveTypeWeights.set(t, attackMoveTypeWeights.get(t) + 1); attackMoveTypeWeights.set(t, attackMoveTypeWeights.get(t)! + 1);
} else { } else {
continue; continue;
} }
@ -889,7 +889,7 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator {
let weight = 0; let weight = 0;
for (const t of attackMoveTypeWeights.keys()) { for (const t of attackMoveTypeWeights.keys()) {
const typeWeight = attackMoveTypeWeights.get(t); const typeWeight = attackMoveTypeWeights.get(t)!; // guranteed to be defined
if (randInt <= weight + typeWeight) { if (randInt <= weight + typeWeight) {
type = t; type = t;
break; break;
@ -897,7 +897,7 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator {
weight += typeWeight; weight += typeWeight;
} }
return new AttackTypeBoosterModifierType(type, 20); return new AttackTypeBoosterModifierType(type!, 20);
}); });
} }
} }
@ -930,7 +930,7 @@ class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator {
for (const p of party) { for (const p of party) {
const speciesId = p.getSpeciesForm(true).speciesId; const speciesId = p.getSpeciesForm(true).speciesId;
const fusionSpeciesId = p.isFusion() ? p.getFusionSpeciesForm(true).speciesId : null; const fusionSpeciesId = p.isFusion() ? p.getFusionSpeciesForm(true).speciesId : null;
const hasFling = p.getMoveset(true).some(m => m.moveId === Moves.FLING); const hasFling = p.getMoveset(true).some(m => m?.moveId === Moves.FLING);
for (const i in values) { for (const i in values) {
const checkedSpecies = values[i].species; const checkedSpecies = values[i].species;
@ -980,7 +980,7 @@ class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator {
class TmModifierTypeGenerator extends ModifierTypeGenerator { class TmModifierTypeGenerator extends ModifierTypeGenerator {
constructor(tier: ModifierTier) { constructor(tier: ModifierTier) {
super((party: Pokemon[]) => { super((party: Pokemon[]) => {
const partyMemberCompatibleTms = party.map(p => (p as PlayerPokemon).compatibleTms.filter(tm => !p.moveset.find(m => m.moveId === tm))); const partyMemberCompatibleTms = party.map(p => (p as PlayerPokemon).compatibleTms.filter(tm => !p.moveset.find(m => m?.moveId === tm)));
const tierUniqueCompatibleTms = partyMemberCompatibleTms.flat().filter(tm => tmPoolTiers[tm] === tier).filter(tm => !allMoves[tm].name.endsWith(" (N)")).filter((tm, i, array) => array.indexOf(tm) === i); const tierUniqueCompatibleTms = partyMemberCompatibleTms.flat().filter(tm => tmPoolTiers[tm] === tier).filter(tm => !allMoves[tm].name.endsWith(" (N)")).filter((tm, i, array) => array.indexOf(tm) === i);
if (!tierUniqueCompatibleTms.length) { if (!tierUniqueCompatibleTms.length) {
return null; return null;
@ -1003,17 +1003,17 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator {
const evolutions = pokemonEvolutions[p.species.speciesId]; const evolutions = pokemonEvolutions[p.species.speciesId];
return evolutions.filter(e => e.item !== EvolutionItem.NONE && (e.evoFormKey === null || (e.preFormKey || "") === p.getFormKey()) && (!e.condition || e.condition.predicate(p))); return evolutions.filter(e => e.item !== EvolutionItem.NONE && (e.evoFormKey === null || (e.preFormKey || "") === p.getFormKey()) && (!e.condition || e.condition.predicate(p)));
}).flat(), }).flat(),
party.filter(p => p.isFusion() && pokemonEvolutions.hasOwnProperty(p.fusionSpecies.speciesId)).map(p => { party.filter(p => p.isFusion() && p.fusionSpecies && pokemonEvolutions.hasOwnProperty(p.fusionSpecies.speciesId)).map(p => {
const evolutions = pokemonEvolutions[p.fusionSpecies.speciesId]; const evolutions = pokemonEvolutions[p.fusionSpecies!.speciesId];
return evolutions.filter(e => e.item !== EvolutionItem.NONE && (e.evoFormKey === null || (e.preFormKey || "") === p.getFusionFormKey()) && (!e.condition || e.condition.predicate(p))); return evolutions.filter(e => e.item !== EvolutionItem.NONE && (e.evoFormKey === null || (e.preFormKey || "") === p.getFusionFormKey()) && (!e.condition || e.condition.predicate(p)));
}).flat() }).flat()
].flat().flatMap(e => e.item).filter(i => (i > 50) === rare); ].flat().flatMap(e => e.item).filter(i => (!!i && i > 50) === rare);
if (!evolutionItemPool.length) { if (!evolutionItemPool.length) {
return null; return null;
} }
return new EvolutionItemModifierType(evolutionItemPool[Utils.randSeedInt(evolutionItemPool.length)]); return new EvolutionItemModifierType(evolutionItemPool[Utils.randSeedInt(evolutionItemPool.length)]!); // TODO: is the bang correct?
}); });
} }
} }
@ -1156,7 +1156,7 @@ class WeightedModifierType {
constructor(modifierTypeFunc: ModifierTypeFunc, weight: integer | WeightedModifierTypeWeightFunc, maxWeight?: integer) { constructor(modifierTypeFunc: ModifierTypeFunc, weight: integer | WeightedModifierTypeWeightFunc, maxWeight?: integer) {
this.modifierType = modifierTypeFunc(); this.modifierType = modifierTypeFunc();
this.modifierType.id = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifierTypeFunc); this.modifierType.id = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifierTypeFunc)!; // TODO: is this bang correct?
this.weight = weight; this.weight = weight;
this.maxWeight = maxWeight || (!(weight instanceof Function) ? weight : 0); this.maxWeight = maxWeight || (!(weight instanceof Function) ? weight : 0);
} }
@ -1410,7 +1410,7 @@ export const modifierTypes = {
VOUCHER_PLUS: () => new AddVoucherModifierType(VoucherType.PLUS, 1), VOUCHER_PLUS: () => new AddVoucherModifierType(VoucherType.PLUS, 1),
VOUCHER_PREMIUM: () => new AddVoucherModifierType(VoucherType.PREMIUM, 1), VOUCHER_PREMIUM: () => new AddVoucherModifierType(VoucherType.PREMIUM, 1),
GOLDEN_POKEBALL: () => new ModifierType("modifierType:ModifierType.GOLDEN_POKEBALL", "pb_gold", (type, _args) => new Modifiers.ExtraModifierModifier(type), null, "pb_bounce_1"), GOLDEN_POKEBALL: () => new ModifierType("modifierType:ModifierType.GOLDEN_POKEBALL", "pb_gold", (type, _args) => new Modifiers.ExtraModifierModifier(type), undefined, "pb_bounce_1"),
ENEMY_DAMAGE_BOOSTER: () => new ModifierType("modifierType:ModifierType.ENEMY_DAMAGE_BOOSTER", "wl_item_drop", (type, _args) => new Modifiers.EnemyDamageBoosterModifier(type, 5)), ENEMY_DAMAGE_BOOSTER: () => new ModifierType("modifierType:ModifierType.ENEMY_DAMAGE_BOOSTER", "wl_item_drop", (type, _args) => new Modifiers.EnemyDamageBoosterModifier(type, 5)),
ENEMY_DAMAGE_REDUCTION: () => new ModifierType("modifierType:ModifierType.ENEMY_DAMAGE_REDUCTION", "wl_guard_spec", (type, _args) => new Modifiers.EnemyDamageReducerModifier(type, 2.5)), ENEMY_DAMAGE_REDUCTION: () => new ModifierType("modifierType:ModifierType.ENEMY_DAMAGE_REDUCTION", "wl_guard_spec", (type, _args) => new Modifiers.EnemyDamageReducerModifier(type, 2.5)),
@ -1451,11 +1451,11 @@ const modifierPool: ModifierPool = {
return thresholdPartyMemberCount; return thresholdPartyMemberCount;
}, 3), }, 3),
new WeightedModifierType(modifierTypes.ETHER, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.ETHER, (party: Pokemon[]) => {
const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.getMoveset().filter(m => m.ppUsed && (m.getMovePp() - m.ppUsed) <= 5).length).length, 3); const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.getMoveset().filter(m => m?.ppUsed && (m.getMovePp() - m.ppUsed) <= 5).length).length, 3);
return thresholdPartyMemberCount * 3; return thresholdPartyMemberCount * 3;
}, 9), }, 9),
new WeightedModifierType(modifierTypes.MAX_ETHER, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.MAX_ETHER, (party: Pokemon[]) => {
const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.getMoveset().filter(m => m.ppUsed && (m.getMovePp() - m.ppUsed) <= 5).length).length, 3); const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.getMoveset().filter(m => m?.ppUsed && (m.getMovePp() - m.ppUsed) <= 5).length).length, 3);
return thresholdPartyMemberCount; return thresholdPartyMemberCount;
}, 3), }, 3),
new WeightedModifierType(modifierTypes.LURE, 2), new WeightedModifierType(modifierTypes.LURE, 2),
@ -1471,7 +1471,7 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.FULL_HEAL, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.FULL_HEAL, (party: Pokemon[]) => {
const statusEffectPartyMemberCount = Math.min(party.filter(p => p.hp && !!p.status && !p.getHeldItems().some(i => { const statusEffectPartyMemberCount = Math.min(party.filter(p => p.hp && !!p.status && !p.getHeldItems().some(i => {
if (i instanceof Modifiers.TurnStatusEffectModifier) { if (i instanceof Modifiers.TurnStatusEffectModifier) {
return (i as Modifiers.TurnStatusEffectModifier).getStatusEffect() === p.status.effect; return (i as Modifiers.TurnStatusEffectModifier).getStatusEffect() === p.status?.effect;
} }
return false; return false;
})).length, 3); })).length, 3);
@ -1499,7 +1499,7 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.FULL_RESTORE, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.FULL_RESTORE, (party: Pokemon[]) => {
const statusEffectPartyMemberCount = Math.min(party.filter(p => p.hp && !!p.status && !p.getHeldItems().some(i => { const statusEffectPartyMemberCount = Math.min(party.filter(p => p.hp && !!p.status && !p.getHeldItems().some(i => {
if (i instanceof Modifiers.TurnStatusEffectModifier) { if (i instanceof Modifiers.TurnStatusEffectModifier) {
return (i as Modifiers.TurnStatusEffectModifier).getStatusEffect() === p.status.effect; return (i as Modifiers.TurnStatusEffectModifier).getStatusEffect() === p.status?.effect;
} }
return false; return false;
})).length, 3); })).length, 3);
@ -1507,11 +1507,11 @@ const modifierPool: ModifierPool = {
return thresholdPartyMemberCount; return thresholdPartyMemberCount;
}, 3), }, 3),
new WeightedModifierType(modifierTypes.ELIXIR, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.ELIXIR, (party: Pokemon[]) => {
const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.getMoveset().filter(m => m.ppUsed && (m.getMovePp() - m.ppUsed) <= 5).length).length, 3); const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.getMoveset().filter(m => m?.ppUsed && (m.getMovePp() - m.ppUsed) <= 5).length).length, 3);
return thresholdPartyMemberCount * 3; return thresholdPartyMemberCount * 3;
}, 9), }, 9),
new WeightedModifierType(modifierTypes.MAX_ELIXIR, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.MAX_ELIXIR, (party: Pokemon[]) => {
const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.getMoveset().filter(m => m.ppUsed && (m.getMovePp() - m.ppUsed) <= 5).length).length, 3); const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.getMoveset().filter(m => m?.ppUsed && (m.getMovePp() - m.ppUsed) <= 5).length).length, 3);
return thresholdPartyMemberCount; return thresholdPartyMemberCount;
}, 3), }, 3),
new WeightedModifierType(modifierTypes.DIRE_HIT, 4), new WeightedModifierType(modifierTypes.DIRE_HIT, 4),
@ -1555,19 +1555,19 @@ const modifierPool: ModifierPool = {
const checkedAbilities = [Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.TOXIC_BOOST, Abilities.POISON_HEAL, Abilities.MAGIC_GUARD]; const checkedAbilities = [Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.TOXIC_BOOST, Abilities.POISON_HEAL, Abilities.MAGIC_GUARD];
const checkedMoves = [Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT]; const checkedMoves = [Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT];
// If a party member doesn't already have one of these two orbs and has one of the above moves or abilities, the orb can appear // If a party member doesn't already have one of these two orbs and has one of the above moves or abilities, the orb can appear
return party.some(p => !p.getHeldItems().some(i => i instanceof Modifiers.TurnStatusEffectModifier) && (checkedAbilities.some(a => p.hasAbility(a, false, true)) || p.getMoveset(true).some(m => checkedMoves.includes(m.moveId)))) ? 10 : 0; return party.some(p => !p.getHeldItems().some(i => i instanceof Modifiers.TurnStatusEffectModifier) && (checkedAbilities.some(a => p.hasAbility(a, false, true)) || p.getMoveset(true).some(m => m && checkedMoves.includes(m.moveId)))) ? 10 : 0;
}, 10), }, 10),
new WeightedModifierType(modifierTypes.FLAME_ORB, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.FLAME_ORB, (party: Pokemon[]) => {
const checkedAbilities = [Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.FLARE_BOOST, Abilities.MAGIC_GUARD]; const checkedAbilities = [Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.FLARE_BOOST, Abilities.MAGIC_GUARD];
const checkedMoves = [Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT]; const checkedMoves = [Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT];
// If a party member doesn't already have one of these two orbs and has one of the above moves or abilities, the orb can appear // If a party member doesn't already have one of these two orbs and has one of the above moves or abilities, the orb can appear
return party.some(p => !p.getHeldItems().some(i => i instanceof Modifiers.TurnStatusEffectModifier) && (checkedAbilities.some(a => p.hasAbility(a, false, true)) || p.getMoveset(true).some(m => checkedMoves.includes(m.moveId)))) ? 10 : 0; return party.some(p => !p.getHeldItems().some(i => i instanceof Modifiers.TurnStatusEffectModifier) && (checkedAbilities.some(a => p.hasAbility(a, false, true)) || p.getMoveset(true).some(m => m && checkedMoves.includes(m.moveId)))) ? 10 : 0;
}, 10), }, 10),
new WeightedModifierType(modifierTypes.WHITE_HERB, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.WHITE_HERB, (party: Pokemon[]) => {
const checkedAbilities = [Abilities.WEAK_ARMOR, Abilities.CONTRARY, Abilities.MOODY, Abilities.ANGER_SHELL, Abilities.COMPETITIVE, Abilities.DEFIANT]; const checkedAbilities = [Abilities.WEAK_ARMOR, Abilities.CONTRARY, Abilities.MOODY, Abilities.ANGER_SHELL, Abilities.COMPETITIVE, Abilities.DEFIANT];
const weightMultiplier = party.filter( const weightMultiplier = party.filter(
p => !p.getHeldItems().some(i => i instanceof Modifiers.PokemonResetNegativeStatStageModifier && i.stackCount >= i.getMaxHeldItemCount(p)) && p => !p.getHeldItems().some(i => i instanceof Modifiers.PokemonResetNegativeStatStageModifier && i.stackCount >= i.getMaxHeldItemCount(p)) &&
(checkedAbilities.some(a => p.hasAbility(a, false, true)) || p.getMoveset(true).some(m => selfStatLowerMoves.includes(m.moveId)))).length; (checkedAbilities.some(a => p.hasAbility(a, false, true)) || p.getMoveset(true).some(m => m && selfStatLowerMoves.includes(m.moveId)))).length;
// If a party member has one of the above moves or abilities and doesn't have max herbs, the herb will appear more frequently // If a party member has one of the above moves or abilities and doesn't have max herbs, the herb will appear more frequently
return 0 * (weightMultiplier ? 2 : 1) + (weightMultiplier ? weightMultiplier * 0 : 0); return 0 * (weightMultiplier ? 2 : 1) + (weightMultiplier ? weightMultiplier * 0 : 0);
}, 10), }, 10),
@ -1722,10 +1722,10 @@ const enemyBuffModifierPool: ModifierPool = {
].map(m => { ].map(m => {
m.setTier(ModifierTier.ULTRA); return m; m.setTier(ModifierTier.ULTRA); return m;
}), }),
[ModifierTier.ROGUE]: [ ].map(m => { [ModifierTier.ROGUE]: [ ].map((m: WeightedModifierType) => {
m.setTier(ModifierTier.ROGUE); return m; m.setTier(ModifierTier.ROGUE); return m;
}), }),
[ModifierTier.MASTER]: [ ].map(m => { [ModifierTier.MASTER]: [ ].map((m: WeightedModifierType) => {
m.setTier(ModifierTier.MASTER); return m; m.setTier(ModifierTier.MASTER); return m;
}) })
}; };
@ -1770,7 +1770,7 @@ const dailyStarterModifierPool: ModifierPool = {
export function getModifierType(modifierTypeFunc: ModifierTypeFunc): ModifierType { export function getModifierType(modifierTypeFunc: ModifierTypeFunc): ModifierType {
const modifierType = modifierTypeFunc(); const modifierType = modifierTypeFunc();
if (!modifierType.id) { if (!modifierType.id) {
modifierType.id = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifierTypeFunc); modifierType.id = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifierTypeFunc)!; // TODO: is this bang correct?
} }
return modifierType; return modifierType;
} }
@ -1893,12 +1893,14 @@ export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemo
const options: ModifierTypeOption[] = []; const options: ModifierTypeOption[] = [];
const retryCount = Math.min(count * 5, 50); const retryCount = Math.min(count * 5, 50);
new Array(count).fill(0).map((_, i) => { new Array(count).fill(0).map((_, i) => {
let candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, modifierTiers?.length > i ? modifierTiers[i] : undefined); let candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, modifierTiers && modifierTiers.length > i ? modifierTiers[i] : undefined);
let r = 0; let r = 0;
while (options.length && ++r < retryCount && options.filter(o => o.type.name === candidate.type.name || o.type.group === candidate.type.group).length) { while (options.length && ++r < retryCount && options.filter(o => o.type?.name === candidate?.type?.name || o.type?.group === candidate?.type?.group).length) {
candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, candidate.type.tier, candidate.upgradeCount); candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, candidate?.type?.tier, candidate?.upgradeCount);
} }
if (candidate) {
options.push(candidate); options.push(candidate);
}
}); });
// OVERRIDE IF NECESSARY // OVERRIDE IF NECESSARY
@ -1950,21 +1952,21 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base
export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: Modifiers.PersistentModifier[], scene: BattleScene): Modifiers.EnemyPersistentModifier { export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: Modifiers.PersistentModifier[], scene: BattleScene): Modifiers.EnemyPersistentModifier {
const tierStackCount = tier === ModifierTier.ULTRA ? 5 : tier === ModifierTier.GREAT ? 3 : 1; const tierStackCount = tier === ModifierTier.ULTRA ? 5 : tier === ModifierTier.GREAT ? 3 : 1;
const retryCount = 50; const retryCount = 50;
let candidate = getNewModifierTypeOption(null, ModifierPoolType.ENEMY_BUFF, tier); let candidate = getNewModifierTypeOption([], ModifierPoolType.ENEMY_BUFF, tier);
let r = 0; let r = 0;
let matchingModifier: Modifiers.PersistentModifier; let matchingModifier: Modifiers.PersistentModifier | undefined;
while (++r < retryCount && (matchingModifier = enemyModifiers.find(m => m.type.id === candidate.type.id)) && matchingModifier.getMaxStackCount(scene) < matchingModifier.stackCount + (r < 10 ? tierStackCount : 1)) { while (++r < retryCount && (matchingModifier = enemyModifiers.find(m => m.type.id === candidate?.type?.id)) && matchingModifier.getMaxStackCount(scene) < matchingModifier.stackCount + (r < 10 ? tierStackCount : 1)) {
candidate = getNewModifierTypeOption(null, ModifierPoolType.ENEMY_BUFF, tier); candidate = getNewModifierTypeOption([], ModifierPoolType.ENEMY_BUFF, tier);
} }
const modifier = candidate.type.newModifier() as Modifiers.EnemyPersistentModifier; const modifier = candidate?.type?.newModifier() as Modifiers.EnemyPersistentModifier;
modifier.stackCount = tierStackCount; modifier.stackCount = tierStackCount;
return modifier; return modifier;
} }
export function getEnemyModifierTypesForWave(waveIndex: integer, count: integer, party: EnemyPokemon[], poolType: ModifierPoolType.WILD | ModifierPoolType.TRAINER, upgradeChance: integer = 0): PokemonHeldItemModifierType[] { export function getEnemyModifierTypesForWave(waveIndex: integer, count: integer, party: EnemyPokemon[], poolType: ModifierPoolType.WILD | ModifierPoolType.TRAINER, upgradeChance: integer = 0): PokemonHeldItemModifierType[] {
const ret = new Array(count).fill(0).map(() => getNewModifierTypeOption(party, poolType, undefined, upgradeChance && !Utils.randSeedInt(upgradeChance) ? 1 : 0).type as PokemonHeldItemModifierType); const ret = new Array(count).fill(0).map(() => getNewModifierTypeOption(party, poolType, undefined, upgradeChance && !Utils.randSeedInt(upgradeChance) ? 1 : 0)?.type as PokemonHeldItemModifierType);
if (!(waveIndex % 1000)) { if (!(waveIndex % 1000)) {
ret.push(getModifierType(modifierTypes.MINI_BLACK_HOLE) as PokemonHeldItemModifierType); ret.push(getModifierType(modifierTypes.MINI_BLACK_HOLE) as PokemonHeldItemModifierType);
} }
@ -1977,7 +1979,7 @@ export function getDailyRunStarterModifiers(party: PlayerPokemon[]): Modifiers.P
for (let m = 0; m < 3; m++) { for (let m = 0; m < 3; m++) {
const tierValue = Utils.randSeedInt(64); const tierValue = Utils.randSeedInt(64);
const tier = tierValue > 25 ? ModifierTier.COMMON : tierValue > 12 ? ModifierTier.GREAT : tierValue > 4 ? ModifierTier.ULTRA : tierValue ? ModifierTier.ROGUE : ModifierTier.MASTER; const tier = tierValue > 25 ? ModifierTier.COMMON : tierValue > 12 ? ModifierTier.GREAT : tierValue > 4 ? ModifierTier.ULTRA : tierValue ? ModifierTier.ROGUE : ModifierTier.MASTER;
const modifier = getNewModifierTypeOption(party, ModifierPoolType.DAILY_STARTER, tier).type.newModifier(p) as Modifiers.PokemonHeldItemModifier; const modifier = getNewModifierTypeOption(party, ModifierPoolType.DAILY_STARTER, tier)?.type?.newModifier(p) as Modifiers.PokemonHeldItemModifier;
ret.push(modifier); ret.push(modifier);
} }
} }
@ -1985,7 +1987,7 @@ export function getDailyRunStarterModifiers(party: PlayerPokemon[]): Modifiers.P
return ret; return ret;
} }
function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, tier?: ModifierTier, upgradeCount?: integer, retryCount: integer = 0): ModifierTypeOption { function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, tier?: ModifierTier, upgradeCount?: integer, retryCount: integer = 0): ModifierTypeOption | null {
const player = !poolType; const player = !poolType;
const pool = getModifierPoolForType(poolType); const pool = getModifierPoolForType(poolType);
let thresholds: object; let thresholds: object;
@ -2052,7 +2054,7 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType,
const tierThresholds = Object.keys(thresholds[tier]); const tierThresholds = Object.keys(thresholds[tier]);
const totalWeight = parseInt(tierThresholds[tierThresholds.length - 1]); const totalWeight = parseInt(tierThresholds[tierThresholds.length - 1]);
const value = Utils.randSeedInt(totalWeight); const value = Utils.randSeedInt(totalWeight);
let index: integer; let index: integer | undefined;
for (const t of tierThresholds) { for (const t of tierThresholds) {
const threshold = parseInt(t); const threshold = parseInt(t);
if (value < threshold) { if (value < threshold) {
@ -2068,7 +2070,7 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType,
if (player) { if (player) {
console.log(index, ignoredPoolIndexes[tier].filter(i => i <= index).length, ignoredPoolIndexes[tier]); console.log(index, ignoredPoolIndexes[tier].filter(i => i <= index).length, ignoredPoolIndexes[tier]);
} }
let modifierType: ModifierType = (pool[tier][index]).modifierType; let modifierType: ModifierType | null = (pool[tier][index]).modifierType;
if (modifierType instanceof ModifierTypeGenerator) { if (modifierType instanceof ModifierTypeGenerator) {
modifierType = (modifierType as ModifierTypeGenerator).generateType(party); modifierType = (modifierType as ModifierTypeGenerator).generateType(party);
if (modifierType === null) { if (modifierType === null) {
@ -2081,7 +2083,7 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType,
console.log(modifierType, !player ? "(enemy)" : ""); console.log(modifierType, !player ? "(enemy)" : "");
return new ModifierTypeOption(modifierType as ModifierType, upgradeCount); return new ModifierTypeOption(modifierType as ModifierType, upgradeCount!); // TODO: is this bang correct?
} }
export function getDefaultModifierTypeForTier(tier: ModifierTier): ModifierType { export function getDefaultModifierTypeForTier(tier: ModifierTier): ModifierType {
@ -2093,7 +2095,7 @@ export function getDefaultModifierTypeForTier(tier: ModifierTier): ModifierType
} }
export class ModifierTypeOption { export class ModifierTypeOption {
public type: ModifierType; public type: ModifierType | null;
public upgradeCount: integer; public upgradeCount: integer;
public cost: integer; public cost: integer;

View File

@ -33,16 +33,16 @@ export type ModifierPredicate = (modifier: Modifier) => boolean;
const iconOverflowIndex = 24; const iconOverflowIndex = 24;
export const modifierSortFunc = (a: Modifier, b: Modifier) => { export const modifierSortFunc = (a: Modifier, b: Modifier): number => {
const itemNameMatch = a.type.name.localeCompare(b.type.name); const itemNameMatch = a.type.name.localeCompare(b.type.name);
const typeNameMatch = a.constructor.name.localeCompare(b.constructor.name); const typeNameMatch = a.constructor.name.localeCompare(b.constructor.name);
const aId = a instanceof PokemonHeldItemModifier ? a.pokemonId : 4294967295; const aId = a instanceof PokemonHeldItemModifier && a.pokemonId ? a.pokemonId : 4294967295;
const bId = b instanceof PokemonHeldItemModifier ? b.pokemonId : 4294967295; const bId = b instanceof PokemonHeldItemModifier && b.pokemonId ? b.pokemonId : 4294967295;
//First sort by pokemonID //First sort by pokemonID
if (aId < bId) { if (aId < bId) {
return 1; return 1;
} else if (aId>bId) { } else if (aId > bId) {
return -1; return -1;
} else if (aId === bId) { } else if (aId === bId) {
//Then sort by item type //Then sort by item type
@ -52,6 +52,8 @@ export const modifierSortFunc = (a: Modifier, b: Modifier) => {
} else { } else {
return typeNameMatch; return typeNameMatch;
} }
} else {
return 0;
} }
}; };
@ -150,7 +152,7 @@ export abstract class PersistentModifier extends Modifier {
public stackCount: integer; public stackCount: integer;
public virtualStackCount: integer; public virtualStackCount: integer;
constructor(type: ModifierType, stackCount: integer) { constructor(type: ModifierType, stackCount?: integer) {
super(type); super(type);
this.stackCount = stackCount === undefined ? 1 : stackCount; this.stackCount = stackCount === undefined ? 1 : stackCount;
this.virtualStackCount = 0; this.virtualStackCount = 0;
@ -221,7 +223,7 @@ export abstract class PersistentModifier extends Modifier {
return container; return container;
} }
getIconStackText(scene: BattleScene, virtual?: boolean): Phaser.GameObjects.BitmapText { getIconStackText(scene: BattleScene, virtual?: boolean): Phaser.GameObjects.BitmapText | null {
if (this.getMaxStackCount(scene) === 1 || (virtual && !this.virtualStackCount)) { if (this.getMaxStackCount(scene) === 1 || (virtual && !this.virtualStackCount)) {
return null; return null;
} }
@ -295,7 +297,7 @@ export abstract class LapsingPersistentModifier extends PersistentModifier {
constructor(type: ModifierTypes.ModifierType, battlesLeft?: integer, stackCount?: integer) { constructor(type: ModifierTypes.ModifierType, battlesLeft?: integer, stackCount?: integer) {
super(type, stackCount); super(type, stackCount);
this.battlesLeft = battlesLeft; this.battlesLeft = battlesLeft!; // TODO: is this bang correct?
} }
lapse(args: any[]): boolean { lapse(args: any[]): boolean {
@ -306,7 +308,7 @@ export abstract class LapsingPersistentModifier extends PersistentModifier {
const container = super.getIcon(scene); const container = super.getIcon(scene);
const battleCountText = addTextObject(scene, 27, 0, this.battlesLeft.toString(), TextStyle.PARTY, { fontSize: "66px", color: "#f89890" }); const battleCountText = addTextObject(scene, 27, 0, this.battlesLeft.toString(), TextStyle.PARTY, { fontSize: "66px", color: "#f89890" });
battleCountText.setShadow(0, 0, null); battleCountText.setShadow(0, 0);
battleCountText.setStroke("#984038", 16); battleCountText.setStroke("#984038", 16);
battleCountText.setOrigin(1, 0); battleCountText.setOrigin(1, 0);
container.add(battleCountText); container.add(battleCountText);
@ -472,7 +474,7 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier {
public pokemonId: integer; public pokemonId: integer;
readonly isTransferrable: boolean = true; readonly isTransferrable: boolean = true;
constructor(type: ModifierType, pokemonId: integer, stackCount: integer) { constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
super(type, stackCount); super(type, stackCount);
this.pokemonId = pokemonId; this.pokemonId = pokemonId;
@ -489,11 +491,11 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier {
} }
shouldApply(args: any[]): boolean { shouldApply(args: any[]): boolean {
return super.shouldApply(args) && args.length && args[0] instanceof Pokemon && (this.pokemonId === -1 || (args[0] as Pokemon).id === this.pokemonId); return super.shouldApply(args) && args.length !== 0 && args[0] instanceof Pokemon && (this.pokemonId === -1 || (args[0] as Pokemon).id === this.pokemonId);
} }
isIconVisible(scene: BattleScene): boolean { isIconVisible(scene: BattleScene): boolean {
return this.getPokemon(scene).isOnField(); return !!(this.getPokemon(scene)?.isOnField());
} }
getIcon(scene: BattleScene, forSummary?: boolean): Phaser.GameObjects.Container { getIcon(scene: BattleScene, forSummary?: boolean): Phaser.GameObjects.Container {
@ -501,9 +503,10 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier {
if (!forSummary) { if (!forSummary) {
const pokemon = this.getPokemon(scene); const pokemon = this.getPokemon(scene);
if (pokemon) {
const pokemonIcon = scene.addPokemonIcon(pokemon, -2, 10, 0, 0.5); const pokemonIcon = scene.addPokemonIcon(pokemon, -2, 10, 0, 0.5);
container.add(pokemonIcon); container.add(pokemonIcon);
}
const item = scene.add.sprite(16, this.virtualStackCount ? 8 : 16, "items"); const item = scene.add.sprite(16, this.virtualStackCount ? 8 : 16, "items");
item.setScale(0.5); item.setScale(0.5);
@ -527,8 +530,8 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier {
return container; return container;
} }
getPokemon(scene: BattleScene): Pokemon { getPokemon(scene: BattleScene): Pokemon | undefined {
return scene.getPokemonById(this.pokemonId); return this.pokemonId ? scene.getPokemonById(this.pokemonId) ?? undefined : undefined;
} }
getScoreMultiplier(): number { getScoreMultiplier(): number {
@ -562,7 +565,7 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier {
return this.getMaxHeldItemCount(pokemon); return this.getMaxHeldItemCount(pokemon);
} }
abstract getMaxHeldItemCount(pokemon: Pokemon): integer; abstract getMaxHeldItemCount(pokemon?: Pokemon): integer;
} }
export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModifier { export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModifier {
@ -572,7 +575,7 @@ export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModi
constructor(type: ModifierTypes.ModifierType, pokemonId: integer, battlesLeft?: integer, stackCount?: integer) { constructor(type: ModifierTypes.ModifierType, pokemonId: integer, battlesLeft?: integer, stackCount?: integer) {
super(type, pokemonId, stackCount); super(type, pokemonId, stackCount);
this.battlesLeft = battlesLeft; this.battlesLeft = battlesLeft!; // TODO: is this bang correct?
} }
lapse(args: any[]): boolean { lapse(args: any[]): boolean {
@ -582,9 +585,9 @@ export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModi
getIcon(scene: BattleScene, forSummary?: boolean): Phaser.GameObjects.Container { getIcon(scene: BattleScene, forSummary?: boolean): Phaser.GameObjects.Container {
const container = super.getIcon(scene, forSummary); const container = super.getIcon(scene, forSummary);
if (this.getPokemon(scene).isPlayer()) { if (this.getPokemon(scene)?.isPlayer()) {
const battleCountText = addTextObject(scene, 27, 0, this.battlesLeft.toString(), TextStyle.PARTY, { fontSize: "66px", color: "#f89890" }); const battleCountText = addTextObject(scene, 27, 0, this.battlesLeft.toString(), TextStyle.PARTY, { fontSize: "66px", color: "#f89890" });
battleCountText.setShadow(0, 0, null); battleCountText.setShadow(0, 0);
battleCountText.setStroke("#984038", 16); battleCountText.setStroke("#984038", 16);
battleCountText.setOrigin(1, 0); battleCountText.setOrigin(1, 0);
container.add(battleCountText); container.add(battleCountText);
@ -1442,7 +1445,7 @@ export abstract class ConsumablePokemonModifier extends ConsumableModifier {
} }
shouldApply(args: any[]): boolean { shouldApply(args: any[]): boolean {
return args.length && args[0] instanceof PlayerPokemon && (this.pokemonId === -1 || (args[0] as PlayerPokemon).id === this.pokemonId); return args.length !== 0 && args[0] instanceof PlayerPokemon && (this.pokemonId === -1 || (args[0] as PlayerPokemon).id === this.pokemonId);
} }
getPokemon(scene: BattleScene) { getPokemon(scene: BattleScene) {
@ -1519,7 +1522,7 @@ export class PokemonPpRestoreModifier extends ConsumablePokemonMoveModifier {
apply(args: any[]): boolean { apply(args: any[]): boolean {
const pokemon = args[0] as Pokemon; const pokemon = args[0] as Pokemon;
const move = pokemon.getMoveset()[this.moveIndex]; const move = pokemon.getMoveset()[this.moveIndex]!; //TODO: is the bang correct?
move.ppUsed = this.restorePoints > -1 ? Math.max(move.ppUsed - this.restorePoints, 0) : 0; move.ppUsed = this.restorePoints > -1 ? Math.max(move.ppUsed - this.restorePoints, 0) : 0;
return true; return true;
@ -1538,7 +1541,7 @@ export class PokemonAllMovePpRestoreModifier extends ConsumablePokemonModifier {
apply(args: any[]): boolean { apply(args: any[]): boolean {
const pokemon = args[0] as Pokemon; const pokemon = args[0] as Pokemon;
for (const move of pokemon.getMoveset()) { for (const move of pokemon.getMoveset()) {
move.ppUsed = this.restorePoints > -1 ? Math.max(move.ppUsed - this.restorePoints, 0) : 0; move!.ppUsed = this.restorePoints > -1 ? Math.max(move!.ppUsed - this.restorePoints, 0) : 0; // TODO: are those bangs correct?
} }
return true; return true;
@ -1556,7 +1559,7 @@ export class PokemonPpUpModifier extends ConsumablePokemonMoveModifier {
apply(args: any[]): boolean { apply(args: any[]): boolean {
const pokemon = args[0] as Pokemon; const pokemon = args[0] as Pokemon;
const move = pokemon.getMoveset()[this.moveIndex]; const move = pokemon.getMoveset()[this.moveIndex]!; // TODO: is the bang correct?
move.ppUp = Math.min(move.ppUp + this.upPoints, 3); move.ppUp = Math.min(move.ppUp + this.upPoints, 3);
return true; return true;
@ -1658,7 +1661,7 @@ export class EvolutionItemModifier extends ConsumablePokemonModifier {
: null; : null;
if (!matchingEvolution && pokemon.isFusion()) { if (!matchingEvolution && pokemon.isFusion()) {
matchingEvolution = pokemonEvolutions[pokemon.fusionSpecies.speciesId].find(e => e.item === (this.type as ModifierTypes.EvolutionItemModifierType).evolutionItem matchingEvolution = pokemonEvolutions[pokemon.fusionSpecies!.speciesId].find(e => e.item === (this.type as ModifierTypes.EvolutionItemModifierType).evolutionItem // TODO: is the bang correct?
&& (e.evoFormKey === null || (e.preFormKey || "") === pokemon.getFusionFormKey()) && (e.evoFormKey === null || (e.preFormKey || "") === pokemon.getFusionFormKey())
&& (!e.condition || e.condition.predicate(pokemon))); && (!e.condition || e.condition.predicate(pokemon)));
if (matchingEvolution) { if (matchingEvolution) {
@ -2135,7 +2138,7 @@ export class MoneyInterestModifier extends PersistentModifier {
const userLocale = navigator.language || "en-US"; const userLocale = navigator.language || "en-US";
const formattedMoneyAmount = interestAmount.toLocaleString(userLocale); const formattedMoneyAmount = interestAmount.toLocaleString(userLocale);
const message = i18next.t("modifier:moneyInterestApply", { moneyAmount: formattedMoneyAmount, typeName: this.type.name }); const message = i18next.t("modifier:moneyInterestApply", { moneyAmount: formattedMoneyAmount, typeName: this.type.name });
scene.queueMessage(message, null, true); scene.queueMessage(message, undefined, true);
return true; return true;
} }
@ -2290,7 +2293,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier {
const transferredModifierTypes: ModifierTypes.ModifierType[] = []; const transferredModifierTypes: ModifierTypes.ModifierType[] = [];
const itemModifiers = pokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier const itemModifiers = pokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === targetPokemon.id && m.isTransferrable, targetPokemon.isPlayer()) as PokemonHeldItemModifier[]; && m.pokemonId === targetPokemon.id && m.isTransferrable, targetPokemon.isPlayer()) as PokemonHeldItemModifier[];
let highestItemTier = itemModifiers.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier, highestTier), 0); let highestItemTier = itemModifiers.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is this bang correct?
let tierItemModifiers = itemModifiers.filter(m => m.type.getOrInferTier(poolType) === highestItemTier); let tierItemModifiers = itemModifiers.filter(m => m.type.getOrInferTier(poolType) === highestItemTier);
const heldItemTransferPromises: Promise<void>[] = []; const heldItemTransferPromises: Promise<void>[] = [];
@ -2782,8 +2785,8 @@ export function overrideHeldItems(scene: BattleScene, pokemon: Pokemon, player:
const modifierType: ModifierType = modifierTypes[itemName](); // we retrieve the item in the list const modifierType: ModifierType = modifierTypes[itemName](); // we retrieve the item in the list
let itemModifier: PokemonHeldItemModifier; let itemModifier: PokemonHeldItemModifier;
if (modifierType instanceof ModifierTypes.ModifierTypeGenerator) { if (modifierType instanceof ModifierTypes.ModifierTypeGenerator) {
const pregenArgs = "type" in item ? [item.type] : null; const pregenArgs = "type" in item ? [item.type] : undefined;
itemModifier = modifierType.generateType(null, pregenArgs).withIdFromFunc(modifierTypes[itemName]).newModifier(pokemon) as PokemonHeldItemModifier; itemModifier = modifierType.generateType([], pregenArgs)?.withIdFromFunc(modifierTypes[itemName]).newModifier(pokemon) as PokemonHeldItemModifier;
} else { } else {
itemModifier = modifierType.withIdFromFunc(modifierTypes[itemName]).newModifier(pokemon) as PokemonHeldItemModifier; itemModifier = modifierType.withIdFromFunc(modifierTypes[itemName]).newModifier(pokemon) as PokemonHeldItemModifier;
} }

View File

@ -48,9 +48,9 @@ class DefaultOverrides {
readonly BATTLE_TYPE_OVERRIDE: "double" | "single" | null = null; readonly BATTLE_TYPE_OVERRIDE: "double" | "single" | null = null;
readonly STARTING_WAVE_OVERRIDE: integer = 0; readonly STARTING_WAVE_OVERRIDE: integer = 0;
readonly STARTING_BIOME_OVERRIDE: Biome = Biome.TOWN; readonly STARTING_BIOME_OVERRIDE: Biome = Biome.TOWN;
readonly ARENA_TINT_OVERRIDE: TimeOfDay = null; readonly ARENA_TINT_OVERRIDE: TimeOfDay | null = null;
/** Multiplies XP gained by this value including 0. Set to null to ignore the override */ /** Multiplies XP gained by this value including 0. Set to null to ignore the override */
readonly XP_MULTIPLIER_OVERRIDE: number = null; readonly XP_MULTIPLIER_OVERRIDE: number | null = null;
/** default 1000 */ /** default 1000 */
readonly STARTING_MONEY_OVERRIDE: integer = 0; readonly STARTING_MONEY_OVERRIDE: integer = 0;
/** Sets all shop item prices to 0 */ /** Sets all shop item prices to 0 */
@ -96,7 +96,7 @@ class DefaultOverrides {
readonly ABILITY_OVERRIDE: Abilities = Abilities.NONE; readonly ABILITY_OVERRIDE: Abilities = Abilities.NONE;
readonly PASSIVE_ABILITY_OVERRIDE: Abilities = Abilities.NONE; readonly PASSIVE_ABILITY_OVERRIDE: Abilities = Abilities.NONE;
readonly STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE; readonly STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE;
readonly GENDER_OVERRIDE: Gender = null; readonly GENDER_OVERRIDE: Gender | null = null;
readonly MOVESET_OVERRIDE: Array<Moves> = []; readonly MOVESET_OVERRIDE: Array<Moves> = [];
readonly SHINY_OVERRIDE: boolean = false; readonly SHINY_OVERRIDE: boolean = false;
readonly VARIANT_OVERRIDE: Variant = 0; readonly VARIANT_OVERRIDE: Variant = 0;
@ -109,7 +109,7 @@ class DefaultOverrides {
readonly OPP_ABILITY_OVERRIDE: Abilities = Abilities.NONE; readonly OPP_ABILITY_OVERRIDE: Abilities = Abilities.NONE;
readonly OPP_PASSIVE_ABILITY_OVERRIDE: Abilities = Abilities.NONE; readonly OPP_PASSIVE_ABILITY_OVERRIDE: Abilities = Abilities.NONE;
readonly OPP_STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE; readonly OPP_STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE;
readonly OPP_GENDER_OVERRIDE: Gender = null; readonly OPP_GENDER_OVERRIDE: Gender | null = null;
readonly OPP_MOVESET_OVERRIDE: Array<Moves> = []; readonly OPP_MOVESET_OVERRIDE: Array<Moves> = [];
readonly OPP_SHINY_OVERRIDE: boolean = false; readonly OPP_SHINY_OVERRIDE: boolean = false;
readonly OPP_VARIANT_OVERRIDE: Variant = 0; readonly OPP_VARIANT_OVERRIDE: Variant = 0;
@ -119,9 +119,9 @@ class DefaultOverrides {
// EGG OVERRIDES // EGG OVERRIDES
// ------------- // -------------
readonly EGG_IMMEDIATE_HATCH_OVERRIDE: boolean = false; readonly EGG_IMMEDIATE_HATCH_OVERRIDE: boolean = false;
readonly EGG_TIER_OVERRIDE: EggTier = null; readonly EGG_TIER_OVERRIDE: EggTier | null = null;
readonly EGG_SHINY_OVERRIDE: boolean = false; readonly EGG_SHINY_OVERRIDE: boolean = false;
readonly EGG_VARIANT_OVERRIDE: VariantTier = null; readonly EGG_VARIANT_OVERRIDE: VariantTier | null = null;
readonly EGG_FREE_GACHA_PULLS_OVERRIDE: boolean = false; readonly EGG_FREE_GACHA_PULLS_OVERRIDE: boolean = false;
readonly EGG_GACHA_PULL_COUNT_OVERRIDE: number = 0; readonly EGG_GACHA_PULL_COUNT_OVERRIDE: number = 0;

File diff suppressed because it is too large Load Diff

View File

@ -377,7 +377,7 @@ export default class SpritePipeline extends FieldSpritePipeline {
this.set2f("texSize", sprite.texture.source[0].width, sprite.texture.source[0].height); this.set2f("texSize", sprite.texture.source[0].width, sprite.texture.source[0].height);
this.set1f("yOffset", sprite.height - sprite.frame.height * (isEntityObj ? sprite.parentContainer.scale : sprite.scale)); this.set1f("yOffset", sprite.height - sprite.frame.height * (isEntityObj ? sprite.parentContainer.scale : sprite.scale));
this.set4fv("tone", tone); this.set4fv("tone", tone);
this.bindTexture(this.game.textures.get("tera").source[0].glTexture, 1); this.bindTexture(this.game.textures.get("tera").source[0].glTexture!, 1); // TODO: is this bang correct?
if ((gameObject.scene as BattleScene).fusionPaletteSwaps) { if ((gameObject.scene as BattleScene).fusionPaletteSwaps) {
const spriteColors = ((ignoreOverride && data["spriteColorsBase"]) || data["spriteColors"] || []) as number[][]; const spriteColors = ((ignoreOverride && data["spriteColorsBase"]) || data["spriteColors"] || []) as number[][];

View File

@ -157,7 +157,7 @@ export async function initI18n(): Promise<void> {
postProcess: ["korean-postposition"], postProcess: ["korean-postposition"],
}); });
await initFonts(localStorage.getItem("prLang")); await initFonts(localStorage.getItem("prLang") ?? undefined);
} }
export default i18next; export default i18next;

View File

@ -6,6 +6,7 @@ import * as Utils from "../utils";
import { PlayerGender } from "#enums/player-gender"; import { PlayerGender } from "#enums/player-gender";
import { ParseKeys } from "i18next"; import { ParseKeys } from "i18next";
import { Challenge, FreshStartChallenge, SingleGenerationChallenge, SingleTypeChallenge } from "#app/data/challenge.js"; import { Challenge, FreshStartChallenge, SingleGenerationChallenge, SingleTypeChallenge } from "#app/data/challenge.js";
import { ConditionFn } from "#app/@types/common.js";
export enum AchvTier { export enum AchvTier {
COMMON, COMMON,
@ -27,9 +28,9 @@ export class Achv {
public hasParent: boolean; public hasParent: boolean;
public parentId: string; public parentId: string;
private conditionFunc: (scene: BattleScene, args: any[]) => boolean; private conditionFunc: ConditionFn | undefined;
constructor(localizationKey:string, name: string, description: string, iconImage: string, score: integer, conditionFunc?: (scene: BattleScene, args: any[]) => boolean) { constructor(localizationKey:string, name: string, description: string, iconImage: string, score: integer, conditionFunc?: ConditionFn) {
this.name = name; this.name = name;
this.description = description; this.description = description;
this.iconImage = iconImage; this.iconImage = iconImage;
@ -63,7 +64,7 @@ export class Achv {
return this; return this;
} }
validate(scene: BattleScene, args: any[]): boolean { validate(scene: BattleScene, args?: any[]): boolean {
return !this.conditionFunc || this.conditionFunc(scene, args); return !this.conditionFunc || this.conditionFunc(scene, args);
} }

View File

@ -6,15 +6,15 @@ import { Terrain } from "#app/data/terrain.js";
export default class ArenaData { export default class ArenaData {
public biome: Biome; public biome: Biome;
public weather: Weather; public weather: Weather | null;
public terrain: Terrain; public terrain: Terrain | null;
public tags: ArenaTag[]; public tags: ArenaTag[];
constructor(source: Arena | any) { constructor(source: Arena | any) {
const sourceArena = source instanceof Arena ? source as Arena : null; const sourceArena = source instanceof Arena ? source as Arena : null;
this.biome = sourceArena ? sourceArena.biomeType : source.biome; this.biome = sourceArena ? sourceArena.biomeType : source.biome;
this.weather = sourceArena ? sourceArena.weather : source.weather ? new Weather(source.weather.weatherType, source.weather.turnsLeft) : undefined; this.weather = sourceArena ? sourceArena.weather : source.weather ? new Weather(source.weather.weatherType, source.weather.turnsLeft) : null;
this.terrain = sourceArena ? sourceArena.terrain : source.terrain ? new Terrain(source.terrain.terrainType, source.terrain.turnsLeft) : undefined; this.terrain = sourceArena ? sourceArena.terrain : source.terrain ? new Terrain(source.terrain.terrainType, source.terrain.turnsLeft) : null;
this.tags = sourceArena ? sourceArena.tags : []; this.tags = sourceArena ? sourceArena.tags : [];
} }
} }

View File

@ -41,6 +41,8 @@ import { Moves } from "#enums/moves";
import { PlayerGender } from "#enums/player-gender"; import { PlayerGender } from "#enums/player-gender";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { applyChallenges, ChallengeType } from "#app/data/challenge.js"; import { applyChallenges, ChallengeType } from "#app/data/challenge.js";
import { WeatherType } from "#app/enums/weather-type.js";
import { TerrainType } from "#app/data/terrain.js";
export const defaultStarterSpecies: Species[] = [ export const defaultStarterSpecies: Species[] = [
Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE, Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE,
@ -78,7 +80,7 @@ export function getDataTypeKey(dataType: GameDataType, slotId: integer = 0): str
export function encrypt(data: string, bypassLogin: boolean): string { export function encrypt(data: string, bypassLogin: boolean): string {
return (bypassLogin return (bypassLogin
? (data: string) => btoa(data) ? (data: string) => btoa(data)
: (data: string) => AES.encrypt(data, saveKey))(data); : (data: string) => AES.encrypt(data, saveKey))(data) as unknown as string; // TODO: is this correct?
} }
export function decrypt(data: string, bypassLogin: boolean): string { export function decrypt(data: string, bypassLogin: boolean): string {
@ -230,7 +232,7 @@ export class StarterPrefs {
} }
export interface StarterDataEntry { export interface StarterDataEntry {
moveset: StarterMoveset | StarterFormMoveData; moveset: StarterMoveset | StarterFormMoveData | null;
eggMoves: integer; eggMoves: integer;
candyCount: integer; candyCount: integer;
friendship: integer; friendship: integer;
@ -279,7 +281,7 @@ export class GameData {
public gender: PlayerGender; public gender: PlayerGender;
public dexData: DexData; public dexData: DexData;
private defaultDexData: DexData; private defaultDexData: DexData | null;
public starterData: StarterData; public starterData: StarterData;
@ -352,7 +354,7 @@ export class GameData {
const maxIntAttrValue = 0x80000000; const maxIntAttrValue = 0x80000000;
const systemData = JSON.stringify(data, (k: any, v: any) => typeof v === "bigint" ? v <= maxIntAttrValue ? Number(v) : v.toString() : v); const systemData = JSON.stringify(data, (k: any, v: any) => typeof v === "bigint" ? v <= maxIntAttrValue ? Number(v) : v.toString() : v);
localStorage.setItem(`data_${loggedInUser.username}`, encrypt(systemData, bypassLogin)); localStorage.setItem(`data_${loggedInUser?.username}`, encrypt(systemData, bypassLogin));
if (!bypassLogin) { if (!bypassLogin) {
Utils.apiPost(`savedata/system/update?clientSessionId=${clientSessionId}`, systemData, undefined, true) Utils.apiPost(`savedata/system/update?clientSessionId=${clientSessionId}`, systemData, undefined, true)
@ -384,7 +386,7 @@ export class GameData {
return new Promise<boolean>(resolve => { return new Promise<boolean>(resolve => {
console.log("Client Session:", clientSessionId); console.log("Client Session:", clientSessionId);
if (bypassLogin && !localStorage.getItem(`data_${loggedInUser.username}`)) { if (bypassLogin && !localStorage.getItem(`data_${loggedInUser?.username}`)) {
return resolve(false); return resolve(false);
} }
@ -404,11 +406,11 @@ export class GameData {
return resolve(false); return resolve(false);
} }
const cachedSystem = localStorage.getItem(`data_${loggedInUser.username}`); const cachedSystem = localStorage.getItem(`data_${loggedInUser?.username}`);
this.initSystem(response, cachedSystem ? AES.decrypt(cachedSystem, saveKey).toString(enc.Utf8) : null).then(resolve); this.initSystem(response, cachedSystem ? AES.decrypt(cachedSystem, saveKey).toString(enc.Utf8) : undefined).then(resolve);
}); });
} else { } else {
this.initSystem(decrypt(localStorage.getItem(`data_${loggedInUser.username}`), bypassLogin)).then(resolve); this.initSystem(decrypt(localStorage.getItem(`data_${loggedInUser?.username}`)!, bypassLogin)).then(resolve); // TODO: is this bang correct?
} }
}); });
} }
@ -431,7 +433,7 @@ export class GameData {
console.debug(systemData); console.debug(systemData);
localStorage.setItem(`data_${loggedInUser.username}`, encrypt(systemDataStr, bypassLogin)); localStorage.setItem(`data_${loggedInUser?.username}`, encrypt(systemDataStr, bypassLogin));
/*const versions = [ this.scene.game.config.gameVersion, data.gameVersion || '0.0.0' ]; /*const versions = [ this.scene.game.config.gameVersion, data.gameVersion || '0.0.0' ];
@ -606,9 +608,9 @@ export class GameData {
if (bypassLogin) { if (bypassLogin) {
return; return;
} }
localStorage.removeItem(`data_${loggedInUser.username}`); localStorage.removeItem(`data_${loggedInUser?.username}`);
for (let s = 0; s < 5; s++) { for (let s = 0; s < 5; s++) {
localStorage.removeItem(`sessionData${s ? s : ""}_${loggedInUser.username}`); localStorage.removeItem(`sessionData${s ? s : ""}_${loggedInUser?.username}`);
} }
} }
@ -621,7 +623,7 @@ export class GameData {
public saveSetting(setting: string, valueIndex: integer): boolean { public saveSetting(setting: string, valueIndex: integer): boolean {
let settings: object = {}; let settings: object = {};
if (localStorage.hasOwnProperty("settings")) { if (localStorage.hasOwnProperty("settings")) {
settings = JSON.parse(localStorage.getItem("settings")); settings = JSON.parse(localStorage.getItem("settings")!); // TODO: is this bang correct?
} }
setSetting(this.scene, setting, valueIndex); setSetting(this.scene, setting, valueIndex);
@ -644,7 +646,7 @@ export class GameData {
const key = deviceName.toLowerCase(); // Convert the gamepad name to lowercase to use as a key const key = deviceName.toLowerCase(); // Convert the gamepad name to lowercase to use as a key
let mappingConfigs: object = {}; // Initialize an empty object to hold the mapping configurations let mappingConfigs: object = {}; // Initialize an empty object to hold the mapping configurations
if (localStorage.hasOwnProperty("mappingConfigs")) {// Check if 'mappingConfigs' exists in localStorage if (localStorage.hasOwnProperty("mappingConfigs")) {// Check if 'mappingConfigs' exists in localStorage
mappingConfigs = JSON.parse(localStorage.getItem("mappingConfigs")); mappingConfigs = JSON.parse(localStorage.getItem("mappingConfigs")!); // TODO: is this bang correct?
} // Parse the existing 'mappingConfigs' from localStorage } // Parse the existing 'mappingConfigs' from localStorage
if (!mappingConfigs[key]) { if (!mappingConfigs[key]) {
mappingConfigs[key] = {}; mappingConfigs[key] = {};
@ -669,7 +671,7 @@ export class GameData {
return false; return false;
} // If 'mappingConfigs' does not exist, return false } // If 'mappingConfigs' does not exist, return false
const mappingConfigs = JSON.parse(localStorage.getItem("mappingConfigs")); // Parse the existing 'mappingConfigs' from localStorage const mappingConfigs = JSON.parse(localStorage.getItem("mappingConfigs")!); // Parse the existing 'mappingConfigs' from localStorage // TODO: is this bang correct?
for (const key of Object.keys(mappingConfigs)) {// Iterate over the keys of the mapping configurations for (const key of Object.keys(mappingConfigs)) {// Iterate over the keys of the mapping configurations
this.scene.inputController.injectConfig(key, mappingConfigs[key]); this.scene.inputController.injectConfig(key, mappingConfigs[key]);
@ -684,6 +686,7 @@ export class GameData {
} // If 'mappingConfigs' does not exist, return false } // If 'mappingConfigs' does not exist, return false
localStorage.removeItem("mappingConfigs"); localStorage.removeItem("mappingConfigs");
this.scene.inputController.resetConfigs(); this.scene.inputController.resetConfigs();
return true; // TODO: is `true` the correct return value?
} }
/** /**
@ -703,7 +706,7 @@ export class GameData {
let settingsControls: object = {}; // Initialize an empty object to hold the gamepad settings let settingsControls: object = {}; // Initialize an empty object to hold the gamepad settings
if (localStorage.hasOwnProperty(localStoragePropertyName)) { // Check if 'settingsControls' exists in localStorage if (localStorage.hasOwnProperty(localStoragePropertyName)) { // Check if 'settingsControls' exists in localStorage
settingsControls = JSON.parse(localStorage.getItem(localStoragePropertyName)); // Parse the existing 'settingsControls' from localStorage settingsControls = JSON.parse(localStorage.getItem(localStoragePropertyName)!); // Parse the existing 'settingsControls' from localStorage // TODO: is this bang correct?
} }
if (device === Device.GAMEPAD) { if (device === Device.GAMEPAD) {
@ -734,11 +737,13 @@ export class GameData {
return false; return false;
} }
const settings = JSON.parse(localStorage.getItem("settings")); const settings = JSON.parse(localStorage.getItem("settings")!); // TODO: is this bang correct?
for (const setting of Object.keys(settings)) { for (const setting of Object.keys(settings)) {
setSetting(this.scene, setting, settings[setting]); setSetting(this.scene, setting, settings[setting]);
} }
return true; // TODO: is `true` the correct return value?
} }
private loadGamepadSettings(): boolean { private loadGamepadSettings(): boolean {
@ -747,18 +752,20 @@ export class GameData {
if (!localStorage.hasOwnProperty("settingsGamepad")) { if (!localStorage.hasOwnProperty("settingsGamepad")) {
return false; return false;
} }
const settingsGamepad = JSON.parse(localStorage.getItem("settingsGamepad")); const settingsGamepad = JSON.parse(localStorage.getItem("settingsGamepad")!); // TODO: is this bang correct?
for (const setting of Object.keys(settingsGamepad)) { for (const setting of Object.keys(settingsGamepad)) {
setSettingGamepad(this.scene, setting as SettingGamepad, settingsGamepad[setting]); setSettingGamepad(this.scene, setting as SettingGamepad, settingsGamepad[setting]);
} }
return true; // TODO: is `true` the correct return value?
} }
public saveTutorialFlag(tutorial: Tutorial, flag: boolean): boolean { public saveTutorialFlag(tutorial: Tutorial, flag: boolean): boolean {
const key = getDataTypeKey(GameDataType.TUTORIALS); const key = getDataTypeKey(GameDataType.TUTORIALS);
let tutorials: object = {}; let tutorials: object = {};
if (localStorage.hasOwnProperty(key)) { if (localStorage.hasOwnProperty(key)) {
tutorials = JSON.parse(localStorage.getItem(key)); tutorials = JSON.parse(localStorage.getItem(key)!); // TODO: is this bang correct?
} }
Object.keys(Tutorial).map(t => t as Tutorial).forEach(t => { Object.keys(Tutorial).map(t => t as Tutorial).forEach(t => {
@ -784,7 +791,7 @@ export class GameData {
return ret; return ret;
} }
const tutorials = JSON.parse(localStorage.getItem(key)); const tutorials = JSON.parse(localStorage.getItem(key)!); // TODO: is this bang correct?
for (const tutorial of Object.keys(tutorials)) { for (const tutorial of Object.keys(tutorials)) {
ret[tutorial] = tutorials[tutorial]; ret[tutorial] = tutorials[tutorial];
@ -812,7 +819,7 @@ export class GameData {
return ret; return ret;
} }
const dialogues = JSON.parse(localStorage.getItem(key)); const dialogues = JSON.parse(localStorage.getItem(key)!); // TODO: is this bang correct?
for (const dialogue of Object.keys(dialogues)) { for (const dialogue of Object.keys(dialogues)) {
ret[dialogue] = dialogues[dialogue]; ret[dialogue] = dialogues[dialogue];
@ -843,7 +850,7 @@ export class GameData {
} as SessionSaveData; } as SessionSaveData;
} }
getSession(slotId: integer): Promise<SessionSaveData> { getSession(slotId: integer): Promise<SessionSaveData | null> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
if (slotId < 0) { if (slotId < 0) {
return resolve(null); return resolve(null);
@ -858,7 +865,7 @@ export class GameData {
} }
}; };
if (!bypassLogin && !localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}`)) { if (!bypassLogin && !localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`)) {
Utils.apiFetch(`savedata/session/get?slot=${slotId}&clientSessionId=${clientSessionId}`, true) Utils.apiFetch(`savedata/session/get?slot=${slotId}&clientSessionId=${clientSessionId}`, true)
.then(response => response.text()) .then(response => response.text())
.then(async response => { .then(async response => {
@ -867,12 +874,12 @@ export class GameData {
return resolve(null); return resolve(null);
} }
localStorage.setItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}`, encrypt(response, bypassLogin)); localStorage.setItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`, encrypt(response, bypassLogin));
await handleSessionData(response); await handleSessionData(response);
}); });
} else { } else {
const sessionData = localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}`); const sessionData = localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`);
if (sessionData) { if (sessionData) {
await handleSessionData(decrypt(sessionData, bypassLogin)); await handleSessionData(decrypt(sessionData, bypassLogin));
} else { } else {
@ -934,7 +941,7 @@ export class GameData {
const battleType = sessionData.battleType || 0; const battleType = sessionData.battleType || 0;
const trainerConfig = sessionData.trainer ? trainerConfigs[sessionData.trainer.trainerType] : null; const trainerConfig = sessionData.trainer ? trainerConfigs[sessionData.trainer.trainerType] : null;
const battle = scene.newBattle(sessionData.waveIndex, battleType, sessionData.trainer, battleType === BattleType.TRAINER ? trainerConfig?.doubleOnly || sessionData.trainer?.variant === TrainerVariant.DOUBLE : sessionData.enemyParty.length > 1); const battle = scene.newBattle(sessionData.waveIndex, battleType, sessionData.trainer, battleType === BattleType.TRAINER ? trainerConfig?.doubleOnly || sessionData.trainer?.variant === TrainerVariant.DOUBLE : sessionData.enemyParty.length > 1)!; // TODO: is this bang correct?
battle.enemyLevels = sessionData.enemyParty.map(p => p.level); battle.enemyLevels = sessionData.enemyParty.map(p => p.level);
scene.arena.init(); scene.arena.init();
@ -950,10 +957,10 @@ export class GameData {
}); });
scene.arena.weather = sessionData.arena.weather; scene.arena.weather = sessionData.arena.weather;
scene.arena.eventTarget.dispatchEvent(new WeatherChangedEvent(null, scene.arena.weather?.weatherType, scene.arena.weather?.turnsLeft)); scene.arena.eventTarget.dispatchEvent(new WeatherChangedEvent(WeatherType.NONE, scene.arena.weather?.weatherType!, scene.arena.weather?.turnsLeft!)); // TODO: is this bang correct?
scene.arena.terrain = sessionData.arena.terrain; scene.arena.terrain = sessionData.arena.terrain;
scene.arena.eventTarget.dispatchEvent(new TerrainChangedEvent(null, scene.arena.terrain?.terrainType, scene.arena.terrain?.turnsLeft)); scene.arena.eventTarget.dispatchEvent(new TerrainChangedEvent(TerrainType.NONE, scene.arena.terrain?.terrainType!, scene.arena.terrain?.turnsLeft!)); // TODO: is this bang correct?
// TODO // TODO
//scene.arena.tags = sessionData.arena.tags; //scene.arena.tags = sessionData.arena.tags;
@ -983,7 +990,7 @@ export class GameData {
initSessionFromData(sessionData); initSessionFromData(sessionData);
} else { } else {
this.getSession(slotId) this.getSession(slotId)
.then(data => initSessionFromData(data)) .then(data => data && initSessionFromData(data))
.catch(err => { .catch(err => {
reject(err); reject(err);
return; return;
@ -999,7 +1006,7 @@ export class GameData {
deleteSession(slotId: integer): Promise<boolean> { deleteSession(slotId: integer): Promise<boolean> {
return new Promise<boolean>(resolve => { return new Promise<boolean>(resolve => {
if (bypassLogin) { if (bypassLogin) {
localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser.username}`); localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser?.username}`);
return resolve(true); return resolve(true);
} }
@ -1009,8 +1016,8 @@ export class GameData {
} }
Utils.apiFetch(`savedata/session/delete?slot=${slotId}&clientSessionId=${clientSessionId}`, true).then(response => { Utils.apiFetch(`savedata/session/delete?slot=${slotId}&clientSessionId=${clientSessionId}`, true).then(response => {
if (response.ok) { if (response.ok) {
loggedInUser.lastSessionSlot = -1; loggedInUser!.lastSessionSlot = -1; // TODO: is the bang correct?
localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser.username}`); localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser?.username}`);
resolve(true); resolve(true);
} }
return response.text(); return response.text();
@ -1040,7 +1047,7 @@ export class GameData {
if (sessionData.gameMode === GameModes.DAILY) { if (sessionData.gameMode === GameModes.DAILY) {
if (localStorage.hasOwnProperty("daily")) { if (localStorage.hasOwnProperty("daily")) {
daily = JSON.parse(atob(localStorage.getItem("daily"))); daily = JSON.parse(atob(localStorage.getItem("daily")!)); // TODO: is this bang correct?
if (daily.includes(seed)) { if (daily.includes(seed)) {
return resolve(false); return resolve(false);
} else { } else {
@ -1062,7 +1069,7 @@ export class GameData {
tryClearSession(scene: BattleScene, slotId: integer): Promise<[success: boolean, newClear: boolean]> { tryClearSession(scene: BattleScene, slotId: integer): Promise<[success: boolean, newClear: boolean]> {
return new Promise<[boolean, boolean]>(resolve => { return new Promise<[boolean, boolean]>(resolve => {
if (bypassLogin) { if (bypassLogin) {
localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}`); localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`);
return resolve([true, true]); return resolve([true, true]);
} }
@ -1073,8 +1080,8 @@ export class GameData {
const sessionData = this.getSessionSaveData(scene); const sessionData = this.getSessionSaveData(scene);
Utils.apiPost(`savedata/session/clear?slot=${slotId}&trainerId=${this.trainerId}&secretId=${this.secretId}&clientSessionId=${clientSessionId}`, JSON.stringify(sessionData), undefined, true).then(response => { Utils.apiPost(`savedata/session/clear?slot=${slotId}&trainerId=${this.trainerId}&secretId=${this.secretId}&clientSessionId=${clientSessionId}`, JSON.stringify(sessionData), undefined, true).then(response => {
if (response.ok) { if (response.ok) {
loggedInUser.lastSessionSlot = -1; loggedInUser!.lastSessionSlot = -1; // TODO: is the bang correct?
localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser.username}`); localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser?.username}`);
} }
return response.json(); return response.json();
}).then(jsonResponse => { }).then(jsonResponse => {
@ -1161,10 +1168,10 @@ export class GameData {
if (sync) { if (sync) {
this.scene.ui.savingIcon.show(); this.scene.ui.savingIcon.show();
} }
const sessionData = useCachedSession ? this.parseSessionData(decrypt(localStorage.getItem(`sessionData${scene.sessionSlotId ? scene.sessionSlotId : ""}_${loggedInUser.username}`), bypassLogin)) : this.getSessionSaveData(scene); const sessionData = useCachedSession ? this.parseSessionData(decrypt(localStorage.getItem(`sessionData${scene.sessionSlotId ? scene.sessionSlotId : ""}_${loggedInUser?.username}`)!, bypassLogin)) : this.getSessionSaveData(scene); // TODO: is this bang correct?
const maxIntAttrValue = 0x80000000; const maxIntAttrValue = 0x80000000;
const systemData = useCachedSystem ? this.parseSystemData(decrypt(localStorage.getItem(`data_${loggedInUser.username}`), bypassLogin)) : this.getSystemSaveData(); const systemData = useCachedSystem ? this.parseSystemData(decrypt(localStorage.getItem(`data_${loggedInUser?.username}`)!, bypassLogin)) : this.getSystemSaveData(); // TODO: is this bang correct?
const request = { const request = {
system: systemData, system: systemData,
@ -1173,9 +1180,9 @@ export class GameData {
clientSessionId: clientSessionId clientSessionId: clientSessionId
}; };
localStorage.setItem(`data_${loggedInUser.username}`, encrypt(JSON.stringify(systemData, (k: any, v: any) => typeof v === "bigint" ? v <= maxIntAttrValue ? Number(v) : v.toString() : v), bypassLogin)); localStorage.setItem(`data_${loggedInUser?.username}`, encrypt(JSON.stringify(systemData, (k: any, v: any) => typeof v === "bigint" ? v <= maxIntAttrValue ? Number(v) : v.toString() : v), bypassLogin));
localStorage.setItem(`sessionData${scene.sessionSlotId ? scene.sessionSlotId : ""}_${loggedInUser.username}`, encrypt(JSON.stringify(sessionData), bypassLogin)); localStorage.setItem(`sessionData${scene.sessionSlotId ? scene.sessionSlotId : ""}_${loggedInUser?.username}`, encrypt(JSON.stringify(sessionData), bypassLogin));
console.debug("Session data saved"); console.debug("Session data saved");
@ -1212,7 +1219,7 @@ export class GameData {
public tryExportData(dataType: GameDataType, slotId: integer = 0): Promise<boolean> { public tryExportData(dataType: GameDataType, slotId: integer = 0): Promise<boolean> {
return new Promise<boolean>(resolve => { return new Promise<boolean>(resolve => {
const dataKey: string = `${getDataTypeKey(dataType, slotId)}_${loggedInUser.username}`; const dataKey: string = `${getDataTypeKey(dataType, slotId)}_${loggedInUser?.username}`;
const handleData = (dataStr: string) => { const handleData = (dataStr: string) => {
switch (dataType) { switch (dataType) {
case GameDataType.SYSTEM: case GameDataType.SYSTEM:
@ -1251,7 +1258,7 @@ export class GameData {
} }
public importData(dataType: GameDataType, slotId: integer = 0): void { public importData(dataType: GameDataType, slotId: integer = 0): void {
const dataKey = `${getDataTypeKey(dataType, slotId)}_${loggedInUser.username}`; const dataKey = `${getDataTypeKey(dataType, slotId)}_${loggedInUser?.username}`;
let saveFile: any = document.getElementById("saveFile"); let saveFile: any = document.getElementById("saveFile");
if (saveFile) { if (saveFile) {
@ -1270,7 +1277,7 @@ export class GameData {
reader.onload = (_ => { reader.onload = (_ => {
return e => { return e => {
let dataName: string; let dataName: string;
let dataStr = AES.decrypt(e.target.result.toString(), saveKey).toString(enc.Utf8); let dataStr = AES.decrypt(e.target?.result?.toString()!, saveKey).toString(enc.Utf8); // TODO: is this bang correct?
let valid = false; let valid = false;
try { try {
dataName = GameDataType[dataType].toLowerCase(); dataName = GameDataType[dataType].toLowerCase();
@ -1293,10 +1300,11 @@ export class GameData {
console.error(ex); console.error(ex);
} }
const displayError = (error: string) => this.scene.ui.showText(error, null, () => this.scene.ui.showText(null, 0), Utils.fixedInt(1500)); const displayError = (error: string) => this.scene.ui.showText(error, null, () => this.scene.ui.showText("", 0), Utils.fixedInt(1500));
dataName = dataName!; // tell TS compiler that dataName is defined!
if (!valid) { if (!valid) {
return this.scene.ui.showText(`Your ${dataName} data could not be loaded. It may be corrupted.`, null, () => this.scene.ui.showText(null, 0), Utils.fixedInt(1500)); return this.scene.ui.showText(`Your ${dataName} data could not be loaded. It may be corrupted.`, null, () => this.scene.ui.showText("", 0), Utils.fixedInt(1500));
} }
this.scene.ui.showText(`Your ${dataName} data will be overridden and the page will reload. Proceed?`, null, () => { this.scene.ui.showText(`Your ${dataName} data will be overridden and the page will reload. Proceed?`, null, () => {
@ -1329,7 +1337,7 @@ export class GameData {
} }
}, () => { }, () => {
this.scene.ui.revertMode(); this.scene.ui.revertMode();
this.scene.ui.showText(null, 0); this.scene.ui.showText("", 0);
}, false, -98); }, false, -98);
}); });
}; };
@ -1652,7 +1660,7 @@ export class GameData {
return dexAttr & DexAttr.SHINY ? dexAttr & DexAttr.VARIANT_3 ? 3 : dexAttr & DexAttr.VARIANT_2 ? 2 : 1 : 0; return dexAttr & DexAttr.SHINY ? dexAttr & DexAttr.VARIANT_3 ? 3 : dexAttr & DexAttr.VARIANT_2 ? 2 : 1 : 0;
} }
getNaturesForAttr(natureAttr: integer): Nature[] { getNaturesForAttr(natureAttr: integer = 0): Nature[] {
const ret: Nature[] = []; const ret: Nature[] = [];
for (let n = 0; n < 25; n++) { for (let n = 0; n < 25; n++) {
if (natureAttr & (1 << (n + 1))) { if (natureAttr & (1 << (n + 1))) {
@ -1707,7 +1715,7 @@ export class GameData {
entry.hatchedCount = 0; entry.hatchedCount = 0;
} }
if (!entry.hasOwnProperty("natureAttr") || (entry.caughtAttr && !entry.natureAttr)) { if (!entry.hasOwnProperty("natureAttr") || (entry.caughtAttr && !entry.natureAttr)) {
entry.natureAttr = this.defaultDexData[k].natureAttr || (1 << Utils.randInt(25, 1)); entry.natureAttr = this.defaultDexData?.[k].natureAttr || (1 << Utils.randInt(25, 1));
} }
} }
} }

View File

@ -1,6 +1,6 @@
import BattleScene from "../battle-scene"; import BattleScene from "../battle-scene";
import { PersistentModifier } from "../modifier/modifier"; import { PersistentModifier } from "../modifier/modifier";
import { GeneratedPersistentModifierType, ModifierTypeGenerator, getModifierTypeFuncById } from "../modifier/modifier-type"; import { GeneratedPersistentModifierType, ModifierType, ModifierTypeGenerator, getModifierTypeFuncById } from "../modifier/modifier-type";
export default class ModifierData { export default class ModifierData {
private player: boolean; private player: boolean;
@ -27,14 +27,14 @@ export default class ModifierData {
this.className = sourceModifier ? sourceModifier.constructor.name : source.className; this.className = sourceModifier ? sourceModifier.constructor.name : source.className;
} }
toModifier(scene: BattleScene, constructor: any): PersistentModifier { toModifier(scene: BattleScene, constructor: any): PersistentModifier | null {
const typeFunc = getModifierTypeFuncById(this.typeId); const typeFunc = getModifierTypeFuncById(this.typeId);
if (!typeFunc) { if (!typeFunc) {
return null; return null;
} }
try { try {
let type = typeFunc(); let type: ModifierType | null = typeFunc();
type.id = this.typeId; type.id = this.typeId;
if (type instanceof ModifierTypeGenerator) { if (type instanceof ModifierTypeGenerator) {

View File

@ -33,8 +33,8 @@ export default class PokemonData {
public ivs: integer[]; public ivs: integer[];
public nature: Nature; public nature: Nature;
public natureOverride: Nature | -1; public natureOverride: Nature | -1;
public moveset: PokemonMove[]; public moveset: (PokemonMove | null)[];
public status: Status; public status: Status | null;
public friendship: integer; public friendship: integer;
public metLevel: integer; public metLevel: integer;
public metBiome: Biome | -1; public metBiome: Biome | -1;
@ -117,7 +117,7 @@ export default class PokemonData {
if (!forHistory) { if (!forHistory) {
this.status = source.status this.status = source.status
? new Status(source.status.effect, source.status.turnCount, source.status.cureTurn) ? new Status(source.status.effect, source.status.turnCount, source.status.cureTurn)
: undefined; : null;
} }
this.summonData = new PokemonSummonData(); this.summonData = new PokemonSummonData();

View File

@ -3,6 +3,7 @@ import i18next from "i18next";
import { Achv, AchvTier, achvs, getAchievementDescription } from "./achv"; import { Achv, AchvTier, achvs, getAchievementDescription } from "./achv";
import { PlayerGender } from "#enums/player-gender"; import { PlayerGender } from "#enums/player-gender";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { ConditionFn } from "#app/@types/common.js";
export enum VoucherType { export enum VoucherType {
REGULAR, REGULAR,
@ -16,15 +17,15 @@ export class Voucher {
public voucherType: VoucherType; public voucherType: VoucherType;
public description: string; public description: string;
private conditionFunc: (scene: BattleScene, args: any[]) => boolean; private conditionFunc: ConditionFn | undefined;
constructor(voucherType: VoucherType, description: string, conditionFunc?: (scene: BattleScene, args: any[]) => boolean) { constructor(voucherType: VoucherType, description: string, conditionFunc?: ConditionFn) {
this.description = description; this.description = description;
this.voucherType = voucherType; this.voucherType = voucherType;
this.conditionFunc = conditionFunc; this.conditionFunc = conditionFunc;
} }
validate(scene: BattleScene, args: any[]): boolean { validate(scene: BattleScene, args?: any[]): boolean {
return !this.conditionFunc || this.conditionFunc(scene, args); return !this.conditionFunc || this.conditionFunc(scene, args);
} }

View File

@ -46,12 +46,12 @@ describe("Abilities - BATTLE BOND", () => {
await game.startBattle([Species.MAGIKARP, Species.GRENINJA]); await game.startBattle([Species.MAGIKARP, Species.GRENINJA]);
const greninja = game.scene.getParty().find((p) => p.species.speciesId === Species.GRENINJA); const greninja = game.scene.getParty().find((p) => p.species.speciesId === Species.GRENINJA);
expect(greninja).not.toBe(undefined); expect(greninja).toBeDefined();
expect(greninja.formIndex).toBe(ashForm); expect(greninja!.formIndex).toBe(ashForm);
greninja.hp = 0; greninja!.hp = 0;
greninja.status = new Status(StatusEffect.FAINT); greninja!.status = new Status(StatusEffect.FAINT);
expect(greninja.isFainted()).toBe(true); expect(greninja!.isFainted()).toBe(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.doKillOpponents(); await game.doKillOpponents();
@ -59,7 +59,7 @@ describe("Abilities - BATTLE BOND", () => {
game.doSelectModifier(); game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase); await game.phaseInterceptor.to(QuietFormChangePhase);
expect(greninja.formIndex).toBe(baseForm); expect(greninja!.formIndex).toBe(baseForm);
}, },
TIMEOUT TIMEOUT
); );

View File

@ -47,11 +47,11 @@ describe("Abilities - DISGUISE", () => {
const mimikyu = game.scene.getParty().find((p) => p.species.speciesId === Species.MIMIKYU); const mimikyu = game.scene.getParty().find((p) => p.species.speciesId === Species.MIMIKYU);
expect(mimikyu).not.toBe(undefined); expect(mimikyu).not.toBe(undefined);
expect(mimikyu.formIndex).toBe(bustedForm); expect(mimikyu!.formIndex).toBe(bustedForm);
mimikyu.hp = 0; mimikyu!.hp = 0;
mimikyu.status = new Status(StatusEffect.FAINT); mimikyu!.status = new Status(StatusEffect.FAINT);
expect(mimikyu.isFainted()).toBe(true); expect(mimikyu!.isFainted()).toBe(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.doKillOpponents(); await game.doKillOpponents();
@ -59,7 +59,7 @@ describe("Abilities - DISGUISE", () => {
game.doSelectModifier(); game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase); await game.phaseInterceptor.to(QuietFormChangePhase);
expect(mimikyu.formIndex).toBe(baseForm); expect(mimikyu!.formIndex).toBe(baseForm);
}, },
TIMEOUT TIMEOUT
); );
@ -80,17 +80,17 @@ describe("Abilities - DISGUISE", () => {
await game.startBattle([Species.MIMIKYU]); await game.startBattle([Species.MIMIKYU]);
const mimikyu = game.scene.getPlayerPokemon(); const mimikyu = game.scene.getPlayerPokemon()!;
const damage = (Math.floor(mimikyu.getMaxHp()/8)); const damage = (Math.floor(mimikyu!.getMaxHp()/8));
expect(mimikyu).not.toBe(undefined); expect(mimikyu).not.toBe(undefined);
expect(mimikyu.formIndex).toBe(baseForm); expect(mimikyu!.formIndex).toBe(baseForm);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(mimikyu.formIndex).toBe(bustedForm); expect(mimikyu!.formIndex).toBe(bustedForm);
expect(game.scene.getEnemyPokemon().turnData.currDamageDealt).toBe(damage); expect(game.scene.getEnemyPokemon()!.turnData.currDamageDealt).toBe(damage);
}, },
TIMEOUT TIMEOUT
); );

View File

@ -38,7 +38,7 @@ describe("Abilities - Dry Skin", () => {
await game.startBattle(); await game.startBattle();
const enemy = game.scene.getEnemyPokemon(); const enemy = game.scene.getEnemyPokemon()!;
expect(enemy).not.toBe(undefined); expect(enemy).not.toBe(undefined);
// first turn // first turn
@ -59,7 +59,7 @@ describe("Abilities - Dry Skin", () => {
await game.startBattle(); await game.startBattle();
const enemy = game.scene.getEnemyPokemon(); const enemy = game.scene.getEnemyPokemon()!;
expect(enemy).not.toBe(undefined); expect(enemy).not.toBe(undefined);
enemy.hp = 1; enemy.hp = 1;
@ -82,7 +82,7 @@ describe("Abilities - Dry Skin", () => {
await game.startBattle(); await game.startBattle();
const enemy = game.scene.getEnemyPokemon(); const enemy = game.scene.getEnemyPokemon()!;
const initialHP = 1000; const initialHP = 1000;
enemy.hp = initialHP; enemy.hp = initialHP;
@ -108,7 +108,7 @@ describe("Abilities - Dry Skin", () => {
await game.startBattle(); await game.startBattle();
const enemy = game.scene.getEnemyPokemon(); const enemy = game.scene.getEnemyPokemon()!;
expect(enemy).not.toBe(undefined); expect(enemy).not.toBe(undefined);
enemy.hp = 1; enemy.hp = 1;
@ -123,7 +123,7 @@ describe("Abilities - Dry Skin", () => {
await game.startBattle(); await game.startBattle();
const enemy = game.scene.getEnemyPokemon(); const enemy = game.scene.getEnemyPokemon()!;
expect(enemy).not.toBe(undefined); expect(enemy).not.toBe(undefined);
enemy.hp = 1; enemy.hp = 1;
@ -139,7 +139,7 @@ describe("Abilities - Dry Skin", () => {
await game.startBattle(); await game.startBattle();
const enemy = game.scene.getEnemyPokemon(); const enemy = game.scene.getEnemyPokemon()!;
expect(enemy).not.toBe(undefined); expect(enemy).not.toBe(undefined);
enemy.hp = 1; enemy.hp = 1;

View File

@ -58,7 +58,7 @@ describe("Abilities - Gulp Missile", () => {
it("changes to Gulping Form if HP is over half when Surf or Dive is used", async () => { it("changes to Gulping Form if HP is over half when Surf or Dive is used", async () => {
await game.startBattle([Species.CRAMORANT]); await game.startBattle([Species.CRAMORANT]);
const cramorant = game.scene.getPlayerPokemon(); const cramorant = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.DIVE)); game.doAttack(getMovePosition(game.scene, 0, Moves.DIVE));
await game.toNextTurn(); await game.toNextTurn();
@ -72,7 +72,7 @@ describe("Abilities - Gulp Missile", () => {
it("changes to Gorging Form if HP is under half when Surf or Dive is used", async () => { it("changes to Gorging Form if HP is under half when Surf or Dive is used", async () => {
await game.startBattle([Species.CRAMORANT]); await game.startBattle([Species.CRAMORANT]);
const cramorant = game.scene.getPlayerPokemon(); const cramorant = game.scene.getPlayerPokemon()!;
vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.49); vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.49);
expect(cramorant.getHpRatio()).toBe(.49); expect(cramorant.getHpRatio()).toBe(.49);
@ -88,7 +88,7 @@ describe("Abilities - Gulp Missile", () => {
game.override.enemyMoveset(Array(4).fill(Moves.TACKLE)); game.override.enemyMoveset(Array(4).fill(Moves.TACKLE));
await game.startBattle([Species.CRAMORANT]); await game.startBattle([Species.CRAMORANT]);
const enemy = game.scene.getEnemyPokemon(); const enemy = game.scene.getEnemyPokemon()!;
vi.spyOn(enemy, "damageAndUpdate"); vi.spyOn(enemy, "damageAndUpdate");
game.doAttack(getMovePosition(game.scene, 0, Moves.SURF)); game.doAttack(getMovePosition(game.scene, 0, Moves.SURF));
@ -101,7 +101,7 @@ describe("Abilities - Gulp Missile", () => {
game.override.enemyMoveset(Array(4).fill(Moves.TAIL_WHIP)); game.override.enemyMoveset(Array(4).fill(Moves.TAIL_WHIP));
await game.startBattle([Species.CRAMORANT]); await game.startBattle([Species.CRAMORANT]);
const cramorant = game.scene.getPlayerPokemon(); const cramorant = game.scene.getPlayerPokemon()!;
vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.55); vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.55);
game.doAttack(getMovePosition(game.scene, 0, Moves.SURF)); game.doAttack(getMovePosition(game.scene, 0, Moves.SURF));
@ -120,8 +120,8 @@ describe("Abilities - Gulp Missile", () => {
game.override.enemyMoveset(Array(4).fill(Moves.TACKLE)); game.override.enemyMoveset(Array(4).fill(Moves.TACKLE));
await game.startBattle([Species.CRAMORANT]); await game.startBattle([Species.CRAMORANT]);
const cramorant = game.scene.getPlayerPokemon(); const cramorant = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon(); const enemy = game.scene.getEnemyPokemon()!;
vi.spyOn(enemy, "damageAndUpdate"); vi.spyOn(enemy, "damageAndUpdate");
vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.55); vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.55);
@ -144,8 +144,8 @@ describe("Abilities - Gulp Missile", () => {
game.override.enemyMoveset(Array(4).fill(Moves.TACKLE)); game.override.enemyMoveset(Array(4).fill(Moves.TACKLE));
await game.startBattle([Species.CRAMORANT]); await game.startBattle([Species.CRAMORANT]);
const cramorant = game.scene.getPlayerPokemon(); const cramorant = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon(); const enemy = game.scene.getEnemyPokemon()!;
vi.spyOn(enemy, "damageAndUpdate"); vi.spyOn(enemy, "damageAndUpdate");
vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.45); vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.45);
@ -159,7 +159,7 @@ describe("Abilities - Gulp Missile", () => {
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(enemy.damageAndUpdate).toHaveReturnedWith(getEffectDamage(enemy)); expect(enemy.damageAndUpdate).toHaveReturnedWith(getEffectDamage(enemy));
expect(enemy.status.effect).toBe(StatusEffect.PARALYSIS); expect(enemy.status?.effect).toBe(StatusEffect.PARALYSIS);
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_PIKACHU)).toBeUndefined(); expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_PIKACHU)).toBeUndefined();
expect(cramorant.formIndex).toBe(NORMAL_FORM); expect(cramorant.formIndex).toBe(NORMAL_FORM);
}); });
@ -172,7 +172,7 @@ describe("Abilities - Gulp Missile", () => {
.enemyLevel(5); .enemyLevel(5);
await game.startBattle([Species.CRAMORANT]); await game.startBattle([Species.CRAMORANT]);
const cramorant = game.scene.getPlayerPokemon(); const cramorant = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.DIVE)); game.doAttack(getMovePosition(game.scene, 0, Moves.DIVE));
await game.toNextTurn(); await game.toNextTurn();
@ -194,8 +194,8 @@ describe("Abilities - Gulp Missile", () => {
game.override.enemyMoveset(Array(4).fill(Moves.TACKLE)).enemyAbility(Abilities.MAGIC_GUARD); game.override.enemyMoveset(Array(4).fill(Moves.TACKLE)).enemyAbility(Abilities.MAGIC_GUARD);
await game.startBattle([Species.CRAMORANT]); await game.startBattle([Species.CRAMORANT]);
const cramorant = game.scene.getPlayerPokemon(); const cramorant = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon(); const enemy = game.scene.getEnemyPokemon()!;
vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.55); vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.55);
@ -218,7 +218,7 @@ describe("Abilities - Gulp Missile", () => {
game.override.enemyMoveset(Array(4).fill(Moves.GASTRO_ACID)); game.override.enemyMoveset(Array(4).fill(Moves.GASTRO_ACID));
await game.startBattle([Species.CRAMORANT]); await game.startBattle([Species.CRAMORANT]);
const cramorant = game.scene.getPlayerPokemon(); const cramorant = game.scene.getPlayerPokemon()!;
vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.55); vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.55);
game.doAttack(getMovePosition(game.scene, 0, Moves.SURF)); game.doAttack(getMovePosition(game.scene, 0, Moves.SURF));
@ -238,7 +238,7 @@ describe("Abilities - Gulp Missile", () => {
game.override.enemyMoveset(Array(4).fill(Moves.SKILL_SWAP)); game.override.enemyMoveset(Array(4).fill(Moves.SKILL_SWAP));
await game.startBattle([Species.CRAMORANT]); await game.startBattle([Species.CRAMORANT]);
const cramorant = game.scene.getPlayerPokemon(); const cramorant = game.scene.getPlayerPokemon()!;
vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.55); vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.55);
game.doAttack(getMovePosition(game.scene, 0, Moves.SURF)); game.doAttack(getMovePosition(game.scene, 0, Moves.SURF));
@ -261,6 +261,6 @@ describe("Abilities - Gulp Missile", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnStartPhase); await game.phaseInterceptor.to(TurnStartPhase);
expect(game.scene.getEnemyPokemon().hasAbility(Abilities.GULP_MISSILE)).toBe(false); expect(game.scene.getEnemyPokemon()?.hasAbility(Abilities.GULP_MISSILE)).toBe(false);
}); });
}); });

View File

@ -38,7 +38,7 @@ describe("Abilities - Hustle", () => {
it("increases the user's Attack stat by 50%", async () => { it("increases the user's Attack stat by 50%", async () => {
await game.startBattle([Species.PIKACHU]); await game.startBattle([Species.PIKACHU]);
const pikachu = game.scene.getPlayerPokemon(); const pikachu = game.scene.getPlayerPokemon()!;
const atk = pikachu.stats[Stat.ATK]; const atk = pikachu.stats[Stat.ATK];
vi.spyOn(pikachu, "getBattleStat"); vi.spyOn(pikachu, "getBattleStat");
@ -53,7 +53,7 @@ describe("Abilities - Hustle", () => {
it("lowers the accuracy of the user's physical moves by 20%", async () => { it("lowers the accuracy of the user's physical moves by 20%", async () => {
await game.startBattle([Species.PIKACHU]); await game.startBattle([Species.PIKACHU]);
const pikachu = game.scene.getPlayerPokemon(); const pikachu = game.scene.getPlayerPokemon()!;
vi.spyOn(pikachu, "getAccuracyMultiplier"); vi.spyOn(pikachu, "getAccuracyMultiplier");
@ -65,7 +65,7 @@ describe("Abilities - Hustle", () => {
it("does not affect non-physical moves", async () => { it("does not affect non-physical moves", async () => {
await game.startBattle([Species.PIKACHU]); await game.startBattle([Species.PIKACHU]);
const pikachu = game.scene.getPlayerPokemon(); const pikachu = game.scene.getPlayerPokemon()!;
const spatk = pikachu.stats[Stat.SPATK]; const spatk = pikachu.stats[Stat.SPATK];
vi.spyOn(pikachu, "getBattleStat"); vi.spyOn(pikachu, "getBattleStat");
@ -83,8 +83,8 @@ describe("Abilities - Hustle", () => {
game.override.enemyLevel(30); game.override.enemyLevel(30);
await game.startBattle([Species.PIKACHU]); await game.startBattle([Species.PIKACHU]);
const pikachu = game.scene.getPlayerPokemon(); const pikachu = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
vi.spyOn(pikachu, "getAccuracyMultiplier"); vi.spyOn(pikachu, "getAccuracyMultiplier");
vi.spyOn(allMoves[Moves.FISSURE], "calculateBattleAccuracy"); vi.spyOn(allMoves[Moves.FISSURE], "calculateBattleAccuracy");

View File

@ -40,11 +40,11 @@ describe("Abilities - Ice Face", () => {
await game.phaseInterceptor.to(MoveEndPhase); await game.phaseInterceptor.to(MoveEndPhase);
const eiscue = game.scene.getEnemyPokemon(); const eiscue = game.scene.getEnemyPokemon()!;
expect(eiscue.isFullHp()).toBe(true); expect(eiscue.isFullHp()).toBe(true);
expect(eiscue.formIndex).toBe(noiceForm); expect(eiscue.formIndex).toBe(noiceForm);
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBe(undefined); expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBeUndefined();
}); });
it("takes no damage from the first hit of multihit physical move and transforms to Noice", async () => { it("takes no damage from the first hit of multihit physical move and transforms to Noice", async () => {
@ -54,7 +54,7 @@ describe("Abilities - Ice Face", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.SURGING_STRIKES)); game.doAttack(getMovePosition(game.scene, 0, Moves.SURGING_STRIKES));
const eiscue = game.scene.getEnemyPokemon(); const eiscue = game.scene.getEnemyPokemon()!;
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBeDefined(); expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBeDefined();
// First hit // First hit
@ -82,7 +82,7 @@ describe("Abilities - Ice Face", () => {
await game.phaseInterceptor.to(MoveEndPhase); await game.phaseInterceptor.to(MoveEndPhase);
const eiscue = game.scene.getEnemyPokemon(); const eiscue = game.scene.getEnemyPokemon()!;
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined); expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined);
expect(eiscue.formIndex).toBe(icefaceForm); expect(eiscue.formIndex).toBe(icefaceForm);
@ -96,7 +96,7 @@ describe("Abilities - Ice Face", () => {
await game.phaseInterceptor.to(MoveEndPhase); await game.phaseInterceptor.to(MoveEndPhase);
const eiscue = game.scene.getEnemyPokemon(); const eiscue = game.scene.getEnemyPokemon()!;
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined); expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined);
expect(eiscue.formIndex).toBe(icefaceForm); expect(eiscue.formIndex).toBe(icefaceForm);
@ -112,15 +112,15 @@ describe("Abilities - Ice Face", () => {
await game.phaseInterceptor.to(MoveEndPhase); await game.phaseInterceptor.to(MoveEndPhase);
const eiscue = game.scene.getEnemyPokemon(); const eiscue = game.scene.getEnemyPokemon()!;
expect(eiscue.isFullHp()).toBe(true); expect(eiscue.isFullHp()).toBe(true);
expect(eiscue.formIndex).toBe(noiceForm); expect(eiscue.formIndex).toBe(noiceForm);
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBe(undefined); expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBeUndefined();
await game.phaseInterceptor.to(MoveEndPhase); await game.phaseInterceptor.to(MoveEndPhase);
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined); expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBeNull();
expect(eiscue.formIndex).toBe(icefaceForm); expect(eiscue.formIndex).toBe(icefaceForm);
}); });
@ -133,9 +133,9 @@ describe("Abilities - Ice Face", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.SNOWSCAPE)); game.doAttack(getMovePosition(game.scene, 0, Moves.SNOWSCAPE));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
let eiscue = game.scene.getPlayerPokemon(); let eiscue = game.scene.getPlayerPokemon()!;
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBe(undefined); expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBeUndefined();
expect(eiscue.formIndex).toBe(noiceForm); expect(eiscue.formIndex).toBe(noiceForm);
expect(eiscue.isFullHp()).toBe(true); expect(eiscue.isFullHp()).toBe(true);
@ -145,7 +145,7 @@ describe("Abilities - Ice Face", () => {
game.doSwitchPokemon(1); game.doSwitchPokemon(1);
await game.phaseInterceptor.to(QuietFormChangePhase); await game.phaseInterceptor.to(QuietFormChangePhase);
eiscue = game.scene.getPlayerPokemon(); eiscue = game.scene.getPlayerPokemon()!;
expect(eiscue.formIndex).toBe(icefaceForm); expect(eiscue.formIndex).toBe(icefaceForm);
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined); expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined);
@ -158,17 +158,17 @@ describe("Abilities - Ice Face", () => {
await game.startBattle([Species.EISCUE]); await game.startBattle([Species.EISCUE]);
game.doAttack(getMovePosition(game.scene, 0, Moves.HAIL)); game.doAttack(getMovePosition(game.scene, 0, Moves.HAIL));
const eiscue = game.scene.getPlayerPokemon(); const eiscue = game.scene.getPlayerPokemon()!;
await game.phaseInterceptor.to(QuietFormChangePhase); await game.phaseInterceptor.to(QuietFormChangePhase);
expect(eiscue.formIndex).toBe(noiceForm); expect(eiscue.formIndex).toBe(noiceForm);
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBe(undefined); expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBeUndefined();
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(eiscue.formIndex).toBe(noiceForm); expect(eiscue.formIndex).toBe(noiceForm);
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBe(undefined); expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBeUndefined();
}); });
it("persists form change when switched out", async () => { it("persists form change when switched out", async () => {
@ -179,9 +179,9 @@ describe("Abilities - Ice Face", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.ICE_BEAM)); game.doAttack(getMovePosition(game.scene, 0, Moves.ICE_BEAM));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
let eiscue = game.scene.getPlayerPokemon(); let eiscue = game.scene.getPlayerPokemon()!;
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBe(undefined); expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBeUndefined();
expect(eiscue.formIndex).toBe(noiceForm); expect(eiscue.formIndex).toBe(noiceForm);
expect(eiscue.isFullHp()).toBe(true); expect(eiscue.isFullHp()).toBe(true);
@ -192,7 +192,7 @@ describe("Abilities - Ice Face", () => {
eiscue = game.scene.getParty()[1]; eiscue = game.scene.getParty()[1];
expect(eiscue.formIndex).toBe(noiceForm); expect(eiscue.formIndex).toBe(noiceForm);
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBe(undefined); expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBeUndefined();
}); });
it("reverts to Ice Face on arena reset", async () => { it("reverts to Ice Face on arena reset", async () => {
@ -205,10 +205,10 @@ describe("Abilities - Ice Face", () => {
await game.startBattle([Species.EISCUE]); await game.startBattle([Species.EISCUE]);
const eiscue = game.scene.getPlayerPokemon(); const eiscue = game.scene.getPlayerPokemon()!;
expect(eiscue.formIndex).toBe(noiceForm); expect(eiscue.formIndex).toBe(noiceForm);
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBe(undefined); expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBeUndefined();
game.doAttack(getMovePosition(game.scene, 0, Moves.ICE_BEAM)); game.doAttack(getMovePosition(game.scene, 0, Moves.ICE_BEAM));
await game.doKillOpponents(); await game.doKillOpponents();
@ -229,7 +229,7 @@ describe("Abilities - Ice Face", () => {
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
const eiscue = game.scene.getEnemyPokemon(); const eiscue = game.scene.getEnemyPokemon()!;
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined); expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined);
expect(eiscue.formIndex).toBe(icefaceForm); expect(eiscue.formIndex).toBe(icefaceForm);
@ -245,7 +245,7 @@ describe("Abilities - Ice Face", () => {
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
const eiscue = game.scene.getEnemyPokemon(); const eiscue = game.scene.getEnemyPokemon()!;
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined); expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined);
expect(eiscue.formIndex).toBe(icefaceForm); expect(eiscue.formIndex).toBe(icefaceForm);
@ -261,10 +261,10 @@ describe("Abilities - Ice Face", () => {
await game.phaseInterceptor.to(TurnInitPhase); await game.phaseInterceptor.to(TurnInitPhase);
const eiscue = game.scene.getEnemyPokemon(); const eiscue = game.scene.getEnemyPokemon()!;
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined); expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined);
expect(eiscue.formIndex).toBe(icefaceForm); expect(eiscue.formIndex).toBe(icefaceForm);
expect(game.scene.getPlayerPokemon().hasAbility(Abilities.TRACE)).toBe(true); expect(game.scene.getPlayerPokemon()!.hasAbility(Abilities.TRACE)).toBe(true);
}); });
}); });

View File

@ -46,7 +46,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
@ -64,7 +64,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP, Species.BULBASAUR]); await game.startBattle([Species.MAGIKARP, Species.BULBASAUR]);
let leadPokemon = game.scene.getPlayerPokemon(); let leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
@ -86,7 +86,7 @@ describe("Abilities - Protean", () => {
game.doSwitchPokemon(1); game.doSwitchPokemon(1);
await game.toNextTurn(); await game.toNextTurn();
leadPokemon = game.scene.getPlayerPokemon(); leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
@ -104,7 +104,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.scene.arena.weather = new Weather(WeatherType.SUNNY); game.scene.arena.weather = new Weather(WeatherType.SUNNY);
@ -128,7 +128,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE)); game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
@ -150,7 +150,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.scene.arena.biomeType = Biome.MOUNTAIN; game.scene.arena.biomeType = Biome.MOUNTAIN;
@ -169,7 +169,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.DIG)); game.doAttack(getMovePosition(game.scene, 0, Moves.DIG));
@ -188,7 +188,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE)); game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
@ -196,7 +196,7 @@ describe("Abilities - Protean", () => {
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValueOnce(false); vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValueOnce(false);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon.isFullHp()).toBe(true); expect(enemyPokemon.isFullHp()).toBe(true);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE); testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE);
}, },
@ -211,7 +211,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE)); game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
@ -230,7 +230,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE)); game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
@ -248,7 +248,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
leadPokemon.summonData.types = [allMoves[Moves.SPLASH].defaultType]; leadPokemon.summonData.types = [allMoves[Moves.SPLASH].defaultType];
@ -267,7 +267,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
vi.spyOn(leadPokemon, "isTerastallized").mockReturnValue(true); vi.spyOn(leadPokemon, "isTerastallized").mockReturnValue(true);
@ -287,7 +287,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.STRUGGLE)); game.doAttack(getMovePosition(game.scene, 0, Moves.STRUGGLE));
@ -305,7 +305,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.BURN_UP)); game.doAttack(getMovePosition(game.scene, 0, Moves.BURN_UP));
@ -324,7 +324,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TRICK_OR_TREAT)); game.doAttack(getMovePosition(game.scene, 0, Moves.TRICK_OR_TREAT));
@ -342,7 +342,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.CURSE)); game.doAttack(getMovePosition(game.scene, 0, Moves.CURSE));

View File

@ -53,9 +53,10 @@ describe("Abilities - Magic Guard", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).toBeDefined();
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
@ -79,7 +80,7 @@ describe("Abilities - Magic Guard", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
@ -91,7 +92,7 @@ describe("Abilities - Magic Guard", () => {
* - The Pokemon's CatchRateMultiplier should be 1.5 * - The Pokemon's CatchRateMultiplier should be 1.5
*/ */
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
expect(getStatusEffectCatchRateMultiplier(leadPokemon.status.effect)).toBe(1.5); expect(getStatusEffectCatchRateMultiplier(leadPokemon.status!.effect)).toBe(1.5);
}, TIMEOUT }, TIMEOUT
); );
@ -103,7 +104,7 @@ describe("Abilities - Magic Guard", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
@ -127,7 +128,7 @@ describe("Abilities - Magic Guard", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
@ -138,7 +139,7 @@ describe("Abilities - Magic Guard", () => {
* - The enemy Pokemon's hypothetical CatchRateMultiplier should be 1.5 * - The enemy Pokemon's hypothetical CatchRateMultiplier should be 1.5
*/ */
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
expect(getStatusEffectCatchRateMultiplier(enemyPokemon.status.effect)).toBe(1.5); expect(getStatusEffectCatchRateMultiplier(enemyPokemon.status!.effect)).toBe(1.5);
}, TIMEOUT }, TIMEOUT
); );
@ -151,9 +152,9 @@ describe("Abilities - Magic Guard", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
const toxicStartCounter = enemyPokemon.status.turnCount; const toxicStartCounter = enemyPokemon.status!.turnCount;
//should be 0 //should be 0
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
@ -165,23 +166,23 @@ describe("Abilities - Magic Guard", () => {
* - The enemy Pokemon's hypothetical CatchRateMultiplier should be 1.5 * - The enemy Pokemon's hypothetical CatchRateMultiplier should be 1.5
*/ */
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
expect(enemyPokemon.status.turnCount).toBeGreaterThan(toxicStartCounter); expect(enemyPokemon.status!.turnCount).toBeGreaterThan(toxicStartCounter);
expect(getStatusEffectCatchRateMultiplier(enemyPokemon.status.effect)).toBe(1.5); expect(getStatusEffectCatchRateMultiplier(enemyPokemon.status!.effect)).toBe(1.5);
}, TIMEOUT }, TIMEOUT
); );
it("Magic Guard prevents damage caused by entry hazards", async () => { it("Magic Guard prevents damage caused by entry hazards", async () => {
//Adds and applies Spikes to both sides of the arena //Adds and applies Spikes to both sides of the arena
const newTag = getArenaTag(ArenaTagType.SPIKES, 5, Moves.SPIKES, 0, 0, ArenaTagSide.BOTH); const newTag = getArenaTag(ArenaTagType.SPIKES, 5, Moves.SPIKES, 0, 0, ArenaTagSide.BOTH)!;
game.scene.arena.tags.push(newTag); game.scene.arena.tags.push(newTag);
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
@ -197,17 +198,17 @@ describe("Abilities - Magic Guard", () => {
it("Magic Guard does not prevent poison from Toxic Spikes", async () => { it("Magic Guard does not prevent poison from Toxic Spikes", async () => {
//Adds and applies Spikes to both sides of the arena //Adds and applies Spikes to both sides of the arena
const playerTag = getArenaTag(ArenaTagType.TOXIC_SPIKES, 5, Moves.TOXIC_SPIKES, 0, 0, ArenaTagSide.PLAYER); const playerTag = getArenaTag(ArenaTagType.TOXIC_SPIKES, 5, Moves.TOXIC_SPIKES, 0, 0, ArenaTagSide.PLAYER)!;
const enemyTag = getArenaTag(ArenaTagType.TOXIC_SPIKES, 5, Moves.TOXIC_SPIKES, 0, 0, ArenaTagSide.ENEMY); const enemyTag = getArenaTag(ArenaTagType.TOXIC_SPIKES, 5, Moves.TOXIC_SPIKES, 0, 0, ArenaTagSide.ENEMY)!;
game.scene.arena.tags.push(playerTag); game.scene.arena.tags.push(playerTag);
game.scene.arena.tags.push(enemyTag); game.scene.arena.tags.push(enemyTag);
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
@ -217,8 +218,8 @@ describe("Abilities - Magic Guard", () => {
* - The player Pokemon (with Magic Guard) has not taken damage from poison * - The player Pokemon (with Magic Guard) has not taken damage from poison
* - The enemy Pokemon (without Magic Guard) has taken damage from poison * - The enemy Pokemon (without Magic Guard) has taken damage from poison
*/ */
expect(leadPokemon.status.effect).toBe(StatusEffect.POISON); expect(leadPokemon.status!.effect).toBe(StatusEffect.POISON);
expect(enemyPokemon.status.effect).toBe(StatusEffect.POISON); expect(enemyPokemon.status!.effect).toBe(StatusEffect.POISON);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp());
}, TIMEOUT }, TIMEOUT
@ -230,11 +231,11 @@ describe("Abilities - Magic Guard", () => {
game.override.moveset([Moves.CURSE]); game.override.moveset([Moves.CURSE]);
game.override.enemyAbility(Abilities.MAGIC_GUARD); game.override.enemyAbility(Abilities.MAGIC_GUARD);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.CURSE)); game.doAttack(getMovePosition(game.scene, 0, Moves.CURSE));
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
@ -254,7 +255,7 @@ describe("Abilities - Magic Guard", () => {
game.override.moveset([Moves.HIGH_JUMP_KICK]); game.override.moveset([Moves.HIGH_JUMP_KICK]);
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.HIGH_JUMP_KICK)); game.doAttack(getMovePosition(game.scene, 0, Moves.HIGH_JUMP_KICK));
await game.phaseInterceptor.to(MoveEffectPhase, false); await game.phaseInterceptor.to(MoveEffectPhase, false);
@ -274,7 +275,7 @@ describe("Abilities - Magic Guard", () => {
game.override.moveset([Moves.TAKE_DOWN]); game.override.moveset([Moves.TAKE_DOWN]);
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.TAKE_DOWN)); game.doAttack(getMovePosition(game.scene, 0, Moves.TAKE_DOWN));
@ -292,7 +293,7 @@ describe("Abilities - Magic Guard", () => {
game.override.moveset([Moves.STRUGGLE]); game.override.moveset([Moves.STRUGGLE]);
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.STRUGGLE)); game.doAttack(getMovePosition(game.scene, 0, Moves.STRUGGLE));
@ -311,7 +312,7 @@ describe("Abilities - Magic Guard", () => {
game.override.moveset([Moves.STEEL_BEAM]); game.override.moveset([Moves.STEEL_BEAM]);
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.STEEL_BEAM)); game.doAttack(getMovePosition(game.scene, 0, Moves.STEEL_BEAM));
@ -339,7 +340,7 @@ describe("Abilities - Magic Guard", () => {
game.override.moveset([Moves.BELLY_DRUM]); game.override.moveset([Moves.BELLY_DRUM]);
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.BELLY_DRUM)); game.doAttack(getMovePosition(game.scene, 0, Moves.BELLY_DRUM));
@ -362,7 +363,7 @@ describe("Abilities - Magic Guard", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
@ -374,7 +375,7 @@ describe("Abilities - Magic Guard", () => {
* - The player Pokemon is asleep * - The player Pokemon is asleep
*/ */
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
expect(leadPokemon.status.effect).toBe(StatusEffect.SLEEP); expect(leadPokemon.status!.effect).toBe(StatusEffect.SLEEP);
}, TIMEOUT }, TIMEOUT
); );
@ -385,9 +386,9 @@ describe("Abilities - Magic Guard", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
enemyPokemon.hp = 1; enemyPokemon.hp = 1;
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE)); game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
@ -410,9 +411,9 @@ describe("Abilities - Magic Guard", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE)); game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
@ -434,9 +435,9 @@ describe("Abilities - Magic Guard", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.ABSORB)); game.doAttack(getMovePosition(game.scene, 0, Moves.ABSORB));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
@ -457,7 +458,7 @@ describe("Abilities - Magic Guard", () => {
game.override.weather(WeatherType.SUNNY); game.override.weather(WeatherType.SUNNY);
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);

View File

@ -47,10 +47,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD]); await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
let enemyStartingHp = enemyPokemon.hp; let enemyStartingHp = enemyPokemon.hp;
@ -80,10 +80,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD]); await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.POWER_UP_PUNCH)); game.doAttack(getMovePosition(game.scene, 0, Moves.POWER_UP_PUNCH));
@ -102,10 +102,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD]); await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.BABY_DOLL_EYES)); game.doAttack(getMovePosition(game.scene, 0, Moves.BABY_DOLL_EYES));
@ -122,10 +122,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD]); await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.DOUBLE_HIT)); game.doAttack(getMovePosition(game.scene, 0, Moves.DOUBLE_HIT));
@ -147,10 +147,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD]); await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.SELF_DESTRUCT)); game.doAttack(getMovePosition(game.scene, 0, Moves.SELF_DESTRUCT));
@ -168,10 +168,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD]); await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.ROLLOUT)); game.doAttack(getMovePosition(game.scene, 0, Moves.ROLLOUT));
@ -192,10 +192,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD]); await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
const enemyStartingHp = enemyPokemon.hp; const enemyStartingHp = enemyPokemon.hp;
@ -215,10 +215,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD]); await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
const playerStartingHp = leadPokemon.hp; const playerStartingHp = leadPokemon.hp;
@ -268,10 +268,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD]); await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.EARTHQUAKE)); game.doAttack(getMovePosition(game.scene, 0, Moves.EARTHQUAKE));
@ -288,10 +288,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.PIDGEOT]); await game.startBattle([Species.PIDGEOT]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.MIND_BLOWN)); game.doAttack(getMovePosition(game.scene, 0, Moves.MIND_BLOWN));
@ -314,10 +314,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD]); await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.BURN_UP)); game.doAttack(getMovePosition(game.scene, 0, Moves.BURN_UP));
@ -342,10 +342,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD]); await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE)); game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
@ -364,10 +364,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD]); await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
const enemyStartingHp = enemyPokemon.hp; const enemyStartingHp = enemyPokemon.hp;
@ -395,10 +395,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD]); await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
const enemyStartingHp = enemyPokemon.hp; const enemyStartingHp = enemyPokemon.hp;
@ -425,10 +425,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD]); await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.HYPER_BEAM)); game.doAttack(getMovePosition(game.scene, 0, Moves.HYPER_BEAM));
@ -455,10 +455,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD]); await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.ANCHOR_SHOT)); game.doAttack(getMovePosition(game.scene, 0, Moves.ANCHOR_SHOT));
@ -487,10 +487,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD]); await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.SMACK_DOWN)); game.doAttack(getMovePosition(game.scene, 0, Moves.SMACK_DOWN));
@ -516,10 +516,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]); await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.U_TURN)); game.doAttack(getMovePosition(game.scene, 0, Moves.U_TURN));
@ -542,10 +542,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD]); await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.WAKE_UP_SLAP)); game.doAttack(getMovePosition(game.scene, 0, Moves.WAKE_UP_SLAP));
@ -572,10 +572,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD]); await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE)); game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
@ -594,10 +594,10 @@ describe("Abilities - Parental Bond", () => {
await game.startBattle([Species.CHARIZARD]); await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.WATER_GUN)); game.doAttack(getMovePosition(game.scene, 0, Moves.WATER_GUN));

View File

@ -51,7 +51,7 @@ describe("Abilities - Pastel Veil", () => {
it("it heals the poisoned status condition of allies if user is sent out into battle", async () => { it("it heals the poisoned status condition of allies if user is sent out into battle", async () => {
await game.startBattle([Species.MAGIKARP, Species.MAGIKARP, Species.GALAR_PONYTA]); await game.startBattle([Species.MAGIKARP, Species.MAGIKARP, Species.GALAR_PONYTA]);
const ponyta = game.scene.getParty().find(p => p.species.speciesId === Species.GALAR_PONYTA); const ponyta = game.scene.getParty().find(p => p.species.speciesId === Species.GALAR_PONYTA)!;
vi.spyOn(ponyta, "getAbility").mockReturnValue(allAbilities[Abilities.PASTEL_VEIL]); vi.spyOn(ponyta, "getAbility").mockReturnValue(allAbilities[Abilities.PASTEL_VEIL]);
@ -66,7 +66,7 @@ describe("Abilities - Pastel Veil", () => {
const poisonedMon = game.scene.getPlayerField().find(p => p.status?.effect === StatusEffect.POISON); const poisonedMon = game.scene.getPlayerField().find(p => p.status?.effect === StatusEffect.POISON);
await game.phaseInterceptor.to(CommandPhase); await game.phaseInterceptor.to(CommandPhase);
game.doAttack(getMovePosition(game.scene, (poisonedMon.getBattlerIndex() as BattlerIndex.PLAYER | BattlerIndex.PLAYER_2), Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, (poisonedMon!.getBattlerIndex() as BattlerIndex.PLAYER | BattlerIndex.PLAYER_2), Moves.SPLASH));
game.doSwitchPokemon(2); game.doSwitchPokemon(2);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);

View File

@ -47,11 +47,11 @@ describe("Abilities - POWER CONSTRUCT", () => {
const zygarde = game.scene.getParty().find((p) => p.species.speciesId === Species.ZYGARDE); const zygarde = game.scene.getParty().find((p) => p.species.speciesId === Species.ZYGARDE);
expect(zygarde).not.toBe(undefined); expect(zygarde).not.toBe(undefined);
expect(zygarde.formIndex).toBe(completeForm); expect(zygarde!.formIndex).toBe(completeForm);
zygarde.hp = 0; zygarde!.hp = 0;
zygarde.status = new Status(StatusEffect.FAINT); zygarde!.status = new Status(StatusEffect.FAINT);
expect(zygarde.isFainted()).toBe(true); expect(zygarde!.isFainted()).toBe(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.doKillOpponents(); await game.doKillOpponents();
@ -59,7 +59,7 @@ describe("Abilities - POWER CONSTRUCT", () => {
game.doSelectModifier(); game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase); await game.phaseInterceptor.to(QuietFormChangePhase);
expect(zygarde.formIndex).toBe(baseForm); expect(zygarde!.formIndex).toBe(baseForm);
}, },
TIMEOUT TIMEOUT
); );

View File

@ -46,7 +46,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
@ -64,7 +64,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP, Species.BULBASAUR]); await game.startBattle([Species.MAGIKARP, Species.BULBASAUR]);
let leadPokemon = game.scene.getPlayerPokemon(); let leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
@ -86,7 +86,7 @@ describe("Abilities - Protean", () => {
game.doSwitchPokemon(1); game.doSwitchPokemon(1);
await game.toNextTurn(); await game.toNextTurn();
leadPokemon = game.scene.getPlayerPokemon(); leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
@ -104,7 +104,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.scene.arena.weather = new Weather(WeatherType.SUNNY); game.scene.arena.weather = new Weather(WeatherType.SUNNY);
@ -128,7 +128,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE)); game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
@ -150,7 +150,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.scene.arena.biomeType = Biome.MOUNTAIN; game.scene.arena.biomeType = Biome.MOUNTAIN;
@ -169,7 +169,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.DIG)); game.doAttack(getMovePosition(game.scene, 0, Moves.DIG));
@ -188,7 +188,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE)); game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
@ -196,7 +196,7 @@ describe("Abilities - Protean", () => {
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValueOnce(false); vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValueOnce(false);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon.isFullHp()).toBe(true); expect(enemyPokemon.isFullHp()).toBe(true);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE); testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE);
}, },
@ -211,7 +211,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE)); game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
@ -230,7 +230,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE)); game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
@ -248,7 +248,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
leadPokemon.summonData.types = [allMoves[Moves.SPLASH].defaultType]; leadPokemon.summonData.types = [allMoves[Moves.SPLASH].defaultType];
@ -267,7 +267,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
vi.spyOn(leadPokemon, "isTerastallized").mockReturnValue(true); vi.spyOn(leadPokemon, "isTerastallized").mockReturnValue(true);
@ -287,7 +287,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.STRUGGLE)); game.doAttack(getMovePosition(game.scene, 0, Moves.STRUGGLE));
@ -305,7 +305,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.BURN_UP)); game.doAttack(getMovePosition(game.scene, 0, Moves.BURN_UP));
@ -324,7 +324,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TRICK_OR_TREAT)); game.doAttack(getMovePosition(game.scene, 0, Moves.TRICK_OR_TREAT));
@ -342,7 +342,7 @@ describe("Abilities - Protean", () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.CURSE)); game.doAttack(getMovePosition(game.scene, 0, Moves.CURSE));

View File

@ -41,8 +41,8 @@ describe("Abilities - Quick Draw", () => {
test("makes pokemon going first in its priority bracket", async () => { test("makes pokemon going first in its priority bracket", async () => {
await game.startBattle(); await game.startBattle();
const pokemon = game.scene.getPlayerPokemon(); const pokemon = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon(); const enemy = game.scene.getEnemyPokemon()!;
pokemon.hp = 1; pokemon.hp = 1;
enemy.hp = 1; enemy.hp = 1;
@ -61,8 +61,8 @@ describe("Abilities - Quick Draw", () => {
}, async () => { }, async () => {
await game.startBattle(); await game.startBattle();
const pokemon = game.scene.getPlayerPokemon(); const pokemon = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon(); const enemy = game.scene.getEnemyPokemon()!;
pokemon.hp = 1; pokemon.hp = 1;
enemy.hp = 1; enemy.hp = 1;
@ -81,8 +81,8 @@ describe("Abilities - Quick Draw", () => {
await game.startBattle(); await game.startBattle();
const pokemon = game.scene.getPlayerPokemon(); const pokemon = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon(); const enemy = game.scene.getEnemyPokemon()!;
pokemon.hp = 1; pokemon.hp = 1;
enemy.hp = 1; enemy.hp = 1;

View File

@ -42,7 +42,7 @@ describe("Ability Timing", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.toNextTurn(); await game.toNextTurn();
expect(game.scene.arena.weather.weatherType).toBe(WeatherType.SANDSTORM); expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SANDSTORM);
}, 20000); }, 20000);
it("should not trigger when targetted with status moves", async() => { it("should not trigger when targetted with status moves", async() => {

View File

@ -87,7 +87,7 @@ describe("Abilities - Sap Sipper", () => {
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(game.scene.arena.terrain).toBeDefined(); expect(game.scene.arena.terrain).toBeDefined();
expect(game.scene.arena.terrain.terrainType).toBe(TerrainType.GRASSY); expect(game.scene.arena.terrain!.terrainType).toBe(TerrainType.GRASSY);
expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(0); expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(0);
}); });

View File

@ -45,7 +45,7 @@ describe("Abilities - SCHOOLING", () => {
await game.startBattle([Species.MAGIKARP, Species.WISHIWASHI]); await game.startBattle([Species.MAGIKARP, Species.WISHIWASHI]);
const wishiwashi = game.scene.getParty().find((p) => p.species.speciesId === Species.WISHIWASHI); const wishiwashi = game.scene.getParty().find((p) => p.species.speciesId === Species.WISHIWASHI)!;
expect(wishiwashi).not.toBe(undefined); expect(wishiwashi).not.toBe(undefined);
expect(wishiwashi.formIndex).toBe(schoolForm); expect(wishiwashi.formIndex).toBe(schoolForm);

View File

@ -66,8 +66,8 @@ describe("Abilities - Serene Grace", () => {
expect(move.id).toBe(Moves.AIR_SLASH); expect(move.id).toBe(Moves.AIR_SLASH);
const chance = new Utils.IntegerHolder(move.chance); const chance = new Utils.IntegerHolder(move.chance);
console.log(move.chance + " Their ability is " + phase.getUserPokemon().getAbility().name); console.log(move.chance + " Their ability is " + phase.getUserPokemon()!.getAbility().name);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon(), null, chance, move, phase.getTarget(), false); applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, chance, move, phase.getTarget(), false);
expect(chance.value).toBe(30); expect(chance.value).toBe(30);
}, 20000); }, 20000);
@ -99,7 +99,7 @@ describe("Abilities - Serene Grace", () => {
expect(move.id).toBe(Moves.AIR_SLASH); expect(move.id).toBe(Moves.AIR_SLASH);
const chance = new Utils.IntegerHolder(move.chance); const chance = new Utils.IntegerHolder(move.chance);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon(), null, chance, move, phase.getTarget(), false); applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, chance, move, phase.getTarget(), false);
expect(chance.value).toBe(60); expect(chance.value).toBe(60);
}, 20000); }, 20000);

View File

@ -69,8 +69,8 @@ describe("Abilities - Sheer Force", () => {
const power = new Utils.IntegerHolder(move.power); const power = new Utils.IntegerHolder(move.power);
const chance = new Utils.IntegerHolder(move.chance); const chance = new Utils.IntegerHolder(move.chance);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon(), null, chance, move, phase.getTarget(), false); applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, chance, move, phase.getTarget(), false);
applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon(), phase.getTarget(), move, power); applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getTarget()!, move, power);
expect(chance.value).toBe(0); expect(chance.value).toBe(0);
expect(power.value).toBe(move.power * 5461/4096); expect(power.value).toBe(move.power * 5461/4096);
@ -108,8 +108,8 @@ describe("Abilities - Sheer Force", () => {
const power = new Utils.IntegerHolder(move.power); const power = new Utils.IntegerHolder(move.power);
const chance = new Utils.IntegerHolder(move.chance); const chance = new Utils.IntegerHolder(move.chance);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon(), null, chance, move, phase.getTarget(), false); applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, chance, move, phase.getTarget(), false);
applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon(), phase.getTarget(), move, power); applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getTarget()!, move, power);
expect(chance.value).toBe(-1); expect(chance.value).toBe(-1);
expect(power.value).toBe(move.power); expect(power.value).toBe(move.power);
@ -147,8 +147,8 @@ describe("Abilities - Sheer Force", () => {
const power = new Utils.IntegerHolder(move.power); const power = new Utils.IntegerHolder(move.power);
const chance = new Utils.IntegerHolder(move.chance); const chance = new Utils.IntegerHolder(move.chance);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon(), null, chance, move, phase.getTarget(), false); applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, chance, move, phase.getTarget(), false);
applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon(), phase.getTarget(), move, power); applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getTarget()!, move, power);
expect(chance.value).toBe(-1); expect(chance.value).toBe(-1);
expect(power.value).toBe(move.power); expect(power.value).toBe(move.power);
@ -187,8 +187,8 @@ describe("Abilities - Sheer Force", () => {
//Disable color change due to being hit by Sheer Force //Disable color change due to being hit by Sheer Force
const power = new Utils.IntegerHolder(move.power); const power = new Utils.IntegerHolder(move.power);
const chance = new Utils.IntegerHolder(move.chance); const chance = new Utils.IntegerHolder(move.chance);
const user = phase.getUserPokemon(); const user = phase.getUserPokemon()!;
const target = phase.getTarget(); const target = phase.getTarget()!;
const opponentType = target.getTypes()[0]; const opponentType = target.getTypes()[0];
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, chance, move, target, false); applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, chance, move, target, false);

View File

@ -67,8 +67,8 @@ describe("Abilities - Shield Dust", () => {
expect(move.id).toBe(Moves.AIR_SLASH); expect(move.id).toBe(Moves.AIR_SLASH);
const chance = new Utils.IntegerHolder(move.chance); const chance = new Utils.IntegerHolder(move.chance);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon(), null, chance, move, phase.getTarget(), false); applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, chance, move, phase.getTarget(), false);
applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr, phase.getTarget(),phase.getUserPokemon(),null,null, chance); applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr, phase.getTarget()!, phase.getUserPokemon()!, null!, null!, chance);
expect(chance.value).toBe(0); expect(chance.value).toBe(0);
}, 20000); }, 20000);

View File

@ -45,7 +45,7 @@ describe("Abilities - SHIELDS DOWN", () => {
await game.startBattle([Species.MAGIKARP, Species.MINIOR]); await game.startBattle([Species.MAGIKARP, Species.MINIOR]);
const minior = game.scene.getParty().find((p) => p.species.speciesId === Species.MINIOR); const minior = game.scene.getParty().find((p) => p.species.speciesId === Species.MINIOR)!;
expect(minior).not.toBe(undefined); expect(minior).not.toBe(undefined);
expect(minior.formIndex).toBe(coreForm); expect(minior.formIndex).toBe(coreForm);

View File

@ -40,7 +40,7 @@ describe("Abilities - Steely Spirit", () => {
it("increases Steel-type moves' power used by the user and its allies by 50%", async () => { it("increases Steel-type moves' power used by the user and its allies by 50%", async () => {
await game.startBattle([Species.PIKACHU, Species.SHUCKLE]); await game.startBattle([Species.PIKACHU, Species.SHUCKLE]);
const boostSource = game.scene.getPlayerField()[1]; const boostSource = game.scene.getPlayerField()[1];
const enemyToCheck = game.scene.getEnemyPokemon(); const enemyToCheck = game.scene.getEnemyPokemon()!;
vi.spyOn(boostSource, "getAbility").mockReturnValue(allAbilities[Abilities.STEELY_SPIRIT]); vi.spyOn(boostSource, "getAbility").mockReturnValue(allAbilities[Abilities.STEELY_SPIRIT]);
@ -57,7 +57,7 @@ describe("Abilities - Steely Spirit", () => {
it("stacks if multiple users with this ability are on the field.", async () => { it("stacks if multiple users with this ability are on the field.", async () => {
await game.startBattle([Species.PIKACHU, Species.PIKACHU]); await game.startBattle([Species.PIKACHU, Species.PIKACHU]);
const enemyToCheck = game.scene.getEnemyPokemon(); const enemyToCheck = game.scene.getEnemyPokemon()!;
game.scene.getPlayerField().forEach(p => { game.scene.getPlayerField().forEach(p => {
vi.spyOn(p, "getAbility").mockReturnValue(allAbilities[Abilities.STEELY_SPIRIT]); vi.spyOn(p, "getAbility").mockReturnValue(allAbilities[Abilities.STEELY_SPIRIT]);
@ -79,7 +79,7 @@ describe("Abilities - Steely Spirit", () => {
it("does not take effect when suppressed", async () => { it("does not take effect when suppressed", async () => {
await game.startBattle([Species.PIKACHU, Species.SHUCKLE]); await game.startBattle([Species.PIKACHU, Species.SHUCKLE]);
const boostSource = game.scene.getPlayerField()[1]; const boostSource = game.scene.getPlayerField()[1];
const enemyToCheck = game.scene.getEnemyPokemon(); const enemyToCheck = game.scene.getEnemyPokemon()!;
vi.spyOn(boostSource, "getAbility").mockReturnValue(allAbilities[Abilities.STEELY_SPIRIT]); vi.spyOn(boostSource, "getAbility").mockReturnValue(allAbilities[Abilities.STEELY_SPIRIT]);
expect(boostSource.hasAbility(Abilities.STEELY_SPIRIT)).toBe(true); expect(boostSource.hasAbility(Abilities.STEELY_SPIRIT)).toBe(true);

View File

@ -92,7 +92,7 @@ describe("Abilities - Sweet Veil", () => {
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
const drowsyMon = game.scene.getPlayerField().find(p => !!p.getTag(BattlerTagType.DROWSY)); const drowsyMon = game.scene.getPlayerField().find(p => !!p.getTag(BattlerTagType.DROWSY))!;
await game.phaseInterceptor.to(CommandPhase); await game.phaseInterceptor.to(CommandPhase);
game.doAttack(getMovePosition(game.scene, (drowsyMon.getBattlerIndex() as BattlerIndex.PLAYER | BattlerIndex.PLAYER_2), Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, (drowsyMon.getBattlerIndex() as BattlerIndex.PLAYER | BattlerIndex.PLAYER_2), Moves.SPLASH));

View File

@ -72,10 +72,10 @@ async function testUnseenFistHitResult(game: GameManager, attackMove: Moves, pro
await game.startBattle(); await game.startBattle();
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
expect(leadPokemon).not.toBe(undefined); expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).not.toBe(undefined); expect(enemyPokemon).not.toBe(undefined);
const enemyStartingHp = enemyPokemon.hp; const enemyStartingHp = enemyPokemon.hp;

View File

@ -34,7 +34,7 @@ describe("Abilities - Wind Power", () => {
it("it becomes charged when hit by wind moves", async () => { it("it becomes charged when hit by wind moves", async () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const shiftry = game.scene.getEnemyPokemon(); const shiftry = game.scene.getEnemyPokemon()!;
expect(shiftry.getTag(BattlerTagType.CHARGED)).toBeUndefined(); expect(shiftry.getTag(BattlerTagType.CHARGED)).toBeUndefined();
@ -49,7 +49,7 @@ describe("Abilities - Wind Power", () => {
game.override.enemySpecies(Species.MAGIKARP); game.override.enemySpecies(Species.MAGIKARP);
await game.startBattle([Species.SHIFTRY]); await game.startBattle([Species.SHIFTRY]);
const shiftry = game.scene.getPlayerPokemon(); const shiftry = game.scene.getPlayerPokemon()!;
expect(shiftry.getTag(BattlerTagType.CHARGED)).toBeUndefined(); expect(shiftry.getTag(BattlerTagType.CHARGED)).toBeUndefined();
@ -64,8 +64,8 @@ describe("Abilities - Wind Power", () => {
game.override.ability(Abilities.WIND_POWER); game.override.ability(Abilities.WIND_POWER);
await game.startBattle([Species.SHIFTRY]); await game.startBattle([Species.SHIFTRY]);
const magikarp = game.scene.getEnemyPokemon(); const magikarp = game.scene.getEnemyPokemon()!;
const shiftry = game.scene.getPlayerPokemon(); const shiftry = game.scene.getPlayerPokemon()!;
expect(shiftry.getTag(BattlerTagType.CHARGED)).toBeUndefined(); expect(shiftry.getTag(BattlerTagType.CHARGED)).toBeUndefined();
expect(magikarp.getTag(BattlerTagType.CHARGED)).toBeUndefined(); expect(magikarp.getTag(BattlerTagType.CHARGED)).toBeUndefined();
@ -82,7 +82,7 @@ describe("Abilities - Wind Power", () => {
game.override.enemySpecies(Species.MAGIKARP); game.override.enemySpecies(Species.MAGIKARP);
await game.startBattle([Species.SHIFTRY]); await game.startBattle([Species.SHIFTRY]);
const shiftry = game.scene.getPlayerPokemon(); const shiftry = game.scene.getPlayerPokemon()!;
expect(shiftry.getTag(BattlerTagType.CHARGED)).toBeUndefined(); expect(shiftry.getTag(BattlerTagType.CHARGED)).toBeUndefined();

View File

@ -34,7 +34,7 @@ describe("Abilities - Wind Rider", () => {
it("takes no damage from wind moves and its Attack is increased by one stage when hit by one", async () => { it("takes no damage from wind moves and its Attack is increased by one stage when hit by one", async () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const shiftry = game.scene.getEnemyPokemon(); const shiftry = game.scene.getEnemyPokemon()!;
expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0); expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0);
@ -51,7 +51,7 @@ describe("Abilities - Wind Rider", () => {
game.override.enemySpecies(Species.MAGIKARP); game.override.enemySpecies(Species.MAGIKARP);
await game.startBattle([Species.SHIFTRY]); await game.startBattle([Species.SHIFTRY]);
const shiftry = game.scene.getPlayerPokemon(); const shiftry = game.scene.getPlayerPokemon()!;
expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0); expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0);
@ -67,8 +67,8 @@ describe("Abilities - Wind Rider", () => {
game.override.enemySpecies(Species.MAGIKARP); game.override.enemySpecies(Species.MAGIKARP);
await game.startBattle([Species.SHIFTRY]); await game.startBattle([Species.SHIFTRY]);
const magikarp = game.scene.getEnemyPokemon(); const magikarp = game.scene.getEnemyPokemon()!;
const shiftry = game.scene.getPlayerPokemon(); const shiftry = game.scene.getPlayerPokemon()!;
expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0); expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(magikarp.summonData.battleStats[BattleStat.ATK]).toBe(0); expect(magikarp.summonData.battleStats[BattleStat.ATK]).toBe(0);
@ -85,8 +85,8 @@ describe("Abilities - Wind Rider", () => {
game.override.enemySpecies(Species.MAGIKARP); game.override.enemySpecies(Species.MAGIKARP);
await game.startBattle([Species.SHIFTRY]); await game.startBattle([Species.SHIFTRY]);
const magikarp = game.scene.getEnemyPokemon(); const magikarp = game.scene.getEnemyPokemon()!;
const shiftry = game.scene.getPlayerPokemon(); const shiftry = game.scene.getPlayerPokemon()!;
expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0); expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(magikarp.summonData.battleStats[BattleStat.ATK]).toBe(0); expect(magikarp.summonData.battleStats[BattleStat.ATK]).toBe(0);
@ -103,7 +103,7 @@ describe("Abilities - Wind Rider", () => {
game.override.enemySpecies(Species.MAGIKARP); game.override.enemySpecies(Species.MAGIKARP);
await game.startBattle([Species.SHIFTRY]); await game.startBattle([Species.SHIFTRY]);
const shiftry = game.scene.getPlayerPokemon(); const shiftry = game.scene.getPlayerPokemon()!;
expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0); expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(shiftry.isFullHp()).toBe(true); expect(shiftry.isFullHp()).toBe(true);

View File

@ -150,7 +150,7 @@ describe("Abilities - ZEN MODE", () => {
await game.startBattle([Species.MAGIKARP, Species.DARMANITAN]); await game.startBattle([Species.MAGIKARP, Species.DARMANITAN]);
const darmanitan = game.scene.getParty().find((p) => p.species.speciesId === Species.DARMANITAN); const darmanitan = game.scene.getParty().find((p) => p.species.speciesId === Species.DARMANITAN)!;
expect(darmanitan).not.toBe(undefined); expect(darmanitan).not.toBe(undefined);
expect(darmanitan.formIndex).toBe(zenForm); expect(darmanitan.formIndex).toBe(zenForm);

View File

@ -45,7 +45,7 @@ describe("Abilities - ZERO TO HERO", () => {
await game.startBattle([Species.MAGIKARP, Species.PALAFIN]); await game.startBattle([Species.MAGIKARP, Species.PALAFIN]);
const palafin = game.scene.getParty().find((p) => p.species.speciesId === Species.PALAFIN); const palafin = game.scene.getParty().find((p) => p.species.speciesId === Species.PALAFIN)!;
expect(palafin).not.toBe(undefined); expect(palafin).not.toBe(undefined);
expect(palafin.formIndex).toBe(heroForm); expect(palafin.formIndex).toBe(heroForm);

View File

@ -8,21 +8,21 @@ describe("account", () => {
it("should set loggedInUser to Guest and lastSessionSlot to -1", () => { it("should set loggedInUser to Guest and lastSessionSlot to -1", () => {
initLoggedInUser(); initLoggedInUser();
expect(loggedInUser.username).toBe("Guest"); expect(loggedInUser!.username).toBe("Guest");
expect(loggedInUser.lastSessionSlot).toBe(-1); expect(loggedInUser!.lastSessionSlot).toBe(-1);
}); });
}); });
describe("updateUserInfo", () => { describe("updateUserInfo", () => {
it("should set loggedInUser to Guest if bypassLogin is true", async () => { it("should set loggedInUser! to Guest if bypassLogin is true", async () => {
vi.spyOn(battleScene, "bypassLogin", "get").mockReturnValue(true); vi.spyOn(battleScene, "bypassLogin", "get").mockReturnValue(true);
const [success, status] = await updateUserInfo(); const [success, status] = await updateUserInfo();
expect(success).toBe(true); expect(success).toBe(true);
expect(status).toBe(200); expect(status).toBe(200);
expect(loggedInUser.username).toBe("Guest"); expect(loggedInUser!.username).toBe("Guest");
expect(loggedInUser.lastSessionSlot).toBe(-1); expect(loggedInUser!.lastSessionSlot).toBe(-1);
}); });
it("should fetch user info from the API if bypassLogin is false", async () => { it("should fetch user info from the API if bypassLogin is false", async () => {
@ -43,8 +43,8 @@ describe("account", () => {
expect(success).toBe(true); expect(success).toBe(true);
expect(status).toBe(200); expect(status).toBe(200);
expect(loggedInUser.username).toBe("test"); expect(loggedInUser!.username).toBe("test");
expect(loggedInUser.lastSessionSlot).toBe(99); expect(loggedInUser!.lastSessionSlot).toBe(99);
}); });
it("should handle resolved API errors", async () => { it("should handle resolved API errors", async () => {

View File

@ -3,12 +3,12 @@ import { Achv, AchvTier, DamageAchv, HealAchv, LevelAchv, ModifierAchv, MoneyAch
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import { IntegerHolder, NumberHolder } from "#app/utils.js"; import { IntegerHolder, NumberHolder } from "#app/utils.js";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import BattleScene from "../../battle-scene"; import BattleScene from "../../battle-scene";
describe("check some Achievement related stuff", () => { describe("check some Achievement related stuff", () => {
it ("should check Achievement creation", () => { it ("should check Achievement creation", () => {
const ach = new MoneyAchv("", "Achievement", 1000, null, 100); const ach = new MoneyAchv("", "Achievement", 1000, null!, 100);
expect(ach.name).toBe("Achievement"); expect(ach.name).toBe("Achievement");
}); });
}); });
@ -58,7 +58,7 @@ describe("Achv", () => {
}); });
it("should validate the achievement based on the condition function", () => { it("should validate the achievement based on the condition function", () => {
const conditionFunc = jest.fn((scene: BattleScene, args: any[]) => args[0] === 10); const conditionFunc = vi.fn((scene: BattleScene, args: any[]) => args[0] === 10);
const achv = new Achv("", "Test Achievement", "Test Description", "test_icon", 10, conditionFunc); const achv = new Achv("", "Test Achievement", "Test Description", "test_icon", 10, conditionFunc);
expect(achv.validate(new BattleScene(), [5])).toBe(false); expect(achv.validate(new BattleScene(), [5])).toBe(false);
@ -196,7 +196,7 @@ describe("ModifierAchv", () => {
it("should validate the achievement based on the modifier function", () => { it("should validate the achievement based on the modifier function", () => {
const modifierAchv = new ModifierAchv("", "Test Modifier Achievement", "Test Description", "modifier_icon", 10, () => true); const modifierAchv = new ModifierAchv("", "Test Modifier Achievement", "Test Description", "modifier_icon", 10, () => true);
const scene = new BattleScene(); const scene = new BattleScene();
const modifier = new TurnHeldItemTransferModifier(null, 3, 1); const modifier = new TurnHeldItemTransferModifier(null!, 3, 1);
expect(modifierAchv.validate(scene, [modifier])).toBe(true); expect(modifierAchv.validate(scene, [modifier])).toBe(true);
}); });

View File

@ -35,8 +35,8 @@ describe("Weather - Strong Winds", () => {
game.override.enemySpecies(Species.RAYQUAZA); game.override.enemySpecies(Species.RAYQUAZA);
await game.startBattle([Species.PIKACHU]); await game.startBattle([Species.PIKACHU]);
const pikachu = game.scene.getPlayerPokemon(); const pikachu = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon(); const enemy = game.scene.getEnemyPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.THUNDERBOLT)); game.doAttack(getMovePosition(game.scene, 0, Moves.THUNDERBOLT));
@ -46,8 +46,8 @@ describe("Weather - Strong Winds", () => {
it("electric type move is neutral for flying type pokemon", async () => { it("electric type move is neutral for flying type pokemon", async () => {
await game.startBattle([Species.PIKACHU]); await game.startBattle([Species.PIKACHU]);
const pikachu = game.scene.getPlayerPokemon(); const pikachu = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon(); const enemy = game.scene.getEnemyPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.THUNDERBOLT)); game.doAttack(getMovePosition(game.scene, 0, Moves.THUNDERBOLT));
@ -57,8 +57,8 @@ describe("Weather - Strong Winds", () => {
it("ice type move is neutral for flying type pokemon", async () => { it("ice type move is neutral for flying type pokemon", async () => {
await game.startBattle([Species.PIKACHU]); await game.startBattle([Species.PIKACHU]);
const pikachu = game.scene.getPlayerPokemon(); const pikachu = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon(); const enemy = game.scene.getEnemyPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.ICE_BEAM)); game.doAttack(getMovePosition(game.scene, 0, Moves.ICE_BEAM));
@ -68,8 +68,8 @@ describe("Weather - Strong Winds", () => {
it("rock type move is neutral for flying type pokemon", async () => { it("rock type move is neutral for flying type pokemon", async () => {
await game.startBattle([Species.PIKACHU]); await game.startBattle([Species.PIKACHU]);
const pikachu = game.scene.getPlayerPokemon(); const pikachu = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon(); const enemy = game.scene.getEnemyPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.ROCK_SLIDE)); game.doAttack(getMovePosition(game.scene, 0, Moves.ROCK_SLIDE));

View File

@ -90,7 +90,7 @@ describe("Test Battle Phase", () => {
it("newGame one-liner", async() => { it("newGame one-liner", async() => {
await game.startBattle(); await game.startBattle();
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name);
}, 20000); }, 20000);
it("do attack wave 3 - single battle - regular - OHKO", async() => { it("do attack wave 3 - single battle - regular - OHKO", async() => {
@ -218,7 +218,7 @@ describe("Test Battle Phase", () => {
Species.CHARIZARD, Species.CHARIZARD,
]); ]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name);
}, 20000); }, 20000);
it("1vs1", async() => { it("1vs1", async() => {
@ -230,7 +230,7 @@ describe("Test Battle Phase", () => {
Species.BLASTOISE, Species.BLASTOISE,
]); ]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name);
}, 20000); }, 20000);
it("2vs2", async() => { it("2vs2", async() => {
@ -244,7 +244,7 @@ describe("Test Battle Phase", () => {
Species.CHARIZARD, Species.CHARIZARD,
]); ]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name);
}, 20000); }, 20000);
it("4vs2", async() => { it("4vs2", async() => {
@ -260,7 +260,7 @@ describe("Test Battle Phase", () => {
Species.GABITE, Species.GABITE,
]); ]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name);
}, 20000); }, 20000);
it("kill opponent pokemon", async() => { it("kill opponent pokemon", async() => {
@ -342,7 +342,7 @@ describe("Test Battle Phase", () => {
.startingHeldItems([{ name: "TEMP_STAT_BOOSTER", type: TempBattleStat.ACC }]); .startingHeldItems([{ name: "TEMP_STAT_BOOSTER", type: TempBattleStat.ACC }]);
await game.startBattle(); await game.startBattle();
game.scene.getPlayerPokemon().hp = 1; game.scene.getPlayerPokemon()!.hp = 1;
game.doAttack(getMovePosition(game.scene, 0, moveToUse)); game.doAttack(getMovePosition(game.scene, 0, moveToUse));
await game.phaseInterceptor.to(BattleEndPhase); await game.phaseInterceptor.to(BattleEndPhase);

View File

@ -40,7 +40,7 @@ describe("Test Battle Phase", () => {
Species.CHARIZARD, Species.CHARIZARD,
]); ]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name);
}, 20000); }, 20000);
it("startBattle 2vs2 boss", async() => { it("startBattle 2vs2 boss", async() => {
@ -52,7 +52,7 @@ describe("Test Battle Phase", () => {
Species.CHARIZARD, Species.CHARIZARD,
]); ]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name);
}, 20000); }, 20000);
it("startBattle 2vs2 trainer", async() => { it("startBattle 2vs2 trainer", async() => {
@ -64,7 +64,7 @@ describe("Test Battle Phase", () => {
Species.CHARIZARD, Species.CHARIZARD,
]); ]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name);
}, 20000); }, 20000);
it("startBattle 2vs1 trainer", async() => { it("startBattle 2vs1 trainer", async() => {
@ -76,7 +76,7 @@ describe("Test Battle Phase", () => {
Species.CHARIZARD, Species.CHARIZARD,
]); ]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name);
}, 20000); }, 20000);
it("startBattle 2vs1 rival", async() => { it("startBattle 2vs1 rival", async() => {
@ -88,7 +88,7 @@ describe("Test Battle Phase", () => {
Species.CHARIZARD, Species.CHARIZARD,
]); ]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name);
}, 20000); }, 20000);
it("startBattle 2vs2 rival", async() => { it("startBattle 2vs2 rival", async() => {
@ -100,7 +100,7 @@ describe("Test Battle Phase", () => {
Species.CHARIZARD, Species.CHARIZARD,
]); ]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name);
}, 20000); }, 20000);
it("startBattle 1vs1 trainer", async() => { it("startBattle 1vs1 trainer", async() => {
@ -111,7 +111,7 @@ describe("Test Battle Phase", () => {
Species.BLASTOISE, Species.BLASTOISE,
]); ]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name);
}, 20000); }, 20000);
it("startBattle 2vs2 trainer", async() => { it("startBattle 2vs2 trainer", async() => {
@ -123,7 +123,7 @@ describe("Test Battle Phase", () => {
Species.CHARIZARD, Species.CHARIZARD,
]); ]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name);
}, 20000); }, 20000);
it("startBattle 4vs2 trainer", async() => { it("startBattle 4vs2 trainer", async() => {
@ -137,7 +137,7 @@ describe("Test Battle Phase", () => {
Species.GABITE, Species.GABITE,
]); ]);
expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND);
expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name);
}, 20000); }, 20000);
}); });

View File

@ -6,7 +6,7 @@ import { StatChangePhase } from "#app/phases.js";
import { BattleStat } from "#app/data/battle-stat.js"; import { BattleStat } from "#app/data/battle-stat.js";
import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js";
jest.mock("#app/battle-scene.js"); vi.mock("#app/battle-scene.js");
describe("BattlerTag - OctolockTag", () => { describe("BattlerTag - OctolockTag", () => {
describe("lapse behavior", () => { describe("lapse behavior", () => {
@ -49,7 +49,7 @@ describe("BattlerTag - OctolockTag", () => {
it("cannot be added to pokemon who are octolocked", { timeout: 2000 }, async => { it("cannot be added to pokemon who are octolocked", { timeout: 2000 }, async => {
const mockPokemon = { const mockPokemon = {
getTag: vi.fn().mockReturnValue(new BattlerTag(null, null, null, null)) as Pokemon["getTag"], getTag: vi.fn().mockReturnValue(new BattlerTag(null!, null!, null!, null!)) as Pokemon["getTag"],
} as Pokemon; } as Pokemon;
const subject = new OctolockTag(1); const subject = new OctolockTag(1);

View File

@ -27,7 +27,7 @@ describe("BattlerTag - StockpilingTag", () => {
expect((phase as StatChangePhase)["levels"]).toEqual(1); expect((phase as StatChangePhase)["levels"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF])); expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF]));
(phase as StatChangePhase)["onChange"](mockPokemon, [BattleStat.DEF, BattleStat.SPDEF], [1, 1]); (phase as StatChangePhase)["onChange"]!(mockPokemon, [BattleStat.DEF, BattleStat.SPDEF], [1, 1]);
}); });
subject.onAdd(mockPokemon); subject.onAdd(mockPokemon);
@ -54,7 +54,7 @@ describe("BattlerTag - StockpilingTag", () => {
expect((phase as StatChangePhase)["levels"]).toEqual(1); expect((phase as StatChangePhase)["levels"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF])); expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF]));
(phase as StatChangePhase)["onChange"](mockPokemon, [BattleStat.DEF, BattleStat.SPDEF], [1, 1]); (phase as StatChangePhase)["onChange"]!(mockPokemon, [BattleStat.DEF, BattleStat.SPDEF], [1, 1]);
}); });
subject.onAdd(mockPokemon); subject.onAdd(mockPokemon);
@ -79,7 +79,7 @@ describe("BattlerTag - StockpilingTag", () => {
expect((phase as StatChangePhase)["levels"]).toEqual(1); expect((phase as StatChangePhase)["levels"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF])); expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF]));
(phase as StatChangePhase)["onChange"](mockPokemon, [BattleStat.DEF, BattleStat.SPDEF], [1, 1]); (phase as StatChangePhase)["onChange"]!(mockPokemon, [BattleStat.DEF, BattleStat.SPDEF], [1, 1]);
}); });
subject.onOverlap(mockPokemon); subject.onOverlap(mockPokemon);
@ -109,7 +109,7 @@ describe("BattlerTag - StockpilingTag", () => {
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF])); expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF]));
// def doesn't change // def doesn't change
(phase as StatChangePhase)["onChange"](mockPokemon, [BattleStat.SPDEF], [1]); (phase as StatChangePhase)["onChange"]!(mockPokemon, [BattleStat.SPDEF], [1]);
}); });
subject.onAdd(mockPokemon); subject.onAdd(mockPokemon);
@ -121,7 +121,7 @@ describe("BattlerTag - StockpilingTag", () => {
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF])); expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF]));
// def doesn't change // def doesn't change
(phase as StatChangePhase)["onChange"](mockPokemon, [BattleStat.SPDEF], [1]); (phase as StatChangePhase)["onChange"]!(mockPokemon, [BattleStat.SPDEF], [1]);
}); });
subject.onOverlap(mockPokemon); subject.onOverlap(mockPokemon);

View File

@ -64,7 +64,7 @@ describe("Evolution", () => {
it("should handle illegal abilityIndex values", async () => { it("should handle illegal abilityIndex values", async () => {
await game.runToSummon([Species.SQUIRTLE]); await game.runToSummon([Species.SQUIRTLE]);
const squirtle = game.scene.getPlayerPokemon(); const squirtle = game.scene.getPlayerPokemon()!;
squirtle.abilityIndex = 5; squirtle.abilityIndex = 5;
squirtle.evolve(pokemonEvolutions[Species.SQUIRTLE][0], squirtle.getSpeciesForm()); squirtle.evolve(pokemonEvolutions[Species.SQUIRTLE][0], squirtle.getSpeciesForm());
@ -74,7 +74,7 @@ describe("Evolution", () => {
it("should handle nincada's unique evolution", async () => { it("should handle nincada's unique evolution", async () => {
await game.runToSummon([Species.NINCADA]); await game.runToSummon([Species.NINCADA]);
const nincada = game.scene.getPlayerPokemon(); const nincada = game.scene.getPlayerPokemon()!;
nincada.abilityIndex = 2; nincada.abilityIndex = 2;
nincada.evolve(pokemonEvolutions[Species.NINCADA][0], nincada.getSpeciesForm()); nincada.evolve(pokemonEvolutions[Species.NINCADA][0], nincada.getSpeciesForm());

View File

@ -34,14 +34,14 @@ describe("Evolution tests", () => {
* 1, 2 or 3, it's a 4 family maushold * 1, 2 or 3, it's a 4 family maushold
*/ */
await game.startBattle([Species.TANDEMAUS]); // starts us off with a tandemaus await game.startBattle([Species.TANDEMAUS]); // starts us off with a tandemaus
const playerPokemon = game.scene.getPlayerPokemon(); const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.level = 25; // tandemaus evolves at level 25 playerPokemon.level = 25; // tandemaus evolves at level 25
vi.spyOn(Utils, "randSeedInt").mockReturnValue(0); // setting the random generator to be 0 to force a three family maushold vi.spyOn(Utils, "randSeedInt").mockReturnValue(0); // setting the random generator to be 0 to force a three family maushold
const threeForm = playerPokemon.getEvolution(); const threeForm = playerPokemon.getEvolution()!;
expect(threeForm.evoFormKey).toBe("three"); // as per pokemon-forms, the evoFormKey for 3 family mausholds is "three" expect(threeForm.evoFormKey).toBe("three"); // as per pokemon-forms, the evoFormKey for 3 family mausholds is "three"
for (let f = 1; f < 4; f++) { for (let f = 1; f < 4; f++) {
vi.spyOn(Utils, "randSeedInt").mockReturnValue(f); // setting the random generator to 1, 2 and 3 to force 4 family mausholds vi.spyOn(Utils, "randSeedInt").mockReturnValue(f); // setting the random generator to 1, 2 and 3 to force 4 family mausholds
const fourForm = playerPokemon.getEvolution(); const fourForm = playerPokemon.getEvolution()!;
expect(fourForm.evoFormKey).toBe(null); // meanwhile, according to the pokemon-forms, the evoFormKey for a 4 family maushold is null expect(fourForm.evoFormKey).toBe(null); // meanwhile, according to the pokemon-forms, the evoFormKey for a 4 family maushold is null
} }
}, 5000); }, 5000);

View File

@ -0,0 +1,31 @@
import { Species } from "#app/enums/species.js";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import GameManager from "../utils/gameManager";
describe("Spec - Pokemon", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
});
it("should not crash when trying to set status of undefined", async () => {
await game.runToSummon([Species.ABRA]);
const pkm = game.scene.getPlayerPokemon()!;
expect(pkm).toBeDefined();
expect(pkm.trySetStatus(undefined)).toBe(true);
});
});

View File

@ -35,7 +35,7 @@ describe("Final Boss", () => {
expect(game.scene.currentBattle.waveIndex).toBe(FinalWave.Classic); expect(game.scene.currentBattle.waveIndex).toBe(FinalWave.Classic);
expect(game.scene.arena.biomeType).toBe(Biome.END); expect(game.scene.arena.biomeType).toBe(Biome.END);
expect(game.scene.getEnemyPokemon().species.speciesId).toBe(Species.ETERNATUS); expect(game.scene.getEnemyPokemon()!.species.speciesId).toBe(Species.ETERNATUS);
}); });
it("should NOT spawn Eternatus before wave 200 in END biome", async () => { it("should NOT spawn Eternatus before wave 200 in END biome", async () => {
@ -44,7 +44,7 @@ describe("Final Boss", () => {
expect(game.scene.currentBattle.waveIndex).not.toBe(FinalWave.Classic); expect(game.scene.currentBattle.waveIndex).not.toBe(FinalWave.Classic);
expect(game.scene.arena.biomeType).toBe(Biome.END); expect(game.scene.arena.biomeType).toBe(Biome.END);
expect(game.scene.getEnemyPokemon().species.speciesId).not.toBe(Species.ETERNATUS); expect(game.scene.getEnemyPokemon()!.species.speciesId).not.toBe(Species.ETERNATUS);
}); });
it("should NOT spawn Eternatus outside of END biome", async () => { it("should NOT spawn Eternatus outside of END biome", async () => {
@ -53,7 +53,7 @@ describe("Final Boss", () => {
expect(game.scene.currentBattle.waveIndex).toBe(FinalWave.Classic); expect(game.scene.currentBattle.waveIndex).toBe(FinalWave.Classic);
expect(game.scene.arena.biomeType).not.toBe(Biome.END); expect(game.scene.arena.biomeType).not.toBe(Biome.END);
expect(game.scene.getEnemyPokemon().species.speciesId).not.toBe(Species.ETERNATUS); expect(game.scene.getEnemyPokemon()!.species.speciesId).not.toBe(Species.ETERNATUS);
}); });
it.todo("should change form on direct hit down to last boss fragment", () => {}); it.todo("should change form on direct hit down to last boss fragment", () => {});

View File

@ -24,7 +24,7 @@ describe("Internals", () => {
it("should provide Eevee with 3 defined abilities", async () => { it("should provide Eevee with 3 defined abilities", async () => {
await game.runToSummon([Species.EEVEE]); await game.runToSummon([Species.EEVEE]);
const eevee = game.scene.getPlayerPokemon(); const eevee = game.scene.getPlayerPokemon()!;
expect(eevee.getSpeciesForm().getAbilityCount()).toBe(3); expect(eevee.getSpeciesForm().getAbilityCount()).toBe(3);
@ -35,7 +35,7 @@ describe("Internals", () => {
it("should set Eeeve abilityIndex between 0-2", async () => { it("should set Eeeve abilityIndex between 0-2", async () => {
await game.runToSummon([Species.EEVEE]); await game.runToSummon([Species.EEVEE]);
const eevee = game.scene.getPlayerPokemon(); const eevee = game.scene.getPlayerPokemon()!;
expect(eevee.abilityIndex).toBeGreaterThanOrEqual(0); expect(eevee.abilityIndex).toBeGreaterThanOrEqual(0);
expect(eevee.abilityIndex).toBeLessThanOrEqual(2); expect(eevee.abilityIndex).toBeLessThanOrEqual(2);

View File

@ -31,7 +31,7 @@ describe("EXP Modifier Items", () => {
game.override.startingHeldItems([{name: "LUCKY_EGG"}, {name: "GOLDEN_EGG"}]); game.override.startingHeldItems([{name: "LUCKY_EGG"}, {name: "GOLDEN_EGG"}]);
await game.startBattle(); await game.startBattle();
const partyMember = game.scene.getPlayerPokemon(); const partyMember = game.scene.getPlayerPokemon()!;
const modifierBonusExp = new Utils.NumberHolder(1); const modifierBonusExp = new Utils.NumberHolder(1);
partyMember.scene.applyModifiers(PokemonExpBoosterModifier, true, partyMember, modifierBonusExp); partyMember.scene.applyModifiers(PokemonExpBoosterModifier, true, partyMember, modifierBonusExp);
expect(modifierBonusExp.value).toBe(2.4); expect(modifierBonusExp.value).toBe(2.4);

View File

@ -56,7 +56,7 @@ describe("Items - Leek", () => {
Species.FARFETCHD Species.FARFETCHD
]); ]);
const partyMember = game.scene.getPlayerPokemon(); const partyMember = game.scene.getPlayerPokemon()!;
// Making sure modifier is not applied without holding item // Making sure modifier is not applied without holding item
const critLevel = new Utils.IntegerHolder(0); const critLevel = new Utils.IntegerHolder(0);
@ -76,7 +76,7 @@ describe("Items - Leek", () => {
Species.GALAR_FARFETCHD Species.GALAR_FARFETCHD
]); ]);
const partyMember = game.scene.getPlayerPokemon(); const partyMember = game.scene.getPlayerPokemon()!;
// Making sure modifier is not applied without holding item // Making sure modifier is not applied without holding item
const critLevel = new Utils.IntegerHolder(0); const critLevel = new Utils.IntegerHolder(0);
@ -96,7 +96,7 @@ describe("Items - Leek", () => {
Species.SIRFETCHD Species.SIRFETCHD
]); ]);
const partyMember = game.scene.getPlayerPokemon(); const partyMember = game.scene.getPlayerPokemon()!;
// Making sure modifier is not applied without holding item // Making sure modifier is not applied without holding item
const critLevel = new Utils.IntegerHolder(0); const critLevel = new Utils.IntegerHolder(0);
@ -186,7 +186,7 @@ describe("Items - Leek", () => {
Species.PIKACHU Species.PIKACHU
]); ]);
const partyMember = game.scene.getPlayerPokemon(); const partyMember = game.scene.getPlayerPokemon()!;
// Making sure modifier is not applied without holding item // Making sure modifier is not applied without holding item
const critLevel = new Utils.IntegerHolder(0); const critLevel = new Utils.IntegerHolder(0);

View File

@ -40,7 +40,7 @@ describe("Items - Leftovers", () => {
// Make sure leftovers are there // Make sure leftovers are there
expect(game.scene.modifiers[0].type.id).toBe("LEFTOVERS"); expect(game.scene.modifiers[0].type.id).toBe("LEFTOVERS");
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
// We should have full hp // We should have full hp
expect(leadPokemon.isFullHp()).toBe(true); expect(leadPokemon.isFullHp()).toBe(true);

View File

@ -83,7 +83,7 @@ describe("Items - Light Ball", () => {
expect(spAtkValue.value / spAtkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["LIGHT_BALL"]).newModifier(partyMember), true); partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], ["LIGHT_BALL"])!.newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue);
@ -122,7 +122,7 @@ describe("Items - Light Ball", () => {
expect(spAtkValue.value / spAtkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["LIGHT_BALL"]).newModifier(partyMember), true); partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], ["LIGHT_BALL"])!.newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue);
@ -161,7 +161,7 @@ describe("Items - Light Ball", () => {
expect(spAtkValue.value / spAtkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["LIGHT_BALL"]).newModifier(partyMember), true); partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], ["LIGHT_BALL"])!.newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue);
@ -189,7 +189,7 @@ describe("Items - Light Ball", () => {
expect(spAtkValue.value / spAtkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["LIGHT_BALL"]).newModifier(partyMember), true); partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], ["LIGHT_BALL"])!.newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue);

View File

@ -79,7 +79,7 @@ describe("Items - Metal Powder", () => {
expect(defValue.value / defStat).toBe(1); expect(defValue.value / defStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["METAL_POWDER"]).newModifier(partyMember), true); partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], ["METAL_POWDER"])!.newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue);
expect(defValue.value / defStat).toBe(2); expect(defValue.value / defStat).toBe(2);
@ -112,7 +112,7 @@ describe("Items - Metal Powder", () => {
expect(defValue.value / defStat).toBe(1); expect(defValue.value / defStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["METAL_POWDER"]).newModifier(partyMember), true); partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], ["METAL_POWDER"])!.newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue);
expect(defValue.value / defStat).toBe(2); expect(defValue.value / defStat).toBe(2);
@ -145,7 +145,7 @@ describe("Items - Metal Powder", () => {
expect(defValue.value / defStat).toBe(1); expect(defValue.value / defStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["METAL_POWDER"]).newModifier(partyMember), true); partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], ["METAL_POWDER"])!.newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue);
expect(defValue.value / defStat).toBe(2); expect(defValue.value / defStat).toBe(2);
@ -167,7 +167,7 @@ describe("Items - Metal Powder", () => {
expect(defValue.value / defStat).toBe(1); expect(defValue.value / defStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["METAL_POWDER"]).newModifier(partyMember), true); partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], ["METAL_POWDER"])!.newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue);
expect(defValue.value / defStat).toBe(1); expect(defValue.value / defStat).toBe(1);

View File

@ -79,7 +79,7 @@ describe("Items - Quick Powder", () => {
expect(spdValue.value / spdStat).toBe(1); expect(spdValue.value / spdStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["QUICK_POWDER"]).newModifier(partyMember), true); partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], ["QUICK_POWDER"])!.newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue);
expect(spdValue.value / spdStat).toBe(2); expect(spdValue.value / spdStat).toBe(2);
@ -112,7 +112,7 @@ describe("Items - Quick Powder", () => {
expect(spdValue.value / spdStat).toBe(1); expect(spdValue.value / spdStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["QUICK_POWDER"]).newModifier(partyMember), true); partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], ["QUICK_POWDER"])!.newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue);
expect(spdValue.value / spdStat).toBe(2); expect(spdValue.value / spdStat).toBe(2);
@ -145,7 +145,7 @@ describe("Items - Quick Powder", () => {
expect(spdValue.value / spdStat).toBe(1); expect(spdValue.value / spdStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["QUICK_POWDER"]).newModifier(partyMember), true); partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], ["QUICK_POWDER"])!.newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue);
expect(spdValue.value / spdStat).toBe(2); expect(spdValue.value / spdStat).toBe(2);
@ -167,7 +167,7 @@ describe("Items - Quick Powder", () => {
expect(spdValue.value / spdStat).toBe(1); expect(spdValue.value / spdStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["QUICK_POWDER"]).newModifier(partyMember), true); partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], ["QUICK_POWDER"])!.newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue);
expect(spdValue.value / spdStat).toBe(1); expect(spdValue.value / spdStat).toBe(1);

View File

@ -55,7 +55,7 @@ describe("Items - Scope Lens", () => {
Species.GASTLY Species.GASTLY
]); ]);
const partyMember = game.scene.getPlayerPokemon(); const partyMember = game.scene.getPlayerPokemon()!;
// Making sure modifier is not applied without holding item // Making sure modifier is not applied without holding item
const critLevel = new Utils.IntegerHolder(0); const critLevel = new Utils.IntegerHolder(0);

View File

@ -79,7 +79,7 @@ describe("Items - Thick Club", () => {
expect(atkValue.value / atkStat).toBe(1); expect(atkValue.value / atkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["THICK_CLUB"]).newModifier(partyMember), true); partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], ["THICK_CLUB"])!.newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(2); expect(atkValue.value / atkStat).toBe(2);
@ -101,7 +101,7 @@ describe("Items - Thick Club", () => {
expect(atkValue.value / atkStat).toBe(1); expect(atkValue.value / atkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["THICK_CLUB"]).newModifier(partyMember), true); partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], ["THICK_CLUB"])!.newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(2); expect(atkValue.value / atkStat).toBe(2);
@ -123,7 +123,7 @@ describe("Items - Thick Club", () => {
expect(atkValue.value / atkStat).toBe(1); expect(atkValue.value / atkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["THICK_CLUB"]).newModifier(partyMember), true); partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], ["THICK_CLUB"])!.newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(2); expect(atkValue.value / atkStat).toBe(2);
@ -160,7 +160,7 @@ describe("Items - Thick Club", () => {
expect(atkValue.value / atkStat).toBe(1); expect(atkValue.value / atkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["THICK_CLUB"]).newModifier(partyMember), true); partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], ["THICK_CLUB"])!.newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(2); expect(atkValue.value / atkStat).toBe(2);
@ -197,7 +197,7 @@ describe("Items - Thick Club", () => {
expect(atkValue.value / atkStat).toBe(1); expect(atkValue.value / atkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["THICK_CLUB"]).newModifier(partyMember), true); partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], ["THICK_CLUB"])!.newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(2); expect(atkValue.value / atkStat).toBe(2);
@ -219,7 +219,7 @@ describe("Items - Thick Club", () => {
expect(atkValue.value / atkStat).toBe(1); expect(atkValue.value / atkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies // Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["THICK_CLUB"]).newModifier(partyMember), true); partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], ["THICK_CLUB"])!.newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(1); expect(atkValue.value / atkStat).toBe(1);

View File

@ -72,6 +72,6 @@ describe("Items - Toxic orb", () => {
const message2 = game.textInterceptor.getLatestMessage(); const message2 = game.textInterceptor.getLatestMessage();
expect(message2).toContain("is hurt"); expect(message2).toContain("is hurt");
expect(message2).toContain("by poison"); expect(message2).toContain("by poison");
expect(game.scene.getParty()[0].status.effect).toBe(StatusEffect.TOXIC); expect(game.scene.getParty()[0].status!.effect).toBe(StatusEffect.TOXIC);
}, 20000); }, 20000);
}); });

View File

@ -37,18 +37,18 @@ describe("terrain", () => {
mockI18next(); mockI18next();
const text = getTerrainStartMessage(terrainType); const text = getTerrainStartMessage(terrainType);
expect(text).toBe(undefined); expect(text).toBeNull();
}); });
it("should return the clear text", () => { it("should return the clear text", () => {
mockI18next(); mockI18next();
const text = getTerrainClearMessage(terrainType); const text = getTerrainClearMessage(terrainType);
expect(text).toBe(undefined); expect(text).toBeNull();
}); });
it("should return the block text", async () => { it("should return the block text", async () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const pokemon = game.scene.getPlayerPokemon(); const pokemon = game.scene.getPlayerPokemon()!;
mockI18next(); mockI18next();
const text = getTerrainBlockMessage(pokemon, terrainType); const text = getTerrainBlockMessage(pokemon, terrainType);
expect(text).toBe("terrain:defaultBlockMessage"); expect(text).toBe("terrain:defaultBlockMessage");
@ -80,7 +80,7 @@ describe("terrain", () => {
it("should return the block text", async () => { it("should return the block text", async () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const pokemon = game.scene.getPlayerPokemon(); const pokemon = game.scene.getPlayerPokemon()!;
mockI18next(); mockI18next();
const text = getTerrainBlockMessage(pokemon, terrainType); const text = getTerrainBlockMessage(pokemon, terrainType);
expect(text).toBe("terrain:mistyBlockMessage"); expect(text).toBe("terrain:mistyBlockMessage");
@ -112,7 +112,7 @@ describe("terrain", () => {
it("should return the block text", async () => { it("should return the block text", async () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const pokemon = game.scene.getPlayerPokemon(); const pokemon = game.scene.getPlayerPokemon()!;
mockI18next(); mockI18next();
const text = getTerrainBlockMessage(pokemon, terrainType); const text = getTerrainBlockMessage(pokemon, terrainType);
expect(text).toBe("terrain:defaultBlockMessage"); expect(text).toBe("terrain:defaultBlockMessage");
@ -144,7 +144,7 @@ describe("terrain", () => {
it("should return the block text", async () => { it("should return the block text", async () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const pokemon = game.scene.getPlayerPokemon(); const pokemon = game.scene.getPlayerPokemon()!;
mockI18next(); mockI18next();
const text = getTerrainBlockMessage(pokemon, terrainType); const text = getTerrainBlockMessage(pokemon, terrainType);
expect(text).toBe("terrain:defaultBlockMessage"); expect(text).toBe("terrain:defaultBlockMessage");
@ -176,7 +176,7 @@ describe("terrain", () => {
it("should return the block text", async () => { it("should return the block text", async () => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const pokemon = game.scene.getPlayerPokemon(); const pokemon = game.scene.getPlayerPokemon()!;
mockI18next(); mockI18next();
const text = getTerrainBlockMessage(pokemon, terrainType); const text = getTerrainBlockMessage(pokemon, terrainType);
expect(text).toBe("terrain:defaultBlockMessage"); expect(text).toBe("terrain:defaultBlockMessage");

View File

@ -43,9 +43,9 @@ describe("Moves - Astonish", () => {
async () => { async () => {
await game.startBattle([Species.MEOWSCARADA]); await game.startBattle([Species.MEOWSCARADA]);
const leadPokemon = game.scene.getPlayerPokemon(); const leadPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.ASTONISH)); game.doAttack(getMovePosition(game.scene, 0, Moves.ASTONISH));

View File

@ -49,7 +49,7 @@ describe("Moves - Aurora Veil", () => {
game.doAttack(getMovePosition(game.scene, 0, moveToUse)); game.doAttack(getMovePosition(game.scene, 0, moveToUse));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
const mockedDmg = getMockedMoveDamage(game.scene.getEnemyPokemon(), game.scene.getPlayerPokemon(), allMoves[moveToUse]); const mockedDmg = getMockedMoveDamage(game.scene.getEnemyPokemon()!, game.scene.getPlayerPokemon()!, allMoves[moveToUse]);
expect(mockedDmg).toBe(allMoves[moveToUse].power * singleBattleMultiplier); expect(mockedDmg).toBe(allMoves[moveToUse].power * singleBattleMultiplier);
}); });
@ -64,7 +64,7 @@ describe("Moves - Aurora Veil", () => {
game.doAttack(getMovePosition(game.scene, 1, moveToUse)); game.doAttack(getMovePosition(game.scene, 1, moveToUse));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
const mockedDmg = getMockedMoveDamage(game.scene.getEnemyPokemon(), game.scene.getPlayerPokemon(), allMoves[moveToUse]); const mockedDmg = getMockedMoveDamage(game.scene.getEnemyPokemon()!, game.scene.getPlayerPokemon()!, allMoves[moveToUse]);
expect(mockedDmg).toBe(allMoves[moveToUse].power * doubleBattleMultiplier); expect(mockedDmg).toBe(allMoves[moveToUse].power * doubleBattleMultiplier);
}); });
@ -77,7 +77,7 @@ describe("Moves - Aurora Veil", () => {
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
const mockedDmg = getMockedMoveDamage(game.scene.getEnemyPokemon(), game.scene.getPlayerPokemon(), allMoves[moveToUse]); const mockedDmg = getMockedMoveDamage(game.scene.getEnemyPokemon()!, game.scene.getPlayerPokemon()!, allMoves[moveToUse]);
expect(mockedDmg).toBe(allMoves[moveToUse].power * singleBattleMultiplier); expect(mockedDmg).toBe(allMoves[moveToUse].power * singleBattleMultiplier);
}); });
@ -92,7 +92,7 @@ describe("Moves - Aurora Veil", () => {
game.doAttack(getMovePosition(game.scene, 1, moveToUse)); game.doAttack(getMovePosition(game.scene, 1, moveToUse));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
const mockedDmg = getMockedMoveDamage(game.scene.getEnemyPokemon(), game.scene.getPlayerPokemon(), allMoves[moveToUse]); const mockedDmg = getMockedMoveDamage(game.scene.getEnemyPokemon()!, game.scene.getPlayerPokemon()!, allMoves[moveToUse]);
expect(mockedDmg).toBe(allMoves[moveToUse].power * doubleBattleMultiplier); expect(mockedDmg).toBe(allMoves[moveToUse].power * doubleBattleMultiplier);
}); });

View File

@ -45,7 +45,7 @@ describe("Moves - Baton Pass", () => {
// round 1 - buff // round 1 - buff
game.doAttack(getMovePosition(game.scene, 0, Moves.NASTY_PLOT)); game.doAttack(getMovePosition(game.scene, 0, Moves.NASTY_PLOT));
await game.toNextTurn(); await game.toNextTurn();
expect(game.scene.getPlayerPokemon().summonData.battleStats[BattleStat.SPATK]).toEqual(2); expect(game.scene.getPlayerPokemon()!.summonData.battleStats[BattleStat.SPATK]).toEqual(2);
// round 2 - baton pass // round 2 - baton pass
game.doAttack(getMovePosition(game.scene, 0, Moves.BATON_PASS)); game.doAttack(getMovePosition(game.scene, 0, Moves.BATON_PASS));
@ -53,8 +53,9 @@ describe("Moves - Baton Pass", () => {
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
// assert // assert
expect(game.scene.getPlayerPokemon().species.speciesId).toEqual(Species.SHUCKLE); const playerPkm = game.scene.getPlayerPokemon()!;
expect(game.scene.getPlayerPokemon().summonData.battleStats[BattleStat.SPATK]).toEqual(2); expect(playerPkm.species.speciesId).toEqual(Species.SHUCKLE);
expect(playerPkm.summonData.battleStats[BattleStat.SPATK]).toEqual(2);
}, 20000); }, 20000);
it("passes stat stage buffs when AI uses it", async() => { it("passes stat stage buffs when AI uses it", async() => {
@ -72,17 +73,17 @@ describe("Moves - Baton Pass", () => {
await game.toNextTurn(); await game.toNextTurn();
// round 2 - baton pass // round 2 - baton pass
game.scene.getEnemyPokemon().hp = 100; game.scene.getEnemyPokemon()!.hp = 100;
game.override.enemyMoveset(new Array(4).fill(Moves.BATON_PASS)); game.override.enemyMoveset(new Array(4).fill(Moves.BATON_PASS));
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(PostSummonPhase, false); await game.phaseInterceptor.to(PostSummonPhase, false);
// assert // assert
// check buffs are still there // check buffs are still there
expect(game.scene.getEnemyPokemon().summonData.battleStats[BattleStat.SPATK]).toEqual(2); expect(game.scene.getEnemyPokemon()!.summonData.battleStats[BattleStat.SPATK]).toEqual(2);
// confirm that a switch actually happened. can't use species because I // confirm that a switch actually happened. can't use species because I
// can't find a way to override trainer parties with more than 1 pokemon species // can't find a way to override trainer parties with more than 1 pokemon species
expect(game.scene.getEnemyPokemon().hp).not.toEqual(100); expect(game.scene.getEnemyPokemon()!.hp).not.toEqual(100);
expect(game.phaseInterceptor.log.slice(-4)).toEqual([ expect(game.phaseInterceptor.log.slice(-4)).toEqual([
"MoveEffectPhase", "MoveEffectPhase",
"SwitchSummonPhase", "SwitchSummonPhase",

Some files were not shown because too many files have changed in this diff Show More