Added Crit Boost held items, King's Rock, Focus Band and Quick Claw

This commit is contained in:
Wlowscha 2025-06-07 23:33:48 +02:00
parent 955592bdf6
commit c323375590
No known key found for this signature in database
GPG Key ID: 3C8F1AD330565D04
14 changed files with 346 additions and 469 deletions

View File

@ -42,8 +42,6 @@ import type { AttackTypeBoosterModifierType, ModifierTypeOption } from "#app/mod
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { import {
AttackTypeBoosterModifier,
BypassSpeedChanceModifier,
ContactHeldItemTransferChanceModifier, ContactHeldItemTransferChanceModifier,
GigantamaxAccessModifier, GigantamaxAccessModifier,
MegaEvolutionAccessModifier, MegaEvolutionAccessModifier,

View File

@ -92,11 +92,8 @@ import {
PokemonHeldItemModifier, PokemonHeldItemModifier,
PokemonNatureWeightModifier, PokemonNatureWeightModifier,
ShinyRateBoosterModifier, ShinyRateBoosterModifier,
SurviveDamageModifier,
TempStatStageBoosterModifier, TempStatStageBoosterModifier,
TempCritBoosterModifier, TempCritBoosterModifier,
StatBoosterModifier,
CritBoosterModifier,
PokemonBaseStatFlatModifier, PokemonBaseStatFlatModifier,
PokemonBaseStatTotalModifier, PokemonBaseStatTotalModifier,
PokemonIncrementingStatModifier, PokemonIncrementingStatModifier,
@ -257,6 +254,8 @@ import { timedEventManager } from "#app/global-event-manager";
import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader";
import { ResetStatusPhase } from "#app/phases/reset-status-phase"; import { ResetStatusPhase } from "#app/phases/reset-status-phase";
import { PokemonItemManager } from "./pokemon-held-item-manager"; import { PokemonItemManager } from "./pokemon-held-item-manager";
import { applyHeldItems } from "#app/items/all-held-items";
import { ITEM_EFFECT } from "#app/items/held-item";
export enum LearnMoveSituation { export enum LearnMoveSituation {
MISC, MISC,
@ -1446,7 +1445,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
getCritStage(source: Pokemon, move: Move): number { getCritStage(source: Pokemon, move: Move): number {
const critStage = new NumberHolder(0); const critStage = new NumberHolder(0);
applyMoveAttrs(HighCritAttr, source, this, move, critStage); applyMoveAttrs(HighCritAttr, source, this, move, critStage);
globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage); applyHeldItems(ITEM_EFFECT.CRIT_BOOST, { pokemon: source, critStage: critStage });
globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage); globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage);
applyAbAttrs(BonusCritAbAttr, source, null, false, critStage); applyAbAttrs(BonusCritAbAttr, source, null, false, critStage);
const critBoostTag = source.getTag(CritBoostTag); const critBoostTag = source.getTag(CritBoostTag);
@ -1503,7 +1502,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
): number { ): number {
const statValue = new NumberHolder(this.getStat(stat, false)); const statValue = new NumberHolder(this.getStat(stat, false));
if (!ignoreHeldItems) { if (!ignoreHeldItems) {
globalScene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statValue); applyHeldItems(ITEM_EFFECT.STAT_BOOST, { pokemon: this, stat: stat, statValue: statValue });
} }
// The Ruin abilities here are never ignored, but they reveal themselves on summon anyway // The Ruin abilities here are never ignored, but they reveal themselves on summon anyway
@ -3995,7 +3994,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
surviveDamage.value = this.lapseTag(BattlerTagType.ENDURE_TOKEN); surviveDamage.value = this.lapseTag(BattlerTagType.ENDURE_TOKEN);
} }
if (!surviveDamage.value) { if (!surviveDamage.value) {
globalScene.applyModifiers(SurviveDamageModifier, this.isPlayer(), this, surviveDamage); applyHeldItems(ITEM_EFFECT.SURVIVE_CHANCE, { pokemon: this, surviveDamage: surviveDamage });
} }
if (surviveDamage.value) { if (surviveDamage.value) {
damage = this.hp - 1; damage = this.hp - 1;

View File

@ -4,6 +4,7 @@ import { HeldItemId } from "#enums/held-item-id";
import type { PokemonType } from "#enums/pokemon-type"; import type { PokemonType } from "#enums/pokemon-type";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { Stat, type PermanentStat } from "#enums/stat"; import { Stat, type PermanentStat } from "#enums/stat";
import { StatusEffect } from "#enums/status-effect";
import { ITEM_EFFECT } from "./held-item"; import { ITEM_EFFECT } from "./held-item";
import { import {
type ATTACK_TYPE_BOOST_PARAMS, type ATTACK_TYPE_BOOST_PARAMS,
@ -16,7 +17,10 @@ import {
permanentStatToHeldItem, permanentStatToHeldItem,
} from "./held-items/base-stat-booster"; } from "./held-items/base-stat-booster";
import { type BERRY_PARAMS, BerryHeldItem, berryTypeToHeldItem } from "./held-items/berry"; import { type BERRY_PARAMS, BerryHeldItem, berryTypeToHeldItem } from "./held-items/berry";
import { type BYPASS_SPEED_CHANCE_PARAMS, BypassSpeedChanceHeldItem } from "./held-items/bypass-speed-chance";
import { type CRIT_BOOST_PARAMS, CritBoostHeldItem, SpeciesCritBoostHeldItem } from "./held-items/crit-booster";
import { type EXP_BOOST_PARAMS, ExpBoosterHeldItem } from "./held-items/exp-booster"; import { type EXP_BOOST_PARAMS, ExpBoosterHeldItem } from "./held-items/exp-booster";
import { type FLINCH_CHANCE_PARAMS, FlinchChanceHeldItem } from "./held-items/flinch-chance";
import { type HIT_HEAL_PARAMS, HitHealHeldItem } from "./held-items/hit-heal"; import { type HIT_HEAL_PARAMS, HitHealHeldItem } from "./held-items/hit-heal";
import { InstantReviveHeldItem, type INSTANT_REVIVE_PARAMS } from "./held-items/instant-revive"; import { InstantReviveHeldItem, type INSTANT_REVIVE_PARAMS } from "./held-items/instant-revive";
import { import {
@ -28,8 +32,9 @@ import {
SpeciesStatBoostHeldItem, SpeciesStatBoostHeldItem,
type STAT_BOOST_PARAMS, type STAT_BOOST_PARAMS,
} from "./held-items/stat-booster"; } from "./held-items/stat-booster";
import type { TURN_END_HEAL_PARAMS } from "./held-items/turn-end-heal"; import { type SURVIVE_CHANCE_PARAMS, SurviveChanceHeldItem } from "./held-items/survive-chance";
import { TurnEndHealHeldItem } from "./held-items/turn-end-heal"; import { type TURN_END_HEAL_PARAMS, TurnEndHealHeldItem } from "./held-items/turn-end-heal";
import { type TURN_END_STATUS_PARAMS, TurnEndStatusHeldItem } from "./held-items/turn-end-status";
export const allHeldItems = {}; export const allHeldItems = {};
@ -55,6 +60,7 @@ export function initHeldItems() {
allHeldItems[heldItemType] = new AttackTypeBoosterHeldItem(heldItemType, 99, pokemonType, 0.2); allHeldItems[heldItemType] = new AttackTypeBoosterHeldItem(heldItemType, 99, pokemonType, 0.2);
} }
// Items that boost specific stats
allHeldItems[HeldItemId.EVIOLITE] = new EvolutionStatBoostHeldItem( allHeldItems[HeldItemId.EVIOLITE] = new EvolutionStatBoostHeldItem(
HeldItemId.EVIOLITE, HeldItemId.EVIOLITE,
1, 1,
@ -86,12 +92,27 @@ export function initHeldItems() {
SpeciesId.CLAMPERL, SpeciesId.CLAMPERL,
]); ]);
// Items that boost the crit rate
allHeldItems[HeldItemId.SCOPE_LENS] = new CritBoostHeldItem(HeldItemId.SCOPE_LENS, 1, 1);
allHeldItems[HeldItemId.LEEK] = new SpeciesCritBoostHeldItem(HeldItemId.LEEK, 1, 2, [
SpeciesId.FARFETCHD,
SpeciesId.GALAR_FARFETCHD,
SpeciesId.SIRFETCHD,
]);
allHeldItems[HeldItemId.LUCKY_EGG] = new ExpBoosterHeldItem(HeldItemId.LUCKY_EGG, 99, 40); allHeldItems[HeldItemId.LUCKY_EGG] = new ExpBoosterHeldItem(HeldItemId.LUCKY_EGG, 99, 40);
allHeldItems[HeldItemId.GOLDEN_EGG] = new ExpBoosterHeldItem(HeldItemId.GOLDEN_EGG, 99, 100); allHeldItems[HeldItemId.GOLDEN_EGG] = new ExpBoosterHeldItem(HeldItemId.GOLDEN_EGG, 99, 100);
allHeldItems[HeldItemId.LEFTOVERS] = new TurnEndHealHeldItem(HeldItemId.LEFTOVERS, 4); allHeldItems[HeldItemId.LEFTOVERS] = new TurnEndHealHeldItem(HeldItemId.LEFTOVERS, 4);
allHeldItems[HeldItemId.SHELL_BELL] = new HitHealHeldItem(HeldItemId.SHELL_BELL, 4); allHeldItems[HeldItemId.SHELL_BELL] = new HitHealHeldItem(HeldItemId.SHELL_BELL, 4);
allHeldItems[HeldItemId.FOCUS_BAND] = new SurviveChanceHeldItem(HeldItemId.FOCUS_BAND, 5);
allHeldItems[HeldItemId.QUICK_CLAW] = new BypassSpeedChanceHeldItem(HeldItemId.QUICK_CLAW, 3);
allHeldItems[HeldItemId.KINGS_ROCK] = new FlinchChanceHeldItem(HeldItemId.KINGS_ROCK, 3, 10);
allHeldItems[HeldItemId.FLAME_ORB] = new TurnEndStatusHeldItem(HeldItemId.FLAME_ORB, 1, StatusEffect.BURN);
allHeldItems[HeldItemId.TOXIC_ORB] = new TurnEndStatusHeldItem(HeldItemId.TOXIC_ORB, 1, StatusEffect.TOXIC);
// vitamins // vitamins
for (const [statKey, heldItemType] of Object.entries(permanentStatToHeldItem)) { for (const [statKey, heldItemType] of Object.entries(permanentStatToHeldItem)) {
const stat = Number(statKey) as PermanentStat; const stat = Number(statKey) as PermanentStat;
@ -109,6 +130,11 @@ type APPLY_HELD_ITEMS_PARAMS = {
[ITEM_EFFECT.BASE_STAT_BOOSTER]: BASE_STAT_BOOSTER_PARAMS; [ITEM_EFFECT.BASE_STAT_BOOSTER]: BASE_STAT_BOOSTER_PARAMS;
[ITEM_EFFECT.INSTANT_REVIVE]: INSTANT_REVIVE_PARAMS; [ITEM_EFFECT.INSTANT_REVIVE]: INSTANT_REVIVE_PARAMS;
[ITEM_EFFECT.STAT_BOOST]: STAT_BOOST_PARAMS; [ITEM_EFFECT.STAT_BOOST]: STAT_BOOST_PARAMS;
[ITEM_EFFECT.CRIT_BOOST]: CRIT_BOOST_PARAMS;
[ITEM_EFFECT.TURN_END_STATUS]: TURN_END_STATUS_PARAMS;
[ITEM_EFFECT.SURVIVE_CHANCE]: SURVIVE_CHANCE_PARAMS;
[ITEM_EFFECT.BYPASS_SPEED_CHANCE]: BYPASS_SPEED_CHANCE_PARAMS;
[ITEM_EFFECT.FLINCH_CHANCE]: FLINCH_CHANCE_PARAMS;
}; };
export function applyHeldItems<T extends ITEM_EFFECT>(effect: T, params: APPLY_HELD_ITEMS_PARAMS[T]) { export function applyHeldItems<T extends ITEM_EFFECT>(effect: T, params: APPLY_HELD_ITEMS_PARAMS[T]) {

View File

@ -15,6 +15,11 @@ export const ITEM_EFFECT = {
BASE_STAT_BOOSTER: 7, BASE_STAT_BOOSTER: 7,
INSTANT_REVIVE: 8, INSTANT_REVIVE: 8,
STAT_BOOST: 9, STAT_BOOST: 9,
CRIT_BOOST: 10,
TURN_END_STATUS: 11,
SURVIVE_CHANCE: 12,
BYPASS_SPEED_CHANCE: 13,
FLINCH_CHANCE: 14,
} as const; } as const;
export type ITEM_EFFECT = (typeof ITEM_EFFECT)[keyof typeof ITEM_EFFECT]; export type ITEM_EFFECT = (typeof ITEM_EFFECT)[keyof typeof ITEM_EFFECT];

View File

@ -0,0 +1,62 @@
import type Pokemon from "#app/field/pokemon";
import { HeldItem, ITEM_EFFECT } from "#app/items/held-item";
import type { BooleanHolder } from "#app/utils/common";
import { globalScene } from "#app/global-scene";
import i18next from "i18next";
import { getPokemonNameWithAffix } from "#app/messages";
import { Command } from "#app/ui/command-ui-handler";
export interface BYPASS_SPEED_CHANCE_PARAMS {
/** The pokemon with the item */
pokemon: Pokemon;
doBypassSpeed: BooleanHolder;
}
/**
* 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 BypassSpeedChanceHeldItem extends HeldItem {
public effects: ITEM_EFFECT[] = [ITEM_EFFECT.BYPASS_SPEED_CHANCE];
/**
* Checks if {@linkcode BypassSpeedChanceModifier} should be applied
* @param pokemon the {@linkcode Pokemon} that holds the item
* @param doBypassSpeed {@linkcode BooleanHolder} that is `true` if speed should be bypassed
* @returns `true` if {@linkcode BypassSpeedChanceModifier} should be applied
*/
// override shouldApply(pokemon?: Pokemon, doBypassSpeed?: BooleanHolder): boolean {
// return super.shouldApply(pokemon, doBypassSpeed) && !!doBypassSpeed;
// }
/**
* Applies {@linkcode BypassSpeedChanceModifier}
* @param pokemon the {@linkcode Pokemon} that holds the item
* @param doBypassSpeed {@linkcode BooleanHolder} that is `true` if speed should be bypassed
* @returns `true` if {@linkcode BypassSpeedChanceModifier} has been applied
*/
apply(params: BYPASS_SPEED_CHANCE_PARAMS): boolean {
const pokemon = params.pokemon;
const doBypassSpeed = params.doBypassSpeed;
const stackCount = pokemon.heldItemManager.getStack(this.type);
if (!doBypassSpeed.value && pokemon.randBattleSeedInt(10) < stackCount) {
doBypassSpeed.value = true;
const isCommandFight =
globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]?.command === Command.FIGHT;
if (isCommandFight) {
globalScene.queueMessage(
i18next.t("modifier:bypassSpeedChanceApply", {
pokemonName: getPokemonNameWithAffix(pokemon),
itemName: this.name,
}),
);
}
return true;
}
return false;
}
}

View File

@ -0,0 +1,86 @@
import type Pokemon from "#app/field/pokemon";
import type { NumberHolder } from "#app/utils/common";
import type { HeldItemId } from "#enums/held-item-id";
import type { SpeciesId } from "#enums/species-id";
import { HeldItem, ITEM_EFFECT } from "../held-item";
export interface CRIT_BOOST_PARAMS {
/** The pokemon with the item */
pokemon: Pokemon;
critStage: NumberHolder;
}
/**
* Modifier used for held items that apply critical-hit stage boost(s).
* using a multiplier.
* @extends PokemonHeldItemModifier
* @see {@linkcode apply}
*/
export class CritBoostHeldItem extends HeldItem {
public effects: ITEM_EFFECT[] = [ITEM_EFFECT.CRIT_BOOST];
/** The amount of stages by which the held item increases the current critical-hit stage value */
protected stageIncrement: number;
constructor(type: HeldItemId, maxStackCount = 1, stageIncrement: number) {
super(type, maxStackCount);
this.stageIncrement = stageIncrement;
}
/**
* Increases the current critical-hit stage value by {@linkcode stageIncrement}.
* @param _pokemon {@linkcode Pokemon} N/A
* @param critStage {@linkcode NumberHolder} that holds the resulting critical-hit level
* @returns always `true`
*/
apply(params: CRIT_BOOST_PARAMS): boolean {
params.critStage.value += this.stageIncrement;
return true;
}
}
/**
* Modifier used for held items that apply critical-hit stage boost(s)
* if the holder is of a specific {@linkcode SpeciesId}.
* @extends CritBoosterModifier
* @see {@linkcode shouldApply}
*/
export class SpeciesCritBoostHeldItem extends CritBoostHeldItem {
/** The species that the held item's critical-hit stage boost applies to */
private species: SpeciesId[];
constructor(type: HeldItemId, maxStackCount = 1, stageIncrement: number, species: SpeciesId[]) {
super(type, maxStackCount, stageIncrement);
this.species = species;
}
/**
* Checks if the holder's {@linkcode SpeciesId} (or its fused species) is listed
* in {@linkcode species}.
* @param pokemon {@linkcode Pokemon} that holds the held item
* @param critStage {@linkcode NumberHolder} that holds the resulting critical-hit level
* @returns `true` if the critical-hit level can be incremented, false otherwise
*/
// override shouldApply(pokemon: Pokemon, critStage: NumberHolder): boolean {
// return (
// super.shouldApply(pokemon, critStage) &&
// (this.species.includes(pokemon.getSpeciesForm(true).speciesId) ||
// (pokemon.isFusion() && this.species.includes(pokemon.getFusionSpeciesForm(true).speciesId)))
// );
// }
apply(params: CRIT_BOOST_PARAMS): boolean {
const pokemon = params.pokemon;
const fitsSpecies =
this.species.includes(pokemon.getSpeciesForm(true).speciesId) ||
(pokemon.isFusion() && this.species.includes(pokemon.getFusionSpeciesForm(true).speciesId));
if (fitsSpecies) {
return super.apply(params);
}
return false;
}
}

View File

@ -1,7 +1,6 @@
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import type { NumberHolder } from "#app/utils/common"; import type { NumberHolder } from "#app/utils/common";
import { HeldItemId } from "#enums/held-item-id"; import type { HeldItemId } from "#enums/held-item-id";
import i18next from "i18next";
import { HeldItem, ITEM_EFFECT } from "../held-item"; import { HeldItem, ITEM_EFFECT } from "../held-item";
export interface EXP_BOOST_PARAMS { export interface EXP_BOOST_PARAMS {
@ -20,22 +19,6 @@ export class ExpBoosterHeldItem extends HeldItem {
this.boostMultiplier = boostPercent * 0.01; this.boostMultiplier = boostPercent * 0.01;
} }
get name(): string {
return this.type === HeldItemId.LUCKY_EGG
? i18next.t("modifierType:ModifierType.LUCKY_EGG.name")
: i18next.t("modifierType:ModifierType.GOLDEN_EGG.name");
}
get description(): string {
return this.type === HeldItemId.LUCKY_EGG
? i18next.t("modifierType:ModifierType.LUCKY_EGG.description")
: i18next.t("modifierType:ModifierType.GOLDEN_EGG.description");
}
get icon(): string {
return this.type === HeldItemId.LUCKY_EGG ? "lucky_egg" : "golden_egg";
}
// TODO: What do we do with this? Need to look up all the shouldApply // TODO: What do we do with this? Need to look up all the shouldApply
/** /**
* Checks if {@linkcode PokemonExpBoosterModifier} should be applied * Checks if {@linkcode PokemonExpBoosterModifier} should be applied

View File

@ -0,0 +1,57 @@
import type Pokemon from "#app/field/pokemon";
import { HeldItem, ITEM_EFFECT } from "#app/items/held-item";
import type { BooleanHolder } from "#app/utils/common";
import type { HeldItemId } from "#enums/held-item-id";
export interface FLINCH_CHANCE_PARAMS {
/** The pokemon with the item */
pokemon: Pokemon;
flinched: BooleanHolder;
}
/**
* 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 FlinchChanceHeldItem extends HeldItem {
public effects: ITEM_EFFECT[] = [ITEM_EFFECT.FLINCH_CHANCE];
private chance: number;
constructor(type: HeldItemId, maxStackCount = 1, chance: number) {
super(type, maxStackCount);
this.chance = chance; // 10
}
/**
* Checks if {@linkcode FlinchChanceModifier} should be applied
* @param pokemon the {@linkcode Pokemon} that holds the item
* @param flinched {@linkcode BooleanHolder} that is `true` if the pokemon flinched
* @returns `true` if {@linkcode FlinchChanceModifier} should be applied
*/
// override shouldApply(pokemon?: Pokemon, flinched?: BooleanHolder): boolean {
// return super.shouldApply(pokemon, flinched) && !!flinched;
// }
/**
* Applies {@linkcode FlinchChanceModifier} to randomly flinch targets hit.
* @param pokemon - The {@linkcode Pokemon} that holds the item
* @param flinched - A {@linkcode BooleanHolder} holding whether the pokemon has flinched
* @returns `true` if {@linkcode FlinchChanceModifier} was applied successfully
*/
apply(params: FLINCH_CHANCE_PARAMS): boolean {
const pokemon = params.pokemon;
const flinched = params.flinched;
const stackCount = pokemon.heldItemManager.getStack(this.type);
// The check for pokemon.summonData is to ensure that a crash doesn't occur when a Pokemon with King's Rock procs a flinch
// TODO: Since summonData is always defined now, we can probably remove this
if (pokemon.summonData && !flinched.value && pokemon.randBattleSeedInt(100) < stackCount * this.chance) {
flinched.value = true;
return true;
}
return false;
}
}

View File

@ -0,0 +1,57 @@
import type Pokemon from "#app/field/pokemon";
import { HeldItem, ITEM_EFFECT } from "#app/items/held-item";
import type { BooleanHolder } from "#app/utils/common";
import { globalScene } from "#app/global-scene";
import i18next from "i18next";
import { getPokemonNameWithAffix } from "#app/messages";
export interface SURVIVE_CHANCE_PARAMS {
/** The pokemon with the item */
pokemon: Pokemon;
surviveDamage: BooleanHolder;
}
/**
* 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 SurviveChanceHeldItem extends HeldItem {
public effects: ITEM_EFFECT[] = [ITEM_EFFECT.SURVIVE_CHANCE];
/**
* Checks if the {@linkcode SurviveDamageModifier} should be applied
* @param pokemon the {@linkcode Pokemon} that holds the item
* @param surviveDamage {@linkcode BooleanHolder} that holds the survive damage
* @returns `true` if the {@linkcode SurviveDamageModifier} should be applied
*/
// override shouldApply(pokemon?: Pokemon, surviveDamage?: BooleanHolder): boolean {
// return super.shouldApply(pokemon, surviveDamage) && !!surviveDamage;
// }
/**
* Applies {@linkcode SurviveDamageModifier}
* @param pokemon the {@linkcode Pokemon} that holds the item
* @param surviveDamage {@linkcode BooleanHolder} that holds the survive damage
* @returns `true` if the survive damage has been applied
*/
apply(params: SURVIVE_CHANCE_PARAMS): boolean {
const pokemon = params.pokemon;
const surviveDamage = params.surviveDamage;
const stackCount = pokemon.heldItemManager.getStack(this.type);
if (!surviveDamage.value && pokemon.randBattleSeedInt(10) < stackCount) {
surviveDamage.value = true;
globalScene.queueMessage(
i18next.t("modifier:surviveDamageApply", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
typeName: this.name,
}),
);
return true;
}
return false;
}
}

