diff --git a/public/battle-anims/future-sight-charging.json b/public/battle-anims/future-sight-charging.json index 73d31ac7f55..e4925bf1f56 100644 --- a/public/battle-anims/future-sight-charging.json +++ b/public/battle-anims/future-sight-charging.json @@ -805,7 +805,7 @@ "0": [ { "frameIndex": 0, - "resourceName": "PRAS- Darkness BG", + "resourceName": "PRAS- Black BG", "bgX": 0, "bgY": 0, "opacity": 0, diff --git a/src/arena.ts b/src/arena.ts index 6ab9b2fdb68..5589a387d29 100644 --- a/src/arena.ts +++ b/src/arena.ts @@ -11,6 +11,7 @@ import Move, { Moves } from "./data/move"; import { ArenaTag, ArenaTagType, getArenaTag } from "./data/arena-tag"; import { GameMode } from "./game-mode"; import { TrainerType } from "./data/trainer-type"; +import { BattlerIndex } from "./battle"; const WEATHER_OVERRIDE = WeatherType.NONE; @@ -289,14 +290,14 @@ export class Arena { tags.forEach(t => t.apply(args)); } - addTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer): boolean { + addTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, targetIndex?: BattlerIndex): boolean { const existingTag = this.getTag(tagType); if (existingTag) { existingTag.onOverlap(this); return false; } - const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId); + const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex); this.tags.push(newTag); newTag.onAdd(this); diff --git a/src/battle-phases.ts b/src/battle-phases.ts index 893e8be5a26..b08dd4f6bfb 100644 --- a/src/battle-phases.ts +++ b/src/battle-phases.ts @@ -1,7 +1,7 @@ import BattleScene, { startingLevel, startingWave } from "./battle-scene"; import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult } from "./pokemon"; import * as Utils from './utils'; -import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveCategory, MoveEffectAttr, MoveFlags, Moves, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, OneHitKOAttr, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove } from "./data/move"; +import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveCategory, MoveEffectAttr, MoveFlags, Moves, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, OneHitKOAttr, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, DelayedAttackAttr } from "./data/move"; import { Mode } from './ui/ui'; import { Command } from "./ui/command-ui-handler"; import { Stat } from "./data/pokemon-stat"; @@ -167,11 +167,11 @@ export abstract class FieldPhase extends BattlePhase { } export abstract class PokemonPhase extends FieldPhase { - protected battlerIndex: BattlerIndex; + protected battlerIndex: BattlerIndex | integer; protected player: boolean; protected fieldIndex: integer; - constructor(scene: BattleScene, battlerIndex: BattlerIndex) { + constructor(scene: BattleScene, battlerIndex: BattlerIndex | integer) { super(scene); if (battlerIndex === undefined) @@ -183,6 +183,8 @@ export abstract class PokemonPhase extends FieldPhase { } getPokemon() { + if (this.battlerIndex > BattlerIndex.ENEMY_2) + return this.scene.getPokemonById(this.battlerIndex); return this.scene.getField()[this.battlerIndex]; } } @@ -1599,7 +1601,7 @@ export class MovePhase extends BattlePhase { } } -class MoveEffectPhase extends PokemonPhase { +export class MoveEffectPhase extends PokemonPhase { protected move: PokemonMove; protected targets: BattlerIndex[]; @@ -1619,13 +1621,13 @@ class MoveEffectPhase extends PokemonPhase { const overridden = new Utils.BooleanHolder(false); // Assume single target for override - applyMoveAttrs(OverrideMoveEffectAttr, user, this.getTarget(), this.move.getMove(), overridden).then(() => { + applyMoveAttrs(OverrideMoveEffectAttr, user, this.getTarget(), this.move.getMove(), overridden, this.move.virtual).then(() => { if (overridden.value) { this.end(); return; } - + user.lapseTags(BattlerTagLapseType.MOVE_EFFECT); if (user.turnData.hitsLeft === undefined) { @@ -1767,6 +1769,8 @@ class MoveEffectPhase extends PokemonPhase { } getUserPokemon(): Pokemon { + if (this.battlerIndex > BattlerIndex.ENEMY_2) + return this.scene.getPokemonById(this.battlerIndex); return (this.player ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.fieldIndex]; } diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index bf6383f75e4..41e972f3b1c 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -3,10 +3,11 @@ import { Type } from "./type"; import * as Utils from "../utils"; import { Moves, allMoves } from "./move"; import { getPokemonMessage } from "../messages"; -import Pokemon, { DamageResult, HitResult, MoveResult } from "../pokemon"; -import { DamagePhase, ObtainStatusEffectPhase } from "../battle-phases"; +import Pokemon, { HitResult, PokemonMove } from "../pokemon"; +import { DamagePhase, MoveEffectPhase, ObtainStatusEffectPhase } from "../battle-phases"; import { StatusEffect } from "./status-effect"; import { BattlerTagType } from "./battler-tag"; +import { BattlerIndex } from "../battle"; export enum ArenaTagType { NONE, @@ -14,6 +15,8 @@ export enum ArenaTagType { WATER_SPORT, SPIKES, TOXIC_SPIKES, + FUTURE_SIGHT, + DOOM_DESIRE, STEALTH_ROCK, TRICK_ROOM, GRAVITY @@ -32,7 +35,7 @@ export abstract class ArenaTag { this.sourceId = sourceId; } - apply(args: any[]): boolean { + apply(args: any[]): boolean { return true; } @@ -185,6 +188,27 @@ class ToxicSpikesTag extends ArenaTrapTag { } } +class DelayedAttackTag extends ArenaTag { + public targetIndex: BattlerIndex; + + constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, targetIndex: BattlerIndex) { + super(tagType, 3, sourceMove, sourceId); + + this.targetIndex = targetIndex; + } + + lapse(arena: Arena): boolean { + const ret = super.lapse(arena); + + if (!ret) + arena.scene.unshiftPhase(new MoveEffectPhase(arena.scene, this.sourceId, [ this.targetIndex ], new PokemonMove(this.sourceMove, 0, 0, true))); + + return ret; + } + + onRemove(arena: Arena): void { } +} + class StealthRockTag extends ArenaTrapTag { constructor(sourceId: integer) { super(ArenaTagType.STEALTH_ROCK, Moves.STEALTH_ROCK, sourceId, 1); @@ -267,7 +291,7 @@ export class GravityTag extends ArenaTag { } } -export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer): ArenaTag { +export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, targetIndex?: BattlerIndex): ArenaTag { switch (tagType) { case ArenaTagType.MUD_SPORT: return new MudSportTag(turnCount, sourceId); @@ -277,6 +301,9 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov return new SpikesTag(sourceId); case ArenaTagType.TOXIC_SPIKES: return new ToxicSpikesTag(sourceId); + case ArenaTagType.FUTURE_SIGHT: + case ArenaTagType.DOOM_DESIRE: + return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex); case ArenaTagType.STEALTH_ROCK: return new StealthRockTag(sourceId); case ArenaTagType.TRICK_ROOM: diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index a1b361fea99..4f6f70ea295 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -1,6 +1,6 @@ //import { battleAnimRawData } from "./battle-anim-raw-data"; import BattleScene from "../battle-scene"; -import { AttackMove, ChargeAttr, MoveFlags, Moves, SelfStatusMove, allMoves } from "./move"; +import { AttackMove, ChargeAttr, DelayedAttackAttr, MoveFlags, Moves, SelfStatusMove, allMoves } from "./move"; import Pokemon from "../pokemon"; import * as Utils from "../utils"; import { BattlerIndex } from "../battle"; @@ -433,6 +433,9 @@ export function initMoveAnim(move: Moves): Promise { else { let loadedCheckTimer = setInterval(() => { if (moveAnims.get(move) !== null) { + const chargeAttr = allMoves[move].getAttrs(ChargeAttr).find(() => true) as ChargeAttr || allMoves[move].getAttrs(DelayedAttackAttr).find(() => true) as DelayedAttackAttr; + if (chargeAttr && chargeAnims.get(chargeAttr.chargeAnim) === null) + return; clearInterval(loadedCheckTimer); resolve(); } @@ -460,9 +463,9 @@ export function initMoveAnim(move: Moves): Promise { populateMoveAnim(move, ba[1]); } else populateMoveAnim(move, ba); - const chargeAttr = allMoves[move].getAttrs(ChargeAttr) as ChargeAttr[]; - if (chargeAttr.length) - initMoveChargeAnim(chargeAttr[0].chargeAnim).then(() => resolve()); + const chargeAttr = allMoves[move].getAttrs(ChargeAttr).find(() => true) as ChargeAttr || allMoves[move].getAttrs(DelayedAttackAttr).find(() => true) as DelayedAttackAttr; + if (chargeAttr) + initMoveChargeAnim(chargeAttr.chargeAnim).then(() => resolve()); else resolve(); }); @@ -529,9 +532,9 @@ export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLo return new Promise(resolve => { const moveAnimations = moveIds.map(m => moveAnims.get(m) as AnimConfig).flat(); for (let moveId of moveIds) { - const chargeAttr = allMoves[moveId].getAttrs(ChargeAttr) as ChargeAttr[]; - if (chargeAttr.length) { - const moveChargeAnims = chargeAnims.get(chargeAttr[0].chargeAnim); + const chargeAttr = allMoves[moveId].getAttrs(ChargeAttr).find(() => true) as ChargeAttr || allMoves[moveId].getAttrs(DelayedAttackAttr).find(() => true) as DelayedAttackAttr; + if (chargeAttr) { + const moveChargeAnims = chargeAnims.get(chargeAttr.chargeAnim); moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims[0]); if (Array.isArray(moveChargeAnims)) moveAnimations.push(moveChargeAnims[1]); diff --git a/src/data/move.ts b/src/data/move.ts index f7cbf5fd15b..995afbbcaf9 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1675,7 +1675,13 @@ export class OneHitKOAttr extends MoveAttr { } } -export class OverrideMoveEffectAttr extends MoveAttr { } +export class OverrideMoveEffectAttr extends MoveAttr { + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise { + //const overridden = args[0] as Utils.BooleanHolder; + //const virtual = arg[1] as boolean; + return true; + } +} export class ChargeAttr extends OverrideMoveEffectAttr { public chargeAnim: ChargeAnim; @@ -1729,6 +1735,36 @@ export class SolarBeamChargeAttr extends ChargeAttr { } } +export class DelayedAttackAttr extends OverrideMoveEffectAttr { + public tagType: ArenaTagType; + public chargeAnim: ChargeAnim; + private chargeText: string; + + constructor(tagType: ArenaTagType, chargeAnim: ChargeAnim, chargeText: string) { + super(); + + this.tagType = tagType; + this.chargeAnim = chargeAnim; + this.chargeText = chargeText; + } + + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { + return new Promise(resolve => { + if (args.length < 2 || !args[1]) { + new MoveChargeAnim(this.chargeAnim, move.id, user).play(user.scene, () => { + (args[0] as Utils.BooleanHolder).value = true; + user.scene.queueMessage(getPokemonMessage(user, ` ${this.chargeText.replace('{TARGET}', target.name)}`)); + user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER }); + user.scene.arena.addTag(this.tagType, 3, move.id, user.id, target.getBattlerIndex()); + + resolve(true); + }); + } else + user.scene.ui.showText(getPokemonMessage(user.scene.getPokemonById(target.id), ` took\nthe ${move.name} attack!`), null, () => resolve(true)); + }); + } +} + export class StatChangeAttr extends MoveEffectAttr { public stats: BattleStat[]; public levels: integer; @@ -3326,7 +3362,8 @@ export function initMoves() { .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true), new AttackMove(Moves.SHADOW_BALL, "Shadow Ball", Type.GHOST, MoveCategory.SPECIAL, 80, 100, 15, 114, "The user hurls a shadowy blob at the target. This may also lower the target's Sp. Def stat.", 20, 0, 2) .attr(StatChangeAttr, BattleStat.SPDEF, -1), - new AttackMove(Moves.FUTURE_SIGHT, "Future Sight (N)", Type.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, "Two turns after this move is used, a hunk of psychic energy attacks the target.", -1, 0, 2), + new AttackMove(Moves.FUTURE_SIGHT, "Future Sight", Type.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, "Two turns after this move is used, a hunk of psychic energy attacks the target.", -1, 0, 2) + .attr(DelayedAttackAttr, ArenaTagType.FUTURE_SIGHT, ChargeAnim.FUTURE_SIGHT_CHARGING, 'foresaw\nan attack!'), new AttackMove(Moves.ROCK_SMASH, "Rock Smash", Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 15, -1, "The user attacks with a punch. This may also lower the target's Defense stat.", 50, 0, 2) .attr(StatChangeAttr, BattleStat.DEF, -1), new AttackMove(Moves.WHIRLPOOL, "Whirlpool", Type.WATER, MoveCategory.SPECIAL, 35, 85, 15, -1, "The user traps the target in a violent swirling whirlpool for four to five turns.", 100, 0, 2) @@ -3546,8 +3583,8 @@ export function initMoves() { new AttackMove(Moves.SHOCK_WAVE, "Shock Wave", Type.ELECTRIC, MoveCategory.SPECIAL, 60, -1, 20, -1, "The user strikes the target with a quick jolt of electricity. This attack never misses.", -1, 0, 3), new AttackMove(Moves.WATER_PULSE, "Water Pulse", Type.WATER, MoveCategory.SPECIAL, 60, 100, 20, 11, "The user attacks the target with a pulsing blast of water. This may also confuse the target.", 20, 0, 3) .attr(ConfuseAttr), - new AttackMove(Moves.DOOM_DESIRE, "Doom Desire (N)", Type.STEEL, MoveCategory.SPECIAL, 140, 100, 5, -1, "Two turns after this move is used, a concentrated bundle of light blasts the target.", -1, 0, 3) - .attr(ChargeAttr, ChargeAnim.DOOM_DESIRE_CHARGING, 'chose\nDOOM DESIRE as its destiny!'), + new AttackMove(Moves.DOOM_DESIRE, "Doom Desire", Type.STEEL, MoveCategory.SPECIAL, 140, 100, 5, -1, "Two turns after this move is used, a concentrated bundle of light blasts the target.", -1, 0, 3) + .attr(DelayedAttackAttr, ArenaTagType.DOOM_DESIRE, ChargeAnim.DOOM_DESIRE_CHARGING, 'chose\nDOOM DESIRE as its destiny!'), new AttackMove(Moves.PSYCHO_BOOST, "Psycho Boost", Type.PSYCHIC, MoveCategory.SPECIAL, 140, 90, 5, -1, "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), new SelfStatusMove(Moves.ROOST, "Roost", Type.FLYING, -1, 10, -1, "The user lands and rests its body. This move restores the user's HP by up to half of its max HP.", -1, 0, 4) diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 0c88ab3ba05..2f2503461b8 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1007,7 +1007,7 @@ export class HealingBoosterModifier extends PersistentModifier { } getMaxStackCount(): integer { - return 3; + return 4; } }