mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2024-11-25 08:16:04 +00:00
Merge branch 'beta' into local_development_and_offline
This commit is contained in:
commit
10f87973af
@ -3614,22 +3614,19 @@ export class MoodyAbAttr extends PostTurnAbAttr {
|
||||
}
|
||||
}
|
||||
|
||||
export class PostTurnStatStageChangeAbAttr extends PostTurnAbAttr {
|
||||
private stats: BattleStat[];
|
||||
private stages: number;
|
||||
export class SpeedBoostAbAttr extends PostTurnAbAttr {
|
||||
|
||||
constructor(stats: BattleStat[], stages: number) {
|
||||
constructor() {
|
||||
super(true);
|
||||
|
||||
this.stats = Array.isArray(stats)
|
||||
? stats
|
||||
: [ stats ];
|
||||
this.stages = stages;
|
||||
}
|
||||
|
||||
applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
||||
if (!simulated) {
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.stages));
|
||||
if (!pokemon.turnData.switchedInThisTurn && !pokemon.turnData.failedRunAway) {
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.SPD ], 1));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -4703,6 +4700,84 @@ export class PreventBypassSpeedChanceAbAttr extends AbAttr {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This applies a terrain-based type change to the Pokemon.
|
||||
* Used by Mimicry.
|
||||
*/
|
||||
export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr {
|
||||
constructor() {
|
||||
super(true);
|
||||
}
|
||||
|
||||
override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, _args: any[]): boolean {
|
||||
if (pokemon.isTerastallized()) {
|
||||
return false;
|
||||
}
|
||||
const currentTerrain = pokemon.scene.arena.getTerrainType();
|
||||
const typeChange: Type[] = this.determineTypeChange(pokemon, currentTerrain);
|
||||
if (typeChange.length !== 0) {
|
||||
if (pokemon.summonData.addedType && typeChange.includes(pokemon.summonData.addedType)) {
|
||||
pokemon.summonData.addedType = null;
|
||||
}
|
||||
pokemon.summonData.types = typeChange;
|
||||
pokemon.updateInfo();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the type(s) the Pokemon should change to in response to a terrain
|
||||
* @param pokemon
|
||||
* @param currentTerrain {@linkcode TerrainType}
|
||||
* @returns a list of type(s)
|
||||
*/
|
||||
private determineTypeChange(pokemon: Pokemon, currentTerrain: TerrainType): Type[] {
|
||||
const typeChange: Type[] = [];
|
||||
switch (currentTerrain) {
|
||||
case TerrainType.ELECTRIC:
|
||||
typeChange.push(Type.ELECTRIC);
|
||||
break;
|
||||
case TerrainType.MISTY:
|
||||
typeChange.push(Type.FAIRY);
|
||||
break;
|
||||
case TerrainType.GRASSY:
|
||||
typeChange.push(Type.GRASS);
|
||||
break;
|
||||
case TerrainType.PSYCHIC:
|
||||
typeChange.push(Type.PSYCHIC);
|
||||
break;
|
||||
default:
|
||||
pokemon.getTypes(false, false, true).forEach(t => {
|
||||
typeChange.push(t);
|
||||
});
|
||||
break;
|
||||
}
|
||||
return typeChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the Pokemon should change types if summoned into an active terrain
|
||||
* @returns `true` if there is an active terrain requiring a type change | `false` if not
|
||||
*/
|
||||
override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
||||
if (pokemon.scene.arena.getTerrainType() !== TerrainType.NONE) {
|
||||
return this.apply(pokemon, passive, simulated, new Utils.BooleanHolder(false), []);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
override getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]) {
|
||||
const currentTerrain = pokemon.scene.arena.getTerrainType();
|
||||
const pokemonNameWithAffix = getPokemonNameWithAffix(pokemon);
|
||||
if (currentTerrain === TerrainType.NONE) {
|
||||
return i18next.t("abilityTriggers:pokemonTypeChangeRevert", { pokemonNameWithAffix });
|
||||
} else {
|
||||
const moveType = i18next.t(`pokemonInfo:Type.${Type[this.determineTypeChange(pokemon, currentTerrain)[0]]}`);
|
||||
return i18next.t("abilityTriggers:pokemonTypeChange", { pokemonNameWithAffix, moveType });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function applyAbAttrsInternal<TAttr extends AbAttr>(
|
||||
attrType: Constructor<TAttr>,
|
||||
pokemon: Pokemon | null,
|
||||
@ -4933,7 +5008,7 @@ export function initAbilities() {
|
||||
.attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN)
|
||||
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.RAIN),
|
||||
new Ability(Abilities.SPEED_BOOST, 3)
|
||||
.attr(PostTurnStatStageChangeAbAttr, [ Stat.SPD ], 1),
|
||||
.attr(SpeedBoostAbAttr),
|
||||
new Ability(Abilities.BATTLE_ARMOR, 3)
|
||||
.attr(BlockCritAbAttr)
|
||||
.ignorable(),
|
||||
@ -5767,7 +5842,7 @@ export function initAbilities() {
|
||||
new Ability(Abilities.POWER_SPOT, 8)
|
||||
.attr(AllyMoveCategoryPowerBoostAbAttr, [ MoveCategory.SPECIAL, MoveCategory.PHYSICAL ], 1.3),
|
||||
new Ability(Abilities.MIMICRY, 8)
|
||||
.unimplemented(),
|
||||
.attr(TerrainEventTypeChangeAbAttr),
|
||||
new Ability(Abilities.SCREEN_CLEANER, 8)
|
||||
.attr(PostSummonRemoveArenaTagAbAttr, [ ArenaTagType.AURORA_VEIL, ArenaTagType.LIGHT_SCREEN, ArenaTagType.REFLECT ]),
|
||||
new Ability(Abilities.STEELY_SPIRIT, 8)
|
||||
@ -5924,7 +5999,7 @@ export function initAbilities() {
|
||||
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.SLICING_MOVE), 1.5),
|
||||
new Ability(Abilities.SUPREME_OVERLORD, 9)
|
||||
.attr(VariableMovePowerBoostAbAttr, (user, target, move) => 1 + 0.1 * Math.min(user.isPlayer() ? user.scene.currentBattle.playerFaints : user.scene.currentBattle.enemyFaints, 5))
|
||||
.partial(), // Counter resets every wave
|
||||
.partial(), // Counter resets every wave instead of on arena reset
|
||||
new Ability(Abilities.COSTAR, 9)
|
||||
.attr(PostSummonCopyAllyStatsAbAttr),
|
||||
new Ability(Abilities.TOXIC_DEBRIS, 9)
|
||||
|
@ -1443,7 +1443,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
|
||||
],
|
||||
[Species.ROCKRUFF]: [
|
||||
new SpeciesFormEvolution(Species.LYCANROC, "", "midday", 25, null, new SpeciesEvolutionCondition(p => (p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY) && (p.formIndex === 0))),
|
||||
new SpeciesFormEvolution(Species.LYCANROC, "", "dusk", 25, null, new SpeciesEvolutionCondition(p => p.formIndex === 1)),
|
||||
new SpeciesFormEvolution(Species.LYCANROC, "own-tempo", "dusk", 25, null, new SpeciesEvolutionCondition(p => p.formIndex === 1)),
|
||||
new SpeciesFormEvolution(Species.LYCANROC, "", "midnight", 25, null, new SpeciesEvolutionCondition(p => (p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT) && (p.formIndex === 0)))
|
||||
],
|
||||
[Species.STEENEE]: [
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1420,6 +1420,11 @@ export class RecoilAttr extends MoveEffectAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Chloroblast and Struggle should not deal recoil damage if the move was not successful
|
||||
if (this.useHp && [ MoveResult.FAIL, MoveResult.MISS ].includes(user.getLastXMoves(1)[0]?.result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const damageValue = (!this.useHp ? user.turnData.damageDealt : user.getMaxHp()) * this.damageRatio;
|
||||
const minValue = user.turnData.damageDealt ? 1 : 0;
|
||||
const recoilDamage = Utils.toDmgValue(damageValue, minValue);
|
||||
@ -2177,7 +2182,10 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, false);
|
||||
return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(this.effect, true, false, user) ? Math.floor(moveChance * -0.1) : 0;
|
||||
const score = (moveChance < 0) ? -10 : Math.floor(moveChance * -0.1);
|
||||
const pokemon = this.selfTarget ? user : target;
|
||||
|
||||
return !pokemon.status && pokemon.canSetStatus(this.effect, true, false, user) ? score : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2197,7 +2205,10 @@ export class MultiStatusEffectAttr extends StatusEffectAttr {
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, false);
|
||||
return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(this.effect, true, false, user) ? Math.floor(moveChance * -0.1) : 0;
|
||||
const score = (moveChance < 0) ? -10 : Math.floor(moveChance * -0.1);
|
||||
const pokemon = this.selfTarget ? user : target;
|
||||
|
||||
return !pokemon.status && pokemon.canSetStatus(this.effect, true, false, user) ? score : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2228,7 +2239,7 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(user.status?.effect, true, false, user) ? Math.floor(move.chance * -0.1) : 0;
|
||||
return !target.status && target.canSetStatus(user.status?.effect, true, false, user) ? -10 : 0;
|
||||
}
|
||||
}
|
||||
/**
|
||||
@ -5858,6 +5869,9 @@ export class RemoveTypeAttr extends MoveEffectAttr {
|
||||
|
||||
const userTypes = user.getTypes(true);
|
||||
const modifiedTypes = userTypes.filter(type => type !== this.removedType);
|
||||
if (modifiedTypes.length === 0) {
|
||||
modifiedTypes.push(Type.UNKNOWN);
|
||||
}
|
||||
user.summonData.types = modifiedTypes;
|
||||
user.updateInfo();
|
||||
|
||||
@ -5880,7 +5894,11 @@ export class CopyTypeAttr extends MoveEffectAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
user.summonData.types = target.getTypes(true);
|
||||
const targetTypes = target.getTypes(true);
|
||||
if (targetTypes.includes(Type.UNKNOWN) && targetTypes.indexOf(Type.UNKNOWN) > -1) {
|
||||
targetTypes[targetTypes.indexOf(Type.UNKNOWN)] = Type.NORMAL;
|
||||
}
|
||||
user.summonData.types = targetTypes;
|
||||
user.updateInfo();
|
||||
|
||||
user.scene.queueMessage(i18next.t("moveTriggers:copyType", { pokemonName: getPokemonNameWithAffix(user), targetPokemonName: getPokemonNameWithAffix(target) }));
|
||||
@ -5889,7 +5907,7 @@ export class CopyTypeAttr extends MoveEffectAttr {
|
||||
}
|
||||
|
||||
getCondition(): MoveConditionFunc {
|
||||
return (user, target, move) => target.getTypes()[0] !== Type.UNKNOWN;
|
||||
return (user, target, move) => target.getTypes()[0] !== Type.UNKNOWN || target.summonData.addedType !== null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5947,11 +5965,7 @@ export class AddTypeAttr extends MoveEffectAttr {
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const types = target.getTypes().slice(0, 2).filter(t => t !== Type.UNKNOWN); // TODO: Figure out some way to actually check if another version of this effect is already applied
|
||||
if (this.type !== Type.UNKNOWN) {
|
||||
types.push(this.type);
|
||||
}
|
||||
target.summonData.types = types;
|
||||
target.summonData.addedType = this.type;
|
||||
target.updateInfo();
|
||||
|
||||
user.scene.queueMessage(i18next.t("moveTriggers:addType", { typeName: i18next.t(`pokemonInfo:Type.${Type[this.type]}`), pokemonName: getPokemonNameWithAffix(target) }));
|
||||
@ -8983,8 +8997,7 @@ export function initMoves() {
|
||||
.ignoresProtect()
|
||||
.ignoresVirtual(),
|
||||
new StatusMove(Moves.TRICK_OR_TREAT, Type.GHOST, 100, 20, -1, 0, 6)
|
||||
.attr(AddTypeAttr, Type.GHOST)
|
||||
.edgeCase(), // Weird interaction with Forest's Curse, reflect type, burn up
|
||||
.attr(AddTypeAttr, Type.GHOST),
|
||||
new StatusMove(Moves.NOBLE_ROAR, Type.NORMAL, 100, 30, -1, 0, 6)
|
||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1)
|
||||
.soundBased(),
|
||||
@ -8996,8 +9009,7 @@ export function initMoves() {
|
||||
.target(MoveTarget.ALL_NEAR_OTHERS)
|
||||
.triageMove(),
|
||||
new StatusMove(Moves.FORESTS_CURSE, Type.GRASS, 100, 20, -1, 0, 6)
|
||||
.attr(AddTypeAttr, Type.GRASS)
|
||||
.edgeCase(), // Weird interaction with Trick or Treat, reflect type, burn up
|
||||
.attr(AddTypeAttr, Type.GRASS),
|
||||
new AttackMove(Moves.PETAL_BLIZZARD, Type.GRASS, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 6)
|
||||
.windMove()
|
||||
.makesContact(false)
|
||||
@ -9491,7 +9503,7 @@ export function initMoves() {
|
||||
.edgeCase() // I assume it needs clanging scales and Kommo-O
|
||||
.ignoresVirtual(),
|
||||
/* End Unused */
|
||||
new AttackMove(Moves.ZIPPY_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 50, 100, 15, -1, 2, 7) //LGPE Implementation
|
||||
new AttackMove(Moves.ZIPPY_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 50, 100, 15, -1, 2, 7) // LGPE Implementation
|
||||
.attr(CritOnlyAttr),
|
||||
new AttackMove(Moves.SPLISHY_SPLASH, Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, 30, 0, 7)
|
||||
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
||||
@ -9501,7 +9513,7 @@ export function initMoves() {
|
||||
new AttackMove(Moves.PIKA_PAPOW, Type.ELECTRIC, MoveCategory.SPECIAL, -1, -1, 20, -1, 0, 7)
|
||||
.attr(FriendshipPowerAttr),
|
||||
new AttackMove(Moves.BOUNCY_BUBBLE, Type.WATER, MoveCategory.SPECIAL, 60, 100, 20, -1, 0, 7)
|
||||
.attr(HitHealAttr, 1.0)
|
||||
.attr(HitHealAttr) // Custom
|
||||
.triageMove()
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
new AttackMove(Moves.BUZZY_BUZZ, Type.ELECTRIC, MoveCategory.SPECIAL, 60, 100, 20, 100, 0, 7)
|
||||
@ -10005,6 +10017,7 @@ export function initMoves() {
|
||||
.attr(ConfuseAttr)
|
||||
.recklessMove(),
|
||||
new AttackMove(Moves.LAST_RESPECTS, Type.GHOST, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 9)
|
||||
.partial() // Counter resets every wave instead of on arena reset
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => 1 + Math.min(user.isPlayer() ? user.scene.currentBattle.playerFaints : user.scene.currentBattle.enemyFaints, 100))
|
||||
.makesContact(false),
|
||||
new AttackMove(Moves.LUMINA_CRASH, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9)
|
||||
|
@ -3,6 +3,8 @@
|
||||
* or {@linkcode SwitchSummonPhase} will carry out.
|
||||
*/
|
||||
export enum SwitchType {
|
||||
/** Switchout specifically for when combat starts and the player is prompted if they will switch Pokemon */
|
||||
INITIAL_SWITCH,
|
||||
/** Basic switchout where the Pokemon to switch in is selected */
|
||||
SWITCH,
|
||||
/** Transfers stat stages and other effects from the returning Pokemon to the switched in Pokemon */
|
||||
|
@ -10,7 +10,14 @@ import Move from "#app/data/move";
|
||||
import { ArenaTag, ArenaTagSide, ArenaTrapTag, getArenaTag } from "#app/data/arena-tag";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { Terrain, TerrainType } from "#app/data/terrain";
|
||||
import { applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs, PostTerrainChangeAbAttr, PostWeatherChangeAbAttr } from "#app/data/ability";
|
||||
import {
|
||||
applyAbAttrs,
|
||||
applyPostTerrainChangeAbAttrs,
|
||||
applyPostWeatherChangeAbAttrs,
|
||||
PostTerrainChangeAbAttr,
|
||||
PostWeatherChangeAbAttr,
|
||||
TerrainEventTypeChangeAbAttr
|
||||
} from "#app/data/ability";
|
||||
import Pokemon from "#app/field/pokemon";
|
||||
import Overrides from "#app/overrides";
|
||||
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
|
||||
@ -387,6 +394,7 @@ export class Arena {
|
||||
this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => {
|
||||
pokemon.findAndRemoveTags(t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain));
|
||||
applyPostTerrainChangeAbAttrs(PostTerrainChangeAbAttr, pokemon, terrain);
|
||||
applyAbAttrs(TerrainEventTypeChangeAbAttr, pokemon, null, false);
|
||||
});
|
||||
|
||||
return true;
|
||||
|
@ -1258,6 +1258,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
}
|
||||
|
||||
// the type added to Pokemon from moves like Forest's Curse or Trick Or Treat
|
||||
if (!ignoreOverride && this.summonData && this.summonData.addedType && !types.includes(this.summonData.addedType)) {
|
||||
types.push(this.summonData.addedType);
|
||||
}
|
||||
|
||||
// If both types are the same (can happen in weird custom typing scenarios), reduce to single type
|
||||
if (types.length > 1 && types[0] === types[1]) {
|
||||
types.splice(0, 1);
|
||||
@ -5100,6 +5105,7 @@ export class PokemonSummonData {
|
||||
public moveset: (PokemonMove | null)[];
|
||||
// If not initialized this value will not be populated from save data.
|
||||
public types: Type[] = [];
|
||||
public addedType: Type | null = null;
|
||||
}
|
||||
|
||||
export class PokemonBattleData {
|
||||
@ -5137,6 +5143,8 @@ export class PokemonTurnData {
|
||||
public statStagesDecreased: boolean = false;
|
||||
public moveEffectiveness: TypeDamageMultiplier | null = null;
|
||||
public combiningPledge?: Moves;
|
||||
public switchedInThisTurn: boolean = false;
|
||||
public failedRunAway: boolean = false;
|
||||
}
|
||||
|
||||
export enum AiType {
|
||||
|
@ -10,6 +10,10 @@ import { NewBattlePhase } from "./new-battle-phase";
|
||||
import { PokemonPhase } from "./pokemon-phase";
|
||||
|
||||
export class AttemptRunPhase extends PokemonPhase {
|
||||
|
||||
/** For testing purposes: this is to force the pokemon to fail and escape */
|
||||
public forceFailEscape = false;
|
||||
|
||||
constructor(scene: BattleScene, fieldIndex: number) {
|
||||
super(scene, fieldIndex);
|
||||
}
|
||||
@ -28,7 +32,7 @@ export class AttemptRunPhase extends PokemonPhase {
|
||||
|
||||
applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, false, escapeChance);
|
||||
|
||||
if (playerPokemon.randSeedInt(100) < escapeChance.value) {
|
||||
if (playerPokemon.randSeedInt(100) < escapeChance.value && !this.forceFailEscape) {
|
||||
this.scene.playSound("se/flee");
|
||||
this.scene.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
|
||||
|
||||
@ -51,6 +55,7 @@ export class AttemptRunPhase extends PokemonPhase {
|
||||
this.scene.pushPhase(new BattleEndPhase(this.scene));
|
||||
this.scene.pushPhase(new NewBattlePhase(this.scene));
|
||||
} else {
|
||||
playerPokemon.turnData.failedRunAway = true;
|
||||
this.scene.queueMessage(i18next.t("battle:runAwayCannotEscape"), null, true, 500);
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ export class CheckSwitchPhase extends BattlePhase {
|
||||
this.scene.ui.setMode(Mode.CONFIRM, () => {
|
||||
this.scene.ui.setMode(Mode.MESSAGE);
|
||||
this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex);
|
||||
this.scene.unshiftPhase(new SwitchPhase(this.scene, SwitchType.SWITCH, this.fieldIndex, false, true));
|
||||
this.scene.unshiftPhase(new SwitchPhase(this.scene, SwitchType.INITIAL_SWITCH, this.fieldIndex, false, true));
|
||||
this.end();
|
||||
}, () => {
|
||||
this.scene.ui.setMode(Mode.MESSAGE);
|
||||
|
@ -65,6 +65,15 @@ export class FaintPhase extends PokemonPhase {
|
||||
}
|
||||
}
|
||||
|
||||
/** In case the current pokemon was just switched in, make sure it is counted as participating in the combat */
|
||||
this.scene.getPlayerField().forEach((pokemon, i) => {
|
||||
if (pokemon?.isActive(true)) {
|
||||
if (pokemon.isPlayer()) {
|
||||
this.scene.currentBattle.addParticipant(pokemon as PlayerPokemon);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!this.tryOverrideForBattleSpec()) {
|
||||
this.doFaint();
|
||||
}
|
||||
|
@ -64,10 +64,8 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||
}
|
||||
|
||||
const pokemon = this.getPokemon();
|
||||
|
||||
(this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id));
|
||||
|
||||
if (this.switchType === SwitchType.SWITCH) {
|
||||
if (this.switchType === SwitchType.SWITCH || this.switchType === SwitchType.INITIAL_SWITCH) {
|
||||
const substitute = pokemon.getTag(SubstituteTag);
|
||||
if (substitute) {
|
||||
this.scene.tweens.add({
|
||||
@ -186,6 +184,11 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.switchType !== SwitchType.INITIAL_SWITCH) {
|
||||
pokemon.resetTurnData();
|
||||
pokemon.turnData.switchedInThisTurn = true;
|
||||
}
|
||||
|
||||
this.lastPokemon?.resetSummonData();
|
||||
|
||||
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);
|
||||
|
91
src/test/abilities/mimicry.test.ts
Normal file
91
src/test/abilities/mimicry.test.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { Type } from "#app/data/type";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
describe("Abilities - Mimicry", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([ Moves.SPLASH ])
|
||||
.ability(Abilities.MIMICRY)
|
||||
.battleType("single")
|
||||
.disableCrits()
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
});
|
||||
|
||||
it("Mimicry activates after the Pokémon with Mimicry is switched in while terrain is present, or whenever there is a change in terrain", async () => {
|
||||
game.override.enemyAbility(Abilities.MISTY_SURGE);
|
||||
await game.classicMode.startBattle([ Species.FEEBAS, Species.ABRA ]);
|
||||
|
||||
const [ playerPokemon1, playerPokemon2 ] = game.scene.getParty();
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
expect(playerPokemon1.getTypes().includes(Type.FAIRY)).toBe(true);
|
||||
|
||||
game.doSwitchPokemon(1);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(playerPokemon2.getTypes().includes(Type.FAIRY)).toBe(true);
|
||||
});
|
||||
|
||||
it("Pokemon should revert back to its original, root type once terrain ends", async () => {
|
||||
game.override
|
||||
.moveset([ Moves.SPLASH, Moves.TRANSFORM ])
|
||||
.enemyAbility(Abilities.MIMICRY)
|
||||
.enemyMoveset([ Moves.SPLASH, Moves.PSYCHIC_TERRAIN ]);
|
||||
await game.classicMode.startBattle([ Species.REGIELEKI ]);
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon();
|
||||
game.move.select(Moves.TRANSFORM);
|
||||
await game.forceEnemyMove(Moves.PSYCHIC_TERRAIN);
|
||||
await game.toNextTurn();
|
||||
expect(playerPokemon?.getTypes().includes(Type.PSYCHIC)).toBe(true);
|
||||
|
||||
if (game.scene.arena.terrain) {
|
||||
game.scene.arena.terrain.turnsLeft = 1;
|
||||
}
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.forceEnemyMove(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
expect(playerPokemon?.getTypes().includes(Type.ELECTRIC)).toBe(true);
|
||||
});
|
||||
|
||||
it("If the Pokemon is under the effect of a type-adding move and an equivalent terrain activates, the move's effect disappears", async () => {
|
||||
game.override
|
||||
.enemyMoveset([ Moves.FORESTS_CURSE, Moves.GRASSY_TERRAIN ]);
|
||||
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon();
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.forceEnemyMove(Moves.FORESTS_CURSE);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(playerPokemon?.summonData.addedType).toBe(Type.GRASS);
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.forceEnemyMove(Moves.GRASSY_TERRAIN);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(playerPokemon?.summonData.addedType).toBeNull();
|
||||
expect(playerPokemon?.getTypes().includes(Type.GRASS)).toBe(true);
|
||||
});
|
||||
});
|
125
src/test/abilities/speed_boost.test.ts
Normal file
125
src/test/abilities/speed_boost.test.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import { Stat } from "#enums/stat";
|
||||
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 { CommandPhase } from "#app/phases/command-phase";
|
||||
import { Command } from "#app/ui/command-ui-handler";
|
||||
import { AttemptRunPhase } from "#app/phases/attempt-run-phase";
|
||||
|
||||
describe("Abilities - Speed Boost", () => {
|
||||
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")
|
||||
.enemySpecies(Species.DRAGAPULT)
|
||||
.ability(Abilities.SPEED_BOOST)
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
.moveset([ Moves.SPLASH, Moves.U_TURN ]);
|
||||
});
|
||||
|
||||
it("should increase speed by 1 stage at end of turn",
|
||||
async () => {
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1);
|
||||
});
|
||||
|
||||
it("should not trigger this turn if pokemon was switched into combat via attack, but the turn after",
|
||||
async () => {
|
||||
await game.classicMode.startBattle([
|
||||
Species.SHUCKLE,
|
||||
Species.NINJASK
|
||||
]);
|
||||
|
||||
game.move.select(Moves.U_TURN);
|
||||
game.doSelectPartyPokemon(1);
|
||||
await game.toNextTurn();
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1);
|
||||
});
|
||||
|
||||
it("checking back to back swtiches",
|
||||
async () => {
|
||||
await game.classicMode.startBattle([
|
||||
Species.SHUCKLE,
|
||||
Species.NINJASK
|
||||
]);
|
||||
|
||||
game.move.select(Moves.U_TURN);
|
||||
game.doSelectPartyPokemon(1);
|
||||
await game.toNextTurn();
|
||||
let playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
|
||||
|
||||
game.move.select(Moves.U_TURN);
|
||||
game.doSelectPartyPokemon(1);
|
||||
await game.toNextTurn();
|
||||
playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1);
|
||||
});
|
||||
|
||||
it("should not trigger this turn if pokemon was switched into combat via normal switch, but the turn after",
|
||||
async () => {
|
||||
await game.classicMode.startBattle([
|
||||
Species.SHUCKLE,
|
||||
Species.NINJASK
|
||||
]);
|
||||
|
||||
game.doSwitchPokemon(1);
|
||||
await game.toNextTurn();
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1);
|
||||
});
|
||||
|
||||
it("should not trigger if pokemon fails to escape",
|
||||
async () => {
|
||||
await game.classicMode.startBattle([ Species.SHUCKLE ]);
|
||||
|
||||
const commandPhase = game.scene.getCurrentPhase() as CommandPhase;
|
||||
commandPhase.handleCommand(Command.RUN, 0);
|
||||
const runPhase = game.scene.getCurrentPhase() as AttemptRunPhase;
|
||||
runPhase.forceFailEscape = true;
|
||||
await game.phaseInterceptor.to(AttemptRunPhase);
|
||||
await game.toNextTurn();
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1);
|
||||
});
|
||||
});
|
42
src/test/moves/chloroblast.test.ts
Normal file
42
src/test/moves/chloroblast.test.ts
Normal file
@ -0,0 +1,42 @@
|
||||
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";
|
||||
|
||||
describe("Moves - Chloroblast", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([ Moves.CHLOROBLAST ])
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.battleType("single")
|
||||
.disableCrits()
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.PROTECT);
|
||||
});
|
||||
|
||||
it("should not deal recoil damage if the opponent uses protect", async () => {
|
||||
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||
|
||||
game.move.select(Moves.CHLOROBLAST);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(game.scene.getPlayerPokemon()!.isFullHp()).toBe(true);
|
||||
});
|
||||
});
|
47
src/test/moves/forests_curse.test.ts
Normal file
47
src/test/moves/forests_curse.test.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { Type } from "#app/data/type";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
describe("Moves - Forest's Curse", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([ Moves.FORESTS_CURSE, Moves.TRICK_OR_TREAT ])
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.battleType("single")
|
||||
.disableCrits()
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
});
|
||||
|
||||
it("will replace the added type from Trick Or Treat", async () => {
|
||||
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||
game.move.select(Moves.TRICK_OR_TREAT);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(enemyPokemon!.summonData.addedType).toBe(Type.GHOST);
|
||||
|
||||
game.move.select(Moves.FORESTS_CURSE);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(enemyPokemon?.summonData.addedType).toBe(Type.GRASS);
|
||||
});
|
||||
});
|
59
src/test/moves/reflect_type.test.ts
Normal file
59
src/test/moves/reflect_type.test.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { Type } from "#app/data/type";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
describe("Moves - Reflect Type", () => {
|
||||
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
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.battleType("single")
|
||||
.disableCrits()
|
||||
.enemyAbility(Abilities.BALL_FETCH);
|
||||
});
|
||||
|
||||
it("will make the user Normal/Grass if targetting a typeless Pokemon affected by Forest's Curse", async () => {
|
||||
game.override
|
||||
.moveset([ Moves.FORESTS_CURSE, Moves.REFLECT_TYPE ])
|
||||
.startingLevel(60)
|
||||
.enemySpecies(Species.CHARMANDER)
|
||||
.enemyMoveset([ Moves.BURN_UP, Moves.SPLASH ]);
|
||||
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon();
|
||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.forceEnemyMove(Moves.BURN_UP);
|
||||
await game.toNextTurn();
|
||||
|
||||
game.move.select(Moves.FORESTS_CURSE);
|
||||
await game.forceEnemyMove(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
expect(enemyPokemon?.getTypes().includes(Type.UNKNOWN)).toBe(true);
|
||||
expect(enemyPokemon?.getTypes().includes(Type.GRASS)).toBe(true);
|
||||
|
||||
game.move.select(Moves.REFLECT_TYPE);
|
||||
await game.forceEnemyMove(Moves.SPLASH);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(playerPokemon?.getTypes()[0]).toBe(Type.NORMAL);
|
||||
expect(playerPokemon?.getTypes().includes(Type.GRASS)).toBe(true);
|
||||
});
|
||||
});
|
47
src/test/moves/trick_or_treat.test.ts
Normal file
47
src/test/moves/trick_or_treat.test.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { Type } from "#app/data/type";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
describe("Moves - Trick Or Treat", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([ Moves.FORESTS_CURSE, Moves.TRICK_OR_TREAT ])
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.battleType("single")
|
||||
.disableCrits()
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
});
|
||||
|
||||
it("will replace added type from Forest's Curse", async () => {
|
||||
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||
game.move.select(Moves.FORESTS_CURSE);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(enemyPokemon!.summonData.addedType).toBe(Type.GRASS);
|
||||
|
||||
game.move.select(Moves.TRICK_OR_TREAT);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(enemyPokemon?.summonData.addedType).toBe(Type.GHOST);
|
||||
});
|
||||
});
|
@ -32,7 +32,7 @@ let wikiUrl = "https://wiki.pokerogue.net/start";
|
||||
const discordUrl = "https://discord.gg/uWpTfdKG49";
|
||||
const githubUrl = "https://github.com/pagefaultgames/pokerogue";
|
||||
const redditUrl = "https://www.reddit.com/r/pokerogue";
|
||||
const donateUrl = "https://github.com/sponsors/patapancakes";
|
||||
const donateUrl = "https://github.com/sponsors/pagefaultgames";
|
||||
|
||||
export default class MenuUiHandler extends MessageUiHandler {
|
||||
private readonly textPadding = 8;
|
||||
|
@ -237,14 +237,20 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
|
||||
|
||||
const formKey = (pokemon.species?.forms?.[pokemon.formIndex!]?.formKey);
|
||||
const formText = Utils.capitalizeString(formKey, "-", false, false) || "";
|
||||
const speciesName = Utils.capitalizeString(Species[pokemon.species.getRootSpeciesId()], "_", true, false);
|
||||
const speciesName = Utils.capitalizeString(Species[pokemon.species.speciesId], "_", true, false);
|
||||
|
||||
let formName = "";
|
||||
if (pokemon.species.speciesId === Species.ARCEUS) {
|
||||
formName = i18next.t(`pokemonInfo:Type.${formText?.toUpperCase()}`);
|
||||
} else {
|
||||
const i18key = `pokemonForm:${speciesName}${formText}`;
|
||||
formName = i18next.exists(i18key) ? i18next.t(i18key) : formText;
|
||||
if (i18next.exists(i18key)) {
|
||||
formName = i18next.t(i18key);
|
||||
} else {
|
||||
const rootSpeciesName = Utils.capitalizeString(Species[pokemon.species.getRootSpeciesId()], "_", true, false);
|
||||
const i18RootKey = `pokemonForm:${rootSpeciesName}${formText}`;
|
||||
formName = i18next.exists(i18RootKey) ? i18next.t(i18RootKey) : formText;
|
||||
}
|
||||
}
|
||||
|
||||
if (formName) {
|
||||
|
Loading…
Reference in New Issue
Block a user