Merge branch 'beta' into pr-illusion

This commit is contained in:
Lylian BALL 2024-11-06 18:53:46 +01:00 committed by GitHub
commit a89591a42e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 152 additions and 31 deletions

View File

@ -38,7 +38,7 @@ import PokemonData from "#app/system/pokemon-data";
import { Nature } from "#app/data/nature";
import { FormChangeItem, pokemonFormChanges, SpeciesFormChange, SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger, SpeciesFormChangeTrigger } from "#app/data/pokemon-forms";
import { FormChangePhase } from "#app/phases/form-change-phase";
import { getTypeRgb } from "#app/data/type";
import { getTypeRgb, Type } from "#app/data/type";
import PokemonSpriteSparkleHandler from "#app/field/pokemon-sprite-sparkle-handler";
import CharSprite from "#app/ui/char-sprite";
import DamageNumberHandler from "#app/field/damage-number-handler";
@ -98,6 +98,7 @@ import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { ExpGainsSpeed } from "#enums/exp-gains-speed";
import { BattlerTagType } from "#enums/battler-tag-type";
import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters";
import { StatusEffect } from "#enums/status-effect";
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
@ -2982,12 +2983,21 @@ export default class BattleScene extends SceneBase {
updateGameInfo(): void {
const gameInfo = {
playTime: this.sessionPlayTime ? this.sessionPlayTime : 0,
playTime: this.sessionPlayTime ?? 0,
gameMode: this.currentBattle ? this.gameMode.getName() : "Title",
biome: this.currentBattle ? getBiomeName(this.arena.biomeType) : "",
wave: this.currentBattle?.waveIndex || 0,
party: this.party ? this.party.map(p => {
return { name: p.name, level: p.level };
wave: this.currentBattle?.waveIndex ?? 0,
party: this.party ? this.party.map((p) => {
return {
name: p.name,
form: p.getFormKey(),
types: p.getTypes().map((type) => Type[type]),
teraType: p.getTeraType() !== Type.UNKNOWN ? Type[p.getTeraType()] : "",
level: p.level,
currentHP: p.hp,
maxHP: p.getMaxHp(),
status: p.status?.effect ? StatusEffect[p.status.effect] : ""
};
}) : [],
modeChain: this.ui?.getModeChain() ?? [],
};

View File

@ -5544,8 +5544,7 @@ export function initAbilities() {
.attr(EffectSporeAbAttr),
new Ability(Abilities.SYNCHRONIZE, 3)
.attr(SyncEncounterNatureAbAttr)
.attr(SynchronizeStatusAbAttr)
.partial(), // interaction with psycho shift needs work, keeping to old Gen interaction for now
.attr(SynchronizeStatusAbAttr),
new Ability(Abilities.CLEAR_BODY, 3)
.attr(ProtectStatAbAttr)
.ignorable(),
@ -6155,7 +6154,7 @@ export function initAbilities() {
.bypassFaint(),
new Ability(Abilities.CORROSION, 7)
.attr(IgnoreTypeStatusEffectImmunityAbAttr, [ StatusEffect.POISON, StatusEffect.TOXIC ], [ Type.STEEL, Type.POISON ])
.edgeCase(), // Should interact correctly with magic coat/bounce (not yet implemented), fling with toxic orb (not implemented yet), and synchronize (not fully implemented yet)
.edgeCase(), // Should interact correctly with magic coat/bounce (not yet implemented) + fling with toxic orb (not implemented yet)
new Ability(Abilities.COMATOSE, 7)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)

View File

@ -18,7 +18,7 @@ import Move, {
StatusCategoryOnAllyAttr
} from "#app/data/move";
import { SpeciesFormChangeManualTrigger } from "#app/data/pokemon-forms";
import { StatusEffect } from "#app/data/status-effect";
import { getStatusEffectHealText, StatusEffect } from "#app/data/status-effect";
import { TerrainType } from "#app/data/terrain";
import { Type } from "#app/data/type";
import { WeatherType } from "#app/data/weather";
@ -2866,6 +2866,28 @@ export class GrudgeTag extends BattlerTag {
}
}
/**
* Tag used to heal the user of Psycho Shift of its status effect if Psycho Shift succeeds in transferring its status effect to the target Pokemon
*/
export class PsychoShiftTag extends BattlerTag {
constructor() {
super(BattlerTagType.PSYCHO_SHIFT, BattlerTagLapseType.AFTER_MOVE, 1, Moves.PSYCHO_SHIFT);
}
/**
* Heals Psycho Shift's user of its status effect after it uses a move
* @returns `false` to expire the tag immediately
*/
override lapse(pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean {
if (pokemon.status && pokemon.isActive(true)) {
pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)));
pokemon.resetStatus();
pokemon.updateInfo();
}
return false;
}
}
/**
* Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID.
* @param sourceId - The ID of the pokemon adding the tag
@ -3049,6 +3071,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
return new PowerTrickTag(sourceMove, sourceId);
case BattlerTagType.GRUDGE:
return new GrudgeTag();
case BattlerTagType.PSYCHO_SHIFT:
return new PsychoShiftTag();
case BattlerTagType.NONE:
default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);

View File

@ -2270,24 +2270,26 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
super(false, { trigger: MoveEffectTrigger.HIT });
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
/**
* Applies the effect of Psycho Shift to its target
* Psycho Shift takes the user's status effect and passes it onto the target. The user is then healed after the move has been successfully executed.
* @returns `true` if Psycho Shift's effect is able to be applied to the target
*/
apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(Abilities.COMATOSE) ? StatusEffect.SLEEP : undefined);
if (target.status) {
return false;
} else {
const canSetStatus = target.canSetStatus(statusToApply, true, false, user);
const trySetStatus = canSetStatus ? target.trySetStatus(statusToApply, true, user) : false;
if (canSetStatus) {
if (user.status) {
user.scene.queueMessage(getStatusEffectHealText(user.status.effect, getPokemonNameWithAffix(user)));
}
user.resetStatus();
user.updateInfo();
target.trySetStatus(statusToApply, true, user);
if (trySetStatus && user.status) {
// PsychoShiftTag is added to the user if move succeeds so that the user is healed of its status effect after its move
user.addTag(BattlerTagType.PSYCHO_SHIFT);
}
return canSetStatus;
return trySetStatus;
}
}

View File

@ -90,5 +90,6 @@ export enum BattlerTagType {
ELECTRIFIED = "ELECTRIFIED",
TELEKINESIS = "TELEKINESIS",
COMMANDED = "COMMANDED",
GRUDGE = "GRUDGE"
GRUDGE = "GRUDGE",
PSYCHO_SHIFT = "PSYCHO_SHIFT",
}

View File

@ -32,6 +32,8 @@ export class CommandPhase extends FieldPhase {
start() {
super.start();
this.scene.updateGameInfo();
const commandUiHandler = this.scene.ui.handlers[Mode.COMMAND];
if (commandUiHandler) {
if (this.scene.currentBattle.turn === 1 || commandUiHandler.getCursor() === Command.POKEMON) {

View File

@ -0,0 +1,46 @@
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("Abilities - Corrosion", () => {
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 ])
.battleType("single")
.disableCrits()
.enemySpecies(Species.GRIMER)
.enemyAbility(Abilities.CORROSION)
.enemyMoveset(Moves.TOXIC);
});
it("If a Poison- or Steel-type Pokémon with this Ability poisons a target with Synchronize, Synchronize does not gain the ability to poison Poison- or Steel-type Pokémon.", async () => {
game.override.ability(Abilities.SYNCHRONIZE);
await game.classicMode.startBattle([ Species.FEEBAS ]);
const playerPokemon = game.scene.getPlayerPokemon();
const enemyPokemon = game.scene.getEnemyPokemon();
expect(playerPokemon!.status).toBeUndefined();
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("BerryPhase");
expect(playerPokemon!.status).toBeDefined();
expect(enemyPokemon!.status).toBeUndefined();
});
});

View File

@ -94,16 +94,4 @@ describe("Abilities - Synchronize", () => {
expect(game.scene.getEnemyPokemon()!.status?.effect).toBe(StatusEffect.PARALYSIS);
expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase");
});
it("should activate with Psycho Shift after the move clears the status", async () => {
game.override.statusEffect(StatusEffect.PARALYSIS);
await game.classicMode.startBattle();
game.move.select(Moves.PSYCHO_SHIFT);
await game.phaseInterceptor.to("BerryPhase");
expect(game.scene.getPlayerPokemon()!.status?.effect).toBe(StatusEffect.PARALYSIS); // keeping old gen < V impl for now since it's buggy otherwise
expect(game.scene.getEnemyPokemon()!.status?.effect).toBe(StatusEffect.PARALYSIS);
expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase");
});
});

View File

@ -0,0 +1,49 @@
import { StatusEffect } from "#app/enums/status-effect";
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 - Psycho Shift", () => {
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.PSYCHO_SHIFT ])
.ability(Abilities.BALL_FETCH)
.statusEffect(StatusEffect.POISON)
.battleType("single")
.disableCrits()
.enemySpecies(Species.MAGIKARP)
.enemyLevel(20)
.enemyAbility(Abilities.SYNCHRONIZE)
.enemyMoveset(Moves.SPLASH);
});
it("If Psycho Shift is used on a Pokémon with Synchronize, the user of Psycho Shift will already be afflicted with a status condition when Synchronize activates", async () => {
await game.classicMode.startBattle([ Species.FEEBAS ]);
const playerPokemon = game.scene.getPlayerPokemon();
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon?.status).toBeUndefined();
game.move.select(Moves.PSYCHO_SHIFT);
await game.phaseInterceptor.to("TurnEndPhase");
expect(playerPokemon?.status).toBeNull();
expect(enemyPokemon?.status).toBeDefined();
});
});