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`.
* @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 => {
const source = itemModifier.pokemonId ? itemModifier.getPokemon() : null;
const source = _itemModifier.pokemonId ? _itemModifier.getPokemon() : null;
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(() => {
if (cancelled.value) {
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;
const matchingModifier = this.findModifier(m => m instanceof 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;
}
removeOld = !itemModifier.stackCount;
if (!removeOld || !source || this.removeModifier(itemModifier, !source.isPlayer())) {
if (!removeOld || !source || removeFunc(simulated ? _itemModifier : itemModifier, !source.isPlayer())) {
const addModifier = () => {
if (!matchingModifier || this.removeModifier(matchingModifier, !target.isPlayer())) {
if (!matchingModifier || removeFunc(matchingModifier, !target.isPlayer())) {
if (simulated) {
return resolve(true);
}
if (target.isPlayer()) {
this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant).then(() => {
if (source && itemLost) {
@ -2749,7 +2754,7 @@ export default class BattleScene extends SceneBase {
resolve(false);
}
};
if (source && source.isPlayer() !== target.isPlayer() && !ignoreUpdate) {
if (!simulated && source && source.isPlayer() !== target.isPlayer() && !ignoreUpdate) {
this.updateModifiers(source.isPlayer(), instant).then(() => addModifier());
} else {
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
* 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 {
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;
}
@ -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 {
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 {
@ -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 {
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 {
@ -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}
* 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.
// If attackRequired is false, we always defer to the secondary requirements.
return this.attackCondition(pokemon, defender, move);
@ -1674,14 +1675,21 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
this.stealCondition = stealCondition ?? null;
}
canApplyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args); // && SUCCESS CHECK
canApplyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
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> {
return new Promise<boolean>(resolve => {
if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.stealCondition || this.stealCondition(pokemon, defender, move))) {
const heldItems = this.getTargetHeldItems(defender).filter(i => i.isTransferable);
const heldItems = getTargetHeldItems(defender).filter(i => i.isTransferable);
if (heldItems.length) {
const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)];
globalScene.tryTransferHeldItemModifier(stolenItem, pokemon, false).then(success => {
@ -1692,15 +1700,9 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
});
return;
}
}
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 {
@ -1776,12 +1778,21 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
this.condition = condition;
}
// SUCCESS CHECK
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
return new Promise<boolean>(resolve => {
if (move.hitsSubstitute(attacker, pokemon)) {
return resolve(false);
}
canStealHeldItem(pokemon, passive, simulated, attacker, move, hitResult, this.condition, args).then(success => {
resolve(success);
});
});
}
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): Promise<boolean> {
return new Promise<boolean>(resolve => {
if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move)) && !move.hitsSubstitute(attacker, pokemon)) {
const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable);
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 => {
@ -1792,15 +1803,9 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
});
return;
}
}
resolve(simulated);
});
}
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
}
}
/**
@ -1841,7 +1846,8 @@ export class SynchronizeStatusAbAttr extends PostSetStatusAbAttr {
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 ||
this.weatherType === WeatherType.HARSH_SUN ||
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 {
@ -2328,6 +2334,10 @@ export class PostSummonTerrainChangeAbAttr extends PostSummonAbAttr {
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 {
if (simulated) {
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 {
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 {
@ -3915,7 +3925,7 @@ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr {
}
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 {
@ -5020,10 +5030,17 @@ async function applyAbAttrsInternal<TAttr extends AbAttr>(
const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility();
for (const attr of ability.getAttrs(attrType)) {
const condition = attr.getCondition();
if ((condition && !condition(pokemon)) || successFunc && !successFunc(attr, passive)) {
if ((condition && !condition(pokemon))) {
continue;
}
let success = successFunc(attr, passive);
if (success instanceof Promise) {
success = await success;
}
if (!success) {
continue;
}
globalScene.setPhaseQueueSplice();
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 function initAbilities() {

View File

@ -249,6 +249,11 @@ export class Arena {
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
* @param weather {@linkcode WeatherType} new {@linkcode WeatherType} to set
@ -260,7 +265,7 @@ export class Arena {
return this.trySetWeatherOverride(Overrides.WEATHER_OVERRIDE);
}
if (this.weather?.weatherType === (weather || undefined)) {
if (!this.canSetWeather(weather)) {
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 {
if (this.terrain?.terrainType === (terrain || undefined)) {
if (!this.canSetTerrain(terrain)) {
return false;
}

View File

@ -2,6 +2,7 @@
import { type PokeballCounts } from "#app/battle-scene";
import { Gender } from "#app/data/gender";
import { Variant } from "#app/data/variant";
import { BerryType } from "#app/enums/berry-type";
import { type ModifierOverride } from "#app/modifier/modifier-type";
import { Unlockables } from "#app/system/unlockables";
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}