This commit is contained in:
AJ Fontaine 2025-02-21 12:01:48 -05:00
commit 009348d13d
65 changed files with 3650 additions and 1691 deletions

View File

@ -170,7 +170,7 @@ input:-internal-autofill-selected {
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleNature, #touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT']) #apadCycleNature,
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX_PAGE'], [data-ui-mode='RUN_INFO']) #apadCycleAbility, #touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX_PAGE'], [data-ui-mode='RUN_INFO']) #apadCycleAbility,
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX_PAGE']) #apadCycleGender, #touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX_PAGE']) #apadCycleGender,
#touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX']) #apadCycleVariant { #touchControls:not(.config-mode):not([data-ui-mode='STARTER_SELECT'], [data-ui-mode='POKEDEX']) #apadCycleTera {
display: none; display: none;
} }

View File

@ -129,7 +129,7 @@
<div id="apadCycleNature" class="apad-button apad-square apad-small" data-key="CYCLE_NATURE"> <div id="apadCycleNature" class="apad-button apad-square apad-small" data-key="CYCLE_NATURE">
<span class="apad-label">N</span> <span class="apad-label">N</span>
</div> </div>
<div id="apadCycleVariant" class="apad-button apad-square apad-small" data-key="V"> <div id="apadCycleTera" class="apad-button apad-square apad-small" data-key="CYCLE_TERA">
<span class="apad-label">V</span> <span class="apad-label">V</span>
</div> </div>
<!-- buttons to display battle-specific information --> <!-- buttons to display battle-specific information -->

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

@ -1 +1 @@
Subproject commit 58dda14ee834204c4bd5ece47694a3c068df4b0e Subproject commit ef43efffe5fe454862c350f1b9393c3ad755bcc2

File diff suppressed because it is too large Load Diff

View File

