From b996624b038c603cfa564ad7b915507ab7277fcd Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Sat, 7 Jun 2025 12:08:21 -0400 Subject: [PATCH] Removed `instant` and `ignoreUpdate` parameters from `tryTransferHeldItemModifier`; fixed post-battle loot code to _not_ break type safety --- src/battle-scene.ts | 40 ++++++++++++----------- src/battle.ts | 17 +++------- src/data/abilities/ability.ts | 49 +++++++++++++++++++---------- src/field/pokemon.ts | 3 +- src/phases/select-modifier-phase.ts | 2 -- src/phases/switch-summon-phase.ts | 11 +------ src/ui/battle-info/battle-info.ts | 19 ++++++++--- 7 files changed, 76 insertions(+), 65 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 7659a9e68bf..a5f94818efd 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2925,10 +2925,10 @@ export default class BattleScene extends SceneBase { addModifier( modifier: Modifier | null, - ignoreUpdate?: boolean, - playSound?: boolean, - virtual?: boolean, - instant?: boolean, + ignoreUpdate = false, + playSound = false, + virtual = false, + instant = false, cost?: number, ): boolean { // We check against modifier.type to stop a bug related to loading in a pokemon that has a form change item, which prior to some patch @@ -2942,7 +2942,7 @@ export default class BattleScene extends SceneBase { this.validateAchvs(ModifierAchv, modifier); const modifiersToRemove: PersistentModifier[] = []; if (modifier instanceof PersistentModifier) { - if ((modifier as PersistentModifier).add(this.modifiers, !!virtual)) { + if ((modifier as PersistentModifier).add(this.modifiers, virtual)) { if (modifier instanceof PokemonFormChangeItemModifier) { const pokemon = this.getPokemonById(modifier.pokemonId); if (pokemon) { @@ -3015,7 +3015,7 @@ export default class BattleScene extends SceneBase { return success; } - addEnemyModifier(modifier: PersistentModifier, ignoreUpdate?: boolean, instant?: boolean): Promise { + addEnemyModifier(modifier: PersistentModifier, ignoreUpdate = false, instant = false): Promise { return new Promise(resolve => { const modifiersToRemove: PersistentModifier[] = []; if ((modifier as PersistentModifier).add(this.enemyModifiers, false)) { @@ -3046,9 +3046,7 @@ export default class BattleScene extends SceneBase { * @param itemModifier - {@linkcode PokemonHeldItemModifier} to transfer (represents whole stack) * @param target - Recipient {@linkcode Pokemon} recieving items * @param playSound - Whether to play a sound when transferring the item - * @param transferQuantity - How many items of the stack to transfer. Optional, defaults to `1` - * @param instant - ??? (Optional) - * @param ignoreUpdate - ??? (Optional) + * @param transferQuantity - How many items of the stack to transfer. Optional, default `1` * @param itemLost - Whether to treat the item's current holder as losing the item (for now, this simply enables Unburden). Default: `true`. * @returns Whether the transfer was successful */ @@ -3057,8 +3055,6 @@ export default class BattleScene extends SceneBase { target: Pokemon, playSound: boolean, transferQuantity = 1, - instant?: boolean, - ignoreUpdate?: boolean, itemLost = true, ): boolean { const source = itemModifier.getPokemon(); @@ -3106,8 +3102,8 @@ export default class BattleScene extends SceneBase { } // TODO: what does this do and why is it here - if (source.isPlayer() !== target.isPlayer() && !ignoreUpdate) { - this.updateModifiers(source.isPlayer(), instant); + if (source.isPlayer() !== target.isPlayer()) { + this.updateModifiers(source.isPlayer(), false); } // Add however much we took to the recieving pokemon, creating a new modifier if the target lacked one prio @@ -3118,9 +3114,9 @@ export default class BattleScene extends SceneBase { newItemModifier.pokemonId = target.id; newItemModifier.stackCount = countTaken; if (target.isPlayer()) { - this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant); + this.addModifier(newItemModifier, false, playSound); } else { - this.addEnemyModifier(newItemModifier, ignoreUpdate, instant); + this.addEnemyModifier(newItemModifier); } } @@ -3286,8 +3282,10 @@ export default class BattleScene extends SceneBase { [this.modifierBar, this.enemyModifierBar].map(m => m.setVisible(visible)); } - // TODO: Document this - updateModifiers(player = true, instant?: boolean): void { + /** + * @param instant - Whether to instantly update any changes to party members' HP bars; default `false` + */ + updateModifiers(player = true, instant = false): void { const modifiers = player ? this.modifiers : (this.enemyModifiers as PersistentModifier[]); for (let m = 0; m < modifiers.length; m++) { const modifier = modifiers[m]; @@ -3319,7 +3317,13 @@ export default class BattleScene extends SceneBase { } } - updatePartyForModifiers(party: Pokemon[], instant?: boolean): Promise { + /** + * Update one or more Pokemon's info containers after having recieved modifiers. + * @param party - An array of {@linkcode Pokemon} to update info. + * @param instant - Whether to instantly update any changes to the party's HP bars; default `false` + * @returns A Promise that resolves once all the info containers have been updated. + */ + updatePartyForModifiers(party: Pokemon[], instant = false): Promise { return new Promise(resolve => { Promise.allSettled( party.map(p => { diff --git a/src/battle.ts b/src/battle.ts index 8e63a680c06..920ea647b9c 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -175,18 +175,11 @@ export default class Battle { } addPostBattleLoot(enemyPokemon: EnemyPokemon): void { - this.postBattleLoot.push( - ...globalScene - .findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferable, - false, - ) - .map(i => { - const ret = i as PokemonHeldItemModifier; - //@ts-ignore - this is awful to fix/change - ret.pokemonId = null; - return ret; - }), + this.postBattleLoot.concat( + globalScene.findModifiers( + m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferable, + false, + ) as PokemonHeldItemModifier[], ); } diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 8e574f11ed1..e9971c91be7 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -4724,31 +4724,46 @@ export class PostBattleAbAttr extends AbAttr { } export class PostBattleLootAbAttr extends PostBattleAbAttr { - private randItem?: PokemonHeldItemModifier; - - override canApplyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const postBattleLoot = globalScene.currentBattle.postBattleLoot; - if (!simulated && postBattleLoot.length && args[0]) { - this.randItem = randSeedItem(postBattleLoot); - return globalScene.canTransferHeldItemModifier(this.randItem, pokemon, 1); - } - return false; - } + /** The index of the random item to steal. */ + private randItemIndex: number = 0; /** * @param args - `[0]`: boolean for if the battle ended in a victory */ - override applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override canApplyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: [boolean]): boolean { const postBattleLoot = globalScene.currentBattle.postBattleLoot; - if (!this.randItem) { - this.randItem = randSeedItem(postBattleLoot); + const wasVictory = args[0]; + if (simulated || postBattleLoot.length === 0 || !wasVictory) { + return false; } - if (globalScene.tryTransferHeldItemModifier(this.randItem, pokemon, true, 1, true, undefined, false)) { - postBattleLoot.splice(postBattleLoot.indexOf(this.randItem), 1); - globalScene.queueMessage(i18next.t("abilityTriggers:postBattleLoot", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), itemName: this.randItem.type.name })); + // Pick a random item and check if we are capped. + this.randItemIndex = randSeedInt(postBattleLoot.length); + const item = postBattleLoot[this.randItemIndex] + + // We can't use `canTransferItemModifier` as that assumes the Pokemon in question already exists (which it does not) + const existingItem = globalScene.findModifier( + (m): m is PokemonHeldItemModifier => + m instanceof PokemonHeldItemModifier && m.matchType(item) && m.pokemonId === pokemon.id, + pokemon.isPlayer(), + ) as PokemonHeldItemModifier | undefined; + + return (existingItem?.getCountUnderMax() ?? Number.MAX_SAFE_INTEGER) > 1 + } + + /** + * Attempt to give the previously selected random item to the ability holder at battle end. + * @param args - `[0]`: boolean for if the battle ended in a victory + */ + override applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: [boolean]): void { + const postBattleLoot = globalScene.currentBattle.postBattleLoot; + const item = postBattleLoot[this.randItemIndex] + item.pokemonId = pokemon.id; + + if (globalScene.addModifier(item, false, true)) { + postBattleLoot.splice(this.randItemIndex, 1); + globalScene.queueMessage(i18next.t("abilityTriggers:postBattleLoot", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), itemName: item.type.name })); } - this.randItem = undefined; } } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 6465acca0ac..9af6f9611b3 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -6005,9 +6005,8 @@ export class PlayerPokemon extends Pokemon { true, ) as PokemonHeldItemModifier[]; for (const modifier of fusedPartyMemberHeldModifiers) { - globalScene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true, false); + globalScene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), false); } - globalScene.updateModifiers(true, true); globalScene.removePartyMemberModifiers(fusedPartyMemberIndex); globalScene.getPlayerParty().splice(fusedPartyMemberIndex, 1)[0]; const newPartyMemberIndex = globalScene.getPlayerParty().indexOf(this); diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 5f11441333b..89496f66396 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -162,8 +162,6 @@ export class SelectModifierPhase extends BattlePhase { party[toSlotIndex], true, itemQuantity, - undefined, - undefined, false, ); } else { diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index 6bdbb66be14..aa051f0805a 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -138,7 +138,6 @@ export class SwitchSummonPhase extends SummonPhase { return; } - if (this.switchType === SwitchType.BATON_PASS) { // If switching via baton pass, update opposing tags coming from the prior pokemon (this.player ? globalScene.getEnemyField() : globalScene.getPlayerField()).forEach((enemyPokemon: Pokemon) => @@ -160,15 +159,7 @@ export class SwitchSummonPhase extends SummonPhase { ) as SwitchEffectTransferModifier; if (batonPassModifier) { - globalScene.tryTransferHeldItemModifier( - batonPassModifier, - switchedInPokemon, - false, - undefined, - undefined, - undefined, - false, - ); + globalScene.tryTransferHeldItemModifier(batonPassModifier, switchedInPokemon, false, 1, false); } } } diff --git a/src/ui/battle-info/battle-info.ts b/src/ui/battle-info/battle-info.ts index e67000bb243..f132ae2d8b9 100644 --- a/src/ui/battle-info/battle-info.ts +++ b/src/ui/battle-info/battle-info.ts @@ -538,9 +538,14 @@ export default abstract class BattleInfo extends Phaser.GameObjects.Container { this.updateHpFrame(); } - /** Update the pokemonHp bar */ - protected updatePokemonHp(pokemon: Pokemon, resolve: (r: void | PromiseLike) => void, instant?: boolean): void { - let duration = !instant ? Phaser.Math.Clamp(Math.abs(this.lastHp - pokemon.hp) * 5, 250, 5000) : 0; + /** + * Update a Pokemon's HP bar. + * @param pokemon - The {@linkcode Pokemon} to whom the HP bar belongs. + * @param resolve - A promise to which the HP bar will be chained unto. + * @param instant - Whether to instantly update the pokemon's HP bar; default `false` + */ + protected updatePokemonHp(pokemon: Pokemon, resolve: (r: void | PromiseLike) => void, instant = false): void { + let duration = instant ? 0 : Phaser.Math.Clamp(Math.abs(this.lastHp - pokemon.hp) * 5, 250, 5000); const speed = globalScene.hpBarSpeed; if (speed) { duration = speed >= 3 ? 0 : duration / Math.pow(2, speed); @@ -563,7 +568,13 @@ export default abstract class BattleInfo extends Phaser.GameObjects.Container { //#endregion - async updateInfo(pokemon: Pokemon, instant?: boolean): Promise { + /** + * Update a Pokemon's battle info, HP bar and other effects. + * @param pokemon - The {@linkcode} Pokemon to whom this BattleInfo belongs. + * @param instant - Whether to instantly update any changes to this Pokemon's HP bar; default `false` + * @returns A Promise that resolves once the Pokemon's info has been successfully updated. + */ + async updateInfo(pokemon: Pokemon, instant = false): Promise { let resolve: (r: void | PromiseLike) => void = () => {}; const promise = new Promise(r => (resolve = r)); if (!globalScene) {