View File

@ -0,0 +1,40 @@
import type Pokemon from "#app/field/pokemon";
import { HeldItem, ITEM_EFFECT } from "#app/items/held-item";
import type { StatusEffect } from "#enums/status-effect";
import type { HeldItemId } from "#enums/held-item-id";
export interface TURN_END_STATUS_PARAMS {
/** The pokemon with the item */
pokemon: Pokemon;
}
/**
* 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 TurnEndStatusHeldItem extends HeldItem {
public effects: ITEM_EFFECT[] = [ITEM_EFFECT.TURN_END_STATUS];
/** The status effect to be applied by the held item */
private effect: StatusEffect;
constructor(type: HeldItemId, maxStackCount = 1, effect: StatusEffect) {
super(type, maxStackCount);
this.effect = effect;
}
/**
* Tries to inflicts the holder with the associated {@linkcode StatusEffect}.
* @param pokemon {@linkcode Pokemon} that holds the held item
* @returns `true` if the status effect was applied successfully
*/
apply(params: TURN_END_STATUS_PARAMS): boolean {
return params.pokemon.trySetStatus(this.effect, true, undefined, undefined, this.name);
}
getStatusEffect(): StatusEffect {
return this.effect;
}
}

View File

@ -13,7 +13,6 @@ import { LearnMovePhase, LearnMoveType } from "#app/phases/learn-move-phase";
import { LevelUpPhase } from "#app/phases/level-up-phase"; import { LevelUpPhase } from "#app/phases/level-up-phase";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import type { VoucherType } from "#app/system/voucher"; import type { VoucherType } from "#app/system/voucher";
import { Command } from "#app/ui/command-ui-handler";
import { addTextObject, TextStyle } from "#app/ui/text"; import { addTextObject, TextStyle } from "#app/ui/text";
import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, toDmgValue } from "#app/utils/common"; import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, toDmgValue } from "#app/utils/common";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
@ -42,7 +41,6 @@ import {
getModifierType, getModifierType,
ModifierTypeGenerator, ModifierTypeGenerator,
modifierTypes, modifierTypes,
PokemonHeldItemModifierType,
} from "./modifier-type"; } from "./modifier-type";
import { Color, ShadowColor } from "#enums/color"; import { Color, ShadowColor } from "#enums/color";
import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters"; import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters";
@ -1029,438 +1027,6 @@ export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier {
} }
} }
/**
* 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: number, stageIncrement: number, stackCount?: number) {
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 _pokemon {@linkcode Pokemon} N/A
* @param critStage {@linkcode NumberHolder} that holds the resulting critical-hit level
* @returns always `true`
*/
override apply(_pokemon: Pokemon, critStage: NumberHolder): boolean {
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 SpeciesId}.
* @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: SpeciesId[];
constructor(
type: ModifierType,
pokemonId: number,
stageIncrement: number,
species: SpeciesId[],
stackCount?: number,
) {
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 SpeciesId} (or its fused species) is listed
* in {@linkcode species}.
* @param pokemon {@linkcode Pokemon} that holds the held item
* @param critStage {@linkcode NumberHolder} that holds the resulting critical-hit level
* @returns `true` if the critical-hit level can be incremented, false otherwise
*/
override shouldApply(pokemon: Pokemon, critStage: NumberHolder): boolean {
return (
super.shouldApply(pokemon, critStage) &&
(this.species.includes(pokemon.getSpeciesForm(true).speciesId) ||
(pokemon.isFusion() && this.species.includes(pokemon.getFusionSpeciesForm(true).speciesId)))
);
}
}
/**
* Applies Specific Type item boosts (e.g., Magnet)
*/
export class AttackTypeBoosterModifier extends PokemonHeldItemModifier {
public moveType: PokemonType;
private boostMultiplier: number;
constructor(type: ModifierType, pokemonId: number, moveType: PokemonType, boostPercent: number, stackCount?: number) {
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]);
}
/**
* Checks if {@linkcode AttackTypeBoosterModifier} should be applied
* @param pokemon the {@linkcode Pokemon} that holds the held item
* @param moveType the {@linkcode PokemonType} of the move being used
* @param movePower the {@linkcode NumberHolder} that holds the power of the move
* @returns `true` if boosts should be applied to the move.
*/
override shouldApply(pokemon?: Pokemon, moveType?: PokemonType, movePower?: NumberHolder): boolean {
return (
super.shouldApply(pokemon, moveType, movePower) &&
typeof moveType === "number" &&
movePower instanceof NumberHolder &&
this.moveType === moveType
);
}
/**
* Applies {@linkcode AttackTypeBoosterModifier}
* @param pokemon {@linkcode Pokemon} that holds the held item
* @param moveType {@linkcode PokemonType} of the move being used
* @param movePower {@linkcode NumberHolder} that holds the power of the move
* @returns `true` if boosts have been applied to the move.
*/
override apply(_pokemon: Pokemon, moveType: PokemonType, movePower: NumberHolder): boolean {
if (moveType === this.moveType && movePower.value >= 1) {
(movePower as NumberHolder).value = Math.floor(
(movePower as NumberHolder).value * (1 + this.getStackCount() * this.boostMultiplier),
);
return true;
}
return false;
}
getScoreMultiplier(): number {
return 1.2;
}
getMaxHeldItemCount(_pokemon: Pokemon): number {
return 99;
}
}
export class SurviveDamageModifier extends PokemonHeldItemModifier {
matchType(modifier: Modifier): boolean {
return modifier instanceof SurviveDamageModifier;
}
clone() {
return new SurviveDamageModifier(this.type, this.pokemonId, this.stackCount);
}
/**
* Checks if the {@linkcode SurviveDamageModifier} should be applied
* @param pokemon the {@linkcode Pokemon} that holds the item
* @param surviveDamage {@linkcode BooleanHolder} that holds the survive damage
* @returns `true` if the {@linkcode SurviveDamageModifier} should be applied
*/
override shouldApply(pokemon?: Pokemon, surviveDamage?: BooleanHolder): boolean {
return super.shouldApply(pokemon, surviveDamage) && !!surviveDamage;
}
/**
* Applies {@linkcode SurviveDamageModifier}
* @param pokemon the {@linkcode Pokemon} that holds the item
* @param surviveDamage {@linkcode BooleanHolder} that holds the survive damage
* @returns `true` if the survive damage has been applied
*/
override apply(pokemon: Pokemon, surviveDamage: BooleanHolder): boolean {
if (!surviveDamage.value && pokemon.randBattleSeedInt(10) < this.getStackCount()) {
surviveDamage.value = true;
globalScene.queueMessage(
i18next.t("modifier:surviveDamageApply", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
typeName: this.type.name,
}),
);
return true;
}
return false;
}
getMaxHeldItemCount(_pokemon: Pokemon): number {
return 5;
}
}
export class BypassSpeedChanceModifier extends PokemonHeldItemModifier {
matchType(modifier: Modifier) {
return modifier instanceof BypassSpeedChanceModifier;
}
clone() {
return new BypassSpeedChanceModifier(this.type, this.pokemonId, this.stackCount);
}
/**
* Checks if {@linkcode BypassSpeedChanceModifier} should be applied
* @param pokemon the {@linkcode Pokemon} that holds the item
* @param doBypassSpeed {@linkcode BooleanHolder} that is `true` if speed should be bypassed
* @returns `true` if {@linkcode BypassSpeedChanceModifier} should be applied
*/
override shouldApply(pokemon?: Pokemon, doBypassSpeed?: BooleanHolder): boolean {
return super.shouldApply(pokemon, doBypassSpeed) && !!doBypassSpeed;
}
/**
* Applies {@linkcode BypassSpeedChanceModifier}
* @param pokemon the {@linkcode Pokemon} that holds the item
* @param doBypassSpeed {@linkcode BooleanHolder} that is `true` if speed should be bypassed
* @returns `true` if {@linkcode BypassSpeedChanceModifier} has been applied
*/
override apply(pokemon: Pokemon, doBypassSpeed: BooleanHolder): boolean {
if (!doBypassSpeed.value && pokemon.randBattleSeedInt(10) < this.getStackCount()) {
doBypassSpeed.value = true;
const isCommandFight =
globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]?.command === Command.FIGHT;
const hasQuickClaw = this.type instanceof PokemonHeldItemModifierType && this.type.id === "QUICK_CLAW";
if (isCommandFight && hasQuickClaw) {
globalScene.queueMessage(
i18next.t("modifier:bypassSpeedChanceApply", {
pokemonName: getPokemonNameWithAffix(pokemon),
itemName: i18next.t("modifierType:ModifierType.QUICK_CLAW.name"),
}),
);
}
return true;
}
return false;
}
getMaxHeldItemCount(_pokemon: Pokemon): number {
return 3;
}
}
/**
* Class for Pokemon held items like King's Rock
* Because King's Rock can be stacked in PokeRogue, unlike mainline, it does not receive a boost from AbilityId.SERENE_GRACE
*/
export class FlinchChanceModifier extends PokemonHeldItemModifier {
private chance: number;
constructor(type: ModifierType, pokemonId: number, stackCount?: number) {
super(type, pokemonId, stackCount);
this.chance = 10;
}
matchType(modifier: Modifier) {
return modifier instanceof FlinchChanceModifier;
}
clone() {
return new FlinchChanceModifier(this.type, this.pokemonId, this.stackCount);
}
/**
* Checks if {@linkcode FlinchChanceModifier} should be applied
* @param pokemon the {@linkcode Pokemon} that holds the item
* @param flinched {@linkcode BooleanHolder} that is `true` if the pokemon flinched
* @returns `true` if {@linkcode FlinchChanceModifier} should be applied
*/
override shouldApply(pokemon?: Pokemon, flinched?: BooleanHolder): boolean {
return super.shouldApply(pokemon, flinched) && !!flinched;
}
/**
* Applies {@linkcode FlinchChanceModifier} to randomly flinch targets hit.
* @param pokemon - The {@linkcode Pokemon} that holds the item
* @param flinched - A {@linkcode BooleanHolder} holding whether the pokemon has flinched
* @returns `true` if {@linkcode FlinchChanceModifier} was applied successfully
*/
override apply(pokemon: Pokemon, flinched: BooleanHolder): boolean {
// The check for pokemon.summonData is to ensure that a crash doesn't occur when a Pokemon with King's Rock procs a flinch
// TODO: Since summonData is always defined now, we can probably remove this
if (pokemon.summonData && !flinched.value && pokemon.randBattleSeedInt(100) < this.getStackCount() * this.chance) {
flinched.value = true;
return true;
}
return false;
}
getMaxHeldItemCount(_pokemon: Pokemon): number {
return 3;
}
}
export class TurnHealModifier extends PokemonHeldItemModifier {
matchType(modifier: Modifier) {
return modifier instanceof TurnHealModifier;
}
clone() {
return new TurnHealModifier(this.type, this.pokemonId, this.stackCount);
}
/**
* Applies {@linkcode TurnHealModifier}
* @param pokemon The {@linkcode Pokemon} that holds the item
* @returns `true` if the {@linkcode Pokemon} was healed
*/
override apply(pokemon: Pokemon): boolean {
if (!pokemon.isFullHp()) {
globalScene.unshiftPhase(
new PokemonHealPhase(
pokemon.getBattlerIndex(),
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): number {
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: number, stackCount?: number) {
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 pokemon {@linkcode Pokemon} that holds the held item
* @returns `true` if the status effect was applied successfully
*/
override apply(pokemon: Pokemon): boolean {
return pokemon.trySetStatus(this.effect, true, undefined, undefined, this.type.name);
}
getMaxHeldItemCount(_pokemon: Pokemon): number {
return 1;
}
getStatusEffect(): StatusEffect {
return this.effect;
}
}
export class HitHealModifier extends PokemonHeldItemModifier { export class HitHealModifier extends PokemonHeldItemModifier {
matchType(modifier: Modifier) { matchType(modifier: Modifier) {
return modifier instanceof HitHealModifier; return modifier instanceof HitHealModifier;

View File

@ -57,8 +57,6 @@ import {
DamageMoneyRewardModifier, DamageMoneyRewardModifier,
EnemyAttackStatusEffectChanceModifier, EnemyAttackStatusEffectChanceModifier,
EnemyEndureChanceModifier, EnemyEndureChanceModifier,
FlinchChanceModifier,
HitHealModifier,
PokemonMultiHitModifier, PokemonMultiHitModifier,
} from "#app/modifier/modifier"; } from "#app/modifier/modifier";
import { PokemonPhase } from "#app/phases/pokemon-phase"; import { PokemonPhase } from "#app/phases/pokemon-phase";
@ -456,7 +454,7 @@ export class MoveEffectPhase extends PokemonPhase {
if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && !this.move.hitsSubstitute(user, target)) { if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && !this.move.hitsSubstitute(user, target)) {
const flinched = new BooleanHolder(false); const flinched = new BooleanHolder(false);
globalScene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); applyHeldItems(ITEM_EFFECT.FLINCH_CHANCE, { pokemon: user, flinched: flinched });
if (flinched.value) { if (flinched.value) {
target.addTag(BattlerTagType.FLINCHED, undefined, this.move.id, user.id); target.addTag(BattlerTagType.FLINCHED, undefined, this.move.id, user.id);
} }

View File

@ -8,7 +8,6 @@ import { getPokemonNameWithAffix } from "#app/messages";
import { import {
EnemyTurnHealModifier, EnemyTurnHealModifier,
EnemyStatusEffectHealChanceModifier, EnemyStatusEffectHealChanceModifier,
TurnStatusEffectModifier,
TurnHeldItemTransferModifier, TurnHeldItemTransferModifier,
} from "#app/modifier/modifier"; } from "#app/modifier/modifier";
import i18next from "i18next"; import i18next from "i18next";
@ -55,7 +54,7 @@ export class TurnEndPhase extends FieldPhase {
applyPostTurnAbAttrs(PostTurnAbAttr, pokemon); applyPostTurnAbAttrs(PostTurnAbAttr, pokemon);
} }
globalScene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon); applyHeldItems(ITEM_EFFECT.TURN_END_STATUS, { pokemon: pokemon });
globalScene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon); globalScene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon);
pokemon.tempSummonData.turnCount++; pokemon.tempSummonData.turnCount++;

View File

@ -5,7 +5,6 @@ import { AbilityId } from "#enums/ability-id";
import { Stat } from "#app/enums/stat"; import { Stat } from "#app/enums/stat";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon";
import { BypassSpeedChanceModifier } from "#app/modifier/modifier";
import { Command } from "#app/ui/command-ui-handler"; import { Command } from "#app/ui/command-ui-handler";
import { randSeedShuffle, BooleanHolder } from "#app/utils/common"; import { randSeedShuffle, BooleanHolder } from "#app/utils/common";
import { AttemptCapturePhase } from "./attempt-capture-phase"; import { AttemptCapturePhase } from "./attempt-capture-phase";
@ -23,6 +22,8 @@ import { TrickRoomTag } from "#app/data/arena-tag";
import { SwitchType } from "#enums/switch-type"; import { SwitchType } from "#enums/switch-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { TeraPhase } from "./tera-phase"; import { TeraPhase } from "./tera-phase";
import { ITEM_EFFECT } from "#app/items/held-item";
import { applyHeldItems } from "#app/items/all-held-items";
export class TurnStartPhase extends FieldPhase { export class TurnStartPhase extends FieldPhase {
public readonly phaseName = "TurnStartPhase"; public readonly phaseName = "TurnStartPhase";
@ -80,7 +81,7 @@ export class TurnStartPhase extends FieldPhase {
applyAbAttrs(BypassSpeedChanceAbAttr, p, null, false, bypassSpeed); applyAbAttrs(BypassSpeedChanceAbAttr, p, null, false, bypassSpeed);
applyAbAttrs(PreventBypassSpeedChanceAbAttr, p, null, false, bypassSpeed, canCheckHeldItems); applyAbAttrs(PreventBypassSpeedChanceAbAttr, p, null, false, bypassSpeed, canCheckHeldItems);
if (canCheckHeldItems.value) { if (canCheckHeldItems.value) {
globalScene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed); applyHeldItems(ITEM_EFFECT.BYPASS_SPEED_CHANCE, { pokemon: p, doBypassSpeed: bypassSpeed });
} }
battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed; battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed;
}); });