@ -180,9 +180,9 @@ const cfg_keyboard_qwerty = {
[SettingKeyboard.Button_Cycle_Gender]: Button.CYCLE_GENDER, [SettingKeyboard.Button_Cycle_Gender]: Button.CYCLE_GENDER,
[SettingKeyboard.Button_Cycle_Ability]: Button.CYCLE_ABILITY, [SettingKeyboard.Button_Cycle_Ability]: Button.CYCLE_ABILITY,
[SettingKeyboard.Button_Cycle_Nature]: Button.CYCLE_NATURE, [SettingKeyboard.Button_Cycle_Nature]: Button.CYCLE_NATURE,
[SettingKeyboard.Button_Cycle_Variant]: Button.V,
[SettingKeyboard.Button_Speed_Up]: Button.SPEED_UP, [SettingKeyboard.Button_Speed_Up]: Button.SPEED_UP,
[SettingKeyboard.Button_Slow_Down]: Button.SLOW_DOWN, [SettingKeyboard.Button_Slow_Down]: Button.SLOW_DOWN,
[SettingKeyboard.Button_Cycle_Tera]: Button.CYCLE_TERA,
[SettingKeyboard.Alt_Button_Up]: Button.UP, [SettingKeyboard.Alt_Button_Up]: Button.UP,
[SettingKeyboard.Alt_Button_Down]: Button.DOWN, [SettingKeyboard.Alt_Button_Down]: Button.DOWN,
[SettingKeyboard.Alt_Button_Left]: Button.LEFT, [SettingKeyboard.Alt_Button_Left]: Button.LEFT,
@ -197,9 +197,9 @@ const cfg_keyboard_qwerty = {
[SettingKeyboard.Alt_Button_Cycle_Gender]: Button.CYCLE_GENDER, [SettingKeyboard.Alt_Button_Cycle_Gender]: Button.CYCLE_GENDER,
[SettingKeyboard.Alt_Button_Cycle_Ability]: Button.CYCLE_ABILITY, [SettingKeyboard.Alt_Button_Cycle_Ability]: Button.CYCLE_ABILITY,
[SettingKeyboard.Alt_Button_Cycle_Nature]: Button.CYCLE_NATURE, [SettingKeyboard.Alt_Button_Cycle_Nature]: Button.CYCLE_NATURE,
[SettingKeyboard.Alt_Button_Cycle_Variant]: Button.V,
[SettingKeyboard.Alt_Button_Speed_Up]: Button.SPEED_UP, [SettingKeyboard.Alt_Button_Speed_Up]: Button.SPEED_UP,
[SettingKeyboard.Alt_Button_Slow_Down]: Button.SLOW_DOWN, [SettingKeyboard.Alt_Button_Slow_Down]: Button.SLOW_DOWN,
[SettingKeyboard.Alt_Button_Cycle_Tera]: Button.CYCLE_TERA,
}, },
default: { default: {
KEY_ARROW_UP: SettingKeyboard.Button_Up, KEY_ARROW_UP: SettingKeyboard.Button_Up,
@ -216,7 +216,7 @@ const cfg_keyboard_qwerty = {
KEY_G: SettingKeyboard.Button_Cycle_Gender, KEY_G: SettingKeyboard.Button_Cycle_Gender,
KEY_E: SettingKeyboard.Button_Cycle_Ability, KEY_E: SettingKeyboard.Button_Cycle_Ability,
KEY_N: SettingKeyboard.Button_Cycle_Nature, KEY_N: SettingKeyboard.Button_Cycle_Nature,
KEY_V: SettingKeyboard.Button_Cycle_Variant, KEY_V: SettingKeyboard.Button_Cycle_Tera,
KEY_PLUS: -1, KEY_PLUS: -1,
KEY_MINUS: -1, KEY_MINUS: -1,
KEY_A: SettingKeyboard.Alt_Button_Left, KEY_A: SettingKeyboard.Alt_Button_Left,

View File

@ -53,7 +53,7 @@ const pad_dualshock = {
[SettingGamepad.Button_Action]: Button.ACTION, [SettingGamepad.Button_Action]: Button.ACTION,
[SettingGamepad.Button_Cancel]: Button.CANCEL, [SettingGamepad.Button_Cancel]: Button.CANCEL,
[SettingGamepad.Button_Cycle_Nature]: Button.CYCLE_NATURE, [SettingGamepad.Button_Cycle_Nature]: Button.CYCLE_NATURE,
[SettingGamepad.Button_Cycle_Variant]: Button.V, [SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA,
[SettingGamepad.Button_Menu]: Button.MENU, [SettingGamepad.Button_Menu]: Button.MENU,
[SettingGamepad.Button_Stats]: Button.STATS, [SettingGamepad.Button_Stats]: Button.STATS,
[SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM, [SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM,
@ -72,7 +72,7 @@ const pad_dualshock = {
RC_S: SettingGamepad.Button_Action, RC_S: SettingGamepad.Button_Action,
RC_E: SettingGamepad.Button_Cancel, RC_E: SettingGamepad.Button_Cancel,
RC_W: SettingGamepad.Button_Cycle_Nature, RC_W: SettingGamepad.Button_Cycle_Nature,
RC_N: SettingGamepad.Button_Cycle_Variant, RC_N: SettingGamepad.Button_Cycle_Tera,
START: SettingGamepad.Button_Menu, START: SettingGamepad.Button_Menu,
SELECT: SettingGamepad.Button_Stats, SELECT: SettingGamepad.Button_Stats,
LB: SettingGamepad.Button_Cycle_Form, LB: SettingGamepad.Button_Cycle_Form,

View File

@ -51,7 +51,7 @@ const pad_generic = {
[SettingGamepad.Button_Action]: Button.ACTION, [SettingGamepad.Button_Action]: Button.ACTION,
[SettingGamepad.Button_Cancel]: Button.CANCEL, [SettingGamepad.Button_Cancel]: Button.CANCEL,
[SettingGamepad.Button_Cycle_Nature]: Button.CYCLE_NATURE, [SettingGamepad.Button_Cycle_Nature]: Button.CYCLE_NATURE,
[SettingGamepad.Button_Cycle_Variant]: Button.V, [SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA,
[SettingGamepad.Button_Menu]: Button.MENU, [SettingGamepad.Button_Menu]: Button.MENU,
[SettingGamepad.Button_Stats]: Button.STATS, [SettingGamepad.Button_Stats]: Button.STATS,
[SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM, [SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM,
@ -69,7 +69,7 @@ const pad_generic = {
RC_S: SettingGamepad.Button_Action, RC_S: SettingGamepad.Button_Action,
RC_E: SettingGamepad.Button_Cancel, RC_E: SettingGamepad.Button_Cancel,
RC_W: SettingGamepad.Button_Cycle_Nature, RC_W: SettingGamepad.Button_Cycle_Nature,
RC_N: SettingGamepad.Button_Cycle_Variant, RC_N: SettingGamepad.Button_Cycle_Tera,
START: SettingGamepad.Button_Menu, START: SettingGamepad.Button_Menu,
SELECT: SettingGamepad.Button_Stats, SELECT: SettingGamepad.Button_Stats,
LB: SettingGamepad.Button_Cycle_Form, LB: SettingGamepad.Button_Cycle_Form,

View File

@ -52,7 +52,7 @@ const pad_procon = {
[SettingGamepad.Button_Action]: Button.ACTION, [SettingGamepad.Button_Action]: Button.ACTION,
[SettingGamepad.Button_Cancel]: Button.CANCEL, [SettingGamepad.Button_Cancel]: Button.CANCEL,
[SettingGamepad.Button_Cycle_Nature]: Button.CYCLE_NATURE, [SettingGamepad.Button_Cycle_Nature]: Button.CYCLE_NATURE,
[SettingGamepad.Button_Cycle_Variant]: Button.V, [SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA,
[SettingGamepad.Button_Menu]: Button.MENU, [SettingGamepad.Button_Menu]: Button.MENU,
[SettingGamepad.Button_Stats]: Button.STATS, [SettingGamepad.Button_Stats]: Button.STATS,
[SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM, [SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM,
@ -70,7 +70,7 @@ const pad_procon = {
RC_S: SettingGamepad.Button_Action, RC_S: SettingGamepad.Button_Action,
RC_E: SettingGamepad.Button_Cancel, RC_E: SettingGamepad.Button_Cancel,
RC_W: SettingGamepad.Button_Cycle_Nature, RC_W: SettingGamepad.Button_Cycle_Nature,
RC_N: SettingGamepad.Button_Cycle_Variant, RC_N: SettingGamepad.Button_Cycle_Tera,
START: SettingGamepad.Button_Menu, START: SettingGamepad.Button_Menu,
SELECT: SettingGamepad.Button_Stats, SELECT: SettingGamepad.Button_Stats,
LB: SettingGamepad.Button_Cycle_Form, LB: SettingGamepad.Button_Cycle_Form,

View File

@ -43,7 +43,7 @@ const pad_unlicensedSNES = {
[SettingGamepad.Button_Action]: Button.ACTION, [SettingGamepad.Button_Action]: Button.ACTION,
[SettingGamepad.Button_Cancel]: Button.CANCEL, [SettingGamepad.Button_Cancel]: Button.CANCEL,
[SettingGamepad.Button_Cycle_Nature]: Button.CYCLE_NATURE, [SettingGamepad.Button_Cycle_Nature]: Button.CYCLE_NATURE,
[SettingGamepad.Button_Cycle_Variant]: Button.V, [SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA,
[SettingGamepad.Button_Menu]: Button.MENU, [SettingGamepad.Button_Menu]: Button.MENU,
[SettingGamepad.Button_Stats]: Button.STATS, [SettingGamepad.Button_Stats]: Button.STATS,
[SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM, [SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM,
@ -61,7 +61,7 @@ const pad_unlicensedSNES = {
RC_S: SettingGamepad.Button_Action, RC_S: SettingGamepad.Button_Action,
RC_E: SettingGamepad.Button_Cancel, RC_E: SettingGamepad.Button_Cancel,
RC_W: SettingGamepad.Button_Cycle_Nature, RC_W: SettingGamepad.Button_Cycle_Nature,
RC_N: SettingGamepad.Button_Cycle_Variant, RC_N: SettingGamepad.Button_Cycle_Tera,
START: SettingGamepad.Button_Menu, START: SettingGamepad.Button_Menu,
SELECT: SettingGamepad.Button_Stats, SELECT: SettingGamepad.Button_Stats,
LB: SettingGamepad.Button_Cycle_Form, LB: SettingGamepad.Button_Cycle_Form,

View File

@ -51,7 +51,7 @@ const pad_xbox360 = {
[SettingGamepad.Button_Action]: Button.ACTION, [SettingGamepad.Button_Action]: Button.ACTION,
[SettingGamepad.Button_Cancel]: Button.CANCEL, [SettingGamepad.Button_Cancel]: Button.CANCEL,
[SettingGamepad.Button_Cycle_Nature]: Button.CYCLE_NATURE, [SettingGamepad.Button_Cycle_Nature]: Button.CYCLE_NATURE,
[SettingGamepad.Button_Cycle_Variant]: Button.V, [SettingGamepad.Button_Cycle_Tera]: Button.CYCLE_TERA,
[SettingGamepad.Button_Menu]: Button.MENU, [SettingGamepad.Button_Menu]: Button.MENU,
[SettingGamepad.Button_Stats]: Button.STATS, [SettingGamepad.Button_Stats]: Button.STATS,
[SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM, [SettingGamepad.Button_Cycle_Form]: Button.CYCLE_FORM,
@ -69,7 +69,7 @@ const pad_xbox360 = {
RC_S: SettingGamepad.Button_Action, RC_S: SettingGamepad.Button_Action,
RC_E: SettingGamepad.Button_Cancel, RC_E: SettingGamepad.Button_Cancel,
RC_W: SettingGamepad.Button_Cycle_Nature, RC_W: SettingGamepad.Button_Cycle_Nature,
RC_N: SettingGamepad.Button_Cycle_Variant, RC_N: SettingGamepad.Button_Cycle_Tera,
START: SettingGamepad.Button_Menu, START: SettingGamepad.Button_Menu,
SELECT: SettingGamepad.Button_Stats, SELECT: SettingGamepad.Button_Stats,
LB: SettingGamepad.Button_Cycle_Form, LB: SettingGamepad.Button_Cycle_Form,

File diff suppressed because it is too large Load Diff

View File

@ -708,6 +708,7 @@ export class FreshStartChallenge extends Challenge {
pokemon.variant = 0; // Not shiny pokemon.variant = 0; // Not shiny
pokemon.formIndex = 0; // Froakie should be base form pokemon.formIndex = 0; // Froakie should be base form
pokemon.ivs = [ 15, 15, 15, 15, 15, 15 ]; // Default IVs of 15 for all stats (Updated to 15 from 10 in 1.2.0) pokemon.ivs = [ 15, 15, 15, 15, 15, 15 ]; // Default IVs of 15 for all stats (Updated to 15 from 10 in 1.2.0)
pokemon.teraType = pokemon.species.type1; // Always primary tera type
return true; return true;
} }

View File

@ -1,5 +1,16 @@
import { ChargeAnim, initMoveAnim, loadMoveAnimAssets, MoveChargeAnim } from "./battle-anims"; import { ChargeAnim, MoveChargeAnim } from "./battle-anims";
import { CommandedTag, EncoreTag, GulpMissileTag, HelpingHandTag, SemiInvulnerableTag, ShellTrapTag, StockpilingTag, SubstituteTag, TrappedTag, TypeBoostTag } from "./battler-tags"; import {
CommandedTag,
EncoreTag,
GulpMissileTag,
HelpingHandTag,
SemiInvulnerableTag,
ShellTrapTag,
StockpilingTag,
SubstituteTag,
TrappedTag,
TypeBoostTag,
} from "./battler-tags";
import { getPokemonNameWithAffix } from "../messages"; import { getPokemonNameWithAffix } from "../messages";
import type { AttackMoveResult, TurnMove } from "../field/pokemon"; import type { AttackMoveResult, TurnMove } from "../field/pokemon";
import type Pokemon from "../field/pokemon"; import type Pokemon from "../field/pokemon";
@ -30,7 +41,7 @@ import { Biome } from "#enums/biome";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { MoveUsedEvent } from "#app/events/battle-scene"; import { MoveUsedEvent } from "#app/events/battle-scene";
import { BATTLE_STATS, type BattleStat, EFFECTIVE_STATS, type EffectiveStat, getStatKey, Stat } from "#app/enums/stat"; import { BATTLE_STATS, type BattleStat, type EffectiveStat, getStatKey, Stat } from "#app/enums/stat";
import { BattleEndPhase } from "#app/phases/battle-end-phase"; import { BattleEndPhase } from "#app/phases/battle-end-phase";
import { MoveEndPhase } from "#app/phases/move-end-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase";
import { MovePhase } from "#app/phases/move-phase"; import { MovePhase } from "#app/phases/move-phase";
@ -46,6 +57,10 @@ import { applyChallenges, ChallengeType } from "./challenge";
import { SwitchType } from "#enums/switch-type"; import { SwitchType } from "#enums/switch-type";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { RevivalBlessingPhase } from "#app/phases/revival-blessing-phase";
import { LoadMoveAnimPhase } from "#app/phases/load-move-anim-phase";
import { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase";
import { MoveAnimPhase } from "#app/phases/move-anim-phase";
export enum MoveCategory { export enum MoveCategory {
PHYSICAL, PHYSICAL,
@ -1057,7 +1072,7 @@ export abstract class MoveAttr {
* @param args Set of unique arguments needed by this attribute * @param args Set of unique arguments needed by this attribute
* @returns true if application of the ability succeeds * @returns true if application of the ability succeeds
*/ */
apply(user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): boolean | Promise<boolean> { apply(user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): boolean {
return true; return true;
} }
@ -1200,7 +1215,7 @@ export class MoveEffectAttr extends MoveAttr {
} }
/** Applies move effects so long as they are able based on {@linkcode canApply} */ /** Applies move effects so long as they are able based on {@linkcode canApply} */
apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean | Promise<boolean> { apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean {
return this.canApply(user, target, move, args); return this.canApply(user, target, move, args);
} }
@ -1866,7 +1881,7 @@ export class FlameBurstAttr extends MoveEffectAttr {
* @param args - n/a * @param args - n/a
* @returns A boolean indicating whether the effect was successfully applied. * @returns A boolean indicating whether the effect was successfully applied.
*/ */
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const targetAlly = target.getAlly(); const targetAlly = target.getAlly();
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
@ -2406,32 +2421,27 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
this.chance = chance; this.chance = chance;
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
return new Promise<boolean>(resolve => {
if (move.hitsSubstitute(user, target)) { if (move.hitsSubstitute(user, target)) {
return resolve(false); return false;
}
const rand = Phaser.Math.RND.realInRange(0, 1);
if (rand >= this.chance) {
return resolve(false);
}
const heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferable);
if (heldItems.length) {
const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD;
const highestItemTier = heldItems.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is the bang after tier correct?
const tierHeldItems = heldItems.filter(m => m.type.getOrInferTier(poolType) === highestItemTier);
const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length)];
globalScene.tryTransferHeldItemModifier(stolenItem, user, false).then(success => {
if (success) {
globalScene.queueMessage(i18next.t("moveTriggers:stoleItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name }));
}
resolve(success);
});
return;
} }
resolve(false); const rand = Phaser.Math.RND.realInRange(0, 1);
}); if (rand >= this.chance) {
return false;
}
const heldItems = this.getTargetHeldItems(target).filter((i) => i.isTransferable);
if (heldItems.length) {
const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD;
const highestItemTier = heldItems.map((m) => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is the bang after tier correct?
const tierHeldItems = heldItems.filter((m) => m.type.getOrInferTier(poolType) === highestItemTier);
const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length)];
if (globalScene.tryTransferHeldItemModifier(stolenItem, user, false)) {
globalScene.queueMessage(i18next.t("moveTriggers:stoleItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name }));
return true;
}
}
return false;
} }
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
@ -2875,9 +2885,7 @@ export class WeatherInstantChargeAttr extends InstantChargeAttr {
} }
export class OverrideMoveEffectAttr extends MoveAttr { export class OverrideMoveEffectAttr extends MoveAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
//const overridden = args[0] as Utils.BooleanHolder;
//const virtual = arg[1] as boolean;
return true; return true;
} }
} }
@ -2903,26 +2911,27 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr {
this.chargeText = chargeText; this.chargeText = chargeText;
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
// Edge case for the move applied on a pokemon that has fainted // Edge case for the move applied on a pokemon that has fainted
if (!target) { if (!target) {
return Promise.resolve(true); return true;
} }
const side = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
return new Promise(resolve => { const overridden = args[0] as Utils.BooleanHolder;
if (args.length < 2 || !args[1]) { const virtual = args[1] as boolean;
new MoveChargeAnim(this.chargeAnim, move.id, user).play(false, () => {
(args[0] as Utils.BooleanHolder).value = true; if (!virtual) {
overridden.value = true;
globalScene.unshiftPhase(new MoveAnimPhase(new MoveChargeAnim(this.chargeAnim, move.id, user)));
globalScene.queueMessage(this.chargeText.replace("{TARGET}", getPokemonNameWithAffix(target)).replace("{USER}", getPokemonNameWithAffix(user))); globalScene.queueMessage(this.chargeText.replace("{TARGET}", getPokemonNameWithAffix(target)).replace("{USER}", getPokemonNameWithAffix(user)));
user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER }); user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER });
const side = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
globalScene.arena.addTag(this.tagType, 3, move.id, user.id, side, false, target.getBattlerIndex()); globalScene.arena.addTag(this.tagType, 3, move.id, user.id, side, false, target.getBattlerIndex());
resolve(true);
});
} else { } else {
globalScene.ui.showText(i18next.t("moveTriggers:tookMoveAttack", { pokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(target.id) ?? undefined), moveName: move.name }), null, () => resolve(true)); globalScene.queueMessage(i18next.t("moveTriggers:tookMoveAttack", { pokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(target.id) ?? undefined), moveName: move.name }));
} }
});
return true;
} }
} }
@ -3053,7 +3062,7 @@ export class StatStageChangeAttr extends MoveEffectAttr {
* @param args unused * @param args unused
* @returns whether stat stages were changed * @returns whether stat stages were changed
*/ */
apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean | Promise<boolean> { apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean {
if (!super.apply(user, target, move, args) || (this.condition && !this.condition(user, target, move))) { if (!super.apply(user, target, move, args) || (this.condition && !this.condition(user, target, move))) {
return false; return false;
} }
@ -3131,7 +3140,7 @@ export class SecretPowerAttr extends MoveEffectAttr {
* Used to apply the secondary effect to the target Pokemon * Used to apply the secondary effect to the target Pokemon
* @returns `true` if a secondary effect is successfully applied * @returns `true` if a secondary effect is successfully applied
*/ */
override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean | Promise<boolean> { override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean {
if (!super.apply(user, target, move, args)) { if (!super.apply(user, target, move, args)) {
return false; return false;
} }
@ -3286,8 +3295,8 @@ export class AcupressureStatStageChangeAttr extends MoveEffectAttr {
super(); super();
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> { override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const randStats = BATTLE_STATS.filter(s => target.getStatStage(s) < 6); const randStats = BATTLE_STATS.filter((s) => target.getStatStage(s) < 6);
if (randStats.length > 0) { if (randStats.length > 0) {
const boostStat = [ randStats[user.randSeedInt(randStats.length)] ]; const boostStat = [ randStats[user.randSeedInt(randStats.length)] ];
globalScene.unshiftPhase(new StatStageChangePhase(target.getBattlerIndex(), this.selfTarget, boostStat, 2)); globalScene.unshiftPhase(new StatStageChangePhase(target.getBattlerIndex(), this.selfTarget, boostStat, 2));
@ -3324,17 +3333,14 @@ export class CutHpStatStageBoostAttr extends StatStageChangeAttr {
this.messageCallback = messageCallback; this.messageCallback = messageCallback;
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> { override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
return new Promise<boolean>(resolve => {
user.damageAndUpdate(Utils.toDmgValue(user.getMaxHp() / this.cutRatio), HitResult.OTHER, false, true); user.damageAndUpdate(Utils.toDmgValue(user.getMaxHp() / this.cutRatio), HitResult.OTHER, false, true);
user.updateInfo().then(() => { user.updateInfo();
const ret = super.apply(user, target, move, args); const ret = super.apply(user, target, move, args);
if (this.messageCallback) { if (this.messageCallback) {
this.messageCallback(user); this.messageCallback(user);
} }
resolve(ret); return ret;
});
});
} }
getCondition(): MoveConditionFunc { getCondition(): MoveConditionFunc {
@ -3426,28 +3432,27 @@ export class ResetStatsAttr extends MoveEffectAttr {
super(); super();
this.targetAllPokemon = targetAllPokemon; this.targetAllPokemon = targetAllPokemon;
} }
async apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
const promises: Promise<void>[] = []; override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (this.targetAllPokemon) { // Target all pokemon on the field when Freezy Frost or Haze are used if (this.targetAllPokemon) {
// Target all pokemon on the field when Freezy Frost or Haze are used
const activePokemon = globalScene.getField(true); const activePokemon = globalScene.getField(true);
activePokemon.forEach(p => promises.push(this.resetStats(p))); activePokemon.forEach((p) => this.resetStats(p));
globalScene.queueMessage(i18next.t("moveTriggers:statEliminated")); globalScene.queueMessage(i18next.t("moveTriggers:statEliminated"));
} else { // Affects only the single target when Clear Smog is used } else { // Affects only the single target when Clear Smog is used
if (!move.hitsSubstitute(user, target)) { if (!move.hitsSubstitute(user, target)) {
promises.push(this.resetStats(target)); this.resetStats(target);
globalScene.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) })); globalScene.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) }));
} }
} }
await Promise.all(promises);
return true; return true;
} }
async resetStats(pokemon: Pokemon): Promise<void> { private resetStats(pokemon: Pokemon): void {
for (const s of BATTLE_STATS) { for (const s of BATTLE_STATS) {
pokemon.setStatStage(s, 0); pokemon.setStatStage(s, 0);
} }
return pokemon.updateInfo(); pokemon.updateInfo();
} }
} }
@ -3503,43 +3508,28 @@ export class SwapStatStagesAttr extends MoveEffectAttr {
} }
export class HpSplitAttr extends MoveEffectAttr { export class HpSplitAttr extends MoveEffectAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
return new Promise(resolve => {
if (!super.apply(user, target, move, args)) { if (!super.apply(user, target, move, args)) {
return resolve(false); return false;
} }
const infoUpdates: Promise<void>[] = [];
const hpValue = Math.floor((target.hp + user.hp) / 2); const hpValue = Math.floor((target.hp + user.hp) / 2);
if (user.hp < hpValue) { [ user, target ].forEach((p) => {
const healing = user.heal(hpValue - user.hp); if (p.hp < hpValue) {
const healing = p.heal(hpValue - p.hp);
if (healing) { if (healing) {
globalScene.damageNumberHandler.add(user, healing, HitResult.HEAL); globalScene.damageNumberHandler.add(p, healing, HitResult.HEAL);
} }
} else if (user.hp > hpValue) { } else if (p.hp > hpValue) {
const damage = user.damage(user.hp - hpValue, true); const damage = p.damage(p.hp - hpValue, true);
if (damage) { if (damage) {
globalScene.damageNumberHandler.add(user, damage); globalScene.damageNumberHandler.add(p, damage);
} }
} }
infoUpdates.push(user.updateInfo()); p.updateInfo();
if (target.hp < hpValue) {
const healing = target.heal(hpValue - target.hp);
if (healing) {
globalScene.damageNumberHandler.add(user, healing, HitResult.HEAL);
}
} else if (target.hp > hpValue) {
const damage = target.damage(target.hp - hpValue, true);
if (damage) {
globalScene.damageNumberHandler.add(target, damage);
}
}
infoUpdates.push(target.updateInfo());
return Promise.all(infoUpdates).then(() => resolve(true));
}); });
return true;
} }
} }
@ -6024,22 +6014,17 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
* @param args N/A * @param args N/A
* @returns Promise, true if function succeeds. * @returns Promise, true if function succeeds.
*/ */
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> { override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
return new Promise(resolve => {
// If user is player, checks if the user has fainted pokemon // If user is player, checks if the user has fainted pokemon
if (user instanceof PlayerPokemon if (user instanceof PlayerPokemon) {
&& globalScene.getPlayerParty().findIndex(p => p.isFainted()) > -1) { globalScene.unshiftPhase(new RevivalBlessingPhase(user));
(user as PlayerPokemon).revivalBlessing().then(() => { return true;
resolve(true); } else if (user instanceof EnemyPokemon && user.hasTrainer() && globalScene.getEnemyParty().findIndex((p) => p.isFainted() && !p.isBoss()) > -1) {
}); // If used by an enemy trainer with at least one fainted non-boss Pokemon, this
// If user is enemy, checks that it is a trainer, and it has fainted non-boss pokemon in party // revives one of said Pokemon selected at random.
} else if (user instanceof EnemyPokemon const faintedPokemon = globalScene.getEnemyParty().filter((p) => p.isFainted() && !p.isBoss());
&& user.hasTrainer()
&& globalScene.getEnemyParty().findIndex(p => p.isFainted() && !p.isBoss()) > -1) {
// Selects a random fainted pokemon
const faintedPokemon = globalScene.getEnemyParty().filter(p => p.isFainted() && !p.isBoss());
const pokemon = faintedPokemon[user.randSeedInt(faintedPokemon.length)]; const pokemon = faintedPokemon[user.randSeedInt(faintedPokemon.length)];
const slotIndex = globalScene.getEnemyParty().findIndex(p => pokemon.id === p.id); const slotIndex = globalScene.getEnemyParty().findIndex((p) => pokemon.id === p.id);
pokemon.resetStatus(); pokemon.resetStatus();
pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp())); pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp()));
globalScene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: getPokemonNameWithAffix(pokemon) }), 0, true); globalScene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: getPokemonNameWithAffix(pokemon) }), 0, true);
@ -6052,16 +6037,21 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, false)); globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, false));
} }
} }
resolve(true); return true;
} else {
globalScene.queueMessage(i18next.t("battle:attackFailed"));
resolve(false);
} }
}); return false;
} }
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { getCondition(): MoveConditionFunc {
if (user.hasTrainer() && globalScene.getEnemyParty().findIndex(p => p.isFainted() && !p.isBoss()) > -1) { return (user, target, move) =>
(user instanceof PlayerPokemon && globalScene.getPlayerParty().some((p) => p.isFainted())) ||
(user instanceof EnemyPokemon &&
user.hasTrainer() &&
globalScene.getEnemyParty().some((p) => p.isFainted() && !p.isBoss()));
}
override getUserBenefitScore(user: Pokemon, _target: Pokemon, _move: Move): number {
if (user.hasTrainer() && globalScene.getEnemyParty().some((p) => p.isFainted() && !p.isBoss())) {
return 20; return 20;
} }
@ -6579,7 +6569,7 @@ export class FirstMoveTypeAttr extends MoveEffectAttr {
class CallMoveAttr extends OverrideMoveEffectAttr { class CallMoveAttr extends OverrideMoveEffectAttr {
protected invalidMoves: Moves[]; protected invalidMoves: Moves[];
protected hasTarget: boolean; protected hasTarget: boolean;
async apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const replaceMoveTarget = move.moveTarget === MoveTarget.NEAR_OTHER ? MoveTarget.NEAR_ENEMY : undefined; const replaceMoveTarget = move.moveTarget === MoveTarget.NEAR_OTHER ? MoveTarget.NEAR_ENEMY : undefined;
const moveTargets = getMoveTargets(user, move.id, replaceMoveTarget); const moveTargets = getMoveTargets(user, move.id, replaceMoveTarget);
if (moveTargets.targets.length === 0) { if (moveTargets.targets.length === 0) {
@ -6589,11 +6579,8 @@ class CallMoveAttr extends OverrideMoveEffectAttr {
? moveTargets.targets ? moveTargets.targets
: [ this.hasTarget ? target.getBattlerIndex() : moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; // account for Mirror Move having a target already : [ this.hasTarget ? target.getBattlerIndex() : moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; // account for Mirror Move having a target already
user.getMoveQueue().push({ move: move.id, targets: targets, virtual: true, ignorePP: true }); user.getMoveQueue().push({ move: move.id, targets: targets, virtual: true, ignorePP: true });
globalScene.unshiftPhase(new LoadMoveAnimPhase(move.id));
globalScene.unshiftPhase(new MovePhase(user, targets, new PokemonMove(move.id, 0, 0, true), true, true)); globalScene.unshiftPhase(new MovePhase(user, targets, new PokemonMove(move.id, 0, 0, true), true, true));
await Promise.resolve(initMoveAnim(move.id).then(() => {
loadMoveAnimAssets([ move.id ], true);
}));
return true; return true;
} }
} }
@ -6626,7 +6613,7 @@ export class RandomMoveAttr extends CallMoveAttr {
* @param move Move being used * @param move Move being used
* @param args Unused * @param args Unused
*/ */
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> { override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const moveIds = Utils.getEnumValues(Moves).map(m => !this.invalidMoves.includes(m) && !allMoves[m].name.endsWith(" (N)") ? m : Moves.NONE); const moveIds = Utils.getEnumValues(Moves).map(m => !this.invalidMoves.includes(m) && !allMoves[m].name.endsWith(" (N)") ? m : Moves.NONE);
let moveId: Moves = Moves.NONE; let moveId: Moves = Moves.NONE;
do { do {
@ -6663,7 +6650,7 @@ export class RandomMovesetMoveAttr extends CallMoveAttr {
* @param move Move being used * @param move Move being used
* @param args Unused * @param args Unused
*/ */
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
return super.apply(user, target, allMoves[this.moveId], args); return super.apply(user, target, allMoves[this.moveId], args);
} }
@ -6965,8 +6952,7 @@ const invalidCopycatMoves = [
]; ];
export class NaturePowerAttr extends OverrideMoveEffectAttr { export class NaturePowerAttr extends OverrideMoveEffectAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
return new Promise(resolve => {
let moveId; let moveId;
switch (globalScene.arena.getTerrainType()) { switch (globalScene.arena.getTerrainType()) {
// this allows terrains to 'override' the biome move // this allows terrains to 'override' the biome move
@ -7098,12 +7084,9 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr {
} }
user.getMoveQueue().push({ move: moveId, targets: [ target.getBattlerIndex() ], ignorePP: true }); user.getMoveQueue().push({ move: moveId, targets: [ target.getBattlerIndex() ], ignorePP: true });
globalScene.unshiftPhase(new LoadMoveAnimPhase(moveId));
globalScene.unshiftPhase(new MovePhase(user, [ target.getBattlerIndex() ], new PokemonMove(moveId, 0, 0, true), true)); globalScene.unshiftPhase(new MovePhase(user, [ target.getBattlerIndex() ], new PokemonMove(moveId, 0, 0, true), true));
initMoveAnim(moveId).then(() => { return true;
loadMoveAnimAssets([ moveId ], true)
.then(() => resolve(true));
});
});
} }
} }
@ -7121,7 +7104,7 @@ export class CopyMoveAttr extends CallMoveAttr {
this.invalidMoves = invalidMoves; this.invalidMoves = invalidMoves;
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
this.hasTarget = this.mirrorMove; this.hasTarget = this.mirrorMove;
const lastMove = this.mirrorMove ? target.getLastXMoves()[0].move : globalScene.currentBattle.lastMove; const lastMove = this.mirrorMove ? target.getLastXMoves()[0].move : globalScene.currentBattle.lastMove;
return super.apply(user, target, allMoves[lastMove], args); return super.apply(user, target, allMoves[lastMove], args);
@ -7682,50 +7665,15 @@ export class SuppressAbilitiesIfActedAttr extends MoveEffectAttr {
* Used by Transform * Used by Transform
*/ */
export class TransformAttr extends MoveEffectAttr { export class TransformAttr extends MoveEffectAttr {
async apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> { override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!super.apply(user, target, move, args)) { if (!super.apply(user, target, move, args)) {
return false; return false;
} }
const promises: Promise<void>[] = []; globalScene.unshiftPhase(new PokemonTransformPhase(user.getBattlerIndex(), target.getBattlerIndex()));
user.summonData.speciesForm = target.getSpeciesForm();
user.summonData.gender = target.getGender();
// Power Trick's effect will not preserved after using Transform
user.removeTag(BattlerTagType.POWER_TRICK);
// Copy all stats (except HP)
for (const s of EFFECTIVE_STATS) {
user.setStat(s, target.getStat(s, false), false);
}
// Copy all stat stages
for (const s of BATTLE_STATS) {
user.setStatStage(s, target.getStatStage(s));
}
user.summonData.moveset = target.getMoveset().map((m) => {
if (m) {
// If PP value is less than 5, do nothing. If greater, we need to reduce the value to 5.
return new PokemonMove(m.moveId, 0, 0, false, Math.min(m.getMove().pp, 5));
} else {
console.warn(`Transform: somehow iterating over a ${m} value when copying moveset!`);
return new PokemonMove(Moves.NONE);
}
});
user.summonData.types = target.getTypes();
promises.push(user.updateInfo());
globalScene.queueMessage(i18next.t("moveTriggers:transformedIntoTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })); globalScene.queueMessage(i18next.t("moveTriggers:transformedIntoTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) }));
promises.push(user.loadAssets(false).then(() => {
user.playAnim();
user.updateInfo();
// If the new ability activates immediately, it needs to happen after all the transform animations
user.setTempAbility(target.getAbility());
}));
await Promise.all(promises);
return true; return true;
} }
} }
@ -8035,6 +7983,56 @@ export class AfterYouAttr extends MoveEffectAttr {
} }
} }
/**
* Move effect to force the target to move last, ignoring priority.
* If applied to multiple targets, they move in speed order after all other moves.
* @extends MoveEffectAttr
*/
export class ForceLastAttr extends MoveEffectAttr {
/**
* Forces the target of this move to move last.
*
* @param user {@linkcode Pokemon} that is using the move.
* @param target {@linkcode Pokemon} that will be forced to move last.
* @param move {@linkcode Move} {@linkcode Moves.QUASH}
* @param _args N/A
* @returns true
*/
override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
globalScene.queueMessage(i18next.t("moveTriggers:forceLast", { targetPokemonName: getPokemonNameWithAffix(target) }));
const targetMovePhase = globalScene.findPhase<MovePhase>((phase) => phase.pokemon === target);
if (targetMovePhase && !targetMovePhase.isForcedLast() && globalScene.tryRemovePhase((phase: MovePhase) => phase.pokemon === target)) {
// Finding the phase to insert the move in front of -
// Either the end of the turn or in front of another, slower move which has also been forced last
const prependPhase = globalScene.findPhase((phase) =>
[ MovePhase, MoveEndPhase ].every(cls => !(phase instanceof cls))
|| (phase instanceof MovePhase) && phaseForcedSlower(phase, target, !!globalScene.arena.getTag(ArenaTagType.TRICK_ROOM))
);
if (prependPhase) {
globalScene.phaseQueue.splice(
globalScene.phaseQueue.indexOf(prependPhase),
0,
new MovePhase(target, [ ...targetMovePhase.targets ], targetMovePhase.move, false, false, false, true)
);
}
}
return true;
}
}
/** Returns whether a {@linkcode MovePhase} has been forced last and the corresponding pokemon is slower than {@linkcode target} */
const phaseForcedSlower = (phase: MovePhase, target: Pokemon, trickRoom: boolean): boolean => {
let slower: boolean;
// quashed pokemon still have speed ties
if (phase.pokemon.getEffectiveStat(Stat.SPD) === target.getEffectiveStat(Stat.SPD)) {
slower = !!target.randSeedInt(2);
} else {
slower = !trickRoom ? phase.pokemon.getEffectiveStat(Stat.SPD) < target.getEffectiveStat(Stat.SPD) : phase.pokemon.getEffectiveStat(Stat.SPD) > target.getEffectiveStat(Stat.SPD);
}
return phase.isForcedLast() && slower;
};
const failOnGravityCondition: MoveConditionFunc = (user, target, move) => !globalScene.arena.getTag(ArenaTagType.GRAVITY); const failOnGravityCondition: MoveConditionFunc = (user, target, move) => !globalScene.arena.getTag(ArenaTagType.GRAVITY);
const failOnBossCondition: MoveConditionFunc = (user, target, move) => !target.isBossImmune(); const failOnBossCondition: MoveConditionFunc = (user, target, move) => !target.isBossImmune();
@ -8078,44 +8076,54 @@ const attackedByItemMessageFunc = (user: Pokemon, target: Pokemon, move: Move) =
export type MoveAttrFilter = (attr: MoveAttr) => boolean; export type MoveAttrFilter = (attr: MoveAttr) => boolean;
function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): Promise<void> { function applyMoveAttrsInternal(
return new Promise(resolve => { attrFilter: MoveAttrFilter,
const attrPromises: Promise<boolean>[] = []; user: Pokemon | null,
const moveAttrs = move.attrs.filter(a => attrFilter(a)); target: Pokemon | null,
for (const attr of moveAttrs) { move: Move,
const result = attr.apply(user, target, move, args); args: any[],
if (result instanceof Promise) { ): void {
attrPromises.push(result); move.attrs.filter((attr) => attrFilter(attr)).forEach((attr) => attr.apply(user, target, move, args));
}
}
Promise.allSettled(attrPromises).then(() => resolve());
});
} }
function applyMoveChargeAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: ChargingMove, args: any[]): Promise<void> { function applyMoveChargeAttrsInternal(
return new Promise(resolve => { attrFilter: MoveAttrFilter,
const chargeAttrPromises: Promise<boolean>[] = []; user: Pokemon | null,
const chargeMoveAttrs = move.chargeAttrs.filter(a => attrFilter(a)); target: Pokemon | null,
for (const attr of chargeMoveAttrs) { move: ChargingMove,
const result = attr.apply(user, target, move, args); args: any[],
if (result instanceof Promise) { ): void {
chargeAttrPromises.push(result); move.chargeAttrs.filter((attr) => attrFilter(attr)).forEach((attr) => attr.apply(user, target, move, args));
}
}
Promise.allSettled(chargeAttrPromises).then(() => resolve());
});
} }
export function applyMoveAttrs(attrType: Constructor<MoveAttr>, user: Pokemon | null, target: Pokemon | null, move: Move, ...args: any[]): Promise<void> { export function applyMoveAttrs(
return applyMoveAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args); attrType: Constructor<MoveAttr>,
user: Pokemon | null,
target: Pokemon | null,
move: Move,
...args: any[]
): void {
applyMoveAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args);
} }
export function applyFilteredMoveAttrs(attrFilter: MoveAttrFilter, user: Pokemon, target: Pokemon | null, move: Move, ...args: any[]): Promise<void> { export function applyFilteredMoveAttrs(
return applyMoveAttrsInternal(attrFilter, user, target, move, args); attrFilter: MoveAttrFilter,
user: Pokemon,
target: Pokemon | null,
move: Move,
...args: any[]
): void {
applyMoveAttrsInternal(attrFilter, user, target, move, args);
} }
export function applyMoveChargeAttrs(attrType: Constructor<MoveAttr>, user: Pokemon | null, target: Pokemon | null, move: ChargingMove, ...args: any[]): Promise<void> { export function applyMoveChargeAttrs(
return applyMoveChargeAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args); attrType: Constructor<MoveAttr>,
user: Pokemon | null,
target: Pokemon | null,
move: ChargingMove,
...args: any[]
): void {
applyMoveChargeAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args);
} }
export class MoveCondition { export class MoveCondition {
@ -9914,7 +9922,8 @@ export function initMoves() {
.attr(RemoveHeldItemAttr, true), .attr(RemoveHeldItemAttr, true),
new StatusMove(Moves.QUASH, Type.DARK, 100, 15, -1, 0, 5) new StatusMove(Moves.QUASH, Type.DARK, 100, 15, -1, 0, 5)
.condition(failIfSingleBattle) .condition(failIfSingleBattle)
.unimplemented(), .condition((user, target, move) => !target.turnData.acted)
.attr(ForceLastAttr),
new AttackMove(Moves.ACROBATICS, Type.FLYING, MoveCategory.PHYSICAL, 55, 100, 15, -1, 0, 5) new AttackMove(Moves.ACROBATICS, Type.FLYING, MoveCategory.PHYSICAL, 55, 100, 15, -1, 0, 5)
.attr(MovePowerMultiplierAttr, (user, target, move) => Math.max(1, 2 - 0.2 * user.getHeldItems().filter(i => i.isTransferable).reduce((v, m) => v + m.stackCount, 0))), .attr(MovePowerMultiplierAttr, (user, target, move) => Math.max(1, 2 - 0.2 * user.getHeldItems().filter(i => i.isTransferable).reduce((v, m) => v + m.stackCount, 0))),
new StatusMove(Moves.REFLECT_TYPE, Type.NORMAL, -1, 15, -1, 0, 5) new StatusMove(Moves.REFLECT_TYPE, Type.NORMAL, -1, 15, -1, 0, 5)

View File

@ -151,7 +151,7 @@ async function spawnNextTrainerOrEndEncounter() {
// Give 10x Voucher // Give 10x Voucher
const newModifier = modifierTypes.VOUCHER_PREMIUM().newModifier(); const newModifier = modifierTypes.VOUCHER_PREMIUM().newModifier();
await globalScene.addModifier(newModifier); globalScene.addModifier(newModifier);
globalScene.playSound("item_fanfare"); globalScene.playSound("item_fanfare");
await showEncounterText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name })); await showEncounterText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }));

