From 113ac10c1bd1cdfa5376aa12f7d741c47900ed7f Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Wed, 6 Mar 2024 21:05:23 -0500 Subject: [PATCH] Implement Pickup ability --- src/battle-scene.ts | 10 +++++----- src/battle.ts | 17 +++++++++++++++-- src/data/ability.ts | 33 ++++++++++++++++++++++++++++++++- src/phases.ts | 7 ++++++- 4 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 2aadef67c..7ec097e23 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1604,9 +1604,9 @@ export default class BattleScene extends Phaser.Scene { tryTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, transferStack: boolean, playSound: boolean, instant?: boolean, ignoreUpdate?: boolean): Promise { return new Promise(resolve => { - const source = itemModifier.getPokemon(target.scene); + const source = itemModifier.pokemonId ? itemModifier.getPokemon(target.scene) : null; const cancelled = new Utils.BooleanHolder(false); - applyAbAttrs(BlockItemTheftAbAttr, source, cancelled).then(() => { + Utils.executeIf(!!source, () => applyAbAttrs(BlockItemTheftAbAttr, source, cancelled)).then(() => { if (cancelled.value) return resolve(false); const newItemModifier = itemModifier.clone() as PokemonHeldItemModifier; @@ -1615,7 +1615,7 @@ export default class BattleScene extends Phaser.Scene { && (m as PokemonHeldItemModifier).matchType(itemModifier) && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier; let removeOld = true; if (matchingModifier) { - const maxStackCount = matchingModifier.getMaxStackCount(source.scene); + const maxStackCount = matchingModifier.getMaxStackCount(target.scene); if (matchingModifier.stackCount >= maxStackCount) return resolve(false); const countTaken = transferStack ? Math.min(itemModifier.stackCount, maxStackCount - matchingModifier.stackCount) : 1; @@ -1626,7 +1626,7 @@ export default class BattleScene extends Phaser.Scene { newItemModifier.stackCount = 1; removeOld = !(--itemModifier.stackCount); } - if (!removeOld || this.removeModifier(itemModifier, !source.isPlayer())) { + if (!removeOld || !source || this.removeModifier(itemModifier, !source.isPlayer())) { const addModifier = () => { if (!matchingModifier || this.removeModifier(matchingModifier, !target.isPlayer())) { if (target.isPlayer()) @@ -1636,7 +1636,7 @@ export default class BattleScene extends Phaser.Scene { } else resolve(false); }; - if (source.isPlayer() !== target.isPlayer() && !ignoreUpdate) + if (source && source.isPlayer() !== target.isPlayer() && !ignoreUpdate) this.updateModifiers(source.isPlayer(), instant).then(() => addModifier()); else addModifier(); diff --git a/src/battle.ts b/src/battle.ts index 8130f3a98..b132533c5 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -9,6 +9,7 @@ import { TrainerType } from "./data/enums/trainer-type"; import { GameMode } from "./game-mode"; import { BattleSpec } from "./enums/battle-spec"; import { PlayerGender } from "./system/game-data"; +import { PersistentModifier, PokemonHeldItemModifier } from "./modifier/modifier"; export enum BattleType { WILD, @@ -49,8 +50,9 @@ export default class Battle { public started: boolean; public turn: integer; public turnCommands: TurnCommands; - public playerParticipantIds: Set = new Set(); - public escapeAttempts: integer = 0; + public playerParticipantIds: Set; + public postBattleLoot: PokemonHeldItemModifier[]; + public escapeAttempts: integer; public lastMove: Moves; public battleSeed: string; private battleSeedState: string; @@ -68,6 +70,9 @@ export default class Battle { this.seenEnemyPartyMemberIds = new Set(); this.double = double; this.turn = 0; + this.playerParticipantIds = new Set(); + this.postBattleLoot = []; + this.escapeAttempts = 0; this.started = false; this.battleSeed = Utils.randomString(16, true); this.battleSeedState = null; @@ -123,6 +128,14 @@ export default class Battle { this.playerParticipantIds.delete(playerPokemon.id); } + addPostBattleLoot(enemyPokemon: EnemyPokemon): void { + this.postBattleLoot.push(...enemyPokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id, false).map(i => { + const ret = i as PokemonHeldItemModifier; + ret.pokemonId = null; + return ret; + })); + } + getBgmOverride(scene: BattleScene): string { const battlers = this.enemyParty.slice(0, this.getBattlerCount()); if (this.battleType === BattleType.TRAINER) { diff --git a/src/data/ability.ts b/src/data/ability.ts index 9b4cc4fef..a49f4ff54 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -1020,6 +1020,31 @@ export class MaxMultiHitAbAttr extends AbAttr { } } +export class PostBattleAbAttr extends AbAttr { + constructor() { + super(true); + } + + applyPostBattle(pokemon: Pokemon, args: any[]): boolean { + return false; + } +} + +export class PostBattleLootAbAttr extends PostBattleAbAttr { + applyPostBattle(pokemon: Pokemon, args: any[]): boolean { + const postBattleLoot = pokemon.scene.currentBattle.postBattleLoot; + if (postBattleLoot.length) { + const randItem = Utils.randSeedItem(postBattleLoot); + if (pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, false, true, true)) { + pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` picked up\n${randItem.type.name}!`)); + return true; + } + } + + return false; + } +} + export class ReduceStatusEffectDurationAbAttr extends AbAttr { private statusEffect: StatusEffect; @@ -1247,6 +1272,11 @@ export function applyCheckTrappedAbAttrs(attrType: { new(...args: any[]): CheckT return applyAbAttrsInternal(attrType, pokemon, attr => attr.applyCheckTrapped(pokemon, trapped, args), true); } +export function applyPostBattleAbAttrs(attrType: { new(...args: any[]): PostBattleAbAttr }, + pokemon: Pokemon, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, attr => attr.applyPostBattle(pokemon, args)); +} + function canApplyAttr(pokemon: Pokemon, attr: AbAttr): boolean { const condition = attr.getCondition(); return !condition || condition(pokemon); @@ -1676,7 +1706,8 @@ export function initAbilities() { .attr(ProtectStatAbAttr, BattleStat.ACC), new Ability(Abilities.HYPER_CUTTER, "Hyper Cutter", "The Pokémon's proud of its powerful pincers. They prevent other Pokémon from lowering its Attack stat.", 3) .attr(ProtectStatAbAttr, BattleStat.ATK), - new Ability(Abilities.PICKUP, "Pickup (N)", "The Pokémon may pick up the item an opposing Pokémon used during a battle. It may pick up items outside of battle, too.", 3), + new Ability(Abilities.PICKUP, "Pickup", "The Pokémon may pick up the item an opposing Pokémon held during a battle.", 3) + .attr(PostBattleLootAbAttr), new Ability(Abilities.TRUANT, "Truant", "The Pokémon can't use a move if it had used a move on the previous turn.", 3) .attr(PostSummonAddBattlerTagAbAttr, BattlerTagType.TRUANT, 1, false), new Ability(Abilities.HUSTLE, "Hustle", "Boosts the Attack stat, but lowers accuracy.", 3) diff --git a/src/phases.ts b/src/phases.ts index 95847187d..a5fb458f1 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -30,7 +30,7 @@ import { Weather, WeatherType, getRandomWeatherType, getWeatherDamageMessage, ge import { TempBattleStat } from "./data/temp-battle-stat"; import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag"; import { ArenaTagType } from "./data/enums/arena-tag-type"; -import { Abilities, CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability"; +import { Abilities, CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability"; import { Unlockables, getUnlockableName } from "./system/unlockables"; import { getBiomeKey } from "./field/arena"; import { BattleType, BattlerIndex, TurnCommand } from "./battle"; @@ -1772,6 +1772,9 @@ export class BattleEndPhase extends BattlePhase { pokemon.resetBattleSummonData(); } + for (let pokemon of this.scene.getParty()) + applyPostBattleAbAttrs(PostBattleAbAttr, pokemon); + this.scene.clearEnemyHeldItemModifiers(); const lapsingModifiers = this.scene.findModifiers(m => m instanceof LapsingPersistentModifier || m instanceof LapsingPokemonHeldItemModifier) as (LapsingPersistentModifier | LapsingPokemonHeldItemModifier)[]; @@ -2688,6 +2691,8 @@ export class FaintPhase extends PokemonPhase { pokemon.trySetStatus(StatusEffect.FAINT); if (pokemon.isPlayer()) this.scene.currentBattle.removeFaintedParticipant(pokemon as PlayerPokemon); + else + this.scene.currentBattle.addPostBattleLoot(pokemon as EnemyPokemon); this.scene.field.remove(pokemon); this.end(); }