Implement Protosynthesis and Quark Drive

pull/16/head
Flashfyre 2024-03-18 21:22:27 -04:00
parent aa1e1a480f
commit 5c02455c97
8 changed files with 203 additions and 16 deletions

View File

@ -1539,6 +1539,24 @@ export default class BattleScene extends Phaser.Scene {
return this.phaseQueue.find(phaseFilter); 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 { pushMovePhase(movePhase: MovePhase, priorityOverride?: integer): void {
const priority = priorityOverride !== undefined ? priorityOverride : movePhase.move.getMove().priority; const priority = priorityOverride !== undefined ? priorityOverride : movePhase.move.getMove().priority;
const lowerPriorityPhase = this.phaseQueue.find(p => p instanceof MovePhase && p.move.getMove().priority < priority); const lowerPriorityPhase = this.phaseQueue.find(p => p instanceof MovePhase && p.move.getMove().priority < priority);

View File

@ -84,8 +84,8 @@ export abstract class AbAttr {
public showAbility: boolean; public showAbility: boolean;
private extraCondition: AbAttrCondition; private extraCondition: AbAttrCondition;
constructor(showAbility?: boolean) { constructor(showAbility: boolean = true) {
this.showAbility = showAbility === undefined || showAbility; this.showAbility = showAbility;
} }
apply(pokemon: Pokemon, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> { apply(pokemon: Pokemon, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
@ -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 { export class PostWeatherLapseAbAttr extends AbAttr {
protected weatherTypes: WeatherType[]; 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 { function getTerrainCondition(...terrainTypes: TerrainType[]): AbAttrCondition {
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const terrainType = pokemon.scene.arena.terrain?.terrainType; const terrainType = pokemon.scene.arena.terrain?.terrainType;
@ -1420,11 +1475,21 @@ export function applyPostTurnAbAttrs(attrType: { new(...args: any[]): PostTurnAb
return applyAbAttrsInternal<PostTurnAbAttr>(attrType, pokemon, attr => attr.applyPostTurn(pokemon, args)); return applyAbAttrsInternal<PostTurnAbAttr>(attrType, pokemon, attr => attr.applyPostTurn(pokemon, args));
} }
export function applyPostWeatherChangeAbAttrs(attrType: { new(...args: any[]): PostWeatherChangeAbAttr },
pokemon: Pokemon, weather: WeatherType, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostWeatherChangeAbAttr>(attrType, pokemon, attr => attr.applyPostWeatherChange(pokemon, weather, args));
}
export function applyPostWeatherLapseAbAttrs(attrType: { new(...args: any[]): PostWeatherLapseAbAttr }, export function applyPostWeatherLapseAbAttrs(attrType: { new(...args: any[]): PostWeatherLapseAbAttr },
pokemon: Pokemon, weather: Weather, ...args: any[]): Promise<void> { pokemon: Pokemon, weather: Weather, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostWeatherLapseAbAttr>(attrType, pokemon, attr => attr.applyPostWeatherLapse(pokemon, weather, args)); return applyAbAttrsInternal<PostWeatherLapseAbAttr>(attrType, pokemon, attr => attr.applyPostWeatherLapse(pokemon, weather, args));
} }
export function applyPostTerrainChangeAbAttrs(attrType: { new(...args: any[]): PostTerrainChangeAbAttr },
pokemon: Pokemon, terrain: TerrainType, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PostTerrainChangeAbAttr>(attrType, pokemon, attr => attr.applyPostTerrainChange(pokemon, terrain, args));
}
export function applyCheckTrappedAbAttrs(attrType: { new(...args: any[]): CheckTrappedAbAttr }, export function applyCheckTrappedAbAttrs(attrType: { new(...args: any[]): CheckTrappedAbAttr },
pokemon: Pokemon, trapped: Utils.BooleanHolder, ...args: any[]): Promise<void> { pokemon: Pokemon, trapped: Utils.BooleanHolder, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<CheckTrappedAbAttr>(attrType, pokemon, attr => attr.applyCheckTrapped(pokemon, trapped, args), true); return applyAbAttrsInternal<CheckTrappedAbAttr>(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) 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), .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.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), .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), .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) 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(), .ignorable(),

View File

@ -2,7 +2,7 @@ import { CommonAnim, CommonBattleAnim } from "./battle-anims";
import { CommonAnimPhase, MoveEffectPhase, MovePhase, PokemonHealPhase, ShowAbilityPhase } from "../phases"; import { CommonAnimPhase, MoveEffectPhase, MovePhase, PokemonHealPhase, ShowAbilityPhase } from "../phases";
import { getPokemonMessage } from "../messages"; import { getPokemonMessage } from "../messages";
import Pokemon, { MoveResult, HitResult } from "../field/pokemon"; import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
import { Stat } from "./pokemon-stat"; import { Stat, getStatName } from "./pokemon-stat";
import { StatusEffect } from "./status-effect"; import { StatusEffect } from "./status-effect";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { Moves } from "./enums/moves"; import { Moves } from "./enums/moves";
@ -11,6 +11,7 @@ import { Type } from "./type";
import { Abilities, FlinchEffectAbAttr, applyAbAttrs } from "./ability"; import { Abilities, FlinchEffectAbAttr, applyAbAttrs } from "./ability";
import { BattlerTagType } from "./enums/battler-tag-type"; import { BattlerTagType } from "./enums/battler-tag-type";
import { TerrainType } from "./terrain"; import { TerrainType } from "./terrain";
import { WeatherType } from "./weather";
export enum BattlerTagLapseType { export enum BattlerTagLapseType {
FAINT, FAINT,
@ -65,6 +66,14 @@ export class BattlerTag {
} }
} }
export interface WeatherBattlerTag {
weatherTypes: WeatherType[];
}
export interface TerrainBattlerTag {
terrainTypes: TerrainType[];
}
export class RechargingTag extends BattlerTag { export class RechargingTag extends BattlerTag {
constructor(sourceMove: Moves) { constructor(sourceMove: Moves) {
super(BattlerTagType.RECHARGING, BattlerTagLapseType.MOVE, 1, sourceMove); 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 { export class HideSpriteTag extends BattlerTag {
constructor(tagType: BattlerTagType, turnCount: integer, sourceMove: Moves) { constructor(tagType: BattlerTagType, turnCount: integer, sourceMove: Moves) {
super(tagType, BattlerTagLapseType.MOVE_EFFECT, turnCount, sourceMove); super(tagType, BattlerTagLapseType.MOVE_EFFECT, turnCount, sourceMove);
@ -840,6 +909,10 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc
return new TruantTag(); return new TruantTag();
case BattlerTagType.SLOW_START: case BattlerTagType.SLOW_START:
return new SlowStartTag(); 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.FLYING:
case BattlerTagType.UNDERGROUND: case BattlerTagType.UNDERGROUND:
case BattlerTagType.HIDDEN: case BattlerTagType.HIDDEN:

View File

@ -28,6 +28,8 @@ export enum BattlerTagType {
PERISH_SONG = "PERISH_SONG", PERISH_SONG = "PERISH_SONG",
TRUANT = "TRUANT", TRUANT = "TRUANT",
SLOW_START = "SLOW_START", SLOW_START = "SLOW_START",
PROTOSYNTHESIS = "PROTOSYNTHESIS",
QUARK_DRIVE = "QUARK_DRIVE",
FLYING = "FLYING", FLYING = "FLYING",
UNDERGROUND = "UNDERGROUND", UNDERGROUND = "UNDERGROUND",
HIDDEN = "HIDDEN", HIDDEN = "HIDDEN",

View File

@ -5,7 +5,7 @@ import { Type } from "./type";
import Move, { AttackMove } from "./move"; import Move, { AttackMove } from "./move";
import * as Utils from "../utils"; import * as Utils from "../utils";
import BattleScene from "../battle-scene"; import BattleScene from "../battle-scene";
import { SuppressWeatherEffectAbAttr, applyPreWeatherEffectAbAttrs } from "./ability"; import { SuppressWeatherEffectAbAttr } from "./ability";
import { TerrainType } from "./terrain"; import { TerrainType } from "./terrain";
export enum WeatherType { export enum WeatherType {

View File

@ -1,11 +1,11 @@
import BattleScene from "../battle-scene"; 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 { Biome } from "../data/enums/biome";
import * as Utils from "../utils"; import * as Utils from "../utils";
import PokemonSpecies, { getPokemonSpecies } from "../data/pokemon-species"; import PokemonSpecies, { getPokemonSpecies } from "../data/pokemon-species";
import { Species } from "../data/enums/species"; import { Species } from "../data/enums/species";
import { Weather, WeatherType, getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage } from "../data/weather"; 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 { 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";
@ -16,6 +16,7 @@ import { BattlerIndex } from "../battle";
import { Moves } from "../data/enums/moves"; import { Moves } from "../data/enums/moves";
import { TimeOfDay } from "../data/enums/time-of-day"; import { TimeOfDay } from "../data/enums/time-of-day";
import { Terrain, TerrainType } from "../data/terrain"; import { Terrain, TerrainType } from "../data/terrain";
import { PostTerrainChangeAbAttr, PostWeatherChangeAbAttr, applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs } from "../data/ability";
const WEATHER_OVERRIDE = WeatherType.NONE; const WEATHER_OVERRIDE = WeatherType.NONE;
@ -268,10 +269,18 @@ export class Arena {
this.weather = weather ? new Weather(weather, viaMove ? 5 : 0) : null; this.weather = weather ? new Weather(weather, viaMove ? 5 : 0) : null;
if (this.weather) { 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.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1)));
this.scene.queueMessage(getWeatherStartMessage(weather)); 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.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; return true;
} }
@ -291,6 +300,11 @@ export class Arena {
} else } else
this.scene.queueMessage(getTerrainClearMessage(oldTerrainType)); 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; return true;
} }

View File

@ -1333,15 +1333,26 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return !!tag; return !!tag;
} }
removeTagsBySourceId(sourceId: integer): void { findAndRemoveTags(tagFilter: ((tag: BattlerTag) => boolean)): boolean {
if (!this.summonData)
return false;
const tags = this.summonData.tags; const tags = this.summonData.tags;
tags.filter(t => t.sourceId === sourceId).forEach(t => { const tagsToRemove = tags.filter(t => tagFilter(t));
t.onRemove(this); for (let tag of tagsToRemove) {
tags.splice(tags.indexOf(t), 1); 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 { transferTagsBySourceId(sourceId: integer, newSourceId: integer): void {
if (!this.summonData)
return;
const tags = this.summonData.tags; const tags = this.summonData.tags;
tags.filter(t => t.sourceId === sourceId).forEach(t => t.sourceId = newSourceId); tags.filter(t => t.sourceId === sourceId).forEach(t => t.sourceId = newSourceId);
} }

View File

@ -1840,7 +1840,7 @@ export class TurnEndPhase extends FieldPhase {
if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) {
this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(), 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); applyPostTurnAbAttrs(PostTurnAbAttr, pokemon);
@ -2505,7 +2505,7 @@ export class StatChangePhase extends PokemonPhase {
} }
export class WeatherEffectPhase extends CommonAnimPhase { export class WeatherEffectPhase extends CommonAnimPhase {
private weather: Weather; public weather: Weather;
constructor(scene: BattleScene, weather: Weather) { constructor(scene: BattleScene, weather: Weather) {
super(scene, undefined, undefined, CommonAnim.SUNNY + (weather.weatherType - 1)); super(scene, undefined, undefined, CommonAnim.SUNNY + (weather.weatherType - 1));