View File

@ -406,7 +406,7 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) {
// Copy old items to new pokemon // Copy old items to new pokemon
for (const item of transformation.heldItems) { for (const item of transformation.heldItems) {
item.pokemonId = newPokemon.id; item.pokemonId = newPokemon.id;
await globalScene.addModifier(item, false, false, false, true); globalScene.addModifier(item, false, false, false, true);
} }
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats // Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
if (shouldGetOldGateau(newPokemon)) { if (shouldGetOldGateau(newPokemon)) {
@ -416,7 +416,7 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) {
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU); ?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU);
const modifier = modType?.newModifier(newPokemon); const modifier = modType?.newModifier(newPokemon);
if (modifier) { if (modifier) {
await globalScene.addModifier(modifier, false, false, false, true); globalScene.addModifier(modifier, false, false, false, true);
} }
} }

View File

@ -326,7 +326,7 @@ export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: numb
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE); ?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE);
const modifier = modType?.newModifier(pokemon); const modifier = modType?.newModifier(pokemon);
if (modifier) { if (modifier) {
await globalScene.addModifier(modifier, false, false, false, true); globalScene.addModifier(modifier, false, false, false, true);
pokemon.calculateStats(); pokemon.calculateStats();
} }
} }
@ -359,7 +359,7 @@ export async function applyModifierTypeToPlayerPokemon(pokemon: PlayerPokemon, m
return applyModifierTypeToPlayerPokemon(pokemon, fallbackModifierType); return applyModifierTypeToPlayerPokemon(pokemon, fallbackModifierType);
} }
await globalScene.addModifier(modifier, false, false, false, true); globalScene.addModifier(modifier, false, false, false, true);
} }
/** /**

View File

@ -1850,7 +1850,7 @@ export function initSpecies() {
new PokemonSpecies(Species.REGIGIGAS, 4, true, false, false, "Colossal Pokémon", Type.NORMAL, null, 3.7, 420, Abilities.SLOW_START, Abilities.NONE, Abilities.NORMALIZE, 670, 110, 160, 110, 80, 110, 100, 3, 0, 335, GrowthRate.SLOW, null, false), new PokemonSpecies(Species.REGIGIGAS, 4, true, false, false, "Colossal Pokémon", Type.NORMAL, null, 3.7, 420, Abilities.SLOW_START, Abilities.NONE, Abilities.NORMALIZE, 670, 110, 160, 110, 80, 110, 100, 3, 0, 335, GrowthRate.SLOW, null, false),
new PokemonSpecies(Species.GIRATINA, 4, false, true, false, "Renegade Pokémon", Type.GHOST, Type.DRAGON, 4.5, 750, Abilities.PRESSURE, Abilities.NONE, Abilities.TELEPATHY, 680, 150, 100, 120, 100, 120, 90, 3, 0, 340, GrowthRate.SLOW, null, false, true, new PokemonSpecies(Species.GIRATINA, 4, false, true, false, "Renegade Pokémon", Type.GHOST, Type.DRAGON, 4.5, 750, Abilities.PRESSURE, Abilities.NONE, Abilities.TELEPATHY, 680, 150, 100, 120, 100, 120, 90, 3, 0, 340, GrowthRate.SLOW, null, false, true,
new PokemonForm("Altered Forme", "altered", Type.GHOST, Type.DRAGON, 4.5, 750, Abilities.PRESSURE, Abilities.NONE, Abilities.TELEPATHY, 680, 150, 100, 120, 100, 120, 90, 3, 0, 340, false, null, true), new PokemonForm("Altered Forme", "altered", Type.GHOST, Type.DRAGON, 4.5, 750, Abilities.PRESSURE, Abilities.NONE, Abilities.TELEPATHY, 680, 150, 100, 120, 100, 120, 90, 3, 0, 340, false, null, true),
new PokemonForm("Origin Forme", "origin", Type.GHOST, Type.DRAGON, 6.9, 650, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 680, 150, 120, 100, 120, 100, 90, 3, 0, 340), new PokemonForm("Origin Forme", "origin", Type.GHOST, Type.DRAGON, 6.9, 650, Abilities.LEVITATE, Abilities.NONE, Abilities.LEVITATE, 680, 150, 120, 100, 120, 100, 90, 3, 0, 340),
), ),
new PokemonSpecies(Species.CRESSELIA, 4, true, false, false, "Lunar Pokémon", Type.PSYCHIC, null, 1.5, 85.6, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 580, 120, 70, 110, 75, 120, 85, 3, 100, 300, GrowthRate.SLOW, 0, false), new PokemonSpecies(Species.CRESSELIA, 4, true, false, false, "Lunar Pokémon", Type.PSYCHIC, null, 1.5, 85.6, Abilities.LEVITATE, Abilities.NONE, Abilities.NONE, 580, 120, 70, 110, 75, 120, 85, 3, 100, 300, GrowthRate.SLOW, 0, false),
new PokemonSpecies(Species.PHIONE, 4, false, false, true, "Sea Drifter Pokémon", Type.WATER, null, 0.4, 3.1, Abilities.HYDRATION, Abilities.NONE, Abilities.NONE, 480, 80, 80, 80, 80, 80, 80, 30, 70, 240, GrowthRate.SLOW, null, false), new PokemonSpecies(Species.PHIONE, 4, false, false, true, "Sea Drifter Pokémon", Type.WATER, null, 0.4, 3.1, Abilities.HYDRATION, Abilities.NONE, Abilities.NONE, 480, 80, 80, 80, 80, 80, 80, 30, 70, 240, GrowthRate.SLOW, null, false),

View File

@ -13,7 +13,7 @@ export enum Button {
CYCLE_GENDER, CYCLE_GENDER,
CYCLE_ABILITY, CYCLE_ABILITY,
CYCLE_NATURE, CYCLE_NATURE,
V, CYCLE_TERA,
SPEED_UP, SPEED_UP,
SLOW_DOWN SLOW_DOWN,
} }

View File

@ -104,7 +104,6 @@ import { MoveEndPhase } from "#app/phases/move-end-phase";
import { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase"; import { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
import { PokemonAnimType } from "#enums/pokemon-anim-type"; import { PokemonAnimType } from "#enums/pokemon-anim-type";
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
@ -2348,12 +2347,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const maxPower = Math.min(movePool.reduce((v, m) => Math.max(allMoves[m[0]].power, v), 40), 90); const maxPower = Math.min(movePool.reduce((v, m) => Math.max(allMoves[m[0]].power, v), 40), 90);
movePool = movePool.map(m => [ m[0], m[1] * (allMoves[m[0]].category === MoveCategory.STATUS ? 1 : Math.max(Math.min(allMoves[m[0]].power / maxPower, 1), 0.5)) ]); movePool = movePool.map(m => [ m[0], m[1] * (allMoves[m[0]].category === MoveCategory.STATUS ? 1 : Math.max(Math.min(allMoves[m[0]].power / maxPower, 1), 0.5)) ]);
// Weight damaging moves against the lower stat // Weight damaging moves against the lower stat. This uses a non-linear relationship.
// If the higher stat is 1 - 1.09x higher, no change. At higher stat ~1.38x lower stat, off-stat moves have half weight.
// One third weight at ~1.58x higher, one quarter weight at ~1.73x higher, one fifth at ~1.87x, and one tenth at ~2.35x higher.
const atk = this.getStat(Stat.ATK); const atk = this.getStat(Stat.ATK);
const spAtk = this.getStat(Stat.SPATK); const spAtk = this.getStat(Stat.SPATK);
const worseCategory: MoveCategory = atk > spAtk ? MoveCategory.SPECIAL : MoveCategory.PHYSICAL; const worseCategory: MoveCategory = atk > spAtk ? MoveCategory.SPECIAL : MoveCategory.PHYSICAL;
const statRatio = worseCategory === MoveCategory.PHYSICAL ? atk / spAtk : spAtk / atk; const statRatio = worseCategory === MoveCategory.PHYSICAL ? atk / spAtk : spAtk / atk;
movePool = movePool.map(m => [ m[0], m[1] * (allMoves[m[0]].category === worseCategory ? statRatio : 1) ]); movePool = movePool.map(m => [ m[0], m[1] * (allMoves[m[0]].category === worseCategory ? Math.min(Math.pow(statRatio, 3) * 1.3, 1) : 1) ]);
/** The higher this is the more the game weights towards higher level moves. At `0` all moves are equal weight. */ /** The higher this is the more the game weights towards higher level moves. At `0` all moves are equal weight. */
let weightMultiplier = 0.9; let weightMultiplier = 0.9;
@ -4509,43 +4510,6 @@ export class PlayerPokemon extends Pokemon {
this.friendship = Math.max(this.friendship + friendship, 0); this.friendship = Math.max(this.friendship + friendship, 0);
} }
} }
/**
* Handles Revival Blessing when used by player.
* @returns Promise to revive a pokemon.
* @see {@linkcode RevivalBlessingAttr}
*/
revivalBlessing(): Promise<void> {
return new Promise(resolve => {
globalScene.ui.setMode(Mode.PARTY, PartyUiMode.REVIVAL_BLESSING, this.getFieldIndex(), (slotIndex:number, option: PartyOption) => {
if (slotIndex >= 0 && slotIndex < 6) {
const pokemon = globalScene.getPlayerParty()[slotIndex];
if (!pokemon || !pokemon.isFainted()) {
resolve();
}
pokemon.resetTurnData();
pokemon.resetStatus();
pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp()));
globalScene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: pokemon.name }), 0, true);
if (globalScene.currentBattle.double && globalScene.getPlayerParty().length > 1) {
const allyPokemon = this.getAlly();
if (slotIndex <= 1) {
// Revived ally pokemon
globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, true));
globalScene.unshiftPhase(new ToggleDoublePositionPhase(true));
} else if (allyPokemon.isFainted()) {
// Revived party pokemon, and ally pokemon is fainted
globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, true));
globalScene.unshiftPhase(new ToggleDoublePositionPhase(true));
}
}
}
globalScene.ui.setMode(Mode.MESSAGE).then(() => resolve());
}, PartyUiHandler.FilterFainted);
});
}
getPossibleEvolution(evolution: SpeciesFormEvolution | null): Promise<Pokemon> { getPossibleEvolution(evolution: SpeciesFormEvolution | null): Promise<Pokemon> {
if (!evolution) { if (!evolution) {
@ -4730,8 +4694,7 @@ export class PlayerPokemon extends Pokemon {
* Returns a Promise to fuse two PlayerPokemon together * Returns a Promise to fuse two PlayerPokemon together
* @param pokemon The PlayerPokemon to fuse to this one * @param pokemon The PlayerPokemon to fuse to this one
*/ */
fuse(pokemon: PlayerPokemon): Promise<void> { fuse(pokemon: PlayerPokemon): void {
return new Promise(resolve => {
this.fusionSpecies = pokemon.species; this.fusionSpecies = pokemon.species;
this.fusionFormIndex = pokemon.formIndex; this.fusionFormIndex = pokemon.formIndex;
this.fusionAbilityIndex = pokemon.abilityIndex; this.fusionAbilityIndex = pokemon.abilityIndex;
@ -4740,7 +4703,7 @@ export class PlayerPokemon extends Pokemon {
this.fusionGender = pokemon.gender; this.fusionGender = pokemon.gender;
this.fusionLuck = pokemon.luck; this.fusionLuck = pokemon.luck;
this.fusionCustomPokemonData = pokemon.customPokemonData; this.fusionCustomPokemonData = pokemon.customPokemonData;
if ((pokemon.pauseEvolutions) || (this.pauseEvolutions)) { if (pokemon.pauseEvolutions || this.pauseEvolutions) {
this.pauseEvolutions = true; this.pauseEvolutions = true;
} }
@ -4749,7 +4712,7 @@ export class PlayerPokemon extends Pokemon {
// Store the average HP% that each Pokemon has // Store the average HP% that each Pokemon has
const maxHp = this.getMaxHp(); const maxHp = this.getMaxHp();
const newHpPercent = ((pokemon.hp / pokemon.getMaxHp()) + (this.hp / maxHp)) / 2; const newHpPercent = (pokemon.hp / pokemon.getMaxHp() + this.hp / maxHp) / 2;
this.generateName(); this.generateName();
this.calculateStats(); this.calculateStats();
@ -4773,24 +4736,17 @@ export class PlayerPokemon extends Pokemon {
if (partyMemberIndex > fusedPartyMemberIndex) { if (partyMemberIndex > fusedPartyMemberIndex) {
partyMemberIndex--; partyMemberIndex--;
} }
const fusedPartyMemberHeldModifiers = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier const fusedPartyMemberHeldModifiers = globalScene.findModifiers((m) => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
&& m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
const transferModifiers: Promise<boolean>[] = [];
for (const modifier of fusedPartyMemberHeldModifiers) { for (const modifier of fusedPartyMemberHeldModifiers) {
transferModifiers.push(globalScene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true, false)); globalScene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true, false);
} }
Promise.allSettled(transferModifiers).then(() => { globalScene.updateModifiers(true, true);
globalScene.updateModifiers(true, true).then(() => {
globalScene.removePartyMemberModifiers(fusedPartyMemberIndex); globalScene.removePartyMemberModifiers(fusedPartyMemberIndex);
globalScene.getPlayerParty().splice(fusedPartyMemberIndex, 1)[0]; globalScene.getPlayerParty().splice(fusedPartyMemberIndex, 1)[0];
const newPartyMemberIndex = globalScene.getPlayerParty().indexOf(this); const newPartyMemberIndex = globalScene.getPlayerParty().indexOf(this);
pokemon.getMoveset(true).map((m: PokemonMove) => globalScene.unshiftPhase(new LearnMovePhase(newPartyMemberIndex, m.getMove().id))); pokemon.getMoveset(true).map((m: PokemonMove) => globalScene.unshiftPhase(new LearnMovePhase(newPartyMemberIndex, m.getMove().id)));
pokemon.destroy(); pokemon.destroy();
this.updateFusionPalette(); this.updateFusionPalette();
resolve();
});
});
});
} }
unfuse(): Promise<void> { unfuse(): Promise<void> {

View File

@ -1822,7 +1822,7 @@ const modifierPool: ModifierPool = {
if (!isHoldingOrb) { if (!isHoldingOrb) {
const moveset = p.getMoveset(true).filter(m => !isNullOrUndefined(m)).map(m => m.moveId); const moveset = p.getMoveset(true).filter(m => !isNullOrUndefined(m)).map(m => m.moveId);
const canSetStatus = p.canSetStatus(StatusEffect.TOXIC, true, true, null, true); const canSetStatus = p.canSetStatus(StatusEffect.BURN, true, true, null, true);
// Moves that take advantage of obtaining the actual status effect // Moves that take advantage of obtaining the actual status effect
const hasStatusMoves = [ Moves.FACADE, Moves.PSYCHO_SHIFT ] const hasStatusMoves = [ Moves.FACADE, Moves.PSYCHO_SHIFT ]

View File

@ -158,7 +158,7 @@ export abstract class Modifier {
* Handles applying of {@linkcode Modifier} * Handles applying of {@linkcode Modifier}
* @param args collection of all passed parameters * @param args collection of all passed parameters
*/ */
abstract apply(...args: unknown[]): boolean | Promise<boolean>; abstract apply(...args: unknown[]): boolean;
} }
export abstract class PersistentModifier extends Modifier { export abstract class PersistentModifier extends Modifier {
@ -1949,7 +1949,7 @@ export abstract class ConsumablePokemonModifier extends ConsumableModifier {
* @param playerPokemon The {@linkcode PlayerPokemon} that consumes the item * @param playerPokemon The {@linkcode PlayerPokemon} that consumes the item
* @param args Additional arguments passed to {@linkcode ConsumablePokemonModifier.apply} * @param args Additional arguments passed to {@linkcode ConsumablePokemonModifier.apply}
*/ */
abstract override apply(playerPokemon: PlayerPokemon, ...args: unknown[]): boolean | Promise<boolean>; abstract override apply(playerPokemon: PlayerPokemon, ...args: unknown[]): boolean;
getPokemon() { getPokemon() {
return globalScene.getPlayerParty().find(p => p.id === this.pokemonId); return globalScene.getPlayerParty().find(p => p.id === this.pokemonId);
@ -2288,8 +2288,8 @@ export class FusePokemonModifier extends ConsumablePokemonModifier {
* @param playerPokemon2 {@linkcode PlayerPokemon} that should be fused with {@linkcode playerPokemon} * @param playerPokemon2 {@linkcode PlayerPokemon} that should be fused with {@linkcode playerPokemon}
* @returns always Promise<true> * @returns always Promise<true>
*/ */
override async apply(playerPokemon: PlayerPokemon, playerPokemon2: PlayerPokemon): Promise<boolean> { override apply(playerPokemon: PlayerPokemon, playerPokemon2: PlayerPokemon): boolean {
await playerPokemon.fuse(playerPokemon2); playerPokemon.fuse(playerPokemon2);
return true; return true;
} }
} }
@ -3136,8 +3136,6 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier {
let highestItemTier = itemModifiers.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is this bang correct? let highestItemTier = itemModifiers.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is this bang correct?
let tierItemModifiers = itemModifiers.filter(m => m.type.getOrInferTier(poolType) === highestItemTier); let tierItemModifiers = itemModifiers.filter(m => m.type.getOrInferTier(poolType) === highestItemTier);
const heldItemTransferPromises: Promise<void>[] = [];
for (let i = 0; i < transferredItemCount; i++) { for (let i = 0; i < transferredItemCount; i++) {
if (!tierItemModifiers.length) { if (!tierItemModifiers.length) {
while (highestItemTier-- && !tierItemModifiers.length) { while (highestItemTier-- && !tierItemModifiers.length) {
@ -3149,19 +3147,15 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier {
} }
const randItemIndex = pokemon.randSeedInt(itemModifiers.length); const randItemIndex = pokemon.randSeedInt(itemModifiers.length);
const randItem = itemModifiers[randItemIndex]; const randItem = itemModifiers[randItemIndex];
heldItemTransferPromises.push(globalScene.tryTransferHeldItemModifier(randItem, pokemon, false).then(success => { if (globalScene.tryTransferHeldItemModifier(randItem, pokemon, false)) {
if (success) {
transferredModifierTypes.push(randItem.type); transferredModifierTypes.push(randItem.type);
itemModifiers.splice(randItemIndex, 1); itemModifiers.splice(randItemIndex, 1);
} }
}));
} }
Promise.all(heldItemTransferPromises).then(() => {
for (const mt of transferredModifierTypes) { for (const mt of transferredModifierTypes) {
globalScene.queueMessage(this.getTransferMessage(pokemon, targetPokemon, mt)); globalScene.queueMessage(this.getTransferMessage(pokemon, targetPokemon, mt));
} }
});
return !!transferredModifierTypes.length; return !!transferredModifierTypes.length;
} }
@ -3390,7 +3384,7 @@ abstract class EnemyDamageMultiplierModifier extends EnemyPersistentModifier {
* @returns always `true` * @returns always `true`
*/ */
override apply(multiplier: NumberHolder): boolean { override apply(multiplier: NumberHolder): boolean {
multiplier.value = Math.floor(multiplier.value * Math.pow(this.damageMultiplier, this.getStackCount())); multiplier.value = toDmgValue(multiplier.value * Math.pow(this.damageMultiplier, this.getStackCount()));
return true; return true;
} }

View File

@ -21,6 +21,7 @@ export class AddEnemyBuffModifierPhase extends Phase {
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
globalScene.addEnemyModifier(getEnemyBuffModifierForWave(tier, globalScene.findModifiers(m => m instanceof EnemyPersistentModifier, false)), true, true); globalScene.addEnemyModifier(getEnemyBuffModifierForWave(tier, globalScene.findModifiers(m => m instanceof EnemyPersistentModifier, false)), true, true);
} }
globalScene.updateModifiers(false, true).then(() => this.end()); globalScene.updateModifiers(false, true);
this.end();
} }
} }

View File

@ -63,6 +63,7 @@ export class BattleEndPhase extends BattlePhase {
} }
} }
globalScene.updateModifiers().then(() => this.end()); globalScene.updateModifiers();
this.end();
} }
} }

View File

@ -38,6 +38,7 @@ import { Species } from "#enums/species";
import { overrideHeldItems, overrideModifiers } from "#app/modifier/modifier"; import { overrideHeldItems, overrideModifiers } from "#app/modifier/modifier";
import i18next from "i18next"; import i18next from "i18next";
import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters"; import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
import { Nature } from "#enums/nature";
export class EncounterPhase extends BattlePhase { export class EncounterPhase extends BattlePhase {
private loaded: boolean; private loaded: boolean;
@ -156,7 +157,31 @@ export class EncounterPhase extends BattlePhase {
loadEnemyAssets.push(enemyPokemon.loadAssets()); loadEnemyAssets.push(enemyPokemon.loadAssets());
console.log(`Pokemon: ${getPokemonNameWithAffix(enemyPokemon)}`, `Species ID: ${enemyPokemon.species.speciesId}`, `Stats: ${enemyPokemon.stats}`, `Ability: ${enemyPokemon.getAbility().name}`, `Passive Ability: ${enemyPokemon.getPassiveAbility().name}`); const stats: string[] = [
`HP: ${enemyPokemon.stats[0]} (${enemyPokemon.ivs[0]})`,
` Atk: ${enemyPokemon.stats[1]} (${enemyPokemon.ivs[1]})`,
` Def: ${enemyPokemon.stats[2]} (${enemyPokemon.ivs[2]})`,
` Spatk: ${enemyPokemon.stats[3]} (${enemyPokemon.ivs[3]})`,
` Spdef: ${enemyPokemon.stats[4]} (${enemyPokemon.ivs[4]})`,
` Spd: ${enemyPokemon.stats[5]} (${enemyPokemon.ivs[5]})`,
];
const moveset: string[] = [];
enemyPokemon.getMoveset().forEach((move) => {
moveset.push(move!.getName());
});
console.log(
`Pokemon: ${getPokemonNameWithAffix(enemyPokemon)}`,
`| Species ID: ${enemyPokemon.species.speciesId}`,
`| Nature: ${Nature[enemyPokemon.getNature()]}`,
);
console.log(`Stats (IVs): ${stats}`);
console.log(
`Ability: ${enemyPokemon.getAbility().name}`,
`| Passive Ability${enemyPokemon.isBoss() ? "" : " (inactive)"}: ${enemyPokemon.getPassiveAbility().name}`,
`${enemyPokemon.isBoss() ? `| Boss Bars: ${enemyPokemon.bossSegments}` : ""}`
);
console.log("Moveset:", moveset);
return true; return true;
}); });

View File

@ -12,16 +12,22 @@ export class GameOverModifierRewardPhase extends ModifierRewardPhase {
doReward(): Promise<void> { doReward(): Promise<void> {
return new Promise<void>(resolve => { return new Promise<void>(resolve => {
const newModifier = this.modifierType.newModifier(); const newModifier = this.modifierType.newModifier();
globalScene.addModifier(newModifier).then(() => { globalScene.addModifier(newModifier);
// Sound loaded into game as is // Sound loaded into game as is
globalScene.playSound("level_up_fanfare"); globalScene.playSound("level_up_fanfare");
globalScene.ui.setMode(Mode.MESSAGE); globalScene.ui.setMode(Mode.MESSAGE);
globalScene.ui.fadeIn(250).then(() => { globalScene.ui.fadeIn(250).then(() => {
globalScene.ui.showText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }), null, () => { globalScene.ui.showText(
i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }),
null,
() => {
globalScene.time.delayedCall(1500, () => globalScene.arenaBg.setVisible(true)); globalScene.time.delayedCall(1500, () => globalScene.arenaBg.setVisible(true));
resolve(); resolve();
}, null, true, 1500); },
}); null,
true,
1500,
);
}); });
}); });
} }

View File

@ -0,0 +1,20 @@
import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims";
import type { Moves } from "#enums/moves";
import { Phase } from "#app/phase";
/**
* Phase for synchronous move animation loading.
* Should be used when a move invokes another move that
* isn't already loaded (e.g. for Metronome)
*/
export class LoadMoveAnimPhase extends Phase {
constructor(protected moveId: Moves) {
super();
}
public override start(): void {
initMoveAnim(this.moveId)
.then(() => loadMoveAnimAssets([ this.moveId ], true))
.then(() => this.end());
}
}

View File

@ -22,10 +22,9 @@ export class ModifierRewardPhase extends BattlePhase {
doReward(): Promise<void> { doReward(): Promise<void> {
return new Promise<void>(resolve => { return new Promise<void>(resolve => {
const newModifier = this.modifierType.newModifier(); const newModifier = this.modifierType.newModifier();
globalScene.addModifier(newModifier).then(() => { globalScene.addModifier(newModifier);
globalScene.playSound("item_fanfare"); globalScene.playSound("item_fanfare");
globalScene.ui.showText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }), null, () => resolve(), null, true); globalScene.ui.showText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }), null, () => resolve(), null, true);
}); });
});
} }
} }

View File

@ -0,0 +1,20 @@
import type { MoveAnim } from "#app/data/battle-anims";
import { Phase } from "#app/phase";
/**
* Plays the given {@linkcode MoveAnim} sequentially.
*/
export class MoveAnimPhase<Anim extends MoveAnim> extends Phase {
constructor(
protected anim: Anim,
protected onSubstitute: boolean = false,
) {
super();
}
public override start(): void {
super.start();
this.anim.play(this.onSubstitute, () => this.end());
}
}

View File

@ -44,11 +44,10 @@ export class MoveChargePhase extends PokemonPhase {
new MoveChargeAnim(move.chargeAnim, move.id, user).play(false, () => { new MoveChargeAnim(move.chargeAnim, move.id, user).play(false, () => {
move.showChargeText(user, target); move.showChargeText(user, target);
applyMoveChargeAttrs(MoveEffectAttr, user, target, move).then(() => { applyMoveChargeAttrs(MoveEffectAttr, user, target, move);
user.addTag(BattlerTagType.CHARGING, 1, move.id, user.id); user.addTag(BattlerTagType.CHARGING, 1, move.id, user.id);
this.end(); this.end();
}); });
});
} }
/** Checks the move's instant charge conditions, then ends this phase. */ /** Checks the move's instant charge conditions, then ends this phase. */

View File

@ -61,7 +61,7 @@ import {
PokemonMultiHitModifier, PokemonMultiHitModifier,
} from "#app/modifier/modifier"; } from "#app/modifier/modifier";
import { PokemonPhase } from "#app/phases/pokemon-phase"; import { PokemonPhase } from "#app/phases/pokemon-phase";
import { BooleanHolder, executeIf, isNullOrUndefined, NumberHolder } from "#app/utils"; import { BooleanHolder, isNullOrUndefined, NumberHolder } from "#app/utils";
import { type nil } from "#app/utils"; import { type nil } from "#app/utils";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import type { Moves } from "#enums/moves"; import type { Moves } from "#enums/moves";
@ -143,7 +143,8 @@ export class MoveEffectPhase extends PokemonPhase {
const move = this.move.getMove(); const move = this.move.getMove();
// Assume single target for override // Assume single target for override
applyMoveAttrs(OverrideMoveEffectAttr, user, this.getFirstTarget() ?? null, move, overridden, this.move.virtual).then(() => { applyMoveAttrs(OverrideMoveEffectAttr, user, this.getFirstTarget() ?? null, move, overridden, this.move.virtual);
// If other effects were overriden, stop this phase before they can be applied // If other effects were overriden, stop this phase before they can be applied
if (overridden.value) { if (overridden.value) {
return this.end(); return this.end();
@ -217,12 +218,11 @@ export class MoveEffectPhase extends PokemonPhase {
return this.end(); return this.end();
} }
/** All move effect attributes are chained together in this array to be applied asynchronously. */
const applyAttrs: Promise<void>[] = [];
const playOnEmptyField = globalScene.currentBattle?.mysteryEncounter?.hasBattleAnimationsWithoutTargets ?? false; const playOnEmptyField = globalScene.currentBattle?.mysteryEncounter?.hasBattleAnimationsWithoutTargets ?? false;
// Move animation only needs one target // Move animation only needs one target
new MoveAnim(move.id as Moves, user, this.getFirstTarget()!.getBattlerIndex(), playOnEmptyField).play(move.hitsSubstitute(user, this.getFirstTarget()!), () => { new MoveAnim(move.id as Moves, user, this.getFirstTarget()!.getBattlerIndex(), playOnEmptyField).play(
move.hitsSubstitute(user, this.getFirstTarget()!),
() => {
/** Has the move successfully hit a target (for damage) yet? */ /** Has the move successfully hit a target (for damage) yet? */
let hasHit: boolean = false; let hasHit: boolean = false;
@ -313,7 +313,7 @@ export class MoveEffectPhase extends PokemonPhase {
} }
/** Does this phase represent the invoked move's first strike? */ /** Does this phase represent the invoked move's first strike? */
const firstHit = (user.turnData.hitsLeft === user.turnData.hitCount); const firstHit = user.turnData.hitsLeft === user.turnData.hitCount;
// Only log the move's result on the first strike // Only log the move's result on the first strike
if (firstHit) { if (firstHit) {
@ -363,7 +363,7 @@ export class MoveEffectPhase extends PokemonPhase {
} }
/** Does this phase represent the invoked move's last strike? */ /** Does this phase represent the invoked move's last strike? */
const lastHit = (user.turnData.hitsLeft === 1 || !this.getFirstTarget()?.isActive()); const lastHit = user.turnData.hitsLeft === 1 || !this.getFirstTarget()?.isActive();
/** /**
* If the user can change forms by using the invoked move, * If the user can change forms by using the invoked move,
@ -381,43 +381,29 @@ export class MoveEffectPhase extends PokemonPhase {
} }
} }
/** applyFilteredMoveAttrs(
* Create a Promise that applies *all* effects from the invoked move's MoveEffectAttrs. (attr: MoveAttr) =>
* These are ordered by trigger type (see {@linkcode MoveEffectTrigger}), and each trigger attr instanceof MoveEffectAttr &&
* type requires different conditions to be met with respect to the move's hit result. attr.trigger === MoveEffectTrigger.PRE_APPLY &&
*/ (!attr.firstHitOnly || firstHit) &&
const k = new Promise<void>((resolve) => { (!attr.lastHitOnly || lastHit) &&
//Start promise chain and apply PRE_APPLY move attributes hitResult !== HitResult.NO_EFFECT,
let promiseChain: Promise<void | null> = applyFilteredMoveAttrs((attr: MoveAttr) => user,
attr instanceof MoveEffectAttr target,
&& attr.trigger === MoveEffectTrigger.PRE_APPLY move,
&& (!attr.firstHitOnly || firstHit) );
&& (!attr.lastHitOnly || lastHit)
&& hitResult !== HitResult.NO_EFFECT, user, target, move);
/** Don't complete if the move failed */ if (hitResult !== HitResult.FAIL) {
if (hitResult === HitResult.FAIL) { this.applySelfTargetEffects(user, target, firstHit, lastHit);
return resolve();
}
/** Apply Move/Ability Effects in correct order */
promiseChain = promiseChain
.then(this.applySelfTargetEffects(user, target, firstHit, lastHit));
if (hitResult !== HitResult.NO_EFFECT) { if (hitResult !== HitResult.NO_EFFECT) {
promiseChain this.applyPostApplyEffects(user, target, firstHit, lastHit);
.then(this.applyPostApplyEffects(user, target, firstHit, lastHit)) this.applyHeldItemFlinchCheck(user, target, dealsDamage);
.then(this.applyHeldItemFlinchCheck(user, target, dealsDamage)) this.applySuccessfulAttackEffects(user, target, firstHit, lastHit, !!isProtected, hitResult, firstTarget);
.then(this.applySuccessfulAttackEffects(user, target, firstHit, lastHit, !!isProtected, hitResult, firstTarget))
.then(() => resolve());
} else { } else {
promiseChain applyMoveAttrs(NoEffectAttr, user, null, move);
.then(() => applyMoveAttrs(NoEffectAttr, user, null, move)) }
.then(resolve);
} }
});
applyAttrs.push(k);
} }
// Apply queued phases // Apply queued phases
@ -425,26 +411,21 @@ export class MoveEffectPhase extends PokemonPhase {
globalScene.appendToPhase(queuedPhases, MoveEndPhase); globalScene.appendToPhase(queuedPhases, MoveEndPhase);
} }
// Apply the move's POST_TARGET effects on the move's last hit, after all targeted effects have resolved // Apply the move's POST_TARGET effects on the move's last hit, after all targeted effects have resolved
const postTarget = (user.turnData.hitsLeft === 1 || !this.getFirstTarget()?.isActive()) ? if (user.turnData.hitsLeft === 1 || !this.getFirstTarget()?.isActive()) {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET, user, null, move) : applyFilteredMoveAttrs(
null; (attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET,
user,
if (postTarget) { null,
if (applyAttrs.length) { // If there is a pending asynchronous move effect, do this after move,
applyAttrs[applyAttrs.length - 1].then(() => postTarget); );
} else { // Otherwise, push a new asynchronous move effect
applyAttrs.push(postTarget);
}
} }
// Wait for all move effects to finish applying, then end this phase
Promise.allSettled(applyAttrs).then(() => {
/** /**
* Remove the target's substitute (if it exists and has expired) * Remove the target's substitute (if it exists and has expired)
* after all targeted effects have applied. * after all targeted effects have applied.
* This prevents blocked effects from applying until after this hit resolves. * This prevents blocked effects from applying until after this hit resolves.
*/ */
targets.forEach(target => { targets.forEach((target) => {
const substitute = target.getTag(SubstituteTag); const substitute = target.getTag(SubstituteTag);
if (substitute && substitute.hp <= 0) { if (substitute && substitute.hp <= 0) {
target.lapseTag(BattlerTagType.SUBSTITUTE); target.lapseTag(BattlerTagType.SUBSTITUTE);
@ -457,9 +438,8 @@ export class MoveEffectPhase extends PokemonPhase {
} }
this.end(); this.end();
}); },
}); );
});
} }
public override end(): void { public override end(): void {
@ -500,8 +480,8 @@ export class MoveEffectPhase extends PokemonPhase {
* @param lastHit - `true` if this is the last hit in a multi-hit attack * @param lastHit - `true` if this is the last hit in a multi-hit attack
* @returns a function intended to be passed into a `then()` call. * @returns a function intended to be passed into a `then()` call.
*/ */
protected applySelfTargetEffects(user: Pokemon, target: Pokemon, firstHit: boolean, lastHit: boolean): () => Promise<void | null> { protected applySelfTargetEffects(user: Pokemon, target: Pokemon, firstHit: boolean, lastHit: boolean): void {
return () => applyFilteredMoveAttrs((attr: MoveAttr) => applyFilteredMoveAttrs((attr: MoveAttr) =>
attr instanceof MoveEffectAttr attr instanceof MoveEffectAttr
&& attr.trigger === MoveEffectTrigger.POST_APPLY && attr.trigger === MoveEffectTrigger.POST_APPLY
&& attr.selfTarget && attr.selfTarget
@ -518,8 +498,8 @@ export class MoveEffectPhase extends PokemonPhase {
* @param lastHit - `true` if this is the last hit in a multi-hit attack * @param lastHit - `true` if this is the last hit in a multi-hit attack
* @returns a function intended to be passed into a `then()` call. * @returns a function intended to be passed into a `then()` call.
*/ */
protected applyPostApplyEffects(user: Pokemon, target: Pokemon, firstHit: boolean, lastHit: boolean): () => Promise<void | null> { protected applyPostApplyEffects(user: Pokemon, target: Pokemon, firstHit: boolean, lastHit: boolean): void {
return () => applyFilteredMoveAttrs((attr: MoveAttr) => applyFilteredMoveAttrs((attr: MoveAttr) =>
attr instanceof MoveEffectAttr attr instanceof MoveEffectAttr
&& attr.trigger === MoveEffectTrigger.POST_APPLY && attr.trigger === MoveEffectTrigger.POST_APPLY
&& !attr.selfTarget && !attr.selfTarget
@ -537,8 +517,8 @@ export class MoveEffectPhase extends PokemonPhase {
* @param firstTarget - `true` if {@linkcode target} is the first target hit by this strike of {@linkcode move} * @param firstTarget - `true` if {@linkcode target} is the first target hit by this strike of {@linkcode move}
* @returns a function intended to be passed into a `then()` call. * @returns a function intended to be passed into a `then()` call.
*/ */
protected applyOnHitEffects(user: Pokemon, target: Pokemon, firstHit : boolean, lastHit: boolean, firstTarget: boolean): Promise<void> { protected applyOnHitEffects(user: Pokemon, target: Pokemon, firstHit : boolean, lastHit: boolean, firstTarget: boolean): void {
return applyFilteredMoveAttrs((attr: MoveAttr) => applyFilteredMoveAttrs((attr: MoveAttr) =>
attr instanceof MoveEffectAttr attr instanceof MoveEffectAttr
&& attr.trigger === MoveEffectTrigger.HIT && attr.trigger === MoveEffectTrigger.HIT
&& (!attr.firstHitOnly || firstHit) && (!attr.firstHitOnly || firstHit)
@ -554,10 +534,9 @@ export class MoveEffectPhase extends PokemonPhase {
* @param hitResult - The {@linkcode HitResult} of the attempted move * @param hitResult - The {@linkcode HitResult} of the attempted move
* @returns a `Promise` intended to be passed into a `then()` call. * @returns a `Promise` intended to be passed into a `then()` call.
*/ */
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): Promise<void | null> { protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): void {
return executeIf(!target.isFainted() || target.canApplyAbility(), () => if (!target.isFainted() || target.canApplyAbility()) {
applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult) applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult);
.then(() => {
if (!this.move.getMove().hitsSubstitute(user, target)) { if (!this.move.getMove().hitsSubstitute(user, target)) {
if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) { if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) {
@ -566,9 +545,7 @@ export class MoveEffectPhase extends PokemonPhase {
target.lapseTags(BattlerTagLapseType.AFTER_HIT); target.lapseTags(BattlerTagLapseType.AFTER_HIT);
} }
}
})
);
} }
/** /**
@ -583,17 +560,15 @@ export class MoveEffectPhase extends PokemonPhase {
* @param firstTarget - `true` if {@linkcode target} is the first target hit by this strike of {@linkcode move} * @param firstTarget - `true` if {@linkcode target} is the first target hit by this strike of {@linkcode move}
* @returns a function intended to be passed into a `then()` call. * @returns a function intended to be passed into a `then()` call.
*/ */
protected applySuccessfulAttackEffects(user: Pokemon, target: Pokemon, firstHit : boolean, lastHit: boolean, isProtected : boolean, hitResult: HitResult, firstTarget: boolean) : () => Promise<void | null> { protected applySuccessfulAttackEffects(user: Pokemon, target: Pokemon, firstHit: boolean, lastHit: boolean, isProtected: boolean, hitResult: HitResult, firstTarget: boolean): void {
return () => executeIf(!isProtected, () => if (!isProtected) {
this.applyOnHitEffects(user, target, firstHit, lastHit, firstTarget).then(() => this.applyOnHitEffects(user, target, firstHit, lastHit, firstTarget);
this.applyOnGetHitAbEffects(user, target, hitResult)).then(() => this.applyOnGetHitAbEffects(user, target, hitResult);
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult)).then(() => { // Item Stealing Effects applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult);
if (this.move.getMove() instanceof AttackMove) { if (this.move.getMove() instanceof AttackMove) {
globalScene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target); globalScene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target);
} }
}) }
);
} }
/** /**
@ -603,8 +578,7 @@ export class MoveEffectPhase extends PokemonPhase {
* @param dealsDamage - `true` if the attempted move successfully dealt damage * @param dealsDamage - `true` if the attempted move successfully dealt damage
* @returns a function intended to be passed into a `then()` call. * @returns a function intended to be passed into a `then()` call.
*/ */
protected applyHeldItemFlinchCheck(user: Pokemon, target: Pokemon, dealsDamage: boolean) : () => void { protected applyHeldItemFlinchCheck(user: Pokemon, target: Pokemon, dealsDamage: boolean) : void {
return () => {
if (this.move.getMove().hasAttr(FlinchAttr)) { if (this.move.getMove().hasAttr(FlinchAttr)) {
return; return;
} }
@ -616,7 +590,6 @@ export class MoveEffectPhase extends PokemonPhase {
target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id); target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id);
} }
} }
};
} }
/** /**

View File

@ -22,9 +22,8 @@ export class MoveHeaderPhase extends BattlePhase {
super.start(); super.start();
if (this.canMove()) { if (this.canMove()) {
applyMoveAttrs(MoveHeaderAttr, this.pokemon, null, this.move.getMove()).then(() => this.end()); applyMoveAttrs(MoveHeaderAttr, this.pokemon, null, this.move.getMove());
} else { }
this.end(); this.end();
} }
}
} }

View File

@ -56,6 +56,7 @@ export class MovePhase extends BattlePhase {
protected _targets: BattlerIndex[]; protected _targets: BattlerIndex[];
protected followUp: boolean; protected followUp: boolean;
protected ignorePp: boolean; protected ignorePp: boolean;
protected forcedLast: boolean;
protected failed: boolean = false; protected failed: boolean = false;
protected cancelled: boolean = false; protected cancelled: boolean = false;
protected reflected: boolean = false; protected reflected: boolean = false;
@ -90,7 +91,8 @@ export class MovePhase extends BattlePhase {
* @param reflected Indicates that the move was reflected by Magic Coat or Magic Bounce. * @param reflected Indicates that the move was reflected by Magic Coat or Magic Bounce.
* Reflected moves cannot be reflected again and will not trigger Dancer. * Reflected moves cannot be reflected again and will not trigger Dancer.
*/ */
constructor(pokemon: Pokemon, targets: BattlerIndex[], move: PokemonMove, followUp: boolean = false, ignorePp: boolean = false, reflected: boolean = false) {
constructor(pokemon: Pokemon, targets: BattlerIndex[], move: PokemonMove, followUp: boolean = false, ignorePp: boolean = false, reflected: boolean = false, forcedLast: boolean = false) {
super(); super();
this.pokemon = pokemon; this.pokemon = pokemon;
@ -99,6 +101,7 @@ export class MovePhase extends BattlePhase {
this.followUp = followUp; this.followUp = followUp;
this.ignorePp = ignorePp; this.ignorePp = ignorePp;
this.reflected = reflected; this.reflected = reflected;
this.forcedLast = forcedLast;
} }
/** /**
@ -120,6 +123,15 @@ export class MovePhase extends BattlePhase {
this.cancelled = true; this.cancelled = true;
} }
/**
* Shows whether the current move has been forced to the end of the turn
* Needed for speed order, see {@linkcode Moves.QUASH}
* */
public isForcedLast(): boolean {
return this.forcedLast;
}
public start(): void { public start(): void {
super.start(); super.start();

View File

@ -0,0 +1,77 @@
import type { BattlerIndex } from "#app/battle";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
import { EFFECTIVE_STATS, BATTLE_STATS } from "#enums/stat";
import { PokemonMove } from "#app/field/pokemon";
import { globalScene } from "#app/global-scene";
import { PokemonPhase } from "./pokemon-phase";
/**
* Transforms a Pokemon into another Pokemon on the field.
* Used for Transform (move) and Imposter (ability)
*/
export class PokemonTransformPhase extends PokemonPhase {
protected targetIndex: BattlerIndex;
private playSound: boolean;
constructor(userIndex: BattlerIndex, targetIndex: BattlerIndex, playSound: boolean = false) {
super(userIndex);
this.targetIndex = targetIndex;
this.playSound = playSound;
}
public override start(): void {
const user = this.getPokemon();
const target = globalScene.getField(true).find((p) => p.getBattlerIndex() === this.targetIndex);
if (!target) {
return this.end();
}
user.summonData.speciesForm = target.getSpeciesForm();
user.summonData.ability = target.getAbility().id;
user.summonData.gender = target.getGender();
// Power Trick's effect is removed after using Transform
user.removeTag(BattlerTagType.POWER_TRICK);
// Copy all stats (except HP)
for (const s of EFFECTIVE_STATS) {
user.setStat(s, target.getStat(s, false), false);
}
// Copy all stat stages
for (const s of BATTLE_STATS) {
user.setStatStage(s, target.getStatStage(s));
}
user.summonData.moveset = target.getMoveset().map((m) => {
if (m) {
// If PP value is less than 5, do nothing. If greater, we need to reduce the value to 5.
return new PokemonMove(m.moveId, 0, 0, false, Math.min(m.getMove().pp, 5));
} else {
console.warn(`Transform: somehow iterating over a ${m} value when copying moveset!`);
return new PokemonMove(Moves.NONE);
}
});
user.summonData.types = target.getTypes();
const promises = [ user.updateInfo() ];
if (this.playSound) {
globalScene.playSound("battle_anims/PRSFX- Transform");
}
promises.push(
user.loadAssets(false).then(() => {
user.playAnim();
user.updateInfo();
// If the new ability activates immediately, it needs to happen after all the transform animations
user.setTempAbility(target.getAbility());
}),
);
Promise.allSettled(promises).then(() => this.end());
}
}

View File

@ -27,12 +27,10 @@ export class PostSummonPhase extends PokemonPhase {
pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON); pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON);
} }
applyPostSummonAbAttrs(PostSummonAbAttr, pokemon) applyPostSummonAbAttrs(PostSummonAbAttr, pokemon);
.then(() => {
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
field.forEach((p) => applyAbAttrs(CommanderAbAttr, p, null, false)); field.forEach((p) => applyAbAttrs(CommanderAbAttr, p, null, false));
this.end(); this.end();
});
} }
} }

View File

@ -0,0 +1,61 @@
import { SwitchType } from "#enums/switch-type";
import { globalScene } from "#app/global-scene";
import type { PartyOption } from "#app/ui/party-ui-handler";
import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler";
import { Mode } from "#app/ui/ui";
import i18next from "i18next";
import * as Utils from "#app/utils";
import { BattlePhase } from "#app/phases/battle-phase";
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase";
import type { PlayerPokemon } from "#app/field/pokemon";
/**
* Sets the Party UI and handles the effect of Revival Blessing
* when used by one of the player's Pokemon.
*/
export class RevivalBlessingPhase extends BattlePhase {
constructor(protected user: PlayerPokemon) {
super();
}
public override start(): void {
globalScene.ui.setMode(
Mode.PARTY,
PartyUiMode.REVIVAL_BLESSING,
this.user.getFieldIndex(),
(slotIndex: integer, option: PartyOption) => {
if (slotIndex >= 0 && slotIndex < 6) {
const pokemon = globalScene.getPlayerParty()[slotIndex];
if (!pokemon || !pokemon.isFainted()) {
return this.end();
}
pokemon.resetTurnData();
pokemon.resetStatus();
pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp()));
globalScene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: pokemon.name }), 0, true);
if (globalScene.currentBattle.double && globalScene.getPlayerParty().length > 1) {
const allyPokemon = this.user.getAlly();
if (slotIndex <= 1) {
// Revived ally pokemon
globalScene.unshiftPhase(
new SwitchSummonPhase(SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, true),
);
globalScene.unshiftPhase(new ToggleDoublePositionPhase(true));
} else if (allyPokemon.isFainted()) {
// Revived party pokemon, and ally pokemon is fainted
globalScene.unshiftPhase(
new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, true),
);
globalScene.unshiftPhase(new ToggleDoublePositionPhase(true));
}
}
}
globalScene.ui.setMode(Mode.MESSAGE).then(() => this.end());
},
PartyUiHandler.FilterFainted,
);
}
}

View File

@ -17,17 +17,16 @@ export class RibbonModifierRewardPhase extends ModifierRewardPhase {
doReward(): Promise<void> { doReward(): Promise<void> {
return new Promise<void>(resolve => { return new Promise<void>(resolve => {
const newModifier = this.modifierType.newModifier(); const newModifier = this.modifierType.newModifier();
globalScene.addModifier(newModifier).then(() => { globalScene.addModifier(newModifier);
globalScene.playSound("level_up_fanfare"); globalScene.playSound("level_up_fanfare");
globalScene.ui.setMode(Mode.MESSAGE); globalScene.ui.setMode(Mode.MESSAGE);
globalScene.ui.showText(i18next.t("battle:beatModeFirstTime", { globalScene.ui.showText(i18next.t("battle:beatModeFirstTime", {
speciesName: this.species.name, speciesName: this.species.name,
gameMode: globalScene.gameMode.getName(), gameMode: globalScene.gameMode.getName(),
newModifier: newModifier?.type.name newModifier: newModifier?.type.name,
}), null, () => { }), null, () => {
resolve(); resolve();
}, null, true, 1500); }, null, true, 1500);
}); });
});
} }
} }

View File

@ -171,8 +171,7 @@ export class SelectModifierPhase extends BattlePhase {
} }
if (cost && !(modifier.type instanceof RememberMoveModifierType)) { if (cost && !(modifier.type instanceof RememberMoveModifierType)) {
result.then(success => { if (result) {
if (success) {
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
globalScene.money -= cost; globalScene.money -= cost;
globalScene.updateMoneyText(); globalScene.updateMoneyText();
@ -183,18 +182,10 @@ export class SelectModifierPhase extends BattlePhase {
} else { } else {
globalScene.ui.playError(); globalScene.ui.playError();
} }
});
} else { } else {
const doEnd = () => {
globalScene.ui.clearText(); globalScene.ui.clearText();
globalScene.ui.setMode(Mode.MESSAGE); globalScene.ui.setMode(Mode.MESSAGE);
super.end(); super.end();
};
if (result instanceof Promise) {
result.then(() => doEnd());
} else {
doEnd();
}
} }
}; };
@ -304,7 +295,7 @@ export class SelectModifierPhase extends BattlePhase {
); );
} }
addModifier(modifier: Modifier): Promise<boolean> { addModifier(modifier: Modifier): boolean {
return globalScene.addModifier(modifier, false, true); return globalScene.addModifier(modifier, false, true);
} }
} }

View File

@ -12,6 +12,7 @@ import type { Starter } from "#app/ui/starter-select-ui-handler";
import { Mode } from "#app/ui/ui"; import { Mode } from "#app/ui/ui";
import type { Species } from "#enums/species"; import type { Species } from "#enums/species";
import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
import * as Utils from "../utils";
export class SelectStarterPhase extends Phase { export class SelectStarterPhase extends Phase {
@ -79,6 +80,12 @@ export class SelectStarterPhase extends Phase {
starterPokemon.nickname = starter.nickname; starterPokemon.nickname = starter.nickname;
} }
if (!Utils.isNullOrUndefined(starter.teraType)) {
starterPokemon.teraType = starter.teraType;
} else {
starterPokemon.teraType = starterPokemon.species.type1;
}
if (globalScene.gameMode.isSplicedOnly || Overrides.STARTER_FUSION_OVERRIDE) { if (globalScene.gameMode.isSplicedOnly || Overrides.STARTER_FUSION_OVERRIDE) {
starterPokemon.generateFusionSpecies(true); starterPokemon.generateFusionSpecies(true);
} }

View File

@ -55,6 +55,7 @@ import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-e
import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
import { ArenaTrapTag } from "#app/data/arena-tag"; import { ArenaTrapTag } from "#app/data/arena-tag";
import type { Type } from "#enums/type";
export const defaultStarterSpecies: Species[] = [ export const defaultStarterSpecies: Species[] = [
Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE, Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE,
@ -229,6 +230,7 @@ export interface StarterAttributes {
shiny?: boolean; shiny?: boolean;
favorite?: boolean; favorite?: boolean;
nickname?: string; nickname?: string;
tera?: Type;
} }
export interface StarterPreferences { export interface StarterPreferences {

View File

@ -21,7 +21,7 @@ export enum SettingGamepad {
Button_Cycle_Gender = "BUTTON_CYCLE_GENDER", Button_Cycle_Gender = "BUTTON_CYCLE_GENDER",
Button_Cycle_Ability = "BUTTON_CYCLE_ABILITY", Button_Cycle_Ability = "BUTTON_CYCLE_ABILITY",
Button_Cycle_Nature = "BUTTON_CYCLE_NATURE", Button_Cycle_Nature = "BUTTON_CYCLE_NATURE",
Button_Cycle_Variant = "BUTTON_CYCLE_VARIANT", Button_Cycle_Tera = "BUTTON_CYCLE_TERA",
Button_Speed_Up = "BUTTON_SPEED_UP", Button_Speed_Up = "BUTTON_SPEED_UP",
Button_Slow_Down = "BUTTON_SLOW_DOWN", Button_Slow_Down = "BUTTON_SLOW_DOWN",
Button_Submit = "BUTTON_SUBMIT", Button_Submit = "BUTTON_SUBMIT",
@ -45,7 +45,7 @@ export const settingGamepadOptions = {
[SettingGamepad.Button_Cycle_Gender]: [ `KEY ${Button.CYCLE_GENDER.toString()}`, pressAction ], [SettingGamepad.Button_Cycle_Gender]: [ `KEY ${Button.CYCLE_GENDER.toString()}`, pressAction ],
[SettingGamepad.Button_Cycle_Ability]: [ `KEY ${Button.CYCLE_ABILITY.toString()}`, pressAction ], [SettingGamepad.Button_Cycle_Ability]: [ `KEY ${Button.CYCLE_ABILITY.toString()}`, pressAction ],
[SettingGamepad.Button_Cycle_Nature]: [ `KEY ${Button.CYCLE_NATURE.toString()}`, pressAction ], [SettingGamepad.Button_Cycle_Nature]: [ `KEY ${Button.CYCLE_NATURE.toString()}`, pressAction ],
[SettingGamepad.Button_Cycle_Variant]: [ `KEY ${Button.V.toString()}`, pressAction ], [SettingGamepad.Button_Cycle_Tera]: [ `KEY ${Button.CYCLE_TERA.toString()}`, pressAction ],
[SettingGamepad.Button_Speed_Up]: [ `KEY ${Button.SPEED_UP.toString()}`, pressAction ], [SettingGamepad.Button_Speed_Up]: [ `KEY ${Button.SPEED_UP.toString()}`, pressAction ],
[SettingGamepad.Button_Slow_Down]: [ `KEY ${Button.SLOW_DOWN.toString()}`, pressAction ], [SettingGamepad.Button_Slow_Down]: [ `KEY ${Button.SLOW_DOWN.toString()}`, pressAction ],
[SettingGamepad.Button_Submit]: [ `KEY ${Button.SUBMIT.toString()}`, pressAction ], [SettingGamepad.Button_Submit]: [ `KEY ${Button.SUBMIT.toString()}`, pressAction ],
@ -67,7 +67,7 @@ export const settingGamepadDefaults = {
[SettingGamepad.Button_Cycle_Gender]: 0, [SettingGamepad.Button_Cycle_Gender]: 0,
[SettingGamepad.Button_Cycle_Ability]: 0, [SettingGamepad.Button_Cycle_Ability]: 0,
[SettingGamepad.Button_Cycle_Nature]: 0, [SettingGamepad.Button_Cycle_Nature]: 0,
[SettingGamepad.Button_Cycle_Variant]: 0, [SettingGamepad.Button_Cycle_Tera]: 0,
[SettingGamepad.Button_Speed_Up]: 0, [SettingGamepad.Button_Speed_Up]: 0,
[SettingGamepad.Button_Slow_Down]: 0, [SettingGamepad.Button_Slow_Down]: 0,
[SettingGamepad.Button_Submit]: 0, [SettingGamepad.Button_Submit]: 0,
@ -96,7 +96,7 @@ export function setSettingGamepad(setting: SettingGamepad, value: number): boole
case SettingGamepad.Button_Cycle_Gender: case SettingGamepad.Button_Cycle_Gender:
case SettingGamepad.Button_Cycle_Ability: case SettingGamepad.Button_Cycle_Ability:
case SettingGamepad.Button_Cycle_Nature: case SettingGamepad.Button_Cycle_Nature:
case SettingGamepad.Button_Cycle_Variant: case SettingGamepad.Button_Cycle_Tera:
case SettingGamepad.Button_Speed_Up: case SettingGamepad.Button_Speed_Up:
case SettingGamepad.Button_Slow_Down: case SettingGamepad.Button_Slow_Down:
case SettingGamepad.Button_Submit: case SettingGamepad.Button_Submit:

View File

@ -32,8 +32,8 @@ export enum SettingKeyboard {
Alt_Button_Cycle_Ability = "ALT_BUTTON_CYCLE_ABILITY", Alt_Button_Cycle_Ability = "ALT_BUTTON_CYCLE_ABILITY",
Button_Cycle_Nature = "BUTTON_CYCLE_NATURE", Button_Cycle_Nature = "BUTTON_CYCLE_NATURE",
Alt_Button_Cycle_Nature = "ALT_BUTTON_CYCLE_NATURE", Alt_Button_Cycle_Nature = "ALT_BUTTON_CYCLE_NATURE",
Button_Cycle_Variant = "BUTTON_CYCLE_VARIANT", Button_Cycle_Tera = "BUTTON_CYCLE_TERA",
Alt_Button_Cycle_Variant = "ALT_BUTTON_CYCLE_VARIANT", Alt_Button_Cycle_Tera = "ALT_BUTTON_CYCLE_TERA",
Button_Speed_Up = "BUTTON_SPEED_UP", Button_Speed_Up = "BUTTON_SPEED_UP",
Alt_Button_Speed_Up = "ALT_BUTTON_SPEED_UP", Alt_Button_Speed_Up = "ALT_BUTTON_SPEED_UP",
Button_Slow_Down = "BUTTON_SLOW_DOWN", Button_Slow_Down = "BUTTON_SLOW_DOWN",
@ -73,8 +73,8 @@ export const settingKeyboardOptions = {
[SettingKeyboard.Alt_Button_Cycle_Ability]: [ `KEY ${Button.CYCLE_ABILITY.toString()}`, pressAction ], [SettingKeyboard.Alt_Button_Cycle_Ability]: [ `KEY ${Button.CYCLE_ABILITY.toString()}`, pressAction ],
[SettingKeyboard.Button_Cycle_Nature]: [ `KEY ${Button.CYCLE_NATURE.toString()}`, pressAction ], [SettingKeyboard.Button_Cycle_Nature]: [ `KEY ${Button.CYCLE_NATURE.toString()}`, pressAction ],
[SettingKeyboard.Alt_Button_Cycle_Nature]: [ `KEY ${Button.CYCLE_NATURE.toString()}`, pressAction ], [SettingKeyboard.Alt_Button_Cycle_Nature]: [ `KEY ${Button.CYCLE_NATURE.toString()}`, pressAction ],
[SettingKeyboard.Button_Cycle_Variant]: [ `KEY ${Button.V.toString()}`, pressAction ], [SettingKeyboard.Button_Cycle_Tera]: [ `KEY ${Button.CYCLE_TERA.toString()}`, pressAction ],
[SettingKeyboard.Alt_Button_Cycle_Variant]: [ `KEY ${Button.V.toString()}`, pressAction ], [SettingKeyboard.Alt_Button_Cycle_Tera]: [ `KEY ${Button.CYCLE_TERA.toString()}`, pressAction ],
[SettingKeyboard.Button_Speed_Up]: [ `KEY ${Button.SPEED_UP.toString()}`, pressAction ], [SettingKeyboard.Button_Speed_Up]: [ `KEY ${Button.SPEED_UP.toString()}`, pressAction ],
[SettingKeyboard.Alt_Button_Speed_Up]: [ `KEY ${Button.SPEED_UP.toString()}`, pressAction ], [SettingKeyboard.Alt_Button_Speed_Up]: [ `KEY ${Button.SPEED_UP.toString()}`, pressAction ],
[SettingKeyboard.Button_Slow_Down]: [ `KEY ${Button.SLOW_DOWN.toString()}`, pressAction ], [SettingKeyboard.Button_Slow_Down]: [ `KEY ${Button.SLOW_DOWN.toString()}`, pressAction ],
@ -112,8 +112,8 @@ export const settingKeyboardDefaults = {
[SettingKeyboard.Alt_Button_Cycle_Ability]: 0, [SettingKeyboard.Alt_Button_Cycle_Ability]: 0,
[SettingKeyboard.Button_Cycle_Nature]: 0, [SettingKeyboard.Button_Cycle_Nature]: 0,
[SettingKeyboard.Alt_Button_Cycle_Nature]: 0, [SettingKeyboard.Alt_Button_Cycle_Nature]: 0,
[SettingKeyboard.Button_Cycle_Variant]: 0, [SettingKeyboard.Button_Cycle_Tera]: 0,
[SettingKeyboard.Alt_Button_Cycle_Variant]: 0, [SettingKeyboard.Alt_Button_Cycle_Tera]: 0,
[SettingKeyboard.Button_Speed_Up]: 0, [SettingKeyboard.Button_Speed_Up]: 0,
[SettingKeyboard.Alt_Button_Speed_Up]: 0, [SettingKeyboard.Alt_Button_Speed_Up]: 0,
[SettingKeyboard.Button_Slow_Down]: 0, [SettingKeyboard.Button_Slow_Down]: 0,
@ -148,7 +148,7 @@ export function setSettingKeyboard(setting: SettingKeyboard, value: number): boo
case SettingKeyboard.Button_Cycle_Gender: case SettingKeyboard.Button_Cycle_Gender:
case SettingKeyboard.Button_Cycle_Ability: case SettingKeyboard.Button_Cycle_Ability:
case SettingKeyboard.Button_Cycle_Nature: case SettingKeyboard.Button_Cycle_Nature:
case SettingKeyboard.Button_Cycle_Variant: case SettingKeyboard.Button_Cycle_Tera:
case SettingKeyboard.Button_Speed_Up: case SettingKeyboard.Button_Speed_Up:
case SettingKeyboard.Button_Slow_Down: case SettingKeyboard.Button_Slow_Down:
case SettingKeyboard.Alt_Button_Up: case SettingKeyboard.Alt_Button_Up:
@ -164,7 +164,7 @@ export function setSettingKeyboard(setting: SettingKeyboard, value: number): boo
case SettingKeyboard.Alt_Button_Cycle_Gender: case SettingKeyboard.Alt_Button_Cycle_Gender:
case SettingKeyboard.Alt_Button_Cycle_Ability: case SettingKeyboard.Alt_Button_Cycle_Ability:
case SettingKeyboard.Alt_Button_Cycle_Nature: case SettingKeyboard.Alt_Button_Cycle_Nature:
case SettingKeyboard.Alt_Button_Cycle_Variant: case SettingKeyboard.Alt_Button_Cycle_Tera:
case SettingKeyboard.Alt_Button_Speed_Up: case SettingKeyboard.Alt_Button_Speed_Up:
case SettingKeyboard.Alt_Button_Slow_Down: case SettingKeyboard.Alt_Button_Slow_Down:
case SettingKeyboard.Alt_Button_Submit: case SettingKeyboard.Alt_Button_Submit:

View File

@ -391,7 +391,7 @@ describe("Abilities - Unburden", () => {
await game.forceEnemyMove(Moves.THIEF, BattlerIndex.PLAYER); await game.forceEnemyMove(Moves.THIEF, BattlerIndex.PLAYER);
await game.forceEnemyMove(Moves.SPLASH); await game.forceEnemyMove(Moves.SPLASH);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2 ]); await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2 ]);
game.doSelectPartyPokemon(0, "MoveEffectPhase"); game.doSelectPartyPokemon(0, "RevivalBlessingPhase");
await game.toNextTurn(); await game.toNextTurn();
expect(game.scene.getPlayerField()[0]).toBe(treecko); expect(game.scene.getPlayerField()[0]).toBe(treecko);

View File

@ -1,4 +1,6 @@
import { allMoves } from "#app/data/move"; import { allMoves } from "#app/data/move";
import type { EnemyPersistentModifier } from "#app/modifier/modifier";
import { modifierTypes } from "#app/modifier/modifier-type";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
@ -65,6 +67,28 @@ describe("Battle Mechanics - Damage Calculation", () => {
expect(aggron.hp).toBe(aggron.getMaxHp() - 1); expect(aggron.hp).toBe(aggron.getMaxHp() - 1);
}); });
it("Attacks deal 1 damage at minimum even with many tokens", async () => {
game.override
.startingLevel(1)
.enemySpecies(Species.AGGRON)
.enemyAbility(Abilities.STURDY)
.enemyLevel(10000);
await game.classicMode.startBattle([ Species.SHUCKLE ]);
const dmg_redux_modifier = modifierTypes.ENEMY_DAMAGE_REDUCTION().newModifier() as EnemyPersistentModifier;
dmg_redux_modifier.stackCount = 1000;
await game.scene.addEnemyModifier(modifierTypes.ENEMY_DAMAGE_REDUCTION().newModifier() as EnemyPersistentModifier);
const aggron = game.scene.getEnemyPokemon()!;
game.move.select(Moves.TACKLE);
await game.phaseInterceptor.to("BerryPhase", false);
expect(aggron.hp).toBe(aggron.getMaxHp() - 1);
});
it("Fixed-damage moves ignore damage multipliers", async () => { it("Fixed-damage moves ignore damage multipliers", async () => {
game.override game.override
.enemySpecies(Species.DRAGONITE) .enemySpecies(Species.DRAGONITE)

View File

@ -0,0 +1,99 @@
import { Species } from "#enums/species";
import { Moves } from "#enums/moves";
import { Abilities } from "#app/enums/abilities";
import { BattlerIndex } from "#app/battle";
import { WeatherType } from "#enums/weather-type";
import { MoveResult } from "#app/field/pokemon";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { describe, beforeAll, afterEach, beforeEach, it, expect } from "vitest";
describe("Moves - Quash", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.battleType("double")
.enemyLevel(1)
.enemySpecies(Species.SLOWPOKE)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset([ Moves.RAIN_DANCE, Moves.SPLASH ])
.ability(Abilities.BALL_FETCH)
.moveset([ Moves.QUASH, Moves.SUNNY_DAY, Moves.RAIN_DANCE, Moves.SPLASH ]);
});
it("makes the target move last in a turn, ignoring priority", async () => {
await game.classicMode.startBattle([ Species.ACCELGOR, Species.RATTATA ]);
game.move.select(Moves.QUASH, 0, BattlerIndex.PLAYER_2);
game.move.select(Moves.SUNNY_DAY, 1);
await game.forceEnemyMove(Moves.SPLASH);
await game.forceEnemyMove(Moves.RAIN_DANCE);
await game.phaseInterceptor.to("TurnEndPhase", false);
// will be sunny if player_2 moved last because of quash, rainy otherwise
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SUNNY);
});
it("fails if the target has already moved", async () => {
await game.classicMode.startBattle([ Species.ACCELGOR, Species.RATTATA ]);
game.move.select(Moves.SPLASH, 0);
game.move.select(Moves.QUASH, 1, BattlerIndex.PLAYER);
await game.phaseInterceptor.to("MoveEndPhase");
await game.phaseInterceptor.to("MoveEndPhase");
expect(game.scene.getPlayerField()[1].getLastXMoves(1)[0].result).toBe(MoveResult.FAIL);
});
it("makes multiple quashed targets move in speed order at the end of the turn", async () => {
game.override.enemySpecies(Species.NINJASK)
.enemyLevel(100);
await game.classicMode.startBattle([ Species.ACCELGOR, Species.RATTATA ]);
// both users are quashed - rattata is slower so sun should be up at end of turn
game.move.select(Moves.RAIN_DANCE, 0);
game.move.select(Moves.SUNNY_DAY, 1);
await game.forceEnemyMove(Moves.QUASH, BattlerIndex.PLAYER);
await game.forceEnemyMove(Moves.QUASH, BattlerIndex.PLAYER_2);
await game.phaseInterceptor.to("TurnEndPhase", false);
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SUNNY);
});
it("respects trick room", async () => {
game.override.enemyMoveset([ Moves.RAIN_DANCE, Moves.SPLASH, Moves.TRICK_ROOM ]);
await game.classicMode.startBattle([ Species.ACCELGOR, Species.RATTATA ]);
game.move.select(Moves.SPLASH, 0);
game.move.select(Moves.SPLASH, 1);
await game.forceEnemyMove(Moves.TRICK_ROOM);
await game.forceEnemyMove(Moves.SPLASH);
await game.phaseInterceptor.to("TurnInitPhase");
// both users are quashed - accelgor should move last w/ TR so rain should be up at end of turn
game.move.select(Moves.RAIN_DANCE, 0);
game.move.select(Moves.SUNNY_DAY, 1);
await game.forceEnemyMove(Moves.QUASH, BattlerIndex.PLAYER);
await game.forceEnemyMove(Moves.QUASH, BattlerIndex.PLAYER_2);
await game.phaseInterceptor.to("TurnEndPhase", false);
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.RAIN);
});
});

View File

@ -0,0 +1,117 @@
import { BattlerIndex } from "#app/battle";
import { MoveResult } from "#app/field/pokemon";
import { toDmgValue } from "#app/utils";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Moves - Revival Blessing", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.moveset([ Moves.SPLASH, Moves.REVIVAL_BLESSING, Moves.MEMENTO ])
.ability(Abilities.BALL_FETCH)
.battleType("single")
.disableCrits()
.enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);
});
it("should revive a selected fainted Pokemon when used by the player", async () => {
await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]);
game.move.select(Moves.MEMENTO);
game.doSelectPartyPokemon(1, "SwitchPhase");
await game.toNextTurn();
const player = game.scene.getPlayerPokemon()!;
expect(player.species.speciesId).toBe(Species.MAGIKARP);
game.move.select(Moves.REVIVAL_BLESSING);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
game.doSelectPartyPokemon(1, "RevivalBlessingPhase");
await game.phaseInterceptor.to("MoveEndPhase", false);
const revivedPokemon = game.scene.getPlayerParty()[1];
expect(revivedPokemon.status?.effect).toBeFalsy();
expect(revivedPokemon.hp).toBe(Math.floor(revivedPokemon.getMaxHp() / 2));
});
it("should revive a random fainted enemy when used by an enemy Trainer", async () => {
game.override.enemyMoveset(Moves.REVIVAL_BLESSING).startingWave(8);
await game.classicMode.startBattle([ Species.MAGIKARP ]);
game.move.select(Moves.SPLASH);
await game.doKillOpponents();
await game.toNextTurn();
game.move.select(Moves.SPLASH);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.phaseInterceptor.to("MoveEndPhase", false);
const revivedPokemon = game.scene.getEnemyParty()[1];
expect(revivedPokemon.status?.effect).toBeFalsy();
expect(revivedPokemon.hp).toBe(Math.floor(revivedPokemon.getMaxHp() / 2));
});
it("should fail when there are no fainted Pokemon to target", async () => {
await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]);
game.move.select(Moves.REVIVAL_BLESSING);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.phaseInterceptor.to("MoveEndPhase", false);
const player = game.scene.getPlayerPokemon()!;
expect(player.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
});
it("should revive a player pokemon and immediately send it back out if used in the same turn it fainted in doubles", async () => {
game.override
.battleType("double")
.enemyMoveset([ Moves.SPLASH, Moves.FISSURE ])
.enemyAbility(Abilities.NO_GUARD)
.enemyLevel(100);
await game.classicMode.startBattle([ Species.FEEBAS, Species.MILOTIC, Species.GYARADOS ]);
const feebas = game.scene.getPlayerField()[0];
game.move.select(Moves.SPLASH);
game.move.select(Moves.REVIVAL_BLESSING, 1);
await game.forceEnemyMove(Moves.FISSURE, BattlerIndex.PLAYER);
await game.forceEnemyMove(Moves.SPLASH);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2 ]);
await game.phaseInterceptor.to("MoveEndPhase");
await game.phaseInterceptor.to("MoveEndPhase");
expect(feebas.isFainted()).toBe(true);
game.doSelectPartyPokemon(0, "RevivalBlessingPhase");
await game.toNextTurn();
expect(feebas.isFainted()).toBe(false);
expect(feebas.hp).toBe(toDmgValue(0.5 * feebas.getMaxHp()));
expect(game.scene.getPlayerField()[0]).toBe(feebas);
});
});

View File

@ -375,6 +375,6 @@ describe("Clowning Around - Mystery Encounter", () => {
async function addItemToPokemon(scene: BattleScene, pokemon: Pokemon, stackCount: number, itemType: PokemonHeldItemModifierType) { async function addItemToPokemon(scene: BattleScene, pokemon: Pokemon, stackCount: number, itemType: PokemonHeldItemModifierType) {
const itemMod = itemType.newModifier(pokemon) as PokemonHeldItemModifier; const itemMod = itemType.newModifier(pokemon) as PokemonHeldItemModifier;
itemMod.stackCount = stackCount; itemMod.stackCount = stackCount;
await scene.addModifier(itemMod, true, false, false, true); scene.addModifier(itemMod, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
} }

View File

@ -123,8 +123,6 @@ describe("Dancing Lessons - Mystery Encounter", () => {
partyLead.level = 1000; partyLead.level = 1000;
partyLead.calculateStats(); partyLead.calculateStats();
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
// For some reason updateModifiers breaks in this test and does not resolve promise
vi.spyOn(game.scene, "updateModifiers").mockImplementation(() => new Promise(resolve => resolve()));
await skipBattleRunMysteryEncounterRewardsPhase(game); await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false); await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);

View File

@ -123,7 +123,7 @@ describe("Delibird-y - Mystery Encounter", () => {
scene.modifiers = []; scene.modifiers = [];
const amuletCoin = generateModifierType(modifierTypes.AMULET_COIN)!.newModifier() as MoneyMultiplierModifier; const amuletCoin = generateModifierType(modifierTypes.AMULET_COIN)!.newModifier() as MoneyMultiplierModifier;
amuletCoin.stackCount = 5; amuletCoin.stackCount = 5;
await scene.addModifier(amuletCoin, true, false, false, true); scene.addModifier(amuletCoin, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
await runMysteryEncounterToEnd(game, 1); await runMysteryEncounterToEnd(game, 1);
@ -193,7 +193,7 @@ describe("Delibird-y - Mystery Encounter", () => {
const sitrus = generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ])!; const sitrus = generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ])!;
const sitrusMod = sitrus.newModifier(scene.getPlayerParty()[0]) as BerryModifier; const sitrusMod = sitrus.newModifier(scene.getPlayerParty()[0]) as BerryModifier;
sitrusMod.stackCount = 2; sitrusMod.stackCount = 2;
await scene.addModifier(sitrusMod, true, false, false, true); scene.addModifier(sitrusMod, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 }); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 });
@ -214,7 +214,7 @@ describe("Delibird-y - Mystery Encounter", () => {
const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!; const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!;
const modifier = revSeed.newModifier(scene.getPlayerParty()[0]) as PokemonInstantReviveModifier; const modifier = revSeed.newModifier(scene.getPlayerParty()[0]) as PokemonInstantReviveModifier;
modifier.stackCount = 1; modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true); scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 }); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 });
@ -234,13 +234,13 @@ describe("Delibird-y - Mystery Encounter", () => {
scene.modifiers = []; scene.modifiers = [];
const candyJar = generateModifierType(modifierTypes.CANDY_JAR)!.newModifier() as LevelIncrementBoosterModifier; const candyJar = generateModifierType(modifierTypes.CANDY_JAR)!.newModifier() as LevelIncrementBoosterModifier;
candyJar.stackCount = 99; candyJar.stackCount = 99;
await scene.addModifier(candyJar, true, false, false, true); scene.addModifier(candyJar, true, false, false, true);
const sitrus = generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ])!; const sitrus = generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ])!;
// Sitrus berries on party // Sitrus berries on party
const sitrusMod = sitrus.newModifier(scene.getPlayerParty()[0]) as BerryModifier; const sitrusMod = sitrus.newModifier(scene.getPlayerParty()[0]) as BerryModifier;
sitrusMod.stackCount = 2; sitrusMod.stackCount = 2;
await scene.addModifier(sitrusMod, true, false, false, true); scene.addModifier(sitrusMod, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 }); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 });
@ -263,13 +263,13 @@ describe("Delibird-y - Mystery Encounter", () => {
scene.modifiers = []; scene.modifiers = [];
const healingCharm = generateModifierType(modifierTypes.BERRY_POUCH)!.newModifier() as PreserveBerryModifier; const healingCharm = generateModifierType(modifierTypes.BERRY_POUCH)!.newModifier() as PreserveBerryModifier;
healingCharm.stackCount = 3; healingCharm.stackCount = 3;
await scene.addModifier(healingCharm, true, false, false, true); scene.addModifier(healingCharm, true, false, false, true);
// Set 1 Reviver Seed on party lead // Set 1 Reviver Seed on party lead
const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!; const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!;
const modifier = revSeed.newModifier(scene.getPlayerParty()[0]) as PokemonInstantReviveModifier; const modifier = revSeed.newModifier(scene.getPlayerParty()[0]) as PokemonInstantReviveModifier;
modifier.stackCount = 1; modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true); scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 }); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 });
@ -292,7 +292,7 @@ describe("Delibird-y - Mystery Encounter", () => {
scene.modifiers = []; scene.modifiers = [];
const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!;
const modifier = soulDew.newModifier(scene.getPlayerParty()[0]); const modifier = soulDew.newModifier(scene.getPlayerParty()[0]);
await scene.addModifier(modifier, true, false, false, true); scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
await game.phaseInterceptor.to(MysteryEncounterPhase, false); await game.phaseInterceptor.to(MysteryEncounterPhase, false);
@ -321,7 +321,7 @@ describe("Delibird-y - Mystery Encounter", () => {
const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!; const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!;
const modifier = revSeed.newModifier(scene.getPlayerParty()[0]) as PokemonInstantReviveModifier; const modifier = revSeed.newModifier(scene.getPlayerParty()[0]) as PokemonInstantReviveModifier;
modifier.stackCount = 1; modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true); scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 }); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 });
@ -355,7 +355,7 @@ describe("Delibird-y - Mystery Encounter", () => {
const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!;
const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier;
modifier.stackCount = 2; modifier.stackCount = 2;
await scene.addModifier(modifier, true, false, false, true); scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 });
@ -376,7 +376,7 @@ describe("Delibird-y - Mystery Encounter", () => {
const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!;
const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier;
modifier.stackCount = 1; modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true); scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 });
@ -396,13 +396,13 @@ describe("Delibird-y - Mystery Encounter", () => {
scene.modifiers = []; scene.modifiers = [];
const healingCharm = generateModifierType(modifierTypes.HEALING_CHARM)!.newModifier() as HealingBoosterModifier; const healingCharm = generateModifierType(modifierTypes.HEALING_CHARM)!.newModifier() as HealingBoosterModifier;
healingCharm.stackCount = 5; healingCharm.stackCount = 5;
await scene.addModifier(healingCharm, true, false, false, true); scene.addModifier(healingCharm, true, false, false, true);
// Set 1 Soul Dew on party lead // Set 1 Soul Dew on party lead
const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!;
const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier;
modifier.stackCount = 1; modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true); scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 });
@ -425,7 +425,7 @@ describe("Delibird-y - Mystery Encounter", () => {
scene.modifiers = []; scene.modifiers = [];
const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!; const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!;
const modifier = revSeed.newModifier(scene.getPlayerParty()[0]); const modifier = revSeed.newModifier(scene.getPlayerParty()[0]);
await scene.addModifier(modifier, true, false, false, true); scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
await game.phaseInterceptor.to(MysteryEncounterPhase, false); await game.phaseInterceptor.to(MysteryEncounterPhase, false);
@ -455,7 +455,7 @@ describe("Delibird-y - Mystery Encounter", () => {
const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!;
const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier;
modifier.stackCount = 1; modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true); scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 });

View File

@ -224,7 +224,7 @@ describe("Global Trade System - Mystery Encounter", () => {
const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!;
const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier;
modifier.stackCount = 2; modifier.stackCount = 2;
await scene.addModifier(modifier, true, false, false, true); scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 });
@ -249,7 +249,7 @@ describe("Global Trade System - Mystery Encounter", () => {
const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!;
const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier;
modifier.stackCount = 1; modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true); scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 });

View File

@ -216,11 +216,11 @@ describe("Uncommon Breed - Mystery Encounter", () => {
const sitrus = generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ])!; const sitrus = generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ])!;
const sitrusMod = sitrus.newModifier(scene.getPlayerParty()[0]) as BerryModifier; const sitrusMod = sitrus.newModifier(scene.getPlayerParty()[0]) as BerryModifier;
sitrusMod.stackCount = 2; sitrusMod.stackCount = 2;
await scene.addModifier(sitrusMod, true, false, false, true); scene.addModifier(sitrusMod, true, false, false, true);
const ganlon = generateModifierType(modifierTypes.BERRY, [ BerryType.GANLON ])!; const ganlon = generateModifierType(modifierTypes.BERRY, [ BerryType.GANLON ])!;
const ganlonMod = ganlon.newModifier(scene.getPlayerParty()[0]) as BerryModifier; const ganlonMod = ganlon.newModifier(scene.getPlayerParty()[0]) as BerryModifier;
ganlonMod.stackCount = 3; ganlonMod.stackCount = 3;
await scene.addModifier(ganlonMod, true, false, false, true); scene.addModifier(ganlonMod, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
await runMysteryEncounterToEnd(game, 2); await runMysteryEncounterToEnd(game, 2);

View File

@ -406,9 +406,9 @@ describe("Test Rebinding", () => {
}); });
it("check to delete all the binds of an action", () => { it("check to delete all the binds of an action", () => {
inGame.whenWePressOnKeyboard("V").weShouldTriggerTheButton("Button_Cycle_Variant"); inGame.whenWePressOnKeyboard("V").weShouldTriggerTheButton("Button_Cycle_Tera");
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Cycle_Variant").thereShouldBeNoIcon().weWantThisBindInstead("K").confirm(); inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Cycle_Tera").thereShouldBeNoIcon().weWantThisBindInstead("K").confirm();
inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Cycle_Variant").iconDisplayedIs("KEY_K").whenWeDelete().thereShouldBeNoIconAnymore(); inTheSettingMenu.whenCursorIsOnSetting("Alt_Button_Cycle_Tera").iconDisplayedIs("KEY_K").whenWeDelete().thereShouldBeNoIconAnymore();
inTheSettingMenu.whenCursorIsOnSetting("Button_Cycle_Variant").iconDisplayedIs("KEY_V").whenWeDelete().thereShouldBeNoIconAnymore(); inTheSettingMenu.whenCursorIsOnSetting("Button_Cycle_Tera").iconDisplayedIs("KEY_V").whenWeDelete().thereShouldBeNoIconAnymore();
}); });
}); });

View File

@ -35,7 +35,7 @@
<div id="apadCycleShiny" class="apad-button apad-square apad-small" data-key="CYCLE_SHINY"> <div id="apadCycleShiny" class="apad-button apad-square apad-small" data-key="CYCLE_SHINY">
<span class="apad-label">R</span> <span class="apad-label">R</span>
</div> </div>
<div id="apadCycleVariant" class="apad-button apad-square apad-small" data-key="V"> <div id="apadCycleTera" class="apad-button apad-square apad-small" data-key="CYCLE_TERA">
<span class="apad-label">V</span> <span class="apad-label">V</span>
</div> </div>
<div id="apadStats" class="apad-button apad-rectangle apad-small" data-key="STATS"> <div id="apadStats" class="apad-button apad-rectangle apad-small" data-key="STATS">
@ -59,7 +59,7 @@
<div id="apadCycleNature" class="apad-button apad-square apad-small" data-key="CYCLE_NATURE"> <div id="apadCycleNature" class="apad-button apad-square apad-small" data-key="CYCLE_NATURE">
<span class="apad-label">N</span> <span class="apad-label">N</span>
</div> </div>
<div id="apadInfo" class="apad-button apad-rectangle apad-small" data-key="V"> <div id="apadInfo" class="apad-button apad-rectangle apad-small" data-key="CYCLE_TERA">
<span class="apad-label">V</span> <span class="apad-label">V</span>
</div> </div>
</div> </div>

View File

@ -91,6 +91,7 @@ export default class GameWrapper {
Pokemon.prototype.updateFusionPalette = () => null; Pokemon.prototype.updateFusionPalette = () => null;
Pokemon.prototype.cry = () => null; Pokemon.prototype.cry = () => null;
Pokemon.prototype.faintCry = (cb) => { if (cb) cb(); }; Pokemon.prototype.faintCry = (cb) => { if (cb) cb(); };
BattleScene.prototype.addPokemonIcon = () => new Phaser.GameObjects.Container(this.scene);
} }
setScene(scene: BattleScene) { setScene(scene: BattleScene) {

View File

@ -60,6 +60,7 @@ import { RibbonModifierRewardPhase } from "#app/phases/ribbon-modifier-reward-ph
import { GameOverModifierRewardPhase } from "#app/phases/game-over-modifier-reward-phase"; import { GameOverModifierRewardPhase } from "#app/phases/game-over-modifier-reward-phase";
import { UnlockPhase } from "#app/phases/unlock-phase"; import { UnlockPhase } from "#app/phases/unlock-phase";
import { PostGameOverPhase } from "#app/phases/post-game-over-phase"; import { PostGameOverPhase } from "#app/phases/post-game-over-phase";
import { RevivalBlessingPhase } from "#app/phases/revival-blessing-phase";
export interface PromptHandler { export interface PromptHandler {
phaseTarget?: string; phaseTarget?: string;
@ -126,7 +127,8 @@ type PhaseClass =
| typeof EncounterPhase | typeof EncounterPhase
| typeof GameOverPhase | typeof GameOverPhase
| typeof UnlockPhase | typeof UnlockPhase
| typeof PostGameOverPhase; | typeof PostGameOverPhase
| typeof RevivalBlessingPhase;
type PhaseString = type PhaseString =
| "LoginPhase" | "LoginPhase"
@ -185,7 +187,8 @@ type PhaseString =
| "EncounterPhase" | "EncounterPhase"
| "GameOverPhase" | "GameOverPhase"
| "UnlockPhase" | "UnlockPhase"
| "PostGameOverPhase"; | "PostGameOverPhase"
| "RevivalBlessingPhase";
type PhaseInterceptorPhase = PhaseClass | PhaseString; type PhaseInterceptorPhase = PhaseClass | PhaseString;
@ -269,6 +272,7 @@ export default class PhaseInterceptor {
[ GameOverPhase, this.startPhase ], [ GameOverPhase, this.startPhase ],
[ UnlockPhase, this.startPhase ], [ UnlockPhase, this.startPhase ],
[ PostGameOverPhase, this.startPhase ], [ PostGameOverPhase, this.startPhase ],
[ RevivalBlessingPhase, this.startPhase ],
]; ];
private endBySetMode = [ private endBySetMode = [
@ -511,11 +515,11 @@ export default class PhaseInterceptor {
if (expireFn) { if (expireFn) {
this.prompts.shift(); this.prompts.shift();
} else if ( } else if (
currentMode === actionForNextPrompt.mode currentMode === actionForNextPrompt.mode &&
&& currentPhase === actionForNextPrompt.phaseTarget currentPhase === actionForNextPrompt.phaseTarget &&
&& currentHandler.active currentHandler.active &&
&& (!actionForNextPrompt.awaitingActionInput (!actionForNextPrompt.awaitingActionInput ||
|| (actionForNextPrompt.awaitingActionInput && currentHandler.awaitingActionInput)) (actionForNextPrompt.awaitingActionInput && currentHandler.awaitingActionInput))
) { ) {
const prompt = this.prompts.shift(); const prompt = this.prompts.shift();
if (prompt?.callback) { if (prompt?.callback) {

View File

@ -86,7 +86,7 @@ export class UiInputs {
[Button.CYCLE_GENDER]: () => this.buttonCycleOption(Button.CYCLE_GENDER), [Button.CYCLE_GENDER]: () => this.buttonCycleOption(Button.CYCLE_GENDER),
[Button.CYCLE_ABILITY]: () => this.buttonCycleOption(Button.CYCLE_ABILITY), [Button.CYCLE_ABILITY]: () => this.buttonCycleOption(Button.CYCLE_ABILITY),
[Button.CYCLE_NATURE]: () => this.buttonCycleOption(Button.CYCLE_NATURE), [Button.CYCLE_NATURE]: () => this.buttonCycleOption(Button.CYCLE_NATURE),
[Button.V]: () => this.buttonCycleOption(Button.V), [Button.CYCLE_TERA]: () => this.buttonCycleOption(Button.CYCLE_TERA),
[Button.SPEED_UP]: () => this.buttonSpeedChange(), [Button.SPEED_UP]: () => this.buttonSpeedChange(),
[Button.SLOW_DOWN]: () => this.buttonSpeedChange(false), [Button.SLOW_DOWN]: () => this.buttonSpeedChange(false),
}; };
@ -109,7 +109,7 @@ export class UiInputs {
[Button.CYCLE_GENDER]: () => undefined, [Button.CYCLE_GENDER]: () => undefined,
[Button.CYCLE_ABILITY]: () => undefined, [Button.CYCLE_ABILITY]: () => undefined,
[Button.CYCLE_NATURE]: () => undefined, [Button.CYCLE_NATURE]: () => undefined,
[Button.V]: () => this.buttonInfo(false), [Button.CYCLE_TERA]: () => undefined,
[Button.SPEED_UP]: () => undefined, [Button.SPEED_UP]: () => undefined,
[Button.SLOW_DOWN]: () => undefined, [Button.SLOW_DOWN]: () => undefined,
}; };
@ -197,7 +197,7 @@ export class UiInputs {
const uiHandler = globalScene.ui?.getHandler(); const uiHandler = globalScene.ui?.getHandler();
if (whitelist.some(handler => uiHandler instanceof handler)) { if (whitelist.some(handler => uiHandler instanceof handler)) {
globalScene.ui.processInput(button); globalScene.ui.processInput(button);
} else if (button === Button.V) { } else if (button === Button.CYCLE_TERA) {
this.buttonInfo(true); this.buttonInfo(true);
} }
} }

View File

@ -1,6 +1,7 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type Pokemon from "../field/pokemon"; import type Pokemon from "../field/pokemon";
import { TextStyle, addTextObject } from "./text"; import { TextStyle, addTextObject } from "./text";
import i18next from "i18next";
export default class PartyExpBar extends Phaser.GameObjects.Container { export default class PartyExpBar extends Phaser.GameObjects.Container {
private bg: Phaser.GameObjects.NineSlice; private bg: Phaser.GameObjects.NineSlice;
@ -43,9 +44,9 @@ export default class PartyExpBar extends Phaser.GameObjects.Container {
// if we want to only display the level in the small frame // if we want to only display the level in the small frame
if (showOnlyLevelUp) { if (showOnlyLevelUp) {
if (newLevel > 200) { // if the level is greater than 200, we only display Lv. UP if (newLevel > 200) { // if the level is greater than 200, we only display Lv. UP
this.expText.setText("Lv. UP"); this.expText.setText(i18next.t("battleScene:levelUp"));
} else { // otherwise we display Lv. Up and the new level } else { // otherwise we display Lv. Up and the new level
this.expText.setText(`Lv. UP: ${newLevel.toString()}`); this.expText.setText(i18next.t("battleScene:levelUpWithLevel", { level: newLevel }));
} }
} else { } else {
// if we want to display the exp // if we want to display the exp

View File

@ -45,7 +45,6 @@ import { EggSourceType } from "#enums/egg-source-types";
import { getPassiveCandyCount, getValueReductionCandyCounts, getSameSpeciesEggCandyCounts } from "#app/data/balance/starters"; import { getPassiveCandyCount, getValueReductionCandyCounts, getSameSpeciesEggCandyCounts } from "#app/data/balance/starters";
import { BooleanHolder, getLocalizedSpriteKey, isNullOrUndefined, NumberHolder, padInt, rgbHexToRgba, toReadableString } from "#app/utils"; import { BooleanHolder, getLocalizedSpriteKey, isNullOrUndefined, NumberHolder, padInt, rgbHexToRgba, toReadableString } from "#app/utils";
import type { Nature } from "#enums/nature"; import type { Nature } from "#enums/nature";
import BgmBar from "./bgm-bar";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { speciesTmMoves } from "#app/data/balance/tms"; import { speciesTmMoves } from "#app/data/balance/tms";
import type { BiomeTierTod } from "#app/data/balance/biomes"; import type { BiomeTierTod } from "#app/data/balance/biomes";
@ -242,7 +241,6 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
private menuContainer: Phaser.GameObjects.Container; private menuContainer: Phaser.GameObjects.Container;
private menuBg: Phaser.GameObjects.NineSlice; private menuBg: Phaser.GameObjects.NineSlice;
protected optionSelectText: Phaser.GameObjects.Text; protected optionSelectText: Phaser.GameObjects.Text;
public bgmBar: BgmBar;
private menuOptions: MenuOptions[]; private menuOptions: MenuOptions[];
protected scale: number = 0.1666666667; protected scale: number = 0.1666666667;
private menuDescriptions: string[]; private menuDescriptions: string[];
@ -480,10 +478,6 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
this.menuContainer.setName("menu"); this.menuContainer.setName("menu");
this.menuContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); this.menuContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains);
this.bgmBar = new BgmBar();
this.bgmBar.setup();
ui.bgmBar = this.bgmBar;
this.menuContainer.add(this.bgmBar);
this.menuContainer.setVisible(false); this.menuContainer.setVisible(false);
this.menuOptions = Utils.getEnumKeys(MenuOptions).map(m => parseInt(MenuOptions[m]) as MenuOptions); this.menuOptions = Utils.getEnumKeys(MenuOptions).map(m => parseInt(MenuOptions[m]) as MenuOptions);
@ -983,13 +977,23 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
ui.setModeWithoutClear(Mode.OPTION_SELECT, { ui.setModeWithoutClear(Mode.OPTION_SELECT, {
options: this.levelMoves.map(m => { options: this.levelMoves.map(m => {
const levelNumber = m[0] > 0 ? String(m[0]) : "";
const option: OptionSelectItem = { const option: OptionSelectItem = {
label: String(m[0]).padEnd(4, " ") + allMoves[m[1]].name, label: levelNumber.padEnd(4, " ") + allMoves[m[1]].name,
handler: () => { handler: () => {
return false; return false;
}, },
onHover: () => { onHover: () => {
this.moveInfoOverlay.show(allMoves[m[1]]); this.moveInfoOverlay.show(allMoves[m[1]]);
if (m[0] === 0) {
this.showText(i18next.t("pokedexUiHandler:onlyEvolutionMove"));
} else if (m[0] === -1) {
this.showText(i18next.t("pokedexUiHandler:onlyRecallMove"));
} else if (m[0] <= 5) {
this.showText(i18next.t("pokedexUiHandler:onStarterSelectMove"));
} else {
this.showText(i18next.t("pokedexUiHandler:byLevelUpMove"));
}
}, },
}; };
return option; return option;

View File

@ -919,7 +919,7 @@ export default class PokedexUiHandler extends MessageUiHandler {
} else { } else {
error = true; error = true;
} }
} else if (button === Button.V) { } else if (button === Button.CYCLE_TERA) {
if (!this.filterTextMode && !this.showingTray) { if (!this.filterTextMode && !this.showingTray) {
this.cursorObj.setVisible(false); this.cursorObj.setVisible(false);
this.setSpecies(null); this.setSpecies(null);
@ -1170,9 +1170,6 @@ export default class PokedexUiHandler extends MessageUiHandler {
case SettingKeyboard.Button_Cycle_Shiny: case SettingKeyboard.Button_Cycle_Shiny:
iconPath = "R.png"; iconPath = "R.png";
break; break;
case SettingKeyboard.Button_Cycle_Variant:
iconPath = "V.png";
break;
case SettingKeyboard.Button_Cycle_Form: case SettingKeyboard.Button_Cycle_Form:
iconPath = "F.png"; iconPath = "F.png";
break; break;

View File

@ -93,9 +93,9 @@ export default class MoveTouchControlsHandler {
toolbar.innerHTML = ` toolbar.innerHTML = `
<div class="column"> <div class="column">
<div class="button-row"> <div class="button-row">
<div id="resetButton" class="button">${i18next.t("settings:reset")}</div> <div id="resetButton" class="button">${i18next.t("settings:touchReset")}</div>
<div id="saveButton" class="button">${i18next.t("settings:saveClose")}</div> <div id="saveButton" class="button">${i18next.t("settings:touchSaveClose")}</div>
<div id="cancelButton" class="button">${i18next.t("settings:cancel")}</div> <div id="cancelButton" class="button">${i18next.t("settings:touchCancel")}</div>
</div> </div>
<div class="info-row"> <div class="info-row">
<div class="orientation-label"> <div class="orientation-label">

View File

@ -56,6 +56,8 @@ import { getPassiveCandyCount, getValueReductionCandyCounts, getSameSpeciesEggCa
import { BooleanHolder, fixedInt, getLocalizedSpriteKey, isNullOrUndefined, NumberHolder, padInt, randIntRange, rgbHexToRgba, toReadableString } from "#app/utils"; import { BooleanHolder, fixedInt, getLocalizedSpriteKey, isNullOrUndefined, NumberHolder, padInt, randIntRange, rgbHexToRgba, toReadableString } from "#app/utils";
import type { Nature } from "#enums/nature"; import type { Nature } from "#enums/nature";
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
import { achvs } from "#app/system/achv";
import * as Utils from "../utils";
export type StarterSelectCallback = (starters: Starter[]) => void; export type StarterSelectCallback = (starters: Starter[]) => void;
@ -68,6 +70,7 @@ export interface Starter {
moveset?: StarterMoveset; moveset?: StarterMoveset;
pokerus: boolean; pokerus: boolean;
nickname?: string; nickname?: string;
teraType?: Type;
} }
interface LanguageSetting { interface LanguageSetting {
@ -212,6 +215,7 @@ interface SpeciesDetails {
abilityIndex?: number, abilityIndex?: number,
natureIndex?: number, natureIndex?: number,
forSeen?: boolean, // default = false forSeen?: boolean, // default = false
teraType?: Type,
} }
export default class StarterSelectUiHandler extends MessageUiHandler { export default class StarterSelectUiHandler extends MessageUiHandler {
@ -262,6 +266,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
private pokemonShinyIcon: Phaser.GameObjects.Sprite; private pokemonShinyIcon: Phaser.GameObjects.Sprite;
private pokemonPassiveDisabledIcon: Phaser.GameObjects.Sprite; private pokemonPassiveDisabledIcon: Phaser.GameObjects.Sprite;
private pokemonPassiveLockedIcon: Phaser.GameObjects.Sprite; private pokemonPassiveLockedIcon: Phaser.GameObjects.Sprite;
private teraIcon: Phaser.GameObjects.Sprite;
private activeTooltip: "ABILITY" | "PASSIVE" | "CANDY" | undefined; private activeTooltip: "ABILITY" | "PASSIVE" | "CANDY" | undefined;
private instructionsContainer: Phaser.GameObjects.Container; private instructionsContainer: Phaser.GameObjects.Container;
@ -271,12 +276,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
private abilityIconElement: Phaser.GameObjects.Sprite; private abilityIconElement: Phaser.GameObjects.Sprite;
private genderIconElement: Phaser.GameObjects.Sprite; private genderIconElement: Phaser.GameObjects.Sprite;
private natureIconElement: Phaser.GameObjects.Sprite; private natureIconElement: Phaser.GameObjects.Sprite;
private teraIconElement: Phaser.GameObjects.Sprite;
private goFilterIconElement: Phaser.GameObjects.Sprite; private goFilterIconElement: Phaser.GameObjects.Sprite;
private shinyLabel: Phaser.GameObjects.Text; private shinyLabel: Phaser.GameObjects.Text;
private formLabel: Phaser.GameObjects.Text; private formLabel: Phaser.GameObjects.Text;
private genderLabel: Phaser.GameObjects.Text; private genderLabel: Phaser.GameObjects.Text;
private abilityLabel: Phaser.GameObjects.Text; private abilityLabel: Phaser.GameObjects.Text;
private natureLabel: Phaser.GameObjects.Text; private natureLabel: Phaser.GameObjects.Text;
private teraLabel: Phaser.GameObjects.Text;
private goFilterLabel: Phaser.GameObjects.Text; private goFilterLabel: Phaser.GameObjects.Text;
private starterSelectMessageBox: Phaser.GameObjects.NineSlice; private starterSelectMessageBox: Phaser.GameObjects.NineSlice;
@ -292,6 +299,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
private dexAttrCursor: bigint = 0n; private dexAttrCursor: bigint = 0n;
private abilityCursor: number = -1; private abilityCursor: number = -1;
private natureCursor: number = -1; private natureCursor: number = -1;
private teraCursor: Type = Type.UNKNOWN;
private filterBarCursor: number = 0; private filterBarCursor: number = 0;
private starterMoveset: StarterMoveset | null; private starterMoveset: StarterMoveset | null;
private scrollCursor: number; private scrollCursor: number;
@ -304,6 +312,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
private starterAttr: bigint[] = []; private starterAttr: bigint[] = [];
private starterAbilityIndexes: number[] = []; private starterAbilityIndexes: number[] = [];
private starterNatures: Nature[] = []; private starterNatures: Nature[] = [];
private starterTeras: Type[] = [];
private starterMovesets: StarterMoveset[] = []; private starterMovesets: StarterMoveset[] = [];
private speciesStarterDexEntry: DexEntry | null; private speciesStarterDexEntry: DexEntry | null;
private speciesStarterMoves: Moves[]; private speciesStarterMoves: Moves[];
@ -312,6 +321,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
private canCycleGender: boolean; private canCycleGender: boolean;
private canCycleAbility: boolean; private canCycleAbility: boolean;
private canCycleNature: boolean; private canCycleNature: boolean;
private canCycleTera: boolean;
private assetLoadCancelled: BooleanHolder | null; private assetLoadCancelled: BooleanHolder | null;
public cursorObj: Phaser.GameObjects.Image; public cursorObj: Phaser.GameObjects.Image;
@ -823,6 +833,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.starterSelectContainer.add(this.pokemonEggMovesContainer); this.starterSelectContainer.add(this.pokemonEggMovesContainer);
this.teraIcon = globalScene.add.sprite(85, 63, "button_tera");
this.teraIcon.setName("terrastallize-icon");
this.teraIcon.setFrame("fire");
this.starterSelectContainer.add(this.teraIcon);
// The font size should be set per language // The font size should be set per language
const instructionTextSize = textSettings.instructionTextSize; const instructionTextSize = textSettings.instructionTextSize;
@ -867,6 +882,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.natureLabel = addTextObject(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleNature"), TextStyle.PARTY, { fontSize: instructionTextSize }); this.natureLabel = addTextObject(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleNature"), TextStyle.PARTY, { fontSize: instructionTextSize });
this.natureLabel.setName("text-nature-label"); this.natureLabel.setName("text-nature-label");
this.teraIconElement = new Phaser.GameObjects.Sprite(globalScene, this.instructionRowX, this.instructionRowY, "keyboard", "V.png");
this.teraIconElement.setName("sprite-tera-icon-element");
this.teraIconElement.setScale(0.675);
this.teraIconElement.setOrigin(0.0, 0.0);
this.teraLabel = addTextObject(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleTera"), TextStyle.PARTY, { fontSize: instructionTextSize });
this.teraLabel.setName("text-tera-label");
this.goFilterIconElement = new Phaser.GameObjects.Sprite(globalScene, this.filterInstructionRowX, this.filterInstructionRowY, "keyboard", "C.png"); this.goFilterIconElement = new Phaser.GameObjects.Sprite(globalScene, this.filterInstructionRowX, this.filterInstructionRowY, "keyboard", "C.png");
this.goFilterIconElement.setName("sprite-goFilter-icon-element"); this.goFilterIconElement.setName("sprite-goFilter-icon-element");
this.goFilterIconElement.setScale(0.675); this.goFilterIconElement.setScale(0.675);
@ -1497,6 +1519,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
const props = globalScene.gameData.getSpeciesDexAttrProps(randomSpecies, dexAttr); const props = globalScene.gameData.getSpeciesDexAttrProps(randomSpecies, dexAttr);
const abilityIndex = this.abilityCursor; const abilityIndex = this.abilityCursor;
const nature = this.natureCursor as unknown as Nature; const nature = this.natureCursor as unknown as Nature;
const teraType = this.teraCursor;
const moveset = this.starterMoveset?.slice(0) as StarterMoveset; const moveset = this.starterMoveset?.slice(0) as StarterMoveset;
const starterCost = globalScene.gameData.getSpeciesStarterValue(randomSpecies.speciesId); const starterCost = globalScene.gameData.getSpeciesStarterValue(randomSpecies.speciesId);
const speciesForm = getPokemonSpeciesForm(randomSpecies.speciesId, props.formIndex); const speciesForm = getPokemonSpeciesForm(randomSpecies.speciesId, props.formIndex);
@ -1505,7 +1528,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
.loadAssets(props.female, props.formIndex, props.shiny, props.variant, true) .loadAssets(props.female, props.formIndex, props.shiny, props.variant, true)
.then(() => { .then(() => {
if (this.tryUpdateValue(starterCost, true)) { if (this.tryUpdateValue(starterCost, true)) {
this.addToParty(randomSpecies, dexAttr, abilityIndex, nature, moveset, true); this.addToParty(randomSpecies, dexAttr, abilityIndex, nature, moveset, teraType, true);
ui.playSelect(); ui.playSelect();
} }
}); });
@ -1585,7 +1608,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
const cursorObj = this.starterCursorObjs[this.starterSpecies.length]; const cursorObj = this.starterCursorObjs[this.starterSpecies.length];
cursorObj.setVisible(true); cursorObj.setVisible(true);
cursorObj.setPosition(this.cursorObj.x, this.cursorObj.y); cursorObj.setPosition(this.cursorObj.x, this.cursorObj.y);
this.addToParty(this.lastSpecies, this.dexAttrCursor, this.abilityCursor, this.natureCursor as unknown as Nature, this.starterMoveset?.slice(0) as StarterMoveset); this.addToParty(this.lastSpecies, this.dexAttrCursor, this.abilityCursor, this.natureCursor as unknown as Nature, this.starterMoveset?.slice(0) as StarterMoveset, this.teraCursor);
ui.playSelect(); ui.playSelect();
} else { } else {
ui.playError(); // this should be redundant as there is now a trigger for when a pokemon can't be added to party ui.playError(); // this should be redundant as there is now a trigger for when a pokemon can't be added to party
@ -1961,15 +1984,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
yOffset: 47 yOffset: 47
}); });
}; };
if (!pokemonPrevolutions.hasOwnProperty(this.lastSpecies.speciesId)) {
options.push({
label: i18next.t("starterSelectUiHandler:useCandies"),
handler: () => {
ui.setMode(Mode.STARTER_SELECT).then(() => showUseCandies());
return true;
}
});
}
options.push({ options.push({
label: i18next.t("menuUiHandler:POKEDEX"), label: i18next.t("menuUiHandler:POKEDEX"),
handler: () => { handler: () => {
@ -1985,6 +1999,15 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
return true; return true;
} }
}); });
if (!pokemonPrevolutions.hasOwnProperty(this.lastSpecies.speciesId)) {
options.push({
label: i18next.t("starterSelectUiHandler:useCandies"),
handler: () => {
ui.setMode(Mode.STARTER_SELECT).then(() => showUseCandies());
return true;
}
});
}
options.push({ options.push({
label: i18next.t("menu:cancel"), label: i18next.t("menu:cancel"),
handler: () => { handler: () => {
@ -2066,7 +2089,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
} }
} while (newFormIndex !== props.formIndex); } while (newFormIndex !== props.formIndex);
starterAttributes.form = newFormIndex; // store the selected form starterAttributes.form = newFormIndex; // store the selected form
this.setSpeciesDetails(this.lastSpecies, { formIndex: newFormIndex }); starterAttributes.tera = this.lastSpecies.forms[newFormIndex].type1;
this.setSpeciesDetails(this.lastSpecies, { formIndex: newFormIndex, teraType: starterAttributes.tera });
success = true; success = true;
} }
break; break;
@ -2125,6 +2149,19 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
success = true; success = true;
} }
break; break;
case Button.CYCLE_TERA:
if (this.canCycleTera) {
const speciesForm = getPokemonSpeciesForm(this.lastSpecies.speciesId, starterAttributes.form ?? 0);
if (speciesForm.type1 === this.teraCursor && !Utils.isNullOrUndefined(speciesForm.type2)) {
starterAttributes.tera = speciesForm.type2!;
this.setSpeciesDetails(this.lastSpecies, { teraType: speciesForm.type2! });
} else {
starterAttributes.tera = speciesForm.type1;
this.setSpeciesDetails(this.lastSpecies, { teraType: speciesForm.type1 });
}
success = true;
}
break;
case Button.UP: case Button.UP:
if (!this.starterIconsCursorObj.visible) { if (!this.starterIconsCursorObj.visible) {
if (currentRow > 0) { if (currentRow > 0) {
@ -2289,7 +2326,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
return [ isDupe, removeIndex ]; return [ isDupe, removeIndex ];
} }
addToParty(species: PokemonSpecies, dexAttr: bigint, abilityIndex: number, nature: Nature, moveset: StarterMoveset, randomSelection: boolean = false) { addToParty(species: PokemonSpecies, dexAttr: bigint, abilityIndex: number, nature: Nature, moveset: StarterMoveset, teraType: Type, randomSelection: boolean = false) {
const props = globalScene.gameData.getSpeciesDexAttrProps(species, dexAttr); const props = globalScene.gameData.getSpeciesDexAttrProps(species, dexAttr);
this.starterIcons[this.starterSpecies.length].setTexture(species.getIconAtlasKey(props.formIndex, props.shiny, props.variant)); this.starterIcons[this.starterSpecies.length].setTexture(species.getIconAtlasKey(props.formIndex, props.shiny, props.variant));
this.starterIcons[this.starterSpecies.length].setFrame(species.getIconId(props.female, props.formIndex, props.shiny, props.variant)); this.starterIcons[this.starterSpecies.length].setFrame(species.getIconId(props.female, props.formIndex, props.shiny, props.variant));
@ -2299,6 +2336,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.starterAttr.push(dexAttr); this.starterAttr.push(dexAttr);
this.starterAbilityIndexes.push(abilityIndex); this.starterAbilityIndexes.push(abilityIndex);
this.starterNatures.push(nature); this.starterNatures.push(nature);
this.starterTeras.push(teraType);
this.starterMovesets.push(moveset); this.starterMovesets.push(moveset);
if (this.speciesLoaded.get(species.speciesId) || randomSelection ) { if (this.speciesLoaded.get(species.speciesId) || randomSelection ) {
getPokemonSpeciesForm(species.speciesId, props.formIndex).cry(); getPokemonSpeciesForm(species.speciesId, props.formIndex).cry();
@ -2379,6 +2417,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
case SettingKeyboard.Button_Cycle_Nature: case SettingKeyboard.Button_Cycle_Nature:
iconPath = "N.png"; iconPath = "N.png";
break; break;
case SettingKeyboard.Button_Cycle_Tera:
iconPath = "V.png";
break;
case SettingKeyboard.Button_Stats: case SettingKeyboard.Button_Stats:
iconPath = "C.png"; iconPath = "C.png";
break; break;
@ -2459,6 +2500,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
if (this.canCycleNature) { if (this.canCycleNature) {
this.updateButtonIcon(SettingKeyboard.Button_Cycle_Nature, gamepadType, this.natureIconElement, this.natureLabel); this.updateButtonIcon(SettingKeyboard.Button_Cycle_Nature, gamepadType, this.natureIconElement, this.natureLabel);
} }
if (this.canCycleTera) {
this.updateButtonIcon(SettingKeyboard.Button_Cycle_Tera, gamepadType, this.teraIconElement, this.teraLabel);
}
} }
// if filter mode is inactivated and gamepadType is not undefined, update the button icons // if filter mode is inactivated and gamepadType is not undefined, update the button icons
@ -2876,6 +2920,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.dexAttrCursor = species ? this.getCurrentDexProps(species.speciesId) : 0n; this.dexAttrCursor = species ? this.getCurrentDexProps(species.speciesId) : 0n;
this.abilityCursor = species ? globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species) : 0; this.abilityCursor = species ? globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species) : 0;
this.natureCursor = species ? globalScene.gameData.getSpeciesDefaultNature(species) : 0; this.natureCursor = species ? globalScene.gameData.getSpeciesDefaultNature(species) : 0;
this.teraCursor = species ? species.type1 : Type.UNKNOWN;
if (!species && globalScene.ui.getTooltip().visible) { if (!species && globalScene.ui.getTooltip().visible) {
globalScene.ui.hideTooltip(); globalScene.ui.hideTooltip();
@ -2894,6 +2939,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
// load default ability from stater save data, if set // load default ability from stater save data, if set
this.abilityCursor = starterAttributes.ability; this.abilityCursor = starterAttributes.ability;
} }
if (starterAttributes?.tera) {
// load default tera from starter save data, if set
this.teraCursor = starterAttributes.tera;
}
if (this.statsMode) { if (this.statsMode) {
if (this.speciesStarterDexEntry?.caughtAttr) { if (this.speciesStarterDexEntry?.caughtAttr) {
@ -3035,7 +3084,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
female: props.female, female: props.female,
variant: props.variant, variant: props.variant,
abilityIndex: this.starterAbilityIndexes[starterIndex], abilityIndex: this.starterAbilityIndexes[starterIndex],
natureIndex: this.starterNatures[starterIndex] natureIndex: this.starterNatures[starterIndex],
teraType: this.starterTeras[starterIndex]
}); });
} else { } else {
const defaultDexAttr = this.getCurrentDexProps(species.speciesId); const defaultDexAttr = this.getCurrentDexProps(species.speciesId);
@ -3083,6 +3133,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonCaughtHatchedContainer.setVisible(false); this.pokemonCaughtHatchedContainer.setVisible(false);
this.pokemonCandyContainer.setVisible(false); this.pokemonCandyContainer.setVisible(false);
this.pokemonFormText.setVisible(false); this.pokemonFormText.setVisible(false);
this.teraIcon.setVisible(false);
const defaultDexAttr = globalScene.gameData.getSpeciesDefaultDexAttr(species, true, true); const defaultDexAttr = globalScene.gameData.getSpeciesDefaultDexAttr(species, true, true);
const defaultAbilityIndex = globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species); const defaultAbilityIndex = globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species);
@ -3117,6 +3168,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonCaughtHatchedContainer.setVisible(false); this.pokemonCaughtHatchedContainer.setVisible(false);
this.pokemonCandyContainer.setVisible(false); this.pokemonCandyContainer.setVisible(false);
this.pokemonFormText.setVisible(false); this.pokemonFormText.setVisible(false);
this.teraIcon.setVisible(false);
this.setSpeciesDetails(species!, { // TODO: is this bang correct? this.setSpeciesDetails(species!, { // TODO: is this bang correct?
shiny: false, shiny: false,
@ -3131,7 +3183,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
} }
setSpeciesDetails(species: PokemonSpecies, options: SpeciesDetails = {}): void { setSpeciesDetails(species: PokemonSpecies, options: SpeciesDetails = {}): void {
let { shiny, formIndex, female, variant, abilityIndex, natureIndex } = options; let { shiny, formIndex, female, variant, abilityIndex, natureIndex, teraType } = options;
const forSeen: boolean = options.forSeen ?? false; const forSeen: boolean = options.forSeen ?? false;
const oldProps = species ? globalScene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor) : null; const oldProps = species ? globalScene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor) : null;
const oldAbilityIndex = this.abilityCursor > -1 ? this.abilityCursor : globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species); const oldAbilityIndex = this.abilityCursor > -1 ? this.abilityCursor : globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species);
@ -3139,6 +3191,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.dexAttrCursor = 0n; this.dexAttrCursor = 0n;
this.abilityCursor = -1; this.abilityCursor = -1;
this.natureCursor = -1; this.natureCursor = -1;
this.teraCursor = Type.UNKNOWN;
// We will only update the sprite if there is a change to form, shiny/variant // We will only update the sprite if there is a change to form, shiny/variant
// or gender for species with gender sprite differences // or gender for species with gender sprite differences
const shouldUpdateSprite = (species?.genderDiffs && !isNullOrUndefined(female)) const shouldUpdateSprite = (species?.genderDiffs && !isNullOrUndefined(female))
@ -3168,6 +3221,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.dexAttrCursor |= globalScene.gameData.getFormAttr(formIndex !== undefined ? formIndex : (formIndex = oldProps!.formIndex)); // TODO: is this bang correct? this.dexAttrCursor |= globalScene.gameData.getFormAttr(formIndex !== undefined ? formIndex : (formIndex = oldProps!.formIndex)); // TODO: is this bang correct?
this.abilityCursor = abilityIndex !== undefined ? abilityIndex : (abilityIndex = oldAbilityIndex); this.abilityCursor = abilityIndex !== undefined ? abilityIndex : (abilityIndex = oldAbilityIndex);
this.natureCursor = natureIndex !== undefined ? natureIndex : (natureIndex = oldNatureIndex); this.natureCursor = natureIndex !== undefined ? natureIndex : (natureIndex = oldNatureIndex);
this.teraCursor = !Utils.isNullOrUndefined(teraType) ? teraType : (teraType = species.type1);
const [ isInParty, partyIndex ]: [boolean, number] = this.isInParty(species); // we use this to firstly check if the pokemon is in the party, and if so, to get the party index in order to update the icon image const [ isInParty, partyIndex ]: [boolean, number] = this.isInParty(species); // we use this to firstly check if the pokemon is in the party, and if so, to get the party index in order to update the icon image
if (isInParty) { if (isInParty) {
this.updatePartyIcon(species, partyIndex); this.updatePartyIcon(species, partyIndex);
@ -3179,6 +3233,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonPassiveText.setVisible(false); this.pokemonPassiveText.setVisible(false);
this.pokemonPassiveDisabledIcon.setVisible(false); this.pokemonPassiveDisabledIcon.setVisible(false);
this.pokemonPassiveLockedIcon.setVisible(false); this.pokemonPassiveLockedIcon.setVisible(false);
this.teraIcon.setVisible(false);
if (this.assetLoadCancelled) { if (this.assetLoadCancelled) {
this.assetLoadCancelled.value = true; this.assetLoadCancelled.value = true;
@ -3230,6 +3285,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.starterAttr[starterIndex] = this.dexAttrCursor; this.starterAttr[starterIndex] = this.dexAttrCursor;
this.starterAbilityIndexes[starterIndex] = this.abilityCursor; this.starterAbilityIndexes[starterIndex] = this.abilityCursor;
this.starterNatures[starterIndex] = this.natureCursor; this.starterNatures[starterIndex] = this.natureCursor;
this.starterTeras[starterIndex] = this.teraCursor;
} }
const assetLoadCancelled = new BooleanHolder(false); const assetLoadCancelled = new BooleanHolder(false);
@ -3288,7 +3344,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.canCycleForm = species.forms.filter(f => f.isStarterSelectable || !pokemonFormChanges[species.speciesId]?.find(fc => fc.formKey)) this.canCycleForm = species.forms.filter(f => f.isStarterSelectable || !pokemonFormChanges[species.speciesId]?.find(fc => fc.formKey))
.map((_, f) => dexEntry.caughtAttr & globalScene.gameData.getFormAttr(f)).filter(f => f).length > 1; .map((_, f) => dexEntry.caughtAttr & globalScene.gameData.getFormAttr(f)).filter(f => f).length > 1;
this.canCycleNature = globalScene.gameData.getNaturesForAttr(dexEntry.natureAttr).length > 1; this.canCycleNature = globalScene.gameData.getNaturesForAttr(dexEntry.natureAttr).length > 1;
this.canCycleTera = globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id) && !Utils.isNullOrUndefined(getPokemonSpeciesForm(species.speciesId, formIndex ?? 0).type2);
} }
if (dexEntry.caughtAttr && species.malePercent !== null) { if (dexEntry.caughtAttr && species.malePercent !== null) {
@ -3412,10 +3468,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonFormText.setText(formText); this.pokemonFormText.setText(formText);
this.setTypeIcons(speciesForm.type1, speciesForm.type2); this.setTypeIcons(speciesForm.type1, speciesForm.type2);
this.teraIcon.setFrame(Type[this.teraCursor].toLowerCase());
this.teraIcon.setVisible(!this.statsMode && globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id));
} else { } else {
this.pokemonAbilityText.setText(""); this.pokemonAbilityText.setText("");
this.pokemonPassiveText.setText(""); this.pokemonPassiveText.setText("");
this.pokemonNatureText.setText(""); this.pokemonNatureText.setText("");
this.teraIcon.setVisible(false);
this.setTypeIcons(null, null); this.setTypeIcons(null, null);
} }
} else { } else {
@ -3426,6 +3486,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonAbilityText.setText(""); this.pokemonAbilityText.setText("");
this.pokemonPassiveText.setText(""); this.pokemonPassiveText.setText("");
this.pokemonNatureText.setText(""); this.pokemonNatureText.setText("");
this.teraIcon.setVisible(false);
this.setTypeIcons(null, null); this.setTypeIcons(null, null);
} }
@ -3479,6 +3540,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.starterAttr.splice(index, 1); this.starterAttr.splice(index, 1);
this.starterAbilityIndexes.splice(index, 1); this.starterAbilityIndexes.splice(index, 1);
this.starterNatures.splice(index, 1); this.starterNatures.splice(index, 1);
this.starterTeras.splice(index, 1);
this.starterMovesets.splice(index, 1); this.starterMovesets.splice(index, 1);
for (let s = 0; s < this.starterSpecies.length; s++) { for (let s = 0; s < this.starterSpecies.length; s++) {
@ -3690,6 +3752,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
abilityIndex: thisObj.starterAbilityIndexes[i], abilityIndex: thisObj.starterAbilityIndexes[i],
passive: !(globalScene.gameData.starterData[starterSpecies.speciesId].passiveAttr ^ (PassiveAttr.ENABLED | PassiveAttr.UNLOCKED)), passive: !(globalScene.gameData.starterData[starterSpecies.speciesId].passiveAttr ^ (PassiveAttr.ENABLED | PassiveAttr.UNLOCKED)),
nature: thisObj.starterNatures[i] as Nature, nature: thisObj.starterNatures[i] as Nature,
teraType: thisObj.starterTeras[i] as Type,
moveset: thisObj.starterMovesets[i], moveset: thisObj.starterMovesets[i],
pokerus: thisObj.pokerusSpecies.includes(starterSpecies), pokerus: thisObj.pokerusSpecies.includes(starterSpecies),
nickname: thisObj.starterPreferences[starterSpecies.speciesId]?.nickname, nickname: thisObj.starterPreferences[starterSpecies.speciesId]?.nickname,
@ -3816,6 +3879,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.abilityLabel.setVisible(false); this.abilityLabel.setVisible(false);
this.natureIconElement.setVisible(false); this.natureIconElement.setVisible(false);
this.natureLabel.setVisible(false); this.natureLabel.setVisible(false);
this.teraIconElement.setVisible(false);
this.teraLabel.setVisible(false);
this.goFilterIconElement.setVisible(false); this.goFilterIconElement.setVisible(false);
this.goFilterLabel.setVisible(false); this.goFilterLabel.setVisible(false);
} }

View File

@ -225,7 +225,7 @@ export default class SummaryUiHandler extends UiHandler {
this.summaryContainer.add(this.championRibbon); this.summaryContainer.add(this.championRibbon);
this.championRibbon.setVisible(false); this.championRibbon.setVisible(false);
this.levelText = addTextObject(36, -17, "", TextStyle.SUMMARY_ALT); this.levelText = addTextObject(24, -17, "", TextStyle.SUMMARY_ALT);
this.levelText.setOrigin(0, 1); this.levelText.setOrigin(0, 1);
this.summaryContainer.add(this.levelText); this.summaryContainer.add(this.levelText);
@ -413,7 +413,7 @@ export default class SummaryUiHandler extends UiHandler {
} }
this.pokeball.setFrame(getPokeballAtlasKey(this.pokemon.pokeball)); this.pokeball.setFrame(getPokeballAtlasKey(this.pokemon.pokeball));
this.levelText.setText(this.pokemon.level.toString()); this.levelText.setText(`${i18next.t("pokemonSummary:lv")}${this.pokemon.level.toString()}`);
this.genderText.setText(getGenderSymbol(this.pokemon.getGender(true))); this.genderText.setText(getGenderSymbol(this.pokemon.getGender(true)));
this.genderText.setColor(getGenderColor(this.pokemon.getGender(true))); this.genderText.setColor(getGenderColor(this.pokemon.getGender(true)));
this.genderText.setShadowColor(getGenderColor(this.pokemon.getGender(true), true)); this.genderText.setShadowColor(getGenderColor(this.pokemon.getGender(true), true));
@ -756,7 +756,7 @@ export default class SummaryUiHandler extends UiHandler {
trainerText.setOrigin(0, 0); trainerText.setOrigin(0, 0);
profileContainer.add(trainerText); profileContainer.add(trainerText);
const trainerIdText = addTextObject(174, 12, globalScene.gameData.trainerId.toString(), TextStyle.SUMMARY_ALT); const trainerIdText = addTextObject(141, 12, `${i18next.t("pokemonSummary:idNo")}${globalScene.gameData.trainerId.toString()}`, TextStyle.SUMMARY_ALT);
trainerIdText.setOrigin(0, 0); trainerIdText.setOrigin(0, 0);
profileContainer.add(trainerIdText); profileContainer.add(trainerIdText);