Skip to content

Commit

Permalink
[Ability] Fully implement Sheer Force (#4890)
Browse files Browse the repository at this point in the history
* Added checks for Sheer Force interactions currently in the code.

* Test for Relic Song interaction

* Test for Shell Bell interaction

* Created new Modifier class MoveEffectModifier

* Applied new modifier class.

* Revert "Applied new modifier class."

This reverts commit 222bc8d.

* Revert "Created new Modifier class MoveEffectModifier"

This reverts commit 0e57ed0.

* Added checks for Shell Bell, Scope Lens, Wide Lens, Leek, and Golden Punch

* Fixing function calls.

* Fixed getSecondaryChanceMultiplier to just look at sheer force.

* Rewrote old Sheer Force tests in accordance to current testing standards.

* Resetting modifiers.ts

* Update src/data/pokemon-forms.ts

Co-authored-by: innerthunder <[email protected]>

* Moved getSecondaryChanceMultiplier to FlinchChanceModifier and revised Serene Grace tests

* Adding an additional override to prevent test failures.

* Removed Serene Grace factor from modifier.

* Added forgotten conditional.

* Added comment

---------

Co-authored-by: frutescens <info@laptop>
Co-authored-by: innerthunder <[email protected]>
  • Loading branch information
3 people authored Dec 1, 2024
1 parent d6854c4 commit cef2f2a
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 201 deletions.
4 changes: 1 addition & 3 deletions src/data/ability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5713,9 +5713,7 @@ export function initAbilities() {
.condition(getSheerForceHitDisableAbCondition()),
new Ability(Abilities.SHEER_FORCE, 5)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.chance >= 1, 5461 / 4096)
.attr(MoveEffectChanceMultiplierAbAttr, 0)
.edgeCase() // Should disable shell bell and Meloetta's relic song transformation
.edgeCase(), // Should disable life orb, eject button, red card, kee/maranga berry if they get implemented
.attr(MoveEffectChanceMultiplierAbAttr, 0), // Should disable life orb, eject button, red card, kee/maranga berry if they get implemented
new Ability(Abilities.CONTRARY, 5)
.attr(StatStageChangeMultiplierAbAttr, -1)
.ignorable(),
Expand Down
4 changes: 4 additions & 0 deletions src/data/pokemon-forms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,10 @@ export class MeloettaFormChangePostMoveTrigger extends SpeciesFormChangePostMove
if (pokemon.scene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) {
return false;
} else {
// Meloetta will not transform if it has the ability Sheer Force when using Relic Song
if (pokemon.hasAbility(Abilities.SHEER_FORCE)) {
return false;
}
return super.canChange(pokemon);
}
}
Expand Down
29 changes: 10 additions & 19 deletions src/modifier/modifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import type { VoucherType } from "#app/system/voucher";
import { Command } from "#app/ui/command-ui-handler";
import { addTextObject, TextStyle } from "#app/ui/text";
import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, toDmgValue } from "#app/utils";
import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type";
import { Moves } from "#enums/moves";
Expand Down Expand Up @@ -726,22 +725,6 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier {
return 1;
}

//Applies to items with chance of activating secondary effects ie Kings Rock
getSecondaryChanceMultiplier(pokemon: Pokemon): number {
// Temporary quickfix to stop game from freezing when the opponet uses u-turn while holding on to king's rock
if (!pokemon.getLastXMoves()[0]) {
return 1;
}
const sheerForceAffected = allMoves[pokemon.getLastXMoves()[0].move].chance >= 0 && pokemon.hasAbility(Abilities.SHEER_FORCE);

if (sheerForceAffected) {
return 0;
} else if (pokemon.hasAbility(Abilities.SERENE_GRACE)) {
return 2;
}
return 1;
}

getMaxStackCount(scene: BattleScene, forThreshold?: boolean): number {
const pokemon = this.getPokemon(scene);
if (!pokemon) {
Expand Down Expand Up @@ -1614,9 +1597,16 @@ export class BypassSpeedChanceModifier extends PokemonHeldItemModifier {
}
}

/**
* Class for Pokemon held items like King's Rock
* Because King's Rock can be stacked in PokeRogue, unlike mainline, it does not receive a boost from Abilities.SERENE_GRACE
*/
export class FlinchChanceModifier extends PokemonHeldItemModifier {
private chance: number;
constructor(type: ModifierType, pokemonId: number, stackCount?: number) {
super(type, pokemonId, stackCount);

this.chance = 10;
}

matchType(modifier: Modifier) {
Expand Down Expand Up @@ -1644,15 +1634,16 @@ export class FlinchChanceModifier extends PokemonHeldItemModifier {
* @returns `true` if {@linkcode FlinchChanceModifier} has been applied
*/
override apply(pokemon: Pokemon, flinched: BooleanHolder): boolean {
if (!flinched.value && pokemon.randSeedInt(10) < (this.getStackCount() * this.getSecondaryChanceMultiplier(pokemon))) {
// The check for pokemon.battleSummonData is to ensure that a crash doesn't occur when a Pokemon with King's Rock procs a flinch
if (pokemon.battleSummonData && !flinched.value && pokemon.randSeedInt(100) < (this.getStackCount() * this.chance)) {
flinched.value = true;
return true;
}

return false;
}

getMaxHeldItemCount(pokemon: Pokemon): number {
getMaxHeldItemCount(_pokemon: Pokemon): number {
return 3;
}
}
Expand Down
81 changes: 19 additions & 62 deletions src/test/abilities/serene_grace.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { BattlerIndex } from "#app/battle";
import { applyAbAttrs, MoveEffectChanceMultiplierAbAttr } from "#app/data/ability";
import { Stat } from "#enums/stat";
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import * as Utils from "#app/utils";
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 { allMoves } from "#app/data/move";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { FlinchAttr } from "#app/data/move";

describe("Abilities - Serene Grace", () => {
let phaserGame: Phaser.Game;
Expand All @@ -27,66 +24,26 @@ describe("Abilities - Serene Grace", () => {

beforeEach(() => {
game = new GameManager(phaserGame);
const movesToUse = [ Moves.AIR_SLASH, Moves.TACKLE ];
game.override.battleType("single");
game.override.enemySpecies(Species.ONIX);
game.override.startingLevel(100);
game.override.moveset(movesToUse);
game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]);
game.override
.battleType("single")
.ability(Abilities.SERENE_GRACE)
.moveset([ Moves.AIR_SLASH, Moves.TACKLE ])
.enemyLevel(10)
.enemyMoveset([ Moves.SPLASH ]);
});

it("Move chance without Serene Grace", async () => {
const moveToUse = Moves.AIR_SLASH;
await game.startBattle([
Species.PIDGEOT
]);

it("Serene Grace should double the secondary effect chance of a move", async () => {
await game.classicMode.startBattle([ Species.SHUCKLE ]);

game.scene.getEnemyParty()[0].stats[Stat.SPDEF] = 10000;
expect(game.scene.getPlayerParty()[0].formIndex).toBe(0);

game.move.select(moveToUse);
const airSlashMove = allMoves[Moves.AIR_SLASH];
const airSlashFlinchAttr = airSlashMove.getAttrs(FlinchAttr)[0];
vi.spyOn(airSlashFlinchAttr, "getMoveChance");

game.move.select(Moves.AIR_SLASH);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.phaseInterceptor.to(MoveEffectPhase, false);

// Check chance of Air Slash without Serene Grace
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
const move = phase.move.getMove();
expect(move.id).toBe(Moves.AIR_SLASH);

const chance = new Utils.IntegerHolder(move.chance);
console.log(move.chance + " Their ability is " + phase.getUserPokemon()!.getAbility().name);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false);
expect(chance.value).toBe(30);

}, 20000);

it("Move chance with Serene Grace", async () => {
const moveToUse = Moves.AIR_SLASH;
game.override.ability(Abilities.SERENE_GRACE);
await game.startBattle([
Species.TOGEKISS
]);

game.scene.getEnemyParty()[0].stats[Stat.SPDEF] = 10000;
expect(game.scene.getPlayerParty()[0].formIndex).toBe(0);
await game.move.forceHit();
await game.phaseInterceptor.to("BerryPhase");

game.move.select(moveToUse);

await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.phaseInterceptor.to(MoveEffectPhase, false);

// Check chance of Air Slash with Serene Grace
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
const move = phase.move.getMove();
expect(move.id).toBe(Moves.AIR_SLASH);

const chance = new Utils.IntegerHolder(move.chance);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false);
expect(chance.value).toBe(60);

}, 20000);

//TODO King's Rock Interaction Unit Test
expect(airSlashFlinchAttr.getMoveChance).toHaveLastReturnedWith(60);
});
});
Loading

0 comments on commit cef2f2a

Please sign in to comment.