From a2ecb4089df31ae21bf2d4dd2c324293ac7b25a1 Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Fri, 28 Apr 2023 15:03:42 -0400 Subject: [PATCH] Add session save data --- jsconfig.json | 3 +- src/battle-phases.ts | 75 +++++++++++++++-- src/battle-scene.ts | 72 ++++++++-------- src/data/battler-tag.ts | 6 +- src/data/status-effect.ts | 11 ++- src/modifier/modifier-type.ts | 120 ++++++++++++++++++++------- src/modifier/modifier.ts | 40 ++++++++- src/pokemon.ts | 30 ++++--- src/system/arena-data.ts | 17 ++++ src/system/game-data.ts | 150 +++++++++++++++++++++++++++++++--- src/system/modifier-data.ts | 43 ++++++++++ src/system/pokemon-data.ts | 79 ++++++++++++++++++ 12 files changed, 541 insertions(+), 105 deletions(-) create mode 100644 src/system/arena-data.ts create mode 100644 src/system/modifier-data.ts create mode 100644 src/system/pokemon-data.ts diff --git a/jsconfig.json b/jsconfig.json index 8bcefca6e..0b18b9ccc 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { - "target": "ES2019", + "target": "ES2020", + "module": "ES2020", "moduleResolution": "node", "checkJs": true, "esModuleInterop": true diff --git a/src/battle-phases.ts b/src/battle-phases.ts index 68f08bc5d..321cc21a3 100644 --- a/src/battle-phases.ts +++ b/src/battle-phases.ts @@ -27,6 +27,56 @@ import { TempBattleStat } from "./data/temp-battle-stat"; import { ArenaTrapTag, TrickRoomTag } from "./data/arena-tag"; import { PostWeatherLapseAbAttr, PreWeatherDamageAbAttr, ProtectStatAttr, SuppressWeatherEffectAbAttr, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability"; +export class CheckLoadPhase extends BattlePhase { + private loaded: boolean; + + constructor(scene: BattleScene) { + super(scene); + + this.loaded = false; + } + + start(): void { + if (!this.scene.gameData.hasSession()) { + this.end(); + return; + } + + this.scene.ui.showText('You currently have a session in progress.\nWould you like to continue where you left off?', null, () => { + this.scene.ui.setMode(Mode.CONFIRM, () => { + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.gameData.loadSession(this.scene).then((success: boolean) => { + if (success) { + this.loaded = true; + this.scene.ui.showText('Session loaded successfully.', null, () => this.end()); + } else + this.end(); + }).catch(err => { + console.error(err); + this.scene.ui.showText('Your session data could not be loaded.\nIt may be corrupted. Please reload the page.', null); + }); + }, () => { + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.clearText(); + this.end(); + }) + }); + } + + end(): void { + if (!this.loaded) { + this.scene.arena.preloadBgm(); + this.scene.pushPhase(new SelectStarterPhase(this.scene)); + } else + this.scene.arena.playBgm(); + + this.scene.pushPhase(new EncounterPhase(this.scene, this.loaded)); + this.scene.pushPhase(new SummonPhase(this.scene)); + + super.end(); + } +} + export class SelectStarterPhase extends BattlePhase { constructor(scene: BattleScene) { super(scene); @@ -62,8 +112,12 @@ export class SelectStarterPhase extends BattlePhase { } export class EncounterPhase extends BattlePhase { - constructor(scene: BattleScene) { + private loaded: boolean; + + constructor(scene: BattleScene, loaded?: boolean) { super(scene); + + this.loaded = !!loaded; } start() { @@ -73,7 +127,8 @@ export class EncounterPhase extends BattlePhase { const battle = this.scene.currentBattle; const enemySpecies = this.scene.randomSpecies(battle.waveIndex, battle.enemyLevel, true); - battle.enemyPokemon = new EnemyPokemon(this.scene, enemySpecies, battle.enemyLevel); + if (!this.loaded) + battle.enemyPokemon = new EnemyPokemon(this.scene, enemySpecies, battle.enemyLevel); const enemyPokemon = this.scene.getEnemyPokemon(); enemyPokemon.resetSummonData(); @@ -87,10 +142,16 @@ export class EncounterPhase extends BattlePhase { this.scene.field.moveBelow(enemyPokemon, this.scene.getPlayerPokemon()); enemyPokemon.tint(0, 0.5); - regenerateModifierPoolThresholds(this.scene.getEnemyParty(), false); - this.scene.generateEnemyModifiers(); + if (!this.loaded) { + regenerateModifierPoolThresholds(this.scene.getEnemyParty(), false); + this.scene.generateEnemyModifiers(); + } - this.scene.ui.setMode(Mode.MESSAGE).then(() => this.doEncounter()); + this.scene.ui.setMode(Mode.MESSAGE).then(() => { + if (!this.loaded) + this.scene.gameData.saveSession(this.scene); + this.doEncounter(); + }); }); } @@ -989,7 +1050,7 @@ abstract class MoveEffectPhase extends PokemonPhase { } return rand <= this.move.getMove().accuracy * accuracyMultiplier; } - + return true; } @@ -1500,6 +1561,8 @@ export class GameOverPhase extends BattlePhase { start() { super.start(); + this.scene.gameData.clearSession(); + this.scene.time.delayedCall(1000, () => { const fadeDuration = this.victory ? 10000 : 5000; this.scene.fadeOutBgm(fadeDuration, true); diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 6c983c7a8..06273453d 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1,7 +1,7 @@ import Phaser from 'phaser'; import { Biome } from './data/biome'; import UI from './ui/ui'; -import { EncounterPhase, SummonPhase, CommandPhase, NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, SelectStarterPhase, MessagePhase } from './battle-phases'; +import { EncounterPhase, SummonPhase, CommandPhase, NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, SelectStarterPhase, MessagePhase, CheckLoadPhase } from './battle-phases'; import Pokemon, { PlayerPokemon, EnemyPokemon } from './pokemon'; import PokemonSpecies, { allSpecies, getPokemonSpecies, initSpecies } from './data/pokemon-species'; import * as Utils from './utils'; @@ -45,7 +45,7 @@ export enum Button { SLOW_DOWN } -interface PokeballCounts { +export interface PokeballCounts { [pb: string]: integer; } @@ -474,16 +474,9 @@ export default class BattleScene extends Phaser.Scene { this.currentBattle = null; this.waveCountText.setText(startingWave.toString()); + this.waveCountText.setVisible(false); - this.newArena(startingBiome); - const biomeKey = this.arena.getBiomeKey(); - - this.arenaBg.setTexture(`${biomeKey}_bg`); - this.arenaBgTransition.setTexture(`${biomeKey}_bg`); - this.arenaPlayer.setTexture(`${biomeKey}_a`); - this.arenaPlayerTransition.setTexture(`${biomeKey}_a`); - this.arenaEnemy.setTexture(`${biomeKey}_b`); - this.arenaNextEnemy.setTexture(`${biomeKey}_b`); + this.newArena(startingBiome, true); this.arenaBgTransition.setPosition(0, 0); this.arenaPlayer.setPosition(340, 20); @@ -495,32 +488,46 @@ export default class BattleScene extends Phaser.Scene { this.trainer.setPosition(406, 132); } - newBattle(): Battle { - if (this.currentBattle) { - this.getEnemyPokemon().destroy(); - if (this.currentBattle.waveIndex % 10) - this.pushPhase(new NextEncounterPhase(this)); - else { - this.pushPhase(new SelectBiomePhase(this)); - this.pushPhase(new NewBiomeEncounterPhase(this)); + newBattle(waveIndex?: integer): Battle { + if (!waveIndex) { + if (this.currentBattle) { + this.getEnemyPokemon().destroy(); + if (this.currentBattle.waveIndex % 10) + this.pushPhase(new NextEncounterPhase(this)); + else { + this.pushPhase(new SelectBiomePhase(this)); + this.pushPhase(new NewBiomeEncounterPhase(this)); + } + } else { + if (!this.quickStart) + this.pushPhase(new CheckLoadPhase(this)); + else { + this.arena.playBgm(); + this.pushPhase(new EncounterPhase(this)); + this.pushPhase(new SummonPhase(this)); + } } - } else { - if (!this.quickStart) { - this.arena.preloadBgm(); - this.pushPhase(new SelectStarterPhase(this)); - } else - this.arena.playBgm(); - this.pushPhase(new EncounterPhase(this)); - this.pushPhase(new SummonPhase(this)); } - this.currentBattle = new Battle((this.currentBattle?.waveIndex || (startingWave - 1)) + 1); + this.currentBattle = new Battle(waveIndex || ((this.currentBattle?.waveIndex || (startingWave - 1)) + 1)); return this.currentBattle; } - newArena(biome: Biome): Arena { + newArena(biome: Biome, init?: boolean): Arena { this.arena = new Arena(this, biome, Biome[biome].toLowerCase()); + + if (init) { + const biomeKey = this.arena.getBiomeKey(); + + this.arenaBg.setTexture(`${biomeKey}_bg`); + this.arenaBgTransition.setTexture(`${biomeKey}_bg`); + this.arenaPlayer.setTexture(`${biomeKey}_a`); + this.arenaPlayerTransition.setTexture(`${biomeKey}_a`); + this.arenaEnemy.setTexture(`${biomeKey}_b`); + this.arenaNextEnemy.setTexture(`${biomeKey}_b`); + } + return this.arena; } @@ -529,6 +536,7 @@ export default class BattleScene extends Phaser.Scene { this.waveCountText.setText(this.currentBattle.waveIndex.toString()); this.waveCountText.setColor(!isBoss ? '#404040' : '#f89890'); this.waveCountText.setShadowColor(!isBoss ? '#ded6b5' : '#984038'); + this.waveCountText.setVisible(true); } updateWaveCountPosition(): void { @@ -868,19 +876,19 @@ export default class BattleScene extends Phaser.Scene { return false; } - getModifiers(modifierType: { new(...args: any[]): Modifier }, player?: boolean): Modifier[] { + getModifiers(modifierType: { new(...args: any[]): Modifier }, player?: boolean): PersistentModifier[] { if (player === undefined) player = true; return (player ? this.modifiers : this.enemyModifiers).filter(m => m instanceof modifierType); } - findModifiers(modifierFilter: ModifierPredicate, player?: boolean): Modifier[] { + findModifiers(modifierFilter: ModifierPredicate, player?: boolean): PersistentModifier[] { if (player === undefined) player = true; return (player ? this.modifiers : this.enemyModifiers).filter(m => (modifierFilter as ModifierPredicate)(m)); } - findModifier(modifierFilter: ModifierPredicate, player?: boolean): Modifier { + findModifier(modifierFilter: ModifierPredicate, player?: boolean): PersistentModifier { if (player === undefined) player = true; return (player ? this.modifiers : this.enemyModifiers).find(m => (modifierFilter as ModifierPredicate)(m)); diff --git a/src/data/battler-tag.ts b/src/data/battler-tag.ts index 1aca6b5ec..1fca52779 100644 --- a/src/data/battler-tag.ts +++ b/src/data/battler-tag.ts @@ -1,11 +1,11 @@ import { CommonAnim, CommonBattleAnim } from "./battle-anims"; -import { CommonAnimPhase, DamagePhase, MessagePhase, MovePhase, ObtainStatusEffectPhase, PokemonHealPhase } from "../battle-phases"; +import { CommonAnimPhase, DamagePhase, MovePhase, ObtainStatusEffectPhase, PokemonHealPhase } from "../battle-phases"; import { getPokemonMessage } from "../messages"; import Pokemon from "../pokemon"; import { Stat } from "./pokemon-stat"; import { StatusEffect } from "./status-effect"; import * as Utils from "../utils"; -import { LapseBattlerTagAttr, Moves, allMoves } from "./move"; +import { Moves, allMoves } from "./move"; import { Type } from "./type"; export enum BattlerTagType { @@ -555,7 +555,7 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc case BattlerTagType.NO_CRIT: return new BattlerTag(tagType, BattlerTagLapseType.AFTER_MOVE, turnCount, sourceMove); case BattlerTagType.IGNORE_ACCURACY: - return new IgnoreAccuracyTag(turnCount, sourceMove); + return new IgnoreAccuracyTag(sourceMove); case BattlerTagType.BYPASS_SLEEP: return new BattlerTag(BattlerTagType.BYPASS_SLEEP, BattlerTagLapseType.TURN_END, turnCount, sourceMove); case BattlerTagType.IGNORE_FLYING: diff --git a/src/data/status-effect.ts b/src/data/status-effect.ts index 3bc3c35cd..6b9ec8233 100644 --- a/src/data/status-effect.ts +++ b/src/data/status-effect.ts @@ -16,11 +16,14 @@ export class Status { public turnCount: integer; public cureTurn: integer; - constructor(effect: StatusEffect) { + constructor(effect: StatusEffect, turnCount?: integer, cureTurn?: integer) { this.effect = effect; - this.turnCount = 0; - if (effect === StatusEffect.SLEEP) - this.cureTurn = Utils.randInt(3, 1); + this.turnCount = turnCount === undefined ? 0 : turnCount; + if (cureTurn === undefined) { + if (effect === StatusEffect.SLEEP) + this.cureTurn = Utils.randInt(3, 1); + } else + this.cureTurn = cureTurn; } incrementTurn(): void { diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 094806739..ea3f7f690 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -24,6 +24,7 @@ export enum ModifierTier { type NewModifierFunc = (type: ModifierType, args: any[]) => Modifier; export class ModifierType { + public id: string; public name: string; public description: string; public iconImage: string; @@ -50,6 +51,30 @@ export class ModifierType { } } +type ModifierTypeGeneratorFunc = (party: Pokemon[], pregenArgs?: any[]) => ModifierType; + +export class ModifierTypeGenerator extends ModifierType { + private genTypeFunc: ModifierTypeGeneratorFunc; + + constructor(genTypeFunc: ModifierTypeGeneratorFunc) { + super(null, null, null, null); + this.genTypeFunc = genTypeFunc; + } + + generateType(party: Pokemon[], pregenArgs?: any[]) { + const ret = this.genTypeFunc(party, pregenArgs); + if (ret) { + ret.id = this.id; + ret.setTier(this.tier); + } + return ret; + } +} + +export interface GeneratedPersistentModifierType { + getPregenArgs(): any[]; +} + class AddPokeballModifierType extends ModifierType { constructor(pokeballType: PokeballType, count: integer, iconImage?: string) { super(`${count}x ${getPokeballName(pokeballType)}`, `Receive ${getPokeballName(pokeballType)} x${count}`, @@ -168,7 +193,7 @@ export class PokemonAllMovePpRestoreModifierType extends PokemonModifierType { } } -export class TempBattleStatBoosterModifierType extends ModifierType { +export class TempBattleStatBoosterModifierType extends ModifierType implements GeneratedPersistentModifierType { public tempBattleStat: TempBattleStat; constructor(tempBattleStat: TempBattleStat) { @@ -179,6 +204,26 @@ export class TempBattleStatBoosterModifierType extends ModifierType { this.tempBattleStat = tempBattleStat; } + + getPregenArgs(): any[] { + return [ this.tempBattleStat ]; + } +} + +export class BerryModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType { + private berryType: BerryType; + + constructor(berryType: BerryType) { + super(getBerryName(berryType), getBerryEffectDescription(berryType), + (type, args) => new Modifiers.BerryModifier(type, (args[0] as Pokemon).id, berryType), + null, 'berry'); + + this.berryType = berryType; + } + + getPregenArgs(): any[] { + return [ this.berryType ]; + } } function getAttackTypeBoosterItemName(type: Type) { @@ -222,7 +267,7 @@ function getAttackTypeBoosterItemName(type: Type) { } } -export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType { +export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType { public moveType: Type; public boostPercent: integer; @@ -234,6 +279,10 @@ export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType { this.moveType = moveType; this.boostPercent = boostPercent; } + + getPregenArgs(): any[] { + return [ this.moveType ]; + } } export class PokemonLevelIncrementModifierType extends PokemonModifierType { @@ -266,7 +315,7 @@ function getBaseStatBoosterItemName(stat: Stat) { } } -export class PokemonBaseStatBoosterModifierType extends PokemonHeldItemModifierType { +export class PokemonBaseStatBoosterModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType { private stat: Stat; constructor(name: string, stat: Stat, _iconImage?: string) { @@ -274,6 +323,10 @@ export class PokemonBaseStatBoosterModifierType extends PokemonHeldItemModifierT this.stat = stat; } + + getPregenArgs(): any[] { + return [ this.stat ]; + } } class AllPokemonFullHpRestoreModifierType extends ModifierType { @@ -343,7 +396,7 @@ function getEvolutionItemName(evolutionItem: EvolutionItem) { } } -export class EvolutionItemModifierType extends PokemonModifierType { +export class EvolutionItemModifierType extends PokemonModifierType implements GeneratedPersistentModifierType { public evolutionItem: EvolutionItem; constructor(evolutionItem: EvolutionItem) { @@ -358,27 +411,18 @@ export class EvolutionItemModifierType extends PokemonModifierType { this.evolutionItem = evolutionItem; } -} -class ModifierTypeGenerator extends ModifierType { - private genTypeFunc: Function; - - constructor(genTypeFunc: Function) { - super(null, null, null, null); - this.genTypeFunc = genTypeFunc; - } - - generateType(party: Pokemon[]) { - const ret = this.genTypeFunc(party); - if (ret) - ret.setTier(this.tier); - return ret; + getPregenArgs(): any[] { + return [ this.evolutionItem ]; } } class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator { constructor() { - super((party: Pokemon[]) => { + super((party: Pokemon[], pregenArgs?: any[]) => { + 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 attackMoveTypeWeights = new Map(); let totalWeight = 0; @@ -417,7 +461,10 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator { class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator { constructor() { - super((party: Pokemon[]) => { + super((party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs) + return new EvolutionItemModifierType(pregenArgs[0] as EvolutionItem); + const evolutionItemPool = party.filter(p => pokemonEvolutions.hasOwnProperty(p.species.speciesId)).map(p => { const evolutions = pokemonEvolutions[p.species.speciesId] return evolutions.filter(e => e.item !== EvolutionItem.NONE && (!e.condition || e.condition.predicate(p))); @@ -446,6 +493,7 @@ class WeightedModifierType { constructor(modifierTypeFunc: ModifierTypeFunc, weight: integer | WeightedModifierTypeWeightFunc) { this.modifierType = modifierTypeFunc(); + this.modifierType.id = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifierTypeFunc); this.weight = weight; } @@ -485,21 +533,29 @@ const modifierTypes = { ELIXIR: () => new PokemonAllMovePpRestoreModifierType('ELIXIR', 10), MAX_ELIXIR: () => new PokemonAllMovePpRestoreModifierType('MAX ELIXIR', -1), - TEMP_STAT_BOOSTER: () => new ModifierTypeGenerator((party: Pokemon[]) => { + TEMP_STAT_BOOSTER: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs) + return new TempBattleStatBoosterModifierType(pregenArgs[0] as TempBattleStat); const randTempBattleStat = Utils.randInt(7) as TempBattleStat; return new TempBattleStatBoosterModifierType(randTempBattleStat); }), - BASE_STAT_BOOSTER: () => new ModifierTypeGenerator((party: Pokemon[]) => { + BASE_STAT_BOOSTER: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs) { + const stat = pregenArgs[0] as Stat; + return new PokemonBaseStatBoosterModifierType(getBaseStatBoosterItemName(stat), stat); + } const randStat = Utils.randInt(6) as Stat; return new PokemonBaseStatBoosterModifierType(getBaseStatBoosterItemName(randStat), randStat); }), ATTACK_TYPE_BOOSTER: () => new AttackTypeBoosterModifierTypeGenerator(), - BERRY: () => new ModifierTypeGenerator((party: Pokemon[]) => { + BERRY: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs) + return new BerryModifierType(pregenArgs[0] as BerryType); const berryTypes = Utils.getEnumValues(BerryType); - let randBerryType; + let randBerryType: BerryType; let rand = Utils.randInt(10); if (rand < 2) randBerryType = BerryType.SITRUS; @@ -507,9 +563,7 @@ const modifierTypes = { randBerryType = BerryType.LUM; else randBerryType = berryTypes[Utils.randInt(berryTypes.length - 2) + 2]; - return new PokemonHeldItemModifierType(getBerryName(randBerryType), getBerryEffectDescription(randBerryType), - (type, args) => new Modifiers.BerryModifier(type, (args[0] as Pokemon).id, randBerryType), - null, 'berry'); + return new BerryModifierType(randBerryType); }), TM: () => new ModifierTypeGenerator((party: Pokemon[]) => { @@ -575,11 +629,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.getPpRatio() <= 0.2).length).length, 3); + const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.moveset.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.getPpRatio() <= 0.2).length).length, 3); + const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.moveset.filter(m => (m.getMove().pp - m.ppUsed) <= 5).length).length, 3); return thresholdPartyMemberCount; }), new WeightedModifierType(modifierTypes.TEMP_STAT_BOOSTER, 4), @@ -611,11 +665,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.getPpRatio() <= 0.2).length).length, 3); + const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.moveset.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.getPpRatio() <= 0.2).length).length, 3); + const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.moveset.filter(m => (m.getMove().pp - m.ppUsed) <= 5).length).length, 3); return thresholdPartyMemberCount; }), new WeightedModifierType(modifierTypes.MAP, (party: Pokemon[]) => { @@ -714,6 +768,10 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], player?: bool } } +export function getModifierTypeFuncById(id: string): ModifierTypeFunc { + return modifierTypes[id]; +} + export function getPlayerModifierTypeOptionsForWave(waveIndex: integer, count: integer, party: PlayerPokemon[]): ModifierTypeOption[] { if (waveIndex % 10 === 0) return modifierPool[ModifierTier.LUXURY].filter(m => !(m.weight instanceof Function) || m.weight(party)).map(m => new ModifierTypeOption(m.modifierType, false)); diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index f2b4521a1..613d475f0 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -92,6 +92,10 @@ export abstract class PersistentModifier extends Modifier { abstract clone(): PersistentModifier; + getArgs(): any[] { + return []; + } + incrementStack(amount: integer, virtual: boolean): boolean { if (this.getStackCount() + amount <= this.getMaxStackCount()) { if (!virtual) @@ -194,17 +198,21 @@ export class TempBattleStatBoosterModifier extends PersistentModifier { private tempBattleStat: TempBattleStat; private battlesLeft: integer; - constructor(type: ModifierTypes.TempBattleStatBoosterModifierType, tempBattleStat: TempBattleStat, stackCount?: integer) { + constructor(type: ModifierTypes.TempBattleStatBoosterModifierType, tempBattleStat: TempBattleStat, battlesLeft?: integer, stackCount?: integer) { super(type, stackCount); this.tempBattleStat = tempBattleStat; - this.battlesLeft = 5; + this.battlesLeft = battlesLeft || 5; } clone(): TempBattleStatBoosterModifier { return new TempBattleStatBoosterModifier(this.type as ModifierTypes.TempBattleStatBoosterModifierType, this.tempBattleStat, this.stackCount); } + getArgs(): any[] { + return [ this.tempBattleStat, this.battlesLeft ]; + } + apply(args: any[]): boolean { const tempBattleStat = args[0] as TempBattleStat; @@ -267,6 +275,10 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier { return this.matchType(modifier) && (modifier as PokemonHeldItemModifier).pokemonId === this.pokemonId; } + getArgs(): any[] { + return [ this.pokemonId ]; + } + shouldApply(args: any[]): boolean { return super.shouldApply(args) && args.length && args[0] instanceof Pokemon && (this.pokemonId === -1 || (args[0] as Pokemon).id === this.pokemonId); } @@ -332,6 +344,10 @@ export class PokemonBaseStatModifier extends PokemonHeldItemModifier { return new PokemonBaseStatModifier(this.type as ModifierTypes.PokemonBaseStatBoosterModifierType, this.pokemonId, this.stat, this.stackCount); } + getArgs(): any[] { + return super.getArgs().concat(this.stat); + } + shouldApply(args: any[]): boolean { return super.shouldApply(args) && args.length === 2 && args[1] instanceof Array; } @@ -365,6 +381,10 @@ export class AttackTypeBoosterModifier extends PokemonHeldItemModifier { return new AttackTypeBoosterModifier(this.type, this.pokemonId, this.moveType, this.boostMultiplier * 100, this.stackCount); } + getArgs(): any[] { + return super.getArgs().concat([ this.moveType, this.boostMultiplier * 100 ]); + } + shouldApply(args: any[]): boolean { return super.shouldApply(args) && args.length === 2 && args[1] instanceof Utils.NumberHolder; } @@ -543,6 +563,10 @@ export class BerryModifier extends PokemonHeldItemModifier { return new BerryModifier(this.type, this.pokemonId, this.berryType, this.stackCount); } + getArgs(): any[] { + return super.getArgs().concat(this.berryType); + } + shouldApply(args: any[]): boolean { return !this.consumed && super.shouldApply(args) && getBerryPredicate(this.berryType)(args[0] as Pokemon); } @@ -818,6 +842,10 @@ export class HealingBoosterModifier extends PersistentModifier { return new HealingBoosterModifier(this.type, this.multiplier, this.stackCount); } + getArgs(): any[] { + return [ this.multiplier ]; + } + apply(args: any[]): boolean { const healingMultiplier = args[0] as Utils.IntegerHolder; for (let s = 0; s < this.getStackCount(); s++) @@ -853,6 +881,10 @@ export class ExpBoosterModifier extends PersistentModifier { return new ExpBoosterModifier(this.type, this.boostMultiplier * 100, this.stackCount); } + getArgs(): any[] { + return [ this.boostMultiplier * 100 ]; + } + apply(args: any[]): boolean { (args[0] as Utils.NumberHolder).value = Math.floor((args[0] as Utils.NumberHolder).value * (1 + (this.getStackCount() * this.boostMultiplier))); @@ -880,6 +912,10 @@ export class PokemonExpBoosterModifier extends PokemonHeldItemModifier { return new PokemonExpBoosterModifier(this.type as ModifierTypes.PokemonExpBoosterModifierType, this.pokemonId, this.boostMultiplier * 100, this.stackCount); } + getArgs(): any[] { + return super.getArgs().concat(this.boostMultiplier * 100); + } + shouldApply(args: any[]): boolean { return super.shouldApply(args) && args.length === 2 && args[1] instanceof Utils.NumberHolder; } diff --git a/src/pokemon.ts b/src/pokemon.ts index bb38ef134..5242e1a8a 100644 --- a/src/pokemon.ts +++ b/src/pokemon.ts @@ -24,6 +24,7 @@ import { TempBattleStat } from './data/temp-battle-stat'; import { WeakenMoveTypeTag } from './data/arena-tag'; import { Biome } from './data/biome'; import { Ability, TypeImmunityAbAttr, VariableMovePowerAbAttr, abilities, applyPreAttackAbAttrs, applyPreDefendAbAttrs } from './data/ability'; +import PokemonData from './system/pokemon-data'; export default abstract class Pokemon extends Phaser.GameObjects.Container { public id: integer; @@ -54,7 +55,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { private shinySparkle: Phaser.GameObjects.Sprite; - constructor(scene: BattleScene, x: number, y: number, species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, dataSource?: Pokemon) { + constructor(scene: BattleScene, x: number, y: number, species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, dataSource?: Pokemon | PokemonData) { super(scene, x, y); if (!species.isObtainable() && this.isPlayer()) @@ -666,7 +667,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getMoveHistory(): TurnMove[] { - return this.summonData.moveHistory; + return this.battleSummonData.moveHistory; } pushMoveHistory(turnMove: TurnMove) { @@ -875,7 +876,7 @@ export class PlayerPokemon extends Pokemon { public metLevel: integer; public compatibleTms: Moves[]; - constructor(scene: BattleScene, species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender?: Gender, shiny?: boolean, dataSource?: Pokemon) { + constructor(scene: BattleScene, species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender?: Gender, shiny?: boolean, dataSource?: Pokemon | PokemonData) { super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, dataSource); this.metBiome = scene.arena?.biomeType || Biome.TOWN; @@ -942,16 +943,19 @@ export class PlayerPokemon extends Pokemon { export class EnemyPokemon extends Pokemon { public aiType: AiType; - constructor(scene: BattleScene, species: PokemonSpecies, level: integer) { - super(scene, -66, 84, species, level, undefined, scene.arena.getFormIndex(species)); + constructor(scene: BattleScene, species: PokemonSpecies, level: integer, dataSource?: PokemonData) { + super(scene, -66, 84, species, level, dataSource?.abilityIndex, dataSource ? dataSource.formIndex : scene.arena.getFormIndex(species), + dataSource?.gender, dataSource?.shiny, dataSource); - let prevolution: Species; - let speciesId = species.speciesId; - while ((prevolution = pokemonPrevolutions[speciesId])) { - const evolution = pokemonEvolutions[prevolution].find(pe => pe.speciesId === speciesId); - if (evolution.condition?.enforceFunc) - evolution.condition.enforceFunc(this); - speciesId = prevolution; + if (!dataSource) { + let prevolution: Species; + let speciesId = species.speciesId; + while ((prevolution = pokemonPrevolutions[speciesId])) { + const evolution = pokemonEvolutions[prevolution].find(pe => pe.speciesId === speciesId); + if (evolution.condition?.enforceFunc) + evolution.condition.enforceFunc(this); + speciesId = prevolution; + } } this.aiType = AiType.SMART_RANDOM; @@ -1077,7 +1081,6 @@ export interface QueuedMove { export class PokemonSummonData { public battleStats: integer[] = [ 0, 0, 0, 0, 0, 0, 0 ]; - public moveHistory: TurnMove[] = []; public moveQueue: QueuedMove[] = []; public tags: BattlerTag[] = []; public types: Type[]; @@ -1085,6 +1088,7 @@ export class PokemonSummonData { export class PokemonBattleSummonData { public turnCount: integer = 1; + public moveHistory: TurnMove[] = []; } export class PokemonTurnData { diff --git a/src/system/arena-data.ts b/src/system/arena-data.ts new file mode 100644 index 000000000..e5236c9b3 --- /dev/null +++ b/src/system/arena-data.ts @@ -0,0 +1,17 @@ +import { Arena } from "../arena"; +import { ArenaTag } from "../data/arena-tag"; +import { Biome } from "../data/biome"; +import { Weather } from "../data/weather"; + +export default class ArenaData { + public biome: Biome; + public weather: Weather; + public tags: ArenaTag[]; + + constructor(source: Arena | any) { + const sourceArena = source instanceof Arena ? source as Arena : null; + this.biome = sourceArena ? sourceArena.biomeType : source.biome; + this.weather = sourceArena ? sourceArena.weather : source.weather ? new Weather(source.weather.weatherType, source.weather.turnsLeft) : undefined; + this.tags = sourceArena ? sourceArena.tags : []; + } +} \ No newline at end of file diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 3e8b605e3..3273d9b55 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -1,16 +1,34 @@ -import BattleScene from "../battle-scene"; +import BattleScene, { PokeballCounts } from "../battle-scene"; import { Gender } from "../data/gender"; -import Pokemon from "../pokemon"; +import Pokemon, { EnemyPokemon, PlayerPokemon } from "../pokemon"; import { pokemonPrevolutions } from "../data/pokemon-evolutions"; import PokemonSpecies, { allSpecies, getPokemonSpecies } from "../data/pokemon-species"; import { Species } from "../data/species"; import * as Utils from "../utils"; +import PokemonData from "./pokemon-data"; +import { Weather } from "../data/weather"; +import PersistentModifierData from "./modifier-data"; +import { Biome } from "../data/biome"; +import { PokemonHeldItemModifier } from "../modifier/modifier"; +import { ArenaTag } from "../data/arena-tag"; +import ArenaData from "./arena-data"; -interface SaveData { +interface SystemSaveData { trainerId: integer; secretId: integer; dexData: DexData; - timestamp: integer + timestamp: integer; +} + +interface SessionSaveData { + party: PokemonData[]; + enemyParty: PokemonData[]; + modifiers: PersistentModifierData[]; + enemyModifiers: PersistentModifierData[]; + arena: ArenaData; + pokeballCounts: PokeballCounts; + waveIndex: integer; + timestamp: integer; } export interface DexData { @@ -52,14 +70,14 @@ export class GameData { this.trainerId = Utils.randInt(65536); this.secretId = Utils.randInt(65536); this.initDexData(); - this.load(); + this.loadSystem(); } - private save(): boolean { + private saveSystem(): boolean { if (this.scene.quickStart) return false; - const data: SaveData = { + const data: SystemSaveData = { trainerId: this.trainerId, secretId: this.secretId, dexData: this.dexData, @@ -71,12 +89,12 @@ export class GameData { return true; } - private load(): boolean { + private loadSystem(): boolean { if (!localStorage.getItem('data')) return false; - const data = JSON.parse(atob(localStorage.getItem('data'))) as SaveData; - console.log(data); + const data = JSON.parse(atob(localStorage.getItem('data'))) as SystemSaveData; + console.debug(data); this.trainerId = data.trainerId; this.secretId = data.secretId; @@ -89,7 +107,113 @@ export class GameData { return true; } - private initDexData() { + saveSession(scene: BattleScene): boolean { + const sessionData = { + party: scene.getParty().map(p => new PokemonData(p)), + enemyParty: scene.getEnemyParty().map(p => new PokemonData(p)), + modifiers: scene.findModifiers(m => true).map(m => new PersistentModifierData(m, true)), + enemyModifiers: scene.findModifiers(m => true, false).map(m => new PersistentModifierData(m, false)), + arena: new ArenaData(scene.arena), + pokeballCounts: scene.pokeballCounts, + waveIndex: scene.currentBattle.waveIndex, + timestamp: new Date().getTime() + } as SessionSaveData; + + localStorage.setItem('sessionData', btoa(JSON.stringify(sessionData))); + + console.debug('Session data saved'); + + return true; + } + + hasSession() { + return !!localStorage.getItem('sessionData'); + } + + loadSession(scene: BattleScene): Promise { + return new Promise(async (resolve, reject) => { + if (!this.hasSession()) + return resolve(false); + + try { + const sessionData = JSON.parse(atob(localStorage.getItem('sessionData')), (k: string, v: any) => { + if (k === 'party' || k === 'enemyParty') { + const ret: PokemonData[] = []; + for (let pd of v) + ret.push(new PokemonData(pd)); + return ret; + } + + if (k === 'modifiers' || k === 'enemyModifiers') { + const player = k === 'modifiers'; + const ret: PersistentModifierData[] = []; + for (let md of v) + ret.push(new PersistentModifierData(md, player)); + return ret; + } + + if (k === 'arena') + return new ArenaData(v); + + return v; + }) as SessionSaveData; + + console.debug(sessionData); + + const loadPokemonAssets: Promise[] = []; + + const party = scene.getParty(); + party.splice(0, party.length); + + for (let p of sessionData.party) { + const pokemon = p.toPokemon(scene) as PlayerPokemon; + pokemon.setVisible(false); + loadPokemonAssets.push(pokemon.loadAssets()); + party.push(pokemon); + } + + const enemyPokemon = sessionData.enemyParty[0].toPokemon(scene) as EnemyPokemon; + + Object.keys(scene.pokeballCounts).forEach((key: string) => { + scene.pokeballCounts[key] = sessionData.pokeballCounts[key] || 0; + }); + + scene.newArena(sessionData.arena.biome, true); + scene.newBattle(sessionData.waveIndex).enemyPokemon = enemyPokemon; + + loadPokemonAssets.push(enemyPokemon.loadAssets()); + + scene.arena.weather = sessionData.arena.weather; + // TODO + //scene.arena.tags = sessionData.arena.tags; + + const modifiersModule = await import('../modifier/modifier'); + + for (let modifierData of sessionData.modifiers) { + const modifier = modifierData.toModifier(scene, modifiersModule[modifierData.className]); + if (modifier) + scene.addModifier(modifier); + } + + for (let enemyModifierData of sessionData.enemyModifiers) { + const modifier = enemyModifierData.toModifier(scene, modifiersModule[enemyModifierData.className]) as PokemonHeldItemModifier; + if (modifier) + scene.addEnemyModifier(modifier); + } + + Promise.all(loadPokemonAssets).then(() => resolve(true)); + } catch (err) { + reject(err); + return; + } + }); + } + + clearSession(): void { + localStorage.removeItem('sessionData'); + } + + private initDexData(): void { const data: DexData = {}; const initDexSubData = (dexData: DexData, count: integer): DexData[] => { @@ -148,7 +272,7 @@ export class GameData { const dexEntry = this.getPokemonDexEntry(pokemon); if (!dexEntry.seen) { dexEntry.seen = true; - this.save(); + this.saveSystem(); } } @@ -159,7 +283,7 @@ export class GameData { const newCatch = !this.getDefaultDexEntry(pokemon.species); dexEntry.caught = true; - this.save(); + this.saveSystem(); if (newCatch && !pokemonPrevolutions.hasOwnProperty(pokemon.species.speciesId)) { this.scene.playSoundWithoutBgm('level_up_fanfare', 1500); diff --git a/src/system/modifier-data.ts b/src/system/modifier-data.ts new file mode 100644 index 000000000..b2d667cd7 --- /dev/null +++ b/src/system/modifier-data.ts @@ -0,0 +1,43 @@ +import BattleScene from "../battle-scene"; +import { PersistentModifier } from "../modifier/modifier"; +import { GeneratedPersistentModifierType, ModifierTypeGenerator, getModifierTypeFuncById } from "../modifier/modifier-type"; + +export default class ModifierData { + private player: boolean; + private typeId: string; + private typePregenArgs: any[]; + private args: any[]; + private stackCount: integer; + + public className: string; + + constructor(source: PersistentModifier | any, player: boolean) { + const sourceModifier = source instanceof PersistentModifier ? source as PersistentModifier : null; + this.player = player; + this.typeId = sourceModifier ? sourceModifier.type.id : source.typeId; + if (sourceModifier) { + if ('getPregenArgs' in source.type) + this.typePregenArgs = (source.type as GeneratedPersistentModifierType).getPregenArgs(); + } else if (source.typePregenArgs) + this.typePregenArgs = source.typePregenArgs; + this.args = sourceModifier ? sourceModifier.getArgs() : source.args; + this.stackCount = source.stackCount; + this.className = sourceModifier ? sourceModifier.constructor.name : source.className; + } + + toModifier(scene: BattleScene, constructor: any): PersistentModifier { + const typeFunc = getModifierTypeFuncById(this.typeId); + if (!typeFunc) + return null; + + let type = typeFunc(); + type.id = this.typeId; + + if (type instanceof ModifierTypeGenerator) + type = (type as ModifierTypeGenerator).generateType(this.player ? scene.getParty() : scene.getEnemyParty(), this.typePregenArgs); + + const ret = Reflect.construct(constructor, ([ type ] as any[]).concat(this.args).concat(this.stackCount)) as PersistentModifier + + return ret; + } +} \ No newline at end of file diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts new file mode 100644 index 000000000..c25461c9d --- /dev/null +++ b/src/system/pokemon-data.ts @@ -0,0 +1,79 @@ +import BattleScene from "../battle-scene"; +import { Gender } from "../data/gender"; +import { PokeballType } from "../data/pokeball"; +import { getPokemonSpecies } from "../data/pokemon-species"; +import { Species } from "../data/species"; +import { Status } from "../data/status-effect"; +import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove, PokemonSummonData } from "../pokemon"; + +export default class PokemonData { + public id: integer; + public player: boolean; + public species: Species; + public formIndex: integer; + public abilityIndex: integer; + public shiny: boolean; + public pokeball: PokeballType; + public level: integer; + public exp: integer; + public levelExp: integer; + public gender: Gender; + public hp: integer; + public stats: integer[]; + public ivs: integer[]; + public moveset: PokemonMove[]; + public status: Status; + public winCount: integer; + + public summonData: PokemonSummonData; + + constructor(source: Pokemon | any) { + const sourcePokemon = source instanceof Pokemon ? source as Pokemon : null; + this.id = source.id; + this.player = sourcePokemon ? sourcePokemon.isPlayer() : source.player; + this.species = sourcePokemon ? sourcePokemon.species.speciesId : source.species; + this.formIndex = source.formIndex; + this.abilityIndex = source.abilityIndex; + this.shiny = source.shiny; + this.pokeball = source.pokeball; + this.level = source.level; + this.exp = source.exp; + this.levelExp = source.levelExp; + this.gender = source.gender; + this.hp = source.hp; + this.stats = source.stats; + this.ivs = source.ivs; + this.winCount = source.winCount; + + if (sourcePokemon) { + this.moveset = sourcePokemon.moveset; + this.status = sourcePokemon.status; + if (this.player) + this.summonData = sourcePokemon.summonData; + } else { + this.moveset = source.moveset.map((m: any) => { + const move = new PokemonMove(m.moveId, m.ppUsed, m.ppUp); + move.disableTurns = m.disableTurns; + return move; + }); + this.status = source.status + ? new Status(source.status.effect, source.status.turnCount, source.status.cureTurn) + : undefined; + + this.summonData = new PokemonSummonData(); + if (source.summonData) { + this.summonData.battleStats = source.summonData.battleStats; + this.summonData.moveQueue = source.summonData.moveQueue; + this.summonData.tags = []; // TODO + this.summonData.types = source.summonData.types; + } + } + } + + toPokemon(scene: BattleScene): Pokemon { + const species = getPokemonSpecies(this.species); + if (this.player) + return new PlayerPokemon(scene, species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this); + return new EnemyPokemon(scene, species, this.level, this); + } +} \ No newline at end of file