mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-05 16:02:20 +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 .",
|
||||
"docs": "typedoc",
|
||||
"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": {
|
||||
"@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 { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
|
||||
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 { Stat, getStatName } from "./pokemon-stat";
|
||||
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 {
|
||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise<boolean> {
|
||||
return false;
|
||||
@ -2387,7 +2428,7 @@ export class PostSummonWeatherSuppressedFormChangeAbAttr extends PostSummonAbAtt
|
||||
|
||||
/**
|
||||
* Triggers weather-based form change when summoned into an active weather.
|
||||
* Used by Forecast.
|
||||
* Used by Forecast and Flower Gift.
|
||||
* @extends PostSummonAbAttr
|
||||
*/
|
||||
export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr {
|
||||
@ -2410,7 +2451,10 @@ export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr {
|
||||
* @returns whether the form change was triggered
|
||||
*/
|
||||
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) {
|
||||
return simulated;
|
||||
}
|
||||
@ -3083,37 +3127,41 @@ export class PostWeatherChangeAbAttr extends AbAttr {
|
||||
|
||||
/**
|
||||
* Triggers weather-based form change when weather changes.
|
||||
* Used by Forecast.
|
||||
* Used by Forecast and Flower Gift.
|
||||
* @extends PostWeatherChangeAbAttr
|
||||
*/
|
||||
export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr {
|
||||
private ability: Abilities;
|
||||
private formRevertingWeathers: WeatherType[];
|
||||
|
||||
constructor(ability: Abilities) {
|
||||
constructor(ability: Abilities, formRevertingWeathers: WeatherType[]) {
|
||||
super(false);
|
||||
|
||||
this.ability = ability;
|
||||
this.formRevertingWeathers = formRevertingWeathers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@linkcode Arena.triggerWeatherBasedFormChangesToNormal | triggerWeatherBasedFormChangesToNormal} when the
|
||||
* 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 weather n/a
|
||||
* @param args n/a
|
||||
* @returns whether the form change was triggered
|
||||
*/
|
||||
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) {
|
||||
return simulated;
|
||||
}
|
||||
|
||||
const formRevertingWeathers: WeatherType[] = [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG ];
|
||||
const weatherType = pokemon.scene.arena.weather?.weatherType;
|
||||
|
||||
if (weatherType && formRevertingWeathers.includes(weatherType)) {
|
||||
if (weatherType && this.formRevertingWeathers.includes(weatherType)) {
|
||||
pokemon.scene.arena.triggerWeatherBasedFormChangesToNormal();
|
||||
} else {
|
||||
pokemon.scene.arena.triggerWeatherBasedFormChanges();
|
||||
@ -4703,7 +4751,8 @@ function setAbilityRevealed(pokemon: Pokemon): void {
|
||||
*/
|
||||
function getPokemonWithWeatherBasedForms(scene: BattleScene) {
|
||||
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(NoFusionAbilityAbAttr)
|
||||
.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)
|
||||
.attr(BlockItemTheftAbAttr)
|
||||
.bypassFaint()
|
||||
@ -5095,8 +5144,10 @@ export function initAbilities() {
|
||||
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), BattleStatMultiplierAbAttr, BattleStat.SPDEF, 1.5)
|
||||
.attr(UncopiableAbilityAbAttr)
|
||||
.attr(NoFusionAbilityAbAttr)
|
||||
.ignorable()
|
||||
.partial(),
|
||||
.attr(PostSummonFormChangeByWeatherAbAttr, Abilities.FLOWER_GIFT)
|
||||
.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)
|
||||
.attr(PostTurnHurtIfSleepingAbAttr),
|
||||
new Ability(Abilities.PICKPOCKET, 5)
|
||||
@ -5761,10 +5812,10 @@ export function initAbilities() {
|
||||
.attr(NoTransformAbilityAbAttr)
|
||||
.attr(NoFusionAbilityAbAttr),
|
||||
new Ability(Abilities.TERA_SHELL, 9)
|
||||
.attr(FullHpResistTypeAbAttr)
|
||||
.attr(UncopiableAbilityAbAttr)
|
||||
.attr(UnswappableAbilityAbAttr)
|
||||
.ignorable()
|
||||
.unimplemented(),
|
||||
.ignorable(),
|
||||
new Ability(Abilities.TERAFORM_ZERO, 9)
|
||||
.attr(UncopiableAbilityAbAttr)
|
||||
.attr(UnswappableAbilityAbAttr)
|
||||
|
@ -788,10 +788,10 @@ export abstract class BattleAnim {
|
||||
targetSprite.pipelineData["tone"] = [ 0.0, 0.0, 0.0, 0.0 ];
|
||||
targetSprite.setAngle(0);
|
||||
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())) {
|
||||
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()) {
|
||||
if (ms) {
|
||||
|
@ -212,7 +212,7 @@ export class TrappedTag extends BattlerTag {
|
||||
|
||||
canAdd(pokemon: Pokemon): boolean {
|
||||
const isGhost = pokemon.isOfType(Type.GHOST);
|
||||
const isTrapped = pokemon.getTag(BattlerTagType.TRAPPED);
|
||||
const isTrapped = pokemon.getTag(TrappedTag);
|
||||
|
||||
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
|
||||
*/
|
||||
@ -868,7 +885,7 @@ export abstract class DamagingTrapTag extends TrappedTag {
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -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 {
|
||||
private sourceIndex: number;
|
||||
|
||||
@ -1868,6 +1904,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
||||
return new DrowsyTag();
|
||||
case BattlerTagType.TRAPPED:
|
||||
return new TrappedTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
||||
case BattlerTagType.NO_RETREAT:
|
||||
return new NoRetreatTag(sourceId);
|
||||
case BattlerTagType.BIND:
|
||||
return new BindTag(turnCount, sourceId);
|
||||
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);
|
||||
case BattlerTagType.CRIT_BOOST:
|
||||
return new CritBoostTag(tagType, sourceMove);
|
||||
case BattlerTagType.DRAGON_CHEER:
|
||||
return new DragonCheerTag();
|
||||
case BattlerTagType.ALWAYS_CRIT:
|
||||
case BattlerTagType.IGNORE_ACCURACY:
|
||||
return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove);
|
||||
|
@ -5889,9 +5889,9 @@ export class SwitchAbilitiesAttr extends MoveEffectAttr {
|
||||
target.summonData.ability = tempAbilityId;
|
||||
|
||||
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();
|
||||
// Swaps Forecast to Castform (edge case)
|
||||
// Swaps Forecast/Flower Gift to Castform/Cherrim (edge case)
|
||||
user.scene.arena.triggerWeatherBasedFormChanges();
|
||||
|
||||
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 {
|
||||
getCondition(): MoveConditionFunc {
|
||||
return (user: Pokemon, target: Pokemon, move: Move) => {
|
||||
@ -8503,7 +8554,7 @@ export function initMoves() {
|
||||
.partial(),
|
||||
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(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
|
||||
new StatusMove(Moves.TAR_SHOT, Type.ROCK, 100, 15, -1, 0, 8)
|
||||
.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)
|
||||
.attr(StatChangeAttr, BattleStat.SPATK, -1),
|
||||
new AttackMove(Moves.BURNING_JEALOUSY, Type.FIRE, MoveCategory.SPECIAL, 70, 100, 5, 100, 0, 8)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
||||
.partial(),
|
||||
.attr(StatusIfBoostedAttr, StatusEffect.BURN)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
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)
|
||||
.attr(AttackedByItemAttr)
|
||||
.makesContact(false),
|
||||
@ -9144,12 +9195,11 @@ export function initMoves() {
|
||||
new AttackMove(Moves.HARD_PRESS, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 9)
|
||||
.attr(OpponentHighHpPowerAttr, 100),
|
||||
new StatusMove(Moves.DRAGON_CHEER, Type.DRAGON, -1, 15, -1, 0, 9)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.CRIT_BOOST, false, true)
|
||||
.target(MoveTarget.NEAR_ALLY)
|
||||
.partial(),
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.DRAGON_CHEER, false, true)
|
||||
.target(MoveTarget.NEAR_ALLY),
|
||||
new AttackMove(Moves.ALLURING_VOICE, Type.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 9)
|
||||
.soundBased()
|
||||
.partial(),
|
||||
.attr(AddBattlerTagIfBoostedAttr, BattlerTagType.CONFUSED)
|
||||
.soundBased(),
|
||||
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),
|
||||
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.
|
||||
* Used by Castform.
|
||||
* Used by Castform and Cherrim.
|
||||
* @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
|
||||
* or when the user loses the ability/is suppressed.
|
||||
* Used by Castform.
|
||||
* Used by Castform and Cherrim.
|
||||
* @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, "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() {
|
||||
|
@ -69,5 +69,7 @@ export enum BattlerTagType {
|
||||
GULP_MISSILE_ARROKUDA = "GULP_MISSILE_ARROKUDA",
|
||||
GULP_MISSILE_PIKACHU = "GULP_MISSILE_PIKACHU",
|
||||
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 {
|
||||
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());
|
||||
this.scene.triggerPokemonFormChange(p, SpeciesFormChangeWeatherTrigger);
|
||||
}
|
||||
@ -351,7 +354,10 @@ export class Arena {
|
||||
*/
|
||||
triggerWeatherBasedFormChangesToNormal(): void {
|
||||
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());
|
||||
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 { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms";
|
||||
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 { TempBattleStat } from "../data/temp-battle-stat";
|
||||
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 { BattlerIndex } from "../battle";
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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;
|
||||
}
|
||||
|
||||
@ -2064,9 +2091,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
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}`);
|
||||
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(critLevel.value, 3))];
|
||||
isCritical = critChance === 1 || !this.scene.randBattleSeedInt(critChance);
|
||||
@ -4298,7 +4332,7 @@ export interface TurnMove {
|
||||
targets?: BattlerIndex[];
|
||||
result: MoveResult;
|
||||
virtual?: boolean;
|
||||
turn?: integer;
|
||||
turn?: number;
|
||||
}
|
||||
|
||||
export interface QueuedMove {
|
||||
@ -4310,17 +4344,17 @@ export interface QueuedMove {
|
||||
export interface AttackMoveResult {
|
||||
move: Moves;
|
||||
result: DamageResult;
|
||||
damage: integer;
|
||||
damage: number;
|
||||
critical: boolean;
|
||||
sourceId: integer;
|
||||
sourceId: number;
|
||||
sourceBattlerIndex: BattlerIndex;
|
||||
}
|
||||
|
||||
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 disabledMove: Moves = Moves.NONE;
|
||||
public disabledTurns: integer = 0;
|
||||
public disabledTurns: number = 0;
|
||||
public tags: BattlerTag[] = [];
|
||||
public abilitySuppressed: boolean = false;
|
||||
public abilitiesApplied: Abilities[] = [];
|
||||
@ -4330,14 +4364,14 @@ export class PokemonSummonData {
|
||||
public ability: Abilities = Abilities.NONE;
|
||||
public gender: Gender;
|
||||
public fusionGender: Gender;
|
||||
public stats: integer[];
|
||||
public stats: number[];
|
||||
public moveset: (PokemonMove | null)[];
|
||||
// If not initialized this value will not be populated from save data.
|
||||
public types: Type[] = [];
|
||||
}
|
||||
|
||||
export class PokemonBattleData {
|
||||
public hitCount: integer = 0;
|
||||
public hitCount: number = 0;
|
||||
public endured: boolean = false;
|
||||
public berriesEaten: BerryType[] = [];
|
||||
public abilitiesApplied: Abilities[] = [];
|
||||
@ -4346,21 +4380,23 @@ export class PokemonBattleData {
|
||||
|
||||
export class PokemonBattleSummonData {
|
||||
/** 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 */
|
||||
public moveHistory: TurnMove[] = [];
|
||||
}
|
||||
|
||||
export class PokemonTurnData {
|
||||
public flinched: boolean;
|
||||
public acted: boolean;
|
||||
public hitCount: integer;
|
||||
public hitsLeft: integer;
|
||||
public damageDealt: integer = 0;
|
||||
public currDamageDealt: integer = 0;
|
||||
public damageTaken: integer = 0;
|
||||
public flinched: boolean = false;
|
||||
public acted: boolean = false;
|
||||
public hitCount: number;
|
||||
public hitsLeft: number;
|
||||
public damageDealt: number = 0;
|
||||
public currDamageDealt: number = 0;
|
||||
public damageTaken: number = 0;
|
||||
public attacksReceived: AttackMoveResult[] = [];
|
||||
public order: number;
|
||||
public battleStatsIncreased: boolean = false;
|
||||
public battleStatsDecreased: boolean = false;
|
||||
}
|
||||
|
||||
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!",
|
||||
"2": "Los geht's! Dies sind meine Gesteins-Pokémon, mein ganzer Stolz!",
|
||||
"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": {
|
||||
"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.",
|
||||
"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?",
|
||||
"5": "Ich habe es vermasselt."
|
||||
"4": "W-was?! Das kann nicht sein! Selbst das war nicht genug?"
|
||||
},
|
||||
"defeat": {
|
||||
"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!",
|
||||
"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": {
|
||||
|
@ -12,6 +12,7 @@
|
||||
"blockItemTheft": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents item theft!",
|
||||
"typeImmunityHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!",
|
||||
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} avoided damage\nwith {{abilityName}}!",
|
||||
"fullHpResistType": "{{pokemonNameWithAffix}} made its shell gleam!\nIt's distorting type matchups!",
|
||||
"moveImmunity": "It doesn't affect {{pokemonNameWithAffix}}!",
|
||||
"reverseDrain": "{{pokemonNameWithAffix}} sucked up the liquid ooze!",
|
||||
"postDefendTypeChange": "{{pokemonNameWithAffix}}'s {{abilityName}}\nmade it the {{typeName}} type!",
|
||||
|
@ -742,7 +742,7 @@
|
||||
"plumeria": {
|
||||
"encounter": {
|
||||
"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."
|
||||
},
|
||||
"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!",
|
||||
"2": "Here goes! These are my rocking Pokémon, my pride and joy!",
|
||||
"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_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": "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": "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": {
|
||||
"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_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.",
|
||||
"4": "Wh-what?! It can't be! Even that wasn't enough?",
|
||||
"5": "I blew it."
|
||||
"4": "Wh-what?! It can't be! Even that wasn't enough?"
|
||||
},
|
||||
"defeat": {
|
||||
"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!",
|
||||
"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": {
|
||||
|
@ -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!",
|
||||
"infiniteLevels": "¡Niveles infinitos!",
|
||||
"everythingStacks": "¡Todo se acumula!",
|
||||
"optionalSaveScumming": "¡Trampas guardando (¡opcionales!)!",
|
||||
"optionalSaveScumming": "¡Trampas de guardado opcionales!",
|
||||
"biomes": "¡35 biomas!",
|
||||
"openSource": "¡Código abierto!",
|
||||
"playWithSpeed": "¡Juega a velocidad 5x!",
|
||||
"liveBugTesting": "¡Arreglo de bugs sobre la marcha!",
|
||||
"heavyInfluence": "¡Influencia Alta en RoR2!",
|
||||
"liveBugTesting": "¡Testeo de bugs en directo!",
|
||||
"heavyInfluence": "¡Mucha Influencia de RoR2!",
|
||||
"pokemonRiskAndPokemonRain": "¡Pokémon Risk y Pokémon Rain!",
|
||||
"nowWithMoreSalt": "¡Con un 33% más de polémica!",
|
||||
"infiniteFusionAtHome": "¡Infinite Fusion en casa!",
|
||||
@ -17,16 +17,16 @@
|
||||
"mubstitute": "¡Mubstituto!",
|
||||
"thatsCrazy": "¡De locos!",
|
||||
"oranceJuice": "¡Zumo de narancia!",
|
||||
"questionableBalancing": "¡Balance cuestionable!",
|
||||
"questionableBalancing": "¡Cambios en balance cuestionables!",
|
||||
"coolShaders": "¡Shaders impresionantes!",
|
||||
"aiFree": "¡Libre de IA!",
|
||||
"suddenDifficultySpikes": "¡Saltos de dificultad repentinos!",
|
||||
"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!",
|
||||
"achievementPointsDontDoAnything": "¡Los Puntos de Logro no hacen nada!",
|
||||
"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!",
|
||||
"alsoTryEmeraldRogue": "¡Prueba también Emerald Rogue!",
|
||||
"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",
|
||||
"misty": "Misty",
|
||||
"lt_surge": "Tt. Surge",
|
||||
"lt_surge": "Teniente Surge",
|
||||
"erika": "Erika",
|
||||
"janine": "Sachiko",
|
||||
"sabrina": "Sabrina",
|
||||
@ -23,7 +23,7 @@
|
||||
"winona": "Alana",
|
||||
"tate": "Vito",
|
||||
"liza": "Leti",
|
||||
"juan": "Galán",
|
||||
"juan": "Galano",
|
||||
"roark": "Roco",
|
||||
"gardenia": "Gardenia",
|
||||
"maylene": "Brega",
|
||||
@ -34,7 +34,7 @@
|
||||
"volkner": "Lectro",
|
||||
"cilan": "Millo",
|
||||
"chili": "Zeo",
|
||||
"cress": "Maiz",
|
||||
"cress": "Maíz",
|
||||
"cheren": "Cheren",
|
||||
"lenora": "Aloe",
|
||||
"roxie": "Hiedra",
|
||||
@ -57,7 +57,7 @@
|
||||
"nessa": "Cathy",
|
||||
"kabu": "Naboru",
|
||||
"bea": "Judith",
|
||||
"allister": "Allistair",
|
||||
"allister": "Alistair",
|
||||
"opal": "Sally",
|
||||
"bede": "Berto",
|
||||
"gordie": "Morris",
|
||||
@ -123,30 +123,28 @@
|
||||
"leon": "Lionel",
|
||||
"rival": "Finn",
|
||||
"rival_female": "Ivy",
|
||||
"archer": "Archer",
|
||||
"ariana": "Ariana",
|
||||
"proton": "Proton",
|
||||
"archer": "Atlas",
|
||||
"ariana": "Atenea",
|
||||
"proton": "Protón",
|
||||
"petrel": "Petrel",
|
||||
"tabitha": "Tabitha",
|
||||
"courtney": "Courtney",
|
||||
"shelly": "Shelly",
|
||||
"matt": "Matt",
|
||||
"mars": "Mars",
|
||||
"jupiter": "Jupiter",
|
||||
"saturn": "Saturn",
|
||||
"zinzolin": "Zinzolin",
|
||||
"rood": "Rood",
|
||||
"xerosic": "Xerosic",
|
||||
"bryony": "Bryony",
|
||||
"tabitha": "Tatiano",
|
||||
"courtney": "Carola",
|
||||
"shelly": "Silvina",
|
||||
"matt": "Matías",
|
||||
"mars": "Venus",
|
||||
"jupiter": "Ceres",
|
||||
"saturn": "Saturno",
|
||||
"zinzolin": "Menek",
|
||||
"rood": "Ruga",
|
||||
"xerosic": "Xero",
|
||||
"bryony": "Begonia",
|
||||
"maxie": "Magno",
|
||||
"archie": "Aquiles",
|
||||
"cyrus": "Helio",
|
||||
"ghetsis": "Ghechis",
|
||||
"lysandre": "Lysson",
|
||||
"faba": "Fabio",
|
||||
|
||||
"maxie": "Maxie",
|
||||
"archie": "Archie",
|
||||
"cyrus": "Cyrus",
|
||||
"ghetsis": "Ghetsis",
|
||||
"lysandre": "Lysandre",
|
||||
"lusamine": "Samina",
|
||||
|
||||
"blue_red_double": "Azul y Rojo",
|
||||
"red_blue_double": "Rojo y Azul",
|
||||
"tate_liza_double": "Vito y Leti",
|
||||
|
@ -13,5 +13,32 @@
|
||||
"metFragment": {
|
||||
"normal": "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": "독침붕처럼 쏴라"
|
||||
},
|
||||
"MONO_GHOST": {
|
||||
"name": "누굴 부를 거야?"
|
||||
"name": "무서운 게 딱 좋아!"
|
||||
},
|
||||
"MONO_STEEL": {
|
||||
"name": "강철 심장"
|
||||
@ -265,4 +265,4 @@
|
||||
"name": "상성 전문가(였던 것)",
|
||||
"description": "거꾸로 배틀 챌린지 모드 클리어."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,18 @@
|
||||
import BattleScene from "#app/battle-scene.js";
|
||||
import { TurnCommand, BattleType } from "#app/battle.js";
|
||||
import { applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "#app/data/ability.js";
|
||||
import { TrappedTag, EncoreTag } from "#app/data/battler-tags.js";
|
||||
import { MoveTargetSet, getMoveTargets } from "#app/data/move.js";
|
||||
import { speciesStarters } from "#app/data/pokemon-species.js";
|
||||
import { Type } from "#app/data/type.js";
|
||||
import { Abilities } from "#app/enums/abilities.js";
|
||||
import { BattlerTagType } from "#app/enums/battler-tag-type.js";
|
||||
import { Biome } from "#app/enums/biome.js";
|
||||
import { Moves } from "#app/enums/moves.js";
|
||||
import { PokeballType } from "#app/enums/pokeball.js";
|
||||
import { FieldPosition, PlayerPokemon } from "#app/field/pokemon.js";
|
||||
import { getPokemonNameWithAffix } from "#app/messages.js";
|
||||
import { Command } from "#app/ui/command-ui-handler.js";
|
||||
import { Mode } from "#app/ui/ui.js";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { TurnCommand, BattleType } from "#app/battle";
|
||||
import { TrappedTag, EncoreTag } from "#app/data/battler-tags";
|
||||
import { MoveTargetSet, getMoveTargets } from "#app/data/move";
|
||||
import { speciesStarters } from "#app/data/pokemon-species";
|
||||
import { Abilities } from "#app/enums/abilities";
|
||||
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||
import { Biome } from "#app/enums/biome";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { PokeballType } from "#app/enums/pokeball";
|
||||
import { FieldPosition, PlayerPokemon } from "#app/field/pokemon";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { Command } from "#app/ui/command-ui-handler";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import i18next from "i18next";
|
||||
import * as Utils from "#app/utils.js";
|
||||
import { FieldPhase } from "./field-phase";
|
||||
import { SelectTargetPhase } from "./select-target-phase";
|
||||
|
||||
@ -77,7 +74,6 @@ export class CommandPhase extends FieldPhase {
|
||||
|
||||
handleCommand(command: Command, cursor: integer, ...args: any[]): boolean {
|
||||
const playerPokemon = this.scene.getPlayerField()[this.fieldIndex];
|
||||
const enemyField = this.scene.getEnemyField();
|
||||
let success: boolean;
|
||||
|
||||
switch (command) {
|
||||
@ -184,14 +180,9 @@ export class CommandPhase extends FieldPhase {
|
||||
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
|
||||
}, null, true);
|
||||
} 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 trappedAbMessages: string[] = [];
|
||||
if (!batonPass) {
|
||||
enemyField.forEach(enemyPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trapped, playerPokemon, trappedAbMessages, true));
|
||||
}
|
||||
if (batonPass || (!trapTag && !trapped.value)) {
|
||||
if (batonPass || !playerPokemon.isTrapped(trappedAbMessages)) {
|
||||
this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch
|
||||
? { command: Command.POKEMON, cursor: cursor, args: args }
|
||||
: { command: Command.RUN };
|
||||
@ -199,14 +190,27 @@ export class CommandPhase extends FieldPhase {
|
||||
if (!isSwitch && this.fieldIndex) {
|
||||
this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true;
|
||||
}
|
||||
} else if (trapTag) {
|
||||
if (trapTag.sourceMove === Moves.INGRAIN && trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId)?.isOfType(Type.GHOST)) {
|
||||
success = true;
|
||||
} else if (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);
|
||||
} 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
|
||||
? { command: Command.POKEMON, cursor: cursor, args: args }
|
||||
: { command: Command.RUN };
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isSwitch) {
|
||||
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
|
||||
this.scene.ui.setMode(Mode.MESSAGE);
|
||||
@ -224,16 +228,6 @@ export class CommandPhase extends FieldPhase {
|
||||
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
|
||||
}
|
||||
}, 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;
|
||||
|
@ -1,9 +1,6 @@
|
||||
import BattleScene from "#app/battle-scene.js";
|
||||
import { BattlerIndex } from "#app/battle.js";
|
||||
import { applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "#app/data/ability.js";
|
||||
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 BattleScene from "#app/battle-scene";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { Command } from "#app/ui/command-ui-handler";
|
||||
import { FieldPhase } from "./field-phase";
|
||||
|
||||
/**
|
||||
@ -45,10 +42,7 @@ export class EnemyCommandPhase extends FieldPhase {
|
||||
if (trainer && !enemyPokemon.getMoveQueue().length) {
|
||||
const opponents = enemyPokemon.getOpponents();
|
||||
|
||||
const trapTag = enemyPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag;
|
||||
const trapped = new Utils.BooleanHolder(false);
|
||||
opponents.forEach(playerPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, playerPokemon, trapped, enemyPokemon, [""], true));
|
||||
if (!trapTag && !trapped.value) {
|
||||
if (!enemyPokemon.isTrapped()) {
|
||||
const partyMemberScores = trainer.getPartyMemberMatchupScores(enemyPokemon.trainerSlot, true);
|
||||
|
||||
if (partyMemberScores.length) {
|
||||
|
@ -1,14 +1,14 @@
|
||||
import BattleScene from "#app/battle-scene.js";
|
||||
import { BattlerIndex } from "#app/battle.js";
|
||||
import { applyPreStatChangeAbAttrs, ProtectStatAbAttr, applyAbAttrs, StatChangeMultiplierAbAttr, StatChangeCopyAbAttr, applyPostStatChangeAbAttrs, PostStatChangeAbAttr } from "#app/data/ability.js";
|
||||
import { MistTag, ArenaTagSide } from "#app/data/arena-tag.js";
|
||||
import { BattleStat, getBattleStatName, getBattleStatLevelChangeDescription } from "#app/data/battle-stat.js";
|
||||
import Pokemon from "#app/field/pokemon.js";
|
||||
import { getPokemonNameWithAffix } from "#app/messages.js";
|
||||
import { PokemonResetNegativeStatStageModifier } from "#app/modifier/modifier.js";
|
||||
import { handleTutorial, Tutorial } from "#app/tutorial.js";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { applyAbAttrs, applyPostStatChangeAbAttrs, applyPreStatChangeAbAttrs, PostStatChangeAbAttr, ProtectStatAbAttr, StatChangeCopyAbAttr, StatChangeMultiplierAbAttr } from "#app/data/ability";
|
||||
import { ArenaTagSide, MistTag } from "#app/data/arena-tag";
|
||||
import { BattleStat, getBattleStatLevelChangeDescription, getBattleStatName } from "#app/data/battle-stat";
|
||||
import Pokemon from "#app/field/pokemon";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { PokemonResetNegativeStatStageModifier } from "#app/modifier/modifier";
|
||||
import { handleTutorial, Tutorial } from "#app/tutorial";
|
||||
import * as Utils from "#app/utils";
|
||||
import i18next from "i18next";
|
||||
import * as Utils from "#app/utils.js";
|
||||
import { PokemonPhase } from "./pokemon-phase";
|
||||
|
||||
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 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);
|
||||
|
||||
@ -85,6 +85,20 @@ export class StatChangePhase extends PokemonPhase {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { BattleStyle } from "#app/enums/battle-style";
|
||||
import { CommandPhase } from "#app/phases/command-phase";
|
||||
import { MessagePhase } from "#app/phases/message-phase";
|
||||
import { TurnInitPhase } from "#app/phases/turn-init-phase";
|
||||
import i18next, { initI18n } from "#app/plugins/i18n";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
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";
|
||||
|
||||
@ -28,19 +26,18 @@ describe("Ability Timing", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override.battleType("single");
|
||||
|
||||
game.override.enemySpecies(Species.PIDGEY);
|
||||
game.override.enemyAbility(Abilities.INTIMIDATE);
|
||||
game.override.enemyMoveset(SPLASH_ONLY);
|
||||
|
||||
game.override.ability(Abilities.BALL_FETCH);
|
||||
game.override.moveset([Moves.SPLASH, Moves.ICE_BEAM]);
|
||||
game.override
|
||||
.battleType("single")
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyAbility(Abilities.INTIMIDATE)
|
||||
.ability(Abilities.BALL_FETCH);
|
||||
});
|
||||
|
||||
it("should trigger after switch check", async() => {
|
||||
it("should trigger after switch check", async () => {
|
||||
initI18n();
|
||||
i18next.changeLanguage("en");
|
||||
game.settings.battleStyle = BattleStyle.SWITCH;
|
||||
await game.classicMode.runToSummon([Species.EEVEE, Species.FEEBAS]);
|
||||
|
||||
game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
|
||||
@ -48,7 +45,7 @@ describe("Ability Timing", () => {
|
||||
game.endPhase();
|
||||
}, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase));
|
||||
|
||||
await game.phaseInterceptor.to(MessagePhase);
|
||||
await game.phaseInterceptor.to("MessagePhase");
|
||||
const message = game.textInterceptor.getLatestMessage();
|
||||
expect(message).toContain("Attack fell");
|
||||
}, 5000);
|
||||
|
@ -1,11 +1,5 @@
|
||||
import { BattleStat } from "#app/data/battle-stat";
|
||||
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 { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
@ -33,13 +27,12 @@ describe("Abilities - Disguise", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override.battleType("single");
|
||||
|
||||
game.override.enemySpecies(Species.MIMIKYU);
|
||||
game.override.enemyMoveset(SPLASH_ONLY);
|
||||
|
||||
game.override.starterSpecies(Species.REGIELEKI);
|
||||
game.override.moveset([Moves.SHADOW_SNEAK, Moves.VACUUM_WAVE, Moves.TOXIC_THREAD, Moves.SPLASH]);
|
||||
game.override
|
||||
.battleType("single")
|
||||
.enemySpecies(Species.MIMIKYU)
|
||||
.enemyMoveset(SPLASH_ONLY)
|
||||
.starterSpecies(Species.REGIELEKI)
|
||||
.moveset([Moves.SHADOW_SNEAK, Moves.VACUUM_WAVE, Moves.TOXIC_THREAD, Moves.SPLASH]);
|
||||
}, TIMEOUT);
|
||||
|
||||
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);
|
||||
|
||||
await game.phaseInterceptor.to(MoveEndPhase);
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
|
||||
expect(mimikyu.hp).equals(maxHp - disguiseDamage);
|
||||
expect(mimikyu.formIndex).toBe(bustedForm);
|
||||
@ -68,7 +61,7 @@ describe("Abilities - Disguise", () => {
|
||||
|
||||
game.move.select(Moves.VACUUM_WAVE);
|
||||
|
||||
await game.phaseInterceptor.to(MoveEndPhase);
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
|
||||
expect(mimikyu.formIndex).toBe(disguisedForm);
|
||||
}, TIMEOUT);
|
||||
@ -87,12 +80,12 @@ describe("Abilities - Disguise", () => {
|
||||
game.move.select(Moves.SURGING_STRIKES);
|
||||
|
||||
// First hit
|
||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
expect(mimikyu.hp).equals(maxHp - disguiseDamage);
|
||||
expect(mimikyu.formIndex).toBe(disguisedForm);
|
||||
|
||||
// Second hit
|
||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
expect(mimikyu.hp).lessThan(maxHp - disguiseDamage);
|
||||
expect(mimikyu.formIndex).toBe(bustedForm);
|
||||
}, TIMEOUT);
|
||||
@ -105,7 +98,7 @@ describe("Abilities - Disguise", () => {
|
||||
|
||||
game.move.select(Moves.TOXIC_THREAD);
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(mimikyu.formIndex).toBe(disguisedForm);
|
||||
expect(mimikyu.status?.effect).toBe(StatusEffect.POISON);
|
||||
@ -125,7 +118,7 @@ describe("Abilities - Disguise", () => {
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(mimikyu.formIndex).toBe(bustedForm);
|
||||
expect(mimikyu.hp).equals(maxHp - disguiseDamage);
|
||||
@ -133,7 +126,7 @@ describe("Abilities - Disguise", () => {
|
||||
await game.toNextTurn();
|
||||
game.doSwitchPokemon(1);
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(mimikyu.formIndex).toBe(bustedForm);
|
||||
}, TIMEOUT);
|
||||
@ -194,15 +187,6 @@ describe("Abilities - Disguise", () => {
|
||||
await game.toNextTurn();
|
||||
game.move.select(Moves.SPLASH);
|
||||
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");
|
||||
|
||||
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 { Status, StatusEffect } from "#app/data/status-effect";
|
||||
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 { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
|
||||
import { SelectStarterPhase } from "#app/phases/select-starter-phase";
|
||||
import { TurnInitPhase } from "#app/phases/turn-init-phase";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
@ -33,36 +29,26 @@ describe("Abilities - Intimidate", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override.battleType("single");
|
||||
game.override.enemySpecies(Species.RATTATA);
|
||||
game.override.enemyAbility(Abilities.INTIMIDATE);
|
||||
game.override.enemyPassiveAbility(Abilities.HYDRATION);
|
||||
game.override.ability(Abilities.INTIMIDATE);
|
||||
game.override.startingWave(3);
|
||||
game.override.enemyMoveset(SPLASH_ONLY);
|
||||
game.override
|
||||
.battleType("single")
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyAbility(Abilities.INTIMIDATE)
|
||||
.ability(Abilities.INTIMIDATE)
|
||||
.moveset([Moves.SPLASH, Moves.AERIAL_ACE])
|
||||
.enemyMoveset(SPLASH_ONLY);
|
||||
});
|
||||
|
||||
it("single - wild with switch", async () => {
|
||||
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);
|
||||
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.MIGHTYENA);
|
||||
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||
|
||||
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||
|
||||
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
||||
|
||||
game.doSwitchPokemon(1);
|
||||
await game.phaseInterceptor.run(CommandPhase);
|
||||
await game.phaseInterceptor.to(CommandPhase);
|
||||
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.POOCHYENA);
|
||||
await game.phaseInterceptor.to("CommandPhase");
|
||||
|
||||
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
|
||||
@ -73,25 +59,16 @@ describe("Abilities - Intimidate", () => {
|
||||
|
||||
it("single - boss should only trigger once then switch", async () => {
|
||||
game.override.startingWave(10);
|
||||
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);
|
||||
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||
|
||||
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||
|
||||
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
||||
|
||||
game.doSwitchPokemon(1);
|
||||
await game.phaseInterceptor.run(CommandPhase);
|
||||
await game.phaseInterceptor.to(CommandPhase);
|
||||
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.POOCHYENA);
|
||||
await game.phaseInterceptor.to("CommandPhase");
|
||||
|
||||
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
|
||||
@ -102,25 +79,16 @@ describe("Abilities - Intimidate", () => {
|
||||
|
||||
it("single - trainer should only trigger once with switch", async () => {
|
||||
game.override.startingWave(5);
|
||||
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);
|
||||
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||
|
||||
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||
|
||||
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
||||
|
||||
game.doSwitchPokemon(1);
|
||||
await game.phaseInterceptor.run(CommandPhase);
|
||||
await game.phaseInterceptor.to(CommandPhase);
|
||||
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.POOCHYENA);
|
||||
await game.phaseInterceptor.to("CommandPhase");
|
||||
|
||||
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
|
||||
@ -130,21 +98,14 @@ describe("Abilities - Intimidate", () => {
|
||||
}, 200000);
|
||||
|
||||
it("double - trainer should only trigger once per pokemon", async () => {
|
||||
game.override.battleType("double");
|
||||
game.override.startingWave(5);
|
||||
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);
|
||||
game.override
|
||||
.battleType("double")
|
||||
.startingWave(5);
|
||||
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||
|
||||
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
|
||||
|
||||
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
|
||||
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-2);
|
||||
|
||||
@ -157,20 +118,11 @@ describe("Abilities - Intimidate", () => {
|
||||
|
||||
it("double - wild: should only trigger once per pokemon", async () => {
|
||||
game.override.battleType("double");
|
||||
game.override.startingWave(3);
|
||||
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);
|
||||
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||
|
||||
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
|
||||
|
||||
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
|
||||
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-2);
|
||||
|
||||
@ -182,21 +134,14 @@ describe("Abilities - Intimidate", () => {
|
||||
}, 20000);
|
||||
|
||||
it("double - boss: should only trigger once per pokemon", async () => {
|
||||
game.override.battleType("double");
|
||||
game.override.startingWave(10);
|
||||
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);
|
||||
game.override
|
||||
.battleType("double")
|
||||
.startingWave(10);
|
||||
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||
|
||||
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
|
||||
|
||||
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
|
||||
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-2);
|
||||
|
||||
@ -208,104 +153,101 @@ describe("Abilities - Intimidate", () => {
|
||||
}, 20000);
|
||||
|
||||
it("single - wild next wave opp triger once, us: none", async () => {
|
||||
game.override.startingWave(2);
|
||||
game.override.moveset([Moves.AERIAL_ACE]);
|
||||
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||
await game.startBattle([Species.MIGHTYENA]);
|
||||
|
||||
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||
|
||||
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
||||
|
||||
game.move.select(Moves.AERIAL_ACE);
|
||||
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(DamagePhase);
|
||||
await game.killPokemon(game.scene.currentBattle.enemyParty[0]);
|
||||
expect(game.scene.currentBattle.enemyParty[0].isFainted()).toBe(true);
|
||||
await game.phaseInterceptor.to("DamagePhase");
|
||||
await game.doKillOpponents();
|
||||
await game.toNextWave();
|
||||
|
||||
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
|
||||
|
||||
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
|
||||
}, 20000);
|
||||
|
||||
it("single - wild next turn - no retrigger on next turn", async () => {
|
||||
game.override.startingWave(2);
|
||||
game.override.moveset([Moves.SPLASH]);
|
||||
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||
await game.startBattle([Species.MIGHTYENA]);
|
||||
|
||||
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||
|
||||
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
||||
|
||||
game.move.select(Moves.AERIAL_ACE);
|
||||
console.log("===to new turn===");
|
||||
game.move.select(Moves.SPLASH);
|
||||
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);
|
||||
}, 20000);
|
||||
|
||||
it("single - trainer should only trigger once and each time he switch", async () => {
|
||||
game.override.moveset([Moves.SPLASH]);
|
||||
game.override.enemyMoveset([Moves.VOLT_SWITCH, Moves.VOLT_SWITCH, Moves.VOLT_SWITCH, Moves.VOLT_SWITCH]);
|
||||
game.override.startingWave(5);
|
||||
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||
game.override
|
||||
.enemyMoveset(Array(4).fill(Moves.VOLT_SWITCH))
|
||||
.startingWave(5);
|
||||
await game.startBattle([Species.MIGHTYENA]);
|
||||
|
||||
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||
|
||||
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
||||
|
||||
game.move.select(Moves.AERIAL_ACE);
|
||||
console.log("===to new turn===");
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
|
||||
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
|
||||
|
||||
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
|
||||
|
||||
game.move.select(Moves.AERIAL_ACE);
|
||||
console.log("===to new turn===");
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
|
||||
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-3);
|
||||
|
||||
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
|
||||
}, 200000);
|
||||
|
||||
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);
|
||||
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||
await game.startBattle([Species.MIGHTYENA]);
|
||||
|
||||
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||
const battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||
|
||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
|
||||
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
|
||||
|
||||
game.move.select(Moves.AERIAL_ACE);
|
||||
console.log("===to new turn===");
|
||||
game.move.select(Moves.SPLASH);
|
||||
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);
|
||||
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
|
||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||
}, 20000);
|
||||
|
||||
it("double - wild vs only 1 on player side", async () => {
|
||||
game.override.battleType("double");
|
||||
game.override.startingWave(3);
|
||||
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;
|
||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||
|
||||
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
|
||||
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-1);
|
||||
|
||||
@ -315,7 +257,6 @@ describe("Abilities - Intimidate", () => {
|
||||
|
||||
it("double - wild vs only 1 alive on player side", async () => {
|
||||
game.override.battleType("double");
|
||||
game.override.startingWave(3);
|
||||
await game.runToTitle();
|
||||
|
||||
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
|
||||
@ -330,9 +271,11 @@ describe("Abilities - Intimidate", () => {
|
||||
|
||||
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;
|
||||
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
|
||||
|
||||
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
|
||||
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 () => {
|
||||
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.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 () => {
|
||||
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.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 () => {
|
||||
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.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 () => {
|
||||
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.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 () => {
|
||||
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.getEnemyPokemon()!.species.speciesId).not.toBe(Species.ETERNATUS);
|
||||
|
@ -2,9 +2,10 @@ import { pokemonEvolutions, SpeciesFormEvolution, SpeciesWildEvolutionDelay } fr
|
||||
import { Abilities } from "#app/enums/abilities";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { Species } from "#app/enums/species";
|
||||
import * as Utils from "#app/utils";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
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";
|
||||
|
||||
describe("Evolution", () => {
|
||||
@ -148,4 +149,28 @@ describe("Evolution", () => {
|
||||
expect(cyndaquil.hp).toBeGreaterThan(hpBefore);
|
||||
expect(cyndaquil.hp).toBeLessThan(cyndaquil.getMaxHp());
|
||||
}, 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 { Moves } from "#app/enums/moves";
|
||||
import { Species } from "#app/enums/species";
|
||||
import { GameModes } from "#app/game-mode";
|
||||
import { TurnHeldItemTransferModifier } from "#app/modifier/modifier";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import GameManager from "./utils/gameManager";
|
||||
import { SPLASH_ONLY } from "./utils/testUtils";
|
||||
|
||||
const FinalWave = {
|
||||
Classic: 200,
|
||||
@ -20,7 +25,13 @@ describe("Final Boss", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
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(() => {
|
||||
@ -28,7 +39,7 @@ describe("Final Boss", () => {
|
||||
});
|
||||
|
||||
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.arena.biomeType).toBe(Biome.END);
|
||||
@ -37,7 +48,7 @@ describe("Final Boss", () => {
|
||||
|
||||
it("should NOT spawn Eternatus before wave 200 in END biome", async () => {
|
||||
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.arena.biomeType).toBe(Biome.END);
|
||||
@ -46,7 +57,7 @@ describe("Final Boss", () => {
|
||||
|
||||
it("should NOT spawn Eternatus outside of END biome", async () => {
|
||||
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.arena.biomeType).not.toBe(Biome.END);
|
||||
@ -54,12 +65,81 @@ describe("Final Boss", () => {
|
||||
});
|
||||
|
||||
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();
|
||||
expect(eternatus?.species.speciesId).toBe(Species.ETERNATUS);
|
||||
expect(eternatus?.hasPassive()).toBe(false);
|
||||
const eternatus = game.scene.getEnemyPokemon()!;
|
||||
expect(eternatus.species.speciesId).toBe(Species.ETERNATUS);
|
||||
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();
|
||||
}).then(data => {
|
||||
spy(); // Call the spy function
|
||||
expect(data).toEqual({"username":"greenlamp", "lastSessionSlot":0});
|
||||
expect(data).toEqual({ "username": "greenlamp", "lastSessionSlot": 0 });
|
||||
});
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
@ -43,7 +43,7 @@ describe("Test misc", () => {
|
||||
return response.json();
|
||||
}).then(data => {
|
||||
spy(); // Call the spy function
|
||||
expect(data).toEqual({"username":"greenlamp", "lastSessionSlot":0});
|
||||
expect(data).toEqual({ "username": "greenlamp", "lastSessionSlot": 0 });
|
||||
});
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
@ -54,7 +54,7 @@ describe("Test misc", () => {
|
||||
|
||||
expect(response.ok).toBe(true);
|
||||
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 () => {
|
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 { 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";
|
||||
import { SPLASH_ONLY } from "../utils/testUtils";
|
||||
|
||||
|
||||
describe("Moves - Spikes", () => {
|
||||
@ -23,93 +23,61 @@ describe("Moves - Spikes", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.scene.battleStyle = 1;
|
||||
game.override.battleType("single");
|
||||
game.override.enemySpecies(Species.RATTATA);
|
||||
game.override.enemyAbility(Abilities.HYDRATION);
|
||||
game.override.enemyPassiveAbility(Abilities.HYDRATION);
|
||||
game.override.ability(Abilities.HYDRATION);
|
||||
game.override.passiveAbility(Abilities.HYDRATION);
|
||||
game.override.startingWave(3);
|
||||
game.override.enemyMoveset([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]);
|
||||
game.override.moveset([Moves.SPIKES, Moves.SPLASH, Moves.ROAR]);
|
||||
game.override
|
||||
.battleType("single")
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(SPLASH_ONLY)
|
||||
.moveset([Moves.SPIKES, Moves.SPLASH, Moves.ROAR]);
|
||||
});
|
||||
|
||||
it("single - wild - stay on field - no damage", async () => {
|
||||
await game.classicMode.runToSummon([
|
||||
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);
|
||||
it("should not damage the team that set them", async () => {
|
||||
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||
|
||||
game.move.select(Moves.SPIKES);
|
||||
await game.toNextTurn();
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
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);
|
||||
await game.phaseInterceptor.run(CommandPhase);
|
||||
await game.phaseInterceptor.to(CommandPhase, false);
|
||||
await game.toNextTurn();
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
// player set spikes on the field and do splash for 3 turns
|
||||
// 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;
|
||||
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||
|
||||
game.move.select(Moves.SPIKES);
|
||||
await game.toNextTurn();
|
||||
|
||||
game.move.select(Moves.ROAR);
|
||||
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);
|
||||
|
||||
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.startingLevel(5000);
|
||||
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;
|
||||
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
|
||||
|
||||
game.move.select(Moves.SPIKES);
|
||||
await game.toNextTurn();
|
||||
|
||||
game.forceOpponentToSwitch();
|
||||
game.move.select(Moves.SPLASH);
|
||||
game.forceOpponentToSwitch();
|
||||
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);
|
||||
|
||||
});
|
||||
|
@ -6,6 +6,7 @@ import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
||||
import Trainer from "#app/field/trainer";
|
||||
import { GameModes, getGameMode } from "#app/game-mode";
|
||||
import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type";
|
||||
import overrides from "#app/overrides";
|
||||
import { CommandPhase } from "#app/phases/command-phase";
|
||||
import { EncounterPhase } from "#app/phases/encounter-phase";
|
||||
import { FaintPhase } from "#app/phases/faint-phase";
|
||||
@ -138,7 +139,7 @@ export default class GameManager {
|
||||
this.scene.hpBarSpeed = 3;
|
||||
this.scene.enableTutorials = false;
|
||||
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 mode
|
||||
*/
|
||||
async runToFinalBossEncounter(game: GameManager, species: Species[], mode: GameModes) {
|
||||
async runToFinalBossEncounter(species: Species[], mode: GameModes) {
|
||||
console.log("===to final boss encounter===");
|
||||
await game.runToTitle();
|
||||
await this.runToTitle();
|
||||
|
||||
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
|
||||
game.scene.gameMode = getGameMode(mode);
|
||||
const starters = generateStarter(game.scene, species);
|
||||
const selectStarterPhase = new SelectStarterPhase(game.scene);
|
||||
game.scene.pushPhase(new EncounterPhase(game.scene, false));
|
||||
this.onNextPrompt("TitlePhase", Mode.TITLE, () => {
|
||||
this.scene.gameMode = getGameMode(mode);
|
||||
const starters = generateStarter(this.scene, species);
|
||||
const selectStarterPhase = new SelectStarterPhase(this.scene);
|
||||
this.scene.pushPhase(new EncounterPhase(this.scene, false));
|
||||
selectStarterPhase.initBattle(starters);
|
||||
});
|
||||
|
||||
game.onNextPrompt("EncounterPhase", Mode.MESSAGE, async () => {
|
||||
// This will skip all entry dialogue (I can't figure out a way to sequentially handle the 8 chained messages via 1 prompt handler)
|
||||
game.setMode(Mode.MESSAGE);
|
||||
const encounterPhase = game.scene.getCurrentPhase() as EncounterPhase;
|
||||
// This will consider all battle entry dialog as seens and skip them
|
||||
vi.spyOn(this.scene.ui, "shouldSkipDialogue").mockReturnValue(true);
|
||||
|
||||
// No need to end phase, this will do it for you
|
||||
encounterPhase.doEncounterCommon(false);
|
||||
});
|
||||
if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0) {
|
||||
this.removeEnemyHeldItems();
|
||||
}
|
||||
|
||||
await game.phaseInterceptor.to(EncounterPhase, true);
|
||||
await this.phaseInterceptor.to(EncounterPhase);
|
||||
console.log("===finished run to final boss encounter===");
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,30 @@
|
||||
import { PlayerGender } from "#app/enums/player-gender";
|
||||
import { BattleStyle } from "#app/enums/battle-style";
|
||||
import { GameManagerHelper } from "./gameManagerHelper";
|
||||
|
||||
/**
|
||||
* Helper to handle settings for tests
|
||||
*/
|
||||
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
|
||||
* @param enable true to enabled, false to disabled
|
||||
*/
|
||||
typeHints(enable: boolean) {
|
||||
typeHints(enable: boolean): void {
|
||||
this.game.scene.typeHints = enable;
|
||||
this.log(`Type Hints ${enable? "enabled" : "disabled"}` );
|
||||
}
|
||||
|
@ -317,10 +317,11 @@ export default class UI extends Phaser.GameObjects.Container {
|
||||
if (i18next.exists(keyOrText) ) {
|
||||
const i18nKey = keyOrText;
|
||||
hasi18n = true;
|
||||
|
||||
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
|
||||
if (battleScene.skipSeenDialogues && battleScene.gameData.getSeenDialogues()[i18nKey] === true) {
|
||||
if (this.shouldSkipDialogue(i18nKey)) {
|
||||
console.log(`Dialogue ${i18nKey} skipped`);
|
||||
callback();
|
||||
return;
|
||||
|
Loading…
Reference in New Issue
Block a user