diff --git a/src/battle-scene.ts b/src/battle-scene.ts index a1136699d..e3e1abd66 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -59,6 +59,8 @@ import { initTouchControls } from './touch-controls'; export const bypassLogin = false; export const SEED_OVERRIDE = ''; +export const STARTER_SPECIES_OVERRIDE = 0; +export const STARTER_FORM_OVERRIDE = 0; export const STARTING_LEVEL_OVERRIDE = 0; export const STARTING_WAVE_OVERRIDE = 0; export const STARTING_BIOME_OVERRIDE = Biome.TOWN; diff --git a/src/data/ability.ts b/src/data/ability.ts index ecea04f07..8a068b49c 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2354,7 +2354,7 @@ export function initAbilities() { new Ability(Abilities.REFRIGERATE, "Refrigerate (N)", "Normal-type moves become Ice-type moves. The power of those moves is boosted a little.", 6), new Ability(Abilities.SWEET_VEIL, "Sweet Veil (N)", "Prevents itself and ally Pokémon from falling asleep.", 6) .ignorable(), - new Ability(Abilities.STANCE_CHANGE, "Stance Change (N)", "The Pokémon changes its form to Blade Forme when it uses an attack move and changes to Shield Forme when it uses King's Shield.", 6) + new Ability(Abilities.STANCE_CHANGE, "Stance Change", "The Pokémon changes its form to Blade Forme when it uses an attack move and changes to Shield Forme when it uses King's Shield.", 6) .attr(ProtectAbilityAbAttr), new Ability(Abilities.GALE_WINGS, "Gale Wings (N)", "Gives priority to Flying-type moves when the Pokémon's HP is full.", 6), new Ability(Abilities.MEGA_LAUNCHER, "Mega Launcher", "Powers up aura and pulse moves.", 6) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 3943674e1..9e935ce51 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1,5 +1,5 @@ import { CommonAnim, CommonBattleAnim } from "./battle-anims"; -import { CommonAnimPhase, MoveEffectPhase, MovePhase, PokemonHealPhase, ShowAbilityPhase } from "../phases"; +import { CommonAnimPhase, MoveEffectPhase, MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases"; import { getPokemonMessage } from "../messages"; import Pokemon, { MoveResult, HitResult } from "../field/pokemon"; import { Stat, getStatName } from "./pokemon-stat"; @@ -12,6 +12,7 @@ import { Abilities, FlinchEffectAbAttr, applyAbAttrs } from "./ability"; import { BattlerTagType } from "./enums/battler-tag-type"; import { TerrainType } from "./terrain"; import { WeatherType } from "./weather"; +import { BattleStat } from "./battle-stat"; export enum BattlerTagLapseType { FAINT, @@ -617,6 +618,32 @@ export class ProtectedTag extends BattlerTag { } } +export class ContactStatChangeProtectedTag extends ProtectedTag { + private stat: BattleStat; + private levels: integer; + + constructor(sourceMove: Moves, tagType: BattlerTagType, stat: BattleStat, levels: integer) { + super(sourceMove, tagType); + + this.stat = stat; + this.levels = levels; + } + + lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { + const ret = super.lapse(pokemon, lapseType); + + if (lapseType === BattlerTagLapseType.CUSTOM) { + const effectPhase = pokemon.scene.getCurrentPhase(); + if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) { + const attacker = effectPhase.getPokemon(); + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, attacker.getBattlerIndex(), true, [ this.stat ], this.levels)); + } + } + + return ret; + } +} + export class ContactPoisonProtectedTag extends ProtectedTag { constructor(sourceMove: Moves) { super(sourceMove, BattlerTagType.BANEFUL_BUNKER); @@ -954,6 +981,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc return new ThunderCageTag(turnCount, sourceId); case BattlerTagType.PROTECTED: return new ProtectedTag(sourceMove); + case BattlerTagType.KINGS_SHIELD: + return new ContactStatChangeProtectedTag(sourceMove, tagType, BattleStat.ATK, -1); case BattlerTagType.BANEFUL_BUNKER: return new ContactPoisonProtectedTag(sourceMove); case BattlerTagType.BURNING_BULWARK: diff --git a/src/data/enums/battler-tag-type.ts b/src/data/enums/battler-tag-type.ts index 90e1e5404..30bb824ff 100644 --- a/src/data/enums/battler-tag-type.ts +++ b/src/data/enums/battler-tag-type.ts @@ -23,6 +23,7 @@ export enum BattlerTagType { MAGMA_STORM = "MAGMA_STORM", THUNDER_CAGE = "THUNDER_CAGE", PROTECTED = "PROTECTED", + KINGS_SHIELD = "KINGS_SHIELD", BANEFUL_BUNKER = "BANEFUL_BUNKER", BURNING_BULWARK = "BURNING_BULWARK", ENDURING = "ENDURING", diff --git a/src/data/move.ts b/src/data/move.ts index ef413c5f3..1f1c02c1f 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -111,6 +111,10 @@ export default class Move { return this.attrs.filter(a => a instanceof attrType); } + findAttr(attrPredicate: (attr: MoveAttr) => boolean): MoveAttr { + return this.attrs.find(attrPredicate); + } + attr MoveAttr>(AttrType: T, ...args: ConstructorParameters): this { const attr = new AttrType(...args); this.attrs.push(attr); @@ -990,7 +994,7 @@ export class HealStatusEffectAttr extends MoveEffectAttr { return false; const pokemon = this.selfTarget ? user : target; - if (pokemon.status && this.effects.indexOf(pokemon.status.effect) > -1) { + if (pokemon.status && this.effects.includes(pokemon.status.effect)) { pokemon.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectHealText(pokemon.status.effect))); pokemon.resetStatus(); pokemon.updateInfo(); @@ -1001,6 +1005,10 @@ export class HealStatusEffectAttr extends MoveEffectAttr { return false; } + isOfEffect(effect: StatusEffect): boolean { + return this.effects.includes(effect); + } + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { return user.status ? 10 : 0; } @@ -3087,7 +3095,7 @@ export function initMoves() { .attr(AddBattlerTagAttr, BattlerTagType.NIGHTMARE) .condition((user, target, move) => target.status?.effect === StatusEffect.SLEEP), new AttackMove(Moves.FLAME_WHEEL, "Flame Wheel", Type.FIRE, MoveCategory.PHYSICAL, 60, 100, 25, "The user cloaks itself in fire and charges at the target. This may also leave the target with a burn.", 10, 0, 2) - .attr(HealStatusEffectAttr, false, StatusEffect.FREEZE) + .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) .attr(StatusEffectAttr, StatusEffect.BURN), new AttackMove(Moves.SNORE, "Snore", Type.NORMAL, MoveCategory.SPECIAL, 50, 100, 15, "This attack can be used only if the user is asleep. The harsh noise may also make the target flinch.", 30, 0, 2) .attr(BypassSleepAttr) @@ -3213,7 +3221,7 @@ export function initMoves() { .attr(HpSplitAttr) .condition(failOnBossCondition), new AttackMove(Moves.SACRED_FIRE, "Sacred Fire", Type.FIRE, MoveCategory.PHYSICAL, 100, 95, 5, "The target is razed with a mystical fire of great intensity. This may also leave the target with a burn.", 50, 0, 2) - .attr(HealStatusEffectAttr, false, StatusEffect.FREEZE) + .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) .attr(StatusEffectAttr, StatusEffect.BURN) .makesContact(false), new AttackMove(Moves.MAGNITUDE, "Magnitude", Type.GROUND, MoveCategory.PHYSICAL, -1, 100, 30, "The user attacks everything around it with a ground-shaking quake. Its power varies.", -1, 0, 2) @@ -3297,7 +3305,7 @@ export function initMoves() { new AttackMove(Moves.SPIT_UP, "Spit Up (N)", Type.NORMAL, MoveCategory.SPECIAL, -1, 100, 10, "The power stored using the move Stockpile is released at once in an attack. The more power is stored, the greater the move's power.", -1, 0, 3), new SelfStatusMove(Moves.SWALLOW, "Swallow (N)", Type.NORMAL, -1, 10, "The power stored using the move Stockpile is absorbed by the user to heal its HP. Storing more power heals more HP.", -1, 0, 3), new AttackMove(Moves.HEAT_WAVE, "Heat Wave", Type.FIRE, MoveCategory.SPECIAL, 95, 90, 10, "The user attacks by exhaling hot breath on opposing Pokémon. This may also leave those Pokémon with a burn.", 10, 0, 3) - .attr(HealStatusEffectAttr, false, StatusEffect.FREEZE) + .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) .attr(StatusEffectAttr, StatusEffect.BURN) .windMove() .target(MoveTarget.ALL_NEAR_ENEMIES), @@ -3321,7 +3329,7 @@ export function initMoves() { .ignoresVirtual(), new AttackMove(Moves.SMELLING_SALTS, "Smelling Salts", Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, "This attack's power is doubled when used on a target with paralysis. This also cures the target's paralysis, however.", -1, 0, 3) .attr(MovePowerMultiplierAttr, (user, target, move) => target.status?.effect === StatusEffect.PARALYSIS ? 2 : 1) - .attr(HealStatusEffectAttr, false, StatusEffect.PARALYSIS), + .attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS), new SelfStatusMove(Moves.FOLLOW_ME, "Follow Me (N)", Type.NORMAL, -1, 20, "The user draws attention to itself, making all targets take aim only at the user.", -1, 2, 3), new StatusMove(Moves.NATURE_POWER, "Nature Power (N)", Type.NORMAL, -1, 20, "This attack makes use of nature's power. Its effects vary depending on the user's environment.", -1, 0, 3), new SelfStatusMove(Moves.CHARGE, "Charge (P)", Type.ELECTRIC, -1, 20, "The user boosts the power of the Electric move it uses on the next turn. This also raises the user's Sp. Def stat.", -1, 0, 3) @@ -3428,7 +3436,7 @@ export function initMoves() { .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(Moves.OVERHEAT, "Overheat", Type.FIRE, MoveCategory.SPECIAL, 130, 90, 5, "The user attacks the target at full power. The attack's recoil harshly lowers the user's Sp. Atk stat.", 100, 0, 3) .attr(StatChangeAttr, BattleStat.SPATK, -2, true) - .attr(HealStatusEffectAttr, false, StatusEffect.FREEZE), + .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE), new StatusMove(Moves.ODOR_SLEUTH, "Odor Sleuth (N)", Type.NORMAL, -1, 40, "Enables a Ghost-type target to be hit by Normal- and Fighting-type attacks. This also enables an evasive target to be hit.", -1, 0, 3), new AttackMove(Moves.ROCK_TOMB, "Rock Tomb", Type.ROCK, MoveCategory.PHYSICAL, 60, 95, 15, "Boulders are hurled at the target. This also lowers the target's Speed stat by preventing its movement.", 100, 0, 3) .attr(StatChangeAttr, BattleStat.SPD, -1) @@ -3617,7 +3625,7 @@ export function initMoves() { new SelfStatusMove(Moves.MAGNET_RISE, "Magnet Rise (N)", Type.ELECTRIC, -1, 10, "The user levitates using electrically generated magnetism for five turns.", -1, 0, 4), new AttackMove(Moves.FLARE_BLITZ, "Flare Blitz", Type.FIRE, MoveCategory.PHYSICAL, 120, 100, 15, "The user cloaks itself in fire and charges the target. This also damages the user quite a lot. This attack may leave the target with a burn.", 10, 0, 4) .attr(RecoilAttr) - .attr(HealStatusEffectAttr, false, StatusEffect.FREEZE) + .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) .attr(StatusEffectAttr, StatusEffect.BURN) .condition(failOnGravityCondition), new AttackMove(Moves.FORCE_PALM, "Force Palm", Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, "The target is attacked with a shock wave. This may also leave the target with paralysis.", 30, 0, 4) @@ -3868,6 +3876,7 @@ export function initMoves() { .ignoresProtect(), new AttackMove(Moves.SCALD, "Scald", Type.WATER, MoveCategory.SPECIAL, 80, 100, 15, "The user shoots boiling hot water at its target. This may also leave the target with a burn.", 30, 0, 5) .attr(HealStatusEffectAttr, false, StatusEffect.FREEZE) + .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) .attr(StatusEffectAttr, StatusEffect.BURN), new SelfStatusMove(Moves.SHELL_SMASH, "Shell Smash", Type.NORMAL, -1, 15, "The user breaks its shell, which lowers Defense and Sp. Def stats but sharply raises its Attack, Sp. Atk, and Speed stats.", -1, 0, 5) .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK, BattleStat.SPD ], 2, true) @@ -3995,7 +4004,7 @@ export function initMoves() { new AttackMove(Moves.V_CREATE, "V-create", Type.FIRE, MoveCategory.PHYSICAL, 180, 95, 5, "With a hot flame on its forehead, the user hurls itself at its target. This lowers the user's Defense, Sp. Def, and Speed stats.", 100, 0, 5) .attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF, BattleStat.SPD ], -1, true), new AttackMove(Moves.FUSION_FLARE, "Fusion Flare (P)", Type.FIRE, MoveCategory.SPECIAL, 100, 100, 5, "The user brings down a giant flame. This move's power is increased when influenced by an enormous lightning bolt.", -1, 0, 5) - .attr(HealStatusEffectAttr, false, StatusEffect.FREEZE), + .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE), new AttackMove(Moves.FUSION_BOLT, "Fusion Bolt (P)", Type.ELECTRIC, MoveCategory.PHYSICAL, 100, 100, 5, "The user throws down a giant lightning bolt. This move's power is increased when influenced by an enormous flame.", -1, 0, 5) .makesContact(false), new AttackMove(Moves.FLYING_PRESS, "Flying Press (P)", Type.FIGHTING, MoveCategory.PHYSICAL, 100, 95, 10, "The user dives down onto the target from the sky. This move is Fighting and Flying type simultaneously.", -1, 0, 6), @@ -4058,8 +4067,8 @@ export function initMoves() { .target(MoveTarget.ALL_NEAR_OTHERS), new StatusMove(Moves.FAIRY_LOCK, "Fairy Lock (N)", Type.FAIRY, -1, 10, "By locking down the battlefield, the user keeps all Pokémon from fleeing during the next turn.", -1, 0, 6) .target(MoveTarget.BOTH_SIDES), - new SelfStatusMove(Moves.KINGS_SHIELD, "King's Shield (P)", Type.STEEL, -1, 10, "The user takes a defensive stance while it protects itself from damage. It also lowers the Attack stat of any attacker that makes direct contact.", -1, 4, 6) - .attr(ProtectAttr), + new SelfStatusMove(Moves.KINGS_SHIELD, "King's Shield", Type.STEEL, -1, 10, "The user takes a defensive stance while it protects itself from damage. It also lowers the Attack stat of any attacker that makes direct contact.", -1, 4, 6) + .attr(ProtectAttr, BattlerTagType.KINGS_SHIELD), new StatusMove(Moves.PLAY_NICE, "Play Nice", Type.NORMAL, -1, 20, "The user and the target become friends, and the target loses its will to fight. This lowers the target's Attack stat.", 100, 0, 6) .attr(StatChangeAttr, BattleStat.ATK, -1), new StatusMove(Moves.CONFIDE, "Confide", Type.NORMAL, -1, 20, "The user tells the target a secret, and the target loses its ability to concentrate. This lowers the target's Sp. Atk stat.", 100, 0, 6) @@ -4069,7 +4078,7 @@ export function initMoves() { .attr(StatChangeAttr, BattleStat.DEF, 2, true) .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(Moves.STEAM_ERUPTION, "Steam Eruption", Type.WATER, MoveCategory.SPECIAL, 110, 95, 5, "The user immerses the target in superheated steam. This may also leave the target with a burn.", 30, 0, 6) - .attr(HealStatusEffectAttr, false, StatusEffect.FREEZE) + .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) .attr(StatusEffectAttr, StatusEffect.BURN), new AttackMove(Moves.HYPERSPACE_HOLE, "Hyperspace Hole", Type.PSYCHIC, MoveCategory.SPECIAL, 80, -1, 5, "Using a hyperspace hole, the user appears right next to the target and strikes. This also hits a target using a move such as Protect or Detect.", -1, 0, 6) .ignoresProtect(), @@ -4226,7 +4235,7 @@ export function initMoves() { .attr(StatChangeAttr, BattleStat.DEF, -1), new AttackMove(Moves.POWER_TRIP, "Power Trip (P)", Type.DARK, MoveCategory.PHYSICAL, 20, 100, 10, "The user boasts its strength and attacks the target. The more the user's stats are raised, the greater the move's power.", -1, 0, 7), new AttackMove(Moves.BURN_UP, "Burn Up (P)", Type.FIRE, MoveCategory.SPECIAL, 130, 100, 5, "To inflict massive damage, the user burns itself out. After using this move, the user will no longer be Fire type.", -1, 0, 7) - .attr(HealStatusEffectAttr, false, StatusEffect.FREEZE), + .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE), new StatusMove(Moves.SPEED_SWAP, "Speed Swap (N)", Type.PSYCHIC, -1, 10, "The user exchanges Speed stats with the target.", -1, 0, 7), new AttackMove(Moves.SMART_STRIKE, "Smart Strike", Type.STEEL, MoveCategory.PHYSICAL, 70, -1, 10, "The user stabs the target with a sharp horn. This attack never misses.", -1, 0, 7), new StatusMove(Moves.PURIFY, "Purify (N)", Type.POISON, -1, 20, "The user heals the target's status condition. If the move succeeds, it also restores the user's own HP.", -1, 0, 7), @@ -4416,7 +4425,7 @@ export function initMoves() { .attr(StatChangeAttr, BattleStat.SPD, -1), new AttackMove(Moves.SNAP_TRAP, "Snap Trap (P)", Type.GRASS, MoveCategory.PHYSICAL, 35, 100, 15, "The user snares the target in a snap trap for four to five turns.", 100, 0, 8), new AttackMove(Moves.PYRO_BALL, "Pyro Ball", Type.FIRE, MoveCategory.PHYSICAL, 120, 90, 5, "The user attacks by igniting a small stone and launching it as a fiery ball at the target. This may also leave the target with a burn.", 10, 0, 8) - .attr(HealStatusEffectAttr, false, StatusEffect.FREEZE) + .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) .attr(StatusEffectAttr, StatusEffect.BURN) .ballBombMove(), new AttackMove(Moves.BEHEMOTH_BLADE, "Behemoth Blade", Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, "The user wields a large, powerful sword using its whole body and cuts the target in a vigorous attack.", -1, 0, 8) @@ -4492,7 +4501,7 @@ export function initMoves() { new AttackMove(Moves.DUAL_WINGBEAT, "Dual Wingbeat", Type.FLYING, MoveCategory.PHYSICAL, 40, 90, 10, "The user slams the target with its wings. The target is hit twice in a row.", -1, 0, 8) .attr(MultiHitAttr, MultiHitType._2), new AttackMove(Moves.SCORCHING_SANDS, "Scorching Sands", Type.GROUND, MoveCategory.SPECIAL, 70, 100, 10, "The user throws scorching sand at the target to attack. This may also leave the target with a burn.", 30, 0, 8) - .attr(HealStatusEffectAttr, false, StatusEffect.FREEZE) + .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) .attr(StatusEffectAttr, StatusEffect.BURN), new StatusMove(Moves.JUNGLE_HEALING, "Jungle Healing (P)", Type.GRASS, -1, 10, "The user becomes one with the jungle, restoring HP and healing any status conditions of itself and its ally Pokémon in battle.", -1, 0, 8) .attr(HealAttr, 0.25) @@ -4767,7 +4776,7 @@ export function initMoves() { new AttackMove(Moves.BLOOD_MOON, "Blood Moon (P)", Type.NORMAL, MoveCategory.SPECIAL, 140, 100, 5, "The user unleashes the full brunt of its spirit from a full moon that shines as red as blood. This move can't be used twice in a row.", -1, 0, 9), new AttackMove(Moves.MATCHA_GOTCHA, "Matcha Gotcha", Type.GRASS, MoveCategory.SPECIAL, 80, 90, 15, "The user fires a blast of tea that it mixed. The user's HP is restored by up to half the damage taken by the target. This may also leave the target with a burn.", 20, 0, 9) .attr(HitHealAttr) - .attr(HealStatusEffectAttr, false, StatusEffect.FREEZE) + .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) .attr(StatusEffectAttr, StatusEffect.BURN) .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(Moves.SYRUP_BOMB, "Syrup Bomb (P)", Type.GRASS, MoveCategory.SPECIAL, 60, 85, 10, "The user sets off an explosion of sticky candy syrup, which coats the target and causes the target's Speed stat to drop each turn for three turns.", -1, 0, 9) diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index 9d8e2332b..175cdf660 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -5,6 +5,7 @@ import { Moves } from "./enums/moves"; import { SpeciesFormKey } from "./pokemon-species"; import { Species } from "./enums/species"; import { StatusEffect } from "./status-effect"; +import { MoveCategory, allMoves } from "./move"; export enum FormChangeItem { NONE, @@ -251,18 +252,27 @@ export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigge } } -export class SpeciesFormChangeMoveUsedTrigger extends SpeciesFormChangeTrigger { - public move: Moves; +export abstract class SpeciesFormChangeMoveTrigger extends SpeciesFormChangeTrigger { + public movePredicate: (m: Moves) => boolean; public used: boolean; - constructor(move: Moves, used: boolean = true) { + constructor(move: Moves | ((m: Moves) => boolean), used: boolean = true) { super(); - this.move = move; + this.movePredicate = typeof move === 'function' ? move : (m: Moves) => m === move; this.used = used; } +} +export class SpeciesFormChangePreMoveTrigger extends SpeciesFormChangeMoveTrigger { canChange(pokemon: Pokemon): boolean { - return pokemon.summonData && !!pokemon.getLastXMoves(1).filter(m => m.move === this.move).length === this.used; + const command = pokemon.scene.currentBattle.turnCommands[pokemon.getFieldIndex()]; + return command?.move && this.movePredicate(command.move.move) === this.used; + } +} + +export class SpeciesFormChangePostMoveTrigger extends SpeciesFormChangeMoveTrigger { + canChange(pokemon: Pokemon): boolean { + return pokemon.summonData && !!pokemon.getLastXMoves(1).filter(m => this.movePredicate(m.move)).length === this.used; } } @@ -512,10 +522,14 @@ export const pokemonFormChanges: PokemonFormChanges = { new SpeciesFormChange(Species.KELDEO, 'resolute', 'ordinary', new SpeciesFormChangeMoveLearnedTrigger(Moves.SECRET_SWORD, false)) ], [Species.MELOETTA]: [ - new SpeciesFormChange(Species.MELOETTA, 'aria', 'pirouette', new SpeciesFormChangeMoveUsedTrigger(Moves.RELIC_SONG), true), - new SpeciesFormChange(Species.MELOETTA, 'pirouette', 'aria', new SpeciesFormChangeMoveUsedTrigger(Moves.RELIC_SONG), true), + new SpeciesFormChange(Species.MELOETTA, 'aria', 'pirouette', new SpeciesFormChangePostMoveTrigger(Moves.RELIC_SONG), true), + new SpeciesFormChange(Species.MELOETTA, 'pirouette', 'aria', new SpeciesFormChangePostMoveTrigger(Moves.RELIC_SONG), true), new SpeciesFormChange(Species.MELOETTA, 'pirouette', 'aria', new SpeciesFormChangeActiveTrigger(false), true) ], + [Species.AEGISLASH]: [ + new SpeciesFormChange(Species.AEGISLASH, 'blade', 'shield', new SpeciesFormChangePreMoveTrigger(Moves.KINGS_SHIELD), true), + new SpeciesFormChange(Species.AEGISLASH, 'shield', 'blade', new SpeciesFormChangePreMoveTrigger(m => allMoves[m].category !== MoveCategory.STATUS), true) + ], [Species.DIANCIE]: [ new SpeciesFormChange(Species.DIANCIE, '', SpeciesFormKey.MEGA, new SpeciesFormChangeItemTrigger(FormChangeItem.DIANCITE)) ], diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 18a985533..602928387 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -37,7 +37,7 @@ import { DamageAchv, achvs } from '../system/achv'; import { DexAttr, StarterMoveset } from '../system/game-data'; import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from '@material/material-color-utilities'; import { Nature, getNatureStatMultiplier } from '../data/nature'; -import { SpeciesFormChange, SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangeMoveUsedTrigger, SpeciesFormChangeStatusEffectTrigger } from '../data/pokemon-forms'; +import { SpeciesFormChange, SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangeMoveTrigger, SpeciesFormChangeStatusEffectTrigger } from '../data/pokemon-forms'; import { TerrainType } from '../data/terrain'; import { TrainerSlot } from '../data/trainer-config'; @@ -268,7 +268,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { abstract getBattlerIndex(): BattlerIndex; - loadAssets(ignoreOveride: boolean = true): Promise { + loadAssets(ignoreOverride: boolean = true): Promise { return new Promise(resolve => { const moveIds = this.getMoveset().map(m => m.getMove().id); Promise.allSettled(moveIds.map(m => initMoveAnim(m))) @@ -276,10 +276,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { loadMoveAnimAssets(this.scene, moveIds); this.getSpeciesForm().loadAssets(this.scene, this.getGender() === Gender.FEMALE, this.formIndex, this.shiny); if (this.isPlayer() || this.getFusionSpeciesForm()) - this.scene.loadPokemonAtlas(this.getBattleSpriteKey(true, ignoreOveride), this.getBattleSpriteAtlasPath(true, ignoreOveride)); + this.scene.loadPokemonAtlas(this.getBattleSpriteKey(true, ignoreOverride), this.getBattleSpriteAtlasPath(true, ignoreOverride)); if (this.getFusionSpeciesForm()) { this.getFusionSpeciesForm().loadAssets(this.scene, this.getFusionGender() === Gender.FEMALE, this.fusionFormIndex, this.fusionShiny); - this.scene.loadPokemonAtlas(this.getFusionBattleSpriteKey(true, ignoreOveride), this.getFusionBattleSpriteAtlasPath(true, ignoreOveride)); + this.scene.loadPokemonAtlas(this.getFusionBattleSpriteKey(true, ignoreOverride), this.getFusionBattleSpriteAtlasPath(true, ignoreOverride)); } this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => { if (this.isPlayer()) { @@ -1413,7 +1413,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const abilityCount = this.getSpeciesForm().getAbilityCount(); if (this.abilityIndex >= abilityCount) // Shouldn't happen this.abilityIndex = abilityCount - 1; - this.scene.gameData.setPokemonSeen(this, true); + this.scene.gameData.setPokemonSeen(this, false); this.setScale(this.getSpriteScale()); this.loadAssets().then(() => { this.calculateStats(); @@ -1697,7 +1697,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.getTag(BattlerTagType.SEEDED)) this.lapseTag(BattlerTagType.SEEDED); if (this.scene) - this.scene.triggerPokemonFormChange(this, SpeciesFormChangeMoveUsedTrigger, true); + this.scene.triggerPokemonFormChange(this, SpeciesFormChangeMoveTrigger, true); } resetTurnData(): void { @@ -2099,14 +2099,17 @@ export class PlayerPokemon extends Pokemon { this.abilityIndex = abilityCount - 1; this.compatibleTms.splice(0, this.compatibleTms.length); this.generateCompatibleTms(); + const updateAndResolve = () => { + this.loadAssets().then(() => { + this.calculateStats(); + this.updateInfo(true).then(() => resolve()); + }); + }; if (!this.scene.gameMode.isDaily || this.metBiome > -1) { this.scene.gameData.setPokemonSeen(this, false); - this.scene.gameData.setPokemonCaught(this, false); - } - this.loadAssets().then(() => { - this.calculateStats(); - this.updateInfo(true).then(() => resolve()); - }); + this.scene.gameData.setPokemonCaught(this, false).then(() => updateAndResolve()); + } else + updateAndResolve(); }); } @@ -2146,15 +2149,18 @@ export class PlayerPokemon extends Pokemon { this.abilityIndex = abilityCount - 1; this.compatibleTms.splice(0, this.compatibleTms.length); this.generateCompatibleTms(); + const updateAndResolve = () => { + this.loadAssets().then(() => { + this.calculateStats(); + this.scene.updateModifiers(true, true); + this.updateInfo(true).then(() => resolve()); + }); + }; if (!this.scene.gameMode.isDaily || this.metBiome > -1) { this.scene.gameData.setPokemonSeen(this, false); - this.scene.gameData.setPokemonCaught(this, false); - } - this.loadAssets().then(() => { - this.calculateStats(); - this.scene.updateModifiers(true, true); - this.updateInfo(true).then(() => resolve()); - }); + this.scene.gameData.setPokemonCaught(this, false).then(() => updateAndResolve()); + } else + updateAndResolve(); }); } diff --git a/src/form-change-phase.ts b/src/form-change-phase.ts index ea0f51c50..631efc980 100644 --- a/src/form-change-phase.ts +++ b/src/form-change-phase.ts @@ -187,7 +187,7 @@ export class QuietFormChangePhase extends BattlePhase { if (!this.pokemon.isOnField()) { this.pokemon.changeForm(this.formChange).then(() => { - this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), null, true); + this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500); }); return; } @@ -195,7 +195,7 @@ export class QuietFormChangePhase extends BattlePhase { const getPokemonSprite = () => { const sprite = this.scene.addPokemonSprite(this.pokemon, this.pokemon.x + this.pokemon.getSprite().x, this.pokemon.y + this.pokemon.getSprite().y, `pkmn__sub`); sprite.setOrigin(0.5, 1); - sprite.play(this.pokemon.getSpriteKey()).stop(); + sprite.play(this.pokemon.getBattleSpriteKey()).stop(); sprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) }); [ 'spriteColors', 'fusionSpriteColors' ].map(k => { if (this.pokemon.summonData?.speciesForm) @@ -231,7 +231,7 @@ export class QuietFormChangePhase extends BattlePhase { this.pokemon.setVisible(false); this.pokemon.changeForm(this.formChange).then(() => { pokemonFormTintSprite.setScale(0.01); - pokemonFormTintSprite.play(this.pokemon.getSpriteKey()).stop(); + pokemonFormTintSprite.play(this.pokemon.getBattleSpriteKey()).stop(); pokemonFormTintSprite.setVisible(true); this.scene.tweens.add({ targets: pokemonTintSprite, @@ -257,7 +257,7 @@ export class QuietFormChangePhase extends BattlePhase { duration: 1000, onComplete: () => { pokemonTintSprite.setVisible(false); - this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), Utils.fixedInt(1500)); + this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500); } }); } diff --git a/src/phases.ts b/src/phases.ts index 86ada84e0..dca73ea55 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1,8 +1,8 @@ -import BattleScene, { bypassLogin, startingWave } from "./battle-scene"; +import BattleScene, { STARTER_FORM_OVERRIDE, STARTER_SPECIES_OVERRIDE, bypassLogin, startingWave } from "./battle-scene"; import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon"; import * as Utils from './utils'; import { Moves } from "./data/enums/moves"; -import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveFlags, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, OneHitKOAttr, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, DelayedAttackAttr, RechargeAttr, PreMoveMessageAttr } from "./data/move"; +import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveFlags, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, OneHitKOAttr, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, DelayedAttackAttr, RechargeAttr, PreMoveMessageAttr, HealStatusEffectAttr } from "./data/move"; import { Mode } from './ui/ui'; import { Command } from "./ui/command-ui-handler"; import { Stat } from "./data/pokemon-stat"; @@ -45,7 +45,7 @@ import { vouchers } from "./system/voucher"; import { loggedInUser, updateUserInfo } from "./account"; import { GameDataType, PlayerGender, SessionSaveData } from "./system/game-data"; import { addPokeballCaptureStars, addPokeballOpenParticles } from "./field/anims"; -import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeManualTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangeMoveUsedTrigger } from "./data/pokemon-forms"; +import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeManualTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangePreMoveTrigger } from "./data/pokemon-forms"; import { battleSpecDialogue, getCharVariantFromDialogue } from "./data/dialogue"; import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "./ui/modifier-select-ui-handler"; import { Setting } from "./system/settings"; @@ -55,6 +55,7 @@ import { OptionSelectConfig, OptionSelectItem } from "./ui/abstact-option-select import { SaveSlotUiMode } from "./ui/save-slot-select-ui-handler"; import { fetchDailyRunSeed, getDailyRunStarters } from "./data/daily-run"; import { GameModes, gameModes } from "./game-mode"; +import { getPokemonSpecies } from "./data/pokemon-species"; export class LoginPhase extends Phase { private showText: boolean; @@ -405,9 +406,13 @@ export class SelectStarterPhase extends Phase { const party = this.scene.getParty(); const loadPokemonAssets: Promise[] = []; - for (let starter of starters) { + starters.forEach((starter: Starter, i: integer) => { + if (!i && STARTER_SPECIES_OVERRIDE) + starter.species = getPokemonSpecies(STARTER_SPECIES_OVERRIDE as Species); const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); - const starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); + let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); + if (!i && STARTER_SPECIES_OVERRIDE) + starterFormIndex = STARTER_FORM_OVERRIDE; const starterGender = starter.species.malePercent !== null ? !starterProps.female ? Gender.MALE : Gender.FEMALE : Gender.GENDERLESS; @@ -421,7 +426,7 @@ export class SelectStarterPhase extends Phase { starterPokemon.setVisible(false); party.push(starterPokemon); loadPokemonAssets.push(starterPokemon.loadAssets()); - } + }); Promise.all(loadPokemonAssets).then(() => { SoundFade.fadeOut(this.scene, this.scene.sound.get('menu'), 500, true); this.scene.time.delayedCall(500, () => this.scene.playBgm()); @@ -2063,6 +2068,8 @@ export class MovePhase extends BattlePhase { const moveQueue = this.pokemon.getMoveQueue(); + this.scene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger); + if (this.move.moveId) this.showMoveText(); @@ -2127,7 +2134,7 @@ export class MovePhase extends BattlePhase { this.cancelled = activated; break; case StatusEffect.FREEZE: - healed = !this.pokemon.randSeedInt(5); + healed = !!this.move.getMove().findAttr(attr => attr instanceof HealStatusEffectAttr && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE)) || !this.pokemon.randSeedInt(5); activated = !healed; this.cancelled = activated; break; @@ -2257,7 +2264,7 @@ export class MoveEffectPhase extends PokemonPhase { const hitResult = !isProtected ? target.apply(user, this.move) : HitResult.NO_EFFECT; - this.scene.triggerPokemonFormChange(user, SpeciesFormChangeMoveUsedTrigger); + this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); applyAttrs.push(new Promise(resolve => { applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit),