From 15cfd3bad4772d3c6cd3bcc8486ab5f99bce26c5 Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Sat, 6 May 2023 00:42:01 -0400 Subject: [PATCH] Add color change ability, some moves, and fix mimic --- src/battle-phases.ts | 42 ++++++++++--------- src/data/ability.ts | 24 ++++++++++- src/data/move.ts | 78 +++++++++++++++++++++++++++++------ src/modifier/modifier-type.ts | 14 +++---- src/modifier/modifier.ts | 4 +- src/pokemon.ts | 33 ++++++++++++--- src/system/auto-play.ts | 4 +- src/system/pokemon-data.ts | 1 + src/ui/fight-ui-handler.ts | 4 +- 9 files changed, 152 insertions(+), 52 deletions(-) diff --git a/src/battle-phases.ts b/src/battle-phases.ts index 71ebed2b6..530cb9ade 100644 --- a/src/battle-phases.ts +++ b/src/battle-phases.ts @@ -413,12 +413,16 @@ export class SummonPhase extends BattlePhase { } end() { - if (this.scene.getPlayerPokemon().shiny) + const pokemon = this.scene.getPlayerPokemon(); + + if (pokemon.shiny) this.scene.unshiftPhase(new ShinySparklePhase(this.scene, true)); - this.scene.arena.applyTags(ArenaTrapTag, this.scene.getPlayerPokemon()); + pokemon.resetTurnData(); - applyPostSummonAbAttrs(PostSummonAbAttr, this.scene.getPlayerPokemon()); + this.scene.arena.applyTags(ArenaTrapTag, pokemon); + + applyPostSummonAbAttrs(PostSummonAbAttr, pokemon); super.end(); } @@ -594,8 +598,8 @@ export class CommandPhase extends FieldPhase { const moveQueue = playerPokemon.getMoveQueue(); while (moveQueue.length && moveQueue[0] - && moveQueue[0].move && (!playerPokemon.moveset.find(m => m.moveId === moveQueue[0].move) - || !playerPokemon.moveset[playerPokemon.moveset.findIndex(m => m.moveId === moveQueue[0].move)].isUsable(moveQueue[0].ignorePP))) + && moveQueue[0].move && (!playerPokemon.getMoveset().find(m => m.moveId === moveQueue[0].move) + || !playerPokemon.getMoveset()[playerPokemon.getMoveset().findIndex(m => m.moveId === moveQueue[0].move)].isUsable(moveQueue[0].ignorePP))) moveQueue.shift(); if (moveQueue.length) { @@ -603,8 +607,8 @@ export class CommandPhase extends FieldPhase { if (!queuedMove.move) this.handleCommand(Command.FIGHT, -1, false); else { - const moveIndex = playerPokemon.moveset.findIndex(m => m.moveId === queuedMove.move); - if (moveIndex > -1 && playerPokemon.moveset[moveIndex].isUsable(queuedMove.ignorePP)) + const moveIndex = playerPokemon.getMoveset().findIndex(m => m.moveId === queuedMove.move); + if (moveIndex > -1 && playerPokemon.getMoveset()[moveIndex].isUsable(queuedMove.ignorePP)) this.handleCommand(Command.FIGHT, moveIndex, queuedMove.ignorePP); } } else @@ -647,12 +651,12 @@ export class CommandPhase extends FieldPhase { } if (playerPokemon.trySelectMove(cursor, args[0] as boolean)) { - playerMove = playerPokemon.moveset[cursor]; + playerMove = playerPokemon.getMoveset()[cursor]; const playerPhase = new PlayerMovePhase(this.scene, playerPokemon, playerMove, false, args[0] as boolean); this.scene.pushPhase(playerPhase); success = true; - } else if (cursor < playerPokemon.moveset.length) { - const move = playerPokemon.moveset[cursor]; + } else if (cursor < playerPokemon.getMoveset().length) { + const move = playerPokemon.getMoveset()[cursor]; if (move.isDisabled()) { this.scene.ui.setMode(Mode.MESSAGE); this.scene.ui.showText(`${move.getName()} is disabled!`, null, () => { @@ -703,7 +707,7 @@ export class CommandPhase extends FieldPhase { const enemyNextMove = enemyPokemon.getNextMove(); let enemyMove: PokemonMove; if (enemyNextMove.move) { - enemyMove = enemyPokemon.moveset.find(m => m.moveId === enemyNextMove.move) || new PokemonMove(enemyNextMove.move, 0, 0); + enemyMove = enemyPokemon.getMoveset().find(m => m.moveId === enemyNextMove.move) || new PokemonMove(enemyNextMove.move, 0, 0); const enemyPhase = new EnemyMovePhase(this.scene, enemyPokemon, enemyMove, false, enemyNextMove.ignorePP); if (isDelayed(command, playerMove, enemyMove)) this.scene.unshiftPhase(enemyPhase); @@ -753,7 +757,7 @@ export class TurnEndPhase extends FieldPhase { pokemon.lapseTags(BattlerTagLapseType.TURN_END); - const disabledMoves = pokemon.moveset.filter(m => m.isDisabled()); + const disabledMoves = pokemon.getMoveset().filter(m => m.isDisabled()); for (let dm of disabledMoves) { if (!--dm.disableTurns) this.scene.pushPhase(new MessagePhase(this.scene, `${dm.getName()} is disabled\nno more!`)); @@ -1048,7 +1052,7 @@ abstract class MoveEffectPhase extends PokemonPhase { if (!isProtected && !this.move.getMove().getAttrs(ChargeAttr).filter(ca => (ca as ChargeAttr).chargeEffect).length) { applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveHitEffectAttr && (!!target.hp || (attr as MoveHitEffectAttr).selfTarget), user, target, this.move.getMove()); if (target.hp) - applyPostDefendAbAttrs(PostDefendAbAttr, user, target, this.move, result); + applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, result); if (this.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user); } @@ -1763,23 +1767,23 @@ export class LearnMovePhase extends PartyMemberPokemonPhase { const pokemon = this.getPokemon(); const move = allMoves[this.moveId]; - const existingMoveIndex = pokemon.moveset.findIndex(m => m?.moveId === move.id); + const existingMoveIndex = pokemon.getMoveset().findIndex(m => m?.moveId === move.id); if (existingMoveIndex > -1) { this.end(); return; } - const emptyMoveIndex = pokemon.moveset.length < 4 - ? pokemon.moveset.length - : pokemon.moveset.findIndex(m => m === null); + const emptyMoveIndex = pokemon.getMoveset().length < 4 + ? pokemon.getMoveset().length + : pokemon.getMoveset().findIndex(m => m === null); const messageMode = this.scene.ui.getHandler() instanceof EvolutionSceneHandler ? Mode.EVOLUTION_SCENE : Mode.MESSAGE; if (emptyMoveIndex > -1) { - pokemon.moveset[emptyMoveIndex] = new PokemonMove(this.moveId, 0, 0); + pokemon.setMove(emptyMoveIndex, this.moveId); initMoveAnim(this.moveId).then(() => { loadMoveAnimAssets(this.scene, [ this.moveId ], true) .then(() => { @@ -1820,7 +1824,7 @@ export class LearnMovePhase extends PartyMemberPokemonPhase { this.scene.ui.showText('@d{32}1, @d{15}2, and@d{15}… @d{15}… @d{15}… @d{15}@s{pb_bounce_1}Poof!', null, () => { this.scene.ui.showText(`${pokemon.name} forgot how to\nuse ${pokemon.moveset[moveIndex].getName()}.`, null, () => { this.scene.ui.showText('And…', null, () => { - pokemon.moveset[moveIndex] = null; + pokemon.setMove(moveIndex, Moves.NONE); this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, this.moveId)); this.end(); }, null, true); diff --git a/src/data/ability.ts b/src/data/ability.ts index b0946cac9..13f7afdbc 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -7,7 +7,7 @@ import { getPokemonMessage } from "../messages"; import { Weather, WeatherType } from "./weather"; import { BattlerTag, BattlerTagType, TrappedTag } from "./battler-tag"; import { StatusEffect, getStatusEffectDescriptor } from "./status-effect"; -import { MoveFlags, Moves, RecoilAttr } from "./move"; +import { MoveFlags, Moves, RecoilAttr, allMoves } from "./move"; export class Ability { public id: Abilities; @@ -230,6 +230,25 @@ export class PostDefendAbAttr extends AbAttr { } } +export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr { + applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, moveResult: MoveResult, args: any[]): boolean { + if (moveResult < MoveResult.NO_EFFECT) { + const type = move.getMove().type; + const type2 = pokemon.species.type2; + if (type !== pokemon.getTypes()[0] && type !== type2) { + pokemon.summonData.types = [ type ].concat(type2 !== null ? [ type2 ] : []); + return true; + } + } + + return false; + } + + getTriggerMessage(pokemon: Pokemon, ...args: any[]): string { + return getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nmade it the ${Type[pokemon.getTypes()[0]]} type!`); + } +} + export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr { private chance: integer; private effects: StatusEffect[]; @@ -1104,7 +1123,8 @@ export function initAbilities() { .attr(ProtectStatAttr), new Ability(Abilities.CLOUD_NINE, "Cloud Nine", "Eliminates the effects of non-severe weather.", 3) .attr(SuppressWeatherEffectAbAttr), - new Ability(Abilities.COLOR_CHANGE, "Color Change (N)", "Changes the POKéMON's type to the foe's move.", 3), + new Ability(Abilities.COLOR_CHANGE, "Color Change", "Changes the POKéMON's type to the foe's move.", 3) + .attr(PostDefendTypeChangeAbAttr), new Ability(Abilities.COMPOUND_EYES, "Compound Eyes", "The POKéMON's accuracy is boosted.", 3) .attr(BattleStatMultiplierAbAttr, BattleStat.ACC, 1.3), new Ability(Abilities.CUTE_CHARM, "Cute Charm", "Contact with the POKéMON may cause infatuation.", 3) diff --git a/src/data/move.ts b/src/data/move.ts index 5c1beaffe..fc7749555 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -3,7 +3,7 @@ import { DamagePhase, EnemyMovePhase, ObtainStatusEffectPhase, PlayerMovePhase, import { BattleStat } from "./battle-stat"; import { BattlerTagType } from "./battler-tag"; import { getPokemonMessage } from "../messages"; -import Pokemon, { EnemyPokemon, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../pokemon"; +import Pokemon, { AttackMoveResult, EnemyPokemon, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../pokemon"; import { StatusEffect, getStatusEffectDescriptor } from "./status-effect"; import { Type } from "./type"; import * as Utils from "../utils"; @@ -847,6 +847,29 @@ export class TargetHalfHpDamageAttr extends FixedDamageAttr { } } +type MoveFilter = (move: Move) => boolean; + +export class CounterDamageAttr extends FixedDamageAttr { + private moveFilter: MoveFilter; + + constructor(moveFilter: MoveFilter) { + super(0); + + this.moveFilter = moveFilter; + } + + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + const damage = user.turnData.attacksReceived.filter(ar => this.moveFilter(allMoves[ar.move])).reduce((total: integer, ar: AttackMoveResult) => total + ar.damage, 0); + (args[0] as Utils.IntegerHolder).value = Math.max(damage * 2, 1); + + return true; + } + + getCondition(): MoveCondition { + return (user: Pokemon, target: Pokemon, move: Move) => !!user.turnData.attacksReceived.filter(ar => this.moveFilter(allMoves[ar.move])).length; + } +} + export class RecoilAttr extends MoveEffectAttr { private useHp: boolean; @@ -1460,11 +1483,11 @@ export class DisableMoveAttr extends MoveEffectAttr { if (turnMove.virtual) continue; - const moveIndex = target.moveset.findIndex(m => m.moveId === turnMove.move); + const moveIndex = target.getMoveset().findIndex(m => m.moveId === turnMove.move); if (moveIndex === -1) return false; - const disabledMove = target.moveset[moveIndex]; + const disabledMove = target.getMoveset()[moveIndex]; disabledMove.disableTurns = 4; user.scene.queueMessage(getPokemonMessage(target, `'s ${disabledMove.getName()}\nwas disabled!`)); @@ -1484,7 +1507,7 @@ export class DisableMoveAttr extends MoveEffectAttr { if (turnMove.virtual) continue; - const move = target.moveset.find(m => m.moveId === turnMove.move); + const move = target.getMoveset().find(m => m.moveId === turnMove.move); if (!move) continue; @@ -1733,7 +1756,7 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const moveset = (!this.enemyMoveset ? user : target).moveset; + const moveset = (!this.enemyMoveset ? user : target).getMoveset(); const moves = moveset.filter(m => !m.getMove().hasFlag(MoveFlags.IGNORE_VIRTUAL)); if (moves.length) { const move = moves[Utils.randInt(moves.length)]; @@ -1805,6 +1828,32 @@ export class CopyMoveAttr extends OverrideMoveEffectAttr { } } +export class MovesetCopyMoveAttr extends OverrideMoveEffectAttr { + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + const targetMoves = target.getMoveHistory().filter(m => !m.virtual); + if (!targetMoves.length) + return false; + + const copiedMove = allMoves[targetMoves[0].move]; + + const thisMoveIndex = user.getMoveset().findIndex(m => m.moveId === move.id); + + if (thisMoveIndex === -1) + return false; + + user.summonData.moveset = user.getMoveset().slice(0); + user.summonData.moveset[thisMoveIndex] = new PokemonMove(copiedMove.id, 0, 0); + + user.scene.queueMessage(getPokemonMessage(user, ` copied\n${copiedMove.name}!`)); + + return true; + } + + getCondition(): MoveCondition { + return targetMoveCopiableCondition; + } +} + export class SketchAttr extends MoveEffectAttr { constructor() { super(true); @@ -1820,12 +1869,12 @@ export class SketchAttr extends MoveEffectAttr { const sketchedMove = allMoves[targetMoves[0].move]; - const sketchIndex = user.moveset.findIndex(m => m.moveId === move.id); + const sketchIndex = user.getMoveset().findIndex(m => m.moveId === move.id); if (sketchIndex === -1) return false; - user.moveset[sketchIndex] = new PokemonMove(sketchedMove.id, 0, 0); + user.setMove(sketchIndex, sketchedMove.id); user.scene.queueMessage(getPokemonMessage(user, ` sketched\n${sketchedMove.name}!`)); @@ -1843,7 +1892,7 @@ export class SketchAttr extends MoveEffectAttr { const sketchableMove = targetMoves[0]; - if (user.moveset.find(m => m.moveId === sketchableMove.move)) + if (user.getMoveset().find(m => m.moveId === sketchableMove.move)) return false; return true; @@ -2034,7 +2083,8 @@ export function initMoves() { .attr(RecoilAttr), new AttackMove(Moves.LOW_KICK, "Low Kick", Type.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 20, 12, "The heavier the opponent, the stronger the attack.", -1, 0, 1) .attr(WeightPowerAttr), - new AttackMove(Moves.COUNTER, "Counter (N)", Type.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 20, -1, "When hit by a Physical Attack, user strikes back with 2x power.", -1, -5, 1) + new AttackMove(Moves.COUNTER, "Counter", Type.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 20, -1, "When hit by a Physical Attack, user strikes back with 2x power.", -1, -5, 1) + .attr(CounterDamageAttr, (move: Move) => move.category === MoveCategory.PHYSICAL) .target(MoveTarget.ATTACKER), new AttackMove(Moves.SEISMIC_TOSS, "Seismic Toss", Type.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 20, -1, "Inflicts damage equal to user's level (maximum 150).", -1, 0, 1) .attr(LevelPowerAttr), @@ -2114,7 +2164,7 @@ export function initMoves() { new AttackMove(Moves.NIGHT_SHADE, "Night Shade", Type.GHOST, MoveCategory.SPECIAL, -1, 100, 15, 42, "Inflicts damage equal to user's level (maximum 150).", -1, 0, 1) .attr(LevelPowerAttr), new StatusMove(Moves.MIMIC, "Mimic", Type.NORMAL, -1, 10, -1, "Copies the opponent's last move.", -1, 0, 1) - .attr(CopyMoveAttr) + .attr(MovesetCopyMoveAttr) .ignoresVirtual(), new StatusMove(Moves.SCREECH, "Screech", Type.NORMAL, 85, 40, -1, "Sharply lowers opponent's Defense.", -1, 0, 1) .attr(StatChangeAttr, BattleStat.DEF, -2), @@ -2150,7 +2200,8 @@ export function initMoves() { new SelfStatusMove(Moves.METRONOME, "Metronome", Type.NORMAL, -1, 10, 80, "User performs almost any move in the game at random.", -1, 0, 1) .attr(RandomMoveAttr) .ignoresVirtual(), - new SelfStatusMove(Moves.MIRROR_MOVE, "Mirror Move (N)", Type.FLYING, -1, 20, -1, "User performs the opponent's last move.", -1, 0, 1) + new SelfStatusMove(Moves.MIRROR_MOVE, "Mirror Move", Type.FLYING, -1, 20, -1, "User performs the opponent's last move.", -1, 0, 1) + .attr(CopyMoveAttr) .ignoresVirtual(), new AttackMove(Moves.SELF_DESTRUCT, "Self-Destruct", Type.NORMAL, MoveCategory.PHYSICAL, 200, 100, 5, -1, "User faints.", -1, 0, 1) .attr(SacrificialAttr) @@ -2434,7 +2485,8 @@ export function initMoves() { .target(MoveTarget.BOTH_SIDES), new AttackMove(Moves.CRUNCH, "Crunch", Type.DARK, MoveCategory.PHYSICAL, 80, 100, 15, 108, "May lower opponent's Defense.", 20, 0, 2) .attr(StatChangeAttr, BattleStat.DEF, -1), - new AttackMove(Moves.MIRROR_COAT, "Mirror Coat (N)", Type.PSYCHIC, MoveCategory.SPECIAL, -1, 100, 20, -1, "When hit by a Special Attack, user strikes back with 2x power.", -1, -5, 2) + new AttackMove(Moves.MIRROR_COAT, "Mirror Coat", Type.PSYCHIC, MoveCategory.SPECIAL, -1, 100, 20, -1, "When hit by a Special Attack, user strikes back with 2x power.", -1, -5, 2) + .attr(CounterDamageAttr, (move: Move) => move.category === MoveCategory.SPECIAL) .target(MoveTarget.ATTACKER), new SelfStatusMove(Moves.PSYCH_UP, "Psych Up (N)", Type.NORMAL, -1, 10, -1, "Copies the opponent's stat changes.", -1, 0, 2), new AttackMove(Moves.EXTREME_SPEED, "Extreme Speed", Type.NORMAL, MoveCategory.PHYSICAL, 80, 100, 5, -1, "User attacks first.", -1, 2, 2), @@ -2721,7 +2773,7 @@ export function initMoves() { new StatusMove(Moves.GUARD_SWAP, "Guard Swap (N)", Type.PSYCHIC, -1, 10, -1, "User and opponent swap Defense and Special Defense.", -1, 0, 4), new AttackMove(Moves.PUNISHMENT, "Punishment (N)", Type.DARK, MoveCategory.PHYSICAL, -1, 100, 5, -1, "Power increases when opponent's stats have been raised.", -1, 0, 4), new AttackMove(Moves.LAST_RESORT, "Last Resort", Type.NORMAL, MoveCategory.PHYSICAL, 140, 100, 5, -1, "Can only be used after all other moves are used.", -1, 0, 4) - .condition((user: Pokemon, target: Pokemon, move: Move) => !user.moveset.filter(m => m.moveId !== move.id && m.getPpRatio() > 0).length), + .condition((user: Pokemon, target: Pokemon, move: Move) => !user.getMoveset().filter(m => m.moveId !== move.id && m.getPpRatio() > 0).length), new StatusMove(Moves.WORRY_SEED, "Worry Seed (N)", Type.GRASS, 100, 10, -1, "Changes the opponent's Ability to Insomnia.", -1, 0, 4), new AttackMove(Moves.SUCKER_PUNCH, "Sucker Punch (N)", Type.DARK, MoveCategory.PHYSICAL, 70, 100, 5, -1, "User attacks first, but only works if opponent is readying an attack.", -1, 0, 4), new StatusMove(Moves.TOXIC_SPIKES, "Toxic Spikes", Type.POISON, -1, 20, 91, "Poisons opponents when they switch into battle.", -1, 0, 4) diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index e481d1e47..8386f60d1 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -185,7 +185,7 @@ export class PokemonAllMovePpRestoreModifierType extends PokemonModifierType { constructor(name: string, restorePoints: integer, iconImage?: string) { super(name, `Restore ${restorePoints > -1 ? restorePoints : 'all'} PP for all of one POKéMON's moves`, (_type, args) => new Modifiers.PokemonAllMovePpRestoreModifier(this, (args[0] as PlayerPokemon).id, this.restorePoints), (pokemon: PlayerPokemon) => { - if (!pokemon.moveset.filter(m => m.ppUsed).length) + if (!pokemon.getMoveset().filter(m => m.ppUsed).length) return PartyUiHandler.NoEffectMessage; return null; }, iconImage, 'elixir'); @@ -361,7 +361,7 @@ export class TmModifierType extends PokemonModifierType { constructor(moveId: Moves) { super(`TM${Utils.padInt(Object.keys(tmSpecies).indexOf(moveId.toString()) + 1, 3)} - ${allMoves[moveId].name}`, `Teach ${allMoves[moveId].name} to a POKéMON`, (_type, args) => new Modifiers.TmModifier(this, (args[0] as PlayerPokemon).id), (pokemon: PlayerPokemon) => { - if (pokemon.compatibleTms.indexOf(moveId) === -1 || pokemon.moveset.filter(m => m?.moveId === moveId).length) + if (pokemon.compatibleTms.indexOf(moveId) === -1 || pokemon.getMoveset().filter(m => m?.moveId === moveId).length) return PartyUiHandler.NoEffectMessage; return null; }, `tm_${Type[allMoves[moveId].type].toLowerCase()}`, 'tm'); @@ -424,7 +424,7 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator { if (pregenArgs) return new AttackTypeBoosterModifierType(pregenArgs[0] as Type, 20); - const attackMoveTypes = party.map(p => p.moveset.map(m => m.getMove()).filter(m => m instanceof AttackMove).map(m => m.type)).flat(); + const attackMoveTypes = party.map(p => p.getMoveset().map(m => m.getMove()).filter(m => m instanceof AttackMove).map(m => m.type)).flat(); const attackMoveTypeWeights = new Map(); let totalWeight = 0; for (let t of attackMoveTypes) { @@ -648,11 +648,11 @@ const modifierPool = { return thresholdPartyMemberCount; }), new WeightedModifierType(modifierTypes.ETHER, (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.moveset.filter(m => (m.getMove().pp - m.ppUsed) <= 5).length).length, 3); + const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.getMoveset().filter(m => (m.getMove().pp - m.ppUsed) <= 5).length).length, 3); return thresholdPartyMemberCount * 3; }), new WeightedModifierType(modifierTypes.MAX_ETHER, (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.moveset.filter(m => (m.getMove().pp - m.ppUsed) <= 5).length).length, 3); + const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.getMoveset().filter(m => (m.getMove().pp - m.ppUsed) <= 5).length).length, 3); return thresholdPartyMemberCount; }), new WeightedModifierType(modifierTypes.TEMP_STAT_BOOSTER, 4), @@ -685,11 +685,11 @@ const modifierPool = { return thresholdPartyMemberCount; }), new WeightedModifierType(modifierTypes.ELIXIR, (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.moveset.filter(m => (m.getMove().pp - m.ppUsed) <= 5).length).length, 3); + const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.getMoveset().filter(m => (m.getMove().pp - m.ppUsed) <= 5).length).length, 3); return thresholdPartyMemberCount * 3; }), new WeightedModifierType(modifierTypes.MAX_ELIXIR, (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.moveset.filter(m => (m.getMove().pp - m.ppUsed) <= 5).length).length, 3); + const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.getMoveset().filter(m => (m.getMove().pp - m.ppUsed) <= 5).length).length, 3); return thresholdPartyMemberCount; }), new WeightedModifierType(modifierTypes.MAP, (party: Pokemon[]) => { diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 4d4ad907a..bc5b1b6a0 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -699,7 +699,7 @@ export class PokemonPpRestoreModifier extends ConsumablePokemonMoveModifier { apply(args: any[]): boolean { const pokemon = args[0] as Pokemon; - const move = pokemon.moveset[this.moveIndex]; + const move = pokemon.getMoveset()[this.moveIndex]; move.ppUsed = this.restorePoints >= -1 ? Math.max(move.ppUsed - this.restorePoints, 0) : 0; return true; @@ -717,7 +717,7 @@ export class PokemonAllMovePpRestoreModifier extends ConsumablePokemonModifier { apply(args: any[]): boolean { const pokemon = args[0] as Pokemon; - for (let move of pokemon.moveset) + for (let move of pokemon.getMoveset()) move.ppUsed = this.restorePoints >= -1 ? Math.max(move.ppUsed - this.restorePoints, 0) : 0; return true; diff --git a/src/pokemon.ts b/src/pokemon.ts index 0416d95d1..eabdb0e4b 100644 --- a/src/pokemon.ts +++ b/src/pokemon.ts @@ -181,7 +181,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { loadAssets(): Promise { return new Promise(resolve => { - const moveIds = this.moveset.map(m => m.getMove().id); + const moveIds = this.getMoveset().map(m => m.getMove().id); Promise.allSettled(moveIds.map(m => initMoveAnim(m))) .then(() => { loadMoveAnimAssets(this.scene, moveIds); @@ -320,6 +320,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return Math.floor((this.hp / this.getMaxHp()) * 100) / 100; } + getMoveset(): PokemonMove[] { + if (this.summonData?.moveset) + return this.summonData.moveset; + return this.moveset; + } + getTypes(): Type[] { const types = []; @@ -395,6 +401,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return ret; } + + setMove(moveIndex: integer, moveId: Moves): void { + const move = moveId ? new PokemonMove(moveId) : null; + this.moveset[moveId] = move; + if (this.summonData.moveset) + this.summonData.moveset[moveIndex] = move; + } generateAndPopulateMoveset(): void { this.moveset = []; @@ -434,8 +447,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } trySelectMove(moveIndex: integer, ignorePp?: boolean): boolean { - const move = this.moveset.length > moveIndex - ? this.moveset[moveIndex] + const move = this.getMoveset().length > moveIndex + ? this.getMoveset()[moveIndex] : null; return move?.isUsable(ignorePp); } @@ -576,6 +589,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.scene.queueMessage('A critical hit!'); this.damage(damage); source.turnData.damageDealt += damage; + this.turnData.attacksReceived.unshift({ move: move.id, result: result as DamageResult, damage: damage, sourceId: source.id }); } switch (result) { @@ -1023,7 +1037,7 @@ export class EnemyPokemon extends Pokemon { getNextMove(): QueuedMove { const queuedMove = this.getMoveQueue().length - ? this.moveset.find(m => m.moveId === this.getMoveQueue()[0].move) + ? this.getMoveset().find(m => m.moveId === this.getMoveQueue()[0].move) : null; if (queuedMove) { if (queuedMove.isUsable(this.getMoveQueue()[0].ignorePP)) @@ -1034,7 +1048,7 @@ export class EnemyPokemon extends Pokemon { } } - const movePool = this.moveset.filter(m => m.isUsable()); + const movePool = this.getMoveset().filter(m => m.isUsable()); if (movePool.length) { if (movePool.length === 1) return { move: movePool[0].moveId }; @@ -1139,10 +1153,18 @@ export interface QueuedMove { ignorePP?: boolean; } +export interface AttackMoveResult { + move: Moves; + result: DamageResult; + damage: integer; + sourceId: integer; +} + export class PokemonSummonData { public battleStats: integer[] = [ 0, 0, 0, 0, 0, 0, 0 ]; public moveQueue: QueuedMove[] = []; public tags: BattlerTag[] = []; + public moveset: PokemonMove[]; public types: Type[]; } @@ -1156,6 +1178,7 @@ export class PokemonTurnData { public hitCount: integer; public hitsLeft: integer; public damageDealt: integer = 0; + public attacksReceived: AttackMoveResult[] = []; } export enum AiType { diff --git a/src/system/auto-play.ts b/src/system/auto-play.ts index f092de81f..a483713b2 100644 --- a/src/system/auto-play.ts +++ b/src/system/auto-play.ts @@ -63,7 +63,7 @@ export function initAutoPlay() { const getMaxMoveEffectiveness = (attacker: Pokemon, defender: Pokemon) => { let maxEffectiveness = 0.5; - for (let m of attacker.moveset) { + for (let m of attacker.getMoveset()) { const moveType = m.getMove().type; const effectiveness = defender.getAttackMoveEffectiveness(moveType); if (effectiveness > maxEffectiveness) @@ -129,7 +129,7 @@ export function initAutoPlay() { playerPokemon.aiType = AiType.SMART; thisArg.time.delayedCall(20, () => { const nextMove = playerPokemon.getNextMove() as PokemonMove; - fightUiHandler.setCursor(playerPokemon.moveset.indexOf(nextMove)); + fightUiHandler.setCursor(playerPokemon.getMoveset().indexOf(nextMove)); thisArg.time.delayedCall(20, () => this.processInput(Button.ACTION)); }); } diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index c25461c9d..1b47ede55 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -65,6 +65,7 @@ export default class PokemonData { this.summonData.battleStats = source.summonData.battleStats; this.summonData.moveQueue = source.summonData.moveQueue; this.summonData.tags = []; // TODO + this.summonData.moveset = source.summonData.moveset; this.summonData.types = source.summonData.types; } } diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 2757d50fe..f856e380c 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -91,7 +91,7 @@ export default class FightUiHandler extends UiHandler { ui.add(this.cursorObj); } - const moveset = this.scene.getPlayerPokemon().moveset; + const moveset = this.scene.getPlayerPokemon().getMoveset(); const hasMove = cursor < moveset.length; @@ -114,7 +114,7 @@ export default class FightUiHandler extends UiHandler { } displayMoves() { - const moveset = this.scene.getPlayerPokemon().moveset; + const moveset = this.scene.getPlayerPokemon().getMoveset(); for (let m = 0; m < 4; m++) { const moveText = addTextObject(this.scene, m % 2 === 0 ? 0 : 100, m < 2 ? 0 : 16, '-', TextStyle.WINDOW); if (m < moveset.length)