mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-20 15:22:19 +02:00
Merge branch 'beta' into refactor-move-file
This commit is contained in:
commit
e630e06256
4
.gitignore
vendored
4
.gitignore
vendored
@ -41,3 +41,7 @@ coverage
|
|||||||
|
|
||||||
/dependency-graph.svg
|
/dependency-graph.svg
|
||||||
/.vs
|
/.vs
|
||||||
|
|
||||||
|
|
||||||
|
# Script outputs
|
||||||
|
./*.csv
|
@ -1 +1 @@
|
|||||||
Subproject commit 0e5c6096ba26f6b87aed1aab3fe9b0b23f6cbb7b
|
Subproject commit 6b3f37cb351552721232f4dabefa17bddb5b9004
|
98
scripts/find_sprite_variant_mismatches.py
Normal file
98
scripts/find_sprite_variant_mismatches.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
"""
|
||||||
|
Validates the contents of the variant's masterlist file and identifies
|
||||||
|
any mismatched entries for the sprite of the same key between front, back, exp, exp back, and female.
|
||||||
|
|
||||||
|
This will create a csv file that contains all of the entries with mismatches.
|
||||||
|
|
||||||
|
An empty entry means that there was not a mismatch for that version of the sprite (meaning it matches front).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if sys.version_info < (3, 7):
|
||||||
|
msg = "This script requires Python 3.7+"
|
||||||
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import csv
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Literal as L
|
||||||
|
|
||||||
|
MASTERLIST_PATH = os.path.join(
|
||||||
|
os.path.dirname(os.path.dirname(__file__)), "public", "images", "pokemon", "variant", "_masterlist.json"
|
||||||
|
)
|
||||||
|
DEFAULT_OUTPUT_PATH = "sprite-mismatches.csv"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(order=True)
|
||||||
|
class Sprite:
|
||||||
|
key: str = field(compare=False)
|
||||||
|
front: list[int] = field(default_factory=list, compare=False)
|
||||||
|
back: list[int] = field(default_factory=list, compare=False)
|
||||||
|
female: list[int] = field(default_factory=list, compare=False)
|
||||||
|
exp: list[int] = field(default_factory=list, compare=False)
|
||||||
|
expback: list[int] = field(default_factory=list, compare=False)
|
||||||
|
sortedKey: tuple[int] | tuple[int, str] = field(init=False, repr=False, compare=True)
|
||||||
|
|
||||||
|
def as_row(self) -> tuple[str, list[int] | L[""], list[int] | L[""], list[int] | L[""], list[int] | L[""], list[int] | L[""]]:
|
||||||
|
"""return sprite information as a tuple for csv writing"""
|
||||||
|
return (self.key, self.front or "", self.back or "", self.exp or "", self.expback or "", self.female or "")
|
||||||
|
|
||||||
|
def is_mismatch(self) -> bool:
|
||||||
|
"""return True if the female, back, or exp sprites do not match the front"""
|
||||||
|
for val in [self.back, self.exp, self.expback, self.female]:
|
||||||
|
if val != [] and val != self.front:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
split = self.key.split("-", maxsplit=1)
|
||||||
|
self.sortedKey = (int(split[0]), split[1]) if len(split) == 2 else (int(split[0]),)
|
||||||
|
|
||||||
|
|
||||||
|
def make_mismatch_sprite_list(path):
|
||||||
|
with open(path, "r") as f:
|
||||||
|
masterlist: dict = json.load(f)
|
||||||
|
|
||||||
|
# Go through the keys in "front" and "back" and make sure they match the masterlist
|
||||||
|
back_data: dict[str, list[int]] = masterlist.pop("back", {})
|
||||||
|
exp_data: dict[str, list[int]] = masterlist.pop("exp", {})
|
||||||
|
exp_back_data: dict[str, list[int]] = exp_data.get("back", [])
|
||||||
|
female_data: dict[str, list[int]] = masterlist.pop("female", {})
|
||||||
|
|
||||||
|
sprites: list[Sprite] = []
|
||||||
|
|
||||||
|
for key, item in masterlist.items():
|
||||||
|
sprite = Sprite(
|
||||||
|
key, front=item, back=back_data.get(key, []), exp=exp_data.get(key, []), expback=exp_back_data.get(key, []), female=female_data.get(key, [])
|
||||||
|
)
|
||||||
|
if sprite.is_mismatch():
|
||||||
|
sprites.append(sprite)
|
||||||
|
|
||||||
|
return sprites
|
||||||
|
|
||||||
|
|
||||||
|
def write_mismatch_csv(filename: str, mismatches: list[Sprite]):
|
||||||
|
with open(filename, "w", newline="") as csvfile:
|
||||||
|
writer = csv.writer(csvfile)
|
||||||
|
writer.writerow(["key", "front", "back", "exp", "expback", "female"])
|
||||||
|
for sprite in sorted(mismatches):
|
||||||
|
writer.writerow(sprite.as_row())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
p = argparse.ArgumentParser("find_sprite_variant_mismatches", description=__doc__)
|
||||||
|
|
||||||
|
p.add_argument(
|
||||||
|
"-o",
|
||||||
|
"--output",
|
||||||
|
default=DEFAULT_OUTPUT_PATH,
|
||||||
|
help=f"The path to a file to save the output file. If not specified, will write to {DEFAULT_OUTPUT_PATH}.",
|
||||||
|
)
|
||||||
|
p.add_argument("--masterlist", default=MASTERLIST_PATH, help=f"The path to the masterlist file to validate. Defaults to {MASTERLIST_PATH}.")
|
||||||
|
args = p.parse_args()
|
||||||
|
mismatches = make_mismatch_sprite_list(args.masterlist)
|
||||||
|
write_mismatch_csv(args.output, mismatches)
|
@ -205,6 +205,28 @@ export function getWeatherClearMessage(weatherType: WeatherType): string | null
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getLegendaryWeatherContinuesMessage(weatherType: WeatherType): string | null {
|
||||||
|
switch (weatherType) {
|
||||||
|
case WeatherType.HARSH_SUN:
|
||||||
|
return i18next.t("weather:harshSunContinueMessage");
|
||||||
|
case WeatherType.HEAVY_RAIN:
|
||||||
|
return i18next.t("weather:heavyRainContinueMessage");
|
||||||
|
case WeatherType.STRONG_WINDS:
|
||||||
|
return i18next.t("weather:strongWindsContinueMessage");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWeatherBlockMessage(weatherType: WeatherType): string {
|
||||||
|
switch (weatherType) {
|
||||||
|
case WeatherType.HARSH_SUN:
|
||||||
|
return i18next.t("weather:harshSunEffectMessage");
|
||||||
|
case WeatherType.HEAVY_RAIN:
|
||||||
|
return i18next.t("weather:heavyRainEffectMessage");
|
||||||
|
}
|
||||||
|
return i18next.t("weather:defaultEffectMessage");
|
||||||
|
}
|
||||||
|
|
||||||
export function getTerrainStartMessage(terrainType: TerrainType): string | null {
|
export function getTerrainStartMessage(terrainType: TerrainType): string | null {
|
||||||
switch (terrainType) {
|
switch (terrainType) {
|
||||||
case TerrainType.MISTY:
|
case TerrainType.MISTY:
|
||||||
|
@ -5,7 +5,7 @@ import type { Constructor } from "#app/utils";
|
|||||||
import * as Utils from "#app/utils";
|
import * as Utils from "#app/utils";
|
||||||
import type PokemonSpecies from "#app/data/pokemon-species";
|
import type PokemonSpecies from "#app/data/pokemon-species";
|
||||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
import { getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage, Weather } from "#app/data/weather";
|
import { getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage, getLegendaryWeatherContinuesMessage, Weather } from "#app/data/weather";
|
||||||
import { CommonAnim } from "#app/data/battle-anims";
|
import { CommonAnim } from "#app/data/battle-anims";
|
||||||
import type { PokemonType } from "#enums/type";
|
import type { PokemonType } from "#enums/type";
|
||||||
import type Move from "#app/data/moves/move";
|
import type Move from "#app/data/moves/move";
|
||||||
@ -274,6 +274,12 @@ export class Arena {
|
|||||||
|
|
||||||
const oldWeatherType = this.weather?.weatherType || WeatherType.NONE;
|
const oldWeatherType = this.weather?.weatherType || WeatherType.NONE;
|
||||||
|
|
||||||
|
if (this.weather?.isImmutable() && ![ WeatherType.HARSH_SUN, WeatherType.HEAVY_RAIN, WeatherType.STRONG_WINDS, WeatherType.NONE ].includes(weather)) {
|
||||||
|
globalScene.unshiftPhase(new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (oldWeatherType - 1), true));
|
||||||
|
globalScene.queueMessage(getLegendaryWeatherContinuesMessage(oldWeatherType)!);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
this.weather = weather ? new Weather(weather, hasPokemonSource ? 5 : 0) : null;
|
this.weather = weather ? new Weather(weather, hasPokemonSource ? 5 : 0) : null;
|
||||||
this.eventTarget.dispatchEvent(new WeatherChangedEvent(oldWeatherType, this.weather?.weatherType!, this.weather?.turnsLeft!)); // TODO: is this bang correct?
|
this.eventTarget.dispatchEvent(new WeatherChangedEvent(oldWeatherType, this.weather?.weatherType!, this.weather?.turnsLeft!)); // TODO: is this bang correct?
|
||||||
|
|
||||||
@ -358,6 +364,10 @@ export class Arena {
|
|||||||
return !!this.terrain && this.terrain.isMoveTerrainCancelled(user, targets, move);
|
return !!this.terrain && this.terrain.isMoveTerrainCancelled(user, targets, move);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getWeatherType(): WeatherType {
|
||||||
|
return this.weather?.weatherType ?? WeatherType.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
public getTerrainType(): TerrainType {
|
public getTerrainType(): TerrainType {
|
||||||
return this.terrain?.terrainType ?? TerrainType.NONE;
|
return this.terrain?.terrainType ?? TerrainType.NONE;
|
||||||
}
|
}
|
||||||
|
@ -2886,7 +2886,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
/** Reduces damage if this Pokemon has a relevant screen (e.g. Light Screen for special attacks) */
|
/** Reduces damage if this Pokemon has a relevant screen (e.g. Light Screen for special attacks) */
|
||||||
const screenMultiplier = new Utils.NumberHolder(1);
|
const screenMultiplier = new Utils.NumberHolder(1);
|
||||||
|
|
||||||
|
// Critical hits should bypass screens
|
||||||
|
if (!isCritical) {
|
||||||
globalScene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, simulated, source, moveCategory, screenMultiplier);
|
globalScene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, simulated, source, moveCategory, screenMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For each {@linkcode HitsTagAttr} the move has, doubles the damage of the move if:
|
* For each {@linkcode HitsTagAttr} the move has, doubles the damage of the move if:
|
||||||
|
@ -30,7 +30,7 @@ import { MoveFlags } from "#enums/MoveFlags";
|
|||||||
import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms";
|
import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms";
|
||||||
import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect";
|
import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect";
|
||||||
import { PokemonType } from "#enums/type";
|
import { PokemonType } from "#enums/type";
|
||||||
import { getTerrainBlockMessage } from "#app/data/weather";
|
import { getTerrainBlockMessage, getWeatherBlockMessage } from "#app/data/weather";
|
||||||
import { MoveUsedEvent } from "#app/events/battle-scene";
|
import { MoveUsedEvent } from "#app/events/battle-scene";
|
||||||
import type { PokemonMove } from "#app/field/pokemon";
|
import type { PokemonMove } from "#app/field/pokemon";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
@ -342,9 +342,10 @@ export class MovePhase extends BattlePhase {
|
|||||||
* TODO: is this sustainable?
|
* TODO: is this sustainable?
|
||||||
*/
|
*/
|
||||||
let failedDueToTerrain: boolean = false;
|
let failedDueToTerrain: boolean = false;
|
||||||
|
let failedDueToWeather: boolean = false;
|
||||||
if (success) {
|
if (success) {
|
||||||
const passesConditions = move.applyConditions(this.pokemon, targets[0], move);
|
const passesConditions = move.applyConditions(this.pokemon, targets[0], move);
|
||||||
const failedDueToWeather: boolean = globalScene.arena.isMoveWeatherCancelled(this.pokemon, move);
|
failedDueToWeather = globalScene.arena.isMoveWeatherCancelled(this.pokemon, move);
|
||||||
failedDueToTerrain = globalScene.arena.isMoveTerrainCancelled(this.pokemon, this.targets, move);
|
failedDueToTerrain = globalScene.arena.isMoveTerrainCancelled(this.pokemon, this.targets, move);
|
||||||
success = passesConditions && !failedDueToWeather && !failedDueToTerrain;
|
success = passesConditions && !failedDueToWeather && !failedDueToTerrain;
|
||||||
}
|
}
|
||||||
@ -381,6 +382,8 @@ export class MovePhase extends BattlePhase {
|
|||||||
failedText = failureMessage;
|
failedText = failureMessage;
|
||||||
} else if (failedDueToTerrain) {
|
} else if (failedDueToTerrain) {
|
||||||
failedText = getTerrainBlockMessage(targets[0], globalScene.arena.getTerrainType());
|
failedText = getTerrainBlockMessage(targets[0], globalScene.arena.getTerrainType());
|
||||||
|
} else if (failedDueToWeather) {
|
||||||
|
failedText = getWeatherBlockMessage(globalScene.arena.getWeatherType());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.showFailedText(failedText);
|
this.showFailedText(failedText);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type BattleScene from "#app/battle-scene";
|
import type BattleScene from "#app/battle-scene";
|
||||||
import { ArenaTagSide } from "#app/data/arena-tag";
|
import { ArenaTagSide } from "#app/data/arena-tag";
|
||||||
import type Move from "#app/data/moves/move";
|
import type Move from "#app/data/moves/move";
|
||||||
import { allMoves } from "#app/data/moves/move";
|
import { allMoves, CritOnlyAttr } from "#app/data/moves/move";
|
||||||
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
||||||
@ -12,7 +12,7 @@ import { Species } from "#enums/species";
|
|||||||
import { WeatherType } from "#enums/weather-type";
|
import { WeatherType } from "#enums/weather-type";
|
||||||
import GameManager from "#test/testUtils/gameManager";
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
let globalScene: BattleScene;
|
let globalScene: BattleScene;
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ describe("Moves - Aurora Veil", () => {
|
|||||||
|
|
||||||
it("reduces damage of physical attacks by half in a single battle", async () => {
|
it("reduces damage of physical attacks by half in a single battle", async () => {
|
||||||
const moveToUse = Moves.TACKLE;
|
const moveToUse = Moves.TACKLE;
|
||||||
await game.startBattle([ Species.SHUCKLE ]);
|
await game.classicMode.startBattle([ Species.SHUCKLE ]);
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ describe("Moves - Aurora Veil", () => {
|
|||||||
game.override.battleType("double");
|
game.override.battleType("double");
|
||||||
|
|
||||||
const moveToUse = Moves.ROCK_SLIDE;
|
const moveToUse = Moves.ROCK_SLIDE;
|
||||||
await game.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]);
|
await game.classicMode.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]);
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
game.move.select(moveToUse, 1);
|
game.move.select(moveToUse, 1);
|
||||||
@ -74,7 +74,7 @@ describe("Moves - Aurora Veil", () => {
|
|||||||
|
|
||||||
it("reduces damage of special attacks by half in a single battle", async () => {
|
it("reduces damage of special attacks by half in a single battle", async () => {
|
||||||
const moveToUse = Moves.ABSORB;
|
const moveToUse = Moves.ABSORB;
|
||||||
await game.startBattle([ Species.SHUCKLE ]);
|
await game.classicMode.startBattle([ Species.SHUCKLE ]);
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ describe("Moves - Aurora Veil", () => {
|
|||||||
game.override.battleType("double");
|
game.override.battleType("double");
|
||||||
|
|
||||||
const moveToUse = Moves.DAZZLING_GLEAM;
|
const moveToUse = Moves.DAZZLING_GLEAM;
|
||||||
await game.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]);
|
await game.classicMode.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]);
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
game.move.select(moveToUse, 1);
|
game.move.select(moveToUse, 1);
|
||||||
@ -99,6 +99,31 @@ describe("Moves - Aurora Veil", () => {
|
|||||||
|
|
||||||
expect(mockedDmg).toBe(allMoves[moveToUse].power * doubleBattleMultiplier);
|
expect(mockedDmg).toBe(allMoves[moveToUse].power * doubleBattleMultiplier);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not affect physical critical hits", async () => {
|
||||||
|
game.override.moveset([ Moves.WICKED_BLOW ]);
|
||||||
|
const moveToUse = Moves.WICKED_BLOW;
|
||||||
|
await game.classicMode.startBattle([ Species.SHUCKLE ]);
|
||||||
|
|
||||||
|
game.move.select(moveToUse);
|
||||||
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
|
||||||
|
const mockedDmg = getMockedMoveDamage(game.scene.getEnemyPokemon()!, game.scene.getPlayerPokemon()!, allMoves[moveToUse]);
|
||||||
|
expect(mockedDmg).toBe(allMoves[moveToUse].power);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not affect critical hits", async () => {
|
||||||
|
game.override.moveset([ Moves.FROST_BREATH ]);
|
||||||
|
const moveToUse = Moves.FROST_BREATH;
|
||||||
|
vi.spyOn(allMoves[Moves.FROST_BREATH], "accuracy", "get").mockReturnValue(100);
|
||||||
|
await game.classicMode.startBattle([ Species.SHUCKLE ]);
|
||||||
|
|
||||||
|
game.move.select(moveToUse);
|
||||||
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
|
||||||
|
const mockedDmg = getMockedMoveDamage(game.scene.getEnemyPokemon()!, game.scene.getPlayerPokemon()!, allMoves[moveToUse]);
|
||||||
|
expect(mockedDmg).toBe(allMoves[moveToUse].power);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -115,8 +140,10 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
|
|||||||
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||||
|
|
||||||
if (globalScene.arena.getTagOnSide(ArenaTagType.AURORA_VEIL, side)) {
|
if (globalScene.arena.getTagOnSide(ArenaTagType.AURORA_VEIL, side)) {
|
||||||
|
if (move.getAttrs(CritOnlyAttr).length === 0) {
|
||||||
globalScene.arena.applyTagsForSide(ArenaTagType.AURORA_VEIL, side, false, attacker, move.category, multiplierHolder);
|
globalScene.arena.applyTagsForSide(ArenaTagType.AURORA_VEIL, side, false, attacker, move.category, multiplierHolder);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return move.power * multiplierHolder.value;
|
return move.power * multiplierHolder.value;
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type BattleScene from "#app/battle-scene";
|
import type BattleScene from "#app/battle-scene";
|
||||||
import { ArenaTagSide } from "#app/data/arena-tag";
|
import { ArenaTagSide } from "#app/data/arena-tag";
|
||||||
import type Move from "#app/data/moves/move";
|
import type Move from "#app/data/moves/move";
|
||||||
import { allMoves } from "#app/data/moves/move";
|
import { allMoves, CritOnlyAttr } from "#app/data/moves/move";
|
||||||
import { Abilities } from "#app/enums/abilities";
|
import { Abilities } from "#app/enums/abilities";
|
||||||
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
@ -11,7 +11,7 @@ import { Moves } from "#enums/moves";
|
|||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import GameManager from "#test/testUtils/gameManager";
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
let globalScene: BattleScene;
|
let globalScene: BattleScene;
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ describe("Moves - Light Screen", () => {
|
|||||||
|
|
||||||
it("reduces damage of special attacks by half in a single battle", async () => {
|
it("reduces damage of special attacks by half in a single battle", async () => {
|
||||||
const moveToUse = Moves.ABSORB;
|
const moveToUse = Moves.ABSORB;
|
||||||
await game.startBattle([ Species.SHUCKLE ]);
|
await game.classicMode.startBattle([ Species.SHUCKLE ]);
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ describe("Moves - Light Screen", () => {
|
|||||||
game.override.battleType("double");
|
game.override.battleType("double");
|
||||||
|
|
||||||
const moveToUse = Moves.DAZZLING_GLEAM;
|
const moveToUse = Moves.DAZZLING_GLEAM;
|
||||||
await game.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]);
|
await game.classicMode.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]);
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
game.move.select(moveToUse, 1);
|
game.move.select(moveToUse, 1);
|
||||||
@ -73,7 +73,7 @@ describe("Moves - Light Screen", () => {
|
|||||||
|
|
||||||
it("does not affect physical attacks", async () => {
|
it("does not affect physical attacks", async () => {
|
||||||
const moveToUse = Moves.TACKLE;
|
const moveToUse = Moves.TACKLE;
|
||||||
await game.startBattle([ Species.SHUCKLE ]);
|
await game.classicMode.startBattle([ Species.SHUCKLE ]);
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
|
|
||||||
@ -82,6 +82,19 @@ describe("Moves - Light Screen", () => {
|
|||||||
|
|
||||||
expect(mockedDmg).toBe(allMoves[moveToUse].power);
|
expect(mockedDmg).toBe(allMoves[moveToUse].power);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not affect critical hits", async () => {
|
||||||
|
game.override.moveset([ Moves.FROST_BREATH ]);
|
||||||
|
const moveToUse = Moves.FROST_BREATH;
|
||||||
|
vi.spyOn(allMoves[Moves.FROST_BREATH], "accuracy", "get").mockReturnValue(100);
|
||||||
|
await game.classicMode.startBattle([ Species.SHUCKLE ]);
|
||||||
|
|
||||||
|
game.move.select(moveToUse);
|
||||||
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
|
||||||
|
const mockedDmg = getMockedMoveDamage(game.scene.getEnemyPokemon()!, game.scene.getPlayerPokemon()!, allMoves[moveToUse]);
|
||||||
|
expect(mockedDmg).toBe(allMoves[moveToUse].power);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -98,8 +111,10 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
|
|||||||
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||||
|
|
||||||
if (globalScene.arena.getTagOnSide(ArenaTagType.LIGHT_SCREEN, side)) {
|
if (globalScene.arena.getTagOnSide(ArenaTagType.LIGHT_SCREEN, side)) {
|
||||||
|
if (move.getAttrs(CritOnlyAttr).length === 0) {
|
||||||
globalScene.arena.applyTagsForSide(ArenaTagType.LIGHT_SCREEN, side, false, attacker, move.category, multiplierHolder);
|
globalScene.arena.applyTagsForSide(ArenaTagType.LIGHT_SCREEN, side, false, attacker, move.category, multiplierHolder);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return move.power * multiplierHolder.value;
|
return move.power * multiplierHolder.value;
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type BattleScene from "#app/battle-scene";
|
import type BattleScene from "#app/battle-scene";
|
||||||
import { ArenaTagSide } from "#app/data/arena-tag";
|
import { ArenaTagSide } from "#app/data/arena-tag";
|
||||||
import type Move from "#app/data/moves/move";
|
import type Move from "#app/data/moves/move";
|
||||||
import { allMoves } from "#app/data/moves/move";
|
import { allMoves, CritOnlyAttr } from "#app/data/moves/move";
|
||||||
import { Abilities } from "#app/enums/abilities";
|
import { Abilities } from "#app/enums/abilities";
|
||||||
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
@ -45,7 +45,7 @@ describe("Moves - Reflect", () => {
|
|||||||
|
|
||||||
it("reduces damage of physical attacks by half in a single battle", async () => {
|
it("reduces damage of physical attacks by half in a single battle", async () => {
|
||||||
const moveToUse = Moves.TACKLE;
|
const moveToUse = Moves.TACKLE;
|
||||||
await game.startBattle([ Species.SHUCKLE ]);
|
await game.classicMode.startBattle([ Species.SHUCKLE ]);
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ describe("Moves - Reflect", () => {
|
|||||||
game.override.battleType("double");
|
game.override.battleType("double");
|
||||||
|
|
||||||
const moveToUse = Moves.ROCK_SLIDE;
|
const moveToUse = Moves.ROCK_SLIDE;
|
||||||
await game.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]);
|
await game.classicMode.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]);
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
game.move.select(moveToUse, 1);
|
game.move.select(moveToUse, 1);
|
||||||
@ -72,7 +72,7 @@ describe("Moves - Reflect", () => {
|
|||||||
|
|
||||||
it("does not affect special attacks", async () => {
|
it("does not affect special attacks", async () => {
|
||||||
const moveToUse = Moves.ABSORB;
|
const moveToUse = Moves.ABSORB;
|
||||||
await game.startBattle([ Species.SHUCKLE ]);
|
await game.classicMode.startBattle([ Species.SHUCKLE ]);
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
|
|
||||||
@ -82,6 +82,18 @@ describe("Moves - Reflect", () => {
|
|||||||
|
|
||||||
expect(mockedDmg).toBe(allMoves[moveToUse].power);
|
expect(mockedDmg).toBe(allMoves[moveToUse].power);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not affect critical hits", async () => {
|
||||||
|
game.override.moveset([ Moves.WICKED_BLOW ]);
|
||||||
|
const moveToUse = Moves.WICKED_BLOW;
|
||||||
|
await game.classicMode.startBattle([ Species.SHUCKLE ]);
|
||||||
|
|
||||||
|
game.move.select(moveToUse);
|
||||||
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
|
||||||
|
const mockedDmg = getMockedMoveDamage(game.scene.getEnemyPokemon()!, game.scene.getPlayerPokemon()!, allMoves[moveToUse]);
|
||||||
|
expect(mockedDmg).toBe(allMoves[moveToUse].power);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -98,8 +110,10 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
|
|||||||
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||||
|
|
||||||
if (globalScene.arena.getTagOnSide(ArenaTagType.REFLECT, side)) {
|
if (globalScene.arena.getTagOnSide(ArenaTagType.REFLECT, side)) {
|
||||||
|
if (move.getAttrs(CritOnlyAttr).length === 0) {
|
||||||
globalScene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, false, attacker, move.category, multiplierHolder);
|
globalScene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, false, attacker, move.category, multiplierHolder);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return move.power * multiplierHolder.value;
|
return move.power * multiplierHolder.value;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user