Compare commits
30 Commits
e88dc1af3b
...
8db1f09dfd
Author | SHA1 | Date |
---|---|---|
Dean | 8db1f09dfd | |
Madmadness65 | ec09186264 | |
NightKev | e88b8aeb6f | |
AJ Fontaine | 8d20b7b5e0 | |
damocleas | d14a5b8819 | |
NightKev | 7563a6cd0b | |
AJ Fontaine | 9fb9bb7e5d | |
damocleas | 7ee573937e | |
Dean | a27ccbf9c0 | |
Dean | 59d1dcedd6 | |
Dean | 8efe67c3c7 | |
Dean | 054bd031ce | |
Dean | 50bd949a40 | |
Dean | f4ad6381d2 | |
Dean | ccf704d97d | |
Dean | 71d5d23dcd | |
Dean | cee65c4808 | |
Dean | 66e7db82de | |
Dean | 2fbcd28ea8 | |
Dean | 22a2905cf8 | |
Dean | c1f6303260 | |
Dean | 2a05517010 | |
Dean | 71ecc683e4 | |
Dean | bde258bdfb | |
Dean | 88724b83eb | |
Dean | a1600161c1 | |
Dean | fadc2e526d | |
Dean | f81c713380 | |
Dean | bdbeaac104 | |
Dean | f6836b26a4 |
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "pokemon-rogue-battle",
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pokemon-rogue-battle",
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.2",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@material/material-color-utilities": "^0.2.7",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "pokemon-rogue-battle",
|
||||
"private": true,
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
|
|
Binary file not shown.
|
@ -2084,8 +2084,11 @@ export default class BattleScene extends SceneBase {
|
|||
return sound;
|
||||
}
|
||||
|
||||
/** The loop point of any given battle, mystery encounter, or title track, read as seconds and milliseconds. */
|
||||
getBgmLoopPoint(bgmName: string): number {
|
||||
switch (bgmName) {
|
||||
case "title": //Firel PokéRogue Title
|
||||
return 46.500;
|
||||
case "battle_kanto_champion": //B2W2 Kanto Champion Battle
|
||||
return 13.950;
|
||||
case "battle_johto_champion": //B2W2 Johto Champion Battle
|
||||
|
|
|
@ -7942,6 +7942,56 @@ export class AfterYouAttr extends MoveEffectAttr {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move effect to force the target to move last, ignoring priority.
|
||||
* If applied to multiple targets, they move in speed order after all other moves.
|
||||
* @extends MoveEffectAttr
|
||||
*/
|
||||
export class ForceLastAttr extends MoveEffectAttr {
|
||||
/**
|
||||
* Forces the target of this move to move last.
|
||||
*
|
||||
* @param user {@linkcode Pokemon} that is using the move.
|
||||
* @param target {@linkcode Pokemon} that will be forced to move last.
|
||||
* @param move {@linkcode Move} {@linkcode Moves.QUASH}
|
||||
* @param _args N/A
|
||||
* @returns true
|
||||
*/
|
||||
override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:forceLast", { targetPokemonName: getPokemonNameWithAffix(target) }));
|
||||
|
||||
const targetMovePhase = globalScene.findPhase<MovePhase>((phase) => phase.pokemon === target);
|
||||
if (targetMovePhase && !targetMovePhase.isForcedLast() && globalScene.tryRemovePhase((phase: MovePhase) => phase.pokemon === target)) {
|
||||
// Finding the phase to insert the move in front of -
|
||||
// Either the end of the turn or in front of another, slower move which has also been forced last
|
||||
const prependPhase = globalScene.findPhase((phase) =>
|
||||
[ MovePhase, MoveEndPhase ].every(cls => !(phase instanceof cls))
|
||||
|| (phase instanceof MovePhase) && phaseForcedSlower(phase, target, !!globalScene.arena.getTag(ArenaTagType.TRICK_ROOM))
|
||||
);
|
||||
if (prependPhase) {
|
||||
globalScene.phaseQueue.splice(
|
||||
globalScene.phaseQueue.indexOf(prependPhase),
|
||||
0,
|
||||
new MovePhase(target, [ ...targetMovePhase.targets ], targetMovePhase.move, false, false, true)
|
||||
);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns whether a {@linkcode MovePhase} has been forced last and the corresponding pokemon is slower than {@linkcode target} */
|
||||
const phaseForcedSlower = (phase: MovePhase, target: Pokemon, trickRoom: boolean): boolean => {
|
||||
let slower: boolean;
|
||||
// quashed pokemon still have speed ties
|
||||
if (phase.pokemon.getEffectiveStat(Stat.SPD) === target.getEffectiveStat(Stat.SPD)) {
|
||||
slower = !!target.randSeedInt(2);
|
||||
} else {
|
||||
slower = !trickRoom ? phase.pokemon.getEffectiveStat(Stat.SPD) < target.getEffectiveStat(Stat.SPD) : phase.pokemon.getEffectiveStat(Stat.SPD) > target.getEffectiveStat(Stat.SPD);
|
||||
}
|
||||
return phase.isForcedLast() && slower;
|
||||
};
|
||||
|
||||
const failOnGravityCondition: MoveConditionFunc = (user, target, move) => !globalScene.arena.getTag(ArenaTagType.GRAVITY);
|
||||
|
||||
const failOnBossCondition: MoveConditionFunc = (user, target, move) => !target.isBossImmune();
|
||||
|
@ -9745,7 +9795,8 @@ export function initMoves() {
|
|||
.attr(RemoveHeldItemAttr, true),
|
||||
new StatusMove(Moves.QUASH, Type.DARK, 100, 15, -1, 0, 5)
|
||||
.condition(failIfSingleBattle)
|
||||
.unimplemented(),
|
||||
.condition((user, target, move) => !target.turnData.acted)
|
||||
.attr(ForceLastAttr),
|
||||
new AttackMove(Moves.ACROBATICS, Type.FLYING, MoveCategory.PHYSICAL, 55, 100, 15, -1, 0, 5)
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => Math.max(1, 2 - 0.2 * user.getHeldItems().filter(i => i.isTransferable).reduce((v, m) => v + m.stackCount, 0))),
|
||||
new StatusMove(Moves.REFLECT_TYPE, Type.NORMAL, -1, 15, -1, 0, 5)
|
||||
|
|
|
@ -890,7 +890,7 @@ export function getRandomEncounterSpecies(level: number, isBoss: boolean = false
|
|||
|
||||
if (eventEncounters.length > 0 && randSeedInt(2) === 1) {
|
||||
const eventEncounter = randSeedItem(eventEncounters);
|
||||
const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, !isNullOrUndefined(eventEncounter.blockEvolution), isBoss, globalScene.gameMode);
|
||||
const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, !eventEncounter.blockEvolution, isBoss, globalScene.gameMode);
|
||||
isEventEncounter = true;
|
||||
bossSpecies = getPokemonSpecies(levelSpecies);
|
||||
} else {
|
||||
|
|
|
@ -695,6 +695,7 @@ export class Arena {
|
|||
globalScene.loadBgm(this.bgm);
|
||||
}
|
||||
|
||||
/** The loop point of any given biome track, read as seconds and milliseconds. */
|
||||
getBgmLoopPoint(): number {
|
||||
switch (this.biomeType) {
|
||||
case Biome.TOWN:
|
||||
|
|
|
@ -56,6 +56,7 @@ export class MovePhase extends BattlePhase {
|
|||
protected _targets: BattlerIndex[];
|
||||
protected followUp: boolean;
|
||||
protected ignorePp: boolean;
|
||||
protected forcedLast: boolean;
|
||||
protected failed: boolean = false;
|
||||
protected cancelled: boolean = false;
|
||||
|
||||
|
@ -87,7 +88,8 @@ export class MovePhase extends BattlePhase {
|
|||
* @param followUp Indicates that the move being uses is a "follow-up" - for example, a move being used by Metronome or Dancer.
|
||||
* Follow-ups bypass a few failure conditions, including flinches, sleep/paralysis/freeze and volatile status checks, etc.
|
||||
*/
|
||||
constructor(pokemon: Pokemon, targets: BattlerIndex[], move: PokemonMove, followUp: boolean = false, ignorePp: boolean = false) {
|
||||
|
||||
constructor(pokemon: Pokemon, targets: BattlerIndex[], move: PokemonMove, followUp: boolean = false, ignorePp: boolean = false, forcedLast: boolean = false) {
|
||||
super();
|
||||
|
||||
this.pokemon = pokemon;
|
||||
|
@ -95,6 +97,7 @@ export class MovePhase extends BattlePhase {
|
|||
this.move = move;
|
||||
this.followUp = followUp;
|
||||
this.ignorePp = ignorePp;
|
||||
this.forcedLast = forcedLast;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -116,6 +119,15 @@ export class MovePhase extends BattlePhase {
|
|||
this.cancelled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows whether the current move has been forced to the end of the turn
|
||||
* Needed for speed order, see {@linkcode Moves.QUASH}
|
||||
* */
|
||||
public isForcedLast(): boolean {
|
||||
return this.forcedLast;
|
||||
}
|
||||
|
||||
|
||||
public start(): void {
|
||||
super.start();
|
||||
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
import { Species } from "#enums/species";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Abilities } from "#app/enums/abilities";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { describe, beforeAll, afterEach, beforeEach, it, expect } from "vitest";
|
||||
|
||||
describe("Moves - Quash", () => {
|
||||
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("double")
|
||||
.enemyLevel(1)
|
||||
.enemySpecies(Species.SLOWPOKE)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset([ Moves.RAIN_DANCE, Moves.SPLASH ])
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.moveset([ Moves.QUASH, Moves.SUNNY_DAY, Moves.RAIN_DANCE, Moves.SPLASH ]);
|
||||
});
|
||||
|
||||
it("makes the target move last in a turn, ignoring priority", async () => {
|
||||
await game.classicMode.startBattle([ Species.ACCELGOR, Species.RATTATA ]);
|
||||
|
||||
game.move.select(Moves.QUASH, 0, BattlerIndex.PLAYER_2);
|
||||
game.move.select(Moves.SUNNY_DAY, 1);
|
||||
await game.forceEnemyMove(Moves.SPLASH);
|
||||
await game.forceEnemyMove(Moves.RAIN_DANCE);
|
||||
|
||||
await game.phaseInterceptor.to("TurnEndPhase", false);
|
||||
// will be sunny if player_2 moved last because of quash, rainy otherwise
|
||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SUNNY);
|
||||
});
|
||||
|
||||
it("fails if the target has already moved", async () => {
|
||||
await game.classicMode.startBattle([ Species.ACCELGOR, Species.RATTATA ]);
|
||||
game.move.select(Moves.SPLASH, 0);
|
||||
game.move.select(Moves.QUASH, 1, BattlerIndex.PLAYER);
|
||||
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
|
||||
expect(game.scene.getPlayerField()[1].getLastXMoves(1)[0].result).toBe(MoveResult.FAIL);
|
||||
});
|
||||
|
||||
it("makes multiple quashed targets move in speed order at the end of the turn", async () => {
|
||||
game.override.enemySpecies(Species.NINJASK)
|
||||
.enemyLevel(100);
|
||||
|
||||
await game.classicMode.startBattle([ Species.ACCELGOR, Species.RATTATA ]);
|
||||
|
||||
// both users are quashed - rattata is slower so sun should be up at end of turn
|
||||
game.move.select(Moves.RAIN_DANCE, 0);
|
||||
game.move.select(Moves.SUNNY_DAY, 1);
|
||||
|
||||
await game.forceEnemyMove(Moves.QUASH, BattlerIndex.PLAYER);
|
||||
await game.forceEnemyMove(Moves.QUASH, BattlerIndex.PLAYER_2);
|
||||
|
||||
await game.phaseInterceptor.to("TurnEndPhase", false);
|
||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SUNNY);
|
||||
});
|
||||
|
||||
it("respects trick room", async () => {
|
||||
game.override.enemyMoveset([ Moves.RAIN_DANCE, Moves.SPLASH, Moves.TRICK_ROOM ]);
|
||||
|
||||
await game.classicMode.startBattle([ Species.ACCELGOR, Species.RATTATA ]);
|
||||
game.move.select(Moves.SPLASH, 0);
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
|
||||
await game.forceEnemyMove(Moves.TRICK_ROOM);
|
||||
await game.forceEnemyMove(Moves.SPLASH);
|
||||
await game.phaseInterceptor.to("TurnInitPhase");
|
||||
// both users are quashed - accelgor should move last w/ TR so rain should be up at end of turn
|
||||
game.move.select(Moves.RAIN_DANCE, 0);
|
||||
game.move.select(Moves.SUNNY_DAY, 1);
|
||||
|
||||
await game.forceEnemyMove(Moves.QUASH, BattlerIndex.PLAYER);
|
||||
await game.forceEnemyMove(Moves.QUASH, BattlerIndex.PLAYER_2);
|
||||
|
||||
await game.phaseInterceptor.to("TurnEndPhase", false);
|
||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.RAIN);
|
||||
});
|
||||
|
||||
});
|
|
@ -102,7 +102,7 @@ const timedEvents: TimedEvent[] = [
|
|||
endDate: new Date(Date.UTC(2025, 1, 3, 0)),
|
||||
bannerKey: "yearofthesnakeevent-",
|
||||
scale: 0.21,
|
||||
availableLangs: [],
|
||||
availableLangs: [ "en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN" ],
|
||||
eventEncounters: [
|
||||
{ species: Species.EKANS },
|
||||
{ species: Species.ONIX },
|
||||
|
|
Loading…
Reference in New Issue