2023-12-31 15:30:37 -08:00
import BattleScene , { PokeballCounts , bypassLogin } from "../battle-scene" ;
2023-04-28 12:03:42 -07:00
import Pokemon , { EnemyPokemon , PlayerPokemon } from "../pokemon" ;
2023-04-20 12:46:05 -07:00
import { pokemonPrevolutions } from "../data/pokemon-evolutions" ;
2023-12-01 14:23:26 -08:00
import PokemonSpecies , { allSpecies , getPokemonSpecies , speciesStarters } from "../data/pokemon-species" ;
2023-04-20 12:46:05 -07:00
import { Species } from "../data/species" ;
import * as Utils from "../utils" ;
2023-04-28 12:03:42 -07:00
import PokemonData from "./pokemon-data" ;
import PersistentModifierData from "./modifier-data" ;
import ArenaData from "./arena-data" ;
2023-04-28 22:40:24 -07:00
import { Unlockables } from "./unlockables" ;
2023-05-31 16:54:57 -07:00
import { GameMode } from "../game-mode" ;
2023-10-07 13:08:33 -07:00
import { BattleType } from "../battle" ;
import TrainerData from "./trainer-data" ;
import { trainerConfigs } from "../data/trainer-type" ;
2023-10-26 13:33:59 -07:00
import { Setting , setSetting , settingDefaults } from "./settings" ;
2023-11-11 21:31:40 -08:00
import { achvs } from "./achv" ;
2023-12-15 20:07:32 -08:00
import EggData from "./egg-data" ;
import { Egg } from "../data/egg" ;
2023-12-19 20:51:48 -08:00
import { VoucherType , vouchers } from "./voucher" ;
2023-12-26 11:49:23 -08:00
import { AES , enc } from "crypto-js" ;
import { Mode } from "../ui/ui" ;
2023-12-30 15:41:25 -08:00
import { updateUserInfo } from "../account" ;
2023-12-26 11:49:23 -08:00
const saveKey = 'x0i2O7WRiANTqPmZ' ; // Temporary; secure encryption is not yet necessary
export enum GameDataType {
SYSTEM ,
SESSION ,
SETTINGS
}
export function getDataTypeKey ( dataType : GameDataType ) : string {
switch ( dataType ) {
case GameDataType . SYSTEM :
return 'data' ;
case GameDataType . SESSION :
return 'sessionData' ;
case GameDataType . SETTINGS :
return 'settings' ;
}
}
2023-04-17 19:44:41 -07:00
2023-04-28 12:03:42 -07:00
interface SystemSaveData {
2023-04-17 22:32:26 -07:00
trainerId : integer ;
secretId : integer ;
dexData : DexData ;
2023-04-28 22:40:24 -07:00
unlocks : Unlocks ;
2023-11-11 21:31:40 -08:00
achvUnlocks : AchvUnlocks ;
2023-12-19 20:51:48 -08:00
voucherUnlocks : VoucherUnlocks ;
voucherCounts : VoucherCounts ;
2023-12-15 20:07:32 -08:00
eggs : EggData [ ] ;
2023-12-13 21:41:35 -08:00
gameVersion : string ;
2023-04-28 12:03:42 -07:00
timestamp : integer ;
}
interface SessionSaveData {
2023-10-18 15:01:15 -07:00
seed : string ;
2023-05-31 16:54:57 -07:00
gameMode : GameMode ;
2023-04-28 12:03:42 -07:00
party : PokemonData [ ] ;
2023-10-07 13:08:33 -07:00
enemyParty : PokemonData [ ] ;
2023-05-18 08:11:06 -07:00
enemyField : PokemonData [ ] ;
2023-04-28 12:03:42 -07:00
modifiers : PersistentModifierData [ ] ;
enemyModifiers : PersistentModifierData [ ] ;
arena : ArenaData ;
pokeballCounts : PokeballCounts ;
2023-11-10 12:51:34 -08:00
money : integer ;
2023-04-28 12:03:42 -07:00
waveIndex : integer ;
2023-10-07 13:08:33 -07:00
battleType : BattleType ;
trainer : TrainerData ;
2023-12-13 21:41:35 -08:00
gameVersion : string ;
2023-04-28 12:03:42 -07:00
timestamp : integer ;
2023-04-17 22:32:26 -07:00
}
2023-04-28 22:40:24 -07:00
interface Unlocks {
[ key : integer ] : boolean ;
}
2023-11-11 21:31:40 -08:00
interface AchvUnlocks {
[ key : string ] : integer
}
2023-12-19 20:51:48 -08:00
interface VoucherUnlocks {
[ key : string ] : integer
}
export interface VoucherCounts {
[ type : string ] : integer ;
}
2023-04-17 19:44:41 -07:00
export interface DexData {
2023-11-12 20:47:04 -08:00
[ key : integer ] : DexEntry
2023-04-17 19:44:41 -07:00
}
export interface DexEntry {
2023-11-12 20:47:04 -08:00
seenAttr : bigint ;
caughtAttr : bigint ;
seenCount : integer ;
caughtCount : integer ;
2023-12-31 10:20:28 -08:00
hatchedCount : integer ;
2023-11-12 20:47:04 -08:00
ivs : integer [ ] ;
2023-04-17 19:44:41 -07:00
}
2023-11-12 20:47:04 -08:00
export const DexAttr = {
NON_SHINY : 1n ,
SHINY : 2n ,
MALE : 4n ,
FEMALE : 8n ,
ABILITY_1 : 16n ,
ABILITY_2 : 32n ,
ABILITY_HIDDEN : 64n ,
DEFAULT_FORM : 128n
}
export interface DexAttrProps {
2023-04-17 19:44:41 -07:00
shiny : boolean ;
female : boolean ;
2023-04-26 09:50:21 -07:00
abilityIndex : integer ;
2023-11-12 20:47:04 -08:00
formIndex : integer ;
2023-04-17 19:44:41 -07:00
}
2023-12-31 10:39:04 -08:00
const systemShortKeys = {
seenAttr : '$sa' ,
caughtAttr : '$ca' ,
seenCount : '$s' ,
caughtCount : '$c' ,
ivs : '$i'
} ;
2023-04-17 22:32:26 -07:00
export class GameData {
2023-04-17 19:44:41 -07:00
private scene : BattleScene ;
public trainerId : integer ;
public secretId : integer ;
public dexData : DexData ;
2023-04-28 22:40:24 -07:00
public unlocks : Unlocks ;
2023-11-11 21:31:40 -08:00
public achvUnlocks : AchvUnlocks ;
2023-12-19 20:51:48 -08:00
public voucherUnlocks : VoucherUnlocks ;
public voucherCounts : VoucherCounts ;
2023-12-15 20:07:32 -08:00
public eggs : Egg [ ] ;
2023-04-17 19:44:41 -07:00
constructor ( scene : BattleScene ) {
this . scene = scene ;
2023-10-26 13:33:59 -07:00
this . loadSettings ( ) ;
this . trainerId = Utils . randSeedInt ( 65536 ) ;
this . secretId = Utils . randSeedInt ( 65536 ) ;
2023-04-28 22:40:24 -07:00
this . unlocks = {
2023-05-31 16:54:57 -07:00
[ Unlockables . ENDLESS_MODE ] : false ,
2023-11-04 16:46:48 -07:00
[ Unlockables . MINI_BLACK_HOLE ] : false ,
[ Unlockables . SPLICED_ENDLESS_MODE ] : false
2023-04-28 22:40:24 -07:00
} ;
2023-11-11 21:31:40 -08:00
this . achvUnlocks = { } ;
2023-12-19 20:51:48 -08:00
this . voucherUnlocks = { } ;
this . voucherCounts = {
[ VoucherType . REGULAR ] : 0 ,
[ VoucherType . PLUS ] : 0 ,
2023-12-20 14:22:50 -08:00
[ VoucherType . PREMIUM ] : 0 ,
[ VoucherType . GOLDEN ] : 0
2023-12-19 20:51:48 -08:00
} ;
2023-12-15 20:07:32 -08:00
this . eggs = [ ] ;
2023-04-26 13:07:29 -07:00
this . initDexData ( ) ;
2023-04-28 12:03:42 -07:00
this . loadSystem ( ) ;
2023-04-17 19:44:41 -07:00
}
2023-12-30 15:41:25 -08:00
public saveSystem ( ) : Promise < boolean > {
return new Promise < boolean > ( resolve = > {
if ( this . scene . quickStart )
return resolve ( true ) ;
updateUserInfo ( ) . then ( success = > {
if ( ! success )
return resolve ( false ) ;
const data : SystemSaveData = {
trainerId : this.trainerId ,
secretId : this.secretId ,
dexData : this.dexData ,
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 ( )
} ;
2023-12-31 15:30:37 -08:00
2023-12-30 15:41:25 -08:00
const maxIntAttrValue = Math . pow ( 2 , 31 ) ;
2023-12-31 15:30:37 -08:00
const systemData = JSON . stringify ( data , ( k : any , v : any ) = > typeof v === 'bigint' ? v <= maxIntAttrValue ? Number ( v ) : v . toString ( ) : v ) ;
if ( ! bypassLogin ) {
Utils . apiPost ( ` savedata/update?datatype= ${ GameDataType . SYSTEM } ` , systemData )
. then ( response = > response . text ( ) )
. then ( error = > {
if ( error ) {
console . error ( error ) ;
return resolve ( false ) ;
}
resolve ( true ) ;
} ) ;
} else {
localStorage . setItem ( 'data_bak' , localStorage . getItem ( 'data' ) ) ;
localStorage . setItem ( 'data' , btoa ( systemData ) ) ;
}
2023-12-30 15:41:25 -08:00
} ) ;
} ) ;
2023-04-17 19:44:41 -07:00
}
2023-12-31 15:30:37 -08:00
private loadSystem ( ) : Promise < boolean > {
return new Promise < boolean > ( resolve = > {
if ( bypassLogin && ! localStorage . hasOwnProperty ( 'data' ) )
return false ;
2023-04-17 22:32:26 -07:00
2023-12-31 15:30:37 -08:00
const handleSystemData = ( systemDataStr : string ) = > {
const systemData = this . parseSystemData ( systemDataStr ) ;
2023-11-12 20:47:04 -08:00
2023-12-31 15:30:37 -08:00
console . debug ( systemData ) ;
2023-04-17 22:32:26 -07:00
2023-12-31 15:30:37 -08:00
/ * c o n s t v e r s i o n s = [ t h i s . s c e n e . g a m e . c o n f i g . g a m e V e r s i o n , d a t a . g a m e V e r s i o n | | ' 0 . 0 . 0 ' ] ;
if ( versions [ 0 ] !== versions [ 1 ] ) {
const [ versionNumbers , oldVersionNumbers ] = versions . map ( ver = > ver . split ( '.' ) . map ( v = > parseInt ( v ) ) ) ;
} * /
2023-12-13 21:41:35 -08:00
2023-12-31 15:30:37 -08:00
this . trainerId = systemData . trainerId ;
this . secretId = systemData . secretId ;
2023-04-17 19:44:41 -07:00
2023-12-31 15:30:37 -08:00
if ( systemData . unlocks ) {
for ( let key of Object . keys ( systemData . unlocks ) ) {
if ( this . unlocks . hasOwnProperty ( key ) )
this . unlocks [ key ] = systemData . unlocks [ key ] ;
}
}
2023-12-19 20:51:48 -08:00
2023-12-31 15:30:37 -08:00
if ( systemData . achvUnlocks ) {
for ( let a of Object . keys ( systemData . achvUnlocks ) ) {
if ( achvs . hasOwnProperty ( a ) )
this . achvUnlocks [ a ] = systemData . achvUnlocks [ a ] ;
}
}
if ( systemData . voucherUnlocks ) {
for ( let v of Object . keys ( systemData . voucherUnlocks ) ) {
if ( vouchers . hasOwnProperty ( v ) )
this . voucherUnlocks [ v ] = systemData . voucherUnlocks [ v ] ;
}
}
2023-12-19 20:51:48 -08:00
2023-12-31 15:30:37 -08:00
if ( systemData . voucherCounts ) {
Utils . getEnumKeys ( VoucherType ) . forEach ( key = > {
const index = VoucherType [ key ] ;
this . voucherCounts [ index ] = systemData . voucherCounts [ index ] || 0 ;
} ) ;
}
2023-12-15 20:07:32 -08:00
2023-12-31 15:30:37 -08:00
this . eggs = systemData . eggs
? systemData . eggs . map ( e = > e . toEgg ( ) )
: [ ] ;
2023-04-26 13:07:29 -07:00
2023-12-31 15:30:37 -08:00
this . dexData = Object . assign ( this . dexData , systemData . dexData ) ;
this . consolidateDexData ( this . dexData ) ;
resolve ( true ) ;
}
if ( ! bypassLogin ) {
Utils . apiFetch ( ` savedata/get?datatype= ${ GameDataType . SYSTEM } ` )
. then ( response = > response . text ( ) )
. then ( response = > {
if ( ! response . length || response [ 0 ] !== '{' ) {
console . error ( response ) ;
return resolve ( false ) ;
}
handleSystemData ( response ) ;
} ) ;
} else
handleSystemData ( atob ( localStorage . getItem ( 'data' ) ) ) ;
} ) ;
2023-04-17 19:44:41 -07:00
}
2023-12-26 11:49:23 -08:00
private parseSystemData ( dataStr : string ) : SystemSaveData {
return JSON . parse ( dataStr , ( k : string , v : any ) = > {
if ( k === 'eggs' ) {
const ret : EggData [ ] = [ ] ;
for ( let e of v )
ret . push ( new EggData ( e ) ) ;
return ret ;
}
return k . endsWith ( 'Attr' ) ? BigInt ( v ) : v ;
} ) as SystemSaveData ;
}
2023-12-31 10:39:04 -08:00
private convertSystemDataStr ( dataStr : string , shorten : boolean = false ) : string {
const fromKeys = shorten ? Object . keys ( systemShortKeys ) : Object . values ( systemShortKeys ) ;
const toKeys = shorten ? Object . values ( systemShortKeys ) : Object . keys ( systemShortKeys ) ;
for ( let k in fromKeys )
dataStr = dataStr . replace ( new RegExp ( ` ${ fromKeys [ k ] . replace ( '$' , '\\$' ) } ` , 'g' ) , toKeys [ k ] ) ;
return dataStr ;
}
2023-10-26 13:33:59 -07:00
public saveSetting ( setting : Setting , valueIndex : integer ) : boolean {
let settings : object = { } ;
if ( localStorage . hasOwnProperty ( 'settings' ) )
settings = JSON . parse ( localStorage . getItem ( 'settings' ) ) ;
setSetting ( this . scene , setting as Setting , valueIndex ) ;
Object . keys ( settingDefaults ) . forEach ( s = > {
if ( s === setting )
settings [ s ] = valueIndex ;
} ) ;
localStorage . setItem ( 'settings' , JSON . stringify ( settings ) ) ;
return true ;
}
private loadSettings ( ) : boolean {
2023-12-25 12:03:50 -08:00
Object . values ( Setting ) . map ( setting = > setting as Setting ) . forEach ( setting = > setSetting ( this . scene , setting , settingDefaults [ setting ] ) ) ;
2023-10-26 13:33:59 -07:00
if ( ! localStorage . hasOwnProperty ( 'settings' ) )
return false ;
const settings = JSON . parse ( localStorage . getItem ( 'settings' ) ) ;
for ( let setting of Object . keys ( settings ) )
setSetting ( this . scene , setting as Setting , settings [ setting ] ) ;
}
2023-12-30 15:41:25 -08:00
saveSession ( scene : BattleScene , skipVerification? : boolean ) : Promise < boolean > {
return new Promise < boolean > ( resolve = > {
Utils . executeIf ( ! skipVerification , updateUserInfo ) . then ( success = > {
if ( success !== null && ! success )
return resolve ( false ) ;
const sessionData = {
seed : scene.seed ,
gameMode : scene.gameMode ,
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 ,
waveIndex : scene.currentBattle.waveIndex ,
battleType : scene.currentBattle.battleType ,
trainer : scene.currentBattle.battleType == BattleType . TRAINER ? new TrainerData ( scene . currentBattle . trainer ) : null ,
gameVersion : scene.game.config.gameVersion ,
timestamp : new Date ( ) . getTime ( )
} as SessionSaveData ;
2023-12-31 15:30:37 -08:00
console . log ( JSON . stringify ( sessionData ) ) ;
2023-12-30 15:41:25 -08:00
2023-12-31 15:30:37 -08:00
if ( ! bypassLogin ) {
Utils . apiPost ( ` savedata/update?datatype= ${ GameDataType . SESSION } ` , JSON . stringify ( sessionData ) )
. then ( response = > response . text ( ) )
. then ( error = > {
if ( error ) {
console . error ( error ) ;
return resolve ( false ) ;
}
console . debug ( 'Session data saved' ) ;
resolve ( true ) ;
} ) ;
} else {
localStorage . setItem ( 'sessionData' , btoa ( JSON . stringify ( sessionData ) ) ) ;
2023-12-30 15:41:25 -08:00
2023-12-31 15:30:37 -08:00
console . debug ( 'Session data saved' ) ;
resolve ( true ) ;
}
2023-12-30 15:41:25 -08:00
} ) ;
} ) ;
2023-04-28 12:03:42 -07:00
}
loadSession ( scene : BattleScene ) : Promise < boolean > {
return new Promise ( async ( resolve , reject ) = > {
2023-12-31 15:30:37 -08:00
const handleSessionData = async ( sessionDataStr : string ) = > {
try {
const sessionData = this . parseSessionData ( sessionDataStr ) ;
2023-04-28 12:03:42 -07:00
2023-12-31 15:30:37 -08:00
console . debug ( sessionData ) ;
2023-04-28 12:03:42 -07:00
2023-12-31 15:30:37 -08:00
scene . seed = sessionData . seed || scene . game . config . seed [ 0 ] ;
scene . resetSeed ( ) ;
2023-04-28 12:03:42 -07:00
2023-12-31 15:30:37 -08:00
scene . gameMode = sessionData . gameMode || GameMode . CLASSIC ;
2023-10-18 15:01:15 -07:00
2023-12-31 15:30:37 -08:00
const loadPokemonAssets : Promise < void > [ ] = [ ] ;
2023-05-31 16:54:57 -07:00
2023-12-31 15:30:37 -08:00
const party = scene . getParty ( ) ;
party . splice ( 0 , party . length ) ;
2023-04-28 12:03:42 -07:00
2023-12-31 15:30:37 -08:00
for ( let p of sessionData . party ) {
const pokemon = p . toPokemon ( scene ) as PlayerPokemon ;
pokemon . setVisible ( false ) ;
loadPokemonAssets . push ( pokemon . loadAssets ( ) ) ;
party . push ( pokemon ) ;
}
2023-04-28 12:03:42 -07:00
2023-12-31 15:30:37 -08:00
Object . keys ( scene . pokeballCounts ) . forEach ( ( key : string ) = > {
scene . pokeballCounts [ key ] = sessionData . pokeballCounts [ key ] || 0 ;
} ) ;
2023-04-28 12:03:42 -07:00
2023-12-31 15:30:37 -08:00
scene . money = sessionData . money || 0 ;
scene . updateMoneyText ( ) ;
2023-04-28 12:03:42 -07:00
2023-12-31 15:30:37 -08:00
const battleType = sessionData . battleType || 0 ;
const battle = scene . newBattle ( sessionData . waveIndex , battleType , sessionData . trainer , battleType === BattleType . TRAINER ? trainerConfigs [ sessionData . trainer . trainerType ] . isDouble : sessionData.enemyParty.length > 1 ) ;
2023-11-10 12:51:34 -08:00
2023-12-31 15:30:37 -08:00
scene . newArena ( sessionData . arena . biome , true ) ;
2023-10-07 13:08:33 -07:00
2023-12-31 15:30:37 -08:00
sessionData . enemyParty . forEach ( ( enemyData , e ) = > {
const enemyPokemon = enemyData . toPokemon ( scene , battleType ) as EnemyPokemon ;
battle . enemyParty [ e ] = enemyPokemon ;
if ( battleType === BattleType . WILD )
battle . seenEnemyPartyMemberIds . add ( enemyPokemon . id ) ;
2023-10-18 15:01:15 -07:00
2023-12-31 15:30:37 -08:00
loadPokemonAssets . push ( enemyPokemon . loadAssets ( ) ) ;
} ) ;
2023-05-18 08:11:06 -07:00
2023-12-31 15:30:37 -08:00
scene . arena . weather = sessionData . arena . weather ;
// TODO
//scene.arena.tags = sessionData.arena.tags;
2023-04-28 12:03:42 -07:00
2023-12-31 15:30:37 -08:00
const modifiersModule = await import ( '../modifier/modifier' ) ;
2023-04-28 12:03:42 -07:00
2023-12-31 15:30:37 -08:00
for ( let modifierData of sessionData . modifiers ) {
const modifier = modifierData . toModifier ( scene , modifiersModule [ modifierData . className ] ) ;
if ( modifier )
scene . addModifier ( modifier , true ) ;
}
2023-04-28 12:03:42 -07:00
2023-12-31 15:30:37 -08:00
scene . updateModifiers ( true ) ;
2023-04-28 12:03:42 -07:00
2023-12-31 15:30:37 -08:00
for ( let enemyModifierData of sessionData . enemyModifiers ) {
const modifier = enemyModifierData . toModifier ( scene , modifiersModule [ enemyModifierData . className ] ) ;
if ( modifier )
scene . addEnemyModifier ( modifier , true ) ;
}
2023-10-31 18:43:22 -07:00
2023-12-31 15:30:37 -08:00
scene . updateModifiers ( false ) ;
2023-04-28 12:03:42 -07:00
2023-12-31 15:30:37 -08:00
Promise . all ( loadPokemonAssets ) . then ( ( ) = > resolve ( true ) ) ;
} catch ( err ) {
reject ( err ) ;
return ;
}
} ;
if ( ! bypassLogin ) {
Utils . apiFetch ( ` savedata/get?datatype= ${ GameDataType . SESSION } ` )
. then ( response = > response . text ( ) )
. then ( async response = > {
if ( ! response . length || response [ 0 ] !== '{' ) {
console . error ( response ) ;
return resolve ( false ) ;
}
console . log ( JSON . parse ( response ) ) ;
await handleSessionData ( response ) ;
} ) ;
} else
await handleSessionData ( atob ( localStorage . getItem ( 'sessionData' ) ) ) ;
2023-04-28 12:03:42 -07:00
} ) ;
}
clearSession ( ) : void {
localStorage . removeItem ( 'sessionData' ) ;
}
2023-12-26 11:49:23 -08:00
parseSessionData ( dataStr : string ) : SessionSaveData {
return JSON . parse ( dataStr , ( k : string , v : any ) = > {
/ * c o n s t v e r s i o n s = [ s c e n e . g a m e . c o n f i g . g a m e V e r s i o n , s e s s i o n D a t a . g a m e V e r s i o n | | ' 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' || k === 'enemyField' ) {
const ret : PokemonData [ ] = [ ] ;
2023-12-31 15:30:37 -08:00
if ( v === null )
v = [ ] ;
2023-12-26 11:49:23 -08:00
for ( let pd of v )
ret . push ( new PokemonData ( pd ) ) ;
return ret ;
}
if ( k === 'trainer' )
return v ? new TrainerData ( v ) : null ;
if ( k === 'modifiers' || k === 'enemyModifiers' ) {
const player = k === 'modifiers' ;
const ret : PersistentModifierData [ ] = [ ] ;
2023-12-31 15:30:37 -08:00
if ( v === null )
v = [ ] ;
2023-12-26 11:49:23 -08:00
for ( let md of v )
ret . push ( new PersistentModifierData ( md , player ) ) ;
return ret ;
}
if ( k === 'arena' )
return new ArenaData ( v ) ;
return v ;
} ) as SessionSaveData ;
}
public exportData ( dataType : GameDataType ) : void {
const dataKey : string = getDataTypeKey ( dataType ) ;
2023-12-31 15:30:37 -08:00
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/get?datatype= ${ dataType } ` )
. then ( response = > response . text ( ) )
. then ( response = > {
if ( ! response . length || response [ 0 ] !== '{' ) {
console . error ( response ) ;
return ;
}
handleData ( response ) ;
} ) ;
} else
handleData ( atob ( localStorage . getItem ( dataKey ) ) ) ;
2023-12-26 11:49:23 -08:00
}
public importData ( dataType : GameDataType ) : void {
const dataKey = getDataTypeKey ( dataType ) ;
let saveFile : any = document . getElementById ( 'saveFile' ) ;
if ( saveFile )
saveFile . remove ( ) ;
saveFile = document . createElement ( 'input' ) ;
saveFile . id = 'saveFile' ;
saveFile . type = 'file' ;
saveFile . accept = '.prsv' ;
saveFile . style . display = 'none' ;
saveFile . addEventListener ( 'change' ,
e = > {
let reader = new FileReader ( ) ;
reader . onload = ( _ = > {
return e = > {
2023-12-31 10:39:04 -08:00
let dataStr = AES . decrypt ( e . target . result . toString ( ) , saveKey ) . toString ( enc . Utf8 ) ;
2023-12-26 11:49:23 -08:00
let valid = false ;
try {
switch ( dataType ) {
case GameDataType . SYSTEM :
2023-12-31 10:39:04 -08:00
dataStr = this . convertSystemDataStr ( dataStr ) ;
2023-12-26 11:49:23 -08:00
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 :
valid = true ;
break ;
}
} catch ( ex ) {
console . error ( ex ) ;
}
let dataName : string ;
switch ( dataType ) {
case GameDataType . SYSTEM :
dataName = 'save' ;
break ;
case GameDataType . SESSION :
dataName = 'session' ;
break ;
case GameDataType . SETTINGS :
dataName = 'settings' ;
break ;
}
2023-12-31 15:30:37 -08:00
const displayError = ( error : string ) = > this . scene . ui . showText ( error , null , ( ) = > this . scene . ui . showText ( null , 0 ) , Utils . fixedInt ( 1500 ) ) ;
2023-12-26 11:49:23 -08:00
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 , ( ) = > {
2023-12-31 15:30:37 -08:00
if ( ! bypassLogin && dataType !== GameDataType . SETTINGS ) {
updateUserInfo ( ) . then ( success = > {
if ( ! success )
return displayError ( ` Could not contact the server. Your ${ dataName } data could not be imported. ` ) ;
Utils . apiPost ( ` savedata/update?datatype= ${ dataType } ` , dataStr )
. 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 {
localStorage . setItem ( dataKey , btoa ( dataStr ) ) ;
window . location = window . location ;
}
2023-12-26 11:49:23 -08:00
} , ( ) = > {
this . scene . ui . revertMode ( ) ;
this . scene . ui . showText ( null , 0 ) ;
} , false , 98 ) ;
} ) ;
} ;
} ) ( ( e . target as any ) . files [ 0 ] ) ;
reader . readAsText ( ( e . target as any ) . files [ 0 ] ) ;
}
) ;
saveFile . click ( ) ;
/ * ( t h i s . s c e n e . p l u g i n s . g e t ( ' r e x f i l e c h o o s e r p l u g i n ' ) a s F i l e C h o o s e r P l u g i n ) . o p e n ( { a c c e p t : ' . p r s v ' } )
. then ( result = > {
} ) ; * /
}
2023-04-28 12:03:42 -07:00
private initDexData ( ) : void {
2023-04-17 19:44:41 -07:00
const data : DexData = { } ;
for ( let species of allSpecies ) {
2023-11-12 20:47:04 -08:00
data [ species . speciesId ] = {
2023-12-31 10:20:28 -08:00
seenAttr : 0n , caughtAttr : 0n , seenCount : 0 , caughtCount : 0 , hatchedCount : 0 , ivs : [ 0 , 0 , 0 , 0 , 0 , 0 ]
2023-11-12 20:47:04 -08:00
} ;
2023-04-17 19:44:41 -07:00
}
const defaultStarters : 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 ,
2023-12-08 13:29:03 -08:00
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-04-17 19:44:41 -07:00
] ;
2023-11-12 20:47:04 -08:00
const defaultStarterAttr = DexAttr . NON_SHINY | DexAttr . MALE | DexAttr . ABILITY_1 | DexAttr . DEFAULT_FORM ;
2023-04-17 19:44:41 -07:00
for ( let ds of defaultStarters ) {
2023-11-12 20:47:04 -08:00
let entry = data [ ds ] as DexEntry ;
entry . seenAttr = defaultStarterAttr ;
entry . caughtAttr = defaultStarterAttr ;
for ( let i in entry . ivs )
entry . ivs [ i ] = 10 ;
2023-04-17 22:32:26 -07:00
}
2023-04-17 19:44:41 -07:00
2023-04-17 22:32:26 -07:00
this . dexData = data ;
}
2023-11-12 20:47:04 -08:00
setPokemonSeen ( pokemon : Pokemon , incrementCount : boolean = true ) : void {
const dexEntry = this . dexData [ pokemon . species . speciesId ] ;
2023-11-13 05:20:31 -08:00
dexEntry . seenAttr |= pokemon . getDexAttr ( ) ;
2023-11-12 20:47:04 -08:00
if ( incrementCount )
dexEntry . seenCount ++ ;
2023-04-17 22:32:26 -07:00
}
2023-04-17 19:44:41 -07:00
2023-12-31 10:20:28 -08:00
setPokemonCaught ( pokemon : Pokemon , incrementCount : boolean = true , fromEgg : boolean = false ) : Promise < void > {
return this . setPokemonSpeciesCaught ( pokemon , pokemon . species , incrementCount , fromEgg ) ;
2023-07-05 11:19:49 -07:00
}
2023-12-31 10:20:28 -08:00
setPokemonSpeciesCaught ( pokemon : Pokemon , species : PokemonSpecies , incrementCount : boolean = true , fromEgg : boolean = false ) : Promise < void > {
2023-07-05 11:19:49 -07:00
return new Promise < void > ( ( resolve ) = > {
2023-11-12 20:47:04 -08:00
const dexEntry = this . dexData [ species . speciesId ] ;
const caughtAttr = dexEntry . caughtAttr ;
2023-11-13 05:20:31 -08:00
dexEntry . caughtAttr |= pokemon . getDexAttr ( ) ;
2023-12-31 10:20:28 -08:00
if ( incrementCount ) {
if ( ! fromEgg )
dexEntry . caughtCount ++ ;
else
dexEntry . hatchedCount ++ ;
}
2023-04-17 22:32:26 -07:00
2023-11-12 20:47:04 -08:00
const hasPrevolution = pokemonPrevolutions . hasOwnProperty ( species . speciesId ) ;
const newCatch = ! caughtAttr ;
2023-04-17 22:32:26 -07:00
2023-12-01 14:23:26 -08:00
const checkPrevolution = ( ) = > {
if ( hasPrevolution ) {
const prevolutionSpecies = pokemonPrevolutions [ species . speciesId ] ;
2023-12-31 10:20:28 -08:00
return this . setPokemonSpeciesCaught ( pokemon , getPokemonSpecies ( prevolutionSpecies ) , incrementCount , fromEgg ) . then ( ( ) = > resolve ( ) ) ;
2023-12-01 14:23:26 -08:00
} else
resolve ( ) ;
} ;
2023-04-17 22:32:26 -07:00
2023-12-01 14:23:26 -08:00
if ( newCatch && speciesStarters . hasOwnProperty ( species . speciesId ) ) {
this . scene . playSoundWithoutBgm ( 'level_up_fanfare' , 1500 ) ;
this . scene . ui . showText ( ` ${ species . name } has been \ nadded as a starter! ` , null , ( ) = > checkPrevolution ( ) , null , true ) ;
2023-07-05 11:19:49 -07:00
} else
2023-12-01 14:23:26 -08:00
checkPrevolution ( ) ;
2023-04-17 22:32:26 -07:00
} ) ;
}
2023-12-19 20:51:48 -08: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 ] ) ) ;
}
2023-11-12 20:47:04 -08:00
getSpeciesDefaultDexAttr ( species : PokemonSpecies ) : bigint {
let ret = 0 n ;
const dexEntry = this . dexData [ species . speciesId ] ;
const attr = dexEntry . caughtAttr ;
ret |= attr & DexAttr . NON_SHINY || ! ( attr & DexAttr . SHINY ) ? DexAttr.NON_SHINY : DexAttr.SHINY ;
ret |= attr & DexAttr . MALE || ! ( attr & DexAttr . FEMALE ) ? DexAttr.MALE : DexAttr.FEMALE ;
ret |= attr & DexAttr . ABILITY_1 || ( ! ( attr & DexAttr . ABILITY_2 ) && ! ( attr & DexAttr . ABILITY_HIDDEN ) ) ? DexAttr.ABILITY_1 : attr & DexAttr . ABILITY_2 ? DexAttr.ABILITY_2 : DexAttr.ABILITY_HIDDEN ;
ret |= this . getFormAttr ( this . getFormIndex ( attr ) ) ;
return ret ;
2023-04-17 19:44:41 -07:00
}
2023-11-12 20:47:04 -08:00
getSpeciesDexAttrProps ( species : PokemonSpecies , dexAttr : bigint ) : DexAttrProps {
const shiny = ! ( dexAttr & DexAttr . NON_SHINY ) ;
const female = ! ( dexAttr & DexAttr . MALE ) ;
const abilityIndex = dexAttr & DexAttr . ABILITY_1 ? 0 : ! species . ability2 || dexAttr & DexAttr . ABILITY_2 ? 1 : 2 ;
const formIndex = this . getFormIndex ( dexAttr ) ;
return {
shiny ,
female ,
abilityIndex ,
formIndex
2023-04-17 19:44:41 -07:00
} ;
2023-11-12 20:47:04 -08:00
}
2023-04-17 19:44:41 -07:00
2023-11-12 20:47:04 -08:00
getFormIndex ( attr : bigint ) : integer {
if ( ! attr || attr < DexAttr . DEFAULT_FORM )
return 0 ;
let f = 0 ;
while ( ! ( attr & this . getFormAttr ( f ) ) )
f ++ ;
return f ;
}
2023-04-17 19:44:41 -07:00
2023-11-12 20:47:04 -08:00
getFormAttr ( formIndex : integer ) : bigint {
return BigInt ( Math . pow ( 2 , 7 + formIndex ) ) ;
2023-04-17 19:44:41 -07:00
}
2023-12-31 10:20:28 -08:00
consolidateDexData ( dexData : DexData ) : void {
for ( let k of Object . keys ( dexData ) ) {
const entry = dexData [ k ] as DexEntry ;
if ( ! entry . hasOwnProperty ( 'hatchedCount' ) )
entry . hatchedCount = 0 ;
2023-04-26 09:50:21 -07:00
}
}
2023-04-17 19:44:41 -07:00
}