Implement some moves and update arena tags to include side

This commit is contained in:
Flashfyre 2024-01-15 23:29:22 -05:00
parent 8c4f336cd6
commit 63cb2ae22f
10 changed files with 190 additions and 76 deletions

View File

@ -14,26 +14,15 @@
- Add IV screen - Add IV screen
- Capture logic - Capture logic
- Critical capture - Critical capture
- Save data
- Update dex format to share attributes
- Modifiers - Modifiers
- PP Up
- Various mainline game items for various enhancements - Various mainline game items for various enhancements
- IV scanner
- Valuable items for money - Valuable items for money
- Trainers - Trainers
- Finish party pools - Finish party pools
- Add dialogue - Add dialogue
- Add reward for gym leader victories - Add reward for gym leader victories
- Encounters
- Add extremely rare chance of Arceus available anywhere with type change
- Balancing
- Biome pools
- Battle animations - Battle animations
- Fix broken battle animations (mostly ones with backgrounds) - Fix broken battle animations (mostly ones with backgrounds)
- Add common animations for modifier effects
- Achievements
- Add more achievements
- Modes - Modes
- Add random mode - Add random mode
- Add Nuzlocke mode - Add Nuzlocke mode

View File

@ -9,7 +9,8 @@ import { CommonAnimPhase } from "./battle-phases";
import { CommonAnim } from "./data/battle-anims"; import { CommonAnim } from "./data/battle-anims";
import { Type } from "./data/type"; import { Type } from "./data/type";
import Move from "./data/move"; import Move from "./data/move";
import { ArenaTag, ArenaTagType, getArenaTag } from "./data/arena-tag"; import { ArenaTag, ArenaTagSide, getArenaTag } from "./data/arena-tag";
import { ArenaTagType } from "./data/enums/arena-tag-type";
import { GameMode } from "./game-mode"; import { GameMode } from "./game-mode";
import { TrainerType } from "./data/enums/trainer-type"; import { TrainerType } from "./data/enums/trainer-type";
import { BattlerIndex } from "./battle"; import { BattlerIndex } from "./battle";
@ -387,21 +388,27 @@ export class Arena {
} }
} }
applyTags(tagType: ArenaTagType | { new(...args: any[]): ArenaTag }, ...args: any[]): void { applyTagsForSide(tagType: ArenaTagType | { new(...args: any[]): ArenaTag }, side: ArenaTagSide, ...args: any[]): void {
const tags = typeof tagType === 'number' let tags = typeof tagType === 'string'
? this.tags.filter(t => t.tagType === tagType) ? this.tags.filter(t => t.tagType === tagType)
: this.tags.filter(t => t instanceof tagType); : this.tags.filter(t => t instanceof tagType);
tags.forEach(t => t.apply(args)); if (side !== ArenaTagSide.BOTH)
tags = tags.filter(t => t.side === side);
tags.forEach(t => t.apply(this, args));
}
applyTags(tagType: ArenaTagType | { new(...args: any[]): ArenaTag }, ...args: any[]): void {
this.applyTagsForSide(tagType, ArenaTagSide.BOTH, args);
} }
addTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, targetIndex?: BattlerIndex): boolean { addTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide = ArenaTagSide.BOTH, targetIndex?: BattlerIndex): boolean {
const existingTag = this.getTag(tagType); const existingTag = this.getTag(tagType);
if (existingTag) { if (existingTag) {
existingTag.onOverlap(this); existingTag.onOverlap(this);
return false; return false;
} }
const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex); const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side);
this.tags.push(newTag); this.tags.push(newTag);
newTag.onAdd(this); newTag.onAdd(this);
@ -409,9 +416,13 @@ export class Arena {
} }
getTag(tagType: ArenaTagType | { new(...args: any[]): ArenaTag }): ArenaTag { getTag(tagType: ArenaTagType | { new(...args: any[]): ArenaTag }): ArenaTag {
return typeof(tagType) === 'number' return this.getTagOnSide(tagType, ArenaTagSide.BOTH);
? this.tags.find(t => t.tagType === tagType) }
: this.tags.find(t => t instanceof tagType);
getTagOnSide(tagType: ArenaTagType | { new(...args: any[]): ArenaTag }, side: ArenaTagSide): ArenaTag {
return typeof(tagType) === 'string'
? this.tags.find(t => t.tagType === tagType && (t.side === ArenaTagSide.BOTH || t.side === side))
: this.tags.find(t => t instanceof tagType && (t.side === ArenaTagSide.BOTH || t.side === side));
} }
lapseTags(): void { lapseTags(): void {

View File

@ -28,7 +28,8 @@ import { Starter } from "./ui/starter-select-ui-handler";
import { Gender } from "./data/gender"; import { Gender } from "./data/gender";
import { Weather, WeatherType, getRandomWeatherType, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather"; import { Weather, WeatherType, getRandomWeatherType, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather";
import { TempBattleStat } from "./data/temp-battle-stat"; import { TempBattleStat } from "./data/temp-battle-stat";
import { ArenaTagType, ArenaTrapTag, TrickRoomTag } from "./data/arena-tag"; import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag";
import { ArenaTagType } from "./data/enums/arena-tag-type";
import { Abilities, CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability"; import { Abilities, CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability";
import { Unlockables, getUnlockableName } from "./system/unlockables"; import { Unlockables, getUnlockableName } from "./system/unlockables";
import { getBiomeKey } from "./arena"; import { getBiomeKey } from "./arena";
@ -2072,7 +2073,9 @@ export class MoveEndPhase extends PokemonPhase {
start() { start() {
super.start(); super.start();
this.getPokemon().lapseTags(BattlerTagLapseType.AFTER_MOVE); const pokemon = this.getPokemon();
if (pokemon.isActive(true))
pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE);
this.end(); this.end();
} }
@ -2132,13 +2135,15 @@ export class StatChangePhase extends PokemonPhase {
private stats: BattleStat[]; private stats: BattleStat[];
private selfTarget: boolean; private selfTarget: boolean;
private levels: integer; private levels: integer;
private showMessage: boolean;
constructor(scene: BattleScene, battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], levels: integer) { constructor(scene: BattleScene, battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], levels: integer, showMessage: boolean = true) {
super(scene, battlerIndex); super(scene, battlerIndex);
this.selfTarget = selfTarget; this.selfTarget = selfTarget;
this.stats = stats; this.stats = stats;
this.levels = levels; this.levels = levels;
this.showMessage = showMessage;
} }
start() { start() {
@ -2152,6 +2157,9 @@ export class StatChangePhase extends PokemonPhase {
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
if (!this.selfTarget && this.levels < 0) if (!this.selfTarget && this.levels < 0)
this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled);
if (!cancelled.value && !this.selfTarget && this.levels < 0)
applyPreStatChangeAbAttrs(ProtectStatAbAttr, this.getPokemon(), stat, cancelled); applyPreStatChangeAbAttrs(ProtectStatAbAttr, this.getPokemon(), stat, cancelled);
return !cancelled.value; return !cancelled.value;
@ -2164,9 +2172,11 @@ export class StatChangePhase extends PokemonPhase {
const relLevels = filteredStats.map(stat => (levels.value >= 1 ? Math.min(battleStats[stat] + levels.value, 6) : Math.max(battleStats[stat] + levels.value, -6)) - battleStats[stat]); const relLevels = filteredStats.map(stat => (levels.value >= 1 ? Math.min(battleStats[stat] + levels.value, 6) : Math.max(battleStats[stat] + levels.value, -6)) - battleStats[stat]);
const end = () => { const end = () => {
const messages = this.getStatChangeMessages(filteredStats, levels.value, relLevels); if (this.showMessage) {
for (let message of messages) const messages = this.getStatChangeMessages(filteredStats, levels.value, relLevels);
this.scene.queueMessage(message); for (let message of messages)
this.scene.queueMessage(message);
}
for (let stat of filteredStats) for (let stat of filteredStats)
pokemon.summonData.battleStats[stat] = Math.max(Math.min(pokemon.summonData.battleStats[stat] + levels.value, 6), -6); pokemon.summonData.battleStats[stat] = Math.max(Math.min(pokemon.summonData.battleStats[stat] + levels.value, 6), -6);

View File

@ -9,7 +9,7 @@ import { BattlerTag } from "./battler-tags";
import { BattlerTagType } from "./enums/battler-tag-type"; import { BattlerTagType } from "./enums/battler-tag-type";
import { StatusEffect, getStatusEffectDescriptor } from "./status-effect"; import { StatusEffect, getStatusEffectDescriptor } from "./status-effect";
import Move, { MoveCategory, MoveFlags, RecoilAttr } from "./move"; import Move, { MoveCategory, MoveFlags, RecoilAttr } from "./move";
import { ArenaTagType } from "./arena-tag"; import { ArenaTagType } from "./enums/arena-tag-type";
import { Stat } from "./pokemon-stat"; import { Stat } from "./pokemon-stat";
import { PokemonHeldItemModifier } from "../modifier/modifier"; import { PokemonHeldItemModifier } from "../modifier/modifier";
import { Moves } from "./enums/moves"; import { Moves } from "./enums/moves";

View File

@ -9,18 +9,12 @@ import { StatusEffect } from "./status-effect";
import { BattlerTagType } from "./enums/battler-tag-type"; import { BattlerTagType } from "./enums/battler-tag-type";
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "../battle";
import { Moves } from "./enums/moves"; import { Moves } from "./enums/moves";
import { ArenaTagType } from "./enums/arena-tag-type";
export enum ArenaTagType { export enum ArenaTagSide {
NONE, BOTH,
MUD_SPORT, PLAYER,
WATER_SPORT, ENEMY
SPIKES,
TOXIC_SPIKES,
FUTURE_SIGHT,
DOOM_DESIRE,
STEALTH_ROCK,
TRICK_ROOM,
GRAVITY
} }
export abstract class ArenaTag { export abstract class ArenaTag {
@ -28,22 +22,24 @@ export abstract class ArenaTag {
public turnCount: integer; public turnCount: integer;
public sourceMove: Moves; public sourceMove: Moves;
public sourceId: integer; public sourceId: integer;
public side: ArenaTagSide;
constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId?: integer) { constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId?: integer, side: ArenaTagSide = ArenaTagSide.BOTH) {
this.tagType = tagType; this.tagType = tagType;
this.turnCount = turnCount; this.turnCount = turnCount;
this.sourceMove = sourceMove; this.sourceMove = sourceMove;
this.sourceId = sourceId; this.sourceId = sourceId;
this.side = side;
} }
apply(args: any[]): boolean { apply(arena: Arena, args: any[]): boolean {
return true; return true;
} }
onAdd(arena: Arena): void { } onAdd(arena: Arena): void { }
onRemove(arena: Arena): void { onRemove(arena: Arena): void {
arena.scene.queueMessage(`${this.getMoveName()}\'s effect wore off.`); arena.scene.queueMessage(`${this.getMoveName()}\'s effect wore off${this.side === ArenaTagSide.PLAYER ? '\non your side' : this.side === ArenaTagSide.ENEMY ? '\non the foe\'s side' : ''}.`);
} }
onOverlap(arena: Arena): void { } onOverlap(arena: Arena): void { }
@ -59,6 +55,27 @@ export abstract class ArenaTag {
} }
} }
export class MistTag extends ArenaTag {
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
super(ArenaTagType.MIST, turnCount, Moves.MIST, sourceId, side);
}
onAdd(arena: Arena): void {
super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId);
arena.scene.queueMessage(getPokemonMessage(source, `'s team became\nshrowded in mist!`));
}
apply(arena: Arena, args: any[]): boolean {
(args[0] as Utils.BooleanHolder).value = true;
arena.scene.queueMessage('The mist prevented\nthe lowering of stats!');
return true;
}
}
export class WeakenMoveTypeTag extends ArenaTag { export class WeakenMoveTypeTag extends ArenaTag {
private weakenedType: Type; private weakenedType: Type;
@ -68,7 +85,7 @@ export class WeakenMoveTypeTag extends ArenaTag {
this.weakenedType = type; this.weakenedType = type;
} }
apply(args: any[]): boolean { apply(arena: Arena, args: any[]): boolean {
if ((args[0] as Type) === this.weakenedType) { if ((args[0] as Type) === this.weakenedType) {
(args[1] as Utils.NumberHolder).value *= 0.33; (args[1] as Utils.NumberHolder).value *= 0.33;
return true; return true;
@ -110,8 +127,8 @@ export class ArenaTrapTag extends ArenaTag {
public layers: integer; public layers: integer;
public maxLayers: integer; public maxLayers: integer;
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, maxLayers: integer) { constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, maxLayers: integer) {
super(tagType, 0, sourceMove, sourceId); super(tagType, 0, sourceMove, sourceId, side);
this.layers = 1; this.layers = 1;
this.maxLayers = maxLayers; this.maxLayers = maxLayers;
@ -125,9 +142,9 @@ export class ArenaTrapTag extends ArenaTag {
} }
} }
apply(args: any[]): boolean { apply(arena: Arena, args: any[]): boolean {
const pokemon = args[0] as Pokemon; const pokemon = args[0] as Pokemon;
if (this.sourceId === pokemon.id || !!(pokemon.scene.getPokemonById(this.sourceId)?.isPlayer()) === pokemon.isPlayer()) if (this.sourceId === pokemon.id || (this.side === ArenaTagSide.PLAYER) === pokemon.isPlayer())
return false; return false;
return this.activateTrap(pokemon); return this.activateTrap(pokemon);
@ -139,8 +156,8 @@ export class ArenaTrapTag extends ArenaTag {
} }
class SpikesTag extends ArenaTrapTag { class SpikesTag extends ArenaTrapTag {
constructor(sourceId: integer) { constructor(sourceId: integer, side: ArenaTagSide) {
super(ArenaTagType.SPIKES, Moves.SPIKES, sourceId, 3); super(ArenaTagType.SPIKES, Moves.SPIKES, sourceId, side, 3);
} }
onAdd(arena: Arena): void { onAdd(arena: Arena): void {
@ -165,8 +182,8 @@ class SpikesTag extends ArenaTrapTag {
} }
class ToxicSpikesTag extends ArenaTrapTag { class ToxicSpikesTag extends ArenaTrapTag {
constructor(sourceId: integer) { constructor(sourceId: integer, side: ArenaTagSide) {
super(ArenaTagType.TOXIC_SPIKES, Moves.TOXIC_SPIKES, sourceId, 2); super(ArenaTagType.TOXIC_SPIKES, Moves.TOXIC_SPIKES, sourceId, side, 2);
} }
onAdd(arena: Arena): void { onAdd(arena: Arena): void {
@ -211,8 +228,8 @@ class DelayedAttackTag extends ArenaTag {
} }
class StealthRockTag extends ArenaTrapTag { class StealthRockTag extends ArenaTrapTag {
constructor(sourceId: integer) { constructor(sourceId: integer, side: ArenaTagSide) {
super(ArenaTagType.STEALTH_ROCK, Moves.STEALTH_ROCK, sourceId, 1); super(ArenaTagType.STEALTH_ROCK, Moves.STEALTH_ROCK, sourceId, side, 1);
} }
onAdd(arena: Arena): void { onAdd(arena: Arena): void {
@ -263,7 +280,7 @@ export class TrickRoomTag extends ArenaTag {
super(ArenaTagType.TRICK_ROOM, turnCount, Moves.TRICK_ROOM, sourceId); super(ArenaTagType.TRICK_ROOM, turnCount, Moves.TRICK_ROOM, sourceId);
} }
apply(args: any[]): boolean { apply(arena: Arena, args: any[]): boolean {
const speedReversed = args[0] as Utils.BooleanHolder; const speedReversed = args[0] as Utils.BooleanHolder;
speedReversed.value = !speedReversed.value; speedReversed.value = !speedReversed.value;
return true; return true;
@ -292,21 +309,23 @@ export class GravityTag extends ArenaTag {
} }
} }
export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, targetIndex?: BattlerIndex): ArenaTag { export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag {
switch (tagType) { switch (tagType) {
case ArenaTagType.MIST:
return new MistTag(turnCount, sourceId, side);
case ArenaTagType.MUD_SPORT: case ArenaTagType.MUD_SPORT:
return new MudSportTag(turnCount, sourceId); return new MudSportTag(turnCount, sourceId);
case ArenaTagType.WATER_SPORT: case ArenaTagType.WATER_SPORT:
return new WaterSportTag(turnCount, sourceId); return new WaterSportTag(turnCount, sourceId);
case ArenaTagType.SPIKES: case ArenaTagType.SPIKES:
return new SpikesTag(sourceId); return new SpikesTag(sourceId, side);
case ArenaTagType.TOXIC_SPIKES: case ArenaTagType.TOXIC_SPIKES:
return new ToxicSpikesTag(sourceId); return new ToxicSpikesTag(sourceId, side);
case ArenaTagType.FUTURE_SIGHT: case ArenaTagType.FUTURE_SIGHT:
case ArenaTagType.DOOM_DESIRE: case ArenaTagType.DOOM_DESIRE:
return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex); return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex);
case ArenaTagType.STEALTH_ROCK: case ArenaTagType.STEALTH_ROCK:
return new StealthRockTag(sourceId); return new StealthRockTag(sourceId, side);
case ArenaTagType.TRICK_ROOM: case ArenaTagType.TRICK_ROOM:
return new TrickRoomTag(turnCount, sourceId); return new TrickRoomTag(turnCount, sourceId);
case ArenaTagType.GRAVITY: case ArenaTagType.GRAVITY:

View File

@ -564,6 +564,27 @@ export class ProtectedTag extends BattlerTag {
} }
} }
export class EnduringTag extends BattlerTag {
constructor(sourceMove: Moves) {
super(BattlerTagType.ENDURING, BattlerTagLapseType.CUSTOM, 0, sourceMove);
}
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' braced\nitself!'));
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.CUSTOM) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' endured\nthe hit!'));
return true;
}
return super.lapse(pokemon, lapseType);
}
}
export class PerishSongTag extends BattlerTag { export class PerishSongTag extends BattlerTag {
constructor(turnCount: integer) { constructor(turnCount: integer) {
super(BattlerTagType.PERISH_SONG, BattlerTagLapseType.TURN_END, turnCount, Moves.PERISH_SONG); super(BattlerTagType.PERISH_SONG, BattlerTagLapseType.TURN_END, turnCount, Moves.PERISH_SONG);
@ -743,6 +764,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc
return new MagmaStormTag(turnCount, sourceId); return new MagmaStormTag(turnCount, sourceId);
case BattlerTagType.PROTECTED: case BattlerTagType.PROTECTED:
return new ProtectedTag(sourceMove); return new ProtectedTag(sourceMove);
case BattlerTagType.ENDURING:
return new EnduringTag(sourceMove);
case BattlerTagType.PERISH_SONG: case BattlerTagType.PERISH_SONG:
return new PerishSongTag(turnCount); return new PerishSongTag(turnCount);
case BattlerTagType.TRUANT: case BattlerTagType.TRUANT:

View File

@ -0,0 +1,14 @@
export enum ArenaTagType {
NONE = "NONE",
MUD_SPORT = "MUD_SPORT",
WATER_SPORT = "WATER_SPORT",
SPIKES = "SPIKES",
TOXIC_SPIKES = "TOXIC_SPIKES",
MIST = "MIST",
FUTURE_SIGHT = "FUTURE_SIGHT",
DOOM_DESIRE = "DOOM_DESIRE",
STEALTH_ROCK = "STEALTH_ROCK",
TRICK_ROOM = "TRICK_ROOM",
GRAVITY = "GRAVITY"
}

View File

@ -21,6 +21,7 @@ export enum BattlerTagType {
SAND_TOMB = "SAND_TOMB", SAND_TOMB = "SAND_TOMB",
MAGMA_STORM = "MAGMA_STORM", MAGMA_STORM = "MAGMA_STORM",
PROTECTED = "PROTECTED", PROTECTED = "PROTECTED",
ENDURING = "ENDURING",
PERISH_SONG = "PERISH_SONG", PERISH_SONG = "PERISH_SONG",
TRUANT = "TRUANT", TRUANT = "TRUANT",
SLOW_START = "SLOW_START", SLOW_START = "SLOW_START",

View File

@ -1,7 +1,7 @@
import { Moves } from "./enums/moves"; import { Moves } from "./enums/moves";
import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims"; import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims";
import { BattleEndPhase, DamagePhase, MovePhase, NewBattlePhase, ObtainStatusEffectPhase, PokemonHealPhase, StatChangePhase, SwitchSummonPhase } from "../battle-phases"; import { BattleEndPhase, DamagePhase, MovePhase, NewBattlePhase, ObtainStatusEffectPhase, PokemonHealPhase, StatChangePhase, SwitchSummonPhase } from "../battle-phases";
import { BattleStat } from "./battle-stat"; import { BattleStat, getBattleStatName } from "./battle-stat";
import { EncoreTag } from "./battler-tags"; import { EncoreTag } from "./battler-tags";
import { BattlerTagType } from "./enums/battler-tag-type"; import { BattlerTagType } from "./enums/battler-tag-type";
import { getPokemonMessage } from "../messages"; import { getPokemonMessage } from "../messages";
@ -10,11 +10,12 @@ import { StatusEffect, getStatusEffectDescriptor } from "./status-effect";
import { Type } from "./type"; import { Type } from "./type";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { WeatherType } from "./weather"; import { WeatherType } from "./weather";
import { ArenaTagType, ArenaTrapTag } from "./arena-tag"; import { ArenaTagSide, ArenaTrapTag } from "./arena-tag";
import { ArenaTagType } from "./enums/arena-tag-type";
import { Abilities, BlockRecoilDamageAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs } from "./ability"; import { Abilities, BlockRecoilDamageAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs } from "./ability";
import { PokemonHeldItemModifier } from "../modifier/modifier"; import { PokemonHeldItemModifier } from "../modifier/modifier";
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "../battle";
import { Stat } from "./pokemon-stat"; import { Stat, getStatName } from "./pokemon-stat";
export enum MoveCategory { export enum MoveCategory {
PHYSICAL, PHYSICAL,
@ -998,7 +999,7 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr {
(args[0] as Utils.BooleanHolder).value = true; (args[0] as Utils.BooleanHolder).value = true;
user.scene.queueMessage(getPokemonMessage(user, ` ${this.chargeText.replace('{TARGET}', target.name)}`)); user.scene.queueMessage(getPokemonMessage(user, ` ${this.chargeText.replace('{TARGET}', target.name)}`));
user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER }); user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER });
user.scene.arena.addTag(this.tagType, 3, move.id, user.id, target.getBattlerIndex()); user.scene.arena.addTag(this.tagType, 3, move.id, user.id, ArenaTagSide.BOTH, target.getBattlerIndex());
resolve(true); resolve(true);
}); });
@ -1012,23 +1013,25 @@ export class StatChangeAttr extends MoveEffectAttr {
public stats: BattleStat[]; public stats: BattleStat[];
public levels: integer; public levels: integer;
private condition: MoveConditionFunc; private condition: MoveConditionFunc;
private showMessage: boolean;
constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean, condition?: MoveConditionFunc) { constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean, condition?: MoveConditionFunc, showMessage: boolean = true) {
super(selfTarget, MoveEffectTrigger.HIT); super(selfTarget, MoveEffectTrigger.HIT);
this.stats = typeof(stats) === 'number' this.stats = typeof(stats) === 'number'
? [ stats as BattleStat ] ? [ stats as BattleStat ]
: stats as BattleStat[]; : stats as BattleStat[];
this.levels = levels; this.levels = levels;
this.condition = condition || null; this.condition = condition || null;
this.showMessage = showMessage;
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<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;
if (move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) { if (move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) {
const levels = this.getLevels(user); const levels = this.getLevels(user);
user.scene.unshiftPhase(new StatChangePhase(user.scene, (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, levels)); user.scene.unshiftPhase(new StatChangePhase(user.scene, (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, levels, this.showMessage));
return true; return true;
} }
@ -1061,6 +1064,27 @@ export class GrowthStatChangeAttr extends StatChangeAttr {
} }
} }
export class HalfHpStatMaxAttr extends StatChangeAttr {
constructor(stat: BattleStat) {
super(stat, 12, true, null, false);
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
return new Promise<boolean>(resolve => {
user.damage(Math.floor(user.getMaxHp() / 2));
user.updateInfo().then(() => {
const ret = super.apply(user, target, move, args);
user.scene.queueMessage(getPokemonMessage(user, ` cut its own hp\nand maximized its ${getBattleStatName(this.stats[0])}!`));
resolve(ret);
});
});
}
getCondition(): MoveConditionFunc {
return (user, target, move) => user.getHpRatio() > 0.5 || user.summonData.battleStats[this.stats[0]] >= 6;
}
}
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[]): Promise<boolean> {
return new Promise(resolve => { return new Promise(resolve => {
@ -1568,8 +1592,8 @@ export class TrapAttr extends AddBattlerTagAttr {
} }
export class ProtectAttr extends AddBattlerTagAttr { export class ProtectAttr extends AddBattlerTagAttr {
constructor() { constructor(tagType: BattlerTagType = BattlerTagType.PROTECTED) {
super(BattlerTagType.PROTECTED, true); super(tagType, true);
} }
getCondition(): MoveConditionFunc { getCondition(): MoveConditionFunc {
@ -1577,7 +1601,7 @@ export class ProtectAttr extends AddBattlerTagAttr {
let timesUsed = 0; let timesUsed = 0;
const moveHistory = user.getLastXMoves(); const moveHistory = user.getLastXMoves();
let turnMove: TurnMove; let turnMove: TurnMove;
while (moveHistory.length && (turnMove = moveHistory.shift()).move === move.id && turnMove.result === MoveResult.SUCCESS) while (moveHistory.length && allMoves[(turnMove = moveHistory.shift()).move].getAttrs(ProtectAttr).find(pa => (pa as ProtectAttr).tagType === this.tagType) && turnMove.result === MoveResult.SUCCESS)
timesUsed++; timesUsed++;
if (timesUsed) if (timesUsed)
return !user.randSeedInt(Math.pow(2, timesUsed)); return !user.randSeedInt(Math.pow(2, timesUsed));
@ -1586,6 +1610,12 @@ export class ProtectAttr extends AddBattlerTagAttr {
} }
} }
export class EndureAttr extends ProtectAttr {
constructor() {
super(BattlerTagType.ENDURING);
}
}
export class IgnoreAccuracyAttr extends AddBattlerTagAttr { export class IgnoreAccuracyAttr extends AddBattlerTagAttr {
constructor() { constructor() {
super(BattlerTagType.IGNORE_ACCURACY, true, false, 1); super(BattlerTagType.IGNORE_ACCURACY, true, false, 1);
@ -1635,12 +1665,14 @@ export class HitsTagAttr extends MoveAttr {
export class AddArenaTagAttr extends MoveEffectAttr { export class AddArenaTagAttr extends MoveEffectAttr {
public tagType: ArenaTagType; public tagType: ArenaTagType;
public turnCount: integer; public turnCount: integer;
private failOnOverlap: boolean;
constructor(tagType: ArenaTagType, turnCount?: integer) { constructor(tagType: ArenaTagType, turnCount?: integer, failOnOverlap: boolean = false) {
super(true); super(true);
this.tagType = tagType; this.tagType = tagType;
this.turnCount = turnCount; this.turnCount = turnCount;
this.failOnOverlap = failOnOverlap;
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
@ -1648,12 +1680,18 @@ export class AddArenaTagAttr extends MoveEffectAttr {
return false; return false;
if (move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) { if (move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) {
user.scene.arena.addTag(this.tagType, this.turnCount, move.id, user.id); user.scene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY);
return true; return true;
} }
return false; return false;
} }
getCondition(): MoveConditionFunc {
return this.failOnOverlap
? (user, target, move) => !user.scene.arena.getTagOnSide(this.tagType, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)
: null;
}
} }
export class AddArenaTrapTagAttr extends AddArenaTagAttr { export class AddArenaTrapTagAttr extends AddArenaTagAttr {
@ -1738,8 +1776,10 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
}; };
} }
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return -100; // Overridden in switch logic if (this.batonPass)
return -100; // Overridden in switch logic
return this.user ? Math.floor(user.getHpRatio() * 20) : super.getUserBenefitScore(user, target, move);
} }
} }
@ -2249,7 +2289,8 @@ export function initMoves() {
.attr(StatusEffectAttr, StatusEffect.BURN), .attr(StatusEffectAttr, StatusEffect.BURN),
new AttackMove(Moves.FLAMETHROWER, "Flamethrower", Type.FIRE, MoveCategory.SPECIAL, 90, 100, 15, 125, "The target is scorched with an intense blast of fire. This may also leave the target with a burn.", 10, 0, 1) new AttackMove(Moves.FLAMETHROWER, "Flamethrower", Type.FIRE, MoveCategory.SPECIAL, 90, 100, 15, 125, "The target is scorched with an intense blast of fire. This may also leave the target with a burn.", 10, 0, 1)
.attr(StatusEffectAttr, StatusEffect.BURN), .attr(StatusEffectAttr, StatusEffect.BURN),
new StatusMove(Moves.MIST, "Mist (N)", Type.ICE, -1, 30, -1, "The user cloaks itself and its allies in a white mist that prevents any of their stats from being lowered for five turns.", -1, 0, 1) new StatusMove(Moves.MIST, "Mist", Type.ICE, -1, 30, -1, "The user cloaks itself and its allies in a white mist that prevents any of their stats from being lowered for five turns.", -1, 0, 1)
.attr(AddArenaTagAttr, ArenaTagType.MIST, 5, true)
.target(MoveTarget.USER_SIDE), .target(MoveTarget.USER_SIDE),
new AttackMove(Moves.WATER_GUN, "Water Gun", Type.WATER, MoveCategory.SPECIAL, 40, 100, 25, -1, "The target is blasted with a forceful shot of water.", -1, 0, 1), new AttackMove(Moves.WATER_GUN, "Water Gun", Type.WATER, MoveCategory.SPECIAL, 40, 100, 25, -1, "The target is blasted with a forceful shot of water.", -1, 0, 1),
new AttackMove(Moves.HYDRO_PUMP, "Hydro Pump", Type.WATER, MoveCategory.SPECIAL, 110, 80, 5, 142, "The target is blasted by a huge volume of water launched under great pressure.", -1, 0, 1), new AttackMove(Moves.HYDRO_PUMP, "Hydro Pump", Type.WATER, MoveCategory.SPECIAL, 110, 80, 5, 142, "The target is blasted by a huge volume of water launched under great pressure.", -1, 0, 1),
@ -2582,7 +2623,8 @@ export function initMoves() {
new AttackMove(Moves.FEINT_ATTACK, "Feint Attack", Type.DARK, MoveCategory.PHYSICAL, 60, -1, 20, -1, "The user approaches the target disarmingly, then throws a sucker punch. This attack never misses.", -1, 0, 2), new AttackMove(Moves.FEINT_ATTACK, "Feint Attack", Type.DARK, MoveCategory.PHYSICAL, 60, -1, 20, -1, "The user approaches the target disarmingly, then throws a sucker punch. This attack never misses.", -1, 0, 2),
new StatusMove(Moves.SWEET_KISS, "Sweet Kiss", Type.FAIRY, 75, 10, -1, "The user kisses the target with a sweet, angelic cuteness that causes confusion.", -1, 0, 2) new StatusMove(Moves.SWEET_KISS, "Sweet Kiss", Type.FAIRY, 75, 10, -1, "The user kisses the target with a sweet, angelic cuteness that causes confusion.", -1, 0, 2)
.attr(ConfuseAttr), .attr(ConfuseAttr),
new SelfStatusMove(Moves.BELLY_DRUM, "Belly Drum (N)", Type.NORMAL, -1, 10, -1, "The user maximizes its Attack stat in exchange for HP equal to half its max HP.", -1, 0, 2), new SelfStatusMove(Moves.BELLY_DRUM, "Belly Drum", Type.NORMAL, -1, 10, -1, "The user maximizes its Attack stat in exchange for HP equal to half its max HP.", -1, 0, 2)
.attr(HalfHpStatMaxAttr, BattleStat.ATK),
new AttackMove(Moves.SLUDGE_BOMB, "Sludge Bomb", Type.POISON, MoveCategory.SPECIAL, 90, 100, 10, 148, "Unsanitary sludge is hurled at the target. This may also poison the target.", 30, 0, 2) new AttackMove(Moves.SLUDGE_BOMB, "Sludge Bomb", Type.POISON, MoveCategory.SPECIAL, 90, 100, 10, 148, "Unsanitary sludge is hurled at the target. This may also poison the target.", 30, 0, 2)
.attr(StatusEffectAttr, StatusEffect.POISON) .attr(StatusEffectAttr, StatusEffect.POISON)
.ballBombMove(), .ballBombMove(),
@ -2627,7 +2669,8 @@ export function initMoves() {
.target(MoveTarget.BOTH_SIDES), .target(MoveTarget.BOTH_SIDES),
new AttackMove(Moves.GIGA_DRAIN, "Giga Drain", Type.GRASS, MoveCategory.SPECIAL, 75, 100, 10, 111, "A nutrient-draining attack. The user's HP is restored by half the damage taken by the target.", -1, 0, 2) new AttackMove(Moves.GIGA_DRAIN, "Giga Drain", Type.GRASS, MoveCategory.SPECIAL, 75, 100, 10, 111, "A nutrient-draining attack. The user's HP is restored by half the damage taken by the target.", -1, 0, 2)
.attr(HitHealAttr), .attr(HitHealAttr),
new SelfStatusMove(Moves.ENDURE, "Endure (N)", Type.NORMAL, -1, 10, 47, "The user endures any attack with at least 1 HP. Its chance of failing rises if it is used in succession.", -1, 4, 2), new SelfStatusMove(Moves.ENDURE, "Endure", Type.NORMAL, -1, 10, 47, "The user endures any attack with at least 1 HP. Its chance of failing rises if it is used in succession.", -1, 4, 2)
.attr(EndureAttr),
new StatusMove(Moves.CHARM, "Charm", Type.FAIRY, 100, 20, 2, "The user gazes at the target rather charmingly, making it less wary. This harshly lowers the target's Attack stat.", -1, 0, 2) new StatusMove(Moves.CHARM, "Charm", Type.FAIRY, 100, 20, 2, "The user gazes at the target rather charmingly, making it less wary. This harshly lowers the target's Attack stat.", -1, 0, 2)
.attr(StatChangeAttr, BattleStat.ATK, -2), .attr(StatChangeAttr, BattleStat.ATK, -2),
new AttackMove(Moves.ROLLOUT, "Rollout", Type.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, "The user continually rolls into the target over five turns. It becomes more powerful each time it hits.", -1, 0, 2) new AttackMove(Moves.ROLLOUT, "Rollout", Type.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, "The user continually rolls into the target over five turns. It becomes more powerful each time it hits.", -1, 0, 2)

View File

@ -22,7 +22,8 @@ import { BattlerTagType } from "./data/enums/battler-tag-type";
import { Species } from './data/enums/species'; import { Species } from './data/enums/species';
import { WeatherType } from './data/weather'; import { WeatherType } from './data/weather';
import { TempBattleStat } from './data/temp-battle-stat'; import { TempBattleStat } from './data/temp-battle-stat';
import { ArenaTagType, WeakenMoveTypeTag } from './data/arena-tag'; import { WeakenMoveTypeTag } from './data/arena-tag';
import { ArenaTagType } from "./data/enums/arena-tag-type";
import { Biome } from "./data/enums/biome"; import { Biome } from "./data/enums/biome";
import { Abilities, Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from './data/ability'; import { Abilities, Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from './data/ability';
import PokemonData from './system/pokemon-data'; import PokemonData from './system/pokemon-data';
@ -1115,7 +1116,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (this.hp > 1 && this.hp - damage <= 0 && !preventEndure) { if (this.hp > 1 && this.hp - damage <= 0 && !preventEndure) {
const surviveDamage = new Utils.BooleanHolder(false); const surviveDamage = new Utils.BooleanHolder(false);
this.scene.applyModifiers(SurviveDamageModifier, this.isPlayer(), this, surviveDamage); if (this.lapseTag(BattlerTagType.ENDURING))
surviveDamage.value = true;
if (!surviveDamage.value)
this.scene.applyModifiers(SurviveDamageModifier, this.isPlayer(), this, surviveDamage);
if (surviveDamage.value) if (surviveDamage.value)
damage = this.hp - 1; damage = this.hp - 1;
} }