Merge branch 'pagefaultgames:beta' into candy-friendship-changes
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5.2 KiB |
@ -4112,9 +4112,13 @@ export class PostBattleAbAttr extends AbAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PostBattleLootAbAttr extends PostBattleAbAttr {
|
export class PostBattleLootAbAttr extends PostBattleAbAttr {
|
||||||
|
/**
|
||||||
|
* @param args - `[0]`: boolean for if the battle ended in a victory
|
||||||
|
* @returns `true` if successful
|
||||||
|
*/
|
||||||
applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
||||||
const postBattleLoot = pokemon.scene.currentBattle.postBattleLoot;
|
const postBattleLoot = pokemon.scene.currentBattle.postBattleLoot;
|
||||||
if (!simulated && postBattleLoot.length) {
|
if (!simulated && postBattleLoot.length && args[0]) {
|
||||||
const randItem = Utils.randSeedItem(postBattleLoot);
|
const randItem = Utils.randSeedItem(postBattleLoot);
|
||||||
//@ts-ignore - TODO see below
|
//@ts-ignore - TODO see below
|
||||||
if (pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, true, 1, true, undefined, false)) { // TODO: fix. This is a promise!?
|
if (pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, true, 1, true, undefined, false)) { // TODO: fix. This is a promise!?
|
||||||
@ -4575,14 +4579,15 @@ export class MoneyAbAttr extends PostBattleAbAttr {
|
|||||||
/**
|
/**
|
||||||
* @param pokemon {@linkcode Pokemon} that is the user of this ability.
|
* @param pokemon {@linkcode Pokemon} that is the user of this ability.
|
||||||
* @param passive N/A
|
* @param passive N/A
|
||||||
* @param args N/A
|
* @param args - `[0]`: boolean for if the battle ended in a victory
|
||||||
* @returns true
|
* @returns `true` if successful
|
||||||
*/
|
*/
|
||||||
applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
||||||
if (!simulated) {
|
if (!simulated && args[0]) {
|
||||||
pokemon.scene.currentBattle.moneyScattered += pokemon.scene.getWaveMoneyAmount(0.2);
|
pokemon.scene.currentBattle.moneyScattered += pokemon.scene.getWaveMoneyAmount(0.2);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4590,13 +4595,12 @@ export class MoneyAbAttr extends PostBattleAbAttr {
|
|||||||
* Applies a stat change after a Pokémon is summoned,
|
* Applies a stat change after a Pokémon is summoned,
|
||||||
* conditioned on the presence of a specific arena tag.
|
* conditioned on the presence of a specific arena tag.
|
||||||
*
|
*
|
||||||
* @extends {PostSummonStatStageChangeAbAttr}
|
* @extends PostSummonStatStageChangeAbAttr
|
||||||
*/
|
*/
|
||||||
export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageChangeAbAttr {
|
export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageChangeAbAttr {
|
||||||
/**
|
/**
|
||||||
* The type of arena tag that conditions the stat change.
|
* The type of arena tag that conditions the stat change.
|
||||||
* @private
|
* @private
|
||||||
* @type {ArenaTagType}
|
|
||||||
*/
|
*/
|
||||||
private tagType: ArenaTagType;
|
private tagType: ArenaTagType;
|
||||||
|
|
||||||
@ -4972,7 +4976,7 @@ class ForceSwitchOutHelper {
|
|||||||
pokemon.scene.clearEnemyHeldItemModifiers();
|
pokemon.scene.clearEnemyHeldItemModifiers();
|
||||||
|
|
||||||
if (switchOutTarget.hp) {
|
if (switchOutTarget.hp) {
|
||||||
pokemon.scene.pushPhase(new BattleEndPhase(pokemon.scene));
|
pokemon.scene.pushPhase(new BattleEndPhase(pokemon.scene, false));
|
||||||
pokemon.scene.pushPhase(new NewBattlePhase(pokemon.scene));
|
pokemon.scene.pushPhase(new NewBattlePhase(pokemon.scene));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5930,10 +5934,10 @@ export function initAbilities() {
|
|||||||
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, Stat.DEF, 1),
|
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, Stat.DEF, 1),
|
||||||
new Ability(Abilities.WIMP_OUT, 7)
|
new Ability(Abilities.WIMP_OUT, 7)
|
||||||
.attr(PostDamageForceSwitchAbAttr)
|
.attr(PostDamageForceSwitchAbAttr)
|
||||||
.edgeCase(), // Should not trigger when hurting itself in confusion
|
.edgeCase(), // Should not trigger when hurting itself in confusion, causes Fake Out to fail turn 1 and succeed turn 2 if pokemon is switched out before battle start via playing in Switch Mode
|
||||||
new Ability(Abilities.EMERGENCY_EXIT, 7)
|
new Ability(Abilities.EMERGENCY_EXIT, 7)
|
||||||
.attr(PostDamageForceSwitchAbAttr)
|
.attr(PostDamageForceSwitchAbAttr)
|
||||||
.edgeCase(), // Should not trigger when hurting itself in confusion
|
.edgeCase(), // Should not trigger when hurting itself in confusion, causes Fake Out to fail turn 1 and succeed turn 2 if pokemon is switched out before battle start via playing in Switch Mode
|
||||||
new Ability(Abilities.WATER_COMPACTION, 7)
|
new Ability(Abilities.WATER_COMPACTION, 7)
|
||||||
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === Type.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2),
|
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === Type.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2),
|
||||||
new Ability(Abilities.MERCILESS, 7)
|
new Ability(Abilities.MERCILESS, 7)
|
||||||
|
@ -6082,7 +6082,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
|||||||
user.scene.clearEnemyHeldItemModifiers();
|
user.scene.clearEnemyHeldItemModifiers();
|
||||||
|
|
||||||
if (switchOutTarget.hp) {
|
if (switchOutTarget.hp) {
|
||||||
user.scene.pushPhase(new BattleEndPhase(user.scene));
|
user.scene.pushPhase(new BattleEndPhase(user.scene, false));
|
||||||
user.scene.pushPhase(new NewBattlePhase(user.scene));
|
user.scene.pushPhase(new NewBattlePhase(user.scene));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -731,7 +731,7 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase:
|
|||||||
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));
|
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));
|
||||||
scene.pushPhase(new EggLapsePhase(scene));
|
scene.pushPhase(new EggLapsePhase(scene));
|
||||||
} else if (!scene.getEnemyParty().find(p => encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) {
|
} else if (!scene.getEnemyParty().find(p => encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) {
|
||||||
scene.pushPhase(new BattleEndPhase(scene));
|
scene.pushPhase(new BattleEndPhase(scene, true));
|
||||||
if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
|
if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
|
||||||
scene.pushPhase(new TrainerVictoryPhase(scene));
|
scene.pushPhase(new TrainerVictoryPhase(scene));
|
||||||
}
|
}
|
||||||
|
@ -707,7 +707,7 @@ export class Arena {
|
|||||||
case Biome.METROPOLIS:
|
case Biome.METROPOLIS:
|
||||||
return 141.470;
|
return 141.470;
|
||||||
case Biome.FOREST:
|
case Biome.FOREST:
|
||||||
return 4.294;
|
return 0.341;
|
||||||
case Biome.SEA:
|
case Biome.SEA:
|
||||||
return 0.024;
|
return 0.024;
|
||||||
case Biome.SWAMP:
|
case Biome.SWAMP:
|
||||||
|
@ -52,7 +52,7 @@ export class AttemptRunPhase extends PokemonPhase {
|
|||||||
enemyPokemon.trySetStatus(StatusEffect.FAINT);
|
enemyPokemon.trySetStatus(StatusEffect.FAINT);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.scene.pushPhase(new BattleEndPhase(this.scene));
|
this.scene.pushPhase(new BattleEndPhase(this.scene, false));
|
||||||
this.scene.pushPhase(new NewBattlePhase(this.scene));
|
this.scene.pushPhase(new NewBattlePhase(this.scene));
|
||||||
} else {
|
} else {
|
||||||
playerPokemon.turnData.failedRunAway = true;
|
playerPokemon.turnData.failedRunAway = true;
|
||||||
|
@ -8,7 +8,7 @@ export class BattleEndPhase extends BattlePhase {
|
|||||||
/** If true, will increment battles won */
|
/** If true, will increment battles won */
|
||||||
isVictory: boolean;
|
isVictory: boolean;
|
||||||
|
|
||||||
constructor(scene: BattleScene, isVictory: boolean = true) {
|
constructor(scene: BattleScene, isVictory: boolean) {
|
||||||
super(scene);
|
super(scene);
|
||||||
|
|
||||||
this.isVictory = isVictory;
|
this.isVictory = isVictory;
|
||||||
@ -17,16 +17,17 @@ export class BattleEndPhase extends BattlePhase {
|
|||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
|
this.scene.gameData.gameStats.battles++;
|
||||||
|
if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex + 1 > this.scene.gameData.gameStats.highestEndlessWave) {
|
||||||
|
this.scene.gameData.gameStats.highestEndlessWave = this.scene.currentBattle.waveIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isVictory) {
|
if (this.isVictory) {
|
||||||
this.scene.currentBattle.addBattleScore(this.scene);
|
this.scene.currentBattle.addBattleScore(this.scene);
|
||||||
|
|
||||||
this.scene.gameData.gameStats.battles++;
|
|
||||||
if (this.scene.currentBattle.trainer) {
|
if (this.scene.currentBattle.trainer) {
|
||||||
this.scene.gameData.gameStats.trainersDefeated++;
|
this.scene.gameData.gameStats.trainersDefeated++;
|
||||||
}
|
}
|
||||||
if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex + 1 > this.scene.gameData.gameStats.highestEndlessWave) {
|
|
||||||
this.scene.gameData.gameStats.highestEndlessWave = this.scene.currentBattle.waveIndex + 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Endless graceful end
|
// Endless graceful end
|
||||||
@ -42,7 +43,7 @@ export class BattleEndPhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const pokemon of this.scene.getPokemonAllowedInBattle()) {
|
for (const pokemon of this.scene.getPokemonAllowedInBattle()) {
|
||||||
applyPostBattleAbAttrs(PostBattleAbAttr, pokemon);
|
applyPostBattleAbAttrs(PostBattleAbAttr, pokemon, false, this.isVictory);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.scene.currentBattle.moneyScattered) {
|
if (this.scene.currentBattle.moneyScattered) {
|
||||||
|
@ -34,7 +34,7 @@ export class EggLapsePhase extends Phase {
|
|||||||
if (eggsToHatchCount >= this.minEggsToSkip && this.scene.eggSkipPreference === 1) {
|
if (eggsToHatchCount >= this.minEggsToSkip && this.scene.eggSkipPreference === 1) {
|
||||||
this.scene.ui.showText(i18next.t("battle:eggHatching"), 0, () => {
|
this.scene.ui.showText(i18next.t("battle:eggHatching"), 0, () => {
|
||||||
// show prompt for skip, blocking inputs for 1 second
|
// show prompt for skip, blocking inputs for 1 second
|
||||||
this.scene.ui.showText(i18next.t("battle:eggSkipPrompt"), 0);
|
this.scene.ui.showText(i18next.t("battle:eggSkipPrompt", { eggsToHatch: eggsToHatchCount }), 0);
|
||||||
this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => {
|
this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => {
|
||||||
this.hatchEggsSkipped(eggsToHatch);
|
this.hatchEggsSkipped(eggsToHatch);
|
||||||
this.showSummary();
|
this.showSummary();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import BattleScene from "#app/battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr } from "#app/data/ability";
|
import { applyPreSwitchOutAbAttrs, PostDamageForceSwitchAbAttr, PreSwitchOutAbAttr } from "#app/data/ability";
|
||||||
import { allMoves, ForceSwitchOutAttr } from "#app/data/move";
|
import { allMoves, ForceSwitchOutAttr } from "#app/data/move";
|
||||||
import { getPokeballTintColor } from "#app/data/pokeball";
|
import { getPokeballTintColor } from "#app/data/pokeball";
|
||||||
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
|
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
|
||||||
@ -166,10 +166,11 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
|
|
||||||
const currentCommand = pokemon.scene.currentBattle.turnCommands[this.fieldIndex]?.command;
|
const currentCommand = pokemon.scene.currentBattle.turnCommands[this.fieldIndex]?.command;
|
||||||
const lastPokemonIsForceSwitchedAndNotFainted = lastUsedMove?.hasAttr(ForceSwitchOutAttr) && !this.lastPokemon.isFainted();
|
const lastPokemonIsForceSwitchedAndNotFainted = lastUsedMove?.hasAttr(ForceSwitchOutAttr) && !this.lastPokemon.isFainted();
|
||||||
|
const lastPokemonHasForceSwitchAbAttr = this.lastPokemon.hasAbilityWithAttr(PostDamageForceSwitchAbAttr) && !this.lastPokemon.isFainted();
|
||||||
|
|
||||||
// Compensate for turn spent summoning
|
// Compensate for turn spent summoning
|
||||||
// Or compensate for force switch move if switched out pokemon is not fainted
|
// Or compensate for force switch move if switched out pokemon is not fainted
|
||||||
if (currentCommand === Command.POKEMON || lastPokemonIsForceSwitchedAndNotFainted) {
|
if (currentCommand === Command.POKEMON || lastPokemonIsForceSwitchedAndNotFainted || lastPokemonHasForceSwitchAbAttr) {
|
||||||
pokemon.battleSummonData.turnCount--;
|
pokemon.battleSummonData.turnCount--;
|
||||||
pokemon.battleSummonData.waveTurnCount--;
|
pokemon.battleSummonData.waveTurnCount--;
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ export class VictoryPhase extends PokemonPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.scene.getEnemyParty().find(p => this.scene.currentBattle.battleType === BattleType.WILD ? p.isOnField() : !p?.isFainted(true))) {
|
if (!this.scene.getEnemyParty().find(p => this.scene.currentBattle.battleType === BattleType.WILD ? p.isOnField() : !p?.isFainted(true))) {
|
||||||
this.scene.pushPhase(new BattleEndPhase(this.scene));
|
this.scene.pushPhase(new BattleEndPhase(this.scene, true));
|
||||||
if (this.scene.currentBattle.battleType === BattleType.TRAINER) {
|
if (this.scene.currentBattle.battleType === BattleType.TRAINER) {
|
||||||
this.scene.pushPhase(new TrainerVictoryPhase(this.scene));
|
this.scene.pushPhase(new TrainerVictoryPhase(this.scene));
|
||||||
}
|
}
|
||||||
|
74
src/test/abilities/honey_gather.test.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import type { CommandPhase } from "#app/phases/command-phase";
|
||||||
|
import { Command } from "#app/ui/command-ui-handler";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Honey Gather", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.moveset([ Moves.SPLASH, Moves.ROAR, Moves.THUNDERBOLT ])
|
||||||
|
.startingLevel(100)
|
||||||
|
.ability(Abilities.HONEY_GATHER)
|
||||||
|
.passiveAbility(Abilities.RUN_AWAY)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should give money when winning a battle", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.MILOTIC ]);
|
||||||
|
game.scene.money = 1000;
|
||||||
|
|
||||||
|
game.move.select(Moves.THUNDERBOLT);
|
||||||
|
await game.toNextWave();
|
||||||
|
|
||||||
|
expect(game.scene.money).toBeGreaterThan(1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not give money when the enemy pokemon flees", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.MILOTIC ]);
|
||||||
|
game.scene.money = 1000;
|
||||||
|
|
||||||
|
game.move.select(Moves.ROAR);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(game.scene.money).toBe(1000);
|
||||||
|
expect(game.scene.currentBattle.waveIndex).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not give money when the player flees", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.MILOTIC ]);
|
||||||
|
game.scene.money = 1000;
|
||||||
|
|
||||||
|
// something weird is going on with the test framework, so this is required to prevent a crash
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
vi.spyOn(enemy, "scene", "get").mockReturnValue(game.scene);
|
||||||
|
|
||||||
|
const commandPhase = game.scene.getCurrentPhase() as CommandPhase;
|
||||||
|
commandPhase.handleCommand(Command.RUN, 0);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(game.scene.money).toBe(1000);
|
||||||
|
expect(game.scene.currentBattle.waveIndex).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
@ -26,7 +26,7 @@ describe("Moves - Shell Side Arm", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
game.override
|
game.override
|
||||||
.moveset([ Moves.SHELL_SIDE_ARM ])
|
.moveset([ Moves.SHELL_SIDE_ARM, Moves.SPLASH ])
|
||||||
.battleType("single")
|
.battleType("single")
|
||||||
.startingLevel(100)
|
.startingLevel(100)
|
||||||
.enemyLevel(100)
|
.enemyLevel(100)
|
||||||
@ -69,6 +69,9 @@ describe("Moves - Shell Side Arm", () => {
|
|||||||
|
|
||||||
vi.spyOn(shellSideArmAttr, "apply");
|
vi.spyOn(shellSideArmAttr, "apply");
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
game.move.select(Moves.SHELL_SIDE_ARM);
|
game.move.select(Moves.SHELL_SIDE_ARM);
|
||||||
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|