mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2024-12-11 08:07:15 +00:00
acb2b66be4
* add .github/workflows/mystery-event.yml * update mystery-event.yml * mystery encounters: resolve review comments: Lost at Sea: -fix typo in handlePokemonGuidingYouPhase function Mysterious Chest: - remove obsolete commented code mystery-encounter.ts - remove unused `onDone` field from MysteryEncounterBuilder * fix typo in CanLearnMoveRequirementOptions * remove redundance from Pokemon.isAllowedInBattle() * chore: jsdoc formatting * fix lost-at-sea tests * add fallback for biomeMysteryEncounters if empty * lost-at-sea-encounter: fix and extend tests * move "battle:fainted" into `koPlayerPokemon` * add retries to quick-draw tests * fix lost-at-sea-encounter tests * clean up battle animation logic * Update and rename mystery-event.yml to mystery-events.yml * Update mystery-events.yml * Fix typo * Update mystery-events.yml Fix debug runs * clean up unit tests and utils * attach github issues to all encounter jsdocs * start dialogue refactor * update sleeping snorlax encounter * migrate encounters dialogue to new format * cleanup and add jsdocs * finish fiery fallout encounter * fix unit test breaks * add skeleton tests to fiery fallout * commit latest test changes * finish unit tests for fiery fallout * bug fix for empty modifier shop * stash working changes * stash changes * Update src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * Update src/test/utils/overridesHelper.ts Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * Update src/test/utils/overridesHelper.ts Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * Update src/test/utils/overridesHelper.ts Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * Update src/test/utils/overridesHelper.ts Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * Update src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * Update src/data/battle-anims.ts Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * nit updates and cleanup * Update src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * add jsdocs and more cleanup * add more jsdoc * add the strong stuff encounter * add the strong stuff encounter and more unit tests * cleanup container length checks in ME ui * add retries to tests * add retries to tests * fix trainer wave disable override * add shuckle juice modifier * add dialogue bug fixes * add dialogue bug fixes * add pokemon salesman encounter and affects pokedex UI display * add unit tests for pokemon salesman * temp stash * add offer you can't refuse * add unit tests for offer you can't refuse encounter * remove unnecessary prompt handlers * add tests for disabled encounter options * add delibird-y encounter * add delibird-y encounter * add absolute avarice encounter * finish absolute avarice encounter * add unit tests and enhancements for item overrides in tests * fix unit test * cleanup absolute avarice PR * small bug fixes with latest sync from main * update visuals loading for safari and stat trainer visuals * update visuals loading for safari and stat trainer visuals * update a trainer's test encounter and add unit tests * add Trash to Treasure encounter * clean up trash to treasure encounter * clean up trash to treasure encounter * add berries abound encounter * start clowning around encounter * first implementation pass at clowning around * add unit tests for clowning around * add unit tests for clowning around * clean up ME unit tests * clean up unit tests * update unit tests * add part timer and dancing lessons encounters * add unit tests for Dancing Lessons and Part-Timer * reordered biome list and adjusted redirection for project and labels * Add Weird Dream encounter and slight reworks to Berries Abound/Fight or Flight * adjusting yml to match new labels * fix yml whoopsie * Expanded 'Weird Dream' banlist and fixed a bug with the BST bump range * adds Winstrate Challenge mystery encounter * small cleanup for winstrates * add unit tests for Winstrate Challenge * fix pokemon not returning after winstrate battle * commit latest beta merge updates * fix ME null checks and unit tests with beta update * fix ME null checks and unit tests with beta update * MEs to pokerogue beta branch * test dialogue changes * test patch fix * test patch fix * test patch fix * adds teleporting hijinks encounter * add unit tests for Teleporting Hijinks * small change to teleporting hijinks dialogue * migrate ME translations to json * add retries to berries-abound.Option1: should reward the player with X berries based on wave * add missing ME dialogue back in * revert template changes * add ME unique trainer dialogue to both dialogue jsons * fix hanging comma in json * fix broken imports * resolve lint issues * fix flaky test * balance tweaks to a few MEs, updates to bug superfan * add unit tests for Bug-Type Superfan and clean up dialogue * Adds Fun and Games mystery encounter * add unit tests for Fun and Games encounter * update jsdoc * small ME balance changes * small ME balance changes * Adds Uncommon Breed ME and misc. ME bug fixes * Update getFinalSessionData() to collect Mystery Encounter data * adds GTS encounter * various ME bug fixes and balance changes * latest ME bug fixes * clean up GTS Encounter and add unit tests * small cleanup to MEs branch * add BGM music names for ME music * bug fixes and balance changes for MEs * ME data schema updates * balance changes and bug fixes to MEs * balance changes and bug fixes to MEs * update tests for MEs * add jsdoc to party exp function * dialogue updates and test fixes for MEs * dialogue updates and test fixes for MEs * PR suggestions and fixees * stash PR feedback and bugfixes * fix all tests for MEs and cleanup * PR feedback * update flaky ME test * update tests, bug fix MEs, and sprite assets * remove unintentional console log * re-enable stubbed function for Phaser text styling * handle undefined introVisuals properly * PR feedback from NightKev * disable Uncommon Breed tests * locales updates and bug fixes for safari zone * more PR feedback and update field trip with Rarer Candy * fix unit test * Change how reroll button gets disabled in Modifier Shop Phase * update continue button text logic * Update src/ui/modifier-select-ui-handler.ts Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * fix money formatting and some nits * more nits * more nits * update ME tsdocs with links * update ME tsdocs with links --------- Co-authored-by: Felix Staud <felix.staud@headwire.com> Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> Co-authored-by: ImperialSympathizer <imperialsympathizer@gmail.com> Co-authored-by: InnocentGameDev <asdargmng@gmail.com> Co-authored-by: Mumble <171087428+frutescens@users.noreply.github.com>
3176 lines
96 KiB
TypeScript
3176 lines
96 KiB
TypeScript
import * as ModifierTypes from "./modifier-type";
|
|
import BattleScene from "../battle-scene";
|
|
import { getLevelTotalExp } from "../data/exp";
|
|
import { MAX_PER_TYPE_POKEBALLS, PokeballType } from "../data/pokeball";
|
|
import Pokemon, { PlayerPokemon } from "../field/pokemon";
|
|
import { addTextObject, TextStyle } from "../ui/text";
|
|
import { Type } from "../data/type";
|
|
import { EvolutionPhase } from "../phases/evolution-phase";
|
|
import { FusionSpeciesFormEvolution, pokemonEvolutions, pokemonPrevolutions } from "../data/pokemon-evolutions";
|
|
import { getPokemonNameWithAffix } from "../messages";
|
|
import * as Utils from "../utils";
|
|
import { getBerryEffectFunc, getBerryPredicate } from "../data/berry";
|
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
|
import { BerryType } from "#enums/berry-type";
|
|
import { StatusEffect, getStatusEffectHealText } from "../data/status-effect";
|
|
import { achvs } from "../system/achv";
|
|
import { VoucherType } from "../system/voucher";
|
|
import { FormChangeItem, SpeciesFormChangeItemTrigger, SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeTeraTrigger } from "../data/pokemon-forms";
|
|
import { Nature } from "#app/data/nature";
|
|
import Overrides from "#app/overrides";
|
|
import { ModifierType, modifierTypes } from "./modifier-type";
|
|
import { Command } from "#app/ui/command-ui-handler";
|
|
import { Species } from "#enums/species";
|
|
import { Stat, type PermanentStat, type TempBattleStat, BATTLE_STATS, TEMP_BATTLE_STATS } from "#app/enums/stat";
|
|
import i18next from "i18next";
|
|
|
|
import { allMoves } from "#app/data/move";
|
|
import { Abilities } from "#app/enums/abilities";
|
|
import { LearnMovePhase } from "#app/phases/learn-move-phase";
|
|
import { LevelUpPhase } from "#app/phases/level-up-phase";
|
|
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
|
|
import { SpeciesFormKey } from "#app/data/pokemon-species";
|
|
|
|
export type ModifierPredicate = (modifier: Modifier) => boolean;
|
|
|
|
const iconOverflowIndex = 24;
|
|
|
|
export const modifierSortFunc = (a: Modifier, b: Modifier): number => {
|
|
const itemNameMatch = a.type.name.localeCompare(b.type.name);
|
|
const typeNameMatch = a.constructor.name.localeCompare(b.constructor.name);
|
|
const aId = a instanceof PokemonHeldItemModifier && a.pokemonId ? a.pokemonId : 4294967295;
|
|
const bId = b instanceof PokemonHeldItemModifier && b.pokemonId ? b.pokemonId : 4294967295;
|
|
|
|
//First sort by pokemonID
|
|
if (aId < bId) {
|
|
return 1;
|
|
} else if (aId > bId) {
|
|
return -1;
|
|
} else if (aId === bId) {
|
|
//Then sort by item type
|
|
if (typeNameMatch === 0) {
|
|
return itemNameMatch;
|
|
//Finally sort by item name
|
|
} else {
|
|
return typeNameMatch;
|
|
}
|
|
} else {
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
export class ModifierBar extends Phaser.GameObjects.Container {
|
|
private player: boolean;
|
|
private modifierCache: PersistentModifier[];
|
|
|
|
constructor(scene: BattleScene, enemy?: boolean) {
|
|
super(scene, 1 + (enemy ? 302 : 0), 2);
|
|
|
|
this.player = !enemy;
|
|
this.setScale(0.5);
|
|
}
|
|
|
|
/**
|
|
* Method to update content displayed in {@linkcode ModifierBar}
|
|
* @param {PersistentModifier[]} modifiers - The list of modifiers to be displayed in the {@linkcode ModifierBar}
|
|
* @param {boolean} hideHeldItems - If set to "true", only modifiers not assigned to a Pokémon are displayed
|
|
*/
|
|
updateModifiers(modifiers: PersistentModifier[], hideHeldItems: boolean = false) {
|
|
this.removeAll(true);
|
|
|
|
const visibleIconModifiers = modifiers.filter(m => m.isIconVisible(this.scene as BattleScene));
|
|
const nonPokemonSpecificModifiers = visibleIconModifiers.filter(m => !(m as PokemonHeldItemModifier).pokemonId).sort(modifierSortFunc);
|
|
const pokemonSpecificModifiers = visibleIconModifiers.filter(m => (m as PokemonHeldItemModifier).pokemonId).sort(modifierSortFunc);
|
|
|
|
const sortedVisibleIconModifiers = hideHeldItems ? nonPokemonSpecificModifiers : nonPokemonSpecificModifiers.concat(pokemonSpecificModifiers);
|
|
|
|
const thisArg = this;
|
|
|
|
sortedVisibleIconModifiers.forEach((modifier: PersistentModifier, i: integer) => {
|
|
const icon = modifier.getIcon(this.scene as BattleScene);
|
|
if (i >= iconOverflowIndex) {
|
|
icon.setVisible(false);
|
|
}
|
|
this.add(icon);
|
|
this.setModifierIconPosition(icon, sortedVisibleIconModifiers.length);
|
|
icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 24), Phaser.Geom.Rectangle.Contains);
|
|
icon.on("pointerover", () => {
|
|
(this.scene as BattleScene).ui.showTooltip(modifier.type.name, modifier.type.getDescription(this.scene as BattleScene));
|
|
if (this.modifierCache && this.modifierCache.length > iconOverflowIndex) {
|
|
thisArg.updateModifierOverflowVisibility(true);
|
|
}
|
|
});
|
|
icon.on("pointerout", () => {
|
|
(this.scene as BattleScene).ui.hideTooltip();
|
|
if (this.modifierCache && this.modifierCache.length > iconOverflowIndex) {
|
|
thisArg.updateModifierOverflowVisibility(false);
|
|
}
|
|
});
|
|
});
|
|
|
|
for (const icon of this.getAll()) {
|
|
this.sendToBack(icon);
|
|
}
|
|
|
|
this.modifierCache = modifiers;
|
|
}
|
|
|
|
updateModifierOverflowVisibility(ignoreLimit: boolean) {
|
|
const modifierIcons = this.getAll().reverse();
|
|
for (const modifier of modifierIcons.map(m => m as Phaser.GameObjects.Container).slice(iconOverflowIndex)) {
|
|
modifier.setVisible(ignoreLimit);
|
|
}
|
|
}
|
|
|
|
setModifierIconPosition(icon: Phaser.GameObjects.Container, modifierCount: integer) {
|
|
const rowIcons: integer = 12 + 6 * Math.max((Math.ceil(Math.min(modifierCount, 24) / 12) - 2), 0);
|
|
|
|
const x = (this.getIndex(icon) % rowIcons) * 26 / (rowIcons / 12);
|
|
const y = Math.floor(this.getIndex(icon) / rowIcons) * 20;
|
|
|
|
icon.setPosition(this.player ? x : -x, y);
|
|
}
|
|
}
|
|
|
|
export abstract class Modifier {
|
|
public type: ModifierType;
|
|
|
|
constructor(type: ModifierType) {
|
|
this.type = type;
|
|
}
|
|
|
|
match(_modifier: Modifier): boolean {
|
|
return false;
|
|
}
|
|
|
|
shouldApply(_args: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
abstract apply(args: any[]): boolean | Promise<boolean>;
|
|
}
|
|
|
|
export abstract class PersistentModifier extends Modifier {
|
|
public stackCount: integer;
|
|
public virtualStackCount: integer;
|
|
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type);
|
|
this.stackCount = stackCount === undefined ? 1 : stackCount;
|
|
this.virtualStackCount = 0;
|
|
}
|
|
|
|
add(modifiers: PersistentModifier[], virtual: boolean, scene: BattleScene): boolean {
|
|
for (const modifier of modifiers) {
|
|
if (this.match(modifier)) {
|
|
return modifier.incrementStack(scene, this.stackCount, virtual);
|
|
}
|
|
}
|
|
|
|
if (virtual) {
|
|
this.virtualStackCount += this.stackCount;
|
|
this.stackCount = 0;
|
|
}
|
|
modifiers.push(this);
|
|
return true;
|
|
}
|
|
|
|
abstract clone(): PersistentModifier;
|
|
|
|
getArgs(): any[] {
|
|
return [];
|
|
}
|
|
|
|
incrementStack(scene: BattleScene, amount: integer, virtual: boolean): boolean {
|
|
if (this.getStackCount() + amount <= this.getMaxStackCount(scene)) {
|
|
if (!virtual) {
|
|
this.stackCount += amount;
|
|
} else {
|
|
this.virtualStackCount += amount;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
getStackCount(): integer {
|
|
return this.stackCount + this.virtualStackCount;
|
|
}
|
|
|
|
abstract getMaxStackCount(scene: BattleScene, forThreshold?: boolean): integer;
|
|
|
|
isIconVisible(scene: BattleScene): boolean {
|
|
return true;
|
|
}
|
|
|
|
getIcon(scene: BattleScene, forSummary?: boolean): Phaser.GameObjects.Container {
|
|
const container = scene.add.container(0, 0);
|
|
|
|
const item = scene.add.sprite(0, 12, "items");
|
|
item.setFrame(this.type.iconImage);
|
|
item.setOrigin(0, 0.5);
|
|
container.add(item);
|
|
|
|
const stackText = this.getIconStackText(scene);
|
|
if (stackText) {
|
|
container.add(stackText);
|
|
}
|
|
|
|
const virtualStackText = this.getIconStackText(scene, true);
|
|
if (virtualStackText) {
|
|
container.add(virtualStackText);
|
|
}
|
|
|
|
return container;
|
|
}
|
|
|
|
getIconStackText(scene: BattleScene, virtual?: boolean): Phaser.GameObjects.BitmapText | null {
|
|
if (this.getMaxStackCount(scene) === 1 || (virtual && !this.virtualStackCount)) {
|
|
return null;
|
|
}
|
|
|
|
const text = scene.add.bitmapText(10, 15, "item-count", this.stackCount.toString(), 11);
|
|
text.letterSpacing = -0.5;
|
|
if (this.getStackCount() >= this.getMaxStackCount(scene)) {
|
|
text.setTint(0xf89890);
|
|
}
|
|
text.setOrigin(0, 0);
|
|
|
|
return text;
|
|
}
|
|
}
|
|
|
|
export abstract class ConsumableModifier extends Modifier {
|
|
constructor(type: ModifierType) {
|
|
super(type);
|
|
}
|
|
|
|
add(_modifiers: Modifier[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
shouldApply(args: any[]): boolean {
|
|
return super.shouldApply(args) && args.length === 1 && args[0] instanceof BattleScene;
|
|
}
|
|
}
|
|
|
|
export class AddPokeballModifier extends ConsumableModifier {
|
|
private pokeballType: PokeballType;
|
|
private count: integer;
|
|
|
|
constructor(type: ModifierType, pokeballType: PokeballType, count: integer) {
|
|
super(type);
|
|
|
|
this.pokeballType = pokeballType;
|
|
this.count = count;
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokeballCounts = (args[0] as BattleScene).pokeballCounts;
|
|
pokeballCounts[this.pokeballType] = Math.min(pokeballCounts[this.pokeballType] + this.count, MAX_PER_TYPE_POKEBALLS);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class AddVoucherModifier extends ConsumableModifier {
|
|
private voucherType: VoucherType;
|
|
private count: integer;
|
|
|
|
constructor(type: ModifierType, voucherType: VoucherType, count: integer) {
|
|
super(type);
|
|
|
|
this.voucherType = voucherType;
|
|
this.count = count;
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const voucherCounts = (args[0] as BattleScene).gameData.voucherCounts;
|
|
voucherCounts[this.voucherType] += this.count;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modifier used for party-wide or passive items that start an initial
|
|
* {@linkcode battleCount} equal to {@linkcode maxBattles} that, for every
|
|
* battle, decrements. Typically, when {@linkcode battleCount} reaches 0, the
|
|
* modifier will be removed. If a modifier of the same type is to be added, it
|
|
* will reset {@linkcode battleCount} back to {@linkcode maxBattles} of the
|
|
* existing modifier instead of adding that modifier directly.
|
|
* @extends PersistentModifier
|
|
* @abstract
|
|
* @see {@linkcode add}
|
|
*/
|
|
export abstract class LapsingPersistentModifier extends PersistentModifier {
|
|
/** The maximum amount of battles the modifier will exist for */
|
|
private maxBattles: number;
|
|
/** The current amount of battles the modifier will exist for */
|
|
private battleCount: number;
|
|
|
|
constructor(type: ModifierTypes.ModifierType, maxBattles: number, battleCount?: number, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
|
|
this.maxBattles = maxBattles;
|
|
this.battleCount = battleCount ?? this.maxBattles;
|
|
}
|
|
|
|
/**
|
|
* Goes through existing modifiers for any that match the selected modifier,
|
|
* which will then either add it to the existing modifiers if none were found
|
|
* or, if one was found, it will refresh {@linkcode battleCount}.
|
|
* @param modifiers {@linkcode PersistentModifier} array of the player's modifiers
|
|
* @param _virtual N/A
|
|
* @param _scene N/A
|
|
* @returns true if the modifier was successfully added or applied, false otherwise
|
|
*/
|
|
add(modifiers: PersistentModifier[], _virtual: boolean, scene: BattleScene): boolean {
|
|
for (const modifier of modifiers) {
|
|
if (this.match(modifier)) {
|
|
const modifierInstance = modifier as LapsingPersistentModifier;
|
|
if (modifierInstance.getBattleCount() < modifierInstance.getMaxBattles()) {
|
|
modifierInstance.resetBattleCount();
|
|
scene.playSound("se/restore");
|
|
return true;
|
|
}
|
|
// should never get here
|
|
return false;
|
|
}
|
|
}
|
|
|
|
modifiers.push(this);
|
|
return true;
|
|
}
|
|
|
|
lapse(_args: any[]): boolean {
|
|
this.battleCount--;
|
|
return this.battleCount > 0;
|
|
}
|
|
|
|
getIcon(scene: BattleScene): Phaser.GameObjects.Container {
|
|
const container = super.getIcon(scene);
|
|
|
|
// Linear interpolation on hue
|
|
const hue = Math.floor(120 * (this.battleCount / this.maxBattles) + 5);
|
|
|
|
// Generates the color hex code with a constant saturation and lightness but varying hue
|
|
const typeHex = Utils.hslToHex(hue, 0.50, 0.90);
|
|
const strokeHex = Utils.hslToHex(hue, 0.70, 0.30);
|
|
|
|
const battleCountText = addTextObject(scene, 27, 0, this.battleCount.toString(), TextStyle.PARTY, { fontSize: "66px", color: typeHex });
|
|
battleCountText.setShadow(0, 0);
|
|
battleCountText.setStroke(strokeHex, 16);
|
|
battleCountText.setOrigin(1, 0);
|
|
container.add(battleCountText);
|
|
|
|
return container;
|
|
}
|
|
|
|
getIconStackText(_scene: BattleScene, _virtual?: boolean): Phaser.GameObjects.BitmapText | null {
|
|
return null;
|
|
}
|
|
|
|
getBattleCount(): number {
|
|
return this.battleCount;
|
|
}
|
|
|
|
resetBattleCount(): void {
|
|
this.battleCount = this.maxBattles;
|
|
}
|
|
|
|
getMaxBattles(): number {
|
|
return this.maxBattles;
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return [ this.maxBattles, this.battleCount ];
|
|
}
|
|
|
|
getMaxStackCount(_scene: BattleScene, _forThreshold?: boolean): number {
|
|
// Must be an abitrary number greater than 1
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modifier used for passive items, specifically lures, that
|
|
* temporarily increases the chance of a double battle.
|
|
* @extends LapsingPersistentModifier
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class DoubleBattleChanceBoosterModifier extends LapsingPersistentModifier {
|
|
constructor(type: ModifierType, maxBattles:number, battleCount?: number, stackCount?: integer) {
|
|
super(type, maxBattles, battleCount, stackCount);
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return (modifier instanceof DoubleBattleChanceBoosterModifier) && (modifier.getMaxBattles() === this.getMaxBattles());
|
|
}
|
|
|
|
clone(): DoubleBattleChanceBoosterModifier {
|
|
return new DoubleBattleChanceBoosterModifier(this.type as ModifierTypes.DoubleBattleChanceBoosterModifierType, this.getMaxBattles(), this.getBattleCount(), this.stackCount);
|
|
}
|
|
|
|
/**
|
|
* Modifies the chance of a double battle occurring
|
|
* @param args [0] {@linkcode Utils.NumberHolder} for double battle chance
|
|
* @returns true if the modifier was applied
|
|
*/
|
|
apply(args: any[]): boolean {
|
|
const doubleBattleChance = args[0] as Utils.NumberHolder;
|
|
// This is divided because the chance is generated as a number from 0 to doubleBattleChance.value using Utils.randSeedInt
|
|
// A double battle will initiate if the generated number is 0
|
|
doubleBattleChance.value = Math.ceil(doubleBattleChance.value / 4);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modifier used for party-wide items, specifically the X items, that
|
|
* temporarily increases the stat stage multiplier of the corresponding
|
|
* {@linkcode TempBattleStat}.
|
|
* @extends LapsingPersistentModifier
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class TempStatStageBoosterModifier extends LapsingPersistentModifier {
|
|
/** The stat whose stat stage multiplier will be temporarily increased */
|
|
private stat: TempBattleStat;
|
|
/** The amount by which the stat stage itself or its multiplier will be increased by */
|
|
private boost: number;
|
|
|
|
constructor(type: ModifierType, stat: TempBattleStat, maxBattles: number, battleCount?: number, stackCount?: number) {
|
|
super(type, maxBattles, battleCount, stackCount);
|
|
|
|
this.stat = stat;
|
|
// Note that, because we want X Accuracy to maintain its original behavior,
|
|
// it will increment as it did previously, directly to the stat stage.
|
|
this.boost = (stat !== Stat.ACC) ? 0.3 : 1;
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
if (modifier instanceof TempStatStageBoosterModifier) {
|
|
const modifierInstance = modifier as TempStatStageBoosterModifier;
|
|
return (modifierInstance.stat === this.stat);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
clone() {
|
|
return new TempStatStageBoosterModifier(this.type, this.stat, this.getMaxBattles(), this.getBattleCount(), this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return [ this.stat, ...super.getArgs() ];
|
|
}
|
|
|
|
/**
|
|
* Checks if {@linkcode args} contains the necessary elements and if the
|
|
* incoming stat is matches {@linkcode stat}.
|
|
* @param args [0] {@linkcode TempBattleStat} being checked at the time
|
|
* [1] {@linkcode Utils.NumberHolder} N/A
|
|
* @returns true if the modifier can be applied, false otherwise
|
|
*/
|
|
shouldApply(args: any[]): boolean {
|
|
return args && (args.length === 2) && TEMP_BATTLE_STATS.includes(args[0]) && (args[0] === this.stat) && (args[1] instanceof Utils.NumberHolder);
|
|
}
|
|
|
|
/**
|
|
* Increases the incoming stat stage matching {@linkcode stat} by {@linkcode boost}.
|
|
* @param args [0] {@linkcode TempBattleStat} N/A
|
|
* [1] {@linkcode Utils.NumberHolder} that holds the resulting value of the stat stage multiplier
|
|
*/
|
|
apply(args: any[]): boolean {
|
|
(args[1] as Utils.NumberHolder).value += this.boost;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modifier used for party-wide items, namely Dire Hit, that
|
|
* temporarily increments the critical-hit stage
|
|
* @extends LapsingPersistentModifier
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class TempCritBoosterModifier extends LapsingPersistentModifier {
|
|
constructor(type: ModifierType, maxBattles: number, battleCount?: number, stackCount?: number) {
|
|
super(type, maxBattles, battleCount, stackCount);
|
|
}
|
|
|
|
clone() {
|
|
return new TempCritBoosterModifier(this.type, this.getMaxBattles(), this.getBattleCount(), this.stackCount);
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return (modifier instanceof TempCritBoosterModifier);
|
|
}
|
|
|
|
/**
|
|
* Checks if {@linkcode args} contains the necessary elements.
|
|
* @param args [1] {@linkcode Utils.NumberHolder} N/A
|
|
* @returns true if the critical-hit stage boost applies successfully
|
|
*/
|
|
shouldApply(args: any[]): boolean {
|
|
return args && (args.length === 1) && (args[0] instanceof Utils.NumberHolder);
|
|
}
|
|
|
|
/**
|
|
* Increases the current critical-hit stage value by 1.
|
|
* @param args [0] {@linkcode Utils.IntegerHolder} that holds the resulting critical-hit level
|
|
* @returns true if the critical-hit stage boost applies successfully
|
|
*/
|
|
apply(args: any[]): boolean {
|
|
(args[0] as Utils.NumberHolder).value++;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class MapModifier extends PersistentModifier {
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
}
|
|
|
|
clone(): MapModifier {
|
|
return new MapModifier(this.type, this.stackCount);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
export class MegaEvolutionAccessModifier extends PersistentModifier {
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
}
|
|
|
|
clone(): MegaEvolutionAccessModifier {
|
|
return new MegaEvolutionAccessModifier(this.type, this.stackCount);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
export class GigantamaxAccessModifier extends PersistentModifier {
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
}
|
|
|
|
clone(): GigantamaxAccessModifier {
|
|
return new GigantamaxAccessModifier(this.type, this.stackCount);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
export class TerastallizeAccessModifier extends PersistentModifier {
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
}
|
|
|
|
clone(): TerastallizeAccessModifier {
|
|
return new TerastallizeAccessModifier(this.type, this.stackCount);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
export abstract class PokemonHeldItemModifier extends PersistentModifier {
|
|
public pokemonId: integer;
|
|
readonly isTransferrable: boolean = true;
|
|
|
|
constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
|
|
this.pokemonId = pokemonId;
|
|
}
|
|
|
|
abstract matchType(_modifier: Modifier): boolean;
|
|
|
|
match(modifier: Modifier) {
|
|
return this.matchType(modifier) && (modifier as PokemonHeldItemModifier).pokemonId === this.pokemonId;
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return [ this.pokemonId ];
|
|
}
|
|
|
|
shouldApply(args: any[]): boolean {
|
|
return super.shouldApply(args) && args.length !== 0 && args[0] instanceof Pokemon && (this.pokemonId === -1 || (args[0] as Pokemon).id === this.pokemonId);
|
|
}
|
|
|
|
isIconVisible(scene: BattleScene): boolean {
|
|
return !!(this.getPokemon(scene)?.isOnField());
|
|
}
|
|
|
|
getIcon(scene: BattleScene, forSummary?: boolean): Phaser.GameObjects.Container {
|
|
const container = !forSummary ? scene.add.container(0, 0) : super.getIcon(scene);
|
|
|
|
if (!forSummary) {
|
|
const pokemon = this.getPokemon(scene);
|
|
if (pokemon) {
|
|
const pokemonIcon = scene.addPokemonIcon(pokemon, -2, 10, 0, 0.5);
|
|
container.add(pokemonIcon);
|
|
container.setName(pokemon.id.toString());
|
|
}
|
|
|
|
const item = scene.add.sprite(16, this.virtualStackCount ? 8 : 16, "items");
|
|
item.setScale(0.5);
|
|
item.setOrigin(0, 0.5);
|
|
item.setTexture("items", this.type.iconImage);
|
|
container.add(item);
|
|
|
|
const stackText = this.getIconStackText(scene);
|
|
if (stackText) {
|
|
container.add(stackText);
|
|
}
|
|
|
|
const virtualStackText = this.getIconStackText(scene, true);
|
|
if (virtualStackText) {
|
|
container.add(virtualStackText);
|
|
}
|
|
} else {
|
|
container.setScale(0.5);
|
|
}
|
|
|
|
return container;
|
|
}
|
|
|
|
getPokemon(scene: BattleScene): Pokemon | undefined {
|
|
return this.pokemonId ? scene.getPokemonById(this.pokemonId) ?? undefined : undefined;
|
|
}
|
|
|
|
getScoreMultiplier(): number {
|
|
return 1;
|
|
}
|
|
|
|
//Applies to items with chance of activating secondary effects ie Kings Rock
|
|
getSecondaryChanceMultiplier(pokemon: Pokemon): integer {
|
|
// Temporary quickfix to stop game from freezing when the opponet uses u-turn while holding on to king's rock
|
|
if (!pokemon.getLastXMoves(0)[0]) {
|
|
return 1;
|
|
}
|
|
const sheerForceAffected = allMoves[pokemon.getLastXMoves(0)[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): integer {
|
|
const pokemon = this.getPokemon(scene);
|
|
if (!pokemon) {
|
|
return 0;
|
|
}
|
|
if (pokemon.isPlayer() && forThreshold) {
|
|
return scene.getParty().map(p => this.getMaxHeldItemCount(p)).reduce((stackCount: integer, maxStackCount: integer) => Math.max(stackCount, maxStackCount), 0);
|
|
}
|
|
return this.getMaxHeldItemCount(pokemon);
|
|
}
|
|
|
|
abstract getMaxHeldItemCount(pokemon?: Pokemon): integer;
|
|
}
|
|
|
|
export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModifier {
|
|
protected battlesLeft: integer;
|
|
readonly isTransferrable: boolean = false;
|
|
|
|
constructor(type: ModifierTypes.ModifierType, pokemonId: integer, battlesLeft?: integer, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
|
|
this.battlesLeft = battlesLeft!; // TODO: is this bang correct?
|
|
}
|
|
|
|
lapse(args: any[]): boolean {
|
|
return !!--this.battlesLeft;
|
|
}
|
|
|
|
getIcon(scene: BattleScene, forSummary?: boolean): Phaser.GameObjects.Container {
|
|
const container = super.getIcon(scene, forSummary);
|
|
|
|
if (this.getPokemon(scene)?.isPlayer()) {
|
|
const battleCountText = addTextObject(scene, 27, 0, this.battlesLeft.toString(), TextStyle.PARTY, { fontSize: "66px", color: "#f89890" });
|
|
battleCountText.setShadow(0, 0);
|
|
battleCountText.setStroke("#984038", 16);
|
|
battleCountText.setOrigin(1, 0);
|
|
container.add(battleCountText);
|
|
}
|
|
|
|
return container;
|
|
}
|
|
|
|
getBattlesLeft(): integer {
|
|
return this.battlesLeft;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene, forThreshold?: boolean): number {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
export class TerastallizeModifier extends LapsingPokemonHeldItemModifier {
|
|
public teraType: Type;
|
|
readonly isTransferrable: boolean = false;
|
|
|
|
constructor(type: ModifierTypes.TerastallizeModifierType, pokemonId: integer, teraType: Type, battlesLeft?: integer, stackCount?: integer) {
|
|
super(type, pokemonId, battlesLeft || 10, stackCount);
|
|
|
|
this.teraType = teraType;
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
if (modifier instanceof TerastallizeModifier && modifier.teraType === this.teraType) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
clone(): TerastallizeModifier {
|
|
return new TerastallizeModifier(this.type as ModifierTypes.TerastallizeModifierType, this.pokemonId, this.teraType, this.battlesLeft, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return [ this.pokemonId, this.teraType, this.battlesLeft ];
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as Pokemon;
|
|
if (pokemon.isPlayer()) {
|
|
pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeTeraTrigger);
|
|
pokemon.scene.validateAchv(achvs.TERASTALLIZE);
|
|
if (this.teraType === Type.STELLAR) {
|
|
pokemon.scene.validateAchv(achvs.STELLAR_TERASTALLIZE);
|
|
}
|
|
}
|
|
pokemon.updateSpritePipelineData();
|
|
return true;
|
|
}
|
|
|
|
lapse(args: any[]): boolean {
|
|
const ret = super.lapse(args);
|
|
if (!ret) {
|
|
const pokemon = args[0] as Pokemon;
|
|
pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeLapseTeraTrigger);
|
|
pokemon.updateSpritePipelineData();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
getScoreMultiplier(): number {
|
|
return 1.25;
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modifier used for held items, specifically vitamins like Carbos, Hp Up, etc., that
|
|
* increase the value of a given {@linkcode PermanentStat}.
|
|
* @extends PokemonHeldItemModifier
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class BaseStatModifier extends PokemonHeldItemModifier {
|
|
protected stat: PermanentStat;
|
|
readonly isTransferrable: boolean = false;
|
|
|
|
constructor(type: ModifierType, pokemonId: integer, stat: PermanentStat, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
this.stat = stat;
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
if (modifier instanceof BaseStatModifier) {
|
|
return (modifier as BaseStatModifier).stat === this.stat;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
clone(): PersistentModifier {
|
|
return new BaseStatModifier(this.type, this.pokemonId, this.stat, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return super.getArgs().concat(this.stat);
|
|
}
|
|
|
|
shouldApply(args: any[]): boolean {
|
|
return super.shouldApply(args) && args.length === 2 && Array.isArray(args[1]);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const baseStats = args[1] as number[];
|
|
baseStats[this.stat] = Math.floor(baseStats[this.stat] * (1 + this.getStackCount() * 0.1));
|
|
return true;
|
|
}
|
|
|
|
getScoreMultiplier(): number {
|
|
return 1.1;
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return pokemon.ivs[this.stat];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Currently used by Shuckle Juice item
|
|
*/
|
|
export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
|
|
private statModifier: integer;
|
|
readonly isTransferrable: boolean = false;
|
|
|
|
constructor(type: ModifierTypes.PokemonBaseStatTotalModifierType, pokemonId: integer, statModifier: integer, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
this.statModifier = statModifier;
|
|
}
|
|
|
|
override matchType(modifier: Modifier): boolean {
|
|
return modifier instanceof PokemonBaseStatTotalModifier;
|
|
}
|
|
|
|
override clone(): PersistentModifier {
|
|
return new PokemonBaseStatTotalModifier(this.type as ModifierTypes.PokemonBaseStatTotalModifierType, this.pokemonId, this.statModifier, this.stackCount);
|
|
}
|
|
|
|
override getArgs(): any[] {
|
|
return super.getArgs().concat(this.statModifier);
|
|
}
|
|
|
|
override shouldApply(args: any[]): boolean {
|
|
return super.shouldApply(args) && args.length === 2 && args[1] instanceof Array;
|
|
}
|
|
|
|
override apply(args: any[]): boolean {
|
|
// Modifies the passed in baseStats[] array
|
|
args[1].forEach((v, i) => {
|
|
// HP is affected by half as much as other stats
|
|
const newVal = i === 0 ? Math.floor(v + this.statModifier / 2) : Math.floor(v + this.statModifier);
|
|
args[1][i] = Math.min(Math.max(newVal, 1), 999999);
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
override getScoreMultiplier(): number {
|
|
return 1.2;
|
|
}
|
|
|
|
override getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Currently used by Old Gateau item
|
|
*/
|
|
export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier {
|
|
private statModifier: integer;
|
|
private stats: Stat[];
|
|
readonly isTransferrable: boolean = false;
|
|
|
|
constructor (type: ModifierType, pokemonId: integer, statModifier: integer, stats: Stat[], stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
|
|
this.statModifier = statModifier;
|
|
this.stats = stats;
|
|
}
|
|
|
|
override matchType(modifier: Modifier): boolean {
|
|
return modifier instanceof PokemonBaseStatFlatModifier;
|
|
}
|
|
|
|
override clone(): PersistentModifier {
|
|
return new PokemonBaseStatFlatModifier(this.type, this.pokemonId, this.statModifier, this.stats, this.stackCount);
|
|
}
|
|
|
|
override getArgs(): any[] {
|
|
return super.getArgs().concat(this.statModifier, this.stats);
|
|
}
|
|
|
|
override shouldApply(args: any[]): boolean {
|
|
return super.shouldApply(args) && args.length === 2 && args[1] instanceof Array;
|
|
}
|
|
|
|
override apply(args: any[]): boolean {
|
|
// Modifies the passed in baseStats[] array by a flat value, only if the stat is specified in this.stats
|
|
args[1].forEach((v, i) => {
|
|
if (this.stats.includes(i)) {
|
|
const newVal = Math.floor(v + this.statModifier);
|
|
args[1][i] = Math.min(Math.max(newVal, 1), 999999);
|
|
}
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
override getScoreMultiplier(): number {
|
|
return 1.1;
|
|
}
|
|
|
|
override getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Currently used by Macho Brace item
|
|
*/
|
|
export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier {
|
|
readonly isTransferrable: boolean = false;
|
|
|
|
constructor (type: ModifierType, pokemonId: integer, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
return modifier instanceof PokemonIncrementingStatModifier;
|
|
}
|
|
|
|
clone(): PersistentModifier {
|
|
return new PokemonIncrementingStatModifier(this.type, this.pokemonId);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return super.getArgs();
|
|
}
|
|
|
|
shouldApply(args: any[]): boolean {
|
|
return super.shouldApply(args) && args.length === 2 && args[1] instanceof Array;
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
// Modifies the passed in stats[] array by +1 per stack for HP, +2 per stack for other stats
|
|
// If the Macho Brace is at max stacks (50), adds additional 5% to total HP and 10% to other stats
|
|
args[1].forEach((v, i) => {
|
|
const isHp = i === 0;
|
|
let mult = 1;
|
|
if (this.stackCount === this.getMaxHeldItemCount()) {
|
|
mult = isHp ? 1.05 : 1.1;
|
|
}
|
|
const newVal = Math.floor((v + this.stackCount * (isHp ? 1 : 2)) * mult);
|
|
args[1][i] = Math.min(Math.max(newVal, 1), 999999);
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
getScoreMultiplier(): number {
|
|
return 1.2;
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon?: Pokemon): integer {
|
|
return 50;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modifier used for held items that apply {@linkcode Stat} boost(s)
|
|
* using a multiplier.
|
|
* @extends PokemonHeldItemModifier
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class StatBoosterModifier extends PokemonHeldItemModifier {
|
|
/** The stats that the held item boosts */
|
|
protected stats: Stat[];
|
|
/** The multiplier used to increase the relevant stat(s) */
|
|
protected multiplier: number;
|
|
|
|
constructor(type: ModifierType, pokemonId: integer, stats: Stat[], multiplier: number, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
|
|
this.stats = stats;
|
|
this.multiplier = multiplier;
|
|
}
|
|
|
|
clone() {
|
|
return new StatBoosterModifier(this.type, this.pokemonId, this.stats, this.multiplier, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return [ ...super.getArgs(), this.stats, this.multiplier ];
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
if (modifier instanceof StatBoosterModifier) {
|
|
const modifierInstance = modifier as StatBoosterModifier;
|
|
if ((modifierInstance.multiplier === this.multiplier) && (modifierInstance.stats.length === this.stats.length)) {
|
|
return modifierInstance.stats.every((e, i) => e === this.stats[i]);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if the incoming stat is listed in {@linkcode stats}
|
|
* @param args [0] {@linkcode Pokemon} N/A
|
|
* [1] {@linkcode Stat} being checked at the time
|
|
* [2] {@linkcode Utils.NumberHolder} N/A
|
|
* @returns true if the stat could be boosted, false otherwise
|
|
*/
|
|
shouldApply(args: any[]): boolean {
|
|
return super.shouldApply(args) && this.stats.includes(args[1] as Stat);
|
|
}
|
|
|
|
/**
|
|
* Boosts the incoming stat by a {@linkcode multiplier} if the stat is listed
|
|
* in {@linkcode stats}.
|
|
* @param args [0] {@linkcode Pokemon} N/A
|
|
* [1] {@linkcode Stat} N/A
|
|
* [2] {@linkcode Utils.NumberHolder} that holds the resulting value of the stat
|
|
* @returns true if the stat boost applies successfully, false otherwise
|
|
* @see shouldApply
|
|
*/
|
|
apply(args: any[]): boolean {
|
|
const statValue = args[2] as Utils.NumberHolder;
|
|
|
|
statValue.value *= this.multiplier;
|
|
return true;
|
|
}
|
|
|
|
getMaxHeldItemCount(_pokemon: Pokemon): number {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modifier used for held items, specifically Eviolite, that apply
|
|
* {@linkcode Stat} boost(s) using a multiplier if the holder can evolve.
|
|
* @extends StatBoosterModifier
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class EvolutionStatBoosterModifier extends StatBoosterModifier {
|
|
clone() {
|
|
return super.clone() as EvolutionStatBoosterModifier;
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
return modifier instanceof EvolutionStatBoosterModifier;
|
|
}
|
|
|
|
/**
|
|
* Checks if the stat boosts can apply and if the holder is not currently
|
|
* Gigantamax'd.
|
|
* @param args [0] {@linkcode Pokemon} that holds the held item
|
|
* [1] {@linkcode Stat} N/A
|
|
* [2] {@linkcode Utils.NumberHolder} N/A
|
|
* @returns true if the stat boosts can be applied, false otherwise
|
|
*/
|
|
shouldApply(args: any[]): boolean {
|
|
return super.shouldApply(args) && ((args[0] as Pokemon).getFormKey() !== SpeciesFormKey.GIGANTAMAX);
|
|
}
|
|
|
|
/**
|
|
* Boosts the incoming stat value by a {@linkcode multiplier} if the holder
|
|
* can evolve. Note that, if the holder is a fusion, they will receive
|
|
* only half of the boost if either of the fused members are fully
|
|
* evolved. However, if they are both unevolved, the full boost
|
|
* will apply.
|
|
* @param args [0] {@linkcode Pokemon} that holds the held item
|
|
* [1] {@linkcode Stat} N/A
|
|
* [2] {@linkcode Utils.NumberHolder} that holds the resulting value of the stat
|
|
* @returns true if the stat boost applies successfully, false otherwise
|
|
* @see shouldApply
|
|
*/
|
|
apply(args: any[]): boolean {
|
|
const holder = args[0] as Pokemon;
|
|
const statValue = args[2] as Utils.NumberHolder;
|
|
const isUnevolved = holder.getSpeciesForm(true).speciesId in pokemonEvolutions;
|
|
|
|
if (holder.isFusion() && (holder.getFusionSpeciesForm(true).speciesId in pokemonEvolutions) !== isUnevolved) {
|
|
// Half boost applied if holder is fused and either part of fusion is fully evolved
|
|
statValue.value *= 1 + (this.multiplier - 1) / 2;
|
|
return true;
|
|
} else if (isUnevolved) {
|
|
// Full boost applied if holder is unfused and unevolved or, if fused, both parts of fusion are unevolved
|
|
return super.apply(args);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modifier used for held items that apply {@linkcode Stat} boost(s) using a
|
|
* multiplier if the holder is of a specific {@linkcode Species}.
|
|
* @extends StatBoosterModifier
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class SpeciesStatBoosterModifier extends StatBoosterModifier {
|
|
/** The species that the held item's stat boost(s) apply to */
|
|
private species: Species[];
|
|
|
|
constructor(type: ModifierType, pokemonId: integer, stats: Stat[], multiplier: number, species: Species[], stackCount?: integer) {
|
|
super(type, pokemonId, stats, multiplier, stackCount);
|
|
|
|
this.species = species;
|
|
}
|
|
|
|
clone() {
|
|
return new SpeciesStatBoosterModifier(this.type, this.pokemonId, this.stats, this.multiplier, this.species, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return [ ...super.getArgs(), this.species ];
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
if (modifier instanceof SpeciesStatBoosterModifier) {
|
|
const modifierInstance = modifier as SpeciesStatBoosterModifier;
|
|
if (modifierInstance.species.length === this.species.length) {
|
|
return super.matchType(modifier) && modifierInstance.species.every((e, i) => e === this.species[i]);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if the incoming stat is listed in {@linkcode stats} and if the holder's {@linkcode Species}
|
|
* (or its fused species) is listed in {@linkcode species}.
|
|
* @param args [0] {@linkcode Pokemon} that holds the held item
|
|
* [1] {@linkcode Stat} being checked at the time
|
|
* [2] {@linkcode Utils.NumberHolder} N/A
|
|
* @returns true if the stat could be boosted, false otherwise
|
|
*/
|
|
shouldApply(args: any[]): boolean {
|
|
const holder = args[0] as Pokemon;
|
|
return super.shouldApply(args) && (this.species.includes(holder.getSpeciesForm(true).speciesId) || (holder.isFusion() && this.species.includes(holder.getFusionSpeciesForm(true).speciesId)));
|
|
}
|
|
|
|
/**
|
|
* Checks if either parameter is included in the corresponding lists
|
|
* @param speciesId {@linkcode Species} being checked
|
|
* @param stat {@linkcode Stat} being checked
|
|
* @returns true if both parameters are in {@linkcode species} and {@linkcode stats} respectively, false otherwise
|
|
*/
|
|
contains(speciesId: Species, stat: Stat): boolean {
|
|
return this.species.includes(speciesId) && this.stats.includes(stat);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modifier used for held items that apply critical-hit stage boost(s).
|
|
* @extends PokemonHeldItemModifier
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class CritBoosterModifier extends PokemonHeldItemModifier {
|
|
/** The amount of stages by which the held item increases the current critical-hit stage value */
|
|
protected stageIncrement: number;
|
|
|
|
constructor(type: ModifierType, pokemonId: integer, stageIncrement: number, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
|
|
this.stageIncrement = stageIncrement;
|
|
}
|
|
|
|
clone() {
|
|
return new CritBoosterModifier(this.type, this.pokemonId, this.stageIncrement, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return super.getArgs().concat(this.stageIncrement);
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
if (modifier instanceof CritBoosterModifier) {
|
|
return (modifier as CritBoosterModifier).stageIncrement === this.stageIncrement;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Increases the current critical-hit stage value by {@linkcode stageIncrement}.
|
|
* @param args [0] {@linkcode Pokemon} N/A
|
|
* [1] {@linkcode Utils.IntegerHolder} that holds the resulting critical-hit level
|
|
* @returns true if the critical-hit stage boost applies successfully, false otherwise
|
|
*/
|
|
apply(args: any[]): boolean {
|
|
const critStage = args[1] as Utils.NumberHolder;
|
|
|
|
critStage.value += this.stageIncrement;
|
|
return true;
|
|
}
|
|
|
|
getMaxHeldItemCount(_pokemon: Pokemon): number {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modifier used for held items that apply critical-hit stage boost(s)
|
|
* if the holder is of a specific {@linkcode Species}.
|
|
* @extends CritBoosterModifier
|
|
* @see {@linkcode shouldApply}
|
|
*/
|
|
export class SpeciesCritBoosterModifier extends CritBoosterModifier {
|
|
/** The species that the held item's critical-hit stage boost applies to */
|
|
private species: Species[];
|
|
|
|
constructor(type: ModifierType, pokemonId: integer, stageIncrement: number, species: Species[], stackCount?: integer) {
|
|
super(type, pokemonId, stageIncrement, stackCount);
|
|
|
|
this.species = species;
|
|
}
|
|
|
|
clone() {
|
|
return new SpeciesCritBoosterModifier(this.type, this.pokemonId, this.stageIncrement, this.species, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return [ ...super.getArgs(), this.species ];
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
return modifier instanceof SpeciesCritBoosterModifier;
|
|
}
|
|
|
|
/**
|
|
* Checks if the holder's {@linkcode Species} (or its fused species) is listed
|
|
* in {@linkcode species}.
|
|
* @param args [0] {@linkcode Pokemon} that holds the held item
|
|
* [1] {@linkcode Utils.IntegerHolder} N/A
|
|
* @returns true if the critical-hit level can be incremented, false otherwise
|
|
*/
|
|
shouldApply(args: any[]) {
|
|
const holder = args[0] as Pokemon;
|
|
|
|
return super.shouldApply(args) && (this.species.includes(holder.getSpeciesForm(true).speciesId) || (holder.isFusion() && this.species.includes(holder.getFusionSpeciesForm(true).speciesId)));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Applies Specific Type item boosts (e.g., Magnet)
|
|
*/
|
|
export class AttackTypeBoosterModifier extends PokemonHeldItemModifier {
|
|
private moveType: Type;
|
|
private boostMultiplier: number;
|
|
|
|
constructor(type: ModifierType, pokemonId: integer, moveType: Type, boostPercent: number, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
|
|
this.moveType = moveType;
|
|
this.boostMultiplier = boostPercent * 0.01;
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
if (modifier instanceof AttackTypeBoosterModifier) {
|
|
const attackTypeBoosterModifier = modifier as AttackTypeBoosterModifier;
|
|
return attackTypeBoosterModifier.moveType === this.moveType && attackTypeBoosterModifier.boostMultiplier === this.boostMultiplier;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
clone() {
|
|
return new AttackTypeBoosterModifier(this.type, this.pokemonId, this.moveType, this.boostMultiplier * 100, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return super.getArgs().concat([ this.moveType, this.boostMultiplier * 100 ]);
|
|
}
|
|
|
|
shouldApply(args: any[]): boolean {
|
|
return super.shouldApply(args) && args.length === 3 && typeof args[1] === "number" && args[2] instanceof Utils.NumberHolder;
|
|
}
|
|
|
|
/**
|
|
* @param {Array<any>} args Array
|
|
* - Index 0: {Pokemon} Pokemon
|
|
* - Index 1: {number} Move type
|
|
* - Index 2: {Utils.NumberHolder} Move power
|
|
* @returns {boolean} Returns true if boosts have been applied to the move.
|
|
*/
|
|
apply(args: any[]): boolean {
|
|
if (args[1] === this.moveType && (args[2] as Utils.NumberHolder).value >= 1) {
|
|
(args[2] as Utils.NumberHolder).value = Math.floor((args[2] as Utils.NumberHolder).value * (1 + (this.getStackCount() * this.boostMultiplier)));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
getScoreMultiplier(): number {
|
|
return 1.2;
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 99;
|
|
}
|
|
}
|
|
|
|
export class SurviveDamageModifier extends PokemonHeldItemModifier {
|
|
constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
return modifier instanceof SurviveDamageModifier;
|
|
}
|
|
|
|
clone() {
|
|
return new SurviveDamageModifier(this.type, this.pokemonId, this.stackCount);
|
|
}
|
|
|
|
shouldApply(args: any[]): boolean {
|
|
return super.shouldApply(args) && args.length === 2 && args[1] instanceof Utils.BooleanHolder;
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as Pokemon;
|
|
const surviveDamage = args[1] as Utils.BooleanHolder;
|
|
|
|
if (!surviveDamage.value && pokemon.randSeedInt(10) < this.getStackCount()) {
|
|
surviveDamage.value = true;
|
|
|
|
pokemon.scene.queueMessage(i18next.t("modifier:surviveDamageApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 5;
|
|
}
|
|
}
|
|
|
|
export class BypassSpeedChanceModifier extends PokemonHeldItemModifier {
|
|
constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
}
|
|
|
|
matchType(modifier: Modifier) {
|
|
return modifier instanceof BypassSpeedChanceModifier;
|
|
}
|
|
|
|
clone() {
|
|
return new BypassSpeedChanceModifier(this.type, this.pokemonId, this.stackCount);
|
|
}
|
|
|
|
shouldApply(args: any[]): boolean {
|
|
return super.shouldApply(args) && args.length === 2 && args[1] instanceof Utils.BooleanHolder;
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as Pokemon;
|
|
const bypassSpeed = args[1] as Utils.BooleanHolder;
|
|
|
|
if (!bypassSpeed.value && pokemon.randSeedInt(10) < this.getStackCount()) {
|
|
bypassSpeed.value = true;
|
|
const isCommandFight = pokemon.scene.currentBattle.turnCommands[pokemon.getBattlerIndex()]?.command === Command.FIGHT;
|
|
const hasQuickClaw = this.type instanceof ModifierTypes.PokemonHeldItemModifierType && this.type.id === "QUICK_CLAW";
|
|
|
|
if (isCommandFight && hasQuickClaw) {
|
|
pokemon.scene.queueMessage(i18next.t("modifier:bypassSpeedChanceApply", { pokemonName: getPokemonNameWithAffix(pokemon), itemName: i18next.t("modifierType:ModifierType.QUICK_CLAW.name") }));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 3;
|
|
}
|
|
}
|
|
|
|
export class FlinchChanceModifier extends PokemonHeldItemModifier {
|
|
constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
}
|
|
|
|
matchType(modifier: Modifier) {
|
|
return modifier instanceof FlinchChanceModifier;
|
|
}
|
|
|
|
clone() {
|
|
return new FlinchChanceModifier(this.type, this.pokemonId, this.stackCount);
|
|
}
|
|
|
|
shouldApply(args: any[]): boolean {
|
|
return super.shouldApply(args) && args.length === 2 && args[1] instanceof Utils.BooleanHolder;
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as Pokemon;
|
|
const flinched = args[1] as Utils.BooleanHolder;
|
|
|
|
if (!flinched.value && pokemon.randSeedInt(10) < (this.getStackCount() * this.getSecondaryChanceMultiplier(pokemon))) {
|
|
flinched.value = true;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 3;
|
|
}
|
|
}
|
|
|
|
export class TurnHealModifier extends PokemonHeldItemModifier {
|
|
constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
}
|
|
|
|
matchType(modifier: Modifier) {
|
|
return modifier instanceof TurnHealModifier;
|
|
}
|
|
|
|
clone() {
|
|
return new TurnHealModifier(this.type, this.pokemonId, this.stackCount);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as Pokemon;
|
|
|
|
if (!pokemon.isFullHp()) {
|
|
const scene = pokemon.scene;
|
|
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
|
|
Utils.toDmgValue(pokemon.getMaxHp() / 16) * this.stackCount, i18next.t("modifier:turnHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 4;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a
|
|
* set {@linkcode StatusEffect} at the end of a turn.
|
|
* @extends PokemonHeldItemModifier
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class TurnStatusEffectModifier extends PokemonHeldItemModifier {
|
|
/** The status effect to be applied by the held item */
|
|
private effect: StatusEffect;
|
|
|
|
constructor (type: ModifierType, pokemonId: integer, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
|
|
switch (type.id) {
|
|
case "TOXIC_ORB":
|
|
this.effect = StatusEffect.TOXIC;
|
|
break;
|
|
case "FLAME_ORB":
|
|
this.effect = StatusEffect.BURN;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if {@linkcode modifier} is an instance of this class,
|
|
* intentionally ignoring potentially different {@linkcode effect}s
|
|
* to prevent held item stockpiling since the item obtained first
|
|
* would be the only item able to {@linkcode apply} successfully.
|
|
* @override
|
|
* @param modifier {@linkcode Modifier} being type tested
|
|
* @return true if {@linkcode modifier} is an instance of
|
|
* TurnStatusEffectModifier, false otherwise
|
|
*/
|
|
matchType(modifier: Modifier): boolean {
|
|
return modifier instanceof TurnStatusEffectModifier;
|
|
}
|
|
|
|
clone() {
|
|
return new TurnStatusEffectModifier(this.type, this.pokemonId, this.stackCount);
|
|
}
|
|
|
|
/**
|
|
* Tries to inflicts the holder with the associated {@linkcode StatusEffect}.
|
|
* @param args [0] {@linkcode Pokemon} that holds the held item
|
|
* @returns true if the status effect was applied successfully, false if
|
|
* otherwise
|
|
*/
|
|
apply(args: any[]): boolean {
|
|
return (args[0] as Pokemon).trySetStatus(this.effect, true, undefined, undefined, this.type.name);
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 1;
|
|
}
|
|
|
|
getStatusEffect(): StatusEffect {
|
|
return this.effect;
|
|
}
|
|
}
|
|
|
|
export class HitHealModifier extends PokemonHeldItemModifier {
|
|
constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
}
|
|
|
|
matchType(modifier: Modifier) {
|
|
return modifier instanceof HitHealModifier;
|
|
}
|
|
|
|
clone() {
|
|
return new HitHealModifier(this.type, this.pokemonId, this.stackCount);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as Pokemon;
|
|
|
|
if (pokemon.turnData.damageDealt && !pokemon.isFullHp()) {
|
|
const scene = pokemon.scene;
|
|
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
|
|
Utils.toDmgValue(pokemon.turnData.damageDealt / 8) * this.stackCount, i18next.t("modifier:hitHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 4;
|
|
}
|
|
}
|
|
|
|
export class LevelIncrementBoosterModifier extends PersistentModifier {
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
}
|
|
|
|
match(modifier: Modifier) {
|
|
return modifier instanceof LevelIncrementBoosterModifier;
|
|
}
|
|
|
|
clone() {
|
|
return new LevelIncrementBoosterModifier(this.type, this.stackCount);
|
|
}
|
|
|
|
shouldApply(args: any[]): boolean {
|
|
return super.shouldApply(args) && args[0] instanceof Utils.IntegerHolder;
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
(args[0] as Utils.IntegerHolder).value += this.getStackCount();
|
|
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene, forThreshold?: boolean): number {
|
|
return 99;
|
|
}
|
|
}
|
|
|
|
export class BerryModifier extends PokemonHeldItemModifier {
|
|
public berryType: BerryType;
|
|
public consumed: boolean;
|
|
|
|
constructor(type: ModifierType, pokemonId: integer, berryType: BerryType, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
|
|
this.berryType = berryType;
|
|
this.consumed = false;
|
|
}
|
|
|
|
matchType(modifier: Modifier) {
|
|
return modifier instanceof BerryModifier && (modifier as BerryModifier).berryType === this.berryType;
|
|
}
|
|
|
|
clone() {
|
|
return new BerryModifier(this.type, this.pokemonId, this.berryType, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return super.getArgs().concat(this.berryType);
|
|
}
|
|
|
|
shouldApply(args: any[]): boolean {
|
|
return !this.consumed && super.shouldApply(args) && getBerryPredicate(this.berryType)(args[0] as Pokemon);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as Pokemon;
|
|
|
|
const preserve = new Utils.BooleanHolder(false);
|
|
pokemon.scene.applyModifiers(PreserveBerryModifier, pokemon.isPlayer(), pokemon, preserve);
|
|
|
|
getBerryEffectFunc(this.berryType)(pokemon);
|
|
if (!preserve.value) {
|
|
this.consumed = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
if ([BerryType.LUM, BerryType.LEPPA, BerryType.SITRUS, BerryType.ENIGMA].includes(this.berryType)) {
|
|
return 2;
|
|
}
|
|
return 3;
|
|
}
|
|
}
|
|
|
|
export class PreserveBerryModifier extends PersistentModifier {
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
}
|
|
|
|
match(modifier: Modifier) {
|
|
return modifier instanceof PreserveBerryModifier;
|
|
}
|
|
|
|
clone() {
|
|
return new PreserveBerryModifier(this.type, this.stackCount);
|
|
}
|
|
|
|
shouldApply(args: any[]): boolean {
|
|
return super.shouldApply(args) && args[0] instanceof Pokemon && args[1] instanceof Utils.BooleanHolder;
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
if (!(args[1] as Utils.BooleanHolder).value) {
|
|
(args[1] as Utils.BooleanHolder).value = (args[0] as Pokemon).randSeedInt(10) < this.getStackCount() * 3;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 3;
|
|
}
|
|
}
|
|
|
|
export class PokemonInstantReviveModifier extends PokemonHeldItemModifier {
|
|
constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
}
|
|
|
|
matchType(modifier: Modifier) {
|
|
return modifier instanceof PokemonInstantReviveModifier;
|
|
}
|
|
|
|
clone() {
|
|
return new PokemonInstantReviveModifier(this.type, this.pokemonId, this.stackCount);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as Pokemon;
|
|
|
|
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
|
|
Utils.toDmgValue(pokemon.getMaxHp() / 2), i18next.t("modifier:pokemonInstantReviveApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), false, false, true));
|
|
|
|
pokemon.resetStatus(true, false, true);
|
|
return true;
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modifier used for held items, namely White Herb, that restore adverse stat
|
|
* stages in battle.
|
|
* @extends PokemonHeldItemModifier
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class ResetNegativeStatStageModifier extends PokemonHeldItemModifier {
|
|
constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
}
|
|
|
|
matchType(modifier: Modifier) {
|
|
return modifier instanceof ResetNegativeStatStageModifier;
|
|
}
|
|
|
|
clone() {
|
|
return new ResetNegativeStatStageModifier(this.type, this.pokemonId, this.stackCount);
|
|
}
|
|
|
|
/**
|
|
* Goes through the holder's stat stages and, if any are negative, resets that
|
|
* stat stage back to 0.
|
|
* @param args [0] {@linkcode Pokemon} that holds the held item
|
|
* @returns true if any stat stages were reset, false otherwise
|
|
*/
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as Pokemon;
|
|
let statRestored = false;
|
|
|
|
for (const s of BATTLE_STATS) {
|
|
if (pokemon.getStatStage(s) < 0) {
|
|
pokemon.setStatStage(s, 0);
|
|
statRestored = true;
|
|
}
|
|
}
|
|
|
|
if (statRestored) {
|
|
pokemon.scene.queueMessage(i18next.t("modifier:resetNegativeStatStageApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }));
|
|
}
|
|
return statRestored;
|
|
}
|
|
|
|
getMaxHeldItemCount(_pokemon: Pokemon): integer {
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
export abstract class ConsumablePokemonModifier extends ConsumableModifier {
|
|
public pokemonId: integer;
|
|
|
|
constructor(type: ModifierType, pokemonId: integer) {
|
|
super(type);
|
|
|
|
this.pokemonId = pokemonId;
|
|
}
|
|
|
|
shouldApply(args: any[]): boolean {
|
|
return args.length !== 0 && args[0] instanceof PlayerPokemon && (this.pokemonId === -1 || (args[0] as PlayerPokemon).id === this.pokemonId);
|
|
}
|
|
|
|
getPokemon(scene: BattleScene) {
|
|
return scene.getParty().find(p => p.id === this.pokemonId);
|
|
}
|
|
}
|
|
|
|
export class PokemonHpRestoreModifier extends ConsumablePokemonModifier {
|
|
private restorePoints: integer;
|
|
private restorePercent: number;
|
|
private healStatus: boolean;
|
|
public fainted: boolean;
|
|
|
|
constructor(type: ModifierType, pokemonId: integer, restorePoints: integer, restorePercent: number, healStatus: boolean, fainted?: boolean) {
|
|
super(type, pokemonId);
|
|
|
|
this.restorePoints = restorePoints;
|
|
this.restorePercent = restorePercent;
|
|
this.healStatus = healStatus;
|
|
this.fainted = !!fainted;
|
|
}
|
|
|
|
shouldApply(args: any[]): boolean {
|
|
return super.shouldApply(args) && (this.fainted || (args.length > 1 && typeof(args[1]) === "number"));
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as Pokemon;
|
|
if (!pokemon.hp === this.fainted) {
|
|
let restorePoints = this.restorePoints;
|
|
if (!this.fainted) {
|
|
restorePoints = Math.floor(restorePoints * (args[1] as number));
|
|
}
|
|
if (this.fainted || this.healStatus) {
|
|
pokemon.resetStatus(true, true);
|
|
}
|
|
pokemon.hp = Math.min(pokemon.hp + Math.max(Math.ceil(Math.max(Math.floor((this.restorePercent * 0.01) * pokemon.getMaxHp()), restorePoints)), 1), pokemon.getMaxHp());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class PokemonStatusHealModifier extends ConsumablePokemonModifier {
|
|
constructor(type: ModifierType, pokemonId: integer) {
|
|
super(type, pokemonId);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as Pokemon;
|
|
pokemon.resetStatus(true, true);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export abstract class ConsumablePokemonMoveModifier extends ConsumablePokemonModifier {
|
|
public moveIndex: integer;
|
|
|
|
constructor(type: ModifierType, pokemonId: integer, moveIndex: integer) {
|
|
super(type, pokemonId);
|
|
|
|
this.moveIndex = moveIndex;
|
|
}
|
|
}
|
|
|
|
export class PokemonPpRestoreModifier extends ConsumablePokemonMoveModifier {
|
|
private restorePoints: integer;
|
|
|
|
constructor(type: ModifierType, pokemonId: integer, moveIndex: integer, restorePoints: integer) {
|
|
super(type, pokemonId, moveIndex);
|
|
|
|
this.restorePoints = restorePoints;
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as Pokemon;
|
|
const move = pokemon.getMoveset()[this.moveIndex]!; //TODO: is the bang correct?
|
|
move.ppUsed = this.restorePoints > -1 ? Math.max(move.ppUsed - this.restorePoints, 0) : 0;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class PokemonAllMovePpRestoreModifier extends ConsumablePokemonModifier {
|
|
private restorePoints: integer;
|
|
|
|
constructor(type: ModifierType, pokemonId: integer, restorePoints: integer) {
|
|
super(type, pokemonId);
|
|
|
|
this.restorePoints = restorePoints;
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as Pokemon;
|
|
for (const move of pokemon.getMoveset()) {
|
|
move!.ppUsed = this.restorePoints > -1 ? Math.max(move!.ppUsed - this.restorePoints, 0) : 0; // TODO: are those bangs correct?
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class PokemonPpUpModifier extends ConsumablePokemonMoveModifier {
|
|
private upPoints: integer;
|
|
|
|
constructor(type: ModifierType, pokemonId: integer, moveIndex: integer, upPoints: integer) {
|
|
super(type, pokemonId, moveIndex);
|
|
|
|
this.upPoints = upPoints;
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as Pokemon;
|
|
const move = pokemon.getMoveset()[this.moveIndex]!; // TODO: is the bang correct?
|
|
move.ppUp = Math.min(move.ppUp + this.upPoints, 3);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class PokemonNatureChangeModifier extends ConsumablePokemonModifier {
|
|
public nature: Nature;
|
|
|
|
constructor(type: ModifierType, pokemonId: integer, nature: Nature) {
|
|
super(type, pokemonId);
|
|
|
|
this.nature = nature;
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as Pokemon;
|
|
pokemon.natureOverride = this.nature;
|
|
let speciesId = pokemon.species.speciesId;
|
|
pokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1);
|
|
|
|
while (pokemonPrevolutions.hasOwnProperty(speciesId)) {
|
|
speciesId = pokemonPrevolutions[speciesId];
|
|
pokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier {
|
|
constructor(type: ModifierType, pokemonId: integer) {
|
|
super(type, pokemonId);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as PlayerPokemon;
|
|
const levelCount = new Utils.IntegerHolder(1);
|
|
pokemon.scene.applyModifiers(LevelIncrementBoosterModifier, true, levelCount);
|
|
|
|
pokemon.level += levelCount.value;
|
|
if (pokemon.level <= pokemon.scene.getMaxExpLevel(true)) {
|
|
pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate);
|
|
pokemon.levelExp = 0;
|
|
}
|
|
|
|
pokemon.addFriendship(5);
|
|
|
|
pokemon.scene.unshiftPhase(new LevelUpPhase(pokemon.scene, pokemon.scene.getParty().indexOf(pokemon), pokemon.level - levelCount.value, pokemon.level));
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class TmModifier extends ConsumablePokemonModifier {
|
|
constructor(type: ModifierTypes.TmModifierType, pokemonId: integer) {
|
|
super(type, pokemonId);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as PlayerPokemon;
|
|
|
|
pokemon.scene.unshiftPhase(new LearnMovePhase(pokemon.scene, pokemon.scene.getParty().indexOf(pokemon), (this.type as ModifierTypes.TmModifierType).moveId, true));
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class RememberMoveModifier extends ConsumablePokemonModifier {
|
|
public levelMoveIndex: integer;
|
|
|
|
constructor(type: ModifierTypes.ModifierType, pokemonId: integer, levelMoveIndex: integer) {
|
|
super(type, pokemonId);
|
|
|
|
this.levelMoveIndex = levelMoveIndex;
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as PlayerPokemon;
|
|
|
|
pokemon.scene.unshiftPhase(new LearnMovePhase(pokemon.scene, pokemon.scene.getParty().indexOf(pokemon), pokemon.getLearnableLevelMoves()[this.levelMoveIndex]));
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class EvolutionItemModifier extends ConsumablePokemonModifier {
|
|
constructor(type: ModifierTypes.EvolutionItemModifierType, pokemonId: integer) {
|
|
super(type, pokemonId);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as PlayerPokemon;
|
|
|
|
let matchingEvolution = pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId)
|
|
? pokemonEvolutions[pokemon.species.speciesId].find(e => e.item === (this.type as ModifierTypes.EvolutionItemModifierType).evolutionItem
|
|
&& (e.evoFormKey === null || (e.preFormKey || "") === pokemon.getFormKey())
|
|
&& (!e.condition || e.condition.predicate(pokemon)))
|
|
: null;
|
|
|
|
if (!matchingEvolution && pokemon.isFusion()) {
|
|
matchingEvolution = pokemonEvolutions[pokemon.fusionSpecies!.speciesId].find(e => e.item === (this.type as ModifierTypes.EvolutionItemModifierType).evolutionItem // TODO: is the bang correct?
|
|
&& (e.evoFormKey === null || (e.preFormKey || "") === pokemon.getFusionFormKey())
|
|
&& (!e.condition || e.condition.predicate(pokemon)));
|
|
if (matchingEvolution) {
|
|
matchingEvolution = new FusionSpeciesFormEvolution(pokemon.species.speciesId, matchingEvolution);
|
|
}
|
|
}
|
|
|
|
if (matchingEvolution) {
|
|
pokemon.scene.unshiftPhase(new EvolutionPhase(pokemon.scene, pokemon, matchingEvolution, pokemon.level - 1));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class FusePokemonModifier extends ConsumablePokemonModifier {
|
|
public fusePokemonId: integer;
|
|
|
|
constructor(type: ModifierType, pokemonId: integer, fusePokemonId: integer) {
|
|
super(type, pokemonId);
|
|
|
|
this.fusePokemonId = fusePokemonId;
|
|
}
|
|
|
|
shouldApply(args: any[]): boolean {
|
|
return super.shouldApply(args) && args[1] instanceof PlayerPokemon && this.fusePokemonId === (args[1] as PlayerPokemon).id;
|
|
}
|
|
|
|
apply(args: any[]): Promise<boolean> {
|
|
return new Promise<boolean>(resolve => {
|
|
(args[0] as PlayerPokemon).fuse(args[1] as PlayerPokemon).then(() => resolve(true));
|
|
});
|
|
}
|
|
}
|
|
|
|
export class MultipleParticipantExpBonusModifier extends PersistentModifier {
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return modifier instanceof MultipleParticipantExpBonusModifier;
|
|
}
|
|
|
|
apply(_args: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
clone(): MultipleParticipantExpBonusModifier {
|
|
return new MultipleParticipantExpBonusModifier(this.type, this.stackCount);
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 5;
|
|
}
|
|
}
|
|
|
|
export class HealingBoosterModifier extends PersistentModifier {
|
|
private multiplier: number;
|
|
|
|
constructor(type: ModifierType, multiplier: number, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
|
|
this.multiplier = multiplier;
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return modifier instanceof HealingBoosterModifier;
|
|
}
|
|
|
|
clone(): HealingBoosterModifier {
|
|
return new HealingBoosterModifier(this.type, this.multiplier, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return [ this.multiplier ];
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const healingMultiplier = args[0] as Utils.IntegerHolder;
|
|
healingMultiplier.value *= 1 + ((this.multiplier - 1) * this.getStackCount());
|
|
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 5;
|
|
}
|
|
}
|
|
|
|
export class ExpBoosterModifier extends PersistentModifier {
|
|
private boostMultiplier: integer;
|
|
|
|
constructor(type: ModifierType, boostPercent: number, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
|
|
this.boostMultiplier = boostPercent * 0.01;
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
if (modifier instanceof ExpBoosterModifier) {
|
|
const expModifier = modifier as ExpBoosterModifier;
|
|
return expModifier.boostMultiplier === this.boostMultiplier;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
clone(): ExpBoosterModifier {
|
|
return new ExpBoosterModifier(this.type, this.boostMultiplier * 100, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return [ this.boostMultiplier * 100 ];
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
(args[0] as Utils.NumberHolder).value = Math.floor((args[0] as Utils.NumberHolder).value * (1 + (this.getStackCount() * this.boostMultiplier)));
|
|
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene, forThreshold?: boolean): integer {
|
|
return this.boostMultiplier < 1 ? this.boostMultiplier < 0.6 ? 99 : 30 : 10;
|
|
}
|
|
}
|
|
|
|
export class PokemonExpBoosterModifier extends PokemonHeldItemModifier {
|
|
private boostMultiplier: integer;
|
|
|
|
constructor(type: ModifierTypes.PokemonExpBoosterModifierType, pokemonId: integer, boostPercent: number, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
this.boostMultiplier = boostPercent * 0.01;
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
if (modifier instanceof PokemonExpBoosterModifier) {
|
|
const pokemonExpModifier = modifier as PokemonExpBoosterModifier;
|
|
return pokemonExpModifier.boostMultiplier === this.boostMultiplier;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
clone(): PersistentModifier {
|
|
return new PokemonExpBoosterModifier(this.type as ModifierTypes.PokemonExpBoosterModifierType, this.pokemonId, this.boostMultiplier * 100, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return super.getArgs().concat(this.boostMultiplier * 100);
|
|
}
|
|
|
|
shouldApply(args: any[]): boolean {
|
|
return super.shouldApply(args) && args.length === 2 && args[1] instanceof Utils.NumberHolder;
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
(args[1] as Utils.NumberHolder).value = Math.floor((args[1] as Utils.NumberHolder).value * (1 + (this.getStackCount() * this.boostMultiplier)));
|
|
|
|
return true;
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 99;
|
|
}
|
|
}
|
|
|
|
export class ExpShareModifier extends PersistentModifier {
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return modifier instanceof ExpShareModifier;
|
|
}
|
|
|
|
clone(): ExpShareModifier {
|
|
return new ExpShareModifier(this.type, this.stackCount);
|
|
}
|
|
|
|
apply(_args: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 5;
|
|
}
|
|
}
|
|
|
|
export class ExpBalanceModifier extends PersistentModifier {
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return modifier instanceof ExpBalanceModifier;
|
|
}
|
|
|
|
clone(): ExpBalanceModifier {
|
|
return new ExpBalanceModifier(this.type, this.stackCount);
|
|
}
|
|
|
|
apply(_args: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 4;
|
|
}
|
|
}
|
|
|
|
export class PokemonFriendshipBoosterModifier extends PokemonHeldItemModifier {
|
|
constructor(type: ModifierTypes.PokemonFriendshipBoosterModifierType, pokemonId: integer, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
return modifier instanceof PokemonFriendshipBoosterModifier;
|
|
}
|
|
|
|
clone(): PersistentModifier {
|
|
return new PokemonFriendshipBoosterModifier(this.type as ModifierTypes.PokemonFriendshipBoosterModifierType, this.pokemonId, this.stackCount);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const friendship = args[1] as Utils.IntegerHolder;
|
|
friendship.value = Math.floor(friendship.value * (1 + 0.5 * this.getStackCount()));
|
|
|
|
return true;
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 3;
|
|
}
|
|
}
|
|
|
|
export class PokemonNatureWeightModifier extends PokemonHeldItemModifier {
|
|
constructor(type: ModifierTypes.ModifierType, pokemonId: integer, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
return modifier instanceof PokemonNatureWeightModifier;
|
|
}
|
|
|
|
clone(): PersistentModifier {
|
|
return new PokemonNatureWeightModifier(this.type, this.pokemonId, this.stackCount);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const multiplier = args[1] as Utils.IntegerHolder;
|
|
if (multiplier.value !== 1) {
|
|
multiplier.value += 0.1 * this.getStackCount() * (multiplier.value > 1 ? 1 : -1);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 10;
|
|
}
|
|
}
|
|
|
|
export class PokemonMoveAccuracyBoosterModifier extends PokemonHeldItemModifier {
|
|
private accuracyAmount: integer;
|
|
|
|
constructor(type: ModifierTypes.PokemonMoveAccuracyBoosterModifierType, pokemonId: integer, accuracy: integer, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
this.accuracyAmount = accuracy;
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
if (modifier instanceof PokemonMoveAccuracyBoosterModifier) {
|
|
const pokemonAccuracyBoosterModifier = modifier as PokemonMoveAccuracyBoosterModifier;
|
|
return pokemonAccuracyBoosterModifier.accuracyAmount === this.accuracyAmount;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
clone(): PersistentModifier {
|
|
return new PokemonMoveAccuracyBoosterModifier(this.type as ModifierTypes.PokemonMoveAccuracyBoosterModifierType, this.pokemonId, this.accuracyAmount, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return super.getArgs().concat(this.accuracyAmount);
|
|
}
|
|
|
|
shouldApply(args: any[]): boolean {
|
|
return super.shouldApply(args) && args.length === 2 && args[1] instanceof Utils.NumberHolder;
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const moveAccuracy = (args[1] as Utils.IntegerHolder);
|
|
moveAccuracy.value = Math.min(moveAccuracy.value + this.accuracyAmount * this.getStackCount(), 100);
|
|
|
|
return true;
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 3;
|
|
}
|
|
}
|
|
|
|
export class PokemonMultiHitModifier extends PokemonHeldItemModifier {
|
|
constructor(type: ModifierTypes.PokemonMultiHitModifierType, pokemonId: integer, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
return modifier instanceof PokemonMultiHitModifier;
|
|
}
|
|
|
|
clone(): PersistentModifier {
|
|
return new PokemonMultiHitModifier(this.type as ModifierTypes.PokemonMultiHitModifierType, this.pokemonId, this.stackCount);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
(args[1] as Utils.IntegerHolder).value *= (this.getStackCount() + 1);
|
|
|
|
const power = args[2] as Utils.NumberHolder;
|
|
switch (this.getStackCount()) {
|
|
case 1:
|
|
power.value *= 0.4;
|
|
break;
|
|
case 2:
|
|
power.value *= 0.25;
|
|
break;
|
|
case 3:
|
|
power.value *= 0.175;
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 3;
|
|
}
|
|
}
|
|
|
|
export class PokemonFormChangeItemModifier extends PokemonHeldItemModifier {
|
|
public formChangeItem: FormChangeItem;
|
|
public active: boolean;
|
|
readonly isTransferrable: boolean = false;
|
|
|
|
constructor(type: ModifierTypes.FormChangeItemModifierType, pokemonId: integer, formChangeItem: FormChangeItem, active: boolean, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
this.formChangeItem = formChangeItem;
|
|
this.active = active;
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
return modifier instanceof PokemonFormChangeItemModifier && modifier.formChangeItem === this.formChangeItem;
|
|
}
|
|
|
|
clone(): PersistentModifier {
|
|
return new PokemonFormChangeItemModifier(this.type as ModifierTypes.FormChangeItemModifierType, this.pokemonId, this.formChangeItem, this.active, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return super.getArgs().concat(this.formChangeItem, this.active);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as Pokemon;
|
|
const active = args[1] as boolean;
|
|
|
|
const switchActive = this.active && !active;
|
|
|
|
if (switchActive) {
|
|
this.active = false;
|
|
}
|
|
|
|
const ret = pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger);
|
|
|
|
if (switchActive) {
|
|
this.active = true;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
export class MoneyRewardModifier extends ConsumableModifier {
|
|
private moneyMultiplier: number;
|
|
|
|
constructor(type: ModifierType, moneyMultiplier: number) {
|
|
super(type);
|
|
|
|
this.moneyMultiplier = moneyMultiplier;
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const scene = args[0] as BattleScene;
|
|
const moneyAmount = new Utils.IntegerHolder(scene.getWaveMoneyAmount(this.moneyMultiplier));
|
|
|
|
scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount);
|
|
|
|
scene.addMoney(moneyAmount.value);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class MoneyMultiplierModifier extends PersistentModifier {
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return modifier instanceof MoneyMultiplierModifier;
|
|
}
|
|
|
|
clone(): MoneyMultiplierModifier {
|
|
return new MoneyMultiplierModifier(this.type, this.stackCount);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
(args[0] as Utils.IntegerHolder).value += Math.floor((args[0] as Utils.IntegerHolder).value * 0.2 * this.getStackCount());
|
|
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 5;
|
|
}
|
|
}
|
|
|
|
export class DamageMoneyRewardModifier extends PokemonHeldItemModifier {
|
|
constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
return modifier instanceof DamageMoneyRewardModifier;
|
|
}
|
|
|
|
clone(): DamageMoneyRewardModifier {
|
|
return new DamageMoneyRewardModifier(this.type, this.pokemonId, this.stackCount);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const scene = (args[0] as Pokemon).scene;
|
|
const moneyAmount = new Utils.IntegerHolder(Math.floor((args[1] as Utils.IntegerHolder).value * (0.5 * this.getStackCount())));
|
|
scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount);
|
|
scene.addMoney(moneyAmount.value);
|
|
|
|
return true;
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 5;
|
|
}
|
|
}
|
|
|
|
export class MoneyInterestModifier extends PersistentModifier {
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return modifier instanceof MoneyInterestModifier;
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const scene = args[0] as BattleScene;
|
|
const interestAmount = Math.floor(scene.money * 0.1 * this.getStackCount());
|
|
scene.addMoney(interestAmount);
|
|
|
|
const userLocale = navigator.language || "en-US";
|
|
const formattedMoneyAmount = interestAmount.toLocaleString(userLocale);
|
|
const message = i18next.t("modifier:moneyInterestApply", { moneyAmount: formattedMoneyAmount, typeName: this.type.name });
|
|
scene.queueMessage(message, undefined, true);
|
|
|
|
return true;
|
|
}
|
|
|
|
clone(): MoneyInterestModifier {
|
|
return new MoneyInterestModifier(this.type, this.stackCount);
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 5;
|
|
}
|
|
}
|
|
|
|
export class HiddenAbilityRateBoosterModifier extends PersistentModifier {
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return modifier instanceof HiddenAbilityRateBoosterModifier;
|
|
}
|
|
|
|
clone(): HiddenAbilityRateBoosterModifier {
|
|
return new HiddenAbilityRateBoosterModifier(this.type, this.stackCount);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
(args[0] as Utils.IntegerHolder).value *= Math.pow(2, -1 - this.getStackCount());
|
|
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 4;
|
|
}
|
|
}
|
|
|
|
export class ShinyRateBoosterModifier extends PersistentModifier {
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return modifier instanceof ShinyRateBoosterModifier;
|
|
}
|
|
|
|
clone(): ShinyRateBoosterModifier {
|
|
return new ShinyRateBoosterModifier(this.type, this.stackCount);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
(args[0] as Utils.IntegerHolder).value *= Math.pow(2, 1 + this.getStackCount());
|
|
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 4;
|
|
}
|
|
}
|
|
|
|
export class LockModifierTiersModifier extends PersistentModifier {
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return modifier instanceof LockModifierTiersModifier;
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
clone(): LockModifierTiersModifier {
|
|
return new LockModifierTiersModifier(this.type, this.stackCount);
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Black Sludge item
|
|
*/
|
|
export class HealShopCostModifier extends PersistentModifier {
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return modifier instanceof HealShopCostModifier;
|
|
}
|
|
|
|
clone(): HealShopCostModifier {
|
|
return new HealShopCostModifier(this.type, this.stackCount);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
(args[0] as Utils.IntegerHolder).value *= Math.pow(3, this.getStackCount());
|
|
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
export class BoostBugSpawnModifier extends PersistentModifier {
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return modifier instanceof BoostBugSpawnModifier;
|
|
}
|
|
|
|
clone(): HealShopCostModifier {
|
|
return new BoostBugSpawnModifier(this.type, this.stackCount);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
export class SwitchEffectTransferModifier extends PokemonHeldItemModifier {
|
|
constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
return modifier instanceof SwitchEffectTransferModifier;
|
|
}
|
|
|
|
clone(): SwitchEffectTransferModifier {
|
|
return new SwitchEffectTransferModifier(this.type, this.pokemonId, this.stackCount);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Abstract class for held items that steal other Pokemon's items.
|
|
* @see {@linkcode TurnHeldItemTransferModifier}
|
|
* @see {@linkcode ContactHeldItemTransferChanceModifier}
|
|
*/
|
|
export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier {
|
|
constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
}
|
|
|
|
/**
|
|
* Determines the targets to transfer items from when this applies.
|
|
* @param args\[0\] the {@linkcode Pokemon} holding this item
|
|
* @returns the opponents of the source {@linkcode Pokemon}
|
|
*/
|
|
getTargets(args: any[]): Pokemon[] {
|
|
const pokemon = args[0];
|
|
|
|
return pokemon instanceof Pokemon
|
|
? pokemon.getOpponents()
|
|
: [];
|
|
}
|
|
|
|
/**
|
|
* Steals an item from a set of target Pokemon.
|
|
* This prioritizes high-tier held items when selecting the item to steal.
|
|
* @param args \[0\] The {@linkcode Pokemon} holding this item
|
|
* @returns true if an item was stolen; false otherwise.
|
|
*/
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as Pokemon;
|
|
const opponents = this.getTargets(args);
|
|
|
|
if (!opponents.length) {
|
|
return false;
|
|
}
|
|
|
|
const targetPokemon = opponents[pokemon.randSeedInt(opponents.length)];
|
|
|
|
const transferredItemCount = this.getTransferredItemCount();
|
|
if (!transferredItemCount) {
|
|
return false;
|
|
}
|
|
|
|
const poolType = pokemon.isPlayer() ? ModifierTypes.ModifierPoolType.PLAYER : pokemon.hasTrainer() ? ModifierTypes.ModifierPoolType.TRAINER : ModifierTypes.ModifierPoolType.WILD;
|
|
|
|
const transferredModifierTypes: ModifierTypes.ModifierType[] = [];
|
|
const itemModifiers = pokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
|
&& m.pokemonId === targetPokemon.id && m.isTransferrable, targetPokemon.isPlayer()) as PokemonHeldItemModifier[];
|
|
let highestItemTier = itemModifiers.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is this bang correct?
|
|
let tierItemModifiers = itemModifiers.filter(m => m.type.getOrInferTier(poolType) === highestItemTier);
|
|
|
|
const heldItemTransferPromises: Promise<void>[] = [];
|
|
|
|
for (let i = 0; i < transferredItemCount; i++) {
|
|
if (!tierItemModifiers.length) {
|
|
while (highestItemTier-- && !tierItemModifiers.length) {
|
|
tierItemModifiers = itemModifiers.filter(m => m.type.tier === highestItemTier);
|
|
}
|
|
if (!tierItemModifiers.length) {
|
|
break;
|
|
}
|
|
}
|
|
const randItemIndex = pokemon.randSeedInt(itemModifiers.length);
|
|
const randItem = itemModifiers[randItemIndex];
|
|
heldItemTransferPromises.push(pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, false).then(success => {
|
|
if (success) {
|
|
transferredModifierTypes.push(randItem.type);
|
|
itemModifiers.splice(randItemIndex, 1);
|
|
}
|
|
}));
|
|
}
|
|
|
|
Promise.all(heldItemTransferPromises).then(() => {
|
|
for (const mt of transferredModifierTypes) {
|
|
pokemon.scene.queueMessage(this.getTransferMessage(pokemon, targetPokemon, mt));
|
|
}
|
|
});
|
|
|
|
return !!transferredModifierTypes.length;
|
|
}
|
|
|
|
abstract getTransferredItemCount(): integer;
|
|
|
|
abstract getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierTypes.ModifierType): string;
|
|
}
|
|
|
|
/**
|
|
* Modifier for held items that steal items from the enemy at the end of
|
|
* each turn.
|
|
* @see {@linkcode modifierTypes[MINI_BLACK_HOLE]}
|
|
*/
|
|
export class TurnHeldItemTransferModifier extends HeldItemTransferModifier {
|
|
isTransferrable: boolean = true;
|
|
constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
return modifier instanceof TurnHeldItemTransferModifier;
|
|
}
|
|
|
|
clone(): TurnHeldItemTransferModifier {
|
|
return new TurnHeldItemTransferModifier(this.type, this.pokemonId, this.stackCount);
|
|
}
|
|
|
|
getTransferredItemCount(): integer {
|
|
return this.getStackCount();
|
|
}
|
|
|
|
getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierTypes.ModifierType): string {
|
|
return i18next.t("modifier:turnHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: pokemon.getNameToRender(), typeName: this.type.name });
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 1;
|
|
}
|
|
|
|
setTransferrableFalse(): void {
|
|
this.isTransferrable = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modifier for held items that add a chance to steal items from the target of a
|
|
* successful attack.
|
|
* @see {@linkcode modifierTypes[GRIP_CLAW]}
|
|
* @see {@linkcode HeldItemTransferModifier}
|
|
*/
|
|
export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModifier {
|
|
private chance: number;
|
|
|
|
constructor(type: ModifierType, pokemonId: integer, chancePercent: number, stackCount?: integer) {
|
|
super(type, pokemonId, stackCount);
|
|
|
|
this.chance = chancePercent / 100;
|
|
}
|
|
|
|
/**
|
|
* Determines the target to steal items from when this applies.
|
|
* @param args\[0\] The {@linkcode Pokemon} holding this item
|
|
* @param args\[1\] The {@linkcode Pokemon} the holder is targeting with an attack
|
|
* @returns The target (args[1]) stored in array format for use in {@linkcode HeldItemTransferModifier.apply}
|
|
*/
|
|
getTargets(args: any[]): Pokemon[] {
|
|
const target = args[1];
|
|
|
|
return target instanceof Pokemon
|
|
? [ target ]
|
|
: [];
|
|
}
|
|
|
|
matchType(modifier: Modifier): boolean {
|
|
return modifier instanceof ContactHeldItemTransferChanceModifier;
|
|
}
|
|
|
|
clone(): ContactHeldItemTransferChanceModifier {
|
|
return new ContactHeldItemTransferChanceModifier(this.type, this.pokemonId, this.chance * 100, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return super.getArgs().concat(this.chance * 100);
|
|
}
|
|
|
|
getTransferredItemCount(): integer {
|
|
return Phaser.Math.RND.realInRange(0, 1) < (this.chance * this.getStackCount()) ? 1 : 0;
|
|
}
|
|
|
|
getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierTypes.ModifierType): string {
|
|
return i18next.t("modifier:contactHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: getPokemonNameWithAffix(pokemon), typeName: this.type.name });
|
|
}
|
|
|
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
|
return 5;
|
|
}
|
|
}
|
|
|
|
export class IvScannerModifier extends PersistentModifier {
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return modifier instanceof IvScannerModifier;
|
|
}
|
|
|
|
clone(): IvScannerModifier {
|
|
return new IvScannerModifier(this.type, this.stackCount);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 3;
|
|
}
|
|
}
|
|
|
|
export class ExtraModifierModifier extends PersistentModifier {
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return modifier instanceof ExtraModifierModifier;
|
|
}
|
|
|
|
clone(): ExtraModifierModifier {
|
|
return new ExtraModifierModifier(this.type, this.stackCount);
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
(args[0] as Utils.IntegerHolder).value += this.getStackCount();
|
|
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 3;
|
|
}
|
|
}
|
|
|
|
export abstract class EnemyPersistentModifier extends PersistentModifier {
|
|
constructor(type: ModifierType, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 5;
|
|
}
|
|
}
|
|
|
|
abstract class EnemyDamageMultiplierModifier extends EnemyPersistentModifier {
|
|
protected damageMultiplier: number;
|
|
|
|
constructor(type: ModifierType, damageMultiplier: number, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
|
|
this.damageMultiplier = damageMultiplier;
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
(args[0] as Utils.NumberHolder).value = Math.floor((args[0] as Utils.NumberHolder).value * Math.pow(this.damageMultiplier, this.getStackCount()));
|
|
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 99;
|
|
}
|
|
}
|
|
|
|
export class EnemyDamageBoosterModifier extends EnemyDamageMultiplierModifier {
|
|
constructor(type: ModifierType, boostPercent: number, stackCount?: integer) {
|
|
//super(type, 1 + ((boostPercent || 10) * 0.01), stackCount);
|
|
super(type, 1.05, stackCount); // Hardcode multiplier temporarily
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return modifier instanceof EnemyDamageBoosterModifier;
|
|
}
|
|
|
|
clone(): EnemyDamageBoosterModifier {
|
|
return new EnemyDamageBoosterModifier(this.type, (this.damageMultiplier - 1) * 100, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return [ (this.damageMultiplier - 1) * 100 ];
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 999;
|
|
}
|
|
}
|
|
|
|
export class EnemyDamageReducerModifier extends EnemyDamageMultiplierModifier {
|
|
constructor(type: ModifierType, reductionPercent: number, stackCount?: integer) {
|
|
//super(type, 1 - ((reductionPercent || 5) * 0.01), stackCount);
|
|
super(type, 0.975, stackCount); // Hardcode multiplier temporarily
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return modifier instanceof EnemyDamageReducerModifier;
|
|
}
|
|
|
|
clone(): EnemyDamageReducerModifier {
|
|
return new EnemyDamageReducerModifier(this.type, (1 - this.damageMultiplier) * 100, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return [ (1 - this.damageMultiplier) * 100 ];
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return scene.currentBattle.waveIndex < 2000 ? super.getMaxStackCount(scene) : 999;
|
|
}
|
|
}
|
|
|
|
export class EnemyTurnHealModifier extends EnemyPersistentModifier {
|
|
public healPercent: number;
|
|
|
|
constructor(type: ModifierType, healPercent: number, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
|
|
// Hardcode temporarily
|
|
this.healPercent = 2;
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return modifier instanceof EnemyTurnHealModifier;
|
|
}
|
|
|
|
clone(): EnemyTurnHealModifier {
|
|
return new EnemyTurnHealModifier(this.type, this.healPercent, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return [ this.healPercent ];
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const pokemon = args[0] as Pokemon;
|
|
|
|
if (!pokemon.isFullHp()) {
|
|
const scene = pokemon.scene;
|
|
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
|
|
Math.max(Math.floor(pokemon.getMaxHp() / (100 / this.healPercent)) * this.stackCount, 1), i18next.t("modifier:enemyTurnHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), true, false, false, false, true));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 10;
|
|
}
|
|
}
|
|
|
|
export class EnemyAttackStatusEffectChanceModifier extends EnemyPersistentModifier {
|
|
public effect: StatusEffect;
|
|
public chance: number;
|
|
|
|
constructor(type: ModifierType, effect: StatusEffect, chancePercent: number, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
|
|
this.effect = effect;
|
|
//Hardcode temporarily
|
|
this.chance = .025 * ((this.effect === StatusEffect.BURN || this.effect === StatusEffect.POISON) ? 2 : 1);
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return modifier instanceof EnemyAttackStatusEffectChanceModifier && modifier.effect === this.effect;
|
|
}
|
|
|
|
clone(): EnemyAttackStatusEffectChanceModifier {
|
|
return new EnemyAttackStatusEffectChanceModifier(this.type, this.effect, this.chance * 100, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return [ this.effect, this.chance * 100 ];
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const target = (args[0] as Pokemon);
|
|
if (Phaser.Math.RND.realInRange(0, 1) < (this.chance * this.getStackCount())) {
|
|
return target.trySetStatus(this.effect, true);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 10;
|
|
}
|
|
}
|
|
|
|
export class EnemyStatusEffectHealChanceModifier extends EnemyPersistentModifier {
|
|
public chance: number;
|
|
|
|
constructor(type: ModifierType, chancePercent: number, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
|
|
//Hardcode temporarily
|
|
this.chance = .025;
|
|
}
|
|
|
|
match(modifier: Modifier): boolean {
|
|
return modifier instanceof EnemyStatusEffectHealChanceModifier;
|
|
}
|
|
|
|
clone(): EnemyStatusEffectHealChanceModifier {
|
|
return new EnemyStatusEffectHealChanceModifier(this.type, this.chance * 100, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return [ this.chance * 100 ];
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const target = (args[0] as Pokemon);
|
|
if (target.status && Phaser.Math.RND.realInRange(0, 1) < (this.chance * this.getStackCount())) {
|
|
target.scene.queueMessage(getStatusEffectHealText(target.status.effect, getPokemonNameWithAffix(target)));
|
|
target.resetStatus();
|
|
target.updateInfo();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 10;
|
|
}
|
|
}
|
|
|
|
export class EnemyEndureChanceModifier extends EnemyPersistentModifier {
|
|
public chance: number;
|
|
|
|
constructor(type: ModifierType, chancePercent?: number, stackCount?: integer) {
|
|
super(type, stackCount || 10);
|
|
|
|
//Hardcode temporarily
|
|
this.chance = .02;
|
|
}
|
|
|
|
match(modifier: Modifier) {
|
|
return modifier instanceof EnemyEndureChanceModifier;
|
|
}
|
|
|
|
clone() {
|
|
return new EnemyEndureChanceModifier(this.type, this.chance * 100, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return [ this.chance * 100 ];
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
const target = (args[0] as Pokemon);
|
|
|
|
if (target.battleData.endured || Phaser.Math.RND.realInRange(0, 1) >= (this.chance * this.getStackCount())) {
|
|
return false;
|
|
}
|
|
|
|
target.addTag(BattlerTagType.ENDURING, 1);
|
|
|
|
target.battleData.endured = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 10;
|
|
}
|
|
}
|
|
|
|
export class EnemyFusionChanceModifier extends EnemyPersistentModifier {
|
|
private chance: number;
|
|
|
|
constructor(type: ModifierType, chancePercent: number, stackCount?: integer) {
|
|
super(type, stackCount);
|
|
|
|
this.chance = chancePercent / 100;
|
|
}
|
|
|
|
match(modifier: Modifier) {
|
|
return modifier instanceof EnemyFusionChanceModifier && modifier.chance === this.chance;
|
|
}
|
|
|
|
clone() {
|
|
return new EnemyFusionChanceModifier(this.type, this.chance * 100, this.stackCount);
|
|
}
|
|
|
|
getArgs(): any[] {
|
|
return [ this.chance * 100 ];
|
|
}
|
|
|
|
apply(args: any[]): boolean {
|
|
if (Phaser.Math.RND.realInRange(0, 1) >= (this.chance * this.getStackCount())) {
|
|
return false;
|
|
}
|
|
|
|
(args[0] as Utils.BooleanHolder).value = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
getMaxStackCount(scene: BattleScene): integer {
|
|
return 10;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Uses either `MODIFIER_OVERRIDE` in overrides.ts to set {@linkcode PersistentModifier}s for either:
|
|
* - The player
|
|
* - The enemy
|
|
* @param scene current {@linkcode BattleScene}
|
|
* @param isPlayer {@linkcode boolean} for whether the player (`true`) or enemy (`false`) is being overridden
|
|
*/
|
|
export function overrideModifiers(scene: BattleScene, isPlayer: boolean = true): void {
|
|
const modifiersOverride: ModifierTypes.ModifierOverride[] = isPlayer ? Overrides.STARTING_MODIFIER_OVERRIDE : Overrides.OPP_MODIFIER_OVERRIDE;
|
|
if (!modifiersOverride || modifiersOverride.length === 0 || !scene) {
|
|
return;
|
|
}
|
|
|
|
// If it's the opponent, clear all of their current modifiers to avoid stacking
|
|
if (!isPlayer) {
|
|
scene.clearEnemyModifiers();
|
|
}
|
|
|
|
modifiersOverride.forEach(item => {
|
|
const modifierFunc = modifierTypes[item.name];
|
|
let modifierType: ModifierType | null = modifierFunc();
|
|
|
|
if (modifierType instanceof ModifierTypes.ModifierTypeGenerator) {
|
|
const pregenArgs = ("type" in item) && (item.type !== null) ? [item.type] : undefined;
|
|
modifierType = modifierType.generateType([], pregenArgs);
|
|
}
|
|
|
|
const modifier = modifierType && modifierType.withIdFromFunc(modifierFunc).newModifier() as PersistentModifier;
|
|
if (modifier) {
|
|
modifier.stackCount = item.count || 1;
|
|
|
|
if (isPlayer) {
|
|
scene.addModifier(modifier, true, false, false, true);
|
|
} else {
|
|
scene.addEnemyModifier(modifier, true, true);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Uses either `HELD_ITEMS_OVERRIDE` in overrides.ts to set {@linkcode PokemonHeldItemModifier}s for either:
|
|
* - The first member of the player's team when starting a new game
|
|
* - An enemy {@linkcode Pokemon} being spawned in
|
|
* @param scene current {@linkcode BattleScene}
|
|
* @param pokemon {@linkcode Pokemon} whose held items are being overridden
|
|
* @param isPlayer {@linkcode boolean} for whether the {@linkcode pokemon} is the player's (`true`) or an enemy (`false`)
|
|
*/
|
|
export function overrideHeldItems(scene: BattleScene, pokemon: Pokemon, isPlayer: boolean = true): void {
|
|
const heldItemsOverride: ModifierTypes.ModifierOverride[] = isPlayer ? Overrides.STARTING_HELD_ITEMS_OVERRIDE : Overrides.OPP_HELD_ITEMS_OVERRIDE;
|
|
if (!heldItemsOverride || heldItemsOverride.length === 0 || !scene) {
|
|
return;
|
|
}
|
|
|
|
if (!isPlayer) {
|
|
scene.clearEnemyHeldItemModifiers();
|
|
}
|
|
|
|
heldItemsOverride.forEach(item => {
|
|
const modifierFunc = modifierTypes[item.name];
|
|
let modifierType: ModifierType | null = modifierFunc();
|
|
const qty = item.count || 1;
|
|
|
|
if (modifierType instanceof ModifierTypes.ModifierTypeGenerator) {
|
|
const pregenArgs = ("type" in item) && (item.type !== null) ? [item.type] : undefined;
|
|
modifierType = modifierType.generateType([], pregenArgs);
|
|
}
|
|
|
|
const heldItemModifier = modifierType && modifierType.withIdFromFunc(modifierFunc).newModifier(pokemon) as PokemonHeldItemModifier;
|
|
if (heldItemModifier) {
|
|
heldItemModifier.pokemonId = pokemon.id;
|
|
heldItemModifier.stackCount = qty;
|
|
if (isPlayer) {
|
|
scene.addModifier(heldItemModifier, true, false, false, true);
|
|
} else {
|
|
scene.addEnemyModifier(heldItemModifier, true, true);
|
|
}
|
|
}
|
|
});
|
|
}
|