mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-06 08:22:16 +02:00
Merge branch 'beta' into MergeFix
This commit is contained in:
commit
0567b4db73
101
create-test-boilerplate.js
Normal file
101
create-test-boilerplate.js
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This script creates a test boilerplate file for a move or ability.
|
||||||
|
* @param {string} type - The type of test to create. Either "move" or "ability".
|
||||||
|
* @param {string} fileName - The name of the file to create.
|
||||||
|
* @example npm run create-test move tackle
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Get the directory name of the current module file
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
// Get the arguments from the command line
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const type = args[0]; // "move" or "ability"
|
||||||
|
let fileName = args[1]; // The file name
|
||||||
|
|
||||||
|
if (!type || !fileName) {
|
||||||
|
console.error('Please provide both a type ("move" or "ability") and a file name.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert fileName from to snake_case if camelCase is given
|
||||||
|
fileName = fileName.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();
|
||||||
|
|
||||||
|
// Format the description for the test case
|
||||||
|
const formattedName = fileName
|
||||||
|
.replace(/_/g, ' ')
|
||||||
|
.replace(/\b\w/g, char => char.toUpperCase());
|
||||||
|
|
||||||
|
// Determine the directory based on the type
|
||||||
|
let dir;
|
||||||
|
let description;
|
||||||
|
if (type === 'move') {
|
||||||
|
dir = path.join(__dirname, 'src', 'test', 'moves');
|
||||||
|
description = `Moves - ${formattedName}`;
|
||||||
|
} else if (type === 'ability') {
|
||||||
|
dir = path.join(__dirname, 'src', 'test', 'abilities');
|
||||||
|
description = `Abilities - ${formattedName}`;
|
||||||
|
} else {
|
||||||
|
console.error('Invalid type. Please use "move" or "ability".');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the directory exists
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the file with the given name
|
||||||
|
const filePath = path.join(dir, `${fileName}.test.ts`);
|
||||||
|
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
console.error(`File "${fileName}.test.ts" already exists.`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the content template
|
||||||
|
const content = `import { Abilities } from "#enums/abilities";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import { SPLASH_ONLY } from "#test/utils/testUtils";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, it } from "vitest";
|
||||||
|
|
||||||
|
describe("${description}", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
const TIMEOUT = 20 * 1000;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.battleType("single")
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(SPLASH_ONLY);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("test case", async () => {
|
||||||
|
// await game.classicMode.startBattle();
|
||||||
|
// game.move.select();
|
||||||
|
}, TIMEOUT);
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Write the template content to the file
|
||||||
|
fs.writeFileSync(filePath, content, 'utf8');
|
||||||
|
|
||||||
|
console.log(`File created at: ${filePath}`);
|
@ -18,7 +18,8 @@
|
|||||||
"eslint-ci": "eslint .",
|
"eslint-ci": "eslint .",
|
||||||
"docs": "typedoc",
|
"docs": "typedoc",
|
||||||
"depcruise": "depcruise src",
|
"depcruise": "depcruise src",
|
||||||
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg"
|
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
|
||||||
|
"create-test": "node ./create-test-boilerplate.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.3.0",
|
"@eslint/js": "^9.3.0",
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
Before Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.9 KiB |
@ -8,7 +8,7 @@ import { Weather, WeatherType } from "./weather";
|
|||||||
import { BattlerTag, GroundedTag, GulpMissileTag, SemiInvulnerableTag } from "./battler-tags";
|
import { BattlerTag, GroundedTag, GulpMissileTag, SemiInvulnerableTag } from "./battler-tags";
|
||||||
import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
|
import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
|
||||||
import { Gender } from "./gender";
|
import { Gender } from "./gender";
|
||||||
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, MoveAttr, MultiHitAttr, ChargeAttr, SacrificialAttr, SacrificialAttrOnHit, NeutralDamageAgainstFlyingTypeMultiplierAttr } from "./move";
|
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, MoveAttr, MultiHitAttr, ChargeAttr, SacrificialAttr, SacrificialAttrOnHit, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./move";
|
||||||
import { ArenaTagSide, ArenaTrapTag } from "./arena-tag";
|
import { ArenaTagSide, ArenaTrapTag } from "./arena-tag";
|
||||||
import { Stat, getStatName } from "./pokemon-stat";
|
import { Stat, getStatName } from "./pokemon-stat";
|
||||||
import { BerryModifier, PokemonHeldItemModifier } from "../modifier/modifier";
|
import { BerryModifier, PokemonHeldItemModifier } from "../modifier/modifier";
|
||||||
@ -475,6 +475,47 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute implementing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Tera_Shell_(Ability) | Tera Shell}
|
||||||
|
* When the source is at full HP, incoming attacks will have a maximum 0.5x type effectiveness multiplier.
|
||||||
|
* @extends PreDefendAbAttr
|
||||||
|
*/
|
||||||
|
export class FullHpResistTypeAbAttr extends PreDefendAbAttr {
|
||||||
|
/**
|
||||||
|
* Reduces a type multiplier to 0.5 if the source is at full HP.
|
||||||
|
* @param pokemon {@linkcode Pokemon} the Pokemon with this ability
|
||||||
|
* @param passive n/a
|
||||||
|
* @param simulated n/a (this doesn't change game state)
|
||||||
|
* @param attacker n/a
|
||||||
|
* @param move {@linkcode Move} the move being used on the source
|
||||||
|
* @param cancelled n/a
|
||||||
|
* @param args `[0]` a container for the move's current type effectiveness multiplier
|
||||||
|
* @returns `true` if the move's effectiveness is reduced; `false` otherwise
|
||||||
|
*/
|
||||||
|
applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, args: any[]): boolean | Promise<boolean> {
|
||||||
|
const typeMultiplier = args[0];
|
||||||
|
if (!(typeMultiplier && typeMultiplier instanceof Utils.NumberHolder)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (move && move.hasAttr(FixedDamageAttr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pokemon.isFullHp() && typeMultiplier.value > 0.5) {
|
||||||
|
typeMultiplier.value = 0.5;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
|
||||||
|
return i18next.t("abilityTriggers:fullHpResistType", {
|
||||||
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class PostDefendAbAttr extends AbAttr {
|
export class PostDefendAbAttr extends AbAttr {
|
||||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise<boolean> {
|
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise<boolean> {
|
||||||
return false;
|
return false;
|
||||||
@ -2387,7 +2428,7 @@ export class PostSummonWeatherSuppressedFormChangeAbAttr extends PostSummonAbAtt
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers weather-based form change when summoned into an active weather.
|
* Triggers weather-based form change when summoned into an active weather.
|
||||||
* Used by Forecast.
|
* Used by Forecast and Flower Gift.
|
||||||
* @extends PostSummonAbAttr
|
* @extends PostSummonAbAttr
|
||||||
*/
|
*/
|
||||||
export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr {
|
export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr {
|
||||||
@ -2410,7 +2451,10 @@ export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr {
|
|||||||
* @returns whether the form change was triggered
|
* @returns whether the form change was triggered
|
||||||
*/
|
*/
|
||||||
applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
||||||
if (pokemon.species.speciesId === Species.CASTFORM && this.ability === Abilities.FORECAST) {
|
const isCastformWithForecast = (pokemon.species.speciesId === Species.CASTFORM && this.ability === Abilities.FORECAST);
|
||||||
|
const isCherrimWithFlowerGift = (pokemon.species.speciesId === Species.CHERRIM && this.ability === Abilities.FLOWER_GIFT);
|
||||||
|
|
||||||
|
if (isCastformWithForecast || isCherrimWithFlowerGift) {
|
||||||
if (simulated) {
|
if (simulated) {
|
||||||
return simulated;
|
return simulated;
|
||||||
}
|
}
|
||||||
@ -3083,37 +3127,41 @@ export class PostWeatherChangeAbAttr extends AbAttr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers weather-based form change when weather changes.
|
* Triggers weather-based form change when weather changes.
|
||||||
* Used by Forecast.
|
* Used by Forecast and Flower Gift.
|
||||||
* @extends PostWeatherChangeAbAttr
|
* @extends PostWeatherChangeAbAttr
|
||||||
*/
|
*/
|
||||||
export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr {
|
export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr {
|
||||||
private ability: Abilities;
|
private ability: Abilities;
|
||||||
|
private formRevertingWeathers: WeatherType[];
|
||||||
|
|
||||||
constructor(ability: Abilities) {
|
constructor(ability: Abilities, formRevertingWeathers: WeatherType[]) {
|
||||||
super(false);
|
super(false);
|
||||||
|
|
||||||
this.ability = ability;
|
this.ability = ability;
|
||||||
|
this.formRevertingWeathers = formRevertingWeathers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls {@linkcode Arena.triggerWeatherBasedFormChangesToNormal | triggerWeatherBasedFormChangesToNormal} when the
|
* Calls {@linkcode Arena.triggerWeatherBasedFormChangesToNormal | triggerWeatherBasedFormChangesToNormal} when the
|
||||||
* weather changed to form-reverting weather, otherwise calls {@linkcode Arena.triggerWeatherBasedFormChanges | triggerWeatherBasedFormChanges}
|
* weather changed to form-reverting weather, otherwise calls {@linkcode Arena.triggerWeatherBasedFormChanges | triggerWeatherBasedFormChanges}
|
||||||
* @param {Pokemon} pokemon the Pokemon that changed the weather
|
* @param {Pokemon} pokemon the Pokemon with this ability
|
||||||
* @param passive n/a
|
* @param passive n/a
|
||||||
* @param weather n/a
|
* @param weather n/a
|
||||||
* @param args n/a
|
* @param args n/a
|
||||||
* @returns whether the form change was triggered
|
* @returns whether the form change was triggered
|
||||||
*/
|
*/
|
||||||
applyPostWeatherChange(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: WeatherType, args: any[]): boolean {
|
applyPostWeatherChange(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: WeatherType, args: any[]): boolean {
|
||||||
if (pokemon.species.speciesId === Species.CASTFORM && this.ability === Abilities.FORECAST) {
|
const isCastformWithForecast = (pokemon.species.speciesId === Species.CASTFORM && this.ability === Abilities.FORECAST);
|
||||||
|
const isCherrimWithFlowerGift = (pokemon.species.speciesId === Species.CHERRIM && this.ability === Abilities.FLOWER_GIFT);
|
||||||
|
|
||||||
|
if (isCastformWithForecast || isCherrimWithFlowerGift) {
|
||||||
if (simulated) {
|
if (simulated) {
|
||||||
return simulated;
|
return simulated;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formRevertingWeathers: WeatherType[] = [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG ];
|
|
||||||
const weatherType = pokemon.scene.arena.weather?.weatherType;
|
const weatherType = pokemon.scene.arena.weather?.weatherType;
|
||||||
|
|
||||||
if (weatherType && formRevertingWeathers.includes(weatherType)) {
|
if (weatherType && this.formRevertingWeathers.includes(weatherType)) {
|
||||||
pokemon.scene.arena.triggerWeatherBasedFormChangesToNormal();
|
pokemon.scene.arena.triggerWeatherBasedFormChangesToNormal();
|
||||||
} else {
|
} else {
|
||||||
pokemon.scene.arena.triggerWeatherBasedFormChanges();
|
pokemon.scene.arena.triggerWeatherBasedFormChanges();
|
||||||
@ -4703,7 +4751,8 @@ function setAbilityRevealed(pokemon: Pokemon): void {
|
|||||||
*/
|
*/
|
||||||
function getPokemonWithWeatherBasedForms(scene: BattleScene) {
|
function getPokemonWithWeatherBasedForms(scene: BattleScene) {
|
||||||
return scene.getField(true).filter(p =>
|
return scene.getField(true).filter(p =>
|
||||||
p.hasAbility(Abilities.FORECAST) && p.species.speciesId === Species.CASTFORM
|
(p.hasAbility(Abilities.FORECAST) && p.species.speciesId === Species.CASTFORM)
|
||||||
|
|| (p.hasAbility(Abilities.FLOWER_GIFT) && p.species.speciesId === Species.CHERRIM)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4902,7 +4951,7 @@ export function initAbilities() {
|
|||||||
.attr(UncopiableAbilityAbAttr)
|
.attr(UncopiableAbilityAbAttr)
|
||||||
.attr(NoFusionAbilityAbAttr)
|
.attr(NoFusionAbilityAbAttr)
|
||||||
.attr(PostSummonFormChangeByWeatherAbAttr, Abilities.FORECAST)
|
.attr(PostSummonFormChangeByWeatherAbAttr, Abilities.FORECAST)
|
||||||
.attr(PostWeatherChangeFormChangeAbAttr, Abilities.FORECAST),
|
.attr(PostWeatherChangeFormChangeAbAttr, Abilities.FORECAST, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG ]),
|
||||||
new Ability(Abilities.STICKY_HOLD, 3)
|
new Ability(Abilities.STICKY_HOLD, 3)
|
||||||
.attr(BlockItemTheftAbAttr)
|
.attr(BlockItemTheftAbAttr)
|
||||||
.bypassFaint()
|
.bypassFaint()
|
||||||
@ -5095,8 +5144,10 @@ export function initAbilities() {
|
|||||||
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), BattleStatMultiplierAbAttr, BattleStat.SPDEF, 1.5)
|
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), BattleStatMultiplierAbAttr, BattleStat.SPDEF, 1.5)
|
||||||
.attr(UncopiableAbilityAbAttr)
|
.attr(UncopiableAbilityAbAttr)
|
||||||
.attr(NoFusionAbilityAbAttr)
|
.attr(NoFusionAbilityAbAttr)
|
||||||
.ignorable()
|
.attr(PostSummonFormChangeByWeatherAbAttr, Abilities.FLOWER_GIFT)
|
||||||
.partial(),
|
.attr(PostWeatherChangeFormChangeAbAttr, Abilities.FLOWER_GIFT, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG, WeatherType.HAIL, WeatherType.HEAVY_RAIN, WeatherType.SNOW, WeatherType.RAIN ])
|
||||||
|
.partial() // Should also boosts stats of ally
|
||||||
|
.ignorable(),
|
||||||
new Ability(Abilities.BAD_DREAMS, 4)
|
new Ability(Abilities.BAD_DREAMS, 4)
|
||||||
.attr(PostTurnHurtIfSleepingAbAttr),
|
.attr(PostTurnHurtIfSleepingAbAttr),
|
||||||
new Ability(Abilities.PICKPOCKET, 5)
|
new Ability(Abilities.PICKPOCKET, 5)
|
||||||
@ -5761,10 +5812,10 @@ export function initAbilities() {
|
|||||||
.attr(NoTransformAbilityAbAttr)
|
.attr(NoTransformAbilityAbAttr)
|
||||||
.attr(NoFusionAbilityAbAttr),
|
.attr(NoFusionAbilityAbAttr),
|
||||||
new Ability(Abilities.TERA_SHELL, 9)
|
new Ability(Abilities.TERA_SHELL, 9)
|
||||||
|
.attr(FullHpResistTypeAbAttr)
|
||||||
.attr(UncopiableAbilityAbAttr)
|
.attr(UncopiableAbilityAbAttr)
|
||||||
.attr(UnswappableAbilityAbAttr)
|
.attr(UnswappableAbilityAbAttr)
|
||||||
.ignorable()
|
.ignorable(),
|
||||||
.unimplemented(),
|
|
||||||
new Ability(Abilities.TERAFORM_ZERO, 9)
|
new Ability(Abilities.TERAFORM_ZERO, 9)
|
||||||
.attr(UncopiableAbilityAbAttr)
|
.attr(UncopiableAbilityAbAttr)
|
||||||
.attr(UnswappableAbilityAbAttr)
|
.attr(UnswappableAbilityAbAttr)
|
||||||
|
@ -788,10 +788,10 @@ export abstract class BattleAnim {
|
|||||||
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() && userSprite) {
|
if (!this.isHideUser() && userSprite) {
|
||||||
userSprite.setVisible(true);
|
this.user?.getSprite().setVisible(true); // using this.user to fix context loss due to isOppAnim swap (#481)
|
||||||
}
|
}
|
||||||
if (!this.isHideTarget() && (targetSprite !== userSprite || !this.isHideUser())) {
|
if (!this.isHideTarget() && (targetSprite !== userSprite || !this.isHideUser())) {
|
||||||
targetSprite.setVisible(true);
|
this.target?.getSprite().setVisible(true); // using this.target to fix context loss due to isOppAnim swap (#481)
|
||||||
}
|
}
|
||||||
for (const ms of Object.values(spriteCache).flat()) {
|
for (const ms of Object.values(spriteCache).flat()) {
|
||||||
if (ms) {
|
if (ms) {
|
||||||
|
@ -212,7 +212,7 @@ export class TrappedTag extends BattlerTag {
|
|||||||
|
|
||||||
canAdd(pokemon: Pokemon): boolean {
|
canAdd(pokemon: Pokemon): boolean {
|
||||||
const isGhost = pokemon.isOfType(Type.GHOST);
|
const isGhost = pokemon.isOfType(Type.GHOST);
|
||||||
const isTrapped = pokemon.getTag(BattlerTagType.TRAPPED);
|
const isTrapped = pokemon.getTag(TrappedTag);
|
||||||
|
|
||||||
return !isTrapped && !isGhost;
|
return !isTrapped && !isGhost;
|
||||||
}
|
}
|
||||||
@ -245,6 +245,23 @@ export class TrappedTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BattlerTag implementing No Retreat's trapping effect.
|
||||||
|
* This is treated separately from other trapping effects to prevent
|
||||||
|
* Ghost-type Pokemon from being able to reuse the move.
|
||||||
|
* @extends TrappedTag
|
||||||
|
*/
|
||||||
|
class NoRetreatTag extends TrappedTag {
|
||||||
|
constructor(sourceId: number) {
|
||||||
|
super(BattlerTagType.NO_RETREAT, BattlerTagLapseType.CUSTOM, 0, Moves.NO_RETREAT, sourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** overrides {@linkcode TrappedTag.apply}, removing the Ghost-type condition */
|
||||||
|
canAdd(pokemon: Pokemon): boolean {
|
||||||
|
return !pokemon.getTag(TrappedTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BattlerTag that represents the {@link https://bulbapedia.bulbagarden.net/wiki/Flinch Flinch} status condition
|
* BattlerTag that represents the {@link https://bulbapedia.bulbagarden.net/wiki/Flinch Flinch} status condition
|
||||||
*/
|
*/
|
||||||
@ -868,7 +885,7 @@ export abstract class DamagingTrapTag extends TrappedTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
canAdd(pokemon: Pokemon): boolean {
|
canAdd(pokemon: Pokemon): boolean {
|
||||||
return !pokemon.isOfType(Type.GHOST) && !pokemon.findTag(t => t instanceof DamagingTrapTag);
|
return !pokemon.getTag(TrappedTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
@ -1511,6 +1528,25 @@ export class CritBoostTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag for the effects of Dragon Cheer, which boosts the critical hit ratio of the user's allies.
|
||||||
|
* @extends {CritBoostTag}
|
||||||
|
*/
|
||||||
|
export class DragonCheerTag extends CritBoostTag {
|
||||||
|
/** The types of the user's ally when the tag is added */
|
||||||
|
public typesOnAdd: Type[];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(BattlerTagType.CRIT_BOOST, Moves.DRAGON_CHEER);
|
||||||
|
}
|
||||||
|
|
||||||
|
onAdd(pokemon: Pokemon): void {
|
||||||
|
super.onAdd(pokemon);
|
||||||
|
|
||||||
|
this.typesOnAdd = pokemon.getTypes(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class SaltCuredTag extends BattlerTag {
|
export class SaltCuredTag extends BattlerTag {
|
||||||
private sourceIndex: number;
|
private sourceIndex: number;
|
||||||
|
|
||||||
@ -1868,6 +1904,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
|||||||
return new DrowsyTag();
|
return new DrowsyTag();
|
||||||
case BattlerTagType.TRAPPED:
|
case BattlerTagType.TRAPPED:
|
||||||
return new TrappedTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
return new TrappedTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
||||||
|
case BattlerTagType.NO_RETREAT:
|
||||||
|
return new NoRetreatTag(sourceId);
|
||||||
case BattlerTagType.BIND:
|
case BattlerTagType.BIND:
|
||||||
return new BindTag(turnCount, sourceId);
|
return new BindTag(turnCount, sourceId);
|
||||||
case BattlerTagType.WRAP:
|
case BattlerTagType.WRAP:
|
||||||
@ -1927,6 +1965,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
|||||||
return new TypeBoostTag(tagType, sourceMove, Type.FIRE, 1.5, false);
|
return new TypeBoostTag(tagType, sourceMove, Type.FIRE, 1.5, false);
|
||||||
case BattlerTagType.CRIT_BOOST:
|
case BattlerTagType.CRIT_BOOST:
|
||||||
return new CritBoostTag(tagType, sourceMove);
|
return new CritBoostTag(tagType, sourceMove);
|
||||||
|
case BattlerTagType.DRAGON_CHEER:
|
||||||
|
return new DragonCheerTag();
|
||||||
case BattlerTagType.ALWAYS_CRIT:
|
case BattlerTagType.ALWAYS_CRIT:
|
||||||
case BattlerTagType.IGNORE_ACCURACY:
|
case BattlerTagType.IGNORE_ACCURACY:
|
||||||
return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove);
|
return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove);
|
||||||
|
@ -5889,9 +5889,9 @@ export class SwitchAbilitiesAttr extends MoveEffectAttr {
|
|||||||
target.summonData.ability = tempAbilityId;
|
target.summonData.ability = tempAbilityId;
|
||||||
|
|
||||||
user.scene.queueMessage(i18next.t("moveTriggers:swappedAbilitiesWithTarget", {pokemonName: getPokemonNameWithAffix(user)}));
|
user.scene.queueMessage(i18next.t("moveTriggers:swappedAbilitiesWithTarget", {pokemonName: getPokemonNameWithAffix(user)}));
|
||||||
// Swaps Forecast from Castform
|
// Swaps Forecast/Flower Gift from Castform/Cherrim
|
||||||
user.scene.arena.triggerWeatherBasedFormChangesToNormal();
|
user.scene.arena.triggerWeatherBasedFormChangesToNormal();
|
||||||
// Swaps Forecast to Castform (edge case)
|
// Swaps Forecast/Flower Gift to Castform/Cherrim (edge case)
|
||||||
user.scene.arena.triggerWeatherBasedFormChanges();
|
user.scene.arena.triggerWeatherBasedFormChanges();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -6038,6 +6038,57 @@ export class DestinyBondAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute to apply a battler tag to the target if they have had their stats boosted this turn.
|
||||||
|
* @extends AddBattlerTagAttr
|
||||||
|
*/
|
||||||
|
export class AddBattlerTagIfBoostedAttr extends AddBattlerTagAttr {
|
||||||
|
constructor(tag: BattlerTagType) {
|
||||||
|
super(tag, false, false, 2, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param user {@linkcode Pokemon} using this move
|
||||||
|
* @param target {@linkcode Pokemon} target of this move
|
||||||
|
* @param move {@linkcode Move} being used
|
||||||
|
* @param {any[]} args N/A
|
||||||
|
* @returns true
|
||||||
|
*/
|
||||||
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||||
|
if (target.turnData.battleStatsIncreased) {
|
||||||
|
super.apply(user, target, move, args);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute to apply a status effect to the target if they have had their stats boosted this turn.
|
||||||
|
* @extends MoveEffectAttr
|
||||||
|
*/
|
||||||
|
export class StatusIfBoostedAttr extends MoveEffectAttr {
|
||||||
|
public effect: StatusEffect;
|
||||||
|
|
||||||
|
constructor(effect: StatusEffect) {
|
||||||
|
super(true, MoveEffectTrigger.HIT);
|
||||||
|
this.effect = effect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param user {@linkcode Pokemon} using this move
|
||||||
|
* @param target {@linkcode Pokemon} target of this move
|
||||||
|
* @param move {@linkcode Move} N/A
|
||||||
|
* @param {any[]} args N/A
|
||||||
|
* @returns true
|
||||||
|
*/
|
||||||
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||||
|
if (target.turnData.battleStatsIncreased) {
|
||||||
|
target.trySetStatus(this.effect, true, user);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class LastResortAttr extends MoveAttr {
|
export class LastResortAttr extends MoveAttr {
|
||||||
getCondition(): MoveConditionFunc {
|
getCondition(): MoveConditionFunc {
|
||||||
return (user: Pokemon, target: Pokemon, move: Move) => {
|
return (user: Pokemon, target: Pokemon, move: Move) => {
|
||||||
@ -8503,7 +8554,7 @@ export function initMoves() {
|
|||||||
.partial(),
|
.partial(),
|
||||||
new SelfStatusMove(Moves.NO_RETREAT, Type.FIGHTING, -1, 5, -1, 0, 8)
|
new SelfStatusMove(Moves.NO_RETREAT, Type.FIGHTING, -1, 5, -1, 0, 8)
|
||||||
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true)
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, true, false, 1)
|
.attr(AddBattlerTagAttr, BattlerTagType.NO_RETREAT, true, false)
|
||||||
.condition((user, target, move) => user.getTag(TrappedTag)?.sourceMove !== Moves.NO_RETREAT), // fails if the user is currently trapped by No Retreat
|
.condition((user, target, move) => user.getTag(TrappedTag)?.sourceMove !== Moves.NO_RETREAT), // fails if the user is currently trapped by No Retreat
|
||||||
new StatusMove(Moves.TAR_SHOT, Type.ROCK, 100, 15, -1, 0, 8)
|
new StatusMove(Moves.TAR_SHOT, Type.ROCK, 100, 15, -1, 0, 8)
|
||||||
.attr(StatChangeAttr, BattleStat.SPD, -1)
|
.attr(StatChangeAttr, BattleStat.SPD, -1)
|
||||||
@ -8695,10 +8746,10 @@ export function initMoves() {
|
|||||||
new AttackMove(Moves.SKITTER_SMACK, Type.BUG, MoveCategory.PHYSICAL, 70, 90, 10, 100, 0, 8)
|
new AttackMove(Moves.SKITTER_SMACK, Type.BUG, MoveCategory.PHYSICAL, 70, 90, 10, 100, 0, 8)
|
||||||
.attr(StatChangeAttr, BattleStat.SPATK, -1),
|
.attr(StatChangeAttr, BattleStat.SPATK, -1),
|
||||||
new AttackMove(Moves.BURNING_JEALOUSY, Type.FIRE, MoveCategory.SPECIAL, 70, 100, 5, 100, 0, 8)
|
new AttackMove(Moves.BURNING_JEALOUSY, Type.FIRE, MoveCategory.SPECIAL, 70, 100, 5, 100, 0, 8)
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
.attr(StatusIfBoostedAttr, StatusEffect.BURN)
|
||||||
.partial(),
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||||
new AttackMove(Moves.LASH_OUT, Type.DARK, MoveCategory.PHYSICAL, 75, 100, 5, -1, 0, 8)
|
new AttackMove(Moves.LASH_OUT, Type.DARK, MoveCategory.PHYSICAL, 75, 100, 5, -1, 0, 8)
|
||||||
.partial(),
|
.attr(MovePowerMultiplierAttr, (user, target, move) => user.turnData.battleStatsDecreased ? 2 : 1),
|
||||||
new AttackMove(Moves.POLTERGEIST, Type.GHOST, MoveCategory.PHYSICAL, 110, 90, 5, -1, 0, 8)
|
new AttackMove(Moves.POLTERGEIST, Type.GHOST, MoveCategory.PHYSICAL, 110, 90, 5, -1, 0, 8)
|
||||||
.attr(AttackedByItemAttr)
|
.attr(AttackedByItemAttr)
|
||||||
.makesContact(false),
|
.makesContact(false),
|
||||||
@ -9144,12 +9195,11 @@ export function initMoves() {
|
|||||||
new AttackMove(Moves.HARD_PRESS, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 9)
|
new AttackMove(Moves.HARD_PRESS, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 9)
|
||||||
.attr(OpponentHighHpPowerAttr, 100),
|
.attr(OpponentHighHpPowerAttr, 100),
|
||||||
new StatusMove(Moves.DRAGON_CHEER, Type.DRAGON, -1, 15, -1, 0, 9)
|
new StatusMove(Moves.DRAGON_CHEER, Type.DRAGON, -1, 15, -1, 0, 9)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.CRIT_BOOST, false, true)
|
.attr(AddBattlerTagAttr, BattlerTagType.DRAGON_CHEER, false, true)
|
||||||
.target(MoveTarget.NEAR_ALLY)
|
.target(MoveTarget.NEAR_ALLY),
|
||||||
.partial(),
|
|
||||||
new AttackMove(Moves.ALLURING_VOICE, Type.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 9)
|
new AttackMove(Moves.ALLURING_VOICE, Type.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 9)
|
||||||
.soundBased()
|
.attr(AddBattlerTagIfBoostedAttr, BattlerTagType.CONFUSED)
|
||||||
.partial(),
|
.soundBased(),
|
||||||
new AttackMove(Moves.TEMPER_FLARE, Type.FIRE, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 9)
|
new AttackMove(Moves.TEMPER_FLARE, Type.FIRE, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 9)
|
||||||
.attr(MovePowerMultiplierAttr, (user, target, move) => user.getLastXMoves(2)[1]?.result === MoveResult.MISS || user.getLastXMoves(2)[1]?.result === MoveResult.FAIL ? 2 : 1),
|
.attr(MovePowerMultiplierAttr, (user, target, move) => user.getLastXMoves(2)[1]?.result === MoveResult.MISS || user.getLastXMoves(2)[1]?.result === MoveResult.FAIL ? 2 : 1),
|
||||||
new AttackMove(Moves.SUPERCELL_SLAM, Type.ELECTRIC, MoveCategory.PHYSICAL, 100, 95, 15, -1, 0, 9)
|
new AttackMove(Moves.SUPERCELL_SLAM, Type.ELECTRIC, MoveCategory.PHYSICAL, 100, 95, 15, -1, 0, 9)
|
||||||
|
@ -359,7 +359,7 @@ export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Class used for triggering form changes based on weather.
|
* Class used for triggering form changes based on weather.
|
||||||
* Used by Castform.
|
* Used by Castform and Cherrim.
|
||||||
* @extends SpeciesFormChangeTrigger
|
* @extends SpeciesFormChangeTrigger
|
||||||
*/
|
*/
|
||||||
export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger {
|
export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger {
|
||||||
@ -392,7 +392,7 @@ export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger {
|
|||||||
/**
|
/**
|
||||||
* Class used for reverting to the original form when the weather runs out
|
* Class used for reverting to the original form when the weather runs out
|
||||||
* or when the user loses the ability/is suppressed.
|
* or when the user loses the ability/is suppressed.
|
||||||
* Used by Castform.
|
* Used by Castform and Cherrim.
|
||||||
* @extends SpeciesFormChangeTrigger
|
* @extends SpeciesFormChangeTrigger
|
||||||
*/
|
*/
|
||||||
export class SpeciesFormChangeRevertWeatherFormTrigger extends SpeciesFormChangeTrigger {
|
export class SpeciesFormChangeRevertWeatherFormTrigger extends SpeciesFormChangeTrigger {
|
||||||
@ -930,6 +930,11 @@ export const pokemonFormChanges: PokemonFormChanges = {
|
|||||||
new SpeciesFormChange(Species.CASTFORM, "rainy", "", new SpeciesFormChangeActiveTrigger(), true),
|
new SpeciesFormChange(Species.CASTFORM, "rainy", "", new SpeciesFormChangeActiveTrigger(), true),
|
||||||
new SpeciesFormChange(Species.CASTFORM, "snowy", "", new SpeciesFormChangeActiveTrigger(), true),
|
new SpeciesFormChange(Species.CASTFORM, "snowy", "", new SpeciesFormChangeActiveTrigger(), true),
|
||||||
],
|
],
|
||||||
|
[Species.CHERRIM]: [
|
||||||
|
new SpeciesFormChange(Species.CHERRIM, "overcast", "sunshine", new SpeciesFormChangeWeatherTrigger(Abilities.FLOWER_GIFT, [ WeatherType.SUNNY, WeatherType.HARSH_SUN ]), true),
|
||||||
|
new SpeciesFormChange(Species.CHERRIM, "sunshine", "overcast", new SpeciesFormChangeRevertWeatherFormTrigger(Abilities.FLOWER_GIFT, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG, WeatherType.HAIL, WeatherType.HEAVY_RAIN, WeatherType.SNOW, WeatherType.RAIN ]), true),
|
||||||
|
new SpeciesFormChange(Species.CHERRIM, "sunshine", "overcast", new SpeciesFormChangeActiveTrigger(), true),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function initPokemonForms() {
|
export function initPokemonForms() {
|
||||||
|
@ -69,5 +69,7 @@ export enum BattlerTagType {
|
|||||||
GULP_MISSILE_ARROKUDA = "GULP_MISSILE_ARROKUDA",
|
GULP_MISSILE_ARROKUDA = "GULP_MISSILE_ARROKUDA",
|
||||||
GULP_MISSILE_PIKACHU = "GULP_MISSILE_PIKACHU",
|
GULP_MISSILE_PIKACHU = "GULP_MISSILE_PIKACHU",
|
||||||
BEAK_BLAST_CHARGING = "BEAK_BLAST_CHARGING",
|
BEAK_BLAST_CHARGING = "BEAK_BLAST_CHARGING",
|
||||||
SHELL_TRAP = "SHELL_TRAP"
|
SHELL_TRAP = "SHELL_TRAP",
|
||||||
|
DRAGON_CHEER = "DRAGON_CHEER",
|
||||||
|
NO_RETREAT = "NO_RETREAT",
|
||||||
}
|
}
|
||||||
|
@ -339,7 +339,10 @@ export class Arena {
|
|||||||
*/
|
*/
|
||||||
triggerWeatherBasedFormChanges(): void {
|
triggerWeatherBasedFormChanges(): void {
|
||||||
this.scene.getField(true).forEach( p => {
|
this.scene.getField(true).forEach( p => {
|
||||||
if (p.hasAbility(Abilities.FORECAST) && p.species.speciesId === Species.CASTFORM) {
|
const isCastformWithForecast = (p.hasAbility(Abilities.FORECAST) && p.species.speciesId === Species.CASTFORM);
|
||||||
|
const isCherrimWithFlowerGift = (p.hasAbility(Abilities.FLOWER_GIFT) && p.species.speciesId === Species.CHERRIM);
|
||||||
|
|
||||||
|
if (isCastformWithForecast || isCherrimWithFlowerGift) {
|
||||||
new ShowAbilityPhase(this.scene, p.getBattlerIndex());
|
new ShowAbilityPhase(this.scene, p.getBattlerIndex());
|
||||||
this.scene.triggerPokemonFormChange(p, SpeciesFormChangeWeatherTrigger);
|
this.scene.triggerPokemonFormChange(p, SpeciesFormChangeWeatherTrigger);
|
||||||
}
|
}
|
||||||
@ -351,7 +354,10 @@ export class Arena {
|
|||||||
*/
|
*/
|
||||||
triggerWeatherBasedFormChangesToNormal(): void {
|
triggerWeatherBasedFormChangesToNormal(): void {
|
||||||
this.scene.getField(true).forEach( p => {
|
this.scene.getField(true).forEach( p => {
|
||||||
if (p.hasAbility(Abilities.FORECAST, false, true) && p.species.speciesId === Species.CASTFORM) {
|
const isCastformWithForecast = (p.hasAbility(Abilities.FORECAST, false, true) && p.species.speciesId === Species.CASTFORM);
|
||||||
|
const isCherrimWithFlowerGift = (p.hasAbility(Abilities.FLOWER_GIFT, false, true) && p.species.speciesId === Species.CHERRIM);
|
||||||
|
|
||||||
|
if (isCastformWithForecast || isCherrimWithFlowerGift) {
|
||||||
new ShowAbilityPhase(this.scene, p.getBattlerIndex());
|
new ShowAbilityPhase(this.scene, p.getBattlerIndex());
|
||||||
return this.scene.triggerPokemonFormChange(p, SpeciesFormChangeRevertWeatherFormTrigger);
|
return this.scene.triggerPokemonFormChange(p, SpeciesFormChangeRevertWeatherFormTrigger);
|
||||||
}
|
}
|
||||||
|
@ -18,11 +18,11 @@ import { Status, StatusEffect, getRandomStatus } from "../data/status-effect";
|
|||||||
import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions";
|
import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions";
|
||||||
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms";
|
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms";
|
||||||
import { BattleStat } from "../data/battle-stat";
|
import { BattleStat } from "../data/battle-stat";
|
||||||
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, ExposedTag } from "../data/battler-tags";
|
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag } from "../data/battler-tags";
|
||||||
import { WeatherType } from "../data/weather";
|
import { WeatherType } from "../data/weather";
|
||||||
import { TempBattleStat } from "../data/temp-battle-stat";
|
import { TempBattleStat } from "../data/temp-battle-stat";
|
||||||
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag";
|
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag";
|
||||||
import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr, AddSecondStrikeAbAttr, IgnoreOpponentEvasionAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr } from "../data/ability";
|
import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr, AddSecondStrikeAbAttr, IgnoreOpponentEvasionAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "../data/ability";
|
||||||
import PokemonData from "../system/pokemon-data";
|
import PokemonData from "../system/pokemon-data";
|
||||||
import { BattlerIndex } from "../battle";
|
import { BattlerIndex } from "../battle";
|
||||||
import { Mode } from "../ui/ui";
|
import { Mode } from "../ui/ui";
|
||||||
@ -1210,6 +1210,28 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
return !!this.getTag(GroundedTag) || (!this.isOfType(Type.FLYING, true, true) && !this.hasAbility(Abilities.LEVITATE) && !this.getTag(BattlerTagType.MAGNET_RISEN) && !this.getTag(SemiInvulnerableTag));
|
return !!this.getTag(GroundedTag) || (!this.isOfType(Type.FLYING, true, true) && !this.hasAbility(Abilities.LEVITATE) && !this.getTag(BattlerTagType.MAGNET_RISEN) && !this.getTag(SemiInvulnerableTag));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether this Pokemon is prevented from running or switching due
|
||||||
|
* to effects from moves and/or abilities.
|
||||||
|
* @param trappedAbMessages `string[]` If defined, ability trigger messages
|
||||||
|
* (e.g. from Shadow Tag) are forwarded through this array.
|
||||||
|
* @param simulated `boolean` if `true`, applies abilities via simulated calls.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
isTrapped(trappedAbMessages: string[] = [], simulated: boolean = true): boolean {
|
||||||
|
if (this.isOfType(Type.GHOST)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trappedByAbility = new Utils.BooleanHolder(false);
|
||||||
|
|
||||||
|
this.scene.getEnemyField()!.forEach(enemyPokemon =>
|
||||||
|
applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trappedByAbility, this, trappedAbMessages, simulated)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (trappedByAbility.value || !!this.getTag(TrappedTag));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the type of a move when used by this Pokemon after
|
* Calculates the type of a move when used by this Pokemon after
|
||||||
* type-changing move and ability attributes have applied.
|
* type-changing move and ability attributes have applied.
|
||||||
@ -1276,6 +1298,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply Tera Shell's effect to attacks after all immunities are accounted for
|
||||||
|
if (!ignoreAbility && move.category !== MoveCategory.STATUS) {
|
||||||
|
applyPreDefendAbAttrs(FullHpResistTypeAbAttr, this, source, move, cancelledHolder, simulated, typeMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
return (!cancelledHolder.value ? typeMultiplier.value : 0) as TypeDamageMultiplier;
|
return (!cancelledHolder.value ? typeMultiplier.value : 0) as TypeDamageMultiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2064,9 +2091,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
critLevel.value += 1;
|
critLevel.value += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (source.getTag(BattlerTagType.CRIT_BOOST)) {
|
|
||||||
critLevel.value += 2;
|
const critBoostTag = source.getTag(CritBoostTag);
|
||||||
|
if (critBoostTag) {
|
||||||
|
if (critBoostTag instanceof DragonCheerTag) {
|
||||||
|
critLevel.value += critBoostTag.typesOnAdd.includes(Type.DRAGON) ? 2 : 1;
|
||||||
|
} else {
|
||||||
|
critLevel.value += 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`crit stage: +${critLevel.value}`);
|
console.log(`crit stage: +${critLevel.value}`);
|
||||||
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(critLevel.value, 3))];
|
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(critLevel.value, 3))];
|
||||||
isCritical = critChance === 1 || !this.scene.randBattleSeedInt(critChance);
|
isCritical = critChance === 1 || !this.scene.randBattleSeedInt(critChance);
|
||||||
@ -4298,7 +4332,7 @@ export interface TurnMove {
|
|||||||
targets?: BattlerIndex[];
|
targets?: BattlerIndex[];
|
||||||
result: MoveResult;
|
result: MoveResult;
|
||||||
virtual?: boolean;
|
virtual?: boolean;
|
||||||
turn?: integer;
|
turn?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueuedMove {
|
export interface QueuedMove {
|
||||||
@ -4310,17 +4344,17 @@ export interface QueuedMove {
|
|||||||
export interface AttackMoveResult {
|
export interface AttackMoveResult {
|
||||||
move: Moves;
|
move: Moves;
|
||||||
result: DamageResult;
|
result: DamageResult;
|
||||||
damage: integer;
|
damage: number;
|
||||||
critical: boolean;
|
critical: boolean;
|
||||||
sourceId: integer;
|
sourceId: number;
|
||||||
sourceBattlerIndex: BattlerIndex;
|
sourceBattlerIndex: BattlerIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PokemonSummonData {
|
export class PokemonSummonData {
|
||||||
public battleStats: integer[] = [ 0, 0, 0, 0, 0, 0, 0 ];
|
public battleStats: number[] = [ 0, 0, 0, 0, 0, 0, 0 ];
|
||||||
public moveQueue: QueuedMove[] = [];
|
public moveQueue: QueuedMove[] = [];
|
||||||
public disabledMove: Moves = Moves.NONE;
|
public disabledMove: Moves = Moves.NONE;
|
||||||
public disabledTurns: integer = 0;
|
public disabledTurns: number = 0;
|
||||||
public tags: BattlerTag[] = [];
|
public tags: BattlerTag[] = [];
|
||||||
public abilitySuppressed: boolean = false;
|
public abilitySuppressed: boolean = false;
|
||||||
public abilitiesApplied: Abilities[] = [];
|
public abilitiesApplied: Abilities[] = [];
|
||||||
@ -4330,14 +4364,14 @@ export class PokemonSummonData {
|
|||||||
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: number[];
|
||||||
public moveset: (PokemonMove | null)[];
|
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[] = [];
|
public types: Type[] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PokemonBattleData {
|
export class PokemonBattleData {
|
||||||
public hitCount: integer = 0;
|
public hitCount: number = 0;
|
||||||
public endured: boolean = false;
|
public endured: boolean = false;
|
||||||
public berriesEaten: BerryType[] = [];
|
public berriesEaten: BerryType[] = [];
|
||||||
public abilitiesApplied: Abilities[] = [];
|
public abilitiesApplied: Abilities[] = [];
|
||||||
@ -4346,21 +4380,23 @@ export class PokemonBattleData {
|
|||||||
|
|
||||||
export class PokemonBattleSummonData {
|
export class PokemonBattleSummonData {
|
||||||
/** The number of turns the pokemon has passed since entering the battle */
|
/** The number of turns the pokemon has passed since entering the battle */
|
||||||
public turnCount: integer = 1;
|
public turnCount: number = 1;
|
||||||
/** The list of moves the pokemon has used since entering the battle */
|
/** The list of moves the pokemon has used since entering the battle */
|
||||||
public moveHistory: TurnMove[] = [];
|
public moveHistory: TurnMove[] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PokemonTurnData {
|
export class PokemonTurnData {
|
||||||
public flinched: boolean;
|
public flinched: boolean = false;
|
||||||
public acted: boolean;
|
public acted: boolean = false;
|
||||||
public hitCount: integer;
|
public hitCount: number;
|
||||||
public hitsLeft: integer;
|
public hitsLeft: number;
|
||||||
public damageDealt: integer = 0;
|
public damageDealt: number = 0;
|
||||||
public currDamageDealt: integer = 0;
|
public currDamageDealt: number = 0;
|
||||||
public damageTaken: integer = 0;
|
public damageTaken: number = 0;
|
||||||
public attacksReceived: AttackMoveResult[] = [];
|
public attacksReceived: AttackMoveResult[] = [];
|
||||||
public order: number;
|
public order: number;
|
||||||
|
public battleStatsIncreased: boolean = false;
|
||||||
|
public battleStatsDecreased: boolean = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AiType {
|
export enum AiType {
|
||||||
|
@ -1403,19 +1403,19 @@
|
|||||||
"1": "Ich muss dein Potenzial als Trainer und die Stärke der Pokémon sehen, die mit dir kämpfen!",
|
"1": "Ich muss dein Potenzial als Trainer und die Stärke der Pokémon sehen, die mit dir kämpfen!",
|
||||||
"2": "Los geht's! Dies sind meine Gesteins-Pokémon, mein ganzer Stolz!",
|
"2": "Los geht's! Dies sind meine Gesteins-Pokémon, mein ganzer Stolz!",
|
||||||
"3": "Gesteins-Pokémon sind einfach die besten!",
|
"3": "Gesteins-Pokémon sind einfach die besten!",
|
||||||
"4": "Ich muss dein Potenzial als Trainer und die Stärke der Pokémon sehen, die mit dir kämpfen!"
|
"4": "Tag für Tag grabe ich hier nach Fossilien.\n$Die viele Arbeit hat meine Pokémon felsenfest gemacht\nund das wirst du jetzt im Kampf zu spüren bekommen!"
|
||||||
},
|
},
|
||||||
"victory": {
|
"victory": {
|
||||||
"1": "W-was? Das kann nicht sein! Meine total tranierten Pokémon!",
|
"1": "W-was? Das kann nicht sein! Meine total tranierten Pokémon!",
|
||||||
"2": "…Wir haben die Kontrolle verloren. Beim nächsten Mal fordere ich dich\n$zu einem Fossilien-Ausgrabungswettbewerb heraus.",
|
"2": "…Wir haben die Kontrolle verloren. Beim nächsten Mal fordere ich dich\n$zu einem Fossilien-Ausgrabungswettbewerb heraus.",
|
||||||
"3": "Mit deinem Können ist es nur natürlich, dass du gewinnst.",
|
"3": "Mit deinem Können ist es nur natürlich, dass du gewinnst.",
|
||||||
"4": "W-was?! Das kann nicht sein! Selbst das war nicht genug?",
|
"4": "W-was?! Das kann nicht sein! Selbst das war nicht genug?"
|
||||||
"5": "Ich habe es vermasselt."
|
|
||||||
},
|
},
|
||||||
"defeat": {
|
"defeat": {
|
||||||
"1": "Siehst du? Ich bin stolz auf meinen steinigen Kampfstil!",
|
"1": "Siehst du? Ich bin stolz auf meinen steinigen Kampfstil!",
|
||||||
"2": "Danke! Der Kampf hat mir Vertrauen gegeben, dass ich vielleicht meinen Vater besiegen kann!",
|
"2": "Danke! Der Kampf hat mir Vertrauen gegeben, dass ich vielleicht meinen Vater besiegen kann!",
|
||||||
"3": "Ich fühle mich, als hätte ich gerade einen wirklich hartnäckigen Felsen durchbrochen!"
|
"3": "Na, was sagst du jetzt? Meine felsenfesten Pokémon waren hart genug für dich, was?",
|
||||||
|
"4": "Ich wusste, dass ich gewinnen würde!"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"morty": {
|
"morty": {
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
"blockItemTheft": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents item theft!",
|
"blockItemTheft": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents item theft!",
|
||||||
"typeImmunityHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!",
|
"typeImmunityHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!",
|
||||||
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} avoided damage\nwith {{abilityName}}!",
|
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} avoided damage\nwith {{abilityName}}!",
|
||||||
|
"fullHpResistType": "{{pokemonNameWithAffix}} made its shell gleam!\nIt's distorting type matchups!",
|
||||||
"moveImmunity": "It doesn't affect {{pokemonNameWithAffix}}!",
|
"moveImmunity": "It doesn't affect {{pokemonNameWithAffix}}!",
|
||||||
"reverseDrain": "{{pokemonNameWithAffix}} sucked up the liquid ooze!",
|
"reverseDrain": "{{pokemonNameWithAffix}} sucked up the liquid ooze!",
|
||||||
"postDefendTypeChange": "{{pokemonNameWithAffix}}'s {{abilityName}}\nmade it the {{typeName}} type!",
|
"postDefendTypeChange": "{{pokemonNameWithAffix}}'s {{abilityName}}\nmade it the {{typeName}} type!",
|
||||||
|
@ -742,7 +742,7 @@
|
|||||||
"plumeria": {
|
"plumeria": {
|
||||||
"encounter": {
|
"encounter": {
|
||||||
"1": " ...Hmph. You don't look like anything special to me.",
|
"1": " ...Hmph. You don't look like anything special to me.",
|
||||||
"2": "It takes these dumb Grunts way too long to deal with you kids..",
|
"2": "It takes these dumb Grunts way too long to deal with you kids...",
|
||||||
"3": "Mess with anyone in Team Skull, and I'll show you how serious I can get."
|
"3": "Mess with anyone in Team Skull, and I'll show you how serious I can get."
|
||||||
},
|
},
|
||||||
"victory": {
|
"victory": {
|
||||||
@ -1479,21 +1479,21 @@
|
|||||||
"1_female": "I need to see your potential as a Trainer. And, I'll need to see the toughness of the Pokémon that battle with you!",
|
"1_female": "I need to see your potential as a Trainer. And, I'll need to see the toughness of the Pokémon that battle with you!",
|
||||||
"2": "Here goes! These are my rocking Pokémon, my pride and joy!",
|
"2": "Here goes! These are my rocking Pokémon, my pride and joy!",
|
||||||
"3": "Rock-type Pokémon are simply the best!",
|
"3": "Rock-type Pokémon are simply the best!",
|
||||||
"4": "I need to see your potential as a Trainer. And, I'll need to see the toughness of the Pokémon that battle with you!",
|
"4": "Every day, I toughened up my Pokémon by digging up Fossils nonstop.\n$Could I show you how tough I made them in a battle?",
|
||||||
"4_female": "I need to see your potential as a Trainer. And, I'll need to see the toughness of the Pokémon that battle with you!"
|
"4_female": "Every day, I toughened up my Pokémon by digging up Fossils nonstop.\n$Could I show you how tough I made them in a battle?"
|
||||||
},
|
},
|
||||||
"victory": {
|
"victory": {
|
||||||
"1": "W-what? That can't be! My buffed-up Pokémon!",
|
"1": "W-what? That can't be! My buffed-up Pokémon!",
|
||||||
"2": "…We lost control there. Next time I'd like to challenge you to a Fossil-digging race underground.",
|
"2": "…We lost control there. Next time I'd like to challenge you to a Fossil-digging race underground.",
|
||||||
"2_female": "…We lost control there. Next time I'd like to challenge you to a Fossil-digging race underground.",
|
"2_female": "…We lost control there. Next time I'd like to challenge you to a Fossil-digging race underground.",
|
||||||
"3": "With skill like yours, it's natural for you to win.",
|
"3": "With skill like yours, it's natural for you to win.",
|
||||||
"4": "Wh-what?! It can't be! Even that wasn't enough?",
|
"4": "Wh-what?! It can't be! Even that wasn't enough?"
|
||||||
"5": "I blew it."
|
|
||||||
},
|
},
|
||||||
"defeat": {
|
"defeat": {
|
||||||
"1": "See? I'm proud of my rocking battle style!",
|
"1": "See? I'm proud of my rocking battle style!",
|
||||||
"2": "Thanks! The battle gave me confidence that I may be able to beat my dad!",
|
"2": "Thanks! The battle gave me confidence that I may be able to beat my dad!",
|
||||||
"3": "I feel like I just smashed through a really stubborn boulder!"
|
"3": "See? These are my rocking Pokémon, my pride and joy!",
|
||||||
|
"4": "I knew I would win!"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"morty": {
|
"morty": {
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
{
|
{
|
||||||
"bypassSpeedChanceApply": "¡Gracias {{itemName}} {{pokemonName}} puede tener prioridad!"
|
"surviveDamageApply": "{{pokemonNameWithAffix}} ha usado {{typeName}} y ha logrado resistir!",
|
||||||
|
"turnHealApply": "{{pokemonNameWithAffix}} ha recuperado unos pocos PS gracias a {{typeName}}!",
|
||||||
|
"hitHealApply": "{{pokemonNameWithAffix}} ha recuperado unos pocos PS gracias a {{typeName}}!",
|
||||||
|
"pokemonInstantReviveApply": "{{pokemonNameWithAffix}} ha sido revivido gracias a su {{typeName}}!",
|
||||||
|
"pokemonResetNegativeStatStageApply": "Las estadísticas bajadas de {{pokemonNameWithAffix}} fueron restauradas gracias a {{typeName}}!",
|
||||||
|
"moneyInterestApply": "Recibiste intereses de ₽{{moneyAmount}}\ngracias a {{typeName}}!",
|
||||||
|
"turnHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} fue absorbido\npor {{pokemonName}}'s {{typeName}}!",
|
||||||
|
"contactHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} fue robado por {{pokemonName}}'s {{typeName}}!",
|
||||||
|
"enemyTurnHealApply": "¡{{pokemonNameWithAffix}}\nrecuperó algunos PS!",
|
||||||
|
"bypassSpeedChanceApply": "¡Gracias a su {{itemName}}, {{pokemonName}} puede tener prioridad!"
|
||||||
}
|
}
|
@ -3,12 +3,12 @@
|
|||||||
"joinTheDiscord": "¡Únete al Discord!",
|
"joinTheDiscord": "¡Únete al Discord!",
|
||||||
"infiniteLevels": "¡Niveles infinitos!",
|
"infiniteLevels": "¡Niveles infinitos!",
|
||||||
"everythingStacks": "¡Todo se acumula!",
|
"everythingStacks": "¡Todo se acumula!",
|
||||||
"optionalSaveScumming": "¡Trampas guardando (¡opcionales!)!",
|
"optionalSaveScumming": "¡Trampas de guardado opcionales!",
|
||||||
"biomes": "¡35 biomas!",
|
"biomes": "¡35 biomas!",
|
||||||
"openSource": "¡Código abierto!",
|
"openSource": "¡Código abierto!",
|
||||||
"playWithSpeed": "¡Juega a velocidad 5x!",
|
"playWithSpeed": "¡Juega a velocidad 5x!",
|
||||||
"liveBugTesting": "¡Arreglo de bugs sobre la marcha!",
|
"liveBugTesting": "¡Testeo de bugs en directo!",
|
||||||
"heavyInfluence": "¡Influencia Alta en RoR2!",
|
"heavyInfluence": "¡Mucha Influencia de RoR2!",
|
||||||
"pokemonRiskAndPokemonRain": "¡Pokémon Risk y Pokémon Rain!",
|
"pokemonRiskAndPokemonRain": "¡Pokémon Risk y Pokémon Rain!",
|
||||||
"nowWithMoreSalt": "¡Con un 33% más de polémica!",
|
"nowWithMoreSalt": "¡Con un 33% más de polémica!",
|
||||||
"infiniteFusionAtHome": "¡Infinite Fusion en casa!",
|
"infiniteFusionAtHome": "¡Infinite Fusion en casa!",
|
||||||
@ -17,16 +17,16 @@
|
|||||||
"mubstitute": "¡Mubstituto!",
|
"mubstitute": "¡Mubstituto!",
|
||||||
"thatsCrazy": "¡De locos!",
|
"thatsCrazy": "¡De locos!",
|
||||||
"oranceJuice": "¡Zumo de narancia!",
|
"oranceJuice": "¡Zumo de narancia!",
|
||||||
"questionableBalancing": "¡Balance cuestionable!",
|
"questionableBalancing": "¡Cambios en balance cuestionables!",
|
||||||
"coolShaders": "¡Shaders impresionantes!",
|
"coolShaders": "¡Shaders impresionantes!",
|
||||||
"aiFree": "¡Libre de IA!",
|
"aiFree": "¡Libre de IA!",
|
||||||
"suddenDifficultySpikes": "¡Saltos de dificultad repentinos!",
|
"suddenDifficultySpikes": "¡Saltos de dificultad repentinos!",
|
||||||
"basedOnAnUnfinishedFlashGame": "¡Basado en un juego Flash inacabado!",
|
"basedOnAnUnfinishedFlashGame": "¡Basado en un juego Flash inacabado!",
|
||||||
"moreAddictiveThanIntended": "¡Más adictivo de la cuenta!",
|
"moreAddictiveThanIntended": "¡Más adictivo de lo previsto!",
|
||||||
"mostlyConsistentSeeds": "¡Semillas CASI consistentes!",
|
"mostlyConsistentSeeds": "¡Semillas CASI consistentes!",
|
||||||
"achievementPointsDontDoAnything": "¡Los Puntos de Logro no hacen nada!",
|
"achievementPointsDontDoAnything": "¡Los Puntos de Logro no hacen nada!",
|
||||||
"youDoNotStartAtLevel": "¡No empiezas al nivel 2000!",
|
"youDoNotStartAtLevel": "¡No empiezas al nivel 2000!",
|
||||||
"dontTalkAboutTheManaphyEggIncident": "¡No hablen del incidente del Huevo Manaphy!",
|
"dontTalkAboutTheManaphyEggIncident": "¡No se habla del Incidente Manaphy!",
|
||||||
"alsoTryPokengine": "¡Prueba también Pokéngine!",
|
"alsoTryPokengine": "¡Prueba también Pokéngine!",
|
||||||
"alsoTryEmeraldRogue": "¡Prueba también Emerald Rogue!",
|
"alsoTryEmeraldRogue": "¡Prueba también Emerald Rogue!",
|
||||||
"alsoTryRadicalRed": "¡Prueba también Radical Red!",
|
"alsoTryRadicalRed": "¡Prueba también Radical Red!",
|
||||||
|
@ -1 +1,16 @@
|
|||||||
{}
|
{
|
||||||
|
"misty": "Niebla",
|
||||||
|
"mistyStartMessage": "¡La niebla ha envuelto el terreno de combate!",
|
||||||
|
"mistyClearMessage": "La niebla se ha disipado.",
|
||||||
|
"mistyBlockMessage": "¡El campo de niebla ha protegido a {{pokemonNameWithAffix}} ",
|
||||||
|
"electric": "Eléctrico",
|
||||||
|
"electricStartMessage": "¡Se ha formado un campo de corriente eléctrica en el terreno\nde combate!",
|
||||||
|
"electricClearMessage": "El campo de corriente eléctrica ha desaparecido.\t",
|
||||||
|
"grassy": "Hierba",
|
||||||
|
"grassyStartMessage": "¡El terreno de combate se ha cubierto de hierba!",
|
||||||
|
"grassyClearMessage": "La hierba ha desaparecido.",
|
||||||
|
"psychic": "Psíquico",
|
||||||
|
"psychicStartMessage": "¡El terreno de combate se ha vuelto muy extraño!",
|
||||||
|
"psychicClearMessage": "Ha desaparecido la extraña sensación que se percibía en el terreno\nde combate.",
|
||||||
|
"defaultBlockMessage": "¡El campo {{terrainName}} ha protegido a {{pokemonNameWithAffix}} "
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"brock": "Brock",
|
"brock": "Brock",
|
||||||
"misty": "Misty",
|
"misty": "Misty",
|
||||||
"lt_surge": "Tt. Surge",
|
"lt_surge": "Teniente Surge",
|
||||||
"erika": "Erika",
|
"erika": "Erika",
|
||||||
"janine": "Sachiko",
|
"janine": "Sachiko",
|
||||||
"sabrina": "Sabrina",
|
"sabrina": "Sabrina",
|
||||||
@ -23,7 +23,7 @@
|
|||||||
"winona": "Alana",
|
"winona": "Alana",
|
||||||
"tate": "Vito",
|
"tate": "Vito",
|
||||||
"liza": "Leti",
|
"liza": "Leti",
|
||||||
"juan": "Galán",
|
"juan": "Galano",
|
||||||
"roark": "Roco",
|
"roark": "Roco",
|
||||||
"gardenia": "Gardenia",
|
"gardenia": "Gardenia",
|
||||||
"maylene": "Brega",
|
"maylene": "Brega",
|
||||||
@ -34,7 +34,7 @@
|
|||||||
"volkner": "Lectro",
|
"volkner": "Lectro",
|
||||||
"cilan": "Millo",
|
"cilan": "Millo",
|
||||||
"chili": "Zeo",
|
"chili": "Zeo",
|
||||||
"cress": "Maiz",
|
"cress": "Maíz",
|
||||||
"cheren": "Cheren",
|
"cheren": "Cheren",
|
||||||
"lenora": "Aloe",
|
"lenora": "Aloe",
|
||||||
"roxie": "Hiedra",
|
"roxie": "Hiedra",
|
||||||
@ -57,7 +57,7 @@
|
|||||||
"nessa": "Cathy",
|
"nessa": "Cathy",
|
||||||
"kabu": "Naboru",
|
"kabu": "Naboru",
|
||||||
"bea": "Judith",
|
"bea": "Judith",
|
||||||
"allister": "Allistair",
|
"allister": "Alistair",
|
||||||
"opal": "Sally",
|
"opal": "Sally",
|
||||||
"bede": "Berto",
|
"bede": "Berto",
|
||||||
"gordie": "Morris",
|
"gordie": "Morris",
|
||||||
@ -123,30 +123,28 @@
|
|||||||
"leon": "Lionel",
|
"leon": "Lionel",
|
||||||
"rival": "Finn",
|
"rival": "Finn",
|
||||||
"rival_female": "Ivy",
|
"rival_female": "Ivy",
|
||||||
"archer": "Archer",
|
"archer": "Atlas",
|
||||||
"ariana": "Ariana",
|
"ariana": "Atenea",
|
||||||
"proton": "Proton",
|
"proton": "Protón",
|
||||||
"petrel": "Petrel",
|
"petrel": "Petrel",
|
||||||
"tabitha": "Tabitha",
|
"tabitha": "Tatiano",
|
||||||
"courtney": "Courtney",
|
"courtney": "Carola",
|
||||||
"shelly": "Shelly",
|
"shelly": "Silvina",
|
||||||
"matt": "Matt",
|
"matt": "Matías",
|
||||||
"mars": "Mars",
|
"mars": "Venus",
|
||||||
"jupiter": "Jupiter",
|
"jupiter": "Ceres",
|
||||||
"saturn": "Saturn",
|
"saturn": "Saturno",
|
||||||
"zinzolin": "Zinzolin",
|
"zinzolin": "Menek",
|
||||||
"rood": "Rood",
|
"rood": "Ruga",
|
||||||
"xerosic": "Xerosic",
|
"xerosic": "Xero",
|
||||||
"bryony": "Bryony",
|
"bryony": "Begonia",
|
||||||
|
"maxie": "Magno",
|
||||||
|
"archie": "Aquiles",
|
||||||
|
"cyrus": "Helio",
|
||||||
|
"ghetsis": "Ghechis",
|
||||||
|
"lysandre": "Lysson",
|
||||||
"faba": "Fabio",
|
"faba": "Fabio",
|
||||||
|
|
||||||
"maxie": "Maxie",
|
|
||||||
"archie": "Archie",
|
|
||||||
"cyrus": "Cyrus",
|
|
||||||
"ghetsis": "Ghetsis",
|
|
||||||
"lysandre": "Lysandre",
|
|
||||||
"lusamine": "Samina",
|
"lusamine": "Samina",
|
||||||
|
|
||||||
"blue_red_double": "Azul y Rojo",
|
"blue_red_double": "Azul y Rojo",
|
||||||
"red_blue_double": "Rojo y Azul",
|
"red_blue_double": "Rojo y Azul",
|
||||||
"tate_liza_double": "Vito y Leti",
|
"tate_liza_double": "Vito y Leti",
|
||||||
|
@ -13,5 +13,32 @@
|
|||||||
"metFragment": {
|
"metFragment": {
|
||||||
"normal": "rencontré au N.{{level}},\n{{biome}}.",
|
"normal": "rencontré au N.{{level}},\n{{biome}}.",
|
||||||
"apparently": "apparemment rencontré au N.{{level}},\n{{biome}}."
|
"apparently": "apparemment rencontré au N.{{level}},\n{{biome}}."
|
||||||
|
},
|
||||||
|
"natureFragment": {
|
||||||
|
"Hardy": "{{nature}}",
|
||||||
|
"Lonely": "{{nature}}",
|
||||||
|
"Brave": "{{nature}}",
|
||||||
|
"Adamant": "{{nature}}",
|
||||||
|
"Naughty": "{{nature}}",
|
||||||
|
"Bold": "{{nature}}",
|
||||||
|
"Docile": "{{nature}}",
|
||||||
|
"Relaxed": "{{nature}}",
|
||||||
|
"Impish": "{{nature}}",
|
||||||
|
"Lax": "{{nature}}",
|
||||||
|
"Timid": "{{nature}}",
|
||||||
|
"Hasty": "{{nature}}",
|
||||||
|
"Serious": "{{nature}}",
|
||||||
|
"Jolly": "{{nature}}",
|
||||||
|
"Naive": "{{nature}}",
|
||||||
|
"Modest": "{{nature}}",
|
||||||
|
"Mild": "{{nature}}",
|
||||||
|
"Quiet": "{{nature}}",
|
||||||
|
"Bashful": "{{nature}}",
|
||||||
|
"Rash": "{{nature}}",
|
||||||
|
"Calm": "{{nature}}",
|
||||||
|
"Gentle": "{{nature}}",
|
||||||
|
"Sassy": "{{nature}}",
|
||||||
|
"Careful": "{{nature}}",
|
||||||
|
"Quirky": "{{nature}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -225,7 +225,7 @@
|
|||||||
"name": "독침붕처럼 쏴라"
|
"name": "독침붕처럼 쏴라"
|
||||||
},
|
},
|
||||||
"MONO_GHOST": {
|
"MONO_GHOST": {
|
||||||
"name": "누굴 부를 거야?"
|
"name": "무서운 게 딱 좋아!"
|
||||||
},
|
},
|
||||||
"MONO_STEEL": {
|
"MONO_STEEL": {
|
||||||
"name": "강철 심장"
|
"name": "강철 심장"
|
||||||
|
@ -1,21 +1,18 @@
|
|||||||
import BattleScene from "#app/battle-scene.js";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { TurnCommand, BattleType } from "#app/battle.js";
|
import { TurnCommand, BattleType } from "#app/battle";
|
||||||
import { applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "#app/data/ability.js";
|
import { TrappedTag, EncoreTag } from "#app/data/battler-tags";
|
||||||
import { TrappedTag, EncoreTag } from "#app/data/battler-tags.js";
|
import { MoveTargetSet, getMoveTargets } from "#app/data/move";
|
||||||
import { MoveTargetSet, getMoveTargets } from "#app/data/move.js";
|
import { speciesStarters } from "#app/data/pokemon-species";
|
||||||
import { speciesStarters } from "#app/data/pokemon-species.js";
|
import { Abilities } from "#app/enums/abilities";
|
||||||
import { Type } from "#app/data/type.js";
|
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||||
import { Abilities } from "#app/enums/abilities.js";
|
import { Biome } from "#app/enums/biome";
|
||||||
import { BattlerTagType } from "#app/enums/battler-tag-type.js";
|
import { Moves } from "#app/enums/moves";
|
||||||
import { Biome } from "#app/enums/biome.js";
|
import { PokeballType } from "#app/enums/pokeball";
|
||||||
import { Moves } from "#app/enums/moves.js";
|
import { FieldPosition, PlayerPokemon } from "#app/field/pokemon";
|
||||||
import { PokeballType } from "#app/enums/pokeball.js";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { FieldPosition, PlayerPokemon } from "#app/field/pokemon.js";
|
import { Command } from "#app/ui/command-ui-handler";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages.js";
|
import { Mode } from "#app/ui/ui";
|
||||||
import { Command } from "#app/ui/command-ui-handler.js";
|
|
||||||
import { Mode } from "#app/ui/ui.js";
|
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as Utils from "#app/utils.js";
|
|
||||||
import { FieldPhase } from "./field-phase";
|
import { FieldPhase } from "./field-phase";
|
||||||
import { SelectTargetPhase } from "./select-target-phase";
|
import { SelectTargetPhase } from "./select-target-phase";
|
||||||
|
|
||||||
@ -77,7 +74,6 @@ export class CommandPhase extends FieldPhase {
|
|||||||
|
|
||||||
handleCommand(command: Command, cursor: integer, ...args: any[]): boolean {
|
handleCommand(command: Command, cursor: integer, ...args: any[]): boolean {
|
||||||
const playerPokemon = this.scene.getPlayerField()[this.fieldIndex];
|
const playerPokemon = this.scene.getPlayerField()[this.fieldIndex];
|
||||||
const enemyField = this.scene.getEnemyField();
|
|
||||||
let success: boolean;
|
let success: boolean;
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
@ -184,14 +180,9 @@ export class CommandPhase extends FieldPhase {
|
|||||||
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
|
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
|
||||||
}, null, true);
|
}, null, true);
|
||||||
} else {
|
} else {
|
||||||
const trapTag = playerPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag;
|
|
||||||
const trapped = new Utils.BooleanHolder(false);
|
|
||||||
const batonPass = isSwitch && args[0] as boolean;
|
const batonPass = isSwitch && args[0] as boolean;
|
||||||
const trappedAbMessages: string[] = [];
|
const trappedAbMessages: string[] = [];
|
||||||
if (!batonPass) {
|
if (batonPass || !playerPokemon.isTrapped(trappedAbMessages)) {
|
||||||
enemyField.forEach(enemyPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trapped, playerPokemon, trappedAbMessages, true));
|
|
||||||
}
|
|
||||||
if (batonPass || (!trapTag && !trapped.value)) {
|
|
||||||
this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch
|
this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch
|
||||||
? { command: Command.POKEMON, cursor: cursor, args: args }
|
? { command: Command.POKEMON, cursor: cursor, args: args }
|
||||||
: { command: Command.RUN };
|
: { command: Command.RUN };
|
||||||
@ -199,14 +190,27 @@ export class CommandPhase extends FieldPhase {
|
|||||||
if (!isSwitch && this.fieldIndex) {
|
if (!isSwitch && this.fieldIndex) {
|
||||||
this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true;
|
this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true;
|
||||||
}
|
}
|
||||||
} else if (trapTag) {
|
} else if (trappedAbMessages.length > 0) {
|
||||||
if (trapTag.sourceMove === Moves.INGRAIN && trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId)?.isOfType(Type.GHOST)) {
|
if (!isSwitch) {
|
||||||
success = true;
|
this.scene.ui.setMode(Mode.MESSAGE);
|
||||||
|
}
|
||||||
|
this.scene.ui.showText(trappedAbMessages[0], null, () => {
|
||||||
|
this.scene.ui.showText("", 0);
|
||||||
|
if (!isSwitch) {
|
||||||
|
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
|
||||||
|
}
|
||||||
|
}, null, true);
|
||||||
|
} else {
|
||||||
|
const trapTag = playerPokemon.getTag(TrappedTag);
|
||||||
|
|
||||||
|
// trapTag should be defined at this point, but just in case...
|
||||||
|
if (!trapTag) {
|
||||||
this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch
|
this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch
|
||||||
? { command: Command.POKEMON, cursor: cursor, args: args }
|
? { command: Command.POKEMON, cursor: cursor, args: args }
|
||||||
: { command: Command.RUN };
|
: { command: Command.RUN };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isSwitch) {
|
if (!isSwitch) {
|
||||||
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
|
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
|
||||||
this.scene.ui.setMode(Mode.MESSAGE);
|
this.scene.ui.setMode(Mode.MESSAGE);
|
||||||
@ -224,16 +228,6 @@ export class CommandPhase extends FieldPhase {
|
|||||||
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
|
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
|
||||||
}
|
}
|
||||||
}, null, true);
|
}, null, true);
|
||||||
} else if (trapped.value && trappedAbMessages.length > 0) {
|
|
||||||
if (!isSwitch) {
|
|
||||||
this.scene.ui.setMode(Mode.MESSAGE);
|
|
||||||
}
|
|
||||||
this.scene.ui.showText(trappedAbMessages[0], null, () => {
|
|
||||||
this.scene.ui.showText("", 0);
|
|
||||||
if (!isSwitch) {
|
|
||||||
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
|
|
||||||
}
|
|
||||||
}, null, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
import BattleScene from "#app/battle-scene.js";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { BattlerIndex } from "#app/battle.js";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "#app/data/ability.js";
|
import { Command } from "#app/ui/command-ui-handler";
|
||||||
import { TrappedTag } from "#app/data/battler-tags.js";
|
|
||||||
import { Command } from "#app/ui/command-ui-handler.js";
|
|
||||||
import * as Utils from "#app/utils.js";
|
|
||||||
import { FieldPhase } from "./field-phase";
|
import { FieldPhase } from "./field-phase";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,10 +42,7 @@ export class EnemyCommandPhase extends FieldPhase {
|
|||||||
if (trainer && !enemyPokemon.getMoveQueue().length) {
|
if (trainer && !enemyPokemon.getMoveQueue().length) {
|
||||||
const opponents = enemyPokemon.getOpponents();
|
const opponents = enemyPokemon.getOpponents();
|
||||||
|
|
||||||
const trapTag = enemyPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag;
|
if (!enemyPokemon.isTrapped()) {
|
||||||
const trapped = new Utils.BooleanHolder(false);
|
|
||||||
opponents.forEach(playerPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, playerPokemon, trapped, enemyPokemon, [""], true));
|
|
||||||
if (!trapTag && !trapped.value) {
|
|
||||||
const partyMemberScores = trainer.getPartyMemberMatchupScores(enemyPokemon.trainerSlot, true);
|
const partyMemberScores = trainer.getPartyMemberMatchupScores(enemyPokemon.trainerSlot, true);
|
||||||
|
|
||||||
if (partyMemberScores.length) {
|
if (partyMemberScores.length) {
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import BattleScene from "#app/battle-scene.js";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { BattlerIndex } from "#app/battle.js";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { applyPreStatChangeAbAttrs, ProtectStatAbAttr, applyAbAttrs, StatChangeMultiplierAbAttr, StatChangeCopyAbAttr, applyPostStatChangeAbAttrs, PostStatChangeAbAttr } from "#app/data/ability.js";
|
import { applyAbAttrs, applyPostStatChangeAbAttrs, applyPreStatChangeAbAttrs, PostStatChangeAbAttr, ProtectStatAbAttr, StatChangeCopyAbAttr, StatChangeMultiplierAbAttr } from "#app/data/ability";
|
||||||
import { MistTag, ArenaTagSide } from "#app/data/arena-tag.js";
|
import { ArenaTagSide, MistTag } from "#app/data/arena-tag";
|
||||||
import { BattleStat, getBattleStatName, getBattleStatLevelChangeDescription } from "#app/data/battle-stat.js";
|
import { BattleStat, getBattleStatLevelChangeDescription, getBattleStatName } from "#app/data/battle-stat";
|
||||||
import Pokemon from "#app/field/pokemon.js";
|
import Pokemon from "#app/field/pokemon";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages.js";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { PokemonResetNegativeStatStageModifier } from "#app/modifier/modifier.js";
|
import { PokemonResetNegativeStatStageModifier } from "#app/modifier/modifier";
|
||||||
import { handleTutorial, Tutorial } from "#app/tutorial.js";
|
import { handleTutorial, Tutorial } from "#app/tutorial";
|
||||||
|
import * as Utils from "#app/utils";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as Utils from "#app/utils.js";
|
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
|
|
||||||
export type StatChangeCallback = (target: Pokemon | null, changed: BattleStat[], relativeChanges: number[]) => void;
|
export type StatChangeCallback = (target: Pokemon | null, changed: BattleStat[], relativeChanges: number[]) => void;
|
||||||
@ -72,7 +72,7 @@ export class StatChangePhase extends PokemonPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const battleStats = this.getPokemon().summonData.battleStats;
|
const battleStats = this.getPokemon().summonData.battleStats;
|
||||||
const relLevels = filteredStats.map(stat => (levels.value >= 1 ? Math.min(battleStats![stat] + levels.value, 6) : Math.max(battleStats![stat] + levels.value, -6)) - battleStats![stat]);
|
const relLevels = filteredStats.map(stat => (levels.value >= 1 ? Math.min(battleStats[stat] + levels.value, 6) : Math.max(battleStats[stat] + levels.value, -6)) - battleStats[stat]);
|
||||||
|
|
||||||
this.onChange && this.onChange(this.getPokemon(), filteredStats, relLevels);
|
this.onChange && this.onChange(this.getPokemon(), filteredStats, relLevels);
|
||||||
|
|
||||||
@ -85,6 +85,20 @@ export class StatChangePhase extends PokemonPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const stat of filteredStats) {
|
for (const stat of filteredStats) {
|
||||||
|
if (levels.value > 0 && pokemon.summonData.battleStats[stat] < 6) {
|
||||||
|
if (!pokemon.turnData) {
|
||||||
|
// Temporary fix for missing turn data struct on turn 1
|
||||||
|
pokemon.resetTurnData();
|
||||||
|
}
|
||||||
|
pokemon.turnData.battleStatsIncreased = true;
|
||||||
|
} else if (levels.value < 0 && pokemon.summonData.battleStats[stat] > -6) {
|
||||||
|
if (!pokemon.turnData) {
|
||||||
|
// Temporary fix for missing turn data struct on turn 1
|
||||||
|
pokemon.resetTurnData();
|
||||||
|
}
|
||||||
|
pokemon.turnData.battleStatsDecreased = true;
|
||||||
|
}
|
||||||
|
|
||||||
pokemon.summonData.battleStats[stat] = Math.max(Math.min(pokemon.summonData.battleStats[stat] + levels.value, 6), -6);
|
pokemon.summonData.battleStats[stat] = Math.max(Math.min(pokemon.summonData.battleStats[stat] + levels.value, 6), -6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
|
import { BattleStyle } from "#app/enums/battle-style";
|
||||||
import { CommandPhase } from "#app/phases/command-phase";
|
import { CommandPhase } from "#app/phases/command-phase";
|
||||||
import { MessagePhase } from "#app/phases/message-phase";
|
|
||||||
import { TurnInitPhase } from "#app/phases/turn-init-phase";
|
import { TurnInitPhase } from "#app/phases/turn-init-phase";
|
||||||
import i18next, { initI18n } from "#app/plugins/i18n";
|
import i18next, { initI18n } from "#app/plugins/i18n";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import GameManager from "#test/utils/gameManager";
|
import GameManager from "#test/utils/gameManager";
|
||||||
import { SPLASH_ONLY } from "#test/utils/testUtils";
|
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
@ -28,19 +26,18 @@ describe("Ability Timing", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
game.override.battleType("single");
|
|
||||||
|
|
||||||
game.override.enemySpecies(Species.PIDGEY);
|
game.override
|
||||||
game.override.enemyAbility(Abilities.INTIMIDATE);
|
.battleType("single")
|
||||||
game.override.enemyMoveset(SPLASH_ONLY);
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.INTIMIDATE)
|
||||||
game.override.ability(Abilities.BALL_FETCH);
|
.ability(Abilities.BALL_FETCH);
|
||||||
game.override.moveset([Moves.SPLASH, Moves.ICE_BEAM]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should trigger after switch check", async() => {
|
it("should trigger after switch check", async () => {
|
||||||
initI18n();
|
initI18n();
|
||||||
i18next.changeLanguage("en");
|
i18next.changeLanguage("en");
|
||||||
|
game.settings.battleStyle = BattleStyle.SWITCH;
|
||||||
await game.classicMode.runToSummon([Species.EEVEE, Species.FEEBAS]);
|
await game.classicMode.runToSummon([Species.EEVEE, Species.FEEBAS]);
|
||||||
|
|
||||||
game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
|
game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
|
||||||
@ -48,7 +45,7 @@ describe("Ability Timing", () => {
|
|||||||
game.endPhase();
|
game.endPhase();
|
||||||
}, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase));
|
}, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase));
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MessagePhase);
|
await game.phaseInterceptor.to("MessagePhase");
|
||||||
const message = game.textInterceptor.getLatestMessage();
|
const message = game.textInterceptor.getLatestMessage();
|
||||||
expect(message).toContain("Attack fell");
|
expect(message).toContain("Attack fell");
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
import { BattleStat } from "#app/data/battle-stat";
|
import { BattleStat } from "#app/data/battle-stat";
|
||||||
import { StatusEffect } from "#app/data/status-effect";
|
import { StatusEffect } from "#app/data/status-effect";
|
||||||
import { CommandPhase } from "#app/phases/command-phase";
|
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
|
||||||
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
|
||||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
|
||||||
import { TurnInitPhase } from "#app/phases/turn-init-phase";
|
|
||||||
import { Mode } from "#app/ui/ui";
|
|
||||||
import { toDmgValue } from "#app/utils";
|
import { toDmgValue } from "#app/utils";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
@ -33,13 +27,12 @@ describe("Abilities - Disguise", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
game.override.battleType("single");
|
game.override
|
||||||
|
.battleType("single")
|
||||||
game.override.enemySpecies(Species.MIMIKYU);
|
.enemySpecies(Species.MIMIKYU)
|
||||||
game.override.enemyMoveset(SPLASH_ONLY);
|
.enemyMoveset(SPLASH_ONLY)
|
||||||
|
.starterSpecies(Species.REGIELEKI)
|
||||||
game.override.starterSpecies(Species.REGIELEKI);
|
.moveset([Moves.SHADOW_SNEAK, Moves.VACUUM_WAVE, Moves.TOXIC_THREAD, Moves.SPLASH]);
|
||||||
game.override.moveset([Moves.SHADOW_SNEAK, Moves.VACUUM_WAVE, Moves.TOXIC_THREAD, Moves.SPLASH]);
|
|
||||||
}, TIMEOUT);
|
}, TIMEOUT);
|
||||||
|
|
||||||
it("takes no damage from attacking move and transforms to Busted form, takes 1/8 max HP damage from the disguise breaking", async () => {
|
it("takes no damage from attacking move and transforms to Busted form, takes 1/8 max HP damage from the disguise breaking", async () => {
|
||||||
@ -53,7 +46,7 @@ describe("Abilities - Disguise", () => {
|
|||||||
|
|
||||||
game.move.select(Moves.SHADOW_SNEAK);
|
game.move.select(Moves.SHADOW_SNEAK);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEndPhase);
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
|
||||||
expect(mimikyu.hp).equals(maxHp - disguiseDamage);
|
expect(mimikyu.hp).equals(maxHp - disguiseDamage);
|
||||||
expect(mimikyu.formIndex).toBe(bustedForm);
|
expect(mimikyu.formIndex).toBe(bustedForm);
|
||||||
@ -68,7 +61,7 @@ describe("Abilities - Disguise", () => {
|
|||||||
|
|
||||||
game.move.select(Moves.VACUUM_WAVE);
|
game.move.select(Moves.VACUUM_WAVE);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEndPhase);
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
|
||||||
expect(mimikyu.formIndex).toBe(disguisedForm);
|
expect(mimikyu.formIndex).toBe(disguisedForm);
|
||||||
}, TIMEOUT);
|
}, TIMEOUT);
|
||||||
@ -87,12 +80,12 @@ describe("Abilities - Disguise", () => {
|
|||||||
game.move.select(Moves.SURGING_STRIKES);
|
game.move.select(Moves.SURGING_STRIKES);
|
||||||
|
|
||||||
// First hit
|
// First hit
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
expect(mimikyu.hp).equals(maxHp - disguiseDamage);
|
expect(mimikyu.hp).equals(maxHp - disguiseDamage);
|
||||||
expect(mimikyu.formIndex).toBe(disguisedForm);
|
expect(mimikyu.formIndex).toBe(disguisedForm);
|
||||||
|
|
||||||
// Second hit
|
// Second hit
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
expect(mimikyu.hp).lessThan(maxHp - disguiseDamage);
|
expect(mimikyu.hp).lessThan(maxHp - disguiseDamage);
|
||||||
expect(mimikyu.formIndex).toBe(bustedForm);
|
expect(mimikyu.formIndex).toBe(bustedForm);
|
||||||
}, TIMEOUT);
|
}, TIMEOUT);
|
||||||
@ -105,7 +98,7 @@ describe("Abilities - Disguise", () => {
|
|||||||
|
|
||||||
game.move.select(Moves.TOXIC_THREAD);
|
game.move.select(Moves.TOXIC_THREAD);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
expect(mimikyu.formIndex).toBe(disguisedForm);
|
expect(mimikyu.formIndex).toBe(disguisedForm);
|
||||||
expect(mimikyu.status?.effect).toBe(StatusEffect.POISON);
|
expect(mimikyu.status?.effect).toBe(StatusEffect.POISON);
|
||||||
@ -125,7 +118,7 @@ describe("Abilities - Disguise", () => {
|
|||||||
|
|
||||||
game.move.select(Moves.SPLASH);
|
game.move.select(Moves.SPLASH);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
expect(mimikyu.formIndex).toBe(bustedForm);
|
expect(mimikyu.formIndex).toBe(bustedForm);
|
||||||
expect(mimikyu.hp).equals(maxHp - disguiseDamage);
|
expect(mimikyu.hp).equals(maxHp - disguiseDamage);
|
||||||
@ -133,7 +126,7 @@ describe("Abilities - Disguise", () => {
|
|||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
game.doSwitchPokemon(1);
|
game.doSwitchPokemon(1);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
expect(mimikyu.formIndex).toBe(bustedForm);
|
expect(mimikyu.formIndex).toBe(bustedForm);
|
||||||
}, TIMEOUT);
|
}, TIMEOUT);
|
||||||
@ -194,15 +187,6 @@ describe("Abilities - Disguise", () => {
|
|||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
game.move.select(Moves.SPLASH);
|
game.move.select(Moves.SPLASH);
|
||||||
await game.doKillOpponents();
|
await game.doKillOpponents();
|
||||||
game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => { // TODO: Make tests run in set mode instead of switch mode
|
|
||||||
game.setMode(Mode.MESSAGE);
|
|
||||||
game.endPhase();
|
|
||||||
}, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase));
|
|
||||||
|
|
||||||
game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
|
|
||||||
game.setMode(Mode.MESSAGE);
|
|
||||||
game.endPhase();
|
|
||||||
}, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase));
|
|
||||||
await game.phaseInterceptor.to("PartyHealPhase");
|
await game.phaseInterceptor.to("PartyHealPhase");
|
||||||
|
|
||||||
expect(mimikyu1.formIndex).toBe(disguisedForm);
|
expect(mimikyu1.formIndex).toBe(disguisedForm);
|
||||||
|
154
src/test/abilities/flower_gift.test.ts
Normal file
154
src/test/abilities/flower_gift.test.ts
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
import { Abilities } from "#app/enums/abilities";
|
||||||
|
import { Stat } from "#app/enums/stat";
|
||||||
|
import { WeatherType } from "#app/enums/weather-type";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import { SPLASH_ONLY } from "#test/utils/testUtils";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Flower Gift", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
const OVERCAST_FORM = 0;
|
||||||
|
const SUNSHINE_FORM = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests reverting to normal form when Cloud Nine/Air Lock is active on the field
|
||||||
|
* @param {GameManager} game The game manager instance
|
||||||
|
* @param {Abilities} ability The ability that is active on the field
|
||||||
|
*/
|
||||||
|
const testRevertFormAgainstAbility = async (game: GameManager, ability: Abilities) => {
|
||||||
|
game.override.starterForms({ [Species.CASTFORM]: SUNSHINE_FORM }).enemyAbility(ability);
|
||||||
|
await game.classicMode.startBattle([Species.CASTFORM]);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
|
||||||
|
expect(game.scene.getPlayerPokemon()?.formIndex).toBe(OVERCAST_FORM);
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.moveset([Moves.SPLASH, Moves.RAIN_DANCE, Moves.SUNNY_DAY, Moves.SKILL_SWAP])
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyMoveset(SPLASH_ONLY)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Uncomment expect statements when the ability is implemented - currently does not increase stats of allies
|
||||||
|
it("increases the Attack and Special Defense stats of the Pokémon with this Ability and its allies by 1.5× during Harsh Sunlight", async () => {
|
||||||
|
game.override.battleType("double");
|
||||||
|
await game.classicMode.startBattle([Species.CHERRIM, Species.MAGIKARP]);
|
||||||
|
|
||||||
|
const [ cherrim ] = game.scene.getPlayerField();
|
||||||
|
const cherrimAtkStat = cherrim.getBattleStat(Stat.ATK);
|
||||||
|
const cherrimSpDefStat = cherrim.getBattleStat(Stat.SPDEF);
|
||||||
|
|
||||||
|
// const magikarpAtkStat = magikarp.getBattleStat(Stat.ATK);;
|
||||||
|
// const magikarpSpDefStat = magikarp.getBattleStat(Stat.SPDEF);
|
||||||
|
|
||||||
|
game.move.select(Moves.SUNNY_DAY, 0);
|
||||||
|
game.move.select(Moves.SPLASH, 1);
|
||||||
|
|
||||||
|
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
|
||||||
|
expect(cherrim.getBattleStat(Stat.ATK)).toBe(Math.floor(cherrimAtkStat * 1.5));
|
||||||
|
expect(cherrim.getBattleStat(Stat.SPDEF)).toBe(Math.floor(cherrimSpDefStat * 1.5));
|
||||||
|
// expect(magikarp.getBattleStat(Stat.ATK)).toBe(Math.floor(magikarpAtkStat * 1.5));
|
||||||
|
// expect(magikarp.getBattleStat(Stat.SPDEF)).toBe(Math.floor(magikarpSpDefStat * 1.5));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("changes the Pokemon's form during Harsh Sunlight", async () => {
|
||||||
|
game.override.weather(WeatherType.HARSH_SUN);
|
||||||
|
await game.classicMode.startBattle([Species.CHERRIM]);
|
||||||
|
|
||||||
|
const cherrim = game.scene.getPlayerPokemon()!;
|
||||||
|
expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reverts to Overcast Form if a Pokémon on the field has Air Lock", async () => {
|
||||||
|
await testRevertFormAgainstAbility(game, Abilities.AIR_LOCK);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reverts to Overcast Form if a Pokémon on the field has Cloud Nine", async () => {
|
||||||
|
await testRevertFormAgainstAbility(game, Abilities.CLOUD_NINE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reverts to Overcast Form when the Pokémon loses Flower Gift, changes form under Harsh Sunlight/Sunny when it regains it", async () => {
|
||||||
|
game.override.enemyMoveset(Array(4).fill(Moves.SKILL_SWAP)).weather(WeatherType.HARSH_SUN);
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([Species.CHERRIM]);
|
||||||
|
|
||||||
|
const cherrim = game.scene.getPlayerPokemon()!;
|
||||||
|
|
||||||
|
game.move.select(Moves.SKILL_SWAP);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("TurnStartPhase");
|
||||||
|
expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
expect(cherrim.formIndex).toBe(OVERCAST_FORM);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reverts to Overcast Form when the Flower Gift is suppressed, changes form under Harsh Sunlight/Sunny when it regains it", async () => {
|
||||||
|
game.override.enemyMoveset(Array(4).fill(Moves.GASTRO_ACID)).weather(WeatherType.HARSH_SUN);
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([Species.CHERRIM, Species.MAGIKARP]);
|
||||||
|
|
||||||
|
const cherrim = game.scene.getPlayerPokemon()!;
|
||||||
|
|
||||||
|
expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
expect(cherrim.summonData.abilitySuppressed).toBe(true);
|
||||||
|
expect(cherrim.formIndex).toBe(OVERCAST_FORM);
|
||||||
|
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
game.doSwitchPokemon(1);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
game.doSwitchPokemon(1);
|
||||||
|
await game.phaseInterceptor.to("MovePhase");
|
||||||
|
|
||||||
|
expect(cherrim.summonData.abilitySuppressed).toBe(false);
|
||||||
|
expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be in Overcast Form after the user is switched out", async () => {
|
||||||
|
game.override.weather(WeatherType.SUNNY);
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([Species.CASTFORM, Species.MAGIKARP]);
|
||||||
|
const cherrim = game.scene.getPlayerPokemon()!;
|
||||||
|
|
||||||
|
expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
|
||||||
|
|
||||||
|
game.doSwitchPokemon(1);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(cherrim.formIndex).toBe(OVERCAST_FORM);
|
||||||
|
});
|
||||||
|
});
|
@ -1,12 +1,8 @@
|
|||||||
import { BattleStat } from "#app/data/battle-stat";
|
import { BattleStat } from "#app/data/battle-stat";
|
||||||
import { Status, StatusEffect } from "#app/data/status-effect";
|
import { Status, StatusEffect } from "#app/data/status-effect";
|
||||||
import { GameModes, getGameMode } from "#app/game-mode";
|
import { GameModes, getGameMode } from "#app/game-mode";
|
||||||
import { CommandPhase } from "#app/phases/command-phase";
|
|
||||||
import { DamagePhase } from "#app/phases/damage-phase";
|
|
||||||
import { EncounterPhase } from "#app/phases/encounter-phase";
|
import { EncounterPhase } from "#app/phases/encounter-phase";
|
||||||
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
|
|
||||||
import { SelectStarterPhase } from "#app/phases/select-starter-phase";
|
import { SelectStarterPhase } from "#app/phases/select-starter-phase";
|
||||||
import { TurnInitPhase } from "#app/phases/turn-init-phase";
|
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
@ -33,36 +29,26 @@ describe("Abilities - Intimidate", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
game.override.battleType("single");
|
game.override
|
||||||
game.override.enemySpecies(Species.RATTATA);
|
.battleType("single")
|
||||||
game.override.enemyAbility(Abilities.INTIMIDATE);
|
.enemySpecies(Species.MAGIKARP)
|
||||||
game.override.enemyPassiveAbility(Abilities.HYDRATION);
|
.enemyAbility(Abilities.INTIMIDATE)
|
||||||
game.override.ability(Abilities.INTIMIDATE);
|
.ability(Abilities.INTIMIDATE)
|
||||||
game.override.startingWave(3);
|
.moveset([Moves.SPLASH, Moves.AERIAL_ACE])
|
||||||
game.override.enemyMoveset(SPLASH_ONLY);
|
.enemyMoveset(SPLASH_ONLY);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("single - wild with switch", async () => {
|
it("single - wild with switch", async () => {
|
||||||
await game.classicMode.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
|
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||||
game.onNextPrompt(
|
|
||||||
"CheckSwitchPhase",
|
|
||||||
Mode.CONFIRM,
|
|
||||||
() => {
|
|
||||||
game.setMode(Mode.MESSAGE);
|
|
||||||
game.endPhase();
|
|
||||||
},
|
|
||||||
() => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
|
|
||||||
);
|
|
||||||
await game.phaseInterceptor.to(CommandPhase, false);
|
|
||||||
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.MIGHTYENA);
|
|
||||||
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||||
|
|
||||||
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
||||||
|
|
||||||
game.doSwitchPokemon(1);
|
game.doSwitchPokemon(1);
|
||||||
await game.phaseInterceptor.run(CommandPhase);
|
await game.phaseInterceptor.to("CommandPhase");
|
||||||
await game.phaseInterceptor.to(CommandPhase);
|
|
||||||
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.POOCHYENA);
|
|
||||||
|
|
||||||
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
|
expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
|
||||||
@ -73,25 +59,16 @@ describe("Abilities - Intimidate", () => {
|
|||||||
|
|
||||||
it("single - boss should only trigger once then switch", async () => {
|
it("single - boss should only trigger once then switch", async () => {
|
||||||
game.override.startingWave(10);
|
game.override.startingWave(10);
|
||||||
await game.classicMode.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
|
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||||
game.onNextPrompt(
|
|
||||||
"CheckSwitchPhase",
|
|
||||||
Mode.CONFIRM,
|
|
||||||
() => {
|
|
||||||
game.setMode(Mode.MESSAGE);
|
|
||||||
game.endPhase();
|
|
||||||
},
|
|
||||||
() => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
|
|
||||||
);
|
|
||||||
await game.phaseInterceptor.to(CommandPhase, false);
|
|
||||||
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||||
|
|
||||||
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
||||||
|
|
||||||
game.doSwitchPokemon(1);
|
game.doSwitchPokemon(1);
|
||||||
await game.phaseInterceptor.run(CommandPhase);
|
await game.phaseInterceptor.to("CommandPhase");
|
||||||
await game.phaseInterceptor.to(CommandPhase);
|
|
||||||
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.POOCHYENA);
|
|
||||||
|
|
||||||
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
|
expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
|
||||||
@ -102,25 +79,16 @@ describe("Abilities - Intimidate", () => {
|
|||||||
|
|
||||||
it("single - trainer should only trigger once with switch", async () => {
|
it("single - trainer should only trigger once with switch", async () => {
|
||||||
game.override.startingWave(5);
|
game.override.startingWave(5);
|
||||||
await game.classicMode.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
|
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||||
game.onNextPrompt(
|
|
||||||
"CheckSwitchPhase",
|
|
||||||
Mode.CONFIRM,
|
|
||||||
() => {
|
|
||||||
game.setMode(Mode.MESSAGE);
|
|
||||||
game.endPhase();
|
|
||||||
},
|
|
||||||
() => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
|
|
||||||
);
|
|
||||||
await game.phaseInterceptor.to(CommandPhase, false);
|
|
||||||
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||||
|
|
||||||
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
||||||
|
|
||||||
game.doSwitchPokemon(1);
|
game.doSwitchPokemon(1);
|
||||||
await game.phaseInterceptor.run(CommandPhase);
|
await game.phaseInterceptor.to("CommandPhase");
|
||||||
await game.phaseInterceptor.to(CommandPhase);
|
|
||||||
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.POOCHYENA);
|
|
||||||
|
|
||||||
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
|
expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
|
||||||
@ -130,21 +98,14 @@ describe("Abilities - Intimidate", () => {
|
|||||||
}, 200000);
|
}, 200000);
|
||||||
|
|
||||||
it("double - trainer should only trigger once per pokemon", async () => {
|
it("double - trainer should only trigger once per pokemon", async () => {
|
||||||
game.override.battleType("double");
|
game.override
|
||||||
game.override.startingWave(5);
|
.battleType("double")
|
||||||
await game.classicMode.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
|
.startingWave(5);
|
||||||
game.onNextPrompt(
|
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||||
"CheckSwitchPhase",
|
|
||||||
Mode.CONFIRM,
|
|
||||||
() => {
|
|
||||||
game.setMode(Mode.MESSAGE);
|
|
||||||
game.endPhase();
|
|
||||||
},
|
|
||||||
() => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
|
|
||||||
);
|
|
||||||
await game.phaseInterceptor.to(CommandPhase, false);
|
|
||||||
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
|
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
|
||||||
|
|
||||||
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
|
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
|
||||||
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-2);
|
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-2);
|
||||||
|
|
||||||
@ -157,20 +118,11 @@ describe("Abilities - Intimidate", () => {
|
|||||||
|
|
||||||
it("double - wild: should only trigger once per pokemon", async () => {
|
it("double - wild: should only trigger once per pokemon", async () => {
|
||||||
game.override.battleType("double");
|
game.override.battleType("double");
|
||||||
game.override.startingWave(3);
|
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||||
await game.classicMode.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
|
|
||||||
game.onNextPrompt(
|
|
||||||
"CheckSwitchPhase",
|
|
||||||
Mode.CONFIRM,
|
|
||||||
() => {
|
|
||||||
game.setMode(Mode.MESSAGE);
|
|
||||||
game.endPhase();
|
|
||||||
},
|
|
||||||
() => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
|
|
||||||
);
|
|
||||||
await game.phaseInterceptor.to(CommandPhase, false);
|
|
||||||
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
|
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
|
||||||
|
|
||||||
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
|
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
|
||||||
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-2);
|
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-2);
|
||||||
|
|
||||||
@ -182,21 +134,14 @@ describe("Abilities - Intimidate", () => {
|
|||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
it("double - boss: should only trigger once per pokemon", async () => {
|
it("double - boss: should only trigger once per pokemon", async () => {
|
||||||
game.override.battleType("double");
|
game.override
|
||||||
game.override.startingWave(10);
|
.battleType("double")
|
||||||
await game.classicMode.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
|
.startingWave(10);
|
||||||
game.onNextPrompt(
|
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||||
"CheckSwitchPhase",
|
|
||||||
Mode.CONFIRM,
|
|
||||||
() => {
|
|
||||||
game.setMode(Mode.MESSAGE);
|
|
||||||
game.endPhase();
|
|
||||||
},
|
|
||||||
() => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
|
|
||||||
);
|
|
||||||
await game.phaseInterceptor.to(CommandPhase, false);
|
|
||||||
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
|
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
|
||||||
|
|
||||||
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
|
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
|
||||||
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-2);
|
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-2);
|
||||||
|
|
||||||
@ -208,104 +153,101 @@ describe("Abilities - Intimidate", () => {
|
|||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
it("single - wild next wave opp triger once, us: none", async () => {
|
it("single - wild next wave opp triger once, us: none", async () => {
|
||||||
game.override.startingWave(2);
|
await game.startBattle([Species.MIGHTYENA]);
|
||||||
game.override.moveset([Moves.AERIAL_ACE]);
|
|
||||||
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
|
||||||
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||||
|
|
||||||
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
||||||
|
|
||||||
game.move.select(Moves.AERIAL_ACE);
|
game.move.select(Moves.AERIAL_ACE);
|
||||||
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(DamagePhase);
|
await game.phaseInterceptor.to("DamagePhase");
|
||||||
await game.killPokemon(game.scene.currentBattle.enemyParty[0]);
|
await game.doKillOpponents();
|
||||||
expect(game.scene.currentBattle.enemyParty[0].isFainted()).toBe(true);
|
|
||||||
await game.toNextWave();
|
await game.toNextWave();
|
||||||
|
|
||||||
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
|
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
|
||||||
|
|
||||||
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
|
expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
it("single - wild next turn - no retrigger on next turn", async () => {
|
it("single - wild next turn - no retrigger on next turn", async () => {
|
||||||
game.override.startingWave(2);
|
await game.startBattle([Species.MIGHTYENA]);
|
||||||
game.override.moveset([Moves.SPLASH]);
|
|
||||||
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
|
||||||
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||||
|
|
||||||
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
||||||
|
|
||||||
game.move.select(Moves.AERIAL_ACE);
|
game.move.select(Moves.SPLASH);
|
||||||
console.log("===to new turn===");
|
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
||||||
|
|
||||||
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
it("single - trainer should only trigger once and each time he switch", async () => {
|
it("single - trainer should only trigger once and each time he switch", async () => {
|
||||||
game.override.moveset([Moves.SPLASH]);
|
game.override
|
||||||
game.override.enemyMoveset([Moves.VOLT_SWITCH, Moves.VOLT_SWITCH, Moves.VOLT_SWITCH, Moves.VOLT_SWITCH]);
|
.enemyMoveset(Array(4).fill(Moves.VOLT_SWITCH))
|
||||||
game.override.startingWave(5);
|
.startingWave(5);
|
||||||
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
await game.startBattle([Species.MIGHTYENA]);
|
||||||
|
|
||||||
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||||
|
|
||||||
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
||||||
|
|
||||||
game.move.select(Moves.AERIAL_ACE);
|
game.move.select(Moves.SPLASH);
|
||||||
console.log("===to new turn===");
|
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
|
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
|
||||||
|
|
||||||
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
|
expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
|
||||||
|
|
||||||
game.move.select(Moves.AERIAL_ACE);
|
game.move.select(Moves.SPLASH);
|
||||||
console.log("===to new turn===");
|
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-3);
|
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-3);
|
||||||
|
|
||||||
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
|
expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
|
||||||
}, 200000);
|
}, 200000);
|
||||||
|
|
||||||
it("single - trainer should only trigger once whatever turn we are", async () => {
|
it("single - trainer should only trigger once whatever turn we are", async () => {
|
||||||
game.override.moveset([Moves.SPLASH]);
|
|
||||||
game.override.enemyMoveset(SPLASH_ONLY);
|
|
||||||
game.override.startingWave(5);
|
game.override.startingWave(5);
|
||||||
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
await game.startBattle([Species.MIGHTYENA]);
|
||||||
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
|
||||||
|
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||||
|
const battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||||
|
|
||||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||||
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
|
||||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
||||||
|
|
||||||
game.move.select(Moves.AERIAL_ACE);
|
game.move.select(Moves.SPLASH);
|
||||||
console.log("===to new turn===");
|
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
|
||||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
|
||||||
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
|
||||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
|
||||||
|
|
||||||
game.move.select(Moves.AERIAL_ACE);
|
|
||||||
console.log("===to new turn===");
|
|
||||||
await game.toNextTurn();
|
|
||||||
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
|
||||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
||||||
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
|
||||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
it("double - wild vs only 1 on player side", async () => {
|
it("double - wild vs only 1 on player side", async () => {
|
||||||
game.override.battleType("double");
|
game.override.battleType("double");
|
||||||
game.override.startingWave(3);
|
|
||||||
await game.classicMode.runToSummon([Species.MIGHTYENA]);
|
await game.classicMode.runToSummon([Species.MIGHTYENA]);
|
||||||
await game.phaseInterceptor.to(CommandPhase, false);
|
await game.phaseInterceptor.to("CommandPhase", false);
|
||||||
|
|
||||||
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||||
|
|
||||||
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
|
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
|
||||||
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-1);
|
||||||
|
|
||||||
@ -315,7 +257,6 @@ describe("Abilities - Intimidate", () => {
|
|||||||
|
|
||||||
it("double - wild vs only 1 alive on player side", async () => {
|
it("double - wild vs only 1 alive on player side", async () => {
|
||||||
game.override.battleType("double");
|
game.override.battleType("double");
|
||||||
game.override.startingWave(3);
|
|
||||||
await game.runToTitle();
|
await game.runToTitle();
|
||||||
|
|
||||||
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
|
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
|
||||||
@ -330,9 +271,11 @@ describe("Abilities - Intimidate", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.run(EncounterPhase);
|
await game.phaseInterceptor.run(EncounterPhase);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(CommandPhase, false);
|
await game.phaseInterceptor.to("CommandPhase", false);
|
||||||
|
|
||||||
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||||
|
|
||||||
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
|
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
|
||||||
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-1);
|
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-1);
|
||||||
|
|
||||||
|
111
src/test/abilities/tera_shell.test.ts
Normal file
111
src/test/abilities/tera_shell.test.ts
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import { Abilities } from "#app/enums/abilities";
|
||||||
|
import { Moves } from "#app/enums/moves";
|
||||||
|
import { Species } from "#app/enums/species";
|
||||||
|
import { HitResult } from "#app/field/pokemon.js";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
const TIMEOUT = 10 * 1000; // 10 second timeout
|
||||||
|
|
||||||
|
describe("Abilities - Tera Shell", () => {
|
||||||
|
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
|
||||||
|
.battleType("single")
|
||||||
|
.ability(Abilities.TERA_SHELL)
|
||||||
|
.moveset([Moves.SPLASH])
|
||||||
|
.enemySpecies(Species.SNORLAX)
|
||||||
|
.enemyAbility(Abilities.INSOMNIA)
|
||||||
|
.enemyMoveset(Array(4).fill(Moves.MACH_PUNCH))
|
||||||
|
.startingLevel(100)
|
||||||
|
.enemyLevel(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(
|
||||||
|
"should change the effectiveness of non-resisted attacks when the source is at full HP",
|
||||||
|
async () => {
|
||||||
|
await game.classicMode.startBattle([Species.SNORLAX]);
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
vi.spyOn(playerPokemon, "getMoveEffectiveness");
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
expect(playerPokemon.getMoveEffectiveness).toHaveLastReturnedWith(0.5);
|
||||||
|
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
expect(playerPokemon.getMoveEffectiveness).toHaveLastReturnedWith(2);
|
||||||
|
}, TIMEOUT
|
||||||
|
);
|
||||||
|
|
||||||
|
it(
|
||||||
|
"should not override type immunities",
|
||||||
|
async () => {
|
||||||
|
game.override.enemyMoveset(Array(4).fill(Moves.SHADOW_SNEAK));
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([Species.SNORLAX]);
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
vi.spyOn(playerPokemon, "getMoveEffectiveness");
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
expect(playerPokemon.getMoveEffectiveness).toHaveLastReturnedWith(0);
|
||||||
|
}, TIMEOUT
|
||||||
|
);
|
||||||
|
|
||||||
|
it(
|
||||||
|
"should not override type multipliers less than 0.5x",
|
||||||
|
async () => {
|
||||||
|
game.override.enemyMoveset(Array(4).fill(Moves.QUICK_ATTACK));
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([Species.AGGRON]);
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
vi.spyOn(playerPokemon, "getMoveEffectiveness");
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
expect(playerPokemon.getMoveEffectiveness).toHaveLastReturnedWith(0.25);
|
||||||
|
}, TIMEOUT
|
||||||
|
);
|
||||||
|
|
||||||
|
it(
|
||||||
|
"should not affect the effectiveness of fixed-damage moves",
|
||||||
|
async () => {
|
||||||
|
game.override.enemyMoveset(Array(4).fill(Moves.DRAGON_RAGE));
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
vi.spyOn(playerPokemon, "apply");
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
expect(playerPokemon.apply).toHaveLastReturnedWith(HitResult.EFFECTIVE);
|
||||||
|
expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp() - 40);
|
||||||
|
}, TIMEOUT
|
||||||
|
);
|
||||||
|
});
|
@ -32,7 +32,7 @@ describe("Endless Boss", () => {
|
|||||||
|
|
||||||
it(`should spawn a minor boss every ${EndlessBossWave.Minor} waves in END biome in Endless`, async () => {
|
it(`should spawn a minor boss every ${EndlessBossWave.Minor} waves in END biome in Endless`, async () => {
|
||||||
game.override.startingWave(EndlessBossWave.Minor);
|
game.override.startingWave(EndlessBossWave.Minor);
|
||||||
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.ENDLESS);
|
await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.ENDLESS);
|
||||||
|
|
||||||
expect(game.scene.currentBattle.waveIndex).toBe(EndlessBossWave.Minor);
|
expect(game.scene.currentBattle.waveIndex).toBe(EndlessBossWave.Minor);
|
||||||
expect(game.scene.arena.biomeType).toBe(Biome.END);
|
expect(game.scene.arena.biomeType).toBe(Biome.END);
|
||||||
@ -44,7 +44,7 @@ describe("Endless Boss", () => {
|
|||||||
|
|
||||||
it(`should spawn a major boss every ${EndlessBossWave.Major} waves in END biome in Endless`, async () => {
|
it(`should spawn a major boss every ${EndlessBossWave.Major} waves in END biome in Endless`, async () => {
|
||||||
game.override.startingWave(EndlessBossWave.Major);
|
game.override.startingWave(EndlessBossWave.Major);
|
||||||
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.ENDLESS);
|
await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.ENDLESS);
|
||||||
|
|
||||||
expect(game.scene.currentBattle.waveIndex).toBe(EndlessBossWave.Major);
|
expect(game.scene.currentBattle.waveIndex).toBe(EndlessBossWave.Major);
|
||||||
expect(game.scene.arena.biomeType).toBe(Biome.END);
|
expect(game.scene.arena.biomeType).toBe(Biome.END);
|
||||||
@ -56,7 +56,7 @@ describe("Endless Boss", () => {
|
|||||||
|
|
||||||
it(`should spawn a minor boss every ${EndlessBossWave.Minor} waves in END biome in Spliced Endless`, async () => {
|
it(`should spawn a minor boss every ${EndlessBossWave.Minor} waves in END biome in Spliced Endless`, async () => {
|
||||||
game.override.startingWave(EndlessBossWave.Minor);
|
game.override.startingWave(EndlessBossWave.Minor);
|
||||||
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.SPLICED_ENDLESS);
|
await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.SPLICED_ENDLESS);
|
||||||
|
|
||||||
expect(game.scene.currentBattle.waveIndex).toBe(EndlessBossWave.Minor);
|
expect(game.scene.currentBattle.waveIndex).toBe(EndlessBossWave.Minor);
|
||||||
expect(game.scene.arena.biomeType).toBe(Biome.END);
|
expect(game.scene.arena.biomeType).toBe(Biome.END);
|
||||||
@ -68,7 +68,7 @@ describe("Endless Boss", () => {
|
|||||||
|
|
||||||
it(`should spawn a major boss every ${EndlessBossWave.Major} waves in END biome in Spliced Endless`, async () => {
|
it(`should spawn a major boss every ${EndlessBossWave.Major} waves in END biome in Spliced Endless`, async () => {
|
||||||
game.override.startingWave(EndlessBossWave.Major);
|
game.override.startingWave(EndlessBossWave.Major);
|
||||||
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.SPLICED_ENDLESS);
|
await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.SPLICED_ENDLESS);
|
||||||
|
|
||||||
expect(game.scene.currentBattle.waveIndex).toBe(EndlessBossWave.Major);
|
expect(game.scene.currentBattle.waveIndex).toBe(EndlessBossWave.Major);
|
||||||
expect(game.scene.arena.biomeType).toBe(Biome.END);
|
expect(game.scene.arena.biomeType).toBe(Biome.END);
|
||||||
@ -80,7 +80,7 @@ describe("Endless Boss", () => {
|
|||||||
|
|
||||||
it(`should NOT spawn major or minor boss outside wave ${EndlessBossWave.Minor}s in END biome`, async () => {
|
it(`should NOT spawn major or minor boss outside wave ${EndlessBossWave.Minor}s in END biome`, async () => {
|
||||||
game.override.startingWave(EndlessBossWave.Minor - 1);
|
game.override.startingWave(EndlessBossWave.Minor - 1);
|
||||||
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.ENDLESS);
|
await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.ENDLESS);
|
||||||
|
|
||||||
expect(game.scene.currentBattle.waveIndex).not.toBe(EndlessBossWave.Minor);
|
expect(game.scene.currentBattle.waveIndex).not.toBe(EndlessBossWave.Minor);
|
||||||
expect(game.scene.getEnemyPokemon()!.species.speciesId).not.toBe(Species.ETERNATUS);
|
expect(game.scene.getEnemyPokemon()!.species.speciesId).not.toBe(Species.ETERNATUS);
|
||||||
|
@ -2,9 +2,10 @@ import { pokemonEvolutions, SpeciesFormEvolution, SpeciesWildEvolutionDelay } fr
|
|||||||
import { Abilities } from "#app/enums/abilities";
|
import { Abilities } from "#app/enums/abilities";
|
||||||
import { Moves } from "#app/enums/moves";
|
import { Moves } from "#app/enums/moves";
|
||||||
import { Species } from "#app/enums/species";
|
import { Species } from "#app/enums/species";
|
||||||
|
import * as Utils from "#app/utils";
|
||||||
import GameManager from "#test/utils/gameManager";
|
import GameManager from "#test/utils/gameManager";
|
||||||
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 { SPLASH_ONLY } from "./utils/testUtils";
|
import { SPLASH_ONLY } from "./utils/testUtils";
|
||||||
|
|
||||||
describe("Evolution", () => {
|
describe("Evolution", () => {
|
||||||
@ -148,4 +149,28 @@ describe("Evolution", () => {
|
|||||||
expect(cyndaquil.hp).toBeGreaterThan(hpBefore);
|
expect(cyndaquil.hp).toBeGreaterThan(hpBefore);
|
||||||
expect(cyndaquil.hp).toBeLessThan(cyndaquil.getMaxHp());
|
expect(cyndaquil.hp).toBeLessThan(cyndaquil.getMaxHp());
|
||||||
}, TIMEOUT);
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("should handle rng-based split evolution", async () => {
|
||||||
|
/* this test checks to make sure that tandemaus will
|
||||||
|
* evolve into a 3 family maushold 25% of the time
|
||||||
|
* and a 4 family maushold the other 75% of the time
|
||||||
|
* This is done by using the getEvolution method in pokemon.ts
|
||||||
|
* getEvolution will give back the form that the pokemon can evolve into
|
||||||
|
* It does this by checking the pokemon conditions in pokemon-forms.ts
|
||||||
|
* For tandemaus, the conditions are random due to a randSeedInt(4)
|
||||||
|
* If the value is 0, it's a 3 family maushold, whereas if the value is
|
||||||
|
* 1, 2 or 3, it's a 4 family maushold
|
||||||
|
*/
|
||||||
|
await game.startBattle([Species.TANDEMAUS]); // starts us off with a tandemaus
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
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
|
||||||
|
const threeForm = playerPokemon.getEvolution()!;
|
||||||
|
expect(threeForm.evoFormKey).toBe("three"); // as per pokemon-forms, the evoFormKey for 3 family mausholds is "three"
|
||||||
|
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
|
||||||
|
const fourForm = playerPokemon.getEvolution()!;
|
||||||
|
expect(fourForm.evoFormKey).toBe(null); // meanwhile, according to the pokemon-forms, the evoFormKey for a 4 family maushold is null
|
||||||
|
}
|
||||||
|
}, TIMEOUT);
|
||||||
});
|
});
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
import * as Utils from "#app/utils";
|
|
||||||
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("Evolution tests", () => {
|
|
||||||
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("tandemaus evolution form test", async () => {
|
|
||||||
/* this test checks to make sure that tandemaus will
|
|
||||||
* evolve into a 3 family maushold 25% of the time
|
|
||||||
* and a 4 family maushold the other 75% of the time
|
|
||||||
* This is done by using the getEvolution method in pokemon.ts
|
|
||||||
* getEvolution will give back the form that the pokemon can evolve into
|
|
||||||
* It does this by checking the pokemon conditions in pokemon-forms.ts
|
|
||||||
* For tandemaus, the conditions are random due to a randSeedInt(4)
|
|
||||||
* If the value is 0, it's a 3 family maushold, whereas if the value is
|
|
||||||
* 1, 2 or 3, it's a 4 family maushold
|
|
||||||
*/
|
|
||||||
await game.startBattle([Species.TANDEMAUS]); // starts us off with a tandemaus
|
|
||||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
|
||||||
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
|
|
||||||
const threeForm = playerPokemon.getEvolution()!;
|
|
||||||
expect(threeForm.evoFormKey).toBe("three"); // as per pokemon-forms, the evoFormKey for 3 family mausholds is "three"
|
|
||||||
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
|
|
||||||
const fourForm = playerPokemon.getEvolution()!;
|
|
||||||
expect(fourForm.evoFormKey).toBe(null); // meanwhile, according to the pokemon-forms, the evoFormKey for a 4 family maushold is null
|
|
||||||
}
|
|
||||||
}, 5000);
|
|
||||||
});
|
|
@ -1,8 +1,13 @@
|
|||||||
|
import { StatusEffect } from "#app/data/status-effect";
|
||||||
|
import { Abilities } from "#app/enums/abilities";
|
||||||
import { Biome } from "#app/enums/biome";
|
import { Biome } from "#app/enums/biome";
|
||||||
|
import { Moves } from "#app/enums/moves";
|
||||||
import { Species } from "#app/enums/species";
|
import { Species } from "#app/enums/species";
|
||||||
import { GameModes } from "#app/game-mode";
|
import { GameModes } from "#app/game-mode";
|
||||||
|
import { TurnHeldItemTransferModifier } from "#app/modifier/modifier";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
import GameManager from "./utils/gameManager";
|
import GameManager from "./utils/gameManager";
|
||||||
|
import { SPLASH_ONLY } from "./utils/testUtils";
|
||||||
|
|
||||||
const FinalWave = {
|
const FinalWave = {
|
||||||
Classic: 200,
|
Classic: 200,
|
||||||
@ -20,7 +25,13 @@ describe("Final Boss", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
game.override.startingWave(FinalWave.Classic).startingBiome(Biome.END).disableCrits();
|
game.override
|
||||||
|
.startingWave(FinalWave.Classic)
|
||||||
|
.startingBiome(Biome.END)
|
||||||
|
.disableCrits()
|
||||||
|
.enemyMoveset(SPLASH_ONLY)
|
||||||
|
.moveset([ Moves.SPLASH, Moves.WILL_O_WISP, Moves.DRAGON_PULSE ])
|
||||||
|
.startingLevel(10000);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -28,7 +39,7 @@ describe("Final Boss", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should spawn Eternatus on wave 200 in END biome", async () => {
|
it("should spawn Eternatus on wave 200 in END biome", async () => {
|
||||||
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.CLASSIC);
|
await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.CLASSIC);
|
||||||
|
|
||||||
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);
|
||||||
@ -37,7 +48,7 @@ describe("Final Boss", () => {
|
|||||||
|
|
||||||
it("should NOT spawn Eternatus before wave 200 in END biome", async () => {
|
it("should NOT spawn Eternatus before wave 200 in END biome", async () => {
|
||||||
game.override.startingWave(FinalWave.Classic - 1);
|
game.override.startingWave(FinalWave.Classic - 1);
|
||||||
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.CLASSIC);
|
await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.CLASSIC);
|
||||||
|
|
||||||
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);
|
||||||
@ -46,7 +57,7 @@ describe("Final Boss", () => {
|
|||||||
|
|
||||||
it("should NOT spawn Eternatus outside of END biome", async () => {
|
it("should NOT spawn Eternatus outside of END biome", async () => {
|
||||||
game.override.startingBiome(Biome.FOREST);
|
game.override.startingBiome(Biome.FOREST);
|
||||||
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.CLASSIC);
|
await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.CLASSIC);
|
||||||
|
|
||||||
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);
|
||||||
@ -54,12 +65,81 @@ describe("Final Boss", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should not have passive enabled on Eternatus", async () => {
|
it("should not have passive enabled on Eternatus", async () => {
|
||||||
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.CLASSIC);
|
await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.CLASSIC);
|
||||||
|
|
||||||
const eternatus = game.scene.getEnemyPokemon();
|
const eternatus = game.scene.getEnemyPokemon()!;
|
||||||
expect(eternatus?.species.speciesId).toBe(Species.ETERNATUS);
|
expect(eternatus.species.speciesId).toBe(Species.ETERNATUS);
|
||||||
expect(eternatus?.hasPassive()).toBe(false);
|
expect(eternatus.hasPassive()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should change form on direct hit down to last boss fragment", async () => {
|
||||||
|
await game.runToFinalBossEncounter([Species.KYUREM], GameModes.CLASSIC);
|
||||||
|
await game.phaseInterceptor.to("CommandPhase");
|
||||||
|
|
||||||
|
// Eternatus phase 1
|
||||||
|
const eternatus = game.scene.getEnemyPokemon()!;
|
||||||
|
const phase1Hp = eternatus.getMaxHp();
|
||||||
|
expect(eternatus.species.speciesId).toBe(Species.ETERNATUS);
|
||||||
|
expect(eternatus.formIndex).toBe(0);
|
||||||
|
expect(eternatus.bossSegments).toBe(4);
|
||||||
|
expect(eternatus.bossSegmentIndex).toBe(3);
|
||||||
|
|
||||||
|
game.move.select(Moves.DRAGON_PULSE);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
// Eternatus phase 2: changed form, healed and restored its shields
|
||||||
|
expect(eternatus.species.speciesId).toBe(Species.ETERNATUS);
|
||||||
|
expect(eternatus.hp).toBeGreaterThan(phase1Hp);
|
||||||
|
expect(eternatus.hp).toBe(eternatus.getMaxHp());
|
||||||
|
expect(eternatus.formIndex).toBe(1);
|
||||||
|
expect(eternatus.bossSegments).toBe(5);
|
||||||
|
expect(eternatus.bossSegmentIndex).toBe(4);
|
||||||
|
const miniBlackHole = eternatus.getHeldItems().find(m => m instanceof TurnHeldItemTransferModifier);
|
||||||
|
expect(miniBlackHole).toBeDefined();
|
||||||
|
expect(miniBlackHole?.stackCount).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should change form on status damage down to last boss fragment", async () => {
|
||||||
|
game.override.ability(Abilities.NO_GUARD);
|
||||||
|
|
||||||
|
await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.CLASSIC);
|
||||||
|
await game.phaseInterceptor.to("CommandPhase");
|
||||||
|
|
||||||
|
// Eternatus phase 1
|
||||||
|
const eternatus = game.scene.getEnemyPokemon()!;
|
||||||
|
const phase1Hp = eternatus.getMaxHp();
|
||||||
|
expect(eternatus.species.speciesId).toBe(Species.ETERNATUS);
|
||||||
|
expect(eternatus.formIndex).toBe(0);
|
||||||
|
expect(eternatus.bossSegments).toBe(4);
|
||||||
|
expect(eternatus.bossSegmentIndex).toBe(3);
|
||||||
|
|
||||||
|
game.move.select(Moves.WILL_O_WISP);
|
||||||
|
await game.toNextTurn();
|
||||||
|
expect(eternatus.status?.effect).toBe(StatusEffect.BURN);
|
||||||
|
|
||||||
|
const tickDamage = phase1Hp - eternatus.hp;
|
||||||
|
const lastShieldHp = Math.ceil(phase1Hp / eternatus.bossSegments);
|
||||||
|
// Stall until the burn is one hit away from breaking the last shield
|
||||||
|
while (eternatus.hp - tickDamage > lastShieldHp) {
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(eternatus.bossSegmentIndex).toBe(1);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
// Eternatus phase 2: changed form, healed and restored its shields
|
||||||
|
expect(eternatus.hp).toBeGreaterThan(phase1Hp);
|
||||||
|
expect(eternatus.hp).toBe(eternatus.getMaxHp());
|
||||||
|
expect(eternatus.status).toBeFalsy();
|
||||||
|
expect(eternatus.formIndex).toBe(1);
|
||||||
|
expect(eternatus.bossSegments).toBe(5);
|
||||||
|
expect(eternatus.bossSegmentIndex).toBe(4);
|
||||||
|
const miniBlackHole = eternatus.getHeldItems().find(m => m instanceof TurnHeldItemTransferModifier);
|
||||||
|
expect(miniBlackHole).toBeDefined();
|
||||||
|
expect(miniBlackHole?.stackCount).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.todo("should change form on direct hit down to last boss fragment", () => {});
|
|
||||||
});
|
});
|
||||||
|
@ -30,7 +30,7 @@ describe("Test misc", () => {
|
|||||||
return response.json();
|
return response.json();
|
||||||
}).then(data => {
|
}).then(data => {
|
||||||
spy(); // Call the spy function
|
spy(); // Call the spy function
|
||||||
expect(data).toEqual({"username":"greenlamp", "lastSessionSlot":0});
|
expect(data).toEqual({ "username": "greenlamp", "lastSessionSlot": 0 });
|
||||||
});
|
});
|
||||||
expect(spy).toHaveBeenCalled();
|
expect(spy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@ -43,7 +43,7 @@ describe("Test misc", () => {
|
|||||||
return response.json();
|
return response.json();
|
||||||
}).then(data => {
|
}).then(data => {
|
||||||
spy(); // Call the spy function
|
spy(); // Call the spy function
|
||||||
expect(data).toEqual({"username":"greenlamp", "lastSessionSlot":0});
|
expect(data).toEqual({ "username": "greenlamp", "lastSessionSlot": 0 });
|
||||||
});
|
});
|
||||||
expect(spy).toHaveBeenCalled();
|
expect(spy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@ -54,7 +54,7 @@ describe("Test misc", () => {
|
|||||||
|
|
||||||
expect(response.ok).toBe(true);
|
expect(response.ok).toBe(true);
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
expect(data).toEqual({"username":"greenlamp", "lastSessionSlot":0});
|
expect(data).toEqual({ "username": "greenlamp", "lastSessionSlot": 0 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("test apifetch mock sync", async () => {
|
it("test apifetch mock sync", async () => {
|
54
src/test/moves/alluring_voice.test.ts
Normal file
54
src/test/moves/alluring_voice.test.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
import { Abilities } from "#app/enums/abilities";
|
||||||
|
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||||
|
import { BerryPhase } from "#app/phases/berry-phase";
|
||||||
|
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 } from "vitest";
|
||||||
|
|
||||||
|
const TIMEOUT = 20 * 1000;
|
||||||
|
|
||||||
|
describe("Moves - Alluring Voice", () => {
|
||||||
|
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
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.ICE_SCALES)
|
||||||
|
.enemyMoveset(Array(4).fill(Moves.HOWL))
|
||||||
|
.startingLevel(10)
|
||||||
|
.enemyLevel(10)
|
||||||
|
.starterSpecies(Species.FEEBAS)
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.moveset([Moves.ALLURING_VOICE]);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should confuse the opponent if their stats were raised", async () => {
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
game.move.select(Moves.ALLURING_VOICE);
|
||||||
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
|
await game.phaseInterceptor.to(BerryPhase);
|
||||||
|
|
||||||
|
expect(enemy.getTag(BattlerTagType.CONFUSED)?.tagType).toBe("CONFUSED");
|
||||||
|
}, TIMEOUT);
|
||||||
|
});
|
103
src/test/moves/burning_jealousy.test.ts
Normal file
103
src/test/moves/burning_jealousy.test.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
import { allMoves } from "#app/data/move";
|
||||||
|
import { Abilities } from "#app/enums/abilities";
|
||||||
|
import { StatusEffect } from "#app/enums/status-effect";
|
||||||
|
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";
|
||||||
|
import { SPLASH_ONLY } from "../utils/testUtils";
|
||||||
|
|
||||||
|
const TIMEOUT = 20 * 1000;
|
||||||
|
|
||||||
|
describe("Moves - Burning Jealousy", () => {
|
||||||
|
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
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.ICE_SCALES)
|
||||||
|
.enemyMoveset(Array(4).fill(Moves.HOWL))
|
||||||
|
.startingLevel(10)
|
||||||
|
.enemyLevel(10)
|
||||||
|
.starterSpecies(Species.FEEBAS)
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.moveset([Moves.BURNING_JEALOUSY, Moves.GROWL]);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should burn the opponent if their stats were raised", async () => {
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
game.move.select(Moves.BURNING_JEALOUSY);
|
||||||
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemy.status?.effect).toBe(StatusEffect.BURN);
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("should still burn the opponent if their stats were both raised and lowered in the same turn", async () => {
|
||||||
|
game.override
|
||||||
|
.starterSpecies(0)
|
||||||
|
.battleType("double");
|
||||||
|
await game.classicMode.startBattle([Species.FEEBAS, Species.ABRA]);
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
game.move.select(Moves.BURNING_JEALOUSY);
|
||||||
|
game.move.select(Moves.GROWL, 1);
|
||||||
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2]);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemy.status?.effect).toBe(StatusEffect.BURN);
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("should ignore stats raised by imposter", async () => {
|
||||||
|
game.override
|
||||||
|
.enemySpecies(Species.DITTO)
|
||||||
|
.enemyAbility(Abilities.IMPOSTER)
|
||||||
|
.enemyMoveset(SPLASH_ONLY);
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
game.move.select(Moves.BURNING_JEALOUSY);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemy.status?.effect).toBeUndefined();
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it.skip("should ignore weakness policy", async () => { // TODO: Make this test if WP is implemented
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("should be boosted by Sheer Force even if opponent didn't raise stats", async () => {
|
||||||
|
game.override
|
||||||
|
.ability(Abilities.SHEER_FORCE)
|
||||||
|
.enemyMoveset(SPLASH_ONLY);
|
||||||
|
vi.spyOn(allMoves[Moves.BURNING_JEALOUSY], "calculateBattlePower");
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
game.move.select(Moves.BURNING_JEALOUSY);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(allMoves[Moves.BURNING_JEALOUSY].calculateBattlePower).toHaveReturnedWith(allMoves[Moves.BURNING_JEALOUSY].power * 5461 / 4096);
|
||||||
|
}, TIMEOUT);
|
||||||
|
});
|
52
src/test/moves/lash_out.test.ts
Normal file
52
src/test/moves/lash_out.test.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
import { allMoves } from "#app/data/move";
|
||||||
|
import { Abilities } from "#app/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";
|
||||||
|
|
||||||
|
const TIMEOUT = 20 * 1000;
|
||||||
|
|
||||||
|
describe("Moves - Lash Out", () => {
|
||||||
|
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
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.FUR_COAT)
|
||||||
|
.enemyMoveset(Array(4).fill(Moves.GROWL))
|
||||||
|
.startingLevel(10)
|
||||||
|
.enemyLevel(10)
|
||||||
|
.starterSpecies(Species.FEEBAS)
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.moveset([Moves.LASH_OUT]);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should deal double damage if the user's stats were lowered this turn", async () => {
|
||||||
|
vi.spyOn(allMoves[Moves.LASH_OUT], "calculateBattlePower");
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
game.move.select(Moves.LASH_OUT);
|
||||||
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(allMoves[Moves.LASH_OUT].calculateBattlePower).toHaveReturnedWith(150);
|
||||||
|
}, TIMEOUT);
|
||||||
|
});
|
@ -1,10 +1,10 @@
|
|||||||
import { CommandPhase } from "#app/phases/command-phase";
|
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import GameManager from "#test/utils/gameManager";
|
import GameManager from "#test/utils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
import { SPLASH_ONLY } from "../utils/testUtils";
|
||||||
|
|
||||||
|
|
||||||
describe("Moves - Spikes", () => {
|
describe("Moves - Spikes", () => {
|
||||||
@ -23,93 +23,61 @@ describe("Moves - Spikes", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
game.scene.battleStyle = 1;
|
game.override
|
||||||
game.override.battleType("single");
|
.battleType("single")
|
||||||
game.override.enemySpecies(Species.RATTATA);
|
.enemySpecies(Species.MAGIKARP)
|
||||||
game.override.enemyAbility(Abilities.HYDRATION);
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
game.override.enemyPassiveAbility(Abilities.HYDRATION);
|
.ability(Abilities.BALL_FETCH)
|
||||||
game.override.ability(Abilities.HYDRATION);
|
.enemyMoveset(SPLASH_ONLY)
|
||||||
game.override.passiveAbility(Abilities.HYDRATION);
|
.moveset([Moves.SPIKES, Moves.SPLASH, Moves.ROAR]);
|
||||||
game.override.startingWave(3);
|
|
||||||
game.override.enemyMoveset([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]);
|
|
||||||
game.override.moveset([Moves.SPIKES, Moves.SPLASH, Moves.ROAR]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("single - wild - stay on field - no damage", async () => {
|
it("should not damage the team that set them", async () => {
|
||||||
await game.classicMode.runToSummon([
|
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||||
Species.MIGHTYENA,
|
|
||||||
Species.POOCHYENA,
|
|
||||||
]);
|
|
||||||
await game.phaseInterceptor.to(CommandPhase, true);
|
|
||||||
const initialHp = game.scene.getParty()[0].hp;
|
|
||||||
expect(game.scene.getParty()[0].hp).toBe(initialHp);
|
|
||||||
game.move.select(Moves.SPIKES);
|
game.move.select(Moves.SPIKES);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
game.move.select(Moves.SPLASH);
|
game.move.select(Moves.SPLASH);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
expect(game.scene.getParty()[0].hp).toBe(initialHp);
|
|
||||||
}, 20000);
|
|
||||||
|
|
||||||
it("single - wild - take some damage", async () => {
|
|
||||||
// player set spikes on the field and switch back to back
|
|
||||||
// opponent do splash for 2 turns
|
|
||||||
// nobody should take damage
|
|
||||||
await game.classicMode.runToSummon([
|
|
||||||
Species.MIGHTYENA,
|
|
||||||
Species.POOCHYENA,
|
|
||||||
]);
|
|
||||||
await game.phaseInterceptor.to(CommandPhase, false);
|
|
||||||
|
|
||||||
const initialHp = game.scene.getParty()[0].hp;
|
|
||||||
game.doSwitchPokemon(1);
|
|
||||||
await game.phaseInterceptor.run(CommandPhase);
|
|
||||||
await game.phaseInterceptor.to(CommandPhase, false);
|
|
||||||
|
|
||||||
game.doSwitchPokemon(1);
|
game.doSwitchPokemon(1);
|
||||||
await game.phaseInterceptor.run(CommandPhase);
|
await game.toNextTurn();
|
||||||
await game.phaseInterceptor.to(CommandPhase, false);
|
|
||||||
|
|
||||||
expect(game.scene.getParty()[0].hp).toBe(initialHp);
|
game.doSwitchPokemon(1);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
const player = game.scene.getParty()[0];
|
||||||
|
expect(player.hp).toBe(player.getMaxHp());
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
it("trainer - wild - force switch opponent - should take damage", async () => {
|
it("should damage opposing pokemon that are forced to switch in", async () => {
|
||||||
game.override.startingWave(5);
|
game.override.startingWave(5);
|
||||||
// player set spikes on the field and do splash for 3 turns
|
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||||
// opponent do splash for 4 turns
|
|
||||||
// nobody should take damage
|
|
||||||
await game.classicMode.runToSummon([
|
|
||||||
Species.MIGHTYENA,
|
|
||||||
Species.POOCHYENA,
|
|
||||||
]);
|
|
||||||
await game.phaseInterceptor.to(CommandPhase, true);
|
|
||||||
const initialHpOpponent = game.scene.currentBattle.enemyParty[1].hp;
|
|
||||||
game.move.select(Moves.SPIKES);
|
game.move.select(Moves.SPIKES);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
game.move.select(Moves.ROAR);
|
game.move.select(Moves.ROAR);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
expect(game.scene.currentBattle.enemyParty[0].hp).toBeLessThan(initialHpOpponent);
|
|
||||||
|
const enemy = game.scene.getEnemyParty()[0];
|
||||||
|
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
it("trainer - wild - force switch by himself opponent - should take damage", async () => {
|
it("should damage opposing pokemon that choose to switch in", async () => {
|
||||||
game.override.startingWave(5);
|
game.override.startingWave(5);
|
||||||
game.override.startingLevel(5000);
|
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||||
game.override.enemySpecies(0);
|
|
||||||
// turn 1: player set spikes, opponent do splash
|
|
||||||
// turn 2: player do splash, opponent switch pokemon
|
|
||||||
// opponent pokemon should trigger spikes and lose HP
|
|
||||||
await game.classicMode.runToSummon([
|
|
||||||
Species.MIGHTYENA,
|
|
||||||
Species.POOCHYENA,
|
|
||||||
]);
|
|
||||||
await game.phaseInterceptor.to(CommandPhase, true);
|
|
||||||
const initialHpOpponent = game.scene.currentBattle.enemyParty[1].hp;
|
|
||||||
game.move.select(Moves.SPIKES);
|
game.move.select(Moves.SPIKES);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
game.forceOpponentToSwitch();
|
|
||||||
game.move.select(Moves.SPLASH);
|
game.move.select(Moves.SPLASH);
|
||||||
|
game.forceOpponentToSwitch();
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
expect(game.scene.currentBattle.enemyParty[0].hp).toBeLessThan(initialHpOpponent);
|
|
||||||
|
const enemy = game.scene.getEnemyParty()[0];
|
||||||
|
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -6,6 +6,7 @@ import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
|||||||
import Trainer from "#app/field/trainer";
|
import Trainer from "#app/field/trainer";
|
||||||
import { GameModes, getGameMode } from "#app/game-mode";
|
import { GameModes, getGameMode } from "#app/game-mode";
|
||||||
import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type";
|
import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type";
|
||||||
|
import overrides from "#app/overrides";
|
||||||
import { CommandPhase } from "#app/phases/command-phase";
|
import { CommandPhase } from "#app/phases/command-phase";
|
||||||
import { EncounterPhase } from "#app/phases/encounter-phase";
|
import { EncounterPhase } from "#app/phases/encounter-phase";
|
||||||
import { FaintPhase } from "#app/phases/faint-phase";
|
import { FaintPhase } from "#app/phases/faint-phase";
|
||||||
@ -138,7 +139,7 @@ export default class GameManager {
|
|||||||
this.scene.hpBarSpeed = 3;
|
this.scene.hpBarSpeed = 3;
|
||||||
this.scene.enableTutorials = false;
|
this.scene.enableTutorials = false;
|
||||||
this.scene.gameData.gender = PlayerGender.MALE; // set initial player gender
|
this.scene.gameData.gender = PlayerGender.MALE; // set initial player gender
|
||||||
|
this.scene.battleStyle = this.settings.battleStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -148,28 +149,26 @@ export default class GameManager {
|
|||||||
* @param species
|
* @param species
|
||||||
* @param mode
|
* @param mode
|
||||||
*/
|
*/
|
||||||
async runToFinalBossEncounter(game: GameManager, species: Species[], mode: GameModes) {
|
async runToFinalBossEncounter(species: Species[], mode: GameModes) {
|
||||||
console.log("===to final boss encounter===");
|
console.log("===to final boss encounter===");
|
||||||
await game.runToTitle();
|
await this.runToTitle();
|
||||||
|
|
||||||
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
|
this.onNextPrompt("TitlePhase", Mode.TITLE, () => {
|
||||||
game.scene.gameMode = getGameMode(mode);
|
this.scene.gameMode = getGameMode(mode);
|
||||||
const starters = generateStarter(game.scene, species);
|
const starters = generateStarter(this.scene, species);
|
||||||
const selectStarterPhase = new SelectStarterPhase(game.scene);
|
const selectStarterPhase = new SelectStarterPhase(this.scene);
|
||||||
game.scene.pushPhase(new EncounterPhase(game.scene, false));
|
this.scene.pushPhase(new EncounterPhase(this.scene, false));
|
||||||
selectStarterPhase.initBattle(starters);
|
selectStarterPhase.initBattle(starters);
|
||||||
});
|
});
|
||||||
|
|
||||||
game.onNextPrompt("EncounterPhase", Mode.MESSAGE, async () => {
|
// This will consider all battle entry dialog as seens and skip them
|
||||||
// This will skip all entry dialogue (I can't figure out a way to sequentially handle the 8 chained messages via 1 prompt handler)
|
vi.spyOn(this.scene.ui, "shouldSkipDialogue").mockReturnValue(true);
|
||||||
game.setMode(Mode.MESSAGE);
|
|
||||||
const encounterPhase = game.scene.getCurrentPhase() as EncounterPhase;
|
|
||||||
|
|
||||||
// No need to end phase, this will do it for you
|
if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0) {
|
||||||
encounterPhase.doEncounterCommon(false);
|
this.removeEnemyHeldItems();
|
||||||
});
|
}
|
||||||
|
|
||||||
await game.phaseInterceptor.to(EncounterPhase, true);
|
await this.phaseInterceptor.to(EncounterPhase);
|
||||||
console.log("===finished run to final boss encounter===");
|
console.log("===finished run to final boss encounter===");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,30 @@
|
|||||||
import { PlayerGender } from "#app/enums/player-gender";
|
import { PlayerGender } from "#app/enums/player-gender";
|
||||||
|
import { BattleStyle } from "#app/enums/battle-style";
|
||||||
import { GameManagerHelper } from "./gameManagerHelper";
|
import { GameManagerHelper } from "./gameManagerHelper";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to handle settings for tests
|
* Helper to handle settings for tests
|
||||||
*/
|
*/
|
||||||
export class SettingsHelper extends GameManagerHelper {
|
export class SettingsHelper extends GameManagerHelper {
|
||||||
|
private _battleStyle: BattleStyle = BattleStyle.SET;
|
||||||
|
|
||||||
|
get battleStyle(): BattleStyle {
|
||||||
|
return this._battleStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the battle style to Switch or Set mode (tests default to {@linkcode BattleStyle.SET})
|
||||||
|
* @param mode {@linkcode BattleStyle.SWITCH} or {@linkcode BattleStyle.SET}
|
||||||
|
*/
|
||||||
|
set battleStyle(mode: BattleStyle.SWITCH | BattleStyle.SET) {
|
||||||
|
this._battleStyle = mode;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable/Enable type hints settings
|
* Disable/Enable type hints settings
|
||||||
* @param enable true to enabled, false to disabled
|
* @param enable true to enabled, false to disabled
|
||||||
*/
|
*/
|
||||||
typeHints(enable: boolean) {
|
typeHints(enable: boolean): void {
|
||||||
this.game.scene.typeHints = enable;
|
this.game.scene.typeHints = enable;
|
||||||
this.log(`Type Hints ${enable? "enabled" : "disabled"}` );
|
this.log(`Type Hints ${enable? "enabled" : "disabled"}` );
|
||||||
}
|
}
|
||||||
|
@ -317,10 +317,11 @@ export default class UI extends Phaser.GameObjects.Container {
|
|||||||
if (i18next.exists(keyOrText) ) {
|
if (i18next.exists(keyOrText) ) {
|
||||||
const i18nKey = keyOrText;
|
const i18nKey = keyOrText;
|
||||||
hasi18n = true;
|
hasi18n = true;
|
||||||
|
|
||||||
text = i18next.t(i18nKey, { context: genderStr }); // override text with translation
|
text = i18next.t(i18nKey, { context: genderStr }); // override text with translation
|
||||||
|
|
||||||
// Skip dialogue if the player has enabled the option and the dialogue has been already seen
|
// Skip dialogue if the player has enabled the option and the dialogue has been already seen
|
||||||
if (battleScene.skipSeenDialogues && battleScene.gameData.getSeenDialogues()[i18nKey] === true) {
|
if (this.shouldSkipDialogue(i18nKey)) {
|
||||||
console.log(`Dialogue ${i18nKey} skipped`);
|
console.log(`Dialogue ${i18nKey} skipped`);
|
||||||
callback();
|
callback();
|
||||||
return;
|
return;
|
||||||
|
Loading…
Reference in New Issue
Block a user