diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 01b7c3949f7..82bb3d92b0c 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1876,7 +1876,20 @@ export default class BattleScene extends SceneBase { }); } - tryTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, transferStack: boolean, playSound: boolean, instant?: boolean, ignoreUpdate?: boolean): Promise { + /** + * Try to transfer a held item to another pokemon. + * If the recepient already has the maximum amount allowed for this item, the transfer is cancelled. + * The quantity to transfer is automatically capped at how much the recepient can take before reaching the maximum stack size for the item. + * A transfer that moves a quantity smaller than what is specified in the transferQuantity parameter is still considered successful. + * @param itemModifier {@linkcode PokemonHeldItemModifier} item to transfer (represents the whole stack) + * @param target {@linkcode Pokemon} pokemon recepient in this transfer + * @param playSound {boolean} + * @param transferQuantity {@linkcode integer} how many items of the stack to transfer. Optional, defaults to 1 + * @param instant {boolean} + * @param ignoreUpdate {boolean} + * @returns true if the transfer was successful + */ + tryTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, playSound: boolean, transferQuantity: integer = 1, instant?: boolean, ignoreUpdate?: boolean): Promise { return new Promise(resolve => { const source = itemModifier.pokemonId ? itemModifier.getPokemon(target.scene) : null; const cancelled = new Utils.BooleanHolder(false); @@ -1894,14 +1907,16 @@ export default class BattleScene extends SceneBase { if (matchingModifier.stackCount >= maxStackCount) { return resolve(false); } - const countTaken = transferStack ? Math.min(itemModifier.stackCount, maxStackCount - matchingModifier.stackCount) : 1; + const countTaken = Math.min(transferQuantity, itemModifier.stackCount, maxStackCount - matchingModifier.stackCount); itemModifier.stackCount -= countTaken; newItemModifier.stackCount = matchingModifier.stackCount + countTaken; removeOld = !itemModifier.stackCount; - } else if (!transferStack) { - newItemModifier.stackCount = 1; - removeOld = !(--itemModifier.stackCount); + } else { + const countTaken = Math.min(transferQuantity, itemModifier.stackCount); + itemModifier.stackCount -= countTaken; + newItemModifier.stackCount = countTaken; } + removeOld = !itemModifier.stackCount; if (!removeOld || !source || this.removeModifier(itemModifier, !source.isPlayer())) { const addModifier = () => { if (!matchingModifier || this.removeModifier(matchingModifier, !target.isPlayer())) { diff --git a/src/data/ability.ts b/src/data/ability.ts index bef81a0220f..502c72dbd92 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -1229,7 +1229,7 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { const heldItems = this.getTargetHeldItems(defender).filter(i => i.getTransferrable(false)); if (heldItems.length) { const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; - pokemon.scene.tryTransferHeldItemModifier(stolenItem, pokemon, false, false).then(success => { + pokemon.scene.tryTransferHeldItemModifier(stolenItem, pokemon, false).then(success => { if (success) { pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` stole\n${defender.name}'s ${stolenItem.type.name}!`)); } @@ -1318,7 +1318,7 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { const heldItems = this.getTargetHeldItems(attacker).filter(i => i.getTransferrable(false)); if (heldItems.length) { const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; - pokemon.scene.tryTransferHeldItemModifier(stolenItem, pokemon, false, false).then(success => { + pokemon.scene.tryTransferHeldItemModifier(stolenItem, pokemon, false).then(success => { if (success) { pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` stole\n${attacker.name}'s ${stolenItem.type.name}!`)); } @@ -2873,7 +2873,7 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr { const postBattleLoot = pokemon.scene.currentBattle.postBattleLoot; if (postBattleLoot.length) { const randItem = Utils.randSeedItem(postBattleLoot); - if (pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, false, true, true)) { + if (pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, true, 1, true)) { postBattleLoot.splice(postBattleLoot.indexOf(randItem), 1); pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` picked up\n${randItem.type.name}!`)); return true; diff --git a/src/data/move.ts b/src/data/move.ts index e20768c69e9..5594a45c195 100755 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1445,7 +1445,7 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { const highestItemTier = heldItems.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier, highestTier), 0); const tierHeldItems = heldItems.filter(m => m.type.getOrInferTier(poolType) === highestItemTier); const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length)]; - user.scene.tryTransferHeldItemModifier(stolenItem, user, false, false).then(success => { + user.scene.tryTransferHeldItemModifier(stolenItem, user, false).then(success => { if (success) { user.scene.queueMessage(getPokemonMessage(user, ` stole\n${target.name}'s ${stolenItem.type.name}!`)); } @@ -1494,7 +1494,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { const highestItemTier = heldItems.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier, highestTier), 0); const tierHeldItems = heldItems.filter(m => m.type.getOrInferTier(poolType) === highestItemTier); const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length)]; - user.scene.tryTransferHeldItemModifier(stolenItem, user, false, false).then(success => { + user.scene.tryTransferHeldItemModifier(stolenItem, user, false).then(success => { if (success) { user.scene.queueMessage(getPokemonMessage(user, ` knocked off\n${target.name}'s ${stolenItem.type.name}!`)); } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index c8aaeeaccfd..2ff53249aeb 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3189,7 +3189,7 @@ export class PlayerPokemon extends Pokemon { && (m as PokemonHeldItemModifier).pokemonId === pokemon.id, true) as PokemonHeldItemModifier[]; const transferModifiers: Promise[] = []; for (const modifier of fusedPartyMemberHeldModifiers) { - transferModifiers.push(this.scene.tryTransferHeldItemModifier(modifier, this, true, false, true, true)); + transferModifiers.push(this.scene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true)); } Promise.allSettled(transferModifiers).then(() => { this.scene.updateModifiers(true, true).then(() => { diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 233a5294849..9035eeda9dd 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1868,7 +1868,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { } const randItemIndex = pokemon.randSeedInt(itemModifiers.length); const randItem = itemModifiers[randItemIndex]; - heldItemTransferPromises.push(pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, false, false).then(success => { + heldItemTransferPromises.push(pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, false).then(success => { if (success) { transferredModifierTypes.push(randItem.type); itemModifiers.splice(randItemIndex, 1); diff --git a/src/phases.ts b/src/phases.ts index dd272cb28ca..7e3202e43b2 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1513,7 +1513,7 @@ export class SwitchSummonPhase extends SummonPhase { const batonPassModifier = this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === this.lastPokemon.id) as SwitchEffectTransferModifier; if (batonPassModifier && !this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedPokemon.id)) { - this.scene.tryTransferHeldItemModifier(batonPassModifier, switchedPokemon, false, false); + this.scene.tryTransferHeldItemModifier(batonPassModifier, switchedPokemon, false); } } } @@ -4895,14 +4895,12 @@ export class SelectModifierPhase extends BattlePhase { this.scene.playSound("buy"); } } else if (cursor === 1) { - this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, toSlotIndex: integer) => { + this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => { if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)).then(() => { - const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).getTransferrable(true) && (m as PokemonHeldItemModifier).pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; - const itemModifier = itemModifiers[itemIndex]; - this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, true); - }); + const itemModifier = itemModifiers[itemIndex]; + this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity); } else { this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); } diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index cc5fe54818b..b62242e3c18 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -57,7 +57,7 @@ export enum PartyOption { } export type PartySelectCallback = (cursor: integer, option: PartyOption) => void; -export type PartyModifierTransferSelectCallback = (fromCursor: integer, index: integer, toCursor?: integer) => void; +export type PartyModifierTransferSelectCallback = (fromCursor: integer, index: integer, itemQuantity?: integer, toCursor?: integer) => void; export type PartyModifierSpliceSelectCallback = (fromCursor: integer, toCursor?: integer) => void; export type PokemonSelectFilter = (pokemon: PlayerPokemon) => string; export type PokemonModifierTransferSelectFilter = (pokemon: PlayerPokemon, modifier: PokemonHeldItemModifier) => string; @@ -87,6 +87,10 @@ export default class PartyUiHandler extends MessageUiHandler { private transferMode: boolean; private transferOptionCursor: integer; private transferCursor: integer; + /** Current quantity selection for every item held by the pokemon selected for the transfer */ + private transferQuantities: integer[]; + /** Stack size of every item that the selected pokemon is holding */ + private transferQuantitiesMax: integer[]; private lastCursor: integer = 0; private selectCallback: PartySelectCallback | PartyModifierTransferSelectCallback; @@ -231,8 +235,8 @@ export default class PartyUiHandler extends MessageUiHandler { let success = false; if (this.optionsMode) { + const option = this.options[this.optionsCursor]; if (button === Button.ACTION) { - const option = this.options[this.optionsCursor]; const pokemon = this.scene.getParty()[this.cursor]; if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && !this.transferMode && option !== PartyOption.CANCEL) { this.startTransfer(); @@ -270,7 +274,9 @@ export default class PartyUiHandler extends MessageUiHandler { } if (this.selectCallback) { if (option === PartyOption.TRANSFER) { - (this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, this.transferOptionCursor, this.cursor); + if (this.transferCursor !== this.cursor) { + (this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, this.transferOptionCursor, this.transferQuantities[this.transferOptionCursor], this.cursor); + } this.clearTransfer(); } else if (this.partyUiMode === PartyUiMode.SPLICE) { if (option === PartyOption.SPLICE) { @@ -369,17 +375,50 @@ export default class PartyUiHandler extends MessageUiHandler { return true; } else { switch (button) { + case Button.LEFT: + /** Decrease quantity for the current item and update UI */ + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + this.transferQuantities[option] = this.transferQuantities[option] === 1 ? this.transferQuantitiesMax[option] : this.transferQuantities[option] - 1; + this.updateOptions(); + success = this.setCursor(this.optionsCursor); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ + } + break; + case Button.RIGHT: + /** Increase quantity for the current item and update UI */ + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + this.transferQuantities[option] = this.transferQuantities[option] === this.transferQuantitiesMax[option] ? 1 : this.transferQuantities[option] + 1; + this.updateOptions(); + success = this.setCursor(this.optionsCursor); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ + } + break; case Button.UP: - success = this.setCursor(this.optionsCursor ? this.optionsCursor - 1 : this.options.length - 1); + /** If currently selecting items to transfer, reset quantity selection */ + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + this.transferQuantities[option] = this.transferQuantitiesMax[option]; + this.updateOptions(); + } + success = this.setCursor(this.optionsCursor ? this.optionsCursor - 1 : this.options.length - 1); /** Move cursor */ break; case Button.DOWN: - success = this.setCursor(this.optionsCursor < this.options.length - 1 ? this.optionsCursor + 1 : 0); + /** If currently selecting items to transfer, reset quantity selection */ + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + this.transferQuantities[option] = this.transferQuantitiesMax[option]; + this.updateOptions(); + } + success = this.setCursor(this.optionsCursor < this.options.length - 1 ? this.optionsCursor + 1 : 0); /** Move cursor */ break; } } } else { if (button === Button.ACTION) { if (this.cursor < 6) { + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && !this.transferMode) { + /** Initialize item quantities for the selected Pokemon */ + const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + && (m as PokemonHeldItemModifier).getTransferrable(true) && (m as PokemonHeldItemModifier).pokemonId === this.scene.getParty()[this.cursor].id) as PokemonHeldItemModifier[]; + this.transferQuantities = itemModifiers.map(item => item.getStackCount()); + this.transferQuantitiesMax = itemModifiers.map(item => item.getStackCount()); + } this.showOptions(); ui.playSelect(); } else if (this.partyUiMode === PartyUiMode.FAINT_SWITCH || this.partyUiMode === PartyUiMode.REVIVAL_BLESSING) { @@ -555,7 +594,7 @@ export default class PartyUiHandler extends MessageUiHandler { break; case PartyUiMode.MODIFIER_TRANSFER: if (!this.transferMode) { - optionsMessage = "Select a held item to transfer."; + optionsMessage = "Select a held item to transfer.\nUse < and > to change the quantity."; } break; case PartyUiMode.SPLICE: @@ -569,7 +608,12 @@ export default class PartyUiHandler extends MessageUiHandler { this.updateOptions(); - this.partyMessageBox.setSize(262 - Math.max(this.optionsBg.displayWidth - 56, 0), 30); + /** When an item is being selected for transfer, the message box is taller as the message occupies two lines */ + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + this.partyMessageBox.setSize(262 - Math.max(this.optionsBg.displayWidth - 56, 0), 42); + } else { + this.partyMessageBox.setSize(262 - Math.max(this.optionsBg.displayWidth - 56, 0), 30); + } this.setCursor(0); } @@ -741,8 +785,9 @@ export default class PartyUiHandler extends MessageUiHandler { } else { const itemModifier = itemModifiers[option]; optionName = itemModifier.type.name; - if (itemModifier.stackCount > 1) { - optionName += ` (${itemModifier.stackCount})`; + /** For every item that has stack bigger than 1, display the current quantity selection */ + if (this.transferQuantitiesMax[option] > 1) { + optionName += ` (${this.transferQuantities[option]})`; } }