Update abilities using promises and trySet... functions

This commit is contained in:
Dean 2025-02-05 21:44:47 -08:00
parent 17232d73b0
commit 953a1e4652
4 changed files with 126 additions and 56 deletions

View File

@ -2699,15 +2699,17 @@ export default class BattleScene extends SceneBase {
* @param itemLost If `true`, treat the item's current holder as losing the item (for now, this simply enables Unburden). Default is `true`. * @param itemLost If `true`, treat the item's current holder as losing the item (for now, this simply enables Unburden). Default is `true`.
* @returns `true` if the transfer was successful * @returns `true` if the transfer was successful
*/ */
tryTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, playSound: boolean, transferQuantity: number = 1, instant?: boolean, ignoreUpdate?: boolean, itemLost: boolean = true): Promise<boolean> { tryTransferHeldItemModifier(_itemModifier: PokemonHeldItemModifier, target: Pokemon, playSound: boolean, transferQuantity: number = 1, instant?: boolean, ignoreUpdate?: boolean, itemLost: boolean = true, simulated: boolean = false): Promise<boolean> {
return new Promise(resolve => { return new Promise(resolve => {
const source = itemModifier.pokemonId ? itemModifier.getPokemon() : null; const source = _itemModifier.pokemonId ? _itemModifier.getPokemon() : null;
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
const removeFunc: (modifier: PersistentModifier, enemy: boolean | undefined) => boolean = simulated ? (modifier, enemy) => this.canRemoveModifier(modifier, enemy) : (modifier, enemy) => this.removeModifier(modifier, enemy);
Utils.executeIf(!!source && source.isPlayer() !== target.isPlayer(), () => applyAbAttrs(BlockItemTheftAbAttr, source! /* checked in condition*/, cancelled)).then(() => { Utils.executeIf(!!source && source.isPlayer() !== target.isPlayer(), () => applyAbAttrs(BlockItemTheftAbAttr, source! /* checked in condition*/, cancelled)).then(() => {
if (cancelled.value) { if (cancelled.value) {
return resolve(false); return resolve(false);
} }
const newItemModifier = itemModifier.clone() as PokemonHeldItemModifier; const newItemModifier = _itemModifier.clone() as PokemonHeldItemModifier;
const itemModifier = simulated ? _itemModifier.clone() : _itemModifier;
newItemModifier.pokemonId = target.id; newItemModifier.pokemonId = target.id;
const matchingModifier = this.findModifier(m => m instanceof PokemonHeldItemModifier const matchingModifier = this.findModifier(m => m instanceof PokemonHeldItemModifier
&& (m as PokemonHeldItemModifier).matchType(itemModifier) && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier; && (m as PokemonHeldItemModifier).matchType(itemModifier) && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier;
@ -2727,9 +2729,12 @@ export default class BattleScene extends SceneBase {
newItemModifier.stackCount = countTaken; newItemModifier.stackCount = countTaken;
} }
removeOld = !itemModifier.stackCount; removeOld = !itemModifier.stackCount;
if (!removeOld || !source || this.removeModifier(itemModifier, !source.isPlayer())) { if (!removeOld || !source || removeFunc(simulated ? _itemModifier : itemModifier, !source.isPlayer())) {
const addModifier = () => { const addModifier = () => {
if (!matchingModifier || this.removeModifier(matchingModifier, !target.isPlayer())) { if (!matchingModifier || removeFunc(matchingModifier, !target.isPlayer())) {
if (simulated) {
return resolve(true);
}
if (target.isPlayer()) { if (target.isPlayer()) {
this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant).then(() => { this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant).then(() => {
if (source && itemLost) { if (source && itemLost) {
@ -2749,7 +2754,7 @@ export default class BattleScene extends SceneBase {
resolve(false); resolve(false);
} }
}; };
if (source && source.isPlayer() !== target.isPlayer() && !ignoreUpdate) { if (!simulated && source && source.isPlayer() !== target.isPlayer() && !ignoreUpdate) {
this.updateModifiers(source.isPlayer(), instant).then(() => addModifier()); this.updateModifiers(source.isPlayer(), instant).then(() => addModifier());
} else { } else {
addModifier(); addModifier();
@ -2909,6 +2914,11 @@ export default class BattleScene extends SceneBase {
}); });
} }
canRemoveModifier(modifier: PersistentModifier, enemy: boolean = false): boolean {
const modifiers = !enemy ? this.modifiers : this.enemyModifiers;
return modifiers.indexOf(modifier) > -1;
}
/** /**
* Removes a currently owned item. If the item is stacked, the entire item stack * Removes a currently owned item. If the item is stacked, the entire item stack
* gets removed. This function does NOT apply in-battle effects, such as Unburden. * gets removed. This function does NOT apply in-battle effects, such as Unburden.

View File

@ -591,7 +591,7 @@ export class FullHpResistTypeAbAttr extends PreDefendAbAttr {
} }
export class PostDefendAbAttr extends AbAttr { export class PostDefendAbAttr extends AbAttr {
canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise<boolean> {
return true; return true;
} }
@ -869,7 +869,7 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr {
} }
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
return hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon); return hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon) && globalScene.arena.canSetTerrain(this.terrainType);
} }
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean {
@ -1048,7 +1048,8 @@ export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr {
} }
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return (!(this.condition && !this.condition(pokemon, attacker, move) || move.hitsSubstitute(attacker, pokemon)) && !globalScene.arena.weather?.isImmutable()); return (!(this.condition && !this.condition(pokemon, attacker, move) || move.hitsSubstitute(attacker, pokemon))
&& !globalScene.arena.weather?.isImmutable() && globalScene.arena.canSetWeather(this.weatherType));
} }
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
@ -1619,7 +1620,7 @@ export class PostAttackAbAttr extends AbAttr {
* applying the effect of any inherited class. This can be changed by providing a different {@link attackCondition} to the constructor. See {@link ConfusionOnStatusEffectAbAttr} * applying the effect of any inherited class. This can be changed by providing a different {@link attackCondition} to the constructor. See {@link ConfusionOnStatusEffectAbAttr}
* for an example of an effect that does not require a damaging move. * for an example of an effect that does not require a damaging move.
*/ */
canApplyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { canApplyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise<boolean> {
// When attackRequired is true, we require the move to be an attack move and to deal damage before checking secondary requirements. // When attackRequired is true, we require the move to be an attack move and to deal damage before checking secondary requirements.
// If attackRequired is false, we always defer to the secondary requirements. // If attackRequired is false, we always defer to the secondary requirements.
return this.attackCondition(pokemon, defender, move); return this.attackCondition(pokemon, defender, move);
@ -1674,33 +1675,34 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
this.stealCondition = stealCondition ?? null; this.stealCondition = stealCondition ?? null;
} }
canApplyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { canApplyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
return super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args); // && SUCCESS CHECK return new Promise<boolean>(resolve => {
if (!super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args)) {
return resolve(false);
}
canStealHeldItem(pokemon, passive, simulated, defender, move, hitResult, this.stealCondition, args).then(success => {
resolve(success);
});
});
} }
applyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> { applyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
return new Promise<boolean>(resolve => { return new Promise<boolean>(resolve => {
if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.stealCondition || this.stealCondition(pokemon, defender, move))) { const heldItems = getTargetHeldItems(defender).filter(i => i.isTransferable);
const heldItems = this.getTargetHeldItems(defender).filter(i => i.isTransferable); if (heldItems.length) {
if (heldItems.length) { const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)];
const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; globalScene.tryTransferHeldItemModifier(stolenItem, pokemon, false).then(success => {
globalScene.tryTransferHeldItemModifier(stolenItem, pokemon, false).then(success => { if (success) {
if (success) { globalScene.queueMessage(i18next.t("abilityTriggers:postAttackStealHeldItem", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), defenderName: defender.name, stolenItemType: stolenItem.type.name }));
globalScene.queueMessage(i18next.t("abilityTriggers:postAttackStealHeldItem", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), defenderName: defender.name, stolenItemType: stolenItem.type.name })); }
} resolve(success);
resolve(success); });
}); return;
return;
}
} }
resolve(simulated); resolve(simulated);
}); });
} }
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
}
} }
export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr { export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr {
@ -1776,30 +1778,33 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
this.condition = condition; this.condition = condition;
} }
// SUCCESS CHECK override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): Promise<boolean> {
return new Promise<boolean>(resolve => { return new Promise<boolean>(resolve => {
if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move)) && !move.hitsSubstitute(attacker, pokemon)) { if (move.hitsSubstitute(attacker, pokemon)) {
const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable); return resolve(false);
if (heldItems.length) {
const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)];
globalScene.tryTransferHeldItemModifier(stolenItem, pokemon, false).then(success => {
if (success) {
globalScene.queueMessage(i18next.t("abilityTriggers:postDefendStealHeldItem", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), attackerName: attacker.name, stolenItemType: stolenItem.type.name }));
}
resolve(success);
});
return;
}
} }
resolve(simulated);
canStealHeldItem(pokemon, passive, simulated, attacker, move, hitResult, this.condition, args).then(success => {
resolve(success);
});
}); });
} }
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): Promise<boolean> {
return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier return new Promise<boolean>(resolve => {
&& m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[]; const heldItems = getTargetHeldItems(attacker).filter(i => i.isTransferable);
if (heldItems.length) {
const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)];
globalScene.tryTransferHeldItemModifier(stolenItem, pokemon, false).then(success => {
if (success) {
globalScene.queueMessage(i18next.t("abilityTriggers:postDefendStealHeldItem", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), attackerName: attacker.name, stolenItemType: stolenItem.type.name }));
}
resolve(success);
});
return;
}
resolve(simulated);
});
} }
} }
@ -1841,7 +1846,8 @@ export class SynchronizeStatusAbAttr extends PostSetStatusAbAttr {
StatusEffect.TOXIC StatusEffect.TOXIC
]); ]);
return (sourcePokemon && syncStatuses.has(effect)) ?? false; // synchronize does not need to check canSetStatus because the ability shows even if it fails to set the status
return ((sourcePokemon ?? false) && syncStatuses.has(effect));
} }
/** /**
@ -2307,7 +2313,7 @@ export class PostSummonWeatherChangeAbAttr extends PostSummonAbAttr {
const weatherReplaceable = (this.weatherType === WeatherType.HEAVY_RAIN || const weatherReplaceable = (this.weatherType === WeatherType.HEAVY_RAIN ||
this.weatherType === WeatherType.HARSH_SUN || this.weatherType === WeatherType.HARSH_SUN ||
this.weatherType === WeatherType.STRONG_WINDS) || !globalScene.arena.weather?.isImmutable(); this.weatherType === WeatherType.STRONG_WINDS) || !globalScene.arena.weather?.isImmutable();
return weatherReplaceable; return weatherReplaceable && globalScene.arena.canSetWeather(this.weatherType);
} }
applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
@ -2328,6 +2334,10 @@ export class PostSummonTerrainChangeAbAttr extends PostSummonAbAttr {
this.terrainType = terrainType; this.terrainType = terrainType;
} }
canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
return globalScene.arena.canSetTerrain(this.terrainType);
}
applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
if (simulated) { if (simulated) {
return globalScene.arena.terrain?.terrainType !== this.terrainType; return globalScene.arena.terrain?.terrainType !== this.terrainType;
@ -3889,7 +3899,7 @@ export class PostBiomeChangeWeatherChangeAbAttr extends PostBiomeChangeAbAttr {
} }
canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
return (globalScene.arena.weather?.isImmutable() && globalScene.arena.weather?.weatherType !== this.weatherType) ?? false; return ((globalScene.arena.weather?.isImmutable() ?? false) && globalScene.arena.canSetWeather(this.weatherType));
} }
apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
@ -3915,7 +3925,7 @@ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr {
} }
canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
return globalScene.arena.terrain?.terrainType !== this.terrainType; return globalScene.arena.canSetTerrain(this.terrainType);
} }
apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
@ -5020,10 +5030,17 @@ async function applyAbAttrsInternal<TAttr extends AbAttr>(
const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility(); const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility();
for (const attr of ability.getAttrs(attrType)) { for (const attr of ability.getAttrs(attrType)) {
const condition = attr.getCondition(); const condition = attr.getCondition();
if ((condition && !condition(pokemon)) || successFunc && !successFunc(attr, passive)) { if ((condition && !condition(pokemon))) {
continue; continue;
} }
let success = successFunc(attr, passive);
if (success instanceof Promise) {
success = await success;
}
if (!success) {
continue;
}
globalScene.setPhaseQueueSplice(); globalScene.setPhaseQueueSplice();
if (attr.showAbility && !simulated) { if (attr.showAbility && !simulated) {
@ -5514,6 +5531,31 @@ function getPokemonWithWeatherBasedForms() {
); );
} }
/**
* Returns if a target's held item can be stolen
*
* Common to attrs for {@linkcode Abilities.MAGICIAN} and {@linkcode Abilities.PICKPOCKET}
*/
async function canStealHeldItem(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, condition: any, args: any[]): Promise<boolean> {
if (!simulated && hitResult < HitResult.NO_EFFECT && (!condition || condition(pokemon, attacker, move))) {
const heldItems = getTargetHeldItems(attacker).filter(i => i.isTransferable);
if (heldItems.length) {
return globalScene.tryTransferHeldItemModifier(heldItems[pokemon.randSeedInt(heldItems.length)], pokemon, false, 1, false, false, true, true);
}
}
return simulated;
}
/**
* Gets the held items of a pokemon
* @param target Target Pokemon
* @returns List of {@linkcode target}'s held items
*/
function getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
}
export const allAbilities = [ new Ability(Abilities.NONE, 3) ]; export const allAbilities = [ new Ability(Abilities.NONE, 3) ];
export function initAbilities() { export function initAbilities() {

View File

@ -249,6 +249,11 @@ export class Arena {
return true; return true;
} }
/** Returns weather or not the weather can be changed to {@linkcode weather} */
canSetWeather(weather: WeatherType): boolean {
return !(this.weather?.weatherType === (weather || undefined));
}
/** /**
* Attempts to set a new weather to the battle * Attempts to set a new weather to the battle
* @param weather {@linkcode WeatherType} new {@linkcode WeatherType} to set * @param weather {@linkcode WeatherType} new {@linkcode WeatherType} to set
@ -260,7 +265,7 @@ export class Arena {
return this.trySetWeatherOverride(Overrides.WEATHER_OVERRIDE); return this.trySetWeatherOverride(Overrides.WEATHER_OVERRIDE);
} }
if (this.weather?.weatherType === (weather || undefined)) { if (!this.canSetWeather(weather)) {
return false; return false;
} }
@ -314,8 +319,13 @@ export class Arena {
}); });
} }
/** Returns whether or not the terrain can be set to {@linkcode terrain} */
canSetTerrain(terrain: TerrainType): boolean {
return !(this.terrain?.terrainType === (terrain || undefined));
}
trySetTerrain(terrain: TerrainType, hasPokemonSource: boolean, ignoreAnim: boolean = false): boolean { trySetTerrain(terrain: TerrainType, hasPokemonSource: boolean, ignoreAnim: boolean = false): boolean {
if (this.terrain?.terrainType === (terrain || undefined)) { if (!this.canSetTerrain(terrain)) {
return false; return false;
} }

View File

@ -2,6 +2,7 @@
import { type PokeballCounts } from "#app/battle-scene"; import { type PokeballCounts } from "#app/battle-scene";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import { Variant } from "#app/data/variant"; import { Variant } from "#app/data/variant";
import { BerryType } from "#app/enums/berry-type";
import { type ModifierOverride } from "#app/modifier/modifier-type"; import { type ModifierOverride } from "#app/modifier/modifier-type";
import { Unlockables } from "#app/system/unlockables"; import { Unlockables } from "#app/system/unlockables";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
@ -32,7 +33,14 @@ import { WeatherType } from "#enums/weather-type";
* } * }
* ``` * ```
*/ */
const overrides = {} satisfies Partial<InstanceType<typeof DefaultOverrides>>; const overrides = {
OPP_HELD_ITEMS_OVERRIDE: [{ name: "BERRY", type: BerryType.GANLON, count: 3 }],
ABILITY_OVERRIDE: Abilities.PICKPOCKET,
OPP_LEVEL_OVERRIDE: 1,
STARTING_LEVEL_OVERRIDE: 100,
OPP_MOVESET_OVERRIDE: Moves.TACKLE,
MOVESET_OVERRIDE: Moves.SPLASH
} satisfies Partial<InstanceType<typeof DefaultOverrides>>;
/** /**
* If you need to add Overrides values for local testing do that inside {@linkcode overrides} * If you need to add Overrides values for local testing do that inside {@linkcode overrides}