mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-06 00:12:16 +02:00
Applied review comments, cleaned up code a bit
This commit is contained in:
parent
4c3447c851
commit
f82d3529ad
@ -874,8 +874,8 @@ export default class BattleScene extends SceneBase {
|
||||
/**
|
||||
* Returns an array of Pokemon on both sides of the battle - player first, then enemy.
|
||||
* Does not actually check if the pokemon are on the field or not, and always has length 4 regardless of battle type.
|
||||
* @param activeOnly Whether to consider only active pokemon
|
||||
* @returns array of {@linkcode Pokemon}
|
||||
* @param activeOnly - Whether to consider only active pokemon; default `false`
|
||||
* @returns An array of {@linkcode Pokemon}, as described above.
|
||||
*/
|
||||
public getField(activeOnly = false): Pokemon[] {
|
||||
const ret = new Array(4).fill(null);
|
||||
|
@ -6,6 +6,10 @@ export abstract class AbAttr {
|
||||
public showAbility: boolean;
|
||||
private extraCondition: AbAttrCondition;
|
||||
|
||||
/**
|
||||
* @param showAbility - Whether to show this ability as a flyout during battle; default `true`.
|
||||
* Should be kept in parity with mainline where possible.
|
||||
*/
|
||||
constructor(showAbility = true) {
|
||||
this.showAbility = showAbility;
|
||||
}
|
||||
|
@ -4045,7 +4045,13 @@ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr {
|
||||
*/
|
||||
export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr {
|
||||
/**
|
||||
* @param procChance - Chance to create an item
|
||||
* Array containing all {@linkcode BerryType | BerryTypes} that are under cap and able to be restored.
|
||||
* Stored inside the class for a minor performance boost
|
||||
*/
|
||||
private berriesUnderCap: BerryType[]
|
||||
|
||||
/**
|
||||
* @param procChance - function providing chance to restore an item
|
||||
* @see {@linkcode createEatenBerry()}
|
||||
*/
|
||||
constructor(
|
||||
@ -4054,19 +4060,19 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr {
|
||||
super();
|
||||
}
|
||||
|
||||
override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
||||
// check if we have at least 1 recoverable berry (at least 1 berry in berriesEaten is not capped)
|
||||
override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
||||
// Ensure we have at least 1 recoverable berry (at least 1 berry in berriesEaten is not capped)
|
||||
const cappedBerries = new Set(
|
||||
globalScene.getModifiers(BerryModifier, pokemon.isPlayer()).filter(
|
||||
(bm) => bm.pokemonId === pokemon.id && bm.getCountUnderMax() < 1
|
||||
bm => bm.pokemonId === pokemon.id && bm.getCountUnderMax() < 1
|
||||
).map(bm => bm.berryType)
|
||||
);
|
||||
|
||||
const hasBerryUnderCap = pokemon.battleData.berriesEaten.some(
|
||||
(bt) => !cappedBerries.has(bt)
|
||||
this.berriesUnderCap = pokemon.battleData.berriesEaten.filter(
|
||||
bt => !cappedBerries.has(bt)
|
||||
);
|
||||
|
||||
if (!hasBerryUnderCap) {
|
||||
if (!this.berriesUnderCap.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -4076,41 +4082,22 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr {
|
||||
}
|
||||
|
||||
override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {
|
||||
this.createEatenBerry(pokemon, simulated);
|
||||
if (!simulated) {
|
||||
this.createEatenBerry(pokemon);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new berry chosen randomly from the berries the pokemon ate this battle
|
||||
* @param pokemon The pokemon with this ability
|
||||
* @param simulated whether the associated ability call is simulated
|
||||
* Create a new berry chosen randomly from all berries the pokemon ate this battle
|
||||
* @param pokemon - The {@linkcode Pokemon} with this ability
|
||||
* @returns `true` if a new berry was created
|
||||
*/
|
||||
createEatenBerry(pokemon: Pokemon, simulated: boolean): boolean {
|
||||
// get all berries we just ate that are under cap
|
||||
const cappedBerries = new Set(
|
||||
globalScene.getModifiers(BerryModifier, pokemon.isPlayer()).filter(
|
||||
(bm) => bm.pokemonId === pokemon.id && bm.getCountUnderMax() < 1
|
||||
).map((bm) => bm.berryType)
|
||||
);
|
||||
|
||||
const berriesEaten = pokemon.battleData.berriesEaten.filter(
|
||||
(bt) => !cappedBerries.has(bt)
|
||||
);
|
||||
|
||||
if (!berriesEaten.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (simulated) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Pick a random berry to yoink
|
||||
const randomIdx = randSeedInt(berriesEaten.length);
|
||||
const chosenBerryType = berriesEaten[randomIdx];
|
||||
createEatenBerry(pokemon: Pokemon): boolean {
|
||||
// Pick a random available berry to yoink
|
||||
const randomIdx = randSeedInt(this.berriesUnderCap.length);
|
||||
const chosenBerryType = this.berriesUnderCap[randomIdx];
|
||||
pokemon.battleData.berriesEaten.splice(randomIdx, 1); // Remove berry from memory
|
||||
const chosenBerry = new BerryModifierType(chosenBerryType);
|
||||
chosenBerry.id = "BERRY" // needed to prevent item deletion; remove after modifier rework
|
||||
|
||||
// Add the randomly chosen berry or update the existing one
|
||||
const berryModifier = globalScene.findModifier(
|
||||
@ -4121,7 +4108,6 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr {
|
||||
if (berryModifier) {
|
||||
berryModifier.stackCount++
|
||||
} else {
|
||||
// make new modifier
|
||||
const newBerry = new BerryModifier(chosenBerry, pokemon.id, chosenBerryType, 1);
|
||||
if (pokemon.isPlayer()) {
|
||||
globalScene.addModifier(newBerry);
|
||||
@ -4141,19 +4127,19 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr {
|
||||
* Used by {@linkcode Abilities.CUD_CHEW}.
|
||||
*/
|
||||
export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr {
|
||||
// no need for constructor; all it does is set `showAbility` which we override before triggering anyways
|
||||
|
||||
/**
|
||||
* @returns `true` if the pokemon ate anything last turn
|
||||
*/
|
||||
override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
||||
this.showAbility = true; // force ability popup if ability triggers
|
||||
// force ability popup for ability triggers on normal turns.
|
||||
// Still not used if ability doesn't proc
|
||||
this.showAbility = true;
|
||||
return !!pokemon.summonData.berriesEatenLast.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cause this {@linkcode Pokemon} to regurgitate and eat all berries
|
||||
* inside its `berriesEatenLast` array.
|
||||
* Cause this {@linkcode Pokemon} to regurgitate and eat all berries inside its `berriesEatenLast` array.
|
||||
* Triggers a berry use animation, but does *not* count for other berry or item-related abilities.
|
||||
* @param pokemon - The {@linkcode Pokemon} having a bad tummy ache
|
||||
* @param _passive - N/A
|
||||
* @param _simulated - N/A
|
||||
@ -4161,13 +4147,12 @@ export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr {
|
||||
* @param _args - N/A
|
||||
*/
|
||||
override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder | null, _args: any[]): void {
|
||||
// play berry animation
|
||||
globalScene.unshiftPhase(
|
||||
new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM),
|
||||
);
|
||||
|
||||
// Re-apply effects of all berries previously scarfed.
|
||||
// This technically doesn't count as "eating" a berry (for unnerve/stuff cheeks/unburden)
|
||||
// This doesn't count as "eating" a berry (for unnerve/stuff cheeks/unburden) as no item is consumed.
|
||||
for (const berryType of pokemon.summonData.berriesEatenLast) {
|
||||
getBerryEffectFunc(berryType)(pokemon);
|
||||
const bMod = new BerryModifier(new BerryModifierType(berryType), pokemon.id, berryType, 1);
|
||||
@ -4179,7 +4164,7 @@ export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr {
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns always `true`
|
||||
* @returns always `true` as we always want to move berries into summon data
|
||||
*/
|
||||
override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
||||
this.showAbility = false; // don't show popup for turn end berry moving (should ideally be hidden)
|
||||
@ -4188,11 +4173,12 @@ export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr {
|
||||
|
||||
/**
|
||||
* Move this {@linkcode Pokemon}'s `berriesEaten` array from `PokemonTurnData`
|
||||
* into `PokemonSummonData`.
|
||||
* @param pokemon The {@linkcode Pokemon} having a nice snack
|
||||
* @param _passive N/A
|
||||
* @param _simulated N/A
|
||||
* @param _args N/A
|
||||
* into `PokemonSummonData` on turn end.
|
||||
* Both arrays are cleared on switch.
|
||||
* @param pokemon - The {@linkcode Pokemon} having a nice snack
|
||||
* @param _passive - N/A
|
||||
* @param _simulated - N/A
|
||||
* @param _args - N/A
|
||||
*/
|
||||
override applyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {
|
||||
pokemon.summonData.berriesEatenLast = pokemon.turnData.berriesEaten;
|
||||
@ -4565,8 +4551,19 @@ export class DoubleBerryEffectAbAttr extends AbAttr {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute to prevent opposing berry use while on the field.
|
||||
* Used by {@linkcode Abilities.UNNERVE}, {@linkcode Abilities.AS_ONE_GLASTRIER} and {@linkcode Abilities.AS_ONE_SPECTRIER}
|
||||
*/
|
||||
export class PreventBerryUseAbAttr extends AbAttr {
|
||||
override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void {
|
||||
/**
|
||||
* Prevent use of opposing berries.
|
||||
* @param _pokemon - Unused
|
||||
* @param _passive - Unused
|
||||
* @param _simulated - Unused
|
||||
* @param cancelled - {@linkcode BooleanHolder} containing whether to block berry use
|
||||
*/
|
||||
override apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, cancelled: BooleanHolder): void {
|
||||
cancelled.value = true;
|
||||
}
|
||||
}
|
||||
@ -5156,7 +5153,7 @@ export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageC
|
||||
/**
|
||||
* Takes no damage from the first hit of a damaging move.
|
||||
* This is used in the Disguise and Ice Face abilities.
|
||||
*
|
||||
*
|
||||
* Does not apply to a user's substitute
|
||||
* @extends ReceivedMoveDamageMultiplierAbAttr
|
||||
*/
|
||||
|
@ -1132,7 +1132,7 @@ export abstract class BattleAnim {
|
||||
if (priority === 0) {
|
||||
// Place the sprite in front of the pokemon on the field.
|
||||
targetSprite = globalScene.getEnemyField().find(p => p) ?? globalScene.getPlayerField().find(p => p);
|
||||
console.log(typeof targetSprite);
|
||||
// console.log(typeof targetSprite);
|
||||
moveFunc = globalScene.field.moveBelow;
|
||||
} else if (priority === 2 && this.bgSprite) {
|
||||
moveFunc = globalScene.field.moveAbove;
|
||||
|
@ -2535,10 +2535,10 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
|
||||
return !target.status && target.canSetStatus(user.status?.effect, true, false, user) ? -10 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The following needs to be implemented for Thief
|
||||
* "If the user faints due to the target's Ability (Rough Skin or Iron Barbs) or held Rocky Helmet, it cannot remove the target's held item."
|
||||
* "If Knock Off causes a Pokémon with the Sticky Hold Ability to faint, it can now remove that Pokémon's held item."
|
||||
* Attribute to steal items upon this move's use.
|
||||
* Used for {@linkcode Moves.THIEF} and {@linkcode Moves.COVET}.
|
||||
*/
|
||||
export class StealHeldItemChanceAttr extends MoveEffectAttr {
|
||||
private chance: number;
|
||||
@ -2553,18 +2553,22 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
|
||||
if (rand > this.chance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const heldItems = this.getTargetHeldItems(target).filter((i) => i.isTransferable);
|
||||
if (heldItems.length) {
|
||||
const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD;
|
||||
const highestItemTier = heldItems.map((m) => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is the bang after tier correct?
|
||||
const tierHeldItems = heldItems.filter((m) => m.type.getOrInferTier(poolType) === highestItemTier);
|
||||
const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length)];
|
||||
if (globalScene.tryTransferHeldItemModifier(stolenItem, user, false)) {
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:stoleItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name }));
|
||||
return true;
|
||||
}
|
||||
if (!heldItems.length) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD;
|
||||
const highestItemTier = heldItems.map((m) => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is the bang after tier correct?
|
||||
const tierHeldItems = heldItems.filter((m) => m.type.getOrInferTier(poolType) === highestItemTier);
|
||||
const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length)];
|
||||
if (!globalScene.tryTransferHeldItemModifier(stolenItem, user, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:stoleItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name }));
|
||||
return true;
|
||||
}
|
||||
|
||||
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
|
||||
@ -2588,58 +2592,62 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
|
||||
* Used for Incinerate and Knock Off.
|
||||
* Not Implemented Cases: (Same applies for Thief)
|
||||
* "If the user faints due to the target's Ability (Rough Skin or Iron Barbs) or held Rocky Helmet, it cannot remove the target's held item."
|
||||
* "If Knock Off causes a Pokémon with the Sticky Hold Ability to faint, it can now remove that Pokémon's held item."
|
||||
* "If the Pokémon is knocked out by the attack, Sticky Hold does not protect the held item.""
|
||||
*/
|
||||
export class RemoveHeldItemAttr extends MoveEffectAttr {
|
||||
|
||||
/** Optional restriction for item pool to berries only i.e. Differentiating Incinerate and Knock Off */
|
||||
/** Optional restriction for item pool to berries only; i.e. Incinerate */
|
||||
private berriesOnly: boolean;
|
||||
|
||||
constructor(berriesOnly: boolean) {
|
||||
constructor(berriesOnly: boolean = false) {
|
||||
super(false);
|
||||
this.berriesOnly = berriesOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param user {@linkcode Pokemon} that used the move
|
||||
* @param target Target {@linkcode Pokemon} that the moves applies to
|
||||
* @param move {@linkcode Move} that is used
|
||||
* Attempt to permanently remove a held
|
||||
* @param user - The {@linkcode Pokemon} that used the move
|
||||
* @param target - The {@linkcode Pokemon} targeted by the move
|
||||
* @param move - N/A
|
||||
* @param args N/A
|
||||
* @returns True if an item was removed
|
||||
* @returns `true` if an item was able to be removed
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (!this.berriesOnly && target.isPlayer()) { // "Wild Pokemon cannot knock off Player Pokemon's held items" (See Bulbapedia)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for abilities that block item theft
|
||||
// TODO: This should not trigger if the target would faint beforehand
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); // Check for abilities that block item theft
|
||||
applyAbAttrs(BlockItemTheftAbAttr, target, cancelled);
|
||||
|
||||
if (cancelled.value === true) {
|
||||
if (cancelled.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Considers entire transferrable item pool by default (Knock Off). Otherwise berries only if specified (Incinerate).
|
||||
// Considers entire transferrable item pool by default (Knock Off).
|
||||
// Otherwise only consider berries (Incinerate).
|
||||
let heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferable);
|
||||
|
||||
if (this.berriesOnly) {
|
||||
heldItems = heldItems.filter(m => m instanceof BerryModifier && m.pokemonId === target.id, target.isPlayer());
|
||||
}
|
||||
|
||||
if (heldItems.length) {
|
||||
const removedItem = heldItems[user.randSeedInt(heldItems.length)];
|
||||
if (!heldItems.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decrease item amount and update icon
|
||||
target.loseHeldItem(removedItem);
|
||||
globalScene.updateModifiers(target.isPlayer());
|
||||
const removedItem = heldItems[user.randSeedInt(heldItems.length)];
|
||||
|
||||
// Decrease item amount and update icon
|
||||
target.loseHeldItem(removedItem);
|
||||
globalScene.updateModifiers(target.isPlayer());
|
||||
|
||||
if (this.berriesOnly) {
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:incineratedItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name }));
|
||||
} else {
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:knockedOffItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name }));
|
||||
}
|
||||
if (this.berriesOnly) {
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:incineratedItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name }));
|
||||
} else {
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:knockedOffItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name }));
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -6349,11 +6357,11 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
|
||||
if (!allyPokemon?.isActive(true) && switchOutTarget.hp) {
|
||||
globalScene.pushPhase(new BattleEndPhase(false));
|
||||
|
||||
|
||||
if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) {
|
||||
globalScene.pushPhase(new SelectBiomePhase());
|
||||
}
|
||||
|
||||
|
||||
globalScene.pushPhase(new NewBattlePhase());
|
||||
}
|
||||
}
|
||||
@ -8711,7 +8719,10 @@ export function initMoves() {
|
||||
.attr(MultiHitPowerIncrementAttr, 3)
|
||||
.checkAllHits(),
|
||||
new AttackMove(Moves.THIEF, PokemonType.DARK, MoveCategory.PHYSICAL, 60, 100, 25, -1, 0, 2)
|
||||
.attr(StealHeldItemChanceAttr, 0.3),
|
||||
.attr(StealHeldItemChanceAttr, 0.3)
|
||||
.edgeCase(),
|
||||
// Should not be able to steal held item if user faints due to Rough Skin, Iron Barbs, etc.
|
||||
// Should be able to steal items from pokemon with Sticky Hold if the damage causes them to faint
|
||||
new StatusMove(Moves.SPIDER_WEB, PokemonType.BUG, -1, 10, -1, 0, 2)
|
||||
.condition(failIfGhostTypeCondition)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1)
|
||||
@ -9098,7 +9109,10 @@ export function initMoves() {
|
||||
.reflectable(),
|
||||
new AttackMove(Moves.KNOCK_OFF, PokemonType.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, 0, 3)
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getHeldItems().filter(i => i.isTransferable).length > 0 ? 1.5 : 1)
|
||||
.attr(RemoveHeldItemAttr, false),
|
||||
.attr(RemoveHeldItemAttr, false)
|
||||
.edgeCase(),
|
||||
// Should not be able to remove held item if user faints due to Rough Skin, Iron Barbs, etc.
|
||||
// Should be able to remove items from pokemon with Sticky Hold if the damage causes them to faint
|
||||
new AttackMove(Moves.ENDEAVOR, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 3)
|
||||
.attr(MatchHpAttr)
|
||||
.condition(failOnBossCondition),
|
||||
@ -9286,7 +9300,10 @@ export function initMoves() {
|
||||
.attr(HighCritAttr)
|
||||
.attr(StatusEffectAttr, StatusEffect.POISON),
|
||||
new AttackMove(Moves.COVET, PokemonType.NORMAL, MoveCategory.PHYSICAL, 60, 100, 25, -1, 0, 3)
|
||||
.attr(StealHeldItemChanceAttr, 0.3),
|
||||
.attr(StealHeldItemChanceAttr, 0.3)
|
||||
.edgeCase(),
|
||||
// Should not be able to steal held item if user faints due to Rough Skin, Iron Barbs, etc.
|
||||
// Should be able to steal items from pokemon with Sticky Hold if the damage causes them to faint
|
||||
new AttackMove(Moves.VOLT_TACKLE, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 120, 100, 15, 10, 0, 3)
|
||||
.attr(RecoilAttr, false, 0.33)
|
||||
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
||||
@ -9797,7 +9814,9 @@ export function initMoves() {
|
||||
.hidesTarget(),
|
||||
new AttackMove(Moves.INCINERATE, PokemonType.FIRE, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
||||
.attr(RemoveHeldItemAttr, true),
|
||||
.attr(RemoveHeldItemAttr, true)
|
||||
.edgeCase(),
|
||||
// Should be able to remove items from pokemon with Sticky Hold if the damage causes them to faint
|
||||
new StatusMove(Moves.QUASH, PokemonType.DARK, 100, 15, -1, 0, 5)
|
||||
.condition(failIfSingleBattle)
|
||||
.condition((user, target, move) => !target.turnData.acted)
|
||||
|
@ -222,6 +222,7 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
|
||||
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger);
|
||||
}
|
||||
|
||||
// Each trainer battle is "supposed" to be a new fight, so reset all per-battle activation effects
|
||||
pokemon.resetBattleAndWaveData();
|
||||
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
|
||||
}
|
||||
|
@ -676,9 +676,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the pokemon is allowed in battle (ie: not fainted, and allowed under any active challenges).
|
||||
* @param onField `true` to also check if the pokemon is currently on the field, defaults to `false`
|
||||
* @returns `true` if the pokemon is "active". Returns `false` if there is no active {@linkcode BattleScene}
|
||||
* Checks if this {@linkcode Pokemon} is allowed in battle (ie: not fainted, and allowed under any active challenges).
|
||||
* @param onField `true` to also check if the pokemon is currently on the field; default `false`
|
||||
* @returns `true` if the pokemon is "active", as described above.
|
||||
* Returns `false` if there is no active {@linkcode BattleScene} or the pokemon is disallowed.
|
||||
*/
|
||||
public isActive(onField = false): boolean {
|
||||
if (!globalScene) {
|
||||
@ -5696,9 +5697,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
/**
|
||||
Reset a {@linkcode Pokemon}'s per-battle {@linkcode PokemonBattleData | battleData},
|
||||
as well as any transient {@linkcode PokemonWaveData | waveData} for the current wave.
|
||||
Called before a new battle starts.
|
||||
* Reset a {@linkcode Pokemon}'s per-battle {@linkcode PokemonBattleData | battleData},
|
||||
* as well as any transient {@linkcode PokemonWaveData | waveData} for the current wave.
|
||||
* Should be called once per arena transition (new biome/trainer battle/Mystery Encounter).
|
||||
*/
|
||||
resetBattleAndWaveData(): void {
|
||||
this.battleData = new PokemonBattleData();
|
||||
@ -5707,7 +5708,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
/**
|
||||
* Reset a {@linkcode Pokemon}'s {@linkcode PokemonWaveData | waveData}.
|
||||
* Called once per new wave start as well as by {@linkcode resetBattleAndWaveData}.
|
||||
* Should be called upon starting a new wave in addition to whenever an arena transition occurs.
|
||||
* @see {@linkcode resetBattleAndWaveData()}
|
||||
*/
|
||||
resetWaveData(): void {
|
||||
this.waveData = new PokemonWaveData();
|
||||
@ -6046,7 +6048,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
let fusionPaletteColors: Map<number, number>;
|
||||
|
||||
const originalRandom = Math.random;
|
||||
Math.random = randSeedFloat;
|
||||
Math.random = () => randSeedFloat();
|
||||
|
||||
globalScene.executeWithSeedOffset(
|
||||
() => {
|
||||
@ -7788,11 +7790,11 @@ export class PokemonSummonData {
|
||||
* Resets at the start of a new battle (but not on switch).
|
||||
*/
|
||||
export class PokemonBattleData {
|
||||
/** counts the hits the pokemon received during this battle; used for {@linkcode Moves.RAGE_FIST} */
|
||||
/** Counter tracking direct hits this Pokemon has received during this battle; used for {@linkcode Moves.RAGE_FIST} */
|
||||
public hitCount = 0;
|
||||
/** Whether this has eaten a berry this battle; used for {@linkcode Moves.BELCH} */
|
||||
/** Whether this Pokemon has eaten a berry this battle; used for {@linkcode Moves.BELCH} */
|
||||
public hasEatenBerry: boolean = false;
|
||||
/** A list of all berries eaten in this current battle; used by {@linkcode Abilities.HARVEST} */
|
||||
/** Array containing all berries eaten in this current battle; used by {@linkcode Abilities.HARVEST} */
|
||||
public berriesEaten: BerryType[] = [];
|
||||
|
||||
constructor(source?: PokemonBattleData | Partial<PokemonBattleData>) {
|
||||
|
@ -789,6 +789,7 @@ export class BerryModifierType extends PokemonHeldItemModifierType implements Ge
|
||||
);
|
||||
|
||||
this.berryType = berryType;
|
||||
this.id = "BERRY"; // needed to prevent harvest item deletion; remove after modifier rework
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
|
@ -58,9 +58,8 @@ export class BattleEndPhase extends BattlePhase {
|
||||
globalScene.unshiftPhase(new GameOverPhase(true));
|
||||
}
|
||||
|
||||
// reset pokemon wave turn count, apply post battle effects, etc etc.
|
||||
for (const pokemon of globalScene.getField()) {
|
||||
if (pokemon?.summonData) {
|
||||
if (pokemon) {
|
||||
pokemon.summonData.waveTurnCount = 1;
|
||||
}
|
||||
}
|
||||
@ -82,7 +81,6 @@ export class BattleEndPhase extends BattlePhase {
|
||||
}
|
||||
}
|
||||
|
||||
// lapse all post battle modifiers that should lapse
|
||||
const lapsingModifiers = globalScene.findModifiers(
|
||||
m => m instanceof LapsingPersistentModifier || m instanceof LapsingPokemonHeldItemModifier,
|
||||
) as (LapsingPersistentModifier | LapsingPokemonHeldItemModifier)[];
|
||||
|
@ -15,7 +15,10 @@ import { CommonAnimPhase } from "./common-anim-phase";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
|
||||
/** The phase after attacks where the pokemon eat berries */
|
||||
/**
|
||||
* The phase after attacks where the pokemon eat berries.
|
||||
* Also triggers Cud Chew's "repeat berry use" effects
|
||||
*/
|
||||
export class BerryPhase extends FieldPhase {
|
||||
start() {
|
||||
super.start();
|
||||
@ -30,18 +33,17 @@ export class BerryPhase extends FieldPhase {
|
||||
|
||||
/**
|
||||
* Attempt to eat all of a given {@linkcode Pokemon}'s berries once.
|
||||
* @param pokemon The {@linkcode Pokemon} to check
|
||||
* @param pokemon - The {@linkcode Pokemon} to check
|
||||
*/
|
||||
eatBerries(pokemon: Pokemon): void {
|
||||
// check if we even have anything to eat
|
||||
const hasUsableBerry = !!globalScene.findModifier(m => {
|
||||
return m instanceof BerryModifier && m.shouldApply(pokemon);
|
||||
}, pokemon.isPlayer());
|
||||
|
||||
if (!hasUsableBerry) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if any opponents have unnerve to block us from eating berries
|
||||
const cancelled = new BooleanHolder(false);
|
||||
pokemon.getOpponents().map(opp => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled));
|
||||
if (cancelled.value) {
|
||||
@ -57,7 +59,6 @@ export class BerryPhase extends FieldPhase {
|
||||
new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM),
|
||||
);
|
||||
|
||||
// try to apply all berry modifiers for this pokemon
|
||||
for (const berryModifier of globalScene.applyModifiers(BerryModifier, pokemon.isPlayer(), pokemon)) {
|
||||
if (berryModifier.consumed) {
|
||||
berryModifier.consumed = false;
|
||||
@ -66,8 +67,6 @@ export class BerryPhase extends FieldPhase {
|
||||
// No need to track berries being eaten; already done inside applyModifiers
|
||||
globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(berryModifier));
|
||||
}
|
||||
|
||||
// update held modifiers and such
|
||||
globalScene.updateModifiers(pokemon.isPlayer());
|
||||
|
||||
// Abilities.CHEEK_POUCH only works once per round of nom noms
|
||||
|
@ -7,7 +7,8 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase {
|
||||
doEncounter(): void {
|
||||
globalScene.playBgm(undefined, true);
|
||||
|
||||
// reset all battle data, perform form changes, etc.
|
||||
// Reset all battle and wave data, perform form changes, etc.
|
||||
// We do this because new biomes are considered "arena transitions" akin to MEs and trainer battles
|
||||
for (const pokemon of globalScene.getPlayerParty()) {
|
||||
if (pokemon) {
|
||||
pokemon.resetBattleAndWaveData();
|
||||
|
@ -1,6 +1,10 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { EncounterPhase } from "./encounter-phase";
|
||||
|
||||
/**
|
||||
* The phase between defeating an encounter and starting another wild wave.
|
||||
* Handles generating, loading and preparing for it.
|
||||
*/
|
||||
export class NextEncounterPhase extends EncounterPhase {
|
||||
start() {
|
||||
super.start();
|
||||
@ -9,7 +13,9 @@ export class NextEncounterPhase extends EncounterPhase {
|
||||
doEncounter(): void {
|
||||
globalScene.playBgm(undefined, true);
|
||||
|
||||
// Reset all player transient wave data/intel.
|
||||
// Reset all player transient wave data/intel before starting a new wild encounter.
|
||||
// We exclusively reset wave data here as wild waves are considered one continuous "battle"
|
||||
// for lack of an arena transition.
|
||||
for (const pokemon of globalScene.getPlayerParty()) {
|
||||
if (pokemon) {
|
||||
pokemon.resetWaveData();
|
||||
|
@ -240,6 +240,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||
}
|
||||
}
|
||||
|
||||
// No need (or particular use) resetting turn data here on initial send in
|
||||
if (this.switchType !== SwitchType.INITIAL_SWITCH) {
|
||||
pokemon.resetTurnData();
|
||||
pokemon.turnData.switchedInThisTurn = true;
|
||||
|
@ -1198,8 +1198,6 @@ export class GameData {
|
||||
}
|
||||
}
|
||||
|
||||
// load modifier data
|
||||
|
||||
if (globalScene.modifiers.length) {
|
||||
console.warn("Existing modifiers not cleared on session load, deleting...");
|
||||
globalScene.modifiers = [];
|
||||
|
@ -13,27 +13,23 @@ export function deepCopy(values: object): object {
|
||||
* This copies all values from `source` that match properties inside `dest`,
|
||||
* checking recursively for non-null nested objects.
|
||||
|
||||
* If a property in `src` does not exist in `dest` or its `typeof` evaluates differently, it is skipped.
|
||||
* If a property in `source` does not exist in `dest` or its `typeof` evaluates differently, it is skipped.
|
||||
* If it is a non-array object, its properties are recursed into and checked in turn.
|
||||
* All other values are copied verbatim.
|
||||
* @param dest The object to merge values into
|
||||
* @param source The object to source merged values from
|
||||
* @param dest - The object to merge values into
|
||||
* @param source - The object to source merged values from
|
||||
* @remarks Do not use for regular objects; this is specifically made for JSON copying.
|
||||
* @see deepMergeObjects
|
||||
*/
|
||||
export function deepMergeSpriteData(dest: object, source: object) {
|
||||
// Grab all the keys present in both with similar types
|
||||
const matchingKeys = Object.keys(source).filter(key => {
|
||||
const destVal = dest[key];
|
||||
const sourceVal = source[key];
|
||||
for (const key of Object.keys(source)) {
|
||||
if (
|
||||
!(key in dest) ||
|
||||
typeof source[key] !== typeof dest[key] ||
|
||||
Array.isArray(source[key]) !== Array.isArray(dest[key])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return (
|
||||
// 1st part somewhat redundant, but makes it clear that we're explicitly interested in properties that exist in both
|
||||
key in source && Array.isArray(sourceVal) === Array.isArray(destVal) && typeof sourceVal === typeof destVal
|
||||
);
|
||||
});
|
||||
|
||||
for (const key of matchingKeys) {
|
||||
// Pure objects get recursed into; everything else gets overwritten
|
||||
if (typeof source[key] !== "object" || source[key] === null || Array.isArray(source[key])) {
|
||||
dest[key] = source[key];
|
||||
|
@ -126,7 +126,7 @@ describe("Abilities - Cud Chew", () => {
|
||||
game.move.select(Moves.STUFF_CHEEKS);
|
||||
await game.toNextTurn();
|
||||
|
||||
// Ate 2 petayas from moves + 1 of each at turn end; all 4 get moved on turn end
|
||||
// Ate 2 petayas from moves + 1 of each at turn end; all 4 get tallied on turn end
|
||||
expect(farigiraf.summonData.berriesEatenLast).toEqual([
|
||||
BerryType.PETAYA,
|
||||
BerryType.PETAYA,
|
||||
@ -145,7 +145,7 @@ describe("Abilities - Cud Chew", () => {
|
||||
expect(farigiraf.getStatStage(Stat.ATK)).toBe(4); // 1+2+1
|
||||
});
|
||||
|
||||
it("resets array on switch", async () => {
|
||||
it("should reset both arrays on switch", async () => {
|
||||
await game.classicMode.startBattle([Species.FARIGIRAF, Species.GIRAFARIG]);
|
||||
|
||||
const farigiraf = game.scene.getPlayerPokemon()!;
|
||||
@ -157,12 +157,20 @@ describe("Abilities - Cud Chew", () => {
|
||||
|
||||
const turn1Hp = farigiraf.hp;
|
||||
game.doSwitchPokemon(1);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
await game.toNextTurn();
|
||||
|
||||
// summonData got cleared due to switch, turnData got cleared due to turn end
|
||||
expect(farigiraf.summonData.berriesEatenLast).toEqual([]);
|
||||
expect(farigiraf.turnData.berriesEaten).toEqual([]);
|
||||
expect(farigiraf.hp).toEqual(turn1Hp);
|
||||
|
||||
game.doSwitchPokemon(1);
|
||||
await game.toNextTurn();
|
||||
|
||||
// TurnData gets cleared while switching in
|
||||
expect(farigiraf.summonData.berriesEatenLast).toEqual([]);
|
||||
expect(farigiraf.turnData.berriesEaten).toEqual([]);
|
||||
expect(farigiraf.hp).toEqual(turn1Hp);
|
||||
});
|
||||
|
||||
it("clears array if disabled", async () => {
|
||||
|
Loading…
Reference in New Issue
Block a user