Merge remote-tracking branch 'upstream/beta' into modifier-fixes

This commit is contained in:
Bertie690 2025-06-09 18:26:25 -04:00
commit 5531b3c5cd
6 changed files with 379 additions and 290 deletions

View File

@ -2534,48 +2534,38 @@ export class AllyStatMultiplierAbAttr extends AbAttr {
} }
/** /**
* Ability attribute for Gorilla Tactics * Takes effect whenever a move succesfully executes, such as gorilla tactics' move-locking.
* @extends PostAttackAbAttr * (More specifically, whenever a move is pushed to the move history)
* @extends AbAttr
*/ */
export class GorillaTacticsAbAttr extends PostAttackAbAttr { export class ExecutedMoveAbAttr extends AbAttr {
constructor() { canApplyExecutedMove(
super((_user, _target, _move) => true, false); _pokemon: Pokemon,
} _simulated: boolean,
override canApplyPostAttack(
pokemon: Pokemon,
passive: boolean,
simulated: boolean,
defender: Pokemon,
move: Move,
hitResult: HitResult | null,
args: any[],
): boolean { ): boolean {
return ( return true;
(super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args) && simulated) ||
!pokemon.getTag(BattlerTagType.GORILLA_TACTICS)
);
} }
/** applyExecutedMove(
* _pokemon: Pokemon,
* @param {Pokemon} pokemon the {@linkcode Pokemon} with this ability _simulated: boolean,
* @param _passive n/a ): void {}
* @param simulated whether the ability is being simulated }
* @param _defender n/a
* @param _move n/a /**
* @param _hitResult n/a * Ability attribute for Gorilla Tactics
* @param _args n/a * @extends ExecutedMoveAbAttr
*/ */
override applyPostAttack( export class GorillaTacticsAbAttr extends ExecutedMoveAbAttr {
pokemon: Pokemon, constructor(showAbility: boolean = false) {
_passive: boolean, super(showAbility);
simulated: boolean, }
_defender: Pokemon,
_move: Move, override canApplyExecutedMove(pokemon: Pokemon, simulated: boolean): boolean {
_hitResult: HitResult | null, return simulated || !pokemon.getTag(BattlerTagType.GORILLA_TACTICS);
_args: any[], }
): void {
override applyExecutedMove(pokemon: Pokemon, simulated: boolean): void {
if (!simulated) { if (!simulated) {
pokemon.addTag(BattlerTagType.GORILLA_TACTICS); pokemon.addTag(BattlerTagType.GORILLA_TACTICS);
} }
@ -7797,6 +7787,22 @@ export function applyPreAttackAbAttrs(
); );
} }
export function applyExecutedMoveAbAttrs(
attrType: Constructor<ExecutedMoveAbAttr>,
pokemon: Pokemon,
simulated: boolean = false,
...args: any[]
): void {
applyAbAttrsInternal<ExecutedMoveAbAttr>(
attrType,
pokemon,
attr => attr.applyExecutedMove(pokemon, simulated),
attr => attr.canApplyExecutedMove(pokemon, simulated),
args,
simulated,
);
}
export function applyPostAttackAbAttrs( export function applyPostAttackAbAttrs(
attrType: Constructor<PostAttackAbAttr>, attrType: Constructor<PostAttackAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,

View File

@ -5988,6 +5988,7 @@ export const tmSpecies: TmSpecies = {
SpeciesId.FEZANDIPITI, SpeciesId.FEZANDIPITI,
SpeciesId.ARCHALUDON, SpeciesId.ARCHALUDON,
SpeciesId.IRON_CROWN, SpeciesId.IRON_CROWN,
SpeciesId.TERAPAGOS,
SpeciesId.ALOLA_RATICATE, SpeciesId.ALOLA_RATICATE,
SpeciesId.ALOLA_RAICHU, SpeciesId.ALOLA_RAICHU,
SpeciesId.ALOLA_SANDSLASH, SpeciesId.ALOLA_SANDSLASH,
@ -16248,6 +16249,7 @@ export const tmSpecies: TmSpecies = {
SpeciesId.CALYREX, SpeciesId.CALYREX,
SpeciesId.SANDY_SHOCKS, SpeciesId.SANDY_SHOCKS,
SpeciesId.IRON_JUGULIS, SpeciesId.IRON_JUGULIS,
SpeciesId.TERAPAGOS,
SpeciesId.ALOLA_DUGTRIO, SpeciesId.ALOLA_DUGTRIO,
SpeciesId.GALAR_SLOWPOKE, SpeciesId.GALAR_SLOWPOKE,
SpeciesId.GALAR_SLOWBRO, SpeciesId.GALAR_SLOWBRO,
@ -39466,6 +39468,8 @@ export const tmSpecies: TmSpecies = {
SpeciesId.FARFETCHD, SpeciesId.FARFETCHD,
SpeciesId.DODUO, SpeciesId.DODUO,
SpeciesId.DODRIO, SpeciesId.DODRIO,
SpeciesId.DEWGONG,
SpeciesId.GRIMER,
SpeciesId.MUK, SpeciesId.MUK,
SpeciesId.GASTLY, SpeciesId.GASTLY,
SpeciesId.HAUNTER, SpeciesId.HAUNTER,
@ -39477,6 +39481,7 @@ export const tmSpecies: TmSpecies = {
SpeciesId.CUBONE, SpeciesId.CUBONE,
SpeciesId.MAROWAK, SpeciesId.MAROWAK,
SpeciesId.HITMONLEE, SpeciesId.HITMONLEE,
SpeciesId.HITMONCHAN,
SpeciesId.LICKITUNG, SpeciesId.LICKITUNG,
SpeciesId.TANGELA, SpeciesId.TANGELA,
SpeciesId.GOLDEEN, SpeciesId.GOLDEEN,
@ -48806,6 +48811,7 @@ export const tmSpecies: TmSpecies = {
SpeciesId.GARGANACL, SpeciesId.GARGANACL,
SpeciesId.GLIMMET, SpeciesId.GLIMMET,
SpeciesId.GLIMMORA, SpeciesId.GLIMMORA,
SpeciesId.TERAPAGOS,
SpeciesId.ALOLA_GEODUDE, SpeciesId.ALOLA_GEODUDE,
SpeciesId.ALOLA_GRAVELER, SpeciesId.ALOLA_GRAVELER,
SpeciesId.ALOLA_GOLEM, SpeciesId.ALOLA_GOLEM,
@ -53077,6 +53083,7 @@ export const tmSpecies: TmSpecies = {
SpeciesId.MIRAIDON, SpeciesId.MIRAIDON,
SpeciesId.ARCHALUDON, SpeciesId.ARCHALUDON,
SpeciesId.IRON_CROWN, SpeciesId.IRON_CROWN,
SpeciesId.TERAPAGOS,
[ [
SpeciesId.WORMADAM, SpeciesId.WORMADAM,
"trash", "trash",

View File

@ -3,10 +3,12 @@ import { globalScene } from "#app/global-scene";
import { import {
AddSecondStrikeAbAttr, AddSecondStrikeAbAttr,
AlwaysHitAbAttr, AlwaysHitAbAttr,
applyExecutedMoveAbAttrs,
applyPostAttackAbAttrs, applyPostAttackAbAttrs,
applyPostDamageAbAttrs, applyPostDamageAbAttrs,
applyPostDefendAbAttrs, applyPostDefendAbAttrs,
applyPreAttackAbAttrs, applyPreAttackAbAttrs,
ExecutedMoveAbAttr,
IgnoreMoveEffectsAbAttr, IgnoreMoveEffectsAbAttr,
MaxMultiHitAbAttr, MaxMultiHitAbAttr,
PostAttackAbAttr, PostAttackAbAttr,
@ -380,6 +382,7 @@ export class MoveEffectPhase extends PokemonPhase {
// Add to the move history entry // Add to the move history entry
if (this.firstHit) { if (this.firstHit) {
user.pushMoveHistory(this.moveHistoryEntry); user.pushMoveHistory(this.moveHistoryEntry);
applyExecutedMoveAbAttrs(ExecutedMoveAbAttr, user);
} }
try { try {

View File

@ -31,6 +31,8 @@ import Overrides from "#app/overrides";
import type { CustomModifierSettings } from "#app/modifier/modifier-type"; import type { CustomModifierSettings } from "#app/modifier/modifier-type";
import { isNullOrUndefined, NumberHolder } from "#app/utils/common"; import { isNullOrUndefined, NumberHolder } from "#app/utils/common";
export type ModifierSelectCallback = (rowCursor: number, cursor: number) => boolean;
export class SelectModifierPhase extends BattlePhase { export class SelectModifierPhase extends BattlePhase {
public readonly phaseName = "SelectModifierPhase"; public readonly phaseName = "SelectModifierPhase";
private rerollCount: number; private rerollCount: number;
@ -57,6 +59,10 @@ export class SelectModifierPhase extends BattlePhase {
start() { start() {
super.start(); super.start();
if (!this.isPlayer()) {
return false;
}
if (!this.rerollCount && !this.isCopy) { if (!this.rerollCount && !this.isCopy) {
this.updateSeed(); this.updateSeed();
} else if (this.rerollCount) { } else if (this.rerollCount) {
@ -67,27 +73,9 @@ export class SelectModifierPhase extends BattlePhase {
if (!this.isCopy) { if (!this.isCopy) {
regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount); regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount);
} }
const modifierCount = new NumberHolder(3); const modifierCount = this.getModifierCount();
if (this.isPlayer()) {
globalScene.applyModifiers(ExtraModifierModifier, true, modifierCount);
globalScene.applyModifiers(TempExtraModifierModifier, true, modifierCount);
}
// If custom modifiers are specified, overrides default item count this.typeOptions = this.getModifierTypeOptions(modifierCount);
if (this.customModifierSettings) {
const newItemCount =
(this.customModifierSettings.guaranteedModifierTiers?.length || 0) +
(this.customModifierSettings.guaranteedModifierTypeOptions?.length || 0) +
(this.customModifierSettings.guaranteedModifierTypeFuncs?.length || 0);
if (this.customModifierSettings.fillRemaining) {
const originalCount = modifierCount.value;
modifierCount.value = originalCount > newItemCount ? originalCount : newItemCount;
} else {
modifierCount.value = newItemCount;
}
}
this.typeOptions = this.getModifierTypeOptions(modifierCount.value);
const modifierSelectCallback = (rowCursor: number, cursor: number) => { const modifierSelectCallback = (rowCursor: number, cursor: number) => {
if (rowCursor < 0 || cursor < 0) { if (rowCursor < 0 || cursor < 0) {
@ -99,256 +87,312 @@ export class SelectModifierPhase extends BattlePhase {
globalScene.ui.setMode(UiMode.MESSAGE); globalScene.ui.setMode(UiMode.MESSAGE);
super.end(); super.end();
}, },
() => () => this.resetModifierSelect(modifierSelectCallback),
globalScene.ui.setMode(
UiMode.MODIFIER_SELECT,
this.isPlayer(),
this.typeOptions,
modifierSelectCallback,
this.getRerollCost(globalScene.lockModifierTiers),
),
); );
}); });
return false; return false;
} }
let modifierType: ModifierType;
let cost: number;
const rerollCost = this.getRerollCost(globalScene.lockModifierTiers);
switch (rowCursor) { switch (rowCursor) {
// Execute one of the options from the bottom row
case 0: case 0:
switch (cursor) { switch (cursor) {
case 0: case 0:
if (rerollCost < 0 || globalScene.money < rerollCost) { return this.rerollModifiers();
globalScene.ui.playError();
return false;
}
globalScene.reroll = true;
globalScene.phaseManager.unshiftNew(
"SelectModifierPhase",
this.rerollCount + 1,
this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[],
);
globalScene.ui.clearText();
globalScene.ui.setMode(UiMode.MESSAGE).then(() => super.end());
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
globalScene.money -= rerollCost;
globalScene.updateMoneyText();
globalScene.animateMoneyChanged(false);
}
globalScene.playSound("se/buy");
break;
case 1: case 1:
globalScene.ui.setModeWithoutClear( return this.openModifierTransferScreen(modifierSelectCallback);
UiMode.PARTY, // Check the party, pass a callback to restore the modifier select screen.
PartyUiMode.MODIFIER_TRANSFER,
-1,
(fromSlotIndex: number, itemIndex: number, itemQuantity: number, toSlotIndex: number) => {
if (
toSlotIndex !== undefined &&
fromSlotIndex < 6 &&
toSlotIndex < 6 &&
fromSlotIndex !== toSlotIndex &&
itemIndex > -1
) {
const itemModifiers = globalScene.findModifiers(
m =>
m instanceof PokemonHeldItemModifier &&
m.isTransferable &&
m.pokemonId === party[fromSlotIndex].id,
) as PokemonHeldItemModifier[];
const itemModifier = itemModifiers[itemIndex];
globalScene.tryTransferHeldItemModifier(
itemModifier,
party[toSlotIndex],
true,
itemQuantity,
false,
);
} else {
globalScene.ui.setMode(
UiMode.MODIFIER_SELECT,
this.isPlayer(),
this.typeOptions,
modifierSelectCallback,
this.getRerollCost(globalScene.lockModifierTiers),
);
}
},
PartyUiHandler.FilterItemMaxStacks,
);
break;
case 2: case 2:
globalScene.ui.setModeWithoutClear(UiMode.PARTY, PartyUiMode.CHECK, -1, () => { globalScene.ui.setModeWithoutClear(UiMode.PARTY, PartyUiMode.CHECK, -1, () => {
globalScene.ui.setMode( this.resetModifierSelect(modifierSelectCallback);
UiMode.MODIFIER_SELECT,
this.isPlayer(),
this.typeOptions,
modifierSelectCallback,
this.getRerollCost(globalScene.lockModifierTiers),
);
}); });
break; return true;
case 3: case 3:
if (rerollCost < 0) { return this.toggleRerollLock();
// Reroll lock button is also disabled when reroll is disabled default:
globalScene.ui.playError();
return false;
}
globalScene.lockModifierTiers = !globalScene.lockModifierTiers;
const uiHandler = globalScene.ui.getHandler() as ModifierSelectUiHandler;
uiHandler.setRerollCost(this.getRerollCost(globalScene.lockModifierTiers));
uiHandler.updateLockRaritiesText();
uiHandler.updateRerollCostText();
return false; return false;
} }
return true; // Pick an option from the rewards
case 1: case 1:
if (this.typeOptions.length === 0) { return this.selectRewardModifierOption(cursor, modifierSelectCallback);
globalScene.ui.clearText(); // Pick an option from the shop
globalScene.ui.setMode(UiMode.MESSAGE); default: {
super.end(); return this.selectShopModifierOption(rowCursor, cursor, modifierSelectCallback);
return true;
}
if (this.typeOptions[cursor].type) {
modifierType = this.typeOptions[cursor].type;
}
break;
default:
const shopOptions = getPlayerShopModifierTypeOptionsForWave(
globalScene.currentBattle.waveIndex,
globalScene.getWaveMoneyAmount(1),
);
const shopOption =
shopOptions[
rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT
];
if (shopOption.type) {
modifierType = shopOption.type;
}
// Apply Black Sludge to healing item cost
const healingItemCost = new NumberHolder(shopOption.cost);
globalScene.applyModifier(HealShopCostModifier, true, healingItemCost);
cost = healingItemCost.value;
break;
}
if (cost! && globalScene.money < cost && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
// TODO: is the bang on cost correct?
globalScene.ui.playError();
return false;
}
const applyModifier = (modifier: Modifier, playSound = false) => {
const result = globalScene.addModifier(modifier, false, playSound, undefined, undefined, cost);
// Queue a copy of this phase when applying a TM or Memory Mushroom.
// If the player selects either of these, then escapes out of consuming them,
// they are returned to a shop in the same state.
if (modifier.type instanceof RememberMoveModifierType || modifier.type instanceof TmModifierType) {
globalScene.phaseManager.unshiftPhase(this.copy());
} }
if (cost && !(modifier.type instanceof RememberMoveModifierType)) {
if (result) {
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
globalScene.money -= cost;
globalScene.updateMoneyText();
globalScene.animateMoneyChanged(false);
}
globalScene.playSound("se/buy");
(globalScene.ui.getHandler() as ModifierSelectUiHandler).updateCostText();
} else {
globalScene.ui.playError();
}
} else {
globalScene.ui.clearText();
globalScene.ui.setMode(UiMode.MESSAGE);
super.end();
}
};
if (modifierType! instanceof PokemonModifierType) {
//TODO: is the bang correct?
if (modifierType instanceof FusePokemonModifierType) {
globalScene.ui.setModeWithoutClear(
UiMode.PARTY,
PartyUiMode.SPLICE,
-1,
(fromSlotIndex: number, spliceSlotIndex: number) => {
if (
spliceSlotIndex !== undefined &&
fromSlotIndex < 6 &&
spliceSlotIndex < 6 &&
fromSlotIndex !== spliceSlotIndex
) {
globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => {
const modifier = modifierType.newModifier(party[fromSlotIndex], party[spliceSlotIndex])!; //TODO: is the bang correct?
applyModifier(modifier, true);
});
} else {
globalScene.ui.setMode(
UiMode.MODIFIER_SELECT,
this.isPlayer(),
this.typeOptions,
modifierSelectCallback,
this.getRerollCost(globalScene.lockModifierTiers),
);
}
},
modifierType.selectFilter,
);
} else {
const pokemonModifierType = modifierType as PokemonModifierType;
const isMoveModifier = modifierType instanceof PokemonMoveModifierType;
const isTmModifier = modifierType instanceof TmModifierType;
const isRememberMoveModifier = modifierType instanceof RememberMoveModifierType;
const isPpRestoreModifier =
modifierType instanceof PokemonPpRestoreModifierType || modifierType instanceof PokemonPpUpModifierType;
const partyUiMode = isMoveModifier
? PartyUiMode.MOVE_MODIFIER
: isTmModifier
? PartyUiMode.TM_MODIFIER
: isRememberMoveModifier
? PartyUiMode.REMEMBER_MOVE_MODIFIER
: PartyUiMode.MODIFIER;
const tmMoveId = isTmModifier ? (modifierType as TmModifierType).moveId : undefined;
globalScene.ui.setModeWithoutClear(
UiMode.PARTY,
partyUiMode,
-1,
(slotIndex: number, option: PartyOption) => {
if (slotIndex < 6) {
globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => {
const modifier = !isMoveModifier
? !isRememberMoveModifier
? modifierType.newModifier(party[slotIndex])
: modifierType.newModifier(party[slotIndex], option as number)
: modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1);
applyModifier(modifier!, true); // TODO: is the bang correct?
});
} else {
globalScene.ui.setMode(
UiMode.MODIFIER_SELECT,
this.isPlayer(),
this.typeOptions,
modifierSelectCallback,
this.getRerollCost(globalScene.lockModifierTiers),
);
}
},
pokemonModifierType.selectFilter,
modifierType instanceof PokemonMoveModifierType
? (modifierType as PokemonMoveModifierType).moveSelectFilter
: undefined,
tmMoveId,
isPpRestoreModifier,
);
}
} else {
applyModifier(modifierType!.newModifier()!); // TODO: is the bang correct?
} }
return !cost!; // TODO: is the bang correct?
}; };
this.resetModifierSelect(modifierSelectCallback);
}
// Pick a modifier from among the rewards and apply it
private selectRewardModifierOption(cursor: number, modifierSelectCallback: ModifierSelectCallback): boolean {
if (this.typeOptions.length === 0) {
globalScene.ui.clearText();
globalScene.ui.setMode(UiMode.MESSAGE);
super.end();
return true;
}
const modifierType = this.typeOptions[cursor].type;
return this.applyChosenModifier(modifierType, 0, modifierSelectCallback);
}
// Pick a modifier from the shop and apply it
private selectShopModifierOption(
rowCursor: number,
cursor: number,
modifierSelectCallback: ModifierSelectCallback,
): boolean {
const shopOptions = getPlayerShopModifierTypeOptionsForWave(
globalScene.currentBattle.waveIndex,
globalScene.getWaveMoneyAmount(1),
);
const shopOption =
shopOptions[
rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT
];
const modifierType = shopOption.type;
// Apply Black Sludge to healing item cost
const healingItemCost = new NumberHolder(shopOption.cost);
globalScene.applyModifier(HealShopCostModifier, true, healingItemCost);
const cost = healingItemCost.value;
if (globalScene.money < cost && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
globalScene.ui.playError();
return false;
}
return this.applyChosenModifier(modifierType, cost, modifierSelectCallback);
}
// Apply a chosen modifier: do an effect or open the party menu
private applyChosenModifier(
modifierType: ModifierType,
cost: number,
modifierSelectCallback: ModifierSelectCallback,
): boolean {
if (modifierType instanceof PokemonModifierType) {
if (modifierType instanceof FusePokemonModifierType) {
this.openFusionMenu(modifierType, cost, modifierSelectCallback);
} else {
this.openModifierMenu(modifierType, cost, modifierSelectCallback);
}
} else {
this.applyModifier(modifierType.newModifier()!);
}
return !cost;
}
// Reroll rewards
private rerollModifiers() {
const rerollCost = this.getRerollCost(globalScene.lockModifierTiers);
if (rerollCost < 0 || globalScene.money < rerollCost) {
globalScene.ui.playError();
return false;
}
globalScene.reroll = true;
globalScene.phaseManager.unshiftNew(
"SelectModifierPhase",
this.rerollCount + 1,
this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[],
);
globalScene.ui.clearText();
globalScene.ui.setMode(UiMode.MESSAGE).then(() => super.end());
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
globalScene.money -= rerollCost;
globalScene.updateMoneyText();
globalScene.animateMoneyChanged(false);
}
globalScene.playSound("se/buy");
return true;
}
// Transfer modifiers among party pokemon
private openModifierTransferScreen(modifierSelectCallback: ModifierSelectCallback) {
const party = globalScene.getPlayerParty();
globalScene.ui.setModeWithoutClear(
UiMode.PARTY,
PartyUiMode.MODIFIER_TRANSFER,
-1,
(fromSlotIndex: number, itemIndex: number, itemQuantity: number, toSlotIndex: number) => {
if (
toSlotIndex !== undefined &&
fromSlotIndex < 6 &&
toSlotIndex < 6 &&
fromSlotIndex !== toSlotIndex &&
itemIndex > -1
) {
const itemModifiers = globalScene.findModifiers(
m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === party[fromSlotIndex].id,
) as PokemonHeldItemModifier[];
const itemModifier = itemModifiers[itemIndex];
globalScene.tryTransferHeldItemModifier(
itemModifier,
party[toSlotIndex],
true,
itemQuantity,
undefined,
undefined,
false,
);
} else {
this.resetModifierSelect(modifierSelectCallback);
}
},
PartyUiHandler.FilterItemMaxStacks,
);
return true;
}
// Toggle reroll lock
private toggleRerollLock() {
const rerollCost = this.getRerollCost(globalScene.lockModifierTiers);
if (rerollCost < 0) {
// Reroll lock button is also disabled when reroll is disabled
globalScene.ui.playError();
return false;
}
globalScene.lockModifierTiers = !globalScene.lockModifierTiers;
const uiHandler = globalScene.ui.getHandler() as ModifierSelectUiHandler;
uiHandler.setRerollCost(this.getRerollCost(globalScene.lockModifierTiers));
uiHandler.updateLockRaritiesText();
uiHandler.updateRerollCostText();
return false;
}
// Applies the effects of the chosen modifier
private applyModifier(modifier: Modifier, cost = 0, playSound = false): void {
const result = globalScene.addModifier(modifier, false, playSound, undefined, undefined, cost);
// Queue a copy of this phase when applying a TM or Memory Mushroom.
// If the player selects either of these, then escapes out of consuming them,
// they are returned to a shop in the same state.
if (modifier.type instanceof RememberMoveModifierType || modifier.type instanceof TmModifierType) {
globalScene.phaseManager.unshiftPhase(this.copy());
}
if (cost && !(modifier.type instanceof RememberMoveModifierType)) {
if (result) {
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
globalScene.money -= cost;
globalScene.updateMoneyText();
globalScene.animateMoneyChanged(false);
}
globalScene.playSound("se/buy");
(globalScene.ui.getHandler() as ModifierSelectUiHandler).updateCostText();
} else {
globalScene.ui.playError();
}
} else {
globalScene.ui.clearText();
globalScene.ui.setMode(UiMode.MESSAGE);
super.end();
}
}
// Opens the party menu specifically for fusions
private openFusionMenu(
modifierType: PokemonModifierType,
cost: number,
modifierSelectCallback: ModifierSelectCallback,
): void {
const party = globalScene.getPlayerParty();
globalScene.ui.setModeWithoutClear(
UiMode.PARTY,
PartyUiMode.SPLICE,
-1,
(fromSlotIndex: number, spliceSlotIndex: number) => {
if (
spliceSlotIndex !== undefined &&
fromSlotIndex < 6 &&
spliceSlotIndex < 6 &&
fromSlotIndex !== spliceSlotIndex
) {
globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => {
const modifier = modifierType.newModifier(party[fromSlotIndex], party[spliceSlotIndex])!; //TODO: is the bang correct?
this.applyModifier(modifier, cost, true);
});
} else {
this.resetModifierSelect(modifierSelectCallback);
}
},
modifierType.selectFilter,
);
}
// Opens the party menu to apply one of various modifiers
private openModifierMenu(
modifierType: PokemonModifierType,
cost: number,
modifierSelectCallback: ModifierSelectCallback,
): void {
const party = globalScene.getPlayerParty();
const pokemonModifierType = modifierType as PokemonModifierType;
const isMoveModifier = modifierType instanceof PokemonMoveModifierType;
const isTmModifier = modifierType instanceof TmModifierType;
const isRememberMoveModifier = modifierType instanceof RememberMoveModifierType;
const isPpRestoreModifier =
modifierType instanceof PokemonPpRestoreModifierType || modifierType instanceof PokemonPpUpModifierType;
const partyUiMode = isMoveModifier
? PartyUiMode.MOVE_MODIFIER
: isTmModifier
? PartyUiMode.TM_MODIFIER
: isRememberMoveModifier
? PartyUiMode.REMEMBER_MOVE_MODIFIER
: PartyUiMode.MODIFIER;
const tmMoveId = isTmModifier ? (modifierType as TmModifierType).moveId : undefined;
globalScene.ui.setModeWithoutClear(
UiMode.PARTY,
partyUiMode,
-1,
(slotIndex: number, option: PartyOption) => {
if (slotIndex < 6) {
globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => {
const modifier = !isMoveModifier
? !isRememberMoveModifier
? modifierType.newModifier(party[slotIndex])
: modifierType.newModifier(party[slotIndex], option as number)
: modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1);
this.applyModifier(modifier!, cost, true); // TODO: is the bang correct?
});
} else {
this.resetModifierSelect(modifierSelectCallback);
}
},
pokemonModifierType.selectFilter,
modifierType instanceof PokemonMoveModifierType
? (modifierType as PokemonMoveModifierType).moveSelectFilter
: undefined,
tmMoveId,
isPpRestoreModifier,
);
}
// Function that determines how many reward slots are available
private getModifierCount(): number {
const modifierCountHolder = new NumberHolder(3);
globalScene.applyModifiers(ExtraModifierModifier, true, modifierCountHolder);
globalScene.applyModifiers(TempExtraModifierModifier, true, modifierCountHolder);
// If custom modifiers are specified, overrides default item count
if (this.customModifierSettings) {
const newItemCount =
(this.customModifierSettings.guaranteedModifierTiers?.length ?? 0) +
(this.customModifierSettings.guaranteedModifierTypeOptions?.length ?? 0) +
(this.customModifierSettings.guaranteedModifierTypeFuncs?.length ?? 0);
if (this.customModifierSettings.fillRemaining) {
const originalCount = modifierCountHolder.value;
modifierCountHolder.value = originalCount > newItemCount ? originalCount : newItemCount;
} else {
modifierCountHolder.value = newItemCount;
}
}
return modifierCountHolder.value;
}
// Function that resets the reward selection screen,
// e.g. after pressing cancel in the party ui or while learning a move
private resetModifierSelect(modifierSelectCallback: ModifierSelectCallback) {
globalScene.ui.setMode( globalScene.ui.setMode(
UiMode.MODIFIER_SELECT, UiMode.MODIFIER_SELECT,
this.isPlayer(), this.isPlayer(),

View File

@ -25,7 +25,7 @@ describe("Abilities - Dancer", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override.battleStyle("double"); game.override.battleStyle("double").enemyAbility(AbilityId.BALL_FETCH);
}); });
// Reference Link: https://bulbapedia.bulbagarden.net/wiki/Dancer_(Ability) // Reference Link: https://bulbapedia.bulbagarden.net/wiki/Dancer_(Ability)

View File

@ -73,9 +73,38 @@ describe("Abilities - Gorilla Tactics", () => {
await game.toNextTurn(); await game.toNextTurn();
game.move.select(MoveId.TACKLE); game.move.select(MoveId.TACKLE);
await game.move.forceEnemyMove(MoveId.SPLASH); //prevent protect from being used by the enemy
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to("MoveEndPhase"); await game.phaseInterceptor.to("MoveEndPhase");
expect(darmanitan.hp).toBeLessThan(darmanitan.getMaxHp()); expect(darmanitan.hp).toBeLessThan(darmanitan.getMaxHp());
}); });
it("should activate when the opponenet protects", async () => {
await game.classicMode.startBattle([SpeciesId.GALAR_DARMANITAN]);
const darmanitan = game.field.getPlayerPokemon();
game.move.select(MoveId.TACKLE);
await game.move.selectEnemyMove(MoveId.PROTECT);
await game.toEndOfTurn();
expect(darmanitan.isMoveRestricted(MoveId.SPLASH)).toBe(true);
expect(darmanitan.isMoveRestricted(MoveId.TACKLE)).toBe(false);
});
it("should activate when a move is succesfully executed but misses", async () => {
await game.classicMode.startBattle([SpeciesId.GALAR_DARMANITAN]);
const darmanitan = game.field.getPlayerPokemon();
game.move.select(MoveId.TACKLE);
await game.move.selectEnemyMove(MoveId.SPLASH);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.move.forceMiss();
await game.toEndOfTurn();
expect(darmanitan.isMoveRestricted(MoveId.SPLASH)).toBe(true);
expect(darmanitan.isMoveRestricted(MoveId.TACKLE)).toBe(false);
});
}); });