pokerogue/src/system/game-data.ts

1759 lines
64 KiB
TypeScript
Raw Normal View History

2023-12-31 18:30:37 -05:00
import BattleScene, { PokeballCounts, bypassLogin } from "../battle-scene";
2024-02-29 20:08:50 -05:00
import Pokemon, { EnemyPokemon, PlayerPokemon } from "../field/pokemon";
import { pokemonEvolutions, pokemonPrevolutions } from "../data/pokemon-evolutions";
import PokemonSpecies, { allSpecies, getPokemonSpecies, noStarterFormKeys, speciesStarters } from "../data/pokemon-species";
2023-04-20 15:46:05 -04:00
import * as Utils from "../utils";
import * as Overrides from "../overrides";
2023-04-28 15:03:42 -04:00
import PokemonData from "./pokemon-data";
import PersistentModifierData from "./modifier-data";
import ArenaData from "./arena-data";
2023-04-29 01:40:24 -04:00
import { Unlockables } from "./unlockables";
Add Challenges (#1459) * Initial challenge framework * Add type localisation * Change how challenges are tracked Also fixes the difficulty total text * MVP Renames challenge types, temporarily hides difficulty, and implements challenge saving. * Attempt to fix one legal pokemon in a double battle * Make monotype ignore type changing effects * Make isOfType correctly detect normal types * Try to fix double battles again * Make challenge function more like classic * Add helper function for fainted or not allowed * Add framework for fresh start challenge and improve comments * Try to fix evolution issues * Make form changing items only usable from rewards screen * Update localisation * Additional localisation change * Add achievements for completing challenges * Fix initialisation bug with challenge achievements * Add support for gamemode specific fixed battles Also make monogen challenges face the e4 of their generation * Add better support for mobile in challenges * Localise illegal evolution/form change message * Update achievement names * Make alternate forms count for monogen * Update monotype achievement icons * Add more comments * Improve comments * Fix mid battle form changes * Reorder mode list * Remove currently unused localisation entry * Add type overrides for monotype challenges Meloetta always counts for psychic and castform always counts for normal * Change how form changes are handled Now attempts a switch at the start of each turn instead of immediately * Add start button to challenge select screen * Make starter select back out to challenge screen if using challenges * Fix daily runs * Update tests to new game mode logic
2024-06-08 15:07:23 +10:00
import { GameModes, getGameMode } from "../game-mode";
2023-10-07 16:08:33 -04:00
import { BattleType } from "../battle";
import TrainerData from "./trainer-data";
import { trainerConfigs } from "../data/trainer-config";
2024-06-03 19:57:47 -04:00
import { SettingKeys, resetSettings, setSetting } from "./settings/settings";
import { achvs } from "./achv";
import EggData from "./egg-data";
import { Egg } from "../data/egg";
import { VoucherType, vouchers } from "./voucher";
2023-12-26 14:49:23 -05:00
import { AES, enc } from "crypto-js";
import { Mode } from "../ui/ui";
import { clientSessionId, loggedInUser, updateUserInfo } from "../account";
2024-01-05 22:24:05 -05:00
import { Nature } from "../data/nature";
import { GameStats } from "./game-stats";
2024-02-13 18:42:11 -05:00
import { Tutorial } from "../tutorial";
2024-02-25 12:45:41 -05:00
import { speciesEggMoves } from "../data/egg-moves";
import { allMoves } from "../data/move";
import { TrainerVariant } from "../field/trainer";
import { OutdatedPhase, ReloadSessionPhase } from "#app/phases";
import { Variant, variantData } from "#app/data/variant";
2024-06-03 19:57:47 -04:00
import {setSettingGamepad, SettingGamepad, settingGamepadDefaults} from "./settings/settings-gamepad";
import {setSettingKeyboard, SettingKeyboard} from "#app/system/settings/settings-keyboard";
import { TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena.js";
import { EnemyAttackStatusEffectChanceModifier } from "../modifier/modifier";
import { StatusEffect } from "#app/data/status-effect.js";
Add Challenges (#1459) * Initial challenge framework * Add type localisation * Change how challenges are tracked Also fixes the difficulty total text * MVP Renames challenge types, temporarily hides difficulty, and implements challenge saving. * Attempt to fix one legal pokemon in a double battle * Make monotype ignore type changing effects * Make isOfType correctly detect normal types * Try to fix double battles again * Make challenge function more like classic * Add helper function for fainted or not allowed * Add framework for fresh start challenge and improve comments * Try to fix evolution issues * Make form changing items only usable from rewards screen * Update localisation * Additional localisation change * Add achievements for completing challenges * Fix initialisation bug with challenge achievements * Add support for gamemode specific fixed battles Also make monogen challenges face the e4 of their generation * Add better support for mobile in challenges * Localise illegal evolution/form change message * Update achievement names * Make alternate forms count for monogen * Update monotype achievement icons * Add more comments * Improve comments * Fix mid battle form changes * Reorder mode list * Remove currently unused localisation entry * Add type overrides for monotype challenges Meloetta always counts for psychic and castform always counts for normal * Change how form changes are handled Now attempts a switch at the start of each turn instead of immediately * Add start button to challenge select screen * Make starter select back out to challenge screen if using challenges * Fix daily runs * Update tests to new game mode logic
2024-06-08 15:07:23 +10:00
import ChallengeData from "./challenge-data";
import { Device } from "#enums/devices";
import { GameDataType } from "#enums/game-data-type";
import { Moves } from "#enums/moves";
import { PlayerGender } from "#enums/player-gender";
import { Species } from "#enums/species";
export const defaultStarterSpecies: Species[] = [
Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE,
Species.CHIKORITA, Species.CYNDAQUIL, Species.TOTODILE,
Species.TREECKO, Species.TORCHIC, Species.MUDKIP,
Species.TURTWIG, Species.CHIMCHAR, Species.PIPLUP,
Species.SNIVY, Species.TEPIG, Species.OSHAWOTT,
Species.CHESPIN, Species.FENNEKIN, Species.FROAKIE,
Species.ROWLET, Species.LITTEN, Species.POPPLIO,
Species.GROOKEY, Species.SCORBUNNY, Species.SOBBLE,
Species.SPRIGATITO, Species.FUECOCO, Species.QUAXLY
];
2023-12-26 14:49:23 -05:00
const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary
2023-12-26 14:49:23 -05:00
export function getDataTypeKey(dataType: GameDataType, slotId: integer = 0): string {
2023-12-26 14:49:23 -05:00
switch (dataType) {
case GameDataType.SYSTEM:
return "data";
case GameDataType.SESSION:
let ret = "sessionData";
if (slotId) {
ret += slotId;
}
return ret;
case GameDataType.SETTINGS:
return "settings";
case GameDataType.TUTORIALS:
return "tutorials";
case GameDataType.SEEN_DIALOGUES:
return "seenDialogues";
2023-12-26 14:49:23 -05:00
}
}
[Testing] Flexible Testing Wrapper for Phaser-Based Battle-Scenes (#1908) * refactor executed code while importing and initializing all of these in loading-scene * reset to main * fix server url * added rule no-trailing-spaces * made progress * test somme data from a session save is working * trying to launch a battle * added fetch wrapper to load data locally * trying to mockAllSettled * pushPhase & shiftPhase * check integrity of exported session * set toke + loggedInUser in tests * progress on starting new battle * tring to test phase but it's async * mocking fetch * working mock fetch * need to handle pile of data * attempt to use real phaser classes * reorder overrides * refactored to use some real classes from phaser * removed useless things * started to work on some container mock * finished the mockContainer time to add some logic * some more mock containers * removed addMethods since there is the mock classes now * commented issues * attempt to create mockTextureManager * fix tests * mockSprite & mockText * yes but not really * yes but not really * fix tutorial callback * reached mode title * added achievement tests * fix test achievements with current state of mock * correct sequence loading for BattleScene with mockLoader ! * deep dive into next step * working wait until starter selection screen * added newGame method into wrapper * expect to save_slot * trying to manage pokemon sprite for getAll without success yet * added test for egg output * fixed egg test for June * fix tests + locate next issue to fix * we are in battle baby * added new game in one-line * export is working but export only what's in the fetch * fix start game as guest * refactored how we start a battle + cleanup * overrided mewtwo but issue with currentBattle * refactor: rename InitAchievements to initAchievements * added missing mock method * override level and pokemon forms working as intended * bringToTop Obj * remove launch battle in achivement test * fix getIndex when same pokemon * can run all tests * first attack, faint, and shop modifiers, MockClock * on method for container * added doAttack one-liner * one-line export data * removed throw error * feat: Make `scenes` property of `GameWrapper` class public The `scenes` property of the `GameWrapper` class was changed from private to public. This change allows external access to the `scenes` map, which is used to store Phaser scenes. This modification was made to enable easier manipulation and interaction with the scenes in the game. * correction * removed CanvasRenderer * added a param to remove console.log and added a param to preven scene create call * fix encounter wave 30 when it's a trainer * test double-battle * test fight without KO * test double fight no ko * fix crashing texture + added Text wrapper to log fight * fix tests on boss - trainer - rival * chore: Refactor BattleScene initialization and add new phases Refactor the BattleScene initialization code to remove unnecessary delay and improve performance. Also, add new phases for the title and unavailable states to enhance the game experience. * rework of Game tests * skipFn is working * added onNextPrompt and restore Og Start * better newGame * added skipFN in remove * not yet working test but updated interceptors * do attack work but not on PostSummonPhase phase when there is mention of silcoon and wurmple * error located, it's just a double fight, i was not there yet * single OHKO & double no OHKO * added expirationFn into next prompt * all tests are passing * working test on non damaging move from opponent * cleaned a bit * removed phaser initialisation on every tests * renamed test file * added load system data * added some ability support * added onKill & onSummon abilities test * removed useless test + cleanup * removed useless test + cleanup * fixed tests after merge main * added itemHeld endTurn trigger test (toxic orb) * added runFrom..To * added mustRun to assert currentPhase * added no-miss move to test things * cleaner restore mock * fix test * fix moxie test + game speed * improve test speed * added onOurself and onOpponent mvoe test * added onDamage test for tackle * removed timeout in intervals to run tests faster * cleanup * added never crit override + separate file per test + remove randomness in randBattleSeedInt * move folders * better org * renamed itemHeld folder to items * fix deploy.yml * cleanup * simplified the gameManager start battle and allow single pokemon in party * remove the need of mode development * added input handler to test inputs + remove time from phaser into inputController * added keyboard support * added fakeMobile support * added details * removed a console.log + added logUp * move test to folder * fixed canvas issue * added starter select tests * added some more test on starter-select * added battle-order tests * added battle-order tests * fixing Phaser RNG * ordering stats for better reading * fix tests for main * adapt battle-order test to be more readable * fix merge * fix some errors and silent all errors from gameWrapper since it's not possible to avoid them * fix mocks to manage childs & stuffs * added some docs * fix achievement test * removed an unused file * separate misc tests to clean battle.test file * added a basic french lokalization test * added i18n where it needs to be used only * revers extracted method * removed unused method * removed handler fetch since we do not test anything server related * fix test with handlers removed * added intrepid sword test * fix enum exp party --------- Co-authored-by: Frederico Santos <frederico.f.santos@tecnico.ulisboa.pt>
2024-06-08 00:33:45 +02:00
export function encrypt(data: string, bypassLogin: boolean): string {
return (bypassLogin
? (data: string) => btoa(data)
: (data: string) => AES.encrypt(data, saveKey))(data);
}
[Testing] Flexible Testing Wrapper for Phaser-Based Battle-Scenes (#1908) * refactor executed code while importing and initializing all of these in loading-scene * reset to main * fix server url * added rule no-trailing-spaces * made progress * test somme data from a session save is working * trying to launch a battle * added fetch wrapper to load data locally * trying to mockAllSettled * pushPhase & shiftPhase * check integrity of exported session * set toke + loggedInUser in tests * progress on starting new battle * tring to test phase but it's async * mocking fetch * working mock fetch * need to handle pile of data * attempt to use real phaser classes * reorder overrides * refactored to use some real classes from phaser * removed useless things * started to work on some container mock * finished the mockContainer time to add some logic * some more mock containers * removed addMethods since there is the mock classes now * commented issues * attempt to create mockTextureManager * fix tests * mockSprite & mockText * yes but not really * yes but not really * fix tutorial callback * reached mode title * added achievement tests * fix test achievements with current state of mock * correct sequence loading for BattleScene with mockLoader ! * deep dive into next step * working wait until starter selection screen * added newGame method into wrapper * expect to save_slot * trying to manage pokemon sprite for getAll without success yet * added test for egg output * fixed egg test for June * fix tests + locate next issue to fix * we are in battle baby * added new game in one-line * export is working but export only what's in the fetch * fix start game as guest * refactored how we start a battle + cleanup * overrided mewtwo but issue with currentBattle * refactor: rename InitAchievements to initAchievements * added missing mock method * override level and pokemon forms working as intended * bringToTop Obj * remove launch battle in achivement test * fix getIndex when same pokemon * can run all tests * first attack, faint, and shop modifiers, MockClock * on method for container * added doAttack one-liner * one-line export data * removed throw error * feat: Make `scenes` property of `GameWrapper` class public The `scenes` property of the `GameWrapper` class was changed from private to public. This change allows external access to the `scenes` map, which is used to store Phaser scenes. This modification was made to enable easier manipulation and interaction with the scenes in the game. * correction * removed CanvasRenderer * added a param to remove console.log and added a param to preven scene create call * fix encounter wave 30 when it's a trainer * test double-battle * test fight without KO * test double fight no ko * fix crashing texture + added Text wrapper to log fight * fix tests on boss - trainer - rival * chore: Refactor BattleScene initialization and add new phases Refactor the BattleScene initialization code to remove unnecessary delay and improve performance. Also, add new phases for the title and unavailable states to enhance the game experience. * rework of Game tests * skipFn is working * added onNextPrompt and restore Og Start * better newGame * added skipFN in remove * not yet working test but updated interceptors * do attack work but not on PostSummonPhase phase when there is mention of silcoon and wurmple * error located, it's just a double fight, i was not there yet * single OHKO & double no OHKO * added expirationFn into next prompt * all tests are passing * working test on non damaging move from opponent * cleaned a bit * removed phaser initialisation on every tests * renamed test file * added load system data * added some ability support * added onKill & onSummon abilities test * removed useless test + cleanup * removed useless test + cleanup * fixed tests after merge main * added itemHeld endTurn trigger test (toxic orb) * added runFrom..To * added mustRun to assert currentPhase * added no-miss move to test things * cleaner restore mock * fix test * fix moxie test + game speed * improve test speed * added onOurself and onOpponent mvoe test * added onDamage test for tackle * removed timeout in intervals to run tests faster * cleanup * added never crit override + separate file per test + remove randomness in randBattleSeedInt * move folders * better org * renamed itemHeld folder to items * fix deploy.yml * cleanup * simplified the gameManager start battle and allow single pokemon in party * remove the need of mode development * added input handler to test inputs + remove time from phaser into inputController * added keyboard support * added fakeMobile support * added details * removed a console.log + added logUp * move test to folder * fixed canvas issue * added starter select tests * added some more test on starter-select * added battle-order tests * added battle-order tests * fixing Phaser RNG * ordering stats for better reading * fix tests for main * adapt battle-order test to be more readable * fix merge * fix some errors and silent all errors from gameWrapper since it's not possible to avoid them * fix mocks to manage childs & stuffs * added some docs * fix achievement test * removed an unused file * separate misc tests to clean battle.test file * added a basic french lokalization test * added i18n where it needs to be used only * revers extracted method * removed unused method * removed handler fetch since we do not test anything server related * fix test with handlers removed * added intrepid sword test * fix enum exp party --------- Co-authored-by: Frederico Santos <frederico.f.santos@tecnico.ulisboa.pt>
2024-06-08 00:33:45 +02:00
export function decrypt(data: string, bypassLogin: boolean): string {
return (bypassLogin
? (data: string) => atob(data)
: (data: string) => AES.decrypt(data, saveKey).toString(enc.Utf8))(data);
}
2023-04-28 15:03:42 -04:00
interface SystemSaveData {
2023-04-18 01:32:26 -04:00
trainerId: integer;
secretId: integer;
2024-02-06 16:15:35 -05:00
gender: PlayerGender;
2023-04-18 01:32:26 -04:00
dexData: DexData;
starterData: StarterData;
gameStats: GameStats;
2023-04-29 01:40:24 -04:00
unlocks: Unlocks;
achvUnlocks: AchvUnlocks;
voucherUnlocks: VoucherUnlocks;
voucherCounts: VoucherCounts;
eggs: EggData[];
gameVersion: string;
2023-04-28 15:03:42 -04:00
timestamp: integer;
eggPity: integer[];
unlockPity: integer[];
2023-04-28 15:03:42 -04:00
}
export interface SessionSaveData {
seed: string;
2024-01-11 20:27:50 -05:00
playTime: integer;
2024-03-14 16:26:57 -04:00
gameMode: GameModes;
2023-04-28 15:03:42 -04:00
party: PokemonData[];
2023-10-07 16:08:33 -04:00
enemyParty: PokemonData[];
2023-04-28 15:03:42 -04:00
modifiers: PersistentModifierData[];
enemyModifiers: PersistentModifierData[];
arena: ArenaData;
pokeballCounts: PokeballCounts;
money: integer;
2024-03-17 11:36:19 -04:00
score: integer;
2023-04-28 15:03:42 -04:00
waveIndex: integer;
2023-10-07 16:08:33 -04:00
battleType: BattleType;
trainer: TrainerData;
gameVersion: string;
2023-04-28 15:03:42 -04:00
timestamp: integer;
Add Challenges (#1459) * Initial challenge framework * Add type localisation * Change how challenges are tracked Also fixes the difficulty total text * MVP Renames challenge types, temporarily hides difficulty, and implements challenge saving. * Attempt to fix one legal pokemon in a double battle * Make monotype ignore type changing effects * Make isOfType correctly detect normal types * Try to fix double battles again * Make challenge function more like classic * Add helper function for fainted or not allowed * Add framework for fresh start challenge and improve comments * Try to fix evolution issues * Make form changing items only usable from rewards screen * Update localisation * Additional localisation change * Add achievements for completing challenges * Fix initialisation bug with challenge achievements * Add support for gamemode specific fixed battles Also make monogen challenges face the e4 of their generation * Add better support for mobile in challenges * Localise illegal evolution/form change message * Update achievement names * Make alternate forms count for monogen * Update monotype achievement icons * Add more comments * Improve comments * Fix mid battle form changes * Reorder mode list * Remove currently unused localisation entry * Add type overrides for monotype challenges Meloetta always counts for psychic and castform always counts for normal * Change how form changes are handled Now attempts a switch at the start of each turn instead of immediately * Add start button to challenge select screen * Make starter select back out to challenge screen if using challenges * Fix daily runs * Update tests to new game mode logic
2024-06-08 15:07:23 +10:00
challenges: ChallengeData[];
2023-04-18 01:32:26 -04:00
}
2023-04-29 01:40:24 -04:00
interface Unlocks {
[key: integer]: boolean;
}
interface AchvUnlocks {
[key: string]: integer
}
interface VoucherUnlocks {
[key: string]: integer
}
export interface VoucherCounts {
[type: string]: integer;
}
export interface DexData {
2023-11-12 23:47:04 -05:00
[key: integer]: DexEntry
}
export interface DexEntry {
2023-11-12 23:47:04 -05:00
seenAttr: bigint;
caughtAttr: bigint;
2024-01-05 22:24:05 -05:00
natureAttr: integer,
2023-11-12 23:47:04 -05:00
seenCount: integer;
caughtCount: integer;
hatchedCount: integer;
2023-11-12 23:47:04 -05:00
ivs: integer[];
}
2023-11-12 23:47:04 -05:00
export const DexAttr = {
NON_SHINY: 1n,
SHINY: 2n,
MALE: 4n,
FEMALE: 8n,
2024-04-18 22:52:26 -04:00
DEFAULT_VARIANT: 16n,
VARIANT_2: 32n,
VARIANT_3: 64n,
2023-11-12 23:47:04 -05:00
DEFAULT_FORM: 128n
};
2023-11-12 23:47:04 -05:00
export interface DexAttrProps {
shiny: boolean;
female: boolean;
2024-04-18 22:52:26 -04:00
variant: Variant;
2023-11-12 23:47:04 -05:00
formIndex: integer;
}
2024-04-18 22:52:26 -04:00
export const AbilityAttr = {
ABILITY_1: 1,
ABILITY_2: 2,
ABILITY_HIDDEN: 4
};
2024-04-18 22:52:26 -04:00
export type StarterMoveset = [ Moves ] | [ Moves, Moves ] | [ Moves, Moves, Moves ] | [ Moves, Moves, Moves, Moves ];
export interface StarterFormMoveData {
[key: integer]: StarterMoveset
}
export interface StarterMoveData {
[key: integer]: StarterMoveset | StarterFormMoveData
}
export interface StarterDataEntry {
2024-05-24 01:45:04 +02:00
moveset: StarterMoveset | StarterFormMoveData;
eggMoves: integer;
candyCount: integer;
friendship: integer;
2024-04-18 22:52:26 -04:00
abilityAttr: integer;
passiveAttr: integer;
valueReduction: integer;
classicWinCount: integer;
}
export interface StarterData {
[key: integer]: StarterDataEntry
2024-02-25 12:45:41 -05:00
}
2024-02-13 18:42:11 -05:00
export interface TutorialFlags {
[key: string]: boolean
}
export interface SeenDialogues {
[key: string]: boolean;
}
const systemShortKeys = {
seenAttr: "$sa",
caughtAttr: "$ca",
natureAttr: "$na",
seenCount: "$s" ,
caughtCount: "$c",
hatchedCount: "$hc",
ivs: "$i",
moveset: "$m",
eggMoves: "$em",
candyCount: "$x",
friendship: "$f",
abilityAttr: "$a",
passiveAttr: "$pa",
valueReduction: "$vr",
classicWinCount: "$wc"
};
2023-04-18 01:32:26 -04:00
export class GameData {
private scene: BattleScene;
public trainerId: integer;
public secretId: integer;
2024-02-06 16:15:35 -05:00
public gender: PlayerGender;
2024-05-24 01:45:04 +02:00
public dexData: DexData;
2024-01-05 22:24:05 -05:00
private defaultDexData: DexData;
public starterData: StarterData;
2024-02-25 12:45:41 -05:00
public gameStats: GameStats;
2023-04-29 01:40:24 -04:00
public unlocks: Unlocks;
public achvUnlocks: AchvUnlocks;
public voucherUnlocks: VoucherUnlocks;
public voucherCounts: VoucherCounts;
public eggs: Egg[];
public eggPity: integer[];
public unlockPity: integer[];
constructor(scene: BattleScene) {
this.scene = scene;
2023-10-26 16:33:59 -04:00
this.loadSettings();
this.loadGamepadSettings();
this.loadMappingConfigs();
this.trainerId = Utils.randInt(65536);
this.secretId = Utils.randInt(65536);
this.starterData = {};
this.gameStats = new GameStats();
2023-04-29 01:40:24 -04:00
this.unlocks = {
[Unlockables.ENDLESS_MODE]: false,
[Unlockables.MINI_BLACK_HOLE]: false,
[Unlockables.SPLICED_ENDLESS_MODE]: false
2023-04-29 01:40:24 -04:00
};
this.achvUnlocks = {};
this.voucherUnlocks = {};
this.voucherCounts = {
[VoucherType.REGULAR]: 0,
[VoucherType.PLUS]: 0,
[VoucherType.PREMIUM]: 0,
[VoucherType.GOLDEN]: 0
};
this.eggs = [];
this.eggPity = [0, 0, 0, 0];
this.unlockPity = [0, 0, 0, 0];
2023-04-26 16:07:29 -04:00
this.initDexData();
this.initStarterData();
}
2024-05-12 23:01:05 -04:00
public getSystemSaveData(): SystemSaveData {
return {
trainerId: this.trainerId,
secretId: this.secretId,
gender: this.gender,
dexData: this.dexData,
starterData: this.starterData,
gameStats: this.gameStats,
unlocks: this.unlocks,
achvUnlocks: this.achvUnlocks,
voucherUnlocks: this.voucherUnlocks,
voucherCounts: this.voucherCounts,
eggs: this.eggs.map(e => new EggData(e)),
gameVersion: this.scene.game.config.gameVersion,
timestamp: new Date().getTime(),
eggPity: this.eggPity.slice(0),
unlockPity: this.unlockPity.slice(0)
2024-05-12 23:01:05 -04:00
};
}
2023-12-30 18:41:25 -05:00
public saveSystem(): Promise<boolean> {
return new Promise<boolean>(resolve => {
2024-04-04 10:16:29 -04:00
this.scene.ui.savingIcon.show();
2024-05-12 23:01:05 -04:00
const data = this.getSystemSaveData();
const maxIntAttrValue = Math.pow(2, 31);
const systemData = JSON.stringify(data, (k: any, v: any) => typeof v === "bigint" ? v <= maxIntAttrValue ? Number(v) : v.toString() : v);
localStorage.setItem(`data_${loggedInUser.username}`, encrypt(systemData, bypassLogin));
if (!bypassLogin) {
Utils.apiPost(`savedata/system/update?clientSessionId=${clientSessionId}`, systemData, undefined, true)
.then(response => response.text())
.then(error => {
this.scene.ui.savingIcon.hide();
if (error) {
if (error.startsWith("client version out of date")) {
this.scene.clearPhaseQueue();
this.scene.unshiftPhase(new OutdatedPhase(this.scene));
} else if (error.startsWith("session out of date")) {
this.scene.clearPhaseQueue();
this.scene.unshiftPhase(new ReloadSessionPhase(this.scene));
2023-12-31 18:30:37 -05:00
}
console.error(error);
return resolve(false);
}
resolve(true);
});
} else {
this.scene.ui.savingIcon.hide();
2024-04-04 10:16:29 -04:00
resolve(true);
}
2023-12-30 18:41:25 -05:00
});
}
2024-02-04 00:49:57 -05:00
public loadSystem(): Promise<boolean> {
2023-12-31 18:30:37 -05:00
return new Promise<boolean>(resolve => {
console.log("Client Session:", clientSessionId);
if (bypassLogin && !localStorage.getItem(`data_${loggedInUser.username}`)) {
2024-02-19 20:36:10 -05:00
return resolve(false);
}
2023-04-18 01:32:26 -04:00
if (!bypassLogin) {
Utils.apiFetch(`savedata/system/get?clientSessionId=${clientSessionId}`, true)
.then(response => response.text())
.then(response => {
if (!response.length || response[0] !== "{") {
if (response.startsWith("sql: no rows in result set")) {
this.scene.queueMessage("Save data could not be found. If this is a new account, you can safely ignore this message.", null, true);
return resolve(true);
} else if (response.indexOf("Too many connections") > -1) {
this.scene.queueMessage("Too many people are trying to connect and the server is overloaded. Please try again later.", null, true);
return resolve(false);
}
console.error(response);
return resolve(false);
}
2023-11-12 23:47:04 -05:00
const cachedSystem = localStorage.getItem(`data_${loggedInUser.username}`);
this.initSystem(response, cachedSystem ? AES.decrypt(cachedSystem, saveKey).toString(enc.Utf8) : null).then(resolve);
});
} else {
this.initSystem(decrypt(localStorage.getItem(`data_${loggedInUser.username}`), bypassLogin)).then(resolve);
}
});
}
2023-04-18 01:32:26 -04:00
public initSystem(systemDataStr: string, cachedSystemDataStr?: string): Promise<boolean> {
return new Promise<boolean>(resolve => {
try {
let systemData = this.parseSystemData(systemDataStr);
if (cachedSystemDataStr) {
const cachedSystemData = this.parseSystemData(cachedSystemDataStr);
if (cachedSystemData.timestamp > systemData.timestamp) {
console.debug("Use cached system");
systemData = cachedSystemData;
systemDataStr = cachedSystemDataStr;
} else {
this.clearLocalData();
}
}
console.debug(systemData);
localStorage.setItem(`data_${loggedInUser.username}`, encrypt(systemDataStr, bypassLogin));
/*const versions = [ this.scene.game.config.gameVersion, data.gameVersion || '0.0.0' ];
2024-05-24 01:45:04 +02:00
if (versions[0] !== versions[1]) {
const [ versionNumbers, oldVersionNumbers ] = versions.map(ver => ver.split('.').map(v => parseInt(v)));
}*/
2024-02-06 16:15:35 -05:00
this.trainerId = systemData.trainerId;
this.secretId = systemData.secretId;
2024-02-06 16:15:35 -05:00
this.gender = systemData.gender;
2024-06-03 19:57:47 -04:00
this.saveSetting(SettingKeys.Player_Gender, systemData.gender === PlayerGender.FEMALE ? 1 : 0);
const initStarterData = !systemData.starterData;
if (initStarterData) {
this.initStarterData();
if (systemData["starterMoveData"]) {
const starterMoveData = systemData["starterMoveData"];
for (const s of Object.keys(starterMoveData)) {
this.starterData[s].moveset = starterMoveData[s];
}
2024-04-18 22:52:26 -04:00
}
if (systemData["starterEggMoveData"]) {
const starterEggMoveData = systemData["starterEggMoveData"];
for (const s of Object.keys(starterEggMoveData)) {
this.starterData[s].eggMoves = starterEggMoveData[s];
}
2024-05-05 17:11:29 -04:00
}
this.migrateStarterAbilities(systemData, this.starterData);
} else {
if ([ "1.0.0", "1.0.1" ].includes(systemData.gameVersion)) {
this.migrateStarterAbilities(systemData);
}
//this.fixVariantData(systemData);
this.fixStarterData(systemData);
// Migrate ability starter data if empty for caught species
Object.keys(systemData.starterData).forEach(sd => {
if (systemData.dexData[sd].caughtAttr && !systemData.starterData[sd].abilityAttr) {
systemData.starterData[sd].abilityAttr = 1;
}
});
this.starterData = systemData.starterData;
}
if (systemData.gameStats) {
if (systemData.gameStats.legendaryPokemonCaught !== undefined && systemData.gameStats.subLegendaryPokemonCaught === undefined) {
this.fixLegendaryStats(systemData);
}
this.gameStats = systemData.gameStats;
}
2023-12-31 18:30:37 -05:00
if (systemData.unlocks) {
for (const key of Object.keys(systemData.unlocks)) {
if (this.unlocks.hasOwnProperty(key)) {
this.unlocks[key] = systemData.unlocks[key];
}
2023-12-31 18:30:37 -05:00
}
}
if (systemData.achvUnlocks) {
for (const a of Object.keys(systemData.achvUnlocks)) {
if (achvs.hasOwnProperty(a)) {
this.achvUnlocks[a] = systemData.achvUnlocks[a];
}
2024-05-24 01:45:04 +02:00
}
}
if (systemData.voucherUnlocks) {
for (const v of Object.keys(systemData.voucherUnlocks)) {
if (vouchers.hasOwnProperty(v)) {
this.voucherUnlocks[v] = systemData.voucherUnlocks[v];
}
}
}
if (systemData.voucherCounts) {
Utils.getEnumKeys(VoucherType).forEach(key => {
const index = VoucherType[key];
this.voucherCounts[index] = systemData.voucherCounts[index] || 0;
});
2024-02-19 20:36:10 -05:00
}
2023-12-31 18:30:37 -05:00
this.eggs = systemData.eggs
? systemData.eggs.map(e => e.toEgg())
: [];
this.eggPity = systemData.eggPity ? systemData.eggPity.slice(0) : [0, 0, 0, 0];
this.unlockPity = systemData.unlockPity ? systemData.unlockPity.slice(0) : [0, 0, 0, 0];
this.dexData = Object.assign(this.dexData, systemData.dexData);
this.consolidateDexData(this.dexData);
this.defaultDexData = null;
if (initStarterData) {
const starterIds = Object.keys(this.starterData).map(s => parseInt(s) as Species);
for (const s of starterIds) {
this.starterData[s].candyCount += this.dexData[s].caughtCount;
this.starterData[s].candyCount += this.dexData[s].hatchedCount * 2;
if (this.dexData[s].caughtAttr & DexAttr.SHINY) {
this.starterData[s].candyCount += 4;
}
}
}
2023-12-31 18:30:37 -05:00
resolve(true);
} catch (err) {
console.error(err);
resolve(false);
}
2023-12-31 18:30:37 -05:00
});
}
[Testing] Flexible Testing Wrapper for Phaser-Based Battle-Scenes (#1908) * refactor executed code while importing and initializing all of these in loading-scene * reset to main * fix server url * added rule no-trailing-spaces * made progress * test somme data from a session save is working * trying to launch a battle * added fetch wrapper to load data locally * trying to mockAllSettled * pushPhase & shiftPhase * check integrity of exported session * set toke + loggedInUser in tests * progress on starting new battle * tring to test phase but it's async * mocking fetch * working mock fetch * need to handle pile of data * attempt to use real phaser classes * reorder overrides * refactored to use some real classes from phaser * removed useless things * started to work on some container mock * finished the mockContainer time to add some logic * some more mock containers * removed addMethods since there is the mock classes now * commented issues * attempt to create mockTextureManager * fix tests * mockSprite & mockText * yes but not really * yes but not really * fix tutorial callback * reached mode title * added achievement tests * fix test achievements with current state of mock * correct sequence loading for BattleScene with mockLoader ! * deep dive into next step * working wait until starter selection screen * added newGame method into wrapper * expect to save_slot * trying to manage pokemon sprite for getAll without success yet * added test for egg output * fixed egg test for June * fix tests + locate next issue to fix * we are in battle baby * added new game in one-line * export is working but export only what's in the fetch * fix start game as guest * refactored how we start a battle + cleanup * overrided mewtwo but issue with currentBattle * refactor: rename InitAchievements to initAchievements * added missing mock method * override level and pokemon forms working as intended * bringToTop Obj * remove launch battle in achivement test * fix getIndex when same pokemon * can run all tests * first attack, faint, and shop modifiers, MockClock * on method for container * added doAttack one-liner * one-line export data * removed throw error * feat: Make `scenes` property of `GameWrapper` class public The `scenes` property of the `GameWrapper` class was changed from private to public. This change allows external access to the `scenes` map, which is used to store Phaser scenes. This modification was made to enable easier manipulation and interaction with the scenes in the game. * correction * removed CanvasRenderer * added a param to remove console.log and added a param to preven scene create call * fix encounter wave 30 when it's a trainer * test double-battle * test fight without KO * test double fight no ko * fix crashing texture + added Text wrapper to log fight * fix tests on boss - trainer - rival * chore: Refactor BattleScene initialization and add new phases Refactor the BattleScene initialization code to remove unnecessary delay and improve performance. Also, add new phases for the title and unavailable states to enhance the game experience. * rework of Game tests * skipFn is working * added onNextPrompt and restore Og Start * better newGame * added skipFN in remove * not yet working test but updated interceptors * do attack work but not on PostSummonPhase phase when there is mention of silcoon and wurmple * error located, it's just a double fight, i was not there yet * single OHKO & double no OHKO * added expirationFn into next prompt * all tests are passing * working test on non damaging move from opponent * cleaned a bit * removed phaser initialisation on every tests * renamed test file * added load system data * added some ability support * added onKill & onSummon abilities test * removed useless test + cleanup * removed useless test + cleanup * fixed tests after merge main * added itemHeld endTurn trigger test (toxic orb) * added runFrom..To * added mustRun to assert currentPhase * added no-miss move to test things * cleaner restore mock * fix test * fix moxie test + game speed * improve test speed * added onOurself and onOpponent mvoe test * added onDamage test for tackle * removed timeout in intervals to run tests faster * cleanup * added never crit override + separate file per test + remove randomness in randBattleSeedInt * move folders * better org * renamed itemHeld folder to items * fix deploy.yml * cleanup * simplified the gameManager start battle and allow single pokemon in party * remove the need of mode development * added input handler to test inputs + remove time from phaser into inputController * added keyboard support * added fakeMobile support * added details * removed a console.log + added logUp * move test to folder * fixed canvas issue * added starter select tests * added some more test on starter-select * added battle-order tests * added battle-order tests * fixing Phaser RNG * ordering stats for better reading * fix tests for main * adapt battle-order test to be more readable * fix merge * fix some errors and silent all errors from gameWrapper since it's not possible to avoid them * fix mocks to manage childs & stuffs * added some docs * fix achievement test * removed an unused file * separate misc tests to clean battle.test file * added a basic french lokalization test * added i18n where it needs to be used only * revers extracted method * removed unused method * removed handler fetch since we do not test anything server related * fix test with handlers removed * added intrepid sword test * fix enum exp party --------- Co-authored-by: Frederico Santos <frederico.f.santos@tecnico.ulisboa.pt>
2024-06-08 00:33:45 +02:00
parseSystemData(dataStr: string): SystemSaveData {
2023-12-26 14:49:23 -05:00
return JSON.parse(dataStr, (k: string, v: any) => {
if (k === "gameStats") {
return new GameStats(v);
} else if (k === "eggs") {
2023-12-26 14:49:23 -05:00
const ret: EggData[] = [];
if (v === null) {
v = [];
}
for (const e of v) {
2023-12-26 14:49:23 -05:00
ret.push(new EggData(e));
}
2023-12-26 14:49:23 -05:00
return ret;
}
return k.endsWith("Attr") && ![ "natureAttr", "abilityAttr", "passiveAttr" ].includes(k) ? BigInt(v) : v;
2023-12-26 14:49:23 -05:00
}) as SystemSaveData;
}
[Testing] Flexible Testing Wrapper for Phaser-Based Battle-Scenes (#1908) * refactor executed code while importing and initializing all of these in loading-scene * reset to main * fix server url * added rule no-trailing-spaces * made progress * test somme data from a session save is working * trying to launch a battle * added fetch wrapper to load data locally * trying to mockAllSettled * pushPhase & shiftPhase * check integrity of exported session * set toke + loggedInUser in tests * progress on starting new battle * tring to test phase but it's async * mocking fetch * working mock fetch * need to handle pile of data * attempt to use real phaser classes * reorder overrides * refactored to use some real classes from phaser * removed useless things * started to work on some container mock * finished the mockContainer time to add some logic * some more mock containers * removed addMethods since there is the mock classes now * commented issues * attempt to create mockTextureManager * fix tests * mockSprite & mockText * yes but not really * yes but not really * fix tutorial callback * reached mode title * added achievement tests * fix test achievements with current state of mock * correct sequence loading for BattleScene with mockLoader ! * deep dive into next step * working wait until starter selection screen * added newGame method into wrapper * expect to save_slot * trying to manage pokemon sprite for getAll without success yet * added test for egg output * fixed egg test for June * fix tests + locate next issue to fix * we are in battle baby * added new game in one-line * export is working but export only what's in the fetch * fix start game as guest * refactored how we start a battle + cleanup * overrided mewtwo but issue with currentBattle * refactor: rename InitAchievements to initAchievements * added missing mock method * override level and pokemon forms working as intended * bringToTop Obj * remove launch battle in achivement test * fix getIndex when same pokemon * can run all tests * first attack, faint, and shop modifiers, MockClock * on method for container * added doAttack one-liner * one-line export data * removed throw error * feat: Make `scenes` property of `GameWrapper` class public The `scenes` property of the `GameWrapper` class was changed from private to public. This change allows external access to the `scenes` map, which is used to store Phaser scenes. This modification was made to enable easier manipulation and interaction with the scenes in the game. * correction * removed CanvasRenderer * added a param to remove console.log and added a param to preven scene create call * fix encounter wave 30 when it's a trainer * test double-battle * test fight without KO * test double fight no ko * fix crashing texture + added Text wrapper to log fight * fix tests on boss - trainer - rival * chore: Refactor BattleScene initialization and add new phases Refactor the BattleScene initialization code to remove unnecessary delay and improve performance. Also, add new phases for the title and unavailable states to enhance the game experience. * rework of Game tests * skipFn is working * added onNextPrompt and restore Og Start * better newGame * added skipFN in remove * not yet working test but updated interceptors * do attack work but not on PostSummonPhase phase when there is mention of silcoon and wurmple * error located, it's just a double fight, i was not there yet * single OHKO & double no OHKO * added expirationFn into next prompt * all tests are passing * working test on non damaging move from opponent * cleaned a bit * removed phaser initialisation on every tests * renamed test file * added load system data * added some ability support * added onKill & onSummon abilities test * removed useless test + cleanup * removed useless test + cleanup * fixed tests after merge main * added itemHeld endTurn trigger test (toxic orb) * added runFrom..To * added mustRun to assert currentPhase * added no-miss move to test things * cleaner restore mock * fix test * fix moxie test + game speed * improve test speed * added onOurself and onOpponent mvoe test * added onDamage test for tackle * removed timeout in intervals to run tests faster * cleanup * added never crit override + separate file per test + remove randomness in randBattleSeedInt * move folders * better org * renamed itemHeld folder to items * fix deploy.yml * cleanup * simplified the gameManager start battle and allow single pokemon in party * remove the need of mode development * added input handler to test inputs + remove time from phaser into inputController * added keyboard support * added fakeMobile support * added details * removed a console.log + added logUp * move test to folder * fixed canvas issue * added starter select tests * added some more test on starter-select * added battle-order tests * added battle-order tests * fixing Phaser RNG * ordering stats for better reading * fix tests for main * adapt battle-order test to be more readable * fix merge * fix some errors and silent all errors from gameWrapper since it's not possible to avoid them * fix mocks to manage childs & stuffs * added some docs * fix achievement test * removed an unused file * separate misc tests to clean battle.test file * added a basic french lokalization test * added i18n where it needs to be used only * revers extracted method * removed unused method * removed handler fetch since we do not test anything server related * fix test with handlers removed * added intrepid sword test * fix enum exp party --------- Co-authored-by: Frederico Santos <frederico.f.santos@tecnico.ulisboa.pt>
2024-06-08 00:33:45 +02:00
convertSystemDataStr(dataStr: string, shorten: boolean = false): string {
if (!shorten) {
// Account for past key oversight
dataStr = dataStr.replace(/\$pAttr/g, "$pa");
}
const fromKeys = shorten ? Object.keys(systemShortKeys) : Object.values(systemShortKeys);
const toKeys = shorten ? Object.values(systemShortKeys) : Object.keys(systemShortKeys);
for (const k in fromKeys) {
dataStr = dataStr.replace(new RegExp(`${fromKeys[k].replace("$", "\\$")}`, "g"), toKeys[k]);
}
return dataStr;
}
public async verify(): Promise<boolean> {
if (bypassLogin) {
return true;
}
const response = await Utils.apiFetch(`savedata/system/verify?clientSessionId=${clientSessionId}`, true)
.then(response => response.json());
if (!response.valid) {
this.scene.clearPhaseQueue();
this.scene.unshiftPhase(new ReloadSessionPhase(this.scene, JSON.stringify(response.systemData)));
this.clearLocalData();
return false;
}
return true;
}
public clearLocalData(): void {
if (bypassLogin) {
2024-05-15 01:54:15 -04:00
return;
}
localStorage.removeItem(`data_${loggedInUser.username}`);
for (let s = 0; s < 5; s++) {
localStorage.removeItem(`sessionData${s ? s : ""}_${loggedInUser.username}`);
}
}
2024-06-03 19:57:47 -04:00
/**
* Saves a setting to localStorage
* @param setting string ideally of SettingKeys
* @param valueIndex index of the setting's option
* @returns true
*/
public saveSetting(setting: string, valueIndex: integer): boolean {
2023-10-26 16:33:59 -04:00
let settings: object = {};
if (localStorage.hasOwnProperty("settings")) {
settings = JSON.parse(localStorage.getItem("settings"));
}
2023-10-26 16:33:59 -04:00
2024-06-03 19:57:47 -04:00
setSetting(this.scene, setting, valueIndex);
2023-10-26 16:33:59 -04:00
2024-06-03 19:57:47 -04:00
settings[setting] = valueIndex;
2023-10-26 16:33:59 -04:00
localStorage.setItem("settings", JSON.stringify(settings));
2023-10-26 16:33:59 -04:00
return true;
}
/**
* Saves the mapping configurations for a specified device.
*
* @param deviceName - The name of the device for which the configurations are being saved.
* @param config - The configuration object containing custom mapping details.
* @returns `true` if the configurations are successfully saved.
*/
public saveMappingConfigs(deviceName: string, config): boolean {
const key = deviceName.toLowerCase(); // Convert the gamepad name to lowercase to use as a key
let mappingConfigs: object = {}; // Initialize an empty object to hold the mapping configurations
if (localStorage.hasOwnProperty("mappingConfigs")) {// Check if 'mappingConfigs' exists in localStorage
mappingConfigs = JSON.parse(localStorage.getItem("mappingConfigs"));
} // Parse the existing 'mappingConfigs' from localStorage
if (!mappingConfigs[key]) {
mappingConfigs[key] = {};
} // If there is no configuration for the given key, create an empty object for it
mappingConfigs[key].custom = config.custom; // Assign the custom configuration to the mapping configuration for the given key
localStorage.setItem("mappingConfigs", JSON.stringify(mappingConfigs)); // Save the updated mapping configurations back to localStorage
return true; // Return true to indicate the operation was successful
}
/**
* Loads the mapping configurations from localStorage and injects them into the input controller.
*
* @returns `true` if the configurations are successfully loaded and injected; `false` if no configurations are found in localStorage.
*
* @remarks
* This method checks if the 'mappingConfigs' entry exists in localStorage. If it does not exist, the method returns `false`.
* If 'mappingConfigs' exists, it parses the configurations and injects each configuration into the input controller
* for the corresponding gamepad or device key. The method then returns `true` to indicate success.
*/
public loadMappingConfigs(): boolean {
if (!localStorage.hasOwnProperty("mappingConfigs")) {// Check if 'mappingConfigs' exists in localStorage
return false;
} // If 'mappingConfigs' does not exist, return false
const mappingConfigs = JSON.parse(localStorage.getItem("mappingConfigs")); // Parse the existing 'mappingConfigs' from localStorage
for (const key of Object.keys(mappingConfigs)) {// Iterate over the keys of the mapping configurations
this.scene.inputController.injectConfig(key, mappingConfigs[key]);
} // Inject each configuration into the input controller for the corresponding key
return true; // Return true to indicate the operation was successful
}
public resetMappingToFactory(): boolean {
if (!localStorage.hasOwnProperty("mappingConfigs")) {// Check if 'mappingConfigs' exists in localStorage
return false;
} // If 'mappingConfigs' does not exist, return false
localStorage.removeItem("mappingConfigs");
this.scene.inputController.resetConfigs();
}
/**
* Saves a gamepad setting to localStorage.
*
* @param setting - The gamepad setting to save.
* @param valueIndex - The index of the value to set for the gamepad setting.
* @returns `true` if the setting is successfully saved.
*
* @remarks
* This method initializes an empty object for gamepad settings if none exist in localStorage.
* It then updates the setting in the current scene and iterates over the default gamepad settings
* to update the specified setting with the new value. Finally, it saves the updated settings back
* to localStorage and returns `true` to indicate success.
*/
2024-06-03 19:57:47 -04:00
public saveControlSetting(device: Device, localStoragePropertyName: string, setting: SettingGamepad|SettingKeyboard, settingDefaults, valueIndex: integer): boolean {
let settingsControls: object = {}; // Initialize an empty object to hold the gamepad settings
2024-06-03 19:57:47 -04:00
if (localStorage.hasOwnProperty(localStoragePropertyName)) { // Check if 'settingsControls' exists in localStorage
settingsControls = JSON.parse(localStorage.getItem(localStoragePropertyName)); // Parse the existing 'settingsControls' from localStorage
}
2024-06-03 19:57:47 -04:00
if (device === Device.GAMEPAD) {
setSettingGamepad(this.scene, setting as SettingGamepad, valueIndex); // Set the gamepad setting in the current scene
} else if (device === Device.KEYBOARD) {
setSettingKeyboard(this.scene, setting as SettingKeyboard, valueIndex); // Set the keyboard setting in the current scene
}
2024-06-03 19:57:47 -04:00
Object.keys(settingDefaults).forEach(s => { // Iterate over the default gamepad settings
if (s === setting) {// If the current setting matches, update its value
2024-06-03 19:57:47 -04:00
settingsControls[s] = valueIndex;
}
});
2024-06-03 19:57:47 -04:00
localStorage.setItem(localStoragePropertyName, JSON.stringify(settingsControls)); // Save the updated gamepad settings back to localStorage
return true; // Return true to indicate the operation was successful
}
/**
2024-06-03 19:57:47 -04:00
* Loads Settings from local storage if available
* @returns true if succesful, false if not
*/
2023-10-26 16:33:59 -04:00
private loadSettings(): boolean {
2024-06-03 19:57:47 -04:00
resetSettings(this.scene);
2023-12-25 15:03:50 -05:00
if (!localStorage.hasOwnProperty("settings")) {
2023-10-26 16:33:59 -04:00
return false;
}
2023-10-26 16:33:59 -04:00
const settings = JSON.parse(localStorage.getItem("settings"));
2023-10-26 16:33:59 -04:00
for (const setting of Object.keys(settings)) {
2024-06-03 19:57:47 -04:00
setSetting(this.scene, setting, settings[setting]);
}
2023-10-26 16:33:59 -04:00
}
private loadGamepadSettings(): boolean {
Object.values(SettingGamepad).map(setting => setting as SettingGamepad).forEach(setting => setSettingGamepad(this.scene, setting, settingGamepadDefaults[setting]));
if (!localStorage.hasOwnProperty("settingsGamepad")) {
return false;
}
const settingsGamepad = JSON.parse(localStorage.getItem("settingsGamepad"));
for (const setting of Object.keys(settingsGamepad)) {
setSettingGamepad(this.scene, setting as SettingGamepad, settingsGamepad[setting]);
}
}
2024-02-13 18:42:11 -05:00
public saveTutorialFlag(tutorial: Tutorial, flag: boolean): boolean {
const key = getDataTypeKey(GameDataType.TUTORIALS);
2024-02-13 18:42:11 -05:00
let tutorials: object = {};
if (localStorage.hasOwnProperty(key)) {
tutorials = JSON.parse(localStorage.getItem(key));
}
2024-02-13 18:42:11 -05:00
Object.keys(Tutorial).map(t => t as Tutorial).forEach(t => {
const key = Tutorial[t];
if (key === tutorial) {
2024-02-13 18:42:11 -05:00
tutorials[key] = flag;
} else {
2024-02-13 18:42:11 -05:00
tutorials[key] ??= false;
}
2024-02-13 18:42:11 -05:00
});
localStorage.setItem(key, JSON.stringify(tutorials));
2024-02-13 18:42:11 -05:00
return true;
}
public getTutorialFlags(): TutorialFlags {
const key = getDataTypeKey(GameDataType.TUTORIALS);
2024-02-13 18:42:11 -05:00
const ret: TutorialFlags = {};
Object.values(Tutorial).map(tutorial => tutorial as Tutorial).forEach(tutorial => ret[Tutorial[tutorial]] = false);
if (!localStorage.hasOwnProperty(key)) {
2024-02-13 18:42:11 -05:00
return ret;
}
2024-02-13 18:42:11 -05:00
const tutorials = JSON.parse(localStorage.getItem(key));
2024-02-13 18:42:11 -05:00
for (const tutorial of Object.keys(tutorials)) {
2024-02-13 18:42:11 -05:00
ret[tutorial] = tutorials[tutorial];
}
2024-02-13 18:42:11 -05:00
return ret;
}
public saveSeenDialogue(dialogue: string): boolean {
const key = getDataTypeKey(GameDataType.SEEN_DIALOGUES);
const dialogues: object = this.getSeenDialogues();
dialogues[dialogue] = true;
localStorage.setItem(key, JSON.stringify(dialogues));
console.log("Dialogue saved as seen:", dialogue);
return true;
}
public getSeenDialogues(): SeenDialogues {
const key = getDataTypeKey(GameDataType.SEEN_DIALOGUES);
const ret: SeenDialogues = {};
if (!localStorage.hasOwnProperty(key)) {
return ret;
}
const dialogues = JSON.parse(localStorage.getItem(key));
for (const dialogue of Object.keys(dialogues)) {
ret[dialogue] = dialogues[dialogue];
}
return ret;
}
2024-03-17 11:36:19 -04:00
private getSessionSaveData(scene: BattleScene): SessionSaveData {
return {
seed: scene.seed,
playTime: scene.sessionPlayTime,
gameMode: scene.gameMode.modeId,
party: scene.getParty().map(p => new PokemonData(p)),
enemyParty: scene.getEnemyParty().map(p => new PokemonData(p)),
modifiers: scene.findModifiers(() => true).map(m => new PersistentModifierData(m, true)),
enemyModifiers: scene.findModifiers(() => true, false).map(m => new PersistentModifierData(m, false)),
arena: new ArenaData(scene.arena),
pokeballCounts: scene.pokeballCounts,
money: scene.money,
score: scene.score,
waveIndex: scene.currentBattle.waveIndex,
battleType: scene.currentBattle.battleType,
trainer: scene.currentBattle.battleType === BattleType.TRAINER ? new TrainerData(scene.currentBattle.trainer) : null,
2024-03-17 11:36:19 -04:00
gameVersion: scene.game.config.gameVersion,
Add Challenges (#1459) * Initial challenge framework * Add type localisation * Change how challenges are tracked Also fixes the difficulty total text * MVP Renames challenge types, temporarily hides difficulty, and implements challenge saving. * Attempt to fix one legal pokemon in a double battle * Make monotype ignore type changing effects * Make isOfType correctly detect normal types * Try to fix double battles again * Make challenge function more like classic * Add helper function for fainted or not allowed * Add framework for fresh start challenge and improve comments * Try to fix evolution issues * Make form changing items only usable from rewards screen * Update localisation * Additional localisation change * Add achievements for completing challenges * Fix initialisation bug with challenge achievements * Add support for gamemode specific fixed battles Also make monogen challenges face the e4 of their generation * Add better support for mobile in challenges * Localise illegal evolution/form change message * Update achievement names * Make alternate forms count for monogen * Update monotype achievement icons * Add more comments * Improve comments * Fix mid battle form changes * Reorder mode list * Remove currently unused localisation entry * Add type overrides for monotype challenges Meloetta always counts for psychic and castform always counts for normal * Change how form changes are handled Now attempts a switch at the start of each turn instead of immediately * Add start button to challenge select screen * Make starter select back out to challenge screen if using challenges * Fix daily runs * Update tests to new game mode logic
2024-06-08 15:07:23 +10:00
timestamp: new Date().getTime(),
challenges: scene.gameMode.challenges.map(c => new ChallengeData(c))
2024-03-17 11:36:19 -04:00
} as SessionSaveData;
}
getSession(slotId: integer): Promise<SessionSaveData> {
2023-04-28 15:03:42 -04:00
return new Promise(async (resolve, reject) => {
if (slotId < 0) {
return resolve(null);
}
2023-12-31 18:30:37 -05:00
const handleSessionData = async (sessionDataStr: string) => {
try {
const sessionData = this.parseSessionData(sessionDataStr);
resolve(sessionData);
} catch (err) {
reject(err);
return;
}
};
if (!bypassLogin && !localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}`)) {
Utils.apiFetch(`savedata/session/get?slot=${slotId}&clientSessionId=${clientSessionId}`, true)
.then(response => response.text())
.then(async response => {
if (!response.length || response[0] !== "{") {
console.error(response);
return resolve(null);
}
2023-04-28 15:03:42 -04:00
localStorage.setItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}`, encrypt(response, bypassLogin));
await handleSessionData(response);
});
} else {
const sessionData = localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}`);
if (sessionData) {
await handleSessionData(decrypt(sessionData, bypassLogin));
} else {
return resolve(null);
}
}
});
}
2024-03-21 13:12:05 -04:00
loadSession(scene: BattleScene, slotId: integer, sessionData?: SessionSaveData): Promise<boolean> {
return new Promise(async (resolve, reject) => {
try {
const initSessionFromData = async (sessionData: SessionSaveData) => {
2023-12-31 18:30:37 -05:00
console.debug(sessionData);
2023-04-28 15:03:42 -04:00
Add Challenges (#1459) * Initial challenge framework * Add type localisation * Change how challenges are tracked Also fixes the difficulty total text * MVP Renames challenge types, temporarily hides difficulty, and implements challenge saving. * Attempt to fix one legal pokemon in a double battle * Make monotype ignore type changing effects * Make isOfType correctly detect normal types * Try to fix double battles again * Make challenge function more like classic * Add helper function for fainted or not allowed * Add framework for fresh start challenge and improve comments * Try to fix evolution issues * Make form changing items only usable from rewards screen * Update localisation * Additional localisation change * Add achievements for completing challenges * Fix initialisation bug with challenge achievements * Add support for gamemode specific fixed battles Also make monogen challenges face the e4 of their generation * Add better support for mobile in challenges * Localise illegal evolution/form change message * Update achievement names * Make alternate forms count for monogen * Update monotype achievement icons * Add more comments * Improve comments * Fix mid battle form changes * Reorder mode list * Remove currently unused localisation entry * Add type overrides for monotype challenges Meloetta always counts for psychic and castform always counts for normal * Change how form changes are handled Now attempts a switch at the start of each turn instead of immediately * Add start button to challenge select screen * Make starter select back out to challenge screen if using challenges * Fix daily runs * Update tests to new game mode logic
2024-06-08 15:07:23 +10:00
scene.gameMode = getGameMode(sessionData.gameMode || GameModes.CLASSIC);
if (sessionData.challenges) {
scene.gameMode.challenges = sessionData.challenges.map(c => c.toChallenge());
}
scene.setSeed(sessionData.seed || scene.game.config.seed[0]);
2023-12-31 18:30:37 -05:00
scene.resetSeed();
2023-04-28 15:03:42 -04:00
console.log("Seed:", scene.seed);
2024-01-11 20:27:50 -05:00
scene.sessionPlayTime = sessionData.playTime || 0;
scene.lastSavePlayTime = 0;
2024-01-11 20:27:50 -05:00
2023-12-31 18:30:37 -05:00
const loadPokemonAssets: Promise<void>[] = [];
2023-12-31 18:30:37 -05:00
const party = scene.getParty();
party.splice(0, party.length);
2023-04-28 15:03:42 -04:00
for (const p of sessionData.party) {
2023-12-31 18:30:37 -05:00
const pokemon = p.toPokemon(scene) as PlayerPokemon;
pokemon.setVisible(false);
loadPokemonAssets.push(pokemon.loadAssets());
party.push(pokemon);
}
2023-04-28 15:03:42 -04:00
2023-12-31 18:30:37 -05:00
Object.keys(scene.pokeballCounts).forEach((key: string) => {
scene.pokeballCounts[key] = sessionData.pokeballCounts[key] || 0;
});
2024-05-13 04:40:53 -04:00
if (Overrides.POKEBALL_OVERRIDE.active) {
scene.pokeballCounts = Overrides.POKEBALL_OVERRIDE.pokeballs;
}
2023-04-28 15:03:42 -04:00
2023-12-31 18:30:37 -05:00
scene.money = sessionData.money || 0;
scene.updateMoneyText();
2023-04-28 15:03:42 -04:00
if (scene.money > this.gameStats.highestMoney) {
this.gameStats.highestMoney = scene.money;
}
2024-03-17 11:36:19 -04:00
scene.score = sessionData.score;
2024-03-19 00:03:41 -04:00
scene.updateScoreText();
2024-03-17 11:36:19 -04:00
scene.newArena(sessionData.arena.biome);
2023-12-31 18:30:37 -05:00
const battleType = sessionData.battleType || 0;
const trainerConfig = sessionData.trainer ? trainerConfigs[sessionData.trainer.trainerType] : null;
const battle = scene.newBattle(sessionData.waveIndex, battleType, sessionData.trainer, battleType === BattleType.TRAINER ? trainerConfig?.doubleOnly || sessionData.trainer?.variant === TrainerVariant.DOUBLE : sessionData.enemyParty.length > 1);
battle.enemyLevels = sessionData.enemyParty.map(p => p.level);
scene.arena.init();
2023-10-07 16:08:33 -04:00
2023-12-31 18:30:37 -05:00
sessionData.enemyParty.forEach((enemyData, e) => {
const enemyPokemon = enemyData.toPokemon(scene, battleType, e, sessionData.trainer?.variant === TrainerVariant.DOUBLE) as EnemyPokemon;
2023-12-31 18:30:37 -05:00
battle.enemyParty[e] = enemyPokemon;
if (battleType === BattleType.WILD) {
2023-12-31 18:30:37 -05:00
battle.seenEnemyPartyMemberIds.add(enemyPokemon.id);
}
2023-12-31 18:30:37 -05:00
loadPokemonAssets.push(enemyPokemon.loadAssets());
});
2023-12-31 18:30:37 -05:00
scene.arena.weather = sessionData.arena.weather;
scene.arena.eventTarget.dispatchEvent(new WeatherChangedEvent(null, scene.arena.weather?.weatherType, scene.arena.weather?.turnsLeft));
scene.arena.terrain = sessionData.arena.terrain;
scene.arena.eventTarget.dispatchEvent(new TerrainChangedEvent(null, scene.arena.terrain?.terrainType, scene.arena.terrain?.turnsLeft));
2023-12-31 18:30:37 -05:00
// TODO
//scene.arena.tags = sessionData.arena.tags;
2023-04-28 15:03:42 -04:00
const modifiersModule = await import("../modifier/modifier");
2023-04-28 15:03:42 -04:00
for (const modifierData of sessionData.modifiers) {
2023-12-31 18:30:37 -05:00
const modifier = modifierData.toModifier(scene, modifiersModule[modifierData.className]);
if (modifier) {
2023-12-31 18:30:37 -05:00
scene.addModifier(modifier, true);
}
2023-12-31 18:30:37 -05:00
}
2023-04-28 15:03:42 -04:00
2023-12-31 18:30:37 -05:00
scene.updateModifiers(true);
2023-04-28 15:03:42 -04:00
for (const enemyModifierData of sessionData.enemyModifiers) {
const modifier = enemyModifierData.toModifier(scene, modifiersModule[enemyModifierData.className]);
if (modifier) {
scene.addEnemyModifier(modifier, true);
}
}
2023-04-28 15:03:42 -04:00
scene.updateModifiers(false);
2023-12-31 18:30:37 -05:00
Promise.all(loadPokemonAssets).then(() => resolve(true));
2024-03-21 13:12:05 -04:00
};
if (sessionData) {
2024-03-21 13:12:05 -04:00
initSessionFromData(sessionData);
} else {
2024-03-21 13:12:05 -04:00
this.getSession(slotId)
.then(data => initSessionFromData(data))
.catch(err => {
reject(err);
return;
});
}
} catch (err) {
reject(err);
return;
}
2023-04-28 15:03:42 -04:00
});
}
2024-03-16 22:06:56 -04:00
deleteSession(slotId: integer): Promise<boolean> {
return new Promise<boolean>(resolve => {
if (bypassLogin) {
localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser.username}`);
return resolve(true);
}
updateUserInfo().then(success => {
if (success !== null && !success) {
return resolve(false);
}
Utils.apiFetch(`savedata/session/delete?slot=${slotId}&clientSessionId=${clientSessionId}`, true).then(response => {
if (response.ok) {
loggedInUser.lastSessionSlot = -1;
localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser.username}`);
resolve(true);
}
return response.text();
}).then(error => {
if (error) {
if (error.startsWith("session out of date")) {
this.scene.clearPhaseQueue();
this.scene.unshiftPhase(new ReloadSessionPhase(this.scene));
}
console.error(error);
resolve(false);
}
resolve(true);
});
});
});
2023-04-28 15:03:42 -04:00
}
/* Defines a localStorage item 'daily' to check on clears, offline implementation of savedata/newclear API
If a GameModes clear other than Daily is checked, newClear = true as usual
If a Daily mode is cleared, checks if it was already cleared before, based on seed, and returns true only to new daily clear runs */
offlineNewClear(scene: BattleScene): Promise<boolean> {
return new Promise<boolean>(resolve => {
const sessionData = this.getSessionSaveData(scene);
const seed = sessionData.seed;
let daily: string[] = [];
if (sessionData.gameMode === GameModes.DAILY) {
if (localStorage.hasOwnProperty("daily")) {
daily = JSON.parse(atob(localStorage.getItem("daily")));
if (daily.includes(seed)) {
return resolve(false);
} else {
daily.push(seed);
localStorage.setItem("daily", btoa(JSON.stringify(daily)));
return resolve(true);
}
} else {
daily.push(seed);
localStorage.setItem("daily", btoa(JSON.stringify(daily)));
return resolve(true);
}
} else {
return resolve(true);
}
});
}
2024-03-17 11:36:19 -04:00
tryClearSession(scene: BattleScene, slotId: integer): Promise<[success: boolean, newClear: boolean]> {
2024-03-16 22:06:56 -04:00
return new Promise<[boolean, boolean]>(resolve => {
if (bypassLogin) {
localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}`);
2024-03-16 22:06:56 -04:00
return resolve([true, true]);
}
updateUserInfo().then(success => {
if (success !== null && !success) {
2024-03-16 22:06:56 -04:00
return resolve([false, false]);
}
2024-03-17 11:36:19 -04:00
const sessionData = this.getSessionSaveData(scene);
Utils.apiPost(`savedata/session/clear?slot=${slotId}&trainerId=${this.trainerId}&secretId=${this.secretId}&clientSessionId=${clientSessionId}`, JSON.stringify(sessionData), undefined, true).then(response => {
if (response.ok) {
2024-03-16 22:06:56 -04:00
loggedInUser.lastSessionSlot = -1;
localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser.username}`);
}
return response.json();
}).then(jsonResponse => {
if (!jsonResponse.error) {
return resolve([true, jsonResponse.success as boolean]);
}
if (jsonResponse && jsonResponse.error.startsWith("session out of date")) {
this.scene.clearPhaseQueue();
this.scene.unshiftPhase(new ReloadSessionPhase(this.scene));
2024-03-16 22:06:56 -04:00
}
console.error(jsonResponse);
2024-03-16 22:06:56 -04:00
resolve([false, false]);
});
2024-03-16 22:06:56 -04:00
});
});
}
2023-12-26 14:49:23 -05:00
parseSessionData(dataStr: string): SessionSaveData {
return JSON.parse(dataStr, (k: string, v: any) => {
/*const versions = [ scene.game.config.gameVersion, sessionData.gameVersion || '0.0.0' ];
if (versions[0] !== versions[1]) {
const [ versionNumbers, oldVersionNumbers ] = versions.map(ver => ver.split('.').map(v => parseInt(v)));
}*/
if (k === "party" || k === "enemyParty") {
2023-12-26 14:49:23 -05:00
const ret: PokemonData[] = [];
if (v === null) {
2023-12-31 18:30:37 -05:00
v = [];
}
for (const pd of v) {
2023-12-26 14:49:23 -05:00
ret.push(new PokemonData(pd));
}
2023-12-26 14:49:23 -05:00
return ret;
}
if (k === "trainer") {
2023-12-26 14:49:23 -05:00
return v ? new TrainerData(v) : null;
}
2023-12-26 14:49:23 -05:00
if (k === "modifiers" || k === "enemyModifiers") {
const player = k === "modifiers";
2023-12-26 14:49:23 -05:00
const ret: PersistentModifierData[] = [];
if (v === null) {
2023-12-31 18:30:37 -05:00
v = [];
}
for (const md of v) {
if (md?.className === "ExpBalanceModifier") { // Temporarily limit EXP Balance until it gets reworked
md.stackCount = Math.min(md.stackCount, 4);
}
if (md instanceof EnemyAttackStatusEffectChanceModifier && md.effect === StatusEffect.FREEZE || md.effect === StatusEffect.SLEEP) {
continue;
}
2023-12-26 14:49:23 -05:00
ret.push(new PersistentModifierData(md, player));
}
2023-12-26 14:49:23 -05:00
return ret;
}
if (k === "arena") {
2023-12-26 14:49:23 -05:00
return new ArenaData(v);
}
2023-12-26 14:49:23 -05:00
Add Challenges (#1459) * Initial challenge framework * Add type localisation * Change how challenges are tracked Also fixes the difficulty total text * MVP Renames challenge types, temporarily hides difficulty, and implements challenge saving. * Attempt to fix one legal pokemon in a double battle * Make monotype ignore type changing effects * Make isOfType correctly detect normal types * Try to fix double battles again * Make challenge function more like classic * Add helper function for fainted or not allowed * Add framework for fresh start challenge and improve comments * Try to fix evolution issues * Make form changing items only usable from rewards screen * Update localisation * Additional localisation change * Add achievements for completing challenges * Fix initialisation bug with challenge achievements * Add support for gamemode specific fixed battles Also make monogen challenges face the e4 of their generation * Add better support for mobile in challenges * Localise illegal evolution/form change message * Update achievement names * Make alternate forms count for monogen * Update monotype achievement icons * Add more comments * Improve comments * Fix mid battle form changes * Reorder mode list * Remove currently unused localisation entry * Add type overrides for monotype challenges Meloetta always counts for psychic and castform always counts for normal * Change how form changes are handled Now attempts a switch at the start of each turn instead of immediately * Add start button to challenge select screen * Make starter select back out to challenge screen if using challenges * Fix daily runs * Update tests to new game mode logic
2024-06-08 15:07:23 +10:00
if (k === "challenges") {
const ret: ChallengeData[] = [];
if (v === null) {
v = [];
}
for (const c of v) {
ret.push(new ChallengeData(c));
}
return ret;
}
2023-12-26 14:49:23 -05:00
return v;
}) as SessionSaveData;
}
saveAll(scene: BattleScene, skipVerification: boolean = false, sync: boolean = false, useCachedSession: boolean = false, useCachedSystem: boolean = false): Promise<boolean> {
2024-05-12 23:01:05 -04:00
return new Promise<boolean>(resolve => {
Utils.executeIf(!skipVerification, updateUserInfo).then(success => {
if (success !== null && !success) {
2024-05-12 23:01:05 -04:00
return resolve(false);
}
if (sync) {
this.scene.ui.savingIcon.show();
}
const sessionData = useCachedSession ? this.parseSessionData(decrypt(localStorage.getItem(`sessionData${scene.sessionSlotId ? scene.sessionSlotId : ""}_${loggedInUser.username}`), bypassLogin)) : this.getSessionSaveData(scene);
2024-05-12 23:01:05 -04:00
const maxIntAttrValue = Math.pow(2, 31);
const systemData = useCachedSystem ? this.parseSystemData(decrypt(localStorage.getItem(`data_${loggedInUser.username}`), bypassLogin)) : this.getSystemSaveData();
2024-05-12 23:01:05 -04:00
const request = {
system: systemData,
session: sessionData,
sessionSlotId: scene.sessionSlotId,
clientSessionId: clientSessionId
2024-05-12 23:01:05 -04:00
};
localStorage.setItem(`data_${loggedInUser.username}`, encrypt(JSON.stringify(systemData, (k: any, v: any) => typeof v === "bigint" ? v <= maxIntAttrValue ? Number(v) : v.toString() : v), bypassLogin));
localStorage.setItem(`sessionData${scene.sessionSlotId ? scene.sessionSlotId : ""}_${loggedInUser.username}`, encrypt(JSON.stringify(sessionData), bypassLogin));
console.debug("Session data saved");
if (!bypassLogin && sync) {
Utils.apiPost("savedata/updateall", JSON.stringify(request, (k: any, v: any) => typeof v === "bigint" ? v <= maxIntAttrValue ? Number(v) : v.toString() : v), undefined, true)
2024-05-12 23:01:05 -04:00
.then(response => response.text())
.then(error => {
if (sync) {
this.scene.lastSavePlayTime = 0;
this.scene.ui.savingIcon.hide();
}
2024-05-12 23:01:05 -04:00
if (error) {
if (error.startsWith("client version out of date")) {
2024-05-12 23:01:05 -04:00
this.scene.clearPhaseQueue();
this.scene.unshiftPhase(new OutdatedPhase(this.scene));
} else if (error.startsWith("session out of date")) {
2024-05-12 23:01:05 -04:00
this.scene.clearPhaseQueue();
this.scene.unshiftPhase(new ReloadSessionPhase(this.scene));
}
console.error(error);
return resolve(false);
}
resolve(true);
});
} else {
this.verify().then(success => {
this.scene.ui.savingIcon.hide();
resolve(success);
});
2024-05-12 23:01:05 -04:00
}
});
});
}
public tryExportData(dataType: GameDataType, slotId: integer = 0): Promise<boolean> {
return new Promise<boolean>(resolve => {
const dataKey: string = `${getDataTypeKey(dataType, slotId)}_${loggedInUser.username}`;
const handleData = (dataStr: string) => {
switch (dataType) {
case GameDataType.SYSTEM:
dataStr = this.convertSystemDataStr(dataStr, true);
break;
}
const encryptedData = AES.encrypt(dataStr, saveKey);
const blob = new Blob([ encryptedData.toString() ], {type: "text/json"});
const link = document.createElement("a");
link.href = window.URL.createObjectURL(blob);
link.download = `${dataKey}.prsv`;
link.click();
link.remove();
};
if (!bypassLogin && dataType < GameDataType.SETTINGS) {
Utils.apiFetch(`savedata/${dataType === GameDataType.SYSTEM ? "system" : "session"}/get?clientSessionId=${clientSessionId}${dataType === GameDataType.SESSION ? `&slot=${slotId}` : ""}`, true)
.then(response => response.text())
.then(response => {
if (!response.length || response[0] !== "{") {
console.error(response);
resolve(false);
return;
}
2023-12-31 18:30:37 -05:00
handleData(response);
resolve(true);
});
} else {
const data = localStorage.getItem(dataKey);
if (data) {
handleData(decrypt(data, bypassLogin));
}
resolve(!!data);
}
});
2023-12-26 14:49:23 -05:00
}
public importData(dataType: GameDataType, slotId: integer = 0): void {
const dataKey = `${getDataTypeKey(dataType, slotId)}_${loggedInUser.username}`;
2023-12-26 14:49:23 -05:00
let saveFile: any = document.getElementById("saveFile");
if (saveFile) {
2023-12-26 14:49:23 -05:00
saveFile.remove();
}
2024-05-24 01:45:04 +02:00
saveFile = document.createElement("input");
saveFile.id = "saveFile";
saveFile.type = "file";
saveFile.accept = ".prsv";
saveFile.style.display = "none";
saveFile.addEventListener("change",
2023-12-26 14:49:23 -05:00
e => {
const reader = new FileReader();
2023-12-26 14:49:23 -05:00
reader.onload = (_ => {
return e => {
let dataName: string;
let dataStr = AES.decrypt(e.target.result.toString(), saveKey).toString(enc.Utf8);
let valid = false;
try {
dataName = GameDataType[dataType].toLowerCase();
2023-12-26 14:49:23 -05:00
switch (dataType) {
case GameDataType.SYSTEM:
dataStr = this.convertSystemDataStr(dataStr);
const systemData = this.parseSystemData(dataStr);
valid = !!systemData.dexData && !!systemData.timestamp;
break;
case GameDataType.SESSION:
const sessionData = this.parseSessionData(dataStr);
valid = !!sessionData.party && !!sessionData.enemyParty && !!sessionData.timestamp;
break;
case GameDataType.SETTINGS:
case GameDataType.TUTORIALS:
valid = true;
break;
2023-12-26 14:49:23 -05:00
}
} catch (ex) {
console.error(ex);
}
const displayError = (error: string) => this.scene.ui.showText(error, null, () => this.scene.ui.showText(null, 0), Utils.fixedInt(1500));
if (!valid) {
return this.scene.ui.showText(`Your ${dataName} data could not be loaded. It may be corrupted.`, null, () => this.scene.ui.showText(null, 0), Utils.fixedInt(1500));
}
this.scene.ui.showText(`Your ${dataName} data will be overridden and the page will reload. Proceed?`, null, () => {
this.scene.ui.setOverlayMode(Mode.CONFIRM, () => {
localStorage.setItem(dataKey, encrypt(dataStr, bypassLogin));
if (!bypassLogin && dataType < GameDataType.SETTINGS) {
updateUserInfo().then(success => {
if (!success) {
return displayError(`Could not contact the server. Your ${dataName} data could not be imported.`);
}
let url: string;
if (dataType === GameDataType.SESSION) {
url = `savedata/session/update?slot=${slotId}&trainerId=${this.trainerId}&secretId=${this.secretId}&clientSessionId=${clientSessionId}`;
} else {
url = `savedata/system/update?trainerId=${this.trainerId}&secretId=${this.secretId}&clientSessionId=${clientSessionId}`;
}
Utils.apiPost(url, dataStr, undefined, true)
.then(response => response.text())
.then(error => {
if (error) {
console.error(error);
return displayError(`An error occurred while updating ${dataName} data. Please contact the administrator.`);
}
window.location = window.location;
});
});
} else {
window.location = window.location;
}
}, () => {
this.scene.ui.revertMode();
this.scene.ui.showText(null, 0);
}, false, -98);
});
};
})((e.target as any).files[0]);
2023-12-26 14:49:23 -05:00
reader.readAsText((e.target as any).files[0]);
}
);
saveFile.click();
/*(this.scene.plugins.get('rexfilechooserplugin') as FileChooserPlugin).open({ accept: '.prsv' })
.then(result => {
});*/
}
2023-04-28 15:03:42 -04:00
private initDexData(): void {
const data: DexData = {};
for (const species of allSpecies) {
2023-11-12 23:47:04 -05:00
data[species.speciesId] = {
2024-01-05 22:24:05 -05:00
seenAttr: 0n, caughtAttr: 0n, natureAttr: 0, seenCount: 0, caughtCount: 0, hatchedCount: 0, ivs: [ 0, 0, 0, 0, 0, 0 ]
2023-11-12 23:47:04 -05:00
};
}
2024-04-18 22:52:26 -04:00
const defaultStarterAttr = DexAttr.NON_SHINY | DexAttr.MALE | DexAttr.DEFAULT_VARIANT | DexAttr.DEFAULT_FORM;
2023-11-12 23:47:04 -05:00
2024-01-05 22:24:05 -05:00
const defaultStarterNatures: Nature[] = [];
this.scene.executeWithSeedOffset(() => {
const neutralNatures = [ Nature.HARDY, Nature.DOCILE, Nature.SERIOUS, Nature.BASHFUL, Nature.QUIRKY ];
for (let s = 0; s < defaultStarterSpecies.length; s++) {
defaultStarterNatures.push(Utils.randSeedItem(neutralNatures));
}
}, 0, "default");
2024-01-05 22:24:05 -05:00
for (let ds = 0; ds < defaultStarterSpecies.length; ds++) {
const entry = data[defaultStarterSpecies[ds]] as DexEntry;
2023-11-12 23:47:04 -05:00
entry.seenAttr = defaultStarterAttr;
entry.caughtAttr = defaultStarterAttr;
2024-01-05 22:24:05 -05:00
entry.natureAttr = Math.pow(2, defaultStarterNatures[ds] + 1);
for (const i in entry.ivs) {
2023-11-12 23:47:04 -05:00
entry.ivs[i] = 10;
}
2023-04-18 01:32:26 -04:00
}
2024-01-05 22:24:05 -05:00
this.defaultDexData = Object.assign({}, data);
2023-04-18 01:32:26 -04:00
this.dexData = data;
}
private initStarterData(): void {
const starterData: StarterData = {};
2024-02-25 12:45:41 -05:00
const starterSpeciesIds = Object.keys(speciesStarters).map(k => parseInt(k) as Species);
for (const speciesId of starterSpeciesIds) {
starterData[speciesId] = {
moveset: null,
eggMoves: 0,
candyCount: 0,
friendship: 0,
2024-04-23 22:32:04 -04:00
abilityAttr: defaultStarterSpecies.includes(speciesId) ? AbilityAttr.ABILITY_1 : 0,
passiveAttr: 0,
valueReduction: 0,
classicWinCount: 0
};
}
this.starterData = starterData;
2024-02-25 12:45:41 -05:00
}
setPokemonSeen(pokemon: Pokemon, incrementCount: boolean = true, trainer: boolean = false): void {
2023-11-12 23:47:04 -05:00
const dexEntry = this.dexData[pokemon.species.speciesId];
dexEntry.seenAttr |= pokemon.getDexAttr();
if (incrementCount) {
2023-11-12 23:47:04 -05:00
dexEntry.seenCount++;
this.gameStats.pokemonSeen++;
if (!trainer && pokemon.species.subLegendary) {
2024-05-05 17:11:29 -04:00
this.gameStats.subLegendaryPokemonSeen++;
} else if (!trainer && pokemon.species.legendary) {
this.gameStats.legendaryPokemonSeen++;
} else if (!trainer && pokemon.species.mythical) {
this.gameStats.mythicalPokemonSeen++;
}
if (!trainer && pokemon.isShiny()) {
this.gameStats.shinyPokemonSeen++;
}
}
2023-04-18 01:32:26 -04:00
}
setPokemonCaught(pokemon: Pokemon, incrementCount: boolean = true, fromEgg: boolean = false): Promise<void> {
return this.setPokemonSpeciesCaught(pokemon, pokemon.species, incrementCount, fromEgg);
2023-07-05 14:19:49 -04:00
}
setPokemonSpeciesCaught(pokemon: Pokemon, species: PokemonSpecies, incrementCount: boolean = true, fromEgg: boolean = false): Promise<void> {
2024-02-25 12:45:41 -05:00
return new Promise<void>(resolve => {
2023-11-12 23:47:04 -05:00
const dexEntry = this.dexData[species.speciesId];
const caughtAttr = dexEntry.caughtAttr;
2024-04-05 22:58:40 -04:00
const formIndex = pokemon.formIndex;
if (noStarterFormKeys.includes(pokemon.getFormKey())) {
2024-04-05 22:58:40 -04:00
pokemon.formIndex = 0;
}
2024-04-05 22:58:40 -04:00
const dexAttr = pokemon.getDexAttr();
pokemon.formIndex = formIndex;
dexEntry.caughtAttr |= dexAttr;
if (speciesStarters.hasOwnProperty(species.speciesId)) {
this.starterData[species.speciesId].abilityAttr |= pokemon.abilityIndex !== 1 || pokemon.species.ability2
? Math.pow(2, pokemon.abilityIndex)
: AbilityAttr.ABILITY_HIDDEN;
}
2024-01-05 22:24:05 -05:00
dexEntry.natureAttr |= Math.pow(2, pokemon.nature + 1);
2024-05-24 01:45:04 +02:00
const hasPrevolution = pokemonPrevolutions.hasOwnProperty(species.speciesId);
const newCatch = !caughtAttr;
const hasNewAttr = (caughtAttr & dexAttr) !== dexAttr;
if (incrementCount) {
if (!fromEgg) {
dexEntry.caughtCount++;
this.gameStats.pokemonCaught++;
if (pokemon.species.subLegendary) {
2024-05-05 17:11:29 -04:00
this.gameStats.subLegendaryPokemonCaught++;
} else if (pokemon.species.legendary) {
this.gameStats.legendaryPokemonCaught++;
} else if (pokemon.species.mythical) {
this.gameStats.mythicalPokemonCaught++;
}
if (pokemon.isShiny()) {
this.gameStats.shinyPokemonCaught++;
}
} else {
dexEntry.hatchedCount++;
this.gameStats.pokemonHatched++;
if (pokemon.species.subLegendary) {
2024-05-05 17:11:29 -04:00
this.gameStats.subLegendaryPokemonHatched++;
} else if (pokemon.species.legendary) {
this.gameStats.legendaryPokemonHatched++;
} else if (pokemon.species.mythical) {
this.gameStats.mythicalPokemonHatched++;
}
if (pokemon.isShiny()) {
this.gameStats.shinyPokemonHatched++;
}
}
2023-04-18 01:32:26 -04:00
if (!hasPrevolution && (!pokemon.scene.gameMode.isDaily || hasNewAttr || fromEgg)) {
2024-04-19 01:01:57 -04:00
this.addStarterCandy(species, (1 * (pokemon.isShiny() ? 5 * Math.pow(2, pokemon.variant || 0) : 1)) * (fromEgg || pokemon.isBoss() ? 2 : 1));
}
}
2024-05-24 01:45:04 +02:00
const checkPrevolution = () => {
if (hasPrevolution) {
const prevolutionSpecies = pokemonPrevolutions[species.speciesId];
return this.setPokemonSpeciesCaught(pokemon, getPokemonSpecies(prevolutionSpecies), incrementCount, fromEgg).then(() => resolve());
} else {
resolve();
}
};
2023-04-18 01:32:26 -04:00
if (newCatch && speciesStarters.hasOwnProperty(species.speciesId)) {
this.scene.playSound("level_up_fanfare");
this.scene.ui.showText(`${species.name} has been\nadded as a starter!`, null, () => checkPrevolution(), null, true);
} else {
checkPrevolution();
}
2023-04-18 01:32:26 -04:00
});
}
incrementRibbonCount(species: PokemonSpecies, forStarter: boolean = false): integer {
const speciesIdToIncrement: Species = species.getRootSpeciesId(forStarter);
if (!this.starterData[speciesIdToIncrement].classicWinCount) {
this.starterData[speciesIdToIncrement].classicWinCount = 0;
}
2024-05-24 01:45:04 +02:00
if (!this.starterData[speciesIdToIncrement].classicWinCount) {
this.scene.gameData.gameStats.ribbonsOwned++;
}
const ribbonsInStats: integer = this.scene.gameData.gameStats.ribbonsOwned;
if (ribbonsInStats >= 100) {
this.scene.validateAchv(achvs._100_RIBBONS);
}
if (ribbonsInStats >= 75) {
this.scene.validateAchv(achvs._75_RIBBONS);
}
if (ribbonsInStats >= 50) {
this.scene.validateAchv(achvs._50_RIBBONS);
}
if (ribbonsInStats >= 25) {
this.scene.validateAchv(achvs._25_RIBBONS);
}
if (ribbonsInStats >= 10) {
this.scene.validateAchv(achvs._10_RIBBONS);
}
return ++this.starterData[speciesIdToIncrement].classicWinCount;
}
addStarterCandy(species: PokemonSpecies, count: integer): void {
this.scene.candyBar.showStarterSpeciesCandy(species.speciesId, count);
this.starterData[species.speciesId].candyCount += count;
}
2024-02-25 12:45:41 -05:00
setEggMoveUnlocked(species: PokemonSpecies, eggMoveIndex: integer): Promise<boolean> {
return new Promise<boolean>(resolve => {
const speciesId = species.speciesId;
if (!speciesEggMoves.hasOwnProperty(speciesId) || !speciesEggMoves[speciesId][eggMoveIndex]) {
resolve(false);
return;
}
if (!this.starterData[speciesId].eggMoves) {
this.starterData[speciesId].eggMoves = 0;
}
2024-02-25 12:45:41 -05:00
const value = Math.pow(2, eggMoveIndex);
if (this.starterData[speciesId].eggMoves & value) {
2024-02-25 12:45:41 -05:00
resolve(false);
return;
}
this.starterData[speciesId].eggMoves |= value;
2024-02-25 12:45:41 -05:00
this.scene.playSound("level_up_fanfare");
this.scene.ui.showText(`${eggMoveIndex === 3 ? "Rare " : ""}Egg Move unlocked: ${allMoves[speciesEggMoves[speciesId][eggMoveIndex]].name}`, null, () => resolve(true), null, true);
2024-02-25 12:45:41 -05:00
});
}
updateSpeciesDexIvs(speciesId: Species, ivs: integer[]): void {
let dexEntry: DexEntry;
do {
dexEntry = this.scene.gameData.dexData[speciesId];
const dexIvs = dexEntry.ivs;
for (let i = 0; i < dexIvs.length; i++) {
if (dexIvs[i] < ivs[i]) {
dexIvs[i] = ivs[i];
}
}
if (dexIvs.filter(iv => iv === 31).length === 6) {
this.scene.validateAchv(achvs.PERFECT_IVS);
}
} while (pokemonPrevolutions.hasOwnProperty(speciesId) && (speciesId = pokemonPrevolutions[speciesId]));
}
2024-04-05 22:58:40 -04:00
getSpeciesCount(dexEntryPredicate: (entry: DexEntry) => boolean): integer {
const dexKeys = Object.keys(this.dexData);
let speciesCount = 0;
for (const s of dexKeys) {
if (dexEntryPredicate(this.dexData[s])) {
2024-04-05 22:58:40 -04:00
speciesCount++;
}
2024-04-05 22:58:40 -04:00
}
return speciesCount;
}
getStarterCount(dexEntryPredicate: (entry: DexEntry) => boolean): integer {
const starterKeys = Object.keys(speciesStarters);
let starterCount = 0;
for (const s of starterKeys) {
2024-04-05 22:58:40 -04:00
const starterDexEntry = this.dexData[s];
if (dexEntryPredicate(starterDexEntry)) {
2024-04-05 22:58:40 -04:00
starterCount++;
}
2024-04-05 22:58:40 -04:00
}
return starterCount;
}
2024-04-18 22:52:26 -04:00
getSpeciesDefaultDexAttr(species: PokemonSpecies, forSeen: boolean = false, optimistic: boolean = false): bigint {
2023-11-12 23:47:04 -05:00
let ret = 0n;
const dexEntry = this.dexData[species.speciesId];
const attr = dexEntry.caughtAttr;
2024-04-18 22:52:26 -04:00
ret |= optimistic
? attr & DexAttr.SHINY ? DexAttr.SHINY : DexAttr.NON_SHINY
: attr & DexAttr.NON_SHINY || !(attr & DexAttr.SHINY) ? DexAttr.NON_SHINY : DexAttr.SHINY;
2023-11-12 23:47:04 -05:00
ret |= attr & DexAttr.MALE || !(attr & DexAttr.FEMALE) ? DexAttr.MALE : DexAttr.FEMALE;
2024-04-18 22:52:26 -04:00
ret |= optimistic
? attr & DexAttr.SHINY ? attr & DexAttr.VARIANT_3 ? DexAttr.VARIANT_3 : attr & DexAttr.VARIANT_2 ? DexAttr.VARIANT_2 : DexAttr.DEFAULT_VARIANT : DexAttr.DEFAULT_VARIANT
: attr & DexAttr.DEFAULT_VARIANT ? DexAttr.DEFAULT_VARIANT : attr & DexAttr.VARIANT_2 ? DexAttr.VARIANT_2 : attr & DexAttr.VARIANT_3 ? DexAttr.VARIANT_3 : DexAttr.DEFAULT_VARIANT;
2023-11-12 23:47:04 -05:00
ret |= this.getFormAttr(this.getFormIndex(attr));
return ret;
}
2023-11-12 23:47:04 -05:00
getSpeciesDexAttrProps(species: PokemonSpecies, dexAttr: bigint): DexAttrProps {
const shiny = !(dexAttr & DexAttr.NON_SHINY);
const female = !(dexAttr & DexAttr.MALE);
2024-04-18 22:52:26 -04:00
const variant = dexAttr & DexAttr.DEFAULT_VARIANT ? 0 : dexAttr & DexAttr.VARIANT_2 ? 1 : dexAttr & DexAttr.VARIANT_3 ? 2 : 0;
2023-11-12 23:47:04 -05:00
const formIndex = this.getFormIndex(dexAttr);
return {
shiny,
female,
2024-04-18 22:52:26 -04:00
variant,
2023-11-12 23:47:04 -05:00
formIndex
};
2023-11-12 23:47:04 -05:00
}
2024-04-18 22:52:26 -04:00
getStarterSpeciesDefaultAbilityIndex(species: PokemonSpecies): integer {
const abilityAttr = this.starterData[species.speciesId].abilityAttr;
return abilityAttr & AbilityAttr.ABILITY_1 ? 0 : !species.ability2 || abilityAttr & AbilityAttr.ABILITY_2 ? 1 : 2;
}
2024-01-05 22:24:05 -05:00
getSpeciesDefaultNature(species: PokemonSpecies): Nature {
const dexEntry = this.dexData[species.speciesId];
for (let n = 0; n < 25; n++) {
if (dexEntry.natureAttr & Math.pow(2, n + 1)) {
2024-01-05 22:24:05 -05:00
return n as Nature;
}
2024-01-05 22:24:05 -05:00
}
return 0 as Nature;
}
getSpeciesDefaultNatureAttr(species: PokemonSpecies): integer {
return Math.pow(2, this.getSpeciesDefaultNature(species));
}
getDexAttrLuck(dexAttr: bigint): integer {
return dexAttr & DexAttr.SHINY ? dexAttr & DexAttr.VARIANT_3 ? 3 : dexAttr & DexAttr.VARIANT_2 ? 2 : 1 : 0;
}
2024-01-05 22:24:05 -05:00
getNaturesForAttr(natureAttr: integer): Nature[] {
const ret: Nature[] = [];
2024-01-05 22:24:05 -05:00
for (let n = 0; n < 25; n++) {
if (natureAttr & Math.pow(2, n + 1)) {
2024-01-05 22:24:05 -05:00
ret.push(n);
}
2024-01-05 22:24:05 -05:00
}
return ret;
}
getSpeciesStarterValue(speciesId: Species): number {
const baseValue = speciesStarters[speciesId];
let value = baseValue;
const decrementValue = (value: number) => {
if (value > 1) {
value--;
} else {
value /= 2;
}
return value;
};
for (let v = 0; v < this.starterData[speciesId].valueReduction; v++) {
value = decrementValue(value);
}
return value;
}
2023-11-12 23:47:04 -05:00
getFormIndex(attr: bigint): integer {
if (!attr || attr < DexAttr.DEFAULT_FORM) {
2023-11-12 23:47:04 -05:00
return 0;
}
2023-11-12 23:47:04 -05:00
let f = 0;
while (!(attr & this.getFormAttr(f))) {
2023-11-12 23:47:04 -05:00
f++;
}
2023-11-12 23:47:04 -05:00
return f;
}
2023-11-12 23:47:04 -05:00
getFormAttr(formIndex: integer): bigint {
return BigInt(Math.pow(2, 7 + formIndex));
}
2024-05-24 01:45:04 +02:00
consolidateDexData(dexData: DexData): void {
for (const k of Object.keys(dexData)) {
const entry = dexData[k] as DexEntry;
if (!entry.hasOwnProperty("hatchedCount")) {
entry.hatchedCount = 0;
}
if (!entry.hasOwnProperty("natureAttr") || (entry.caughtAttr && !entry.natureAttr)) {
2024-01-05 22:24:05 -05:00
entry.natureAttr = this.defaultDexData[k].natureAttr || Math.pow(2, Utils.randInt(25, 1));
}
2023-04-26 12:50:21 -04:00
}
}
2024-04-18 22:52:26 -04:00
migrateStarterAbilities(systemData: SystemSaveData, initialStarterData?: StarterData): void {
2024-04-18 22:52:26 -04:00
const starterIds = Object.keys(this.starterData).map(s => parseInt(s) as Species);
const starterData = initialStarterData || systemData.starterData;
2024-04-18 22:52:26 -04:00
const dexData = systemData.dexData;
for (const s of starterIds) {
2024-04-18 22:52:26 -04:00
const dexAttr = dexData[s].caughtAttr;
starterData[s].abilityAttr = (dexAttr & DexAttr.DEFAULT_VARIANT ? AbilityAttr.ABILITY_1 : 0)
| (dexAttr & DexAttr.VARIANT_2 ? AbilityAttr.ABILITY_2 : 0)
| (dexAttr & DexAttr.VARIANT_3 ? AbilityAttr.ABILITY_HIDDEN : 0);
if (dexAttr) {
if (!(dexAttr & DexAttr.DEFAULT_VARIANT)) {
2024-04-18 22:52:26 -04:00
dexData[s].caughtAttr ^= DexAttr.DEFAULT_VARIANT;
}
if (dexAttr & DexAttr.VARIANT_2) {
2024-04-18 22:52:26 -04:00
dexData[s].caughtAttr ^= DexAttr.VARIANT_2;
}
if (dexAttr & DexAttr.VARIANT_3) {
2024-04-18 22:52:26 -04:00
dexData[s].caughtAttr ^= DexAttr.VARIANT_3;
}
2024-04-18 22:52:26 -04:00
}
}
}
fixVariantData(systemData: SystemSaveData): void {
const starterIds = Object.keys(this.starterData).map(s => parseInt(s) as Species);
const starterData = systemData.starterData;
const dexData = systemData.dexData;
if (starterIds.find(id => (dexData[id].caughtAttr & DexAttr.VARIANT_2 || dexData[id].caughtAttr & DexAttr.VARIANT_3) && !variantData[id])) {
for (const s of starterIds) {
const species = getPokemonSpecies(s);
if (variantData[s]) {
const tempCaughtAttr = dexData[s].caughtAttr;
let seenVariant2 = false;
let seenVariant3 = false;
const checkEvoSpecies = (es: Species) => {
seenVariant2 ||= !!(dexData[es].seenAttr & DexAttr.VARIANT_2);
seenVariant3 ||= !!(dexData[es].seenAttr & DexAttr.VARIANT_3);
if (pokemonEvolutions.hasOwnProperty(es)) {
for (const pe of pokemonEvolutions[es]) {
checkEvoSpecies(pe.speciesId);
}
}
};
checkEvoSpecies(s);
if (dexData[s].caughtAttr & DexAttr.VARIANT_2 && !seenVariant2) {
dexData[s].caughtAttr ^= DexAttr.VARIANT_2;
}
if (dexData[s].caughtAttr & DexAttr.VARIANT_3 && !seenVariant3) {
dexData[s].caughtAttr ^= DexAttr.VARIANT_3;
}
starterData[s].abilityAttr = (tempCaughtAttr & DexAttr.DEFAULT_VARIANT ? AbilityAttr.ABILITY_1 : 0)
| (tempCaughtAttr & DexAttr.VARIANT_2 && species.ability2 ? AbilityAttr.ABILITY_2 : 0)
| (tempCaughtAttr & DexAttr.VARIANT_3 && species.abilityHidden ? AbilityAttr.ABILITY_HIDDEN : 0);
} else {
const tempCaughtAttr = dexData[s].caughtAttr;
if (dexData[s].caughtAttr & DexAttr.VARIANT_2) {
dexData[s].caughtAttr ^= DexAttr.VARIANT_2;
}
if (dexData[s].caughtAttr & DexAttr.VARIANT_3) {
dexData[s].caughtAttr ^= DexAttr.VARIANT_3;
}
starterData[s].abilityAttr = (tempCaughtAttr & DexAttr.DEFAULT_VARIANT ? AbilityAttr.ABILITY_1 : 0)
| (tempCaughtAttr & DexAttr.VARIANT_2 && species.ability2 ? AbilityAttr.ABILITY_2 : 0)
| (tempCaughtAttr & DexAttr.VARIANT_3 && species.abilityHidden ? AbilityAttr.ABILITY_HIDDEN : 0);
}
}
}
}
2024-05-24 01:45:04 +02:00
fixStarterData(systemData: SystemSaveData): void {
for (const starterId of defaultStarterSpecies) {
systemData.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1;
}
}
2024-05-05 17:11:29 -04:00
fixLegendaryStats(systemData: SystemSaveData): void {
systemData.gameStats.subLegendaryPokemonSeen = 0;
systemData.gameStats.subLegendaryPokemonCaught = 0;
systemData.gameStats.subLegendaryPokemonHatched = 0;
allSpecies.filter(s => s.subLegendary).forEach(s => {
const dexEntry = systemData.dexData[s.speciesId];
systemData.gameStats.subLegendaryPokemonSeen += dexEntry.seenCount;
systemData.gameStats.legendaryPokemonSeen = Math.max(systemData.gameStats.legendaryPokemonSeen - dexEntry.seenCount, 0);
systemData.gameStats.subLegendaryPokemonCaught += dexEntry.caughtCount;
systemData.gameStats.legendaryPokemonCaught = Math.max(systemData.gameStats.legendaryPokemonCaught - dexEntry.caughtCount, 0);
systemData.gameStats.subLegendaryPokemonHatched += dexEntry.hatchedCount;
systemData.gameStats.legendaryPokemonHatched = Math.max(systemData.gameStats.legendaryPokemonHatched - dexEntry.hatchedCount, 0);
});
systemData.gameStats.subLegendaryPokemonSeen = Math.max(systemData.gameStats.subLegendaryPokemonSeen, systemData.gameStats.subLegendaryPokemonCaught);
systemData.gameStats.legendaryPokemonSeen = Math.max(systemData.gameStats.legendaryPokemonSeen, systemData.gameStats.legendaryPokemonCaught);
systemData.gameStats.mythicalPokemonSeen = Math.max(systemData.gameStats.mythicalPokemonSeen, systemData.gameStats.mythicalPokemonCaught);
}
}