diff --git a/src/battle-scene.ts b/src/battle-scene.ts index fe9e8fc36a8..1a1690224e2 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1539,6 +1539,24 @@ export default class BattleScene extends Phaser.Scene { return this.phaseQueue.find(phaseFilter); } + tryReplacePhase(phaseFilter: (phase: Phase) => boolean, phase: Phase): boolean { + const phaseIndex = this.phaseQueue.findIndex(phaseFilter); + if (phaseIndex > -1) { + this.phaseQueue[phaseIndex] = phase; + return true; + } + return false; + } + + tryRemovePhase(phaseFilter: (phase: Phase) => boolean): boolean { + const phaseIndex = this.phaseQueue.findIndex(phaseFilter); + if (phaseIndex > -1) { + this.phaseQueue.splice(phaseIndex, 1); + return true; + } + return false; + } + pushMovePhase(movePhase: MovePhase, priorityOverride?: integer): void { const priority = priorityOverride !== undefined ? priorityOverride : movePhase.move.getMove().priority; const lowerPriorityPhase = this.phaseQueue.find(p => p instanceof MovePhase && p.move.getMove().priority < priority); diff --git a/src/data/ability.ts b/src/data/ability.ts index cac67fbbe62..3bac26bce1f 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -84,8 +84,8 @@ export abstract class AbAttr { public showAbility: boolean; private extraCondition: AbAttrCondition; - constructor(showAbility?: boolean) { - this.showAbility = showAbility === undefined || showAbility; + constructor(showAbility: boolean = true) { + this.showAbility = showAbility; } apply(pokemon: Pokemon, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { @@ -946,6 +946,34 @@ function getWeatherCondition(...weatherTypes: WeatherType[]): AbAttrCondition { }; } +export class PostWeatherChangeAbAttr extends AbAttr { + applyPostWeatherChange(pokemon: Pokemon, weather: WeatherType, args: any[]): boolean { + return false; + } +} + +export class PostWeatherChangeAddBattlerTagAttr extends PostWeatherChangeAbAttr { + private tagType: BattlerTagType; + private turnCount: integer; + private weatherTypes: WeatherType[]; + + constructor(tagType: BattlerTagType, turnCount: integer, ...weatherTypes: WeatherType[]) { + super(); + + this.tagType = tagType; + this.turnCount = turnCount; + this.weatherTypes = weatherTypes; + } + + applyPostWeatherChange(pokemon: Pokemon, weather: WeatherType, args: any[]): boolean { + console.log(this.weatherTypes.find(w => weather === w), WeatherType[weather]); + if (!this.weatherTypes.find(w => weather === w)) + return false; + + return pokemon.addTag(this.tagType, this.turnCount); + } +} + export class PostWeatherLapseAbAttr extends AbAttr { protected weatherTypes: WeatherType[]; @@ -1006,6 +1034,33 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr { } } +export class PostTerrainChangeAbAttr extends AbAttr { + applyPostTerrainChange(pokemon: Pokemon, terrain: TerrainType, args: any[]): boolean { + return false; + } +} + +export class PostTerrainChangeAddBattlerTagAttr extends PostTerrainChangeAbAttr { + private tagType: BattlerTagType; + private turnCount: integer; + private terrainTypes: TerrainType[]; + + constructor(tagType: BattlerTagType, turnCount: integer, ...terrainTypes: TerrainType[]) { + super(); + + this.tagType = tagType; + this.turnCount = turnCount; + this.terrainTypes = terrainTypes; + } + + applyPostTerrainChange(pokemon: Pokemon, terrain: TerrainType, args: any[]): boolean { + if (!this.terrainTypes.find(t => terrain === terrain)) + return false; + + return pokemon.addTag(this.tagType, this.turnCount); + } +} + function getTerrainCondition(...terrainTypes: TerrainType[]): AbAttrCondition { return (pokemon: Pokemon) => { const terrainType = pokemon.scene.arena.terrain?.terrainType; @@ -1420,11 +1475,21 @@ export function applyPostTurnAbAttrs(attrType: { new(...args: any[]): PostTurnAb return applyAbAttrsInternal(attrType, pokemon, attr => attr.applyPostTurn(pokemon, args)); } +export function applyPostWeatherChangeAbAttrs(attrType: { new(...args: any[]): PostWeatherChangeAbAttr }, + pokemon: Pokemon, weather: WeatherType, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, attr => attr.applyPostWeatherChange(pokemon, weather, args)); +} + export function applyPostWeatherLapseAbAttrs(attrType: { new(...args: any[]): PostWeatherLapseAbAttr }, pokemon: Pokemon, weather: Weather, ...args: any[]): Promise { return applyAbAttrsInternal(attrType, pokemon, attr => attr.applyPostWeatherLapse(pokemon, weather, args)); } +export function applyPostTerrainChangeAbAttrs(attrType: { new(...args: any[]): PostTerrainChangeAbAttr }, + pokemon: Pokemon, terrain: TerrainType, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, attr => attr.applyPostTerrainChange(pokemon, terrain, args)); +} + export function applyCheckTrappedAbAttrs(attrType: { new(...args: any[]): CheckTrappedAbAttr }, pokemon: Pokemon, trapped: Utils.BooleanHolder, ...args: any[]): Promise { return applyAbAttrsInternal(attrType, pokemon, attr => attr.applyCheckTrapped(pokemon, trapped, args), true); @@ -2312,9 +2377,13 @@ export function initAbilities() { new Ability(Abilities.COMMANDER, "Commander (N)", "When the Pokémon enters a battle, it goes inside the mouth of an ally Dondozo if one is on the field. The Pokémon then issues commands from there.", 9) .attr(ProtectAbilityAbAttr), new Ability(Abilities.ELECTROMORPHOSIS, "Electromorphosis (N)", "The Pokémon becomes charged when it takes damage, boosting the power of the next Electric-type move the Pokémon uses.", 9), - new Ability(Abilities.PROTOSYNTHESIS, "Protosynthesis (N)", "Boosts the Pokémon's most proficient stat in harsh sunlight or if the Pokémon is holding Booster Energy.", 9) + new Ability(Abilities.PROTOSYNTHESIS, "Protosynthesis", "Boosts the Pokémon's most proficient stat in harsh sunlight or if the Pokémon is holding Booster Energy.", 9) + .conditionalAttr(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN), PostSummonAddBattlerTagAbAttr, BattlerTagType.PROTOSYNTHESIS, 0, true) + .attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.PROTOSYNTHESIS, 0, WeatherType.SUNNY, WeatherType.HARSH_SUN) .attr(ProtectAbilityAbAttr), - new Ability(Abilities.QUARK_DRIVE, "Quark Drive (N)", "Boosts the Pokémon's most proficient stat on Electric Terrain or if the Pokémon is holding Booster Energy.", 9) + new Ability(Abilities.QUARK_DRIVE, "Quark Drive", "Boosts the Pokémon's most proficient stat on Electric Terrain or if the Pokémon is holding Booster Energy.", 9) + .conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), PostSummonAddBattlerTagAbAttr, BattlerTagType.QUARK_DRIVE, 0, true) + .attr(PostTerrainChangeAddBattlerTagAttr, BattlerTagType.QUARK_DRIVE, 0, TerrainType.ELECTRIC) .attr(ProtectAbilityAbAttr), new Ability(Abilities.GOOD_AS_GOLD, "Good as Gold (N)", "A body of pure, solid gold gives the Pokémon full immunity to other Pokémon's status moves.", 9) .ignorable(), diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 0ca94272be3..8786c29c72e 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2,7 +2,7 @@ import { CommonAnim, CommonBattleAnim } from "./battle-anims"; import { CommonAnimPhase, MoveEffectPhase, MovePhase, PokemonHealPhase, ShowAbilityPhase } from "../phases"; import { getPokemonMessage } from "../messages"; import Pokemon, { MoveResult, HitResult } from "../field/pokemon"; -import { Stat } from "./pokemon-stat"; +import { Stat, getStatName } from "./pokemon-stat"; import { StatusEffect } from "./status-effect"; import * as Utils from "../utils"; import { Moves } from "./enums/moves"; @@ -11,6 +11,7 @@ import { Type } from "./type"; import { Abilities, FlinchEffectAbAttr, applyAbAttrs } from "./ability"; import { BattlerTagType } from "./enums/battler-tag-type"; import { TerrainType } from "./terrain"; +import { WeatherType } from "./weather"; export enum BattlerTagLapseType { FAINT, @@ -65,6 +66,14 @@ export class BattlerTag { } } +export interface WeatherBattlerTag { + weatherTypes: WeatherType[]; +} + +export interface TerrainBattlerTag { + terrainTypes: TerrainType[]; +} + export class RechargingTag extends BattlerTag { constructor(sourceMove: Moves) { super(BattlerTagType.RECHARGING, BattlerTagLapseType.MOVE, 1, sourceMove); @@ -722,6 +731,66 @@ export class SlowStartTag extends AbilityBattlerTag { } } +export class HighestStatBoostTag extends AbilityBattlerTag { + public stat: Stat; + public multiplier: number; + + constructor(tagType: BattlerTagType, ability: Abilities) { + super(tagType, ability, BattlerTagLapseType.CUSTOM, 1); + } + + onAdd(pokemon: Pokemon): void { + super.onAdd(pokemon); + + const stats = [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ]; + let highestStat: Stat; + stats.map(s => pokemon.getBattleStat(s)).reduce((highestValue: integer, value: integer, i: integer) => { + if (value > highestValue) { + highestStat = stats[i]; + return highestValue += value; + } + return highestValue; + }, 0); + + this.stat = highestStat; + + switch (this.stat) { + case Stat.SPD: + this.multiplier = 1.5; + break; + default: + this.multiplier = 1.3; + break; + } + + pokemon.scene.queueMessage(getPokemonMessage(pokemon, `'s ${getStatName(highestStat)}\nwas heightened!`), null, false, null, true); + } + + onRemove(pokemon: Pokemon): void { + super.onRemove(pokemon); + + pokemon.scene.queueMessage(`The effects of ${getPokemonMessage(pokemon, `'s\n${pokemon.getAbility().name} wore off!`)}`); + } +} + +export class WeatherHighestStatBoostTag extends HighestStatBoostTag implements WeatherBattlerTag { + public weatherTypes: WeatherType[]; + + constructor(tagType: BattlerTagType, ability: Abilities, ...weatherTypes: WeatherType[]) { + super(tagType, ability); + this.weatherTypes = weatherTypes; + } +} + +export class TerrainHighestStatBoostTag extends HighestStatBoostTag implements TerrainBattlerTag { + public terrainTypes: TerrainType[]; + + constructor(tagType: BattlerTagType, ability: Abilities, ...terrainTypes: TerrainType[]) { + super(tagType, ability); + this.terrainTypes = terrainTypes; + } +} + export class HideSpriteTag extends BattlerTag { constructor(tagType: BattlerTagType, turnCount: integer, sourceMove: Moves) { super(tagType, BattlerTagLapseType.MOVE_EFFECT, turnCount, sourceMove); @@ -840,6 +909,10 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc return new TruantTag(); case BattlerTagType.SLOW_START: return new SlowStartTag(); + case BattlerTagType.PROTOSYNTHESIS: + return new WeatherHighestStatBoostTag(tagType, Abilities.PROTOSYNTHESIS, WeatherType.SUNNY, WeatherType.HARSH_SUN); + case BattlerTagType.QUARK_DRIVE: + return new TerrainHighestStatBoostTag(tagType, Abilities.QUARK_DRIVE, TerrainType.ELECTRIC); case BattlerTagType.FLYING: case BattlerTagType.UNDERGROUND: case BattlerTagType.HIDDEN: diff --git a/src/data/enums/battler-tag-type.ts b/src/data/enums/battler-tag-type.ts index 20f73e63a4a..f02d1ea3420 100644 --- a/src/data/enums/battler-tag-type.ts +++ b/src/data/enums/battler-tag-type.ts @@ -28,6 +28,8 @@ export enum BattlerTagType { PERISH_SONG = "PERISH_SONG", TRUANT = "TRUANT", SLOW_START = "SLOW_START", + PROTOSYNTHESIS = "PROTOSYNTHESIS", + QUARK_DRIVE = "QUARK_DRIVE", FLYING = "FLYING", UNDERGROUND = "UNDERGROUND", HIDDEN = "HIDDEN", diff --git a/src/data/weather.ts b/src/data/weather.ts index b030df32ee1..802e6cf5360 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -5,7 +5,7 @@ import { Type } from "./type"; import Move, { AttackMove } from "./move"; import * as Utils from "../utils"; import BattleScene from "../battle-scene"; -import { SuppressWeatherEffectAbAttr, applyPreWeatherEffectAbAttrs } from "./ability"; +import { SuppressWeatherEffectAbAttr } from "./ability"; import { TerrainType } from "./terrain"; export enum WeatherType { diff --git a/src/field/arena.ts b/src/field/arena.ts index 41cf01f1179..c673d4c9303 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -1,11 +1,11 @@ import BattleScene from "../battle-scene"; -import { BiomePoolTier, BiomeTierPokemonPools, PokemonPools, BiomeTierTrainerPools, biomePokemonPools, biomeTrainerPools } from "../data/biomes"; +import { BiomePoolTier, PokemonPools, BiomeTierTrainerPools, biomePokemonPools, biomeTrainerPools } from "../data/biomes"; import { Biome } from "../data/enums/biome"; import * as Utils from "../utils"; import PokemonSpecies, { getPokemonSpecies } from "../data/pokemon-species"; import { Species } from "../data/enums/species"; import { Weather, WeatherType, getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage } from "../data/weather"; -import { CommonAnimPhase } from "../phases"; +import { CommonAnimPhase, WeatherEffectPhase } from "../phases"; import { CommonAnim } from "../data/battle-anims"; import { Type } from "../data/type"; import Move from "../data/move"; @@ -16,6 +16,7 @@ import { BattlerIndex } from "../battle"; import { Moves } from "../data/enums/moves"; import { TimeOfDay } from "../data/enums/time-of-day"; import { Terrain, TerrainType } from "../data/terrain"; +import { PostTerrainChangeAbAttr, PostWeatherChangeAbAttr, applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs } from "../data/ability"; const WEATHER_OVERRIDE = WeatherType.NONE; @@ -268,10 +269,18 @@ export class Arena { this.weather = weather ? new Weather(weather, viaMove ? 5 : 0) : null; if (this.weather) { + this.scene.tryReplacePhase(phase => phase instanceof WeatherEffectPhase && phase.weather.weatherType === oldWeatherType, new WeatherEffectPhase(this.scene, this.weather)); this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1))); this.scene.queueMessage(getWeatherStartMessage(weather)); - } else + } else { + this.scene.tryRemovePhase(phase => phase instanceof WeatherEffectPhase && phase.weather.weatherType === oldWeatherType); this.scene.queueMessage(getWeatherClearMessage(oldWeatherType)); + } + + this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => { + pokemon.findAndRemoveTags(t => 'weatherTypes' in t && !(t.weatherTypes as WeatherType[]).find(t => t === weather)); + applyPostWeatherChangeAbAttrs(PostWeatherChangeAbAttr, pokemon, weather); + }); return true; } @@ -290,6 +299,11 @@ export class Arena { this.scene.queueMessage(getTerrainStartMessage(terrain)); } else this.scene.queueMessage(getTerrainClearMessage(oldTerrainType)); + + this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => { + pokemon.findAndRemoveTags(t => 'terrainTypes' in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain)); + applyPostTerrainChangeAbAttrs(PostTerrainChangeAbAttr, pokemon, terrain); + }); return true; } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 9a88ea1d09c..7ebb5dcfd5b 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1333,15 +1333,26 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return !!tag; } - removeTagsBySourceId(sourceId: integer): void { + findAndRemoveTags(tagFilter: ((tag: BattlerTag) => boolean)): boolean { + if (!this.summonData) + return false; const tags = this.summonData.tags; - tags.filter(t => t.sourceId === sourceId).forEach(t => { - t.onRemove(this); - tags.splice(tags.indexOf(t), 1); - }); + const tagsToRemove = tags.filter(t => tagFilter(t)); + for (let tag of tagsToRemove) { + tag.turnCount = 0; + tag.onRemove(this); + tags.splice(tags.indexOf(tag), 1); + } + return true; + } + + removeTagsBySourceId(sourceId: integer): void { + this.findAndRemoveTags(t => t.sourceId === sourceId); } transferTagsBySourceId(sourceId: integer, newSourceId: integer): void { + if (!this.summonData) + return; const tags = this.summonData.tags; tags.filter(t => t.sourceId === sourceId).forEach(t => t.sourceId = newSourceId); } diff --git a/src/phases.ts b/src/phases.ts index d29317941c5..6e488aef579 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1840,7 +1840,7 @@ export class TurnEndPhase extends FieldPhase { if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(), - Math.max(pokemon.getMaxHp() >> 4, 1), getPokemonMessage(pokemon, ' regained\nhealth from the Grassy Terrain!'), true)); + Math.max(pokemon.getMaxHp() >> 4, 1), getPokemonMessage(pokemon, '\'s HP was restored.'), true)); } applyPostTurnAbAttrs(PostTurnAbAttr, pokemon); @@ -2505,7 +2505,7 @@ export class StatChangePhase extends PokemonPhase { } export class WeatherEffectPhase extends CommonAnimPhase { - private weather: Weather; + public weather: Weather; constructor(scene: BattleScene, weather: Weather) { super(scene, undefined, undefined, CommonAnim.SUNNY + (weather.weatherType - 1));