mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-06 16:32: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.
|
* 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.
|
* 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
|
* @param activeOnly - Whether to consider only active pokemon; default `false`
|
||||||
* @returns array of {@linkcode Pokemon}
|
* @returns An array of {@linkcode Pokemon}, as described above.
|
||||||
*/
|
*/
|
||||||
public getField(activeOnly = false): Pokemon[] {
|
public getField(activeOnly = false): Pokemon[] {
|
||||||
const ret = new Array(4).fill(null);
|
const ret = new Array(4).fill(null);
|
||||||
|
@ -6,6 +6,10 @@ export abstract class AbAttr {
|
|||||||
public showAbility: boolean;
|
public showAbility: boolean;
|
||||||
private extraCondition: AbAttrCondition;
|
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) {
|
constructor(showAbility = true) {
|
||||||
this.showAbility = showAbility;
|
this.showAbility = showAbility;
|
||||||
}
|
}
|
||||||
|
@ -4045,7 +4045,13 @@ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr {
|
|||||||
*/
|
*/
|
||||||
export class PostTurnRestoreBerryAbAttr 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()}
|
* @see {@linkcode createEatenBerry()}
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
@ -4054,19 +4060,19 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
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)
|
// Ensure we have at least 1 recoverable berry (at least 1 berry in berriesEaten is not capped)
|
||||||
const cappedBerries = new Set(
|
const cappedBerries = new Set(
|
||||||
globalScene.getModifiers(BerryModifier, pokemon.isPlayer()).filter(
|
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)
|
).map(bm => bm.berryType)
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasBerryUnderCap = pokemon.battleData.berriesEaten.some(
|
this.berriesUnderCap = pokemon.battleData.berriesEaten.filter(
|
||||||
(bt) => !cappedBerries.has(bt)
|
bt => !cappedBerries.has(bt)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!hasBerryUnderCap) {
|
if (!this.berriesUnderCap.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4076,41 +4082,22 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {
|
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
|
* Create a new berry chosen randomly from all berries the pokemon ate this battle
|
||||||
* @param pokemon The pokemon with this ability
|
* @param pokemon - The {@linkcode Pokemon} with this ability
|
||||||
* @param simulated whether the associated ability call is simulated
|
|
||||||
* @returns `true` if a new berry was created
|
* @returns `true` if a new berry was created
|
||||||
*/
|
*/
|
||||||
createEatenBerry(pokemon: Pokemon, simulated: boolean): boolean {
|
createEatenBerry(pokemon: Pokemon): boolean {
|
||||||
// get all berries we just ate that are under cap
|
// Pick a random available berry to yoink
|
||||||
const cappedBerries = new Set(
|
const randomIdx = randSeedInt(this.berriesUnderCap.length);
|
||||||
globalScene.getModifiers(BerryModifier, pokemon.isPlayer()).filter(
|
const chosenBerryType = this.berriesUnderCap[randomIdx];
|
||||||
(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];
|
|
||||||
pokemon.battleData.berriesEaten.splice(randomIdx, 1); // Remove berry from memory
|
pokemon.battleData.berriesEaten.splice(randomIdx, 1); // Remove berry from memory
|
||||||
const chosenBerry = new BerryModifierType(chosenBerryType);
|
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
|
// Add the randomly chosen berry or update the existing one
|
||||||
const berryModifier = globalScene.findModifier(
|
const berryModifier = globalScene.findModifier(
|
||||||
@ -4121,7 +4108,6 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr {
|
|||||||
if (berryModifier) {
|
if (berryModifier) {
|
||||||
berryModifier.stackCount++
|
berryModifier.stackCount++
|
||||||
} else {
|
} else {
|
||||||
// make new modifier
|
|
||||||
const newBerry = new BerryModifier(chosenBerry, pokemon.id, chosenBerryType, 1);
|
const newBerry = new BerryModifier(chosenBerry, pokemon.id, chosenBerryType, 1);
|
||||||
if (pokemon.isPlayer()) {
|
if (pokemon.isPlayer()) {
|
||||||
globalScene.addModifier(newBerry);
|
globalScene.addModifier(newBerry);
|
||||||
@ -4141,19 +4127,19 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr {
|
|||||||
* Used by {@linkcode Abilities.CUD_CHEW}.
|
* Used by {@linkcode Abilities.CUD_CHEW}.
|
||||||
*/
|
*/
|
||||||
export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr {
|
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
|
* @returns `true` if the pokemon ate anything last turn
|
||||||
*/
|
*/
|
||||||
override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
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;
|
return !!pokemon.summonData.berriesEatenLast.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cause this {@linkcode Pokemon} to regurgitate and eat all berries
|
* Cause this {@linkcode Pokemon} to regurgitate and eat all berries inside its `berriesEatenLast` array.
|
||||||
* 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 pokemon - The {@linkcode Pokemon} having a bad tummy ache
|
||||||
* @param _passive - N/A
|
* @param _passive - N/A
|
||||||
* @param _simulated - N/A
|
* @param _simulated - N/A
|
||||||
@ -4161,13 +4147,12 @@ export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr {
|
|||||||
* @param _args - N/A
|
* @param _args - N/A
|
||||||
*/
|
*/
|
||||||
override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder | null, _args: any[]): void {
|
override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder | null, _args: any[]): void {
|
||||||
// play berry animation
|
|
||||||
globalScene.unshiftPhase(
|
globalScene.unshiftPhase(
|
||||||
new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM),
|
new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Re-apply effects of all berries previously scarfed.
|
// 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) {
|
for (const berryType of pokemon.summonData.berriesEatenLast) {
|
||||||
getBerryEffectFunc(berryType)(pokemon);
|
getBerryEffectFunc(berryType)(pokemon);
|
||||||
const bMod = new BerryModifier(new BerryModifierType(berryType), pokemon.id, berryType, 1);
|
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 {
|
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)
|
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`
|
* Move this {@linkcode Pokemon}'s `berriesEaten` array from `PokemonTurnData`
|
||||||
* into `PokemonSummonData`.
|
* into `PokemonSummonData` on turn end.
|
||||||
* @param pokemon The {@linkcode Pokemon} having a nice snack
|
* Both arrays are cleared on switch.
|
||||||
* @param _passive N/A
|
* @param pokemon - The {@linkcode Pokemon} having a nice snack
|
||||||
* @param _simulated N/A
|
* @param _passive - N/A
|
||||||
* @param _args N/A
|
* @param _simulated - N/A
|
||||||
|
* @param _args - N/A
|
||||||
*/
|
*/
|
||||||
override applyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {
|
override applyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {
|
||||||
pokemon.summonData.berriesEatenLast = pokemon.turnData.berriesEaten;
|
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 {
|
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;
|
cancelled.value = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1132,7 +1132,7 @@ export abstract class BattleAnim {
|
|||||||
if (priority === 0) {
|
if (priority === 0) {
|
||||||
// Place the sprite in front of the pokemon on the field.
|
// Place the sprite in front of the pokemon on the field.
|
||||||
targetSprite = globalScene.getEnemyField().find(p => p) ?? globalScene.getPlayerField().find(p => p);
|
targetSprite = globalScene.getEnemyField().find(p => p) ?? globalScene.getPlayerField().find(p => p);
|
||||||
console.log(typeof targetSprite);
|
// console.log(typeof targetSprite);
|
||||||
moveFunc = globalScene.field.moveBelow;
|
moveFunc = globalScene.field.moveBelow;
|
||||||
} else if (priority === 2 && this.bgSprite) {
|
} else if (priority === 2 && this.bgSprite) {
|
||||||
moveFunc = globalScene.field.moveAbove;
|
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;
|
return !target.status && target.canSetStatus(user.status?.effect, true, false, user) ? -10 : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The following needs to be implemented for Thief
|
* Attribute to steal items upon this move's use.
|
||||||
* "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."
|
* Used for {@linkcode Moves.THIEF} and {@linkcode Moves.COVET}.
|
||||||
* "If Knock Off causes a Pokémon with the Sticky Hold Ability to faint, it can now remove that Pokémon's held item."
|
|
||||||
*/
|
*/
|
||||||
export class StealHeldItemChanceAttr extends MoveEffectAttr {
|
export class StealHeldItemChanceAttr extends MoveEffectAttr {
|
||||||
private chance: number;
|
private chance: number;
|
||||||
@ -2553,19 +2553,23 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
|
|||||||
if (rand > this.chance) {
|
if (rand > this.chance) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const heldItems = this.getTargetHeldItems(target).filter((i) => i.isTransferable);
|
const heldItems = this.getTargetHeldItems(target).filter((i) => i.isTransferable);
|
||||||
if (heldItems.length) {
|
if (!heldItems.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD;
|
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 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 tierHeldItems = heldItems.filter((m) => m.type.getOrInferTier(poolType) === highestItemTier);
|
||||||
const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length)];
|
const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length)];
|
||||||
if (globalScene.tryTransferHeldItemModifier(stolenItem, user, false)) {
|
if (!globalScene.tryTransferHeldItemModifier(stolenItem, user, false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
globalScene.queueMessage(i18next.t("moveTriggers:stoleItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name }));
|
globalScene.queueMessage(i18next.t("moveTriggers:stoleItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name }));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
|
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
|
||||||
return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||||
@ -2588,59 +2592,63 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
|
|||||||
* Used for Incinerate and Knock Off.
|
* Used for Incinerate and Knock Off.
|
||||||
* Not Implemented Cases: (Same applies for Thief)
|
* 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 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 {
|
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;
|
private berriesOnly: boolean;
|
||||||
|
|
||||||
constructor(berriesOnly: boolean) {
|
constructor(berriesOnly: boolean = false) {
|
||||||
super(false);
|
super(false);
|
||||||
this.berriesOnly = berriesOnly;
|
this.berriesOnly = berriesOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Attempt to permanently remove a held
|
||||||
* @param user {@linkcode Pokemon} that used the move
|
* @param user - The {@linkcode Pokemon} that used the move
|
||||||
* @param target Target {@linkcode Pokemon} that the moves applies to
|
* @param target - The {@linkcode Pokemon} targeted by the move
|
||||||
* @param move {@linkcode Move} that is used
|
* @param move - N/A
|
||||||
* @param args 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 {
|
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)
|
if (!this.berriesOnly && target.isPlayer()) { // "Wild Pokemon cannot knock off Player Pokemon's held items" (See Bulbapedia)
|
||||||
return false;
|
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);
|
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;
|
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);
|
let heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferable);
|
||||||
|
|
||||||
if (this.berriesOnly) {
|
if (this.berriesOnly) {
|
||||||
heldItems = heldItems.filter(m => m instanceof BerryModifier && m.pokemonId === target.id, target.isPlayer());
|
heldItems = heldItems.filter(m => m instanceof BerryModifier && m.pokemonId === target.id, target.isPlayer());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (heldItems.length) {
|
if (!heldItems.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const removedItem = heldItems[user.randSeedInt(heldItems.length)];
|
const removedItem = heldItems[user.randSeedInt(heldItems.length)];
|
||||||
|
|
||||||
// Decrease item amount and update icon
|
// Decrease item amount and update icon
|
||||||
target.loseHeldItem(removedItem);
|
target.loseHeldItem(removedItem);
|
||||||
globalScene.updateModifiers(target.isPlayer());
|
globalScene.updateModifiers(target.isPlayer());
|
||||||
|
|
||||||
|
|
||||||
if (this.berriesOnly) {
|
if (this.berriesOnly) {
|
||||||
globalScene.queueMessage(i18next.t("moveTriggers:incineratedItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name }));
|
globalScene.queueMessage(i18next.t("moveTriggers:incineratedItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name }));
|
||||||
} else {
|
} else {
|
||||||
globalScene.queueMessage(i18next.t("moveTriggers:knockedOffItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name }));
|
globalScene.queueMessage(i18next.t("moveTriggers:knockedOffItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name }));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -8711,7 +8719,10 @@ export function initMoves() {
|
|||||||
.attr(MultiHitPowerIncrementAttr, 3)
|
.attr(MultiHitPowerIncrementAttr, 3)
|
||||||
.checkAllHits(),
|
.checkAllHits(),
|
||||||
new AttackMove(Moves.THIEF, PokemonType.DARK, MoveCategory.PHYSICAL, 60, 100, 25, -1, 0, 2)
|
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)
|
new StatusMove(Moves.SPIDER_WEB, PokemonType.BUG, -1, 10, -1, 0, 2)
|
||||||
.condition(failIfGhostTypeCondition)
|
.condition(failIfGhostTypeCondition)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1)
|
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1)
|
||||||
@ -9098,7 +9109,10 @@ export function initMoves() {
|
|||||||
.reflectable(),
|
.reflectable(),
|
||||||
new AttackMove(Moves.KNOCK_OFF, PokemonType.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, 0, 3)
|
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(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)
|
new AttackMove(Moves.ENDEAVOR, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 3)
|
||||||
.attr(MatchHpAttr)
|
.attr(MatchHpAttr)
|
||||||
.condition(failOnBossCondition),
|
.condition(failOnBossCondition),
|
||||||
@ -9286,7 +9300,10 @@ export function initMoves() {
|
|||||||
.attr(HighCritAttr)
|
.attr(HighCritAttr)
|
||||||
.attr(StatusEffectAttr, StatusEffect.POISON),
|
.attr(StatusEffectAttr, StatusEffect.POISON),
|
||||||
new AttackMove(Moves.COVET, PokemonType.NORMAL, MoveCategory.PHYSICAL, 60, 100, 25, -1, 0, 3)
|
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)
|
new AttackMove(Moves.VOLT_TACKLE, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 120, 100, 15, 10, 0, 3)
|
||||||
.attr(RecoilAttr, false, 0.33)
|
.attr(RecoilAttr, false, 0.33)
|
||||||
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
||||||
@ -9797,7 +9814,9 @@ export function initMoves() {
|
|||||||
.hidesTarget(),
|
.hidesTarget(),
|
||||||
new AttackMove(Moves.INCINERATE, PokemonType.FIRE, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5)
|
new AttackMove(Moves.INCINERATE, PokemonType.FIRE, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5)
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
.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)
|
new StatusMove(Moves.QUASH, PokemonType.DARK, 100, 15, -1, 0, 5)
|
||||||
.condition(failIfSingleBattle)
|
.condition(failIfSingleBattle)
|
||||||
.condition((user, target, move) => !target.turnData.acted)
|
.condition((user, target, move) => !target.turnData.acted)
|
||||||
|
@ -222,6 +222,7 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
|
|||||||
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger);
|
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Each trainer battle is "supposed" to be a new fight, so reset all per-battle activation effects
|
||||||
pokemon.resetBattleAndWaveData();
|
pokemon.resetBattleAndWaveData();
|
||||||
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
|
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).
|
* 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, defaults to `false`
|
* @param onField `true` to also check if the pokemon is currently on the field; default `false`
|
||||||
* @returns `true` if the pokemon is "active". Returns `false` if there is no active {@linkcode BattleScene}
|
* @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 {
|
public isActive(onField = false): boolean {
|
||||||
if (!globalScene) {
|
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},
|
* Reset a {@linkcode Pokemon}'s per-battle {@linkcode PokemonBattleData | battleData},
|
||||||
as well as any transient {@linkcode PokemonWaveData | waveData} for the current wave.
|
* as well as any transient {@linkcode PokemonWaveData | waveData} for the current wave.
|
||||||
Called before a new battle starts.
|
* Should be called once per arena transition (new biome/trainer battle/Mystery Encounter).
|
||||||
*/
|
*/
|
||||||
resetBattleAndWaveData(): void {
|
resetBattleAndWaveData(): void {
|
||||||
this.battleData = new PokemonBattleData();
|
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}.
|
* 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 {
|
resetWaveData(): void {
|
||||||
this.waveData = new PokemonWaveData();
|
this.waveData = new PokemonWaveData();
|
||||||
@ -6046,7 +6048,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
let fusionPaletteColors: Map<number, number>;
|
let fusionPaletteColors: Map<number, number>;
|
||||||
|
|
||||||
const originalRandom = Math.random;
|
const originalRandom = Math.random;
|
||||||
Math.random = randSeedFloat;
|
Math.random = () => randSeedFloat();
|
||||||
|
|
||||||
globalScene.executeWithSeedOffset(
|
globalScene.executeWithSeedOffset(
|
||||||
() => {
|
() => {
|
||||||
@ -7788,11 +7790,11 @@ export class PokemonSummonData {
|
|||||||
* Resets at the start of a new battle (but not on switch).
|
* Resets at the start of a new battle (but not on switch).
|
||||||
*/
|
*/
|
||||||
export class PokemonBattleData {
|
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;
|
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;
|
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[] = [];
|
public berriesEaten: BerryType[] = [];
|
||||||
|
|
||||||
constructor(source?: PokemonBattleData | Partial<PokemonBattleData>) {
|
constructor(source?: PokemonBattleData | Partial<PokemonBattleData>) {
|
||||||
|
@ -789,6 +789,7 @@ export class BerryModifierType extends PokemonHeldItemModifierType implements Ge
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.berryType = berryType;
|
this.berryType = berryType;
|
||||||
|
this.id = "BERRY"; // needed to prevent harvest item deletion; remove after modifier rework
|
||||||
}
|
}
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
|
@ -58,9 +58,8 @@ export class BattleEndPhase extends BattlePhase {
|
|||||||
globalScene.unshiftPhase(new GameOverPhase(true));
|
globalScene.unshiftPhase(new GameOverPhase(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset pokemon wave turn count, apply post battle effects, etc etc.
|
|
||||||
for (const pokemon of globalScene.getField()) {
|
for (const pokemon of globalScene.getField()) {
|
||||||
if (pokemon?.summonData) {
|
if (pokemon) {
|
||||||
pokemon.summonData.waveTurnCount = 1;
|
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(
|
const lapsingModifiers = globalScene.findModifiers(
|
||||||
m => m instanceof LapsingPersistentModifier || m instanceof LapsingPokemonHeldItemModifier,
|
m => m instanceof LapsingPersistentModifier || m instanceof LapsingPokemonHeldItemModifier,
|
||||||
) as (LapsingPersistentModifier | LapsingPokemonHeldItemModifier)[];
|
) as (LapsingPersistentModifier | LapsingPokemonHeldItemModifier)[];
|
||||||
|
@ -15,7 +15,10 @@ import { CommonAnimPhase } from "./common-anim-phase";
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
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 {
|
export class BerryPhase extends FieldPhase {
|
||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
@ -30,18 +33,17 @@ export class BerryPhase extends FieldPhase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to eat all of a given {@linkcode Pokemon}'s berries once.
|
* 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 {
|
eatBerries(pokemon: Pokemon): void {
|
||||||
// check if we even have anything to eat
|
|
||||||
const hasUsableBerry = !!globalScene.findModifier(m => {
|
const hasUsableBerry = !!globalScene.findModifier(m => {
|
||||||
return m instanceof BerryModifier && m.shouldApply(pokemon);
|
return m instanceof BerryModifier && m.shouldApply(pokemon);
|
||||||
}, pokemon.isPlayer());
|
}, pokemon.isPlayer());
|
||||||
|
|
||||||
if (!hasUsableBerry) {
|
if (!hasUsableBerry) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if any opponents have unnerve to block us from eating berries
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
pokemon.getOpponents().map(opp => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled));
|
pokemon.getOpponents().map(opp => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled));
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
@ -57,7 +59,6 @@ export class BerryPhase extends FieldPhase {
|
|||||||
new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM),
|
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)) {
|
for (const berryModifier of globalScene.applyModifiers(BerryModifier, pokemon.isPlayer(), pokemon)) {
|
||||||
if (berryModifier.consumed) {
|
if (berryModifier.consumed) {
|
||||||
berryModifier.consumed = false;
|
berryModifier.consumed = false;
|
||||||
@ -66,8 +67,6 @@ export class BerryPhase extends FieldPhase {
|
|||||||
// No need to track berries being eaten; already done inside applyModifiers
|
// No need to track berries being eaten; already done inside applyModifiers
|
||||||
globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(berryModifier));
|
globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(berryModifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
// update held modifiers and such
|
|
||||||
globalScene.updateModifiers(pokemon.isPlayer());
|
globalScene.updateModifiers(pokemon.isPlayer());
|
||||||
|
|
||||||
// Abilities.CHEEK_POUCH only works once per round of nom noms
|
// Abilities.CHEEK_POUCH only works once per round of nom noms
|
||||||
|
@ -7,7 +7,8 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase {
|
|||||||
doEncounter(): void {
|
doEncounter(): void {
|
||||||
globalScene.playBgm(undefined, true);
|
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()) {
|
for (const pokemon of globalScene.getPlayerParty()) {
|
||||||
if (pokemon) {
|
if (pokemon) {
|
||||||
pokemon.resetBattleAndWaveData();
|
pokemon.resetBattleAndWaveData();
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { EncounterPhase } from "./encounter-phase";
|
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 {
|
export class NextEncounterPhase extends EncounterPhase {
|
||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
@ -9,7 +13,9 @@ export class NextEncounterPhase extends EncounterPhase {
|
|||||||
doEncounter(): void {
|
doEncounter(): void {
|
||||||
globalScene.playBgm(undefined, true);
|
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()) {
|
for (const pokemon of globalScene.getPlayerParty()) {
|
||||||
if (pokemon) {
|
if (pokemon) {
|
||||||
pokemon.resetWaveData();
|
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) {
|
if (this.switchType !== SwitchType.INITIAL_SWITCH) {
|
||||||
pokemon.resetTurnData();
|
pokemon.resetTurnData();
|
||||||
pokemon.turnData.switchedInThisTurn = true;
|
pokemon.turnData.switchedInThisTurn = true;
|
||||||
|
@ -1198,8 +1198,6 @@ export class GameData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// load modifier data
|
|
||||||
|
|
||||||
if (globalScene.modifiers.length) {
|
if (globalScene.modifiers.length) {
|
||||||
console.warn("Existing modifiers not cleared on session load, deleting...");
|
console.warn("Existing modifiers not cleared on session load, deleting...");
|
||||||
globalScene.modifiers = [];
|
globalScene.modifiers = [];
|
||||||
|
@ -13,27 +13,23 @@ export function deepCopy(values: object): object {
|
|||||||
* This copies all values from `source` that match properties inside `dest`,
|
* This copies all values from `source` that match properties inside `dest`,
|
||||||
* checking recursively for non-null nested objects.
|
* 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.
|
* If it is a non-array object, its properties are recursed into and checked in turn.
|
||||||
* All other values are copied verbatim.
|
* All other values are copied verbatim.
|
||||||
* @param dest The object to merge values into
|
* @param dest - The object to merge values into
|
||||||
* @param source The object to source merged values from
|
* @param source - The object to source merged values from
|
||||||
* @remarks Do not use for regular objects; this is specifically made for JSON copying.
|
* @remarks Do not use for regular objects; this is specifically made for JSON copying.
|
||||||
* @see deepMergeObjects
|
|
||||||
*/
|
*/
|
||||||
export function deepMergeSpriteData(dest: object, source: object) {
|
export function deepMergeSpriteData(dest: object, source: object) {
|
||||||
// Grab all the keys present in both with similar types
|
for (const key of Object.keys(source)) {
|
||||||
const matchingKeys = Object.keys(source).filter(key => {
|
if (
|
||||||
const destVal = dest[key];
|
!(key in dest) ||
|
||||||
const sourceVal = source[key];
|
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
|
// Pure objects get recursed into; everything else gets overwritten
|
||||||
if (typeof source[key] !== "object" || source[key] === null || Array.isArray(source[key])) {
|
if (typeof source[key] !== "object" || source[key] === null || Array.isArray(source[key])) {
|
||||||
dest[key] = source[key];
|
dest[key] = source[key];
|
||||||
|
@ -126,7 +126,7 @@ describe("Abilities - Cud Chew", () => {
|
|||||||
game.move.select(Moves.STUFF_CHEEKS);
|
game.move.select(Moves.STUFF_CHEEKS);
|
||||||
await game.toNextTurn();
|
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([
|
expect(farigiraf.summonData.berriesEatenLast).toEqual([
|
||||||
BerryType.PETAYA,
|
BerryType.PETAYA,
|
||||||
BerryType.PETAYA,
|
BerryType.PETAYA,
|
||||||
@ -145,7 +145,7 @@ describe("Abilities - Cud Chew", () => {
|
|||||||
expect(farigiraf.getStatStage(Stat.ATK)).toBe(4); // 1+2+1
|
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]);
|
await game.classicMode.startBattle([Species.FARIGIRAF, Species.GIRAFARIG]);
|
||||||
|
|
||||||
const farigiraf = game.scene.getPlayerPokemon()!;
|
const farigiraf = game.scene.getPlayerPokemon()!;
|
||||||
@ -157,12 +157,20 @@ describe("Abilities - Cud Chew", () => {
|
|||||||
|
|
||||||
const turn1Hp = farigiraf.hp;
|
const turn1Hp = farigiraf.hp;
|
||||||
game.doSwitchPokemon(1);
|
game.doSwitchPokemon(1);
|
||||||
await game.phaseInterceptor.to("TurnEndPhase");
|
await game.toNextTurn();
|
||||||
|
|
||||||
// summonData got cleared due to switch, turnData got cleared due to turn end
|
// summonData got cleared due to switch, turnData got cleared due to turn end
|
||||||
expect(farigiraf.summonData.berriesEatenLast).toEqual([]);
|
expect(farigiraf.summonData.berriesEatenLast).toEqual([]);
|
||||||
expect(farigiraf.turnData.berriesEaten).toEqual([]);
|
expect(farigiraf.turnData.berriesEaten).toEqual([]);
|
||||||
expect(farigiraf.hp).toEqual(turn1Hp);
|
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 () => {
|
it("clears array if disabled", async () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user