# pragma region License
/*
License ( OLC - 3 )
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
Copyright 2024 Joshua Sigona < sigonasr2 @ gmail . com >
Redistribution and use in source and binary forms , with or without modification ,
are permitted provided that the following conditions are met :
1. Redistributions or derivations of source code must retain the above copyright
notice , this list of conditions and the following disclaimer .
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice . This list of conditions and the following disclaimer must be
reproduced in the documentation and / or other materials provided with the distribution .
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission .
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " AS IS " AND ANY
EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED . IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT , INDIRECT ,
INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT LIMITED
TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR
BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN
CONTRACT , STRICT LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE .
Portions of this software are copyright © 2024 The FreeType
Project ( www . freetype . org ) . Please see LICENSE_FT . txt for more information .
All rights reserved .
*/
# pragma endregion
# include <filesystem>
# include "config.h"
# include "Item.h"
# include "AdventuresInLestoria.h"
# include "Unlock.h"
# include "State_OverworldMap.h"
# include "SaveFile.h"
# include "ClassInfo.h"
# include "ScrollableWindowComponent.h"
# include "LoadFileButton.h"
# include "Checkbox.h"
# include "InputDisplayComponent.h"
# include "GameSettings.h"
# include "Tutorial.h"
INCLUDE_game
size_t SaveFile : : saveFileID = 0 ;
std : : string SaveFile : : saveFileName = " " ;
std : : string SaveFile : : username = " " ;
bool SaveFile : : onlineMode = false ;
std : : function < void ( ) > SaveFile : : afterSaveGameDataUpdate ;
const size_t SaveFile : : GetSaveFileCount ( ) {
std : : filesystem : : create_directories ( " save_file_path " _S ) ;
size_t count = 0 ;
if ( std : : filesystem : : exists ( " save_file_path " _S + " metadata.dat " ) ) {
utils : : datafile metadata ;
utils : : datafile : : Read ( metadata , " save_file_path " _S + " metadata.dat " ) ;
return metadata . GetKeys ( ) . size ( ) ;
}
return count ;
}
const size_t SaveFile : : GetOnlineSaveFileCount ( ) {
std : : filesystem : : create_directories ( " save_file_path " _S ) ;
size_t count = 0 ;
if ( std : : filesystem : : exists ( " save_file_path " _S + " metadata.dat " + " _online " ) ) {
utils : : datafile metadata ;
utils : : datafile : : Read ( metadata , " save_file_path " _S + " metadata.dat " + " _online " ) ;
return metadata . GetKeys ( ) . size ( ) ;
}
return count ;
}
const void SaveFile : : SaveGame ( ) {
game - > saveGameDisplayTime = 6.f ;
std : : filesystem : : create_directories ( " save_file_path " _S ) ;
utils : : datafile saveFile ;
utils : : datafile saveSystemFile ;
utils : : datafile : : INITIAL_SETUP_COMPLETE = false ;
for ( size_t itemCount = 0 ; auto & item : Inventory : : GetInventory ( ) ) {
saveFile [ " Items " ] [ std : : format ( " Item[{}] " , itemCount ) ] [ " Amt " ] . SetInt ( item - > Amt ( ) ) ;
saveFile [ " Items " ] [ std : : format ( " Item[{}] " , itemCount ) ] [ " Enhancement Level " ] . SetInt ( item - > EnhancementLevel ( ) ) ;
saveFile [ " Items " ] [ std : : format ( " Item[{}] " , itemCount ) ] [ " Item Name " ] . SetString ( item - > ActualName ( ) ) ;
saveFile [ " Items " ] [ std : : format ( " Item[{}] " , itemCount ) ] [ " Equip Slot " ] . SetInt ( int ( Inventory : : GetSlotEquippedIn ( item ) ) ) ;
saveFile [ " Items " ] [ std : : format ( " Item[{}] " , itemCount ) ] [ " Locked " ] . SetBool ( item - > IsLocked ( ) ) ;
uint8_t loadoutSlotNumber = 255 ;
for ( int i = 0 ; i < game - > loadout . size ( ) ; i + + ) {
if ( item = = game - > GetLoadoutItem ( i ) ) { loadoutSlotNumber = i ; break ; }
}
saveFile [ " Items " ] [ std : : format ( " Item[{}] " , itemCount ) ] [ " LoadoutSlot " ] . SetInt ( loadoutSlotNumber ) ;
for ( const auto & [ attr , val ] : item - > RandomStats ( ) ) {
saveFile [ " Items " ] [ std : : format ( " Item[{}] " , itemCount ) ] [ " Attributes " ] [ std : : string ( attr . ActualName ( ) ) ] . SetReal ( val ) ;
}
itemCount + + ;
}
saveFile [ " Player " ] [ " Class " ] . SetString ( game - > GetPlayer ( ) - > GetClassName ( ) ) ;
saveFile [ " Player " ] [ " Level " ] . SetInt ( game - > GetPlayer ( ) - > Level ( ) ) ;
saveFile [ " Player " ] [ " Money " ] . SetInt ( game - > GetPlayer ( ) - > GetMoney ( ) ) ;
saveFile [ " Player " ] [ " Current EXP " ] . SetInt ( game - > GetPlayer ( ) - > CurrentXP ( ) ) ;
saveFile [ " Player " ] [ " Total EXP " ] . SetInt ( game - > GetPlayer ( ) - > TotalXP ( ) ) ;
for ( const auto & [ attr , val ] : game - > GetPlayer ( ) - > GetBaseStats ( ) ) {
saveFile [ " Player " ] [ " Base Stats " ] [ std : : string ( attr . ActualName ( ) ) ] . SetReal ( val ) ;
}
for ( const std : : string & unlockName : Unlock : : unlocks ) {
if ( unlockName = = " WORLD_MAP " ) continue ; //This is a special exception, because the world map is not an actual stage.
saveFile [ " Unlocks " ] [ unlockName ] . SetString ( " True " ) ;
auto opt_cp = State_OverworldMap : : ConnectionPointFromString ( unlockName ) ;
if ( ! opt_cp . has_value ( ) ) continue ; //Harmless, we probably just deleted the map.
if ( opt_cp . value ( ) - > Visited ( ) ) {
saveFile [ " Unlocks " ] [ unlockName ] . SetString ( " True " , 1U ) ;
} else {
saveFile [ " Unlocks " ] [ unlockName ] . SetString ( " False " , 1U ) ;
}
}
for ( auto & [ taskName , task ] : Tutorial : : taskList ) {
saveFile [ " Tutorial " ] [ std : : to_string ( int ( taskName ) ) ] . SetBool ( Tutorial : : TaskIsComplete ( taskName ) ) ;
}
saveFile [ " Overworld Map Location " ] . SetString ( State_OverworldMap : : GetCurrentConnectionPoint ( ) . name ) ;
saveFile [ " Chapter " ] . SetInt ( game - > GetCurrentChapter ( ) ) ;
saveFile [ " Save Name " ] . SetString ( std : : string ( GetSaveFileName ( ) ) ) ;
saveFile [ " Game Time " ] . SetReal ( game - > GetRuntime ( ) ) ;
saveFile [ " TravelingMerchant " ] . SetString ( std : : string ( Merchant : : GetCurrentTravelingMerchant ( ) . GetKeyName ( ) ) ) ;
saveFile [ " Achievement " ] [ " KillCount " ] . SetInt ( Unlock : : GetKillCount ( ) ) ;
# pragma region Save Keyboard / Controller mappings
//NOTE: We are shadowing code from InputKeyboardWindow! If at some point the retrival method for getting input displays changes, we likely will be changing the code here as well!
//ALSO NOTE: The menu inputs are saved to the system file while gameplay inputs are per-character and saved to the character settings file!
const int menuRowCount = DATA . GetProperty ( " Inputs.Menu Input Names " ) . GetValueCount ( ) % 2 = = 0 ? DATA . GetProperty ( " Inputs.Menu Input Names " ) . GetValueCount ( ) / 2 : DATA . GetProperty ( " Inputs.Menu Input Names " ) . GetValueCount ( ) / 2 + 1 ;
const int menuColCount = 2 ;
for ( int row = 0 ; row < menuRowCount ; row + + ) {
for ( int col = 0 ; col < menuColCount ; col + + ) {
int inputID = row * menuColCount + col ;
if ( DATA . GetProperty ( " Inputs.Menu Input Names " ) . GetValueCount ( ) % 2 = = 1 & & col = = 1 & & row = = menuRowCount - 1 ) continue ; //We only continue on a blank space when we have an odd number of elements.
saveSystemFile [ " Menu Keyboard Input_ " + " Inputs.Menu Input Names " _s [ inputID ] ] . SetInt ( Component < InputDisplayComponent > ( INPUT_KEY_DISPLAY , std : : format ( " Input_{}_{} Input Displayer " , row , col ) ) - > GetInput ( ) . GetPrimaryKey ( KEY ) . value ( ) . GetKeyCode ( ) ) ;
saveSystemFile [ " Menu Controller Input_ " + " Inputs.Menu Input Names " _s [ inputID ] ] . SetInt ( Component < InputDisplayComponent > ( INPUT_KEY_DISPLAY , std : : format ( " Input_{}_{} Input Displayer " , row , col ) ) - > GetInput ( ) . GetPrimaryKey ( CONTROLLER ) . value ( ) . GetKeyCode ( ) ) ;
}
}
const int ingameControlsRowCount = DATA . GetProperty ( " Inputs.Gameplay Input Names " ) . GetValueCount ( ) % 2 = = 0 ? DATA . GetProperty ( " Inputs.Gameplay Input Names " ) . GetValueCount ( ) / 2 : DATA . GetProperty ( " Inputs.Gameplay Input Names " ) . GetValueCount ( ) / 2 + 1 ;
const int ingameControlsColCount = 2 ;
for ( int row = 0 ; row < ingameControlsRowCount ; row + + ) {
for ( int col = 0 ; col < ingameControlsColCount ; col + + ) {
int inputID = row * menuColCount + col ;
if ( DATA . GetProperty ( " Inputs.Gameplay Input Names " ) . GetValueCount ( ) % 2 = = 1 & & col = = 1 & & row = = ingameControlsRowCount - 1 ) continue ; //We only continue on a blank space when we have an odd number of elements.
saveFile [ " Gameplay Keyboard Input_ " + " Inputs.Gameplay Input Names " _s [ inputID ] ] . SetInt ( Component < InputDisplayComponent > ( INPUT_KEY_DISPLAY , std : : format ( " Input_{}_{} Gameplay Input Displayer " , row , col ) ) - > GetInput ( ) . GetPrimaryKey ( KEY ) . value ( ) . GetKeyCode ( ) ) ;
saveFile [ " Gameplay Controller Input_ " + " Inputs.Gameplay Input Names " _s [ inputID ] ] . SetInt ( Component < InputDisplayComponent > ( INPUT_KEY_DISPLAY , std : : format ( " Input_{}_{} Gameplay Input Displayer " , row , col ) ) - > GetInput ( ) . GetPrimaryKey ( CONTROLLER ) . value ( ) . GetKeyCode ( ) ) ;
}
}
# pragma endregion
# pragma region Save System Settings
saveSystemFile [ " BGM Level " ] . SetReal ( Audio : : GetBGMVolume ( ) ) ;
saveSystemFile [ " SFX Level " ] . SetReal ( Audio : : GetSFXVolume ( ) ) ;
saveSystemFile [ " Show Max Health " ] . SetBool ( GameSettings : : ShowMaxHealth ( ) ) ;
saveSystemFile [ " Show Max Mana " ] . SetBool ( GameSettings : : ShowMaxMana ( ) ) ;
saveSystemFile [ " Screen Shake " ] . SetBool ( GameSettings : : ScreenShakeEnabled ( ) ) ;
saveSystemFile [ " Controller Rumble " ] . SetBool ( GameSettings : : RumbleEnabled ( ) ) ;
saveSystemFile [ " Terrain Collision Boxes " ] . SetBool ( GameSettings : : TerrainCollisionBoxesEnabled ( ) ) ;
saveSystemFile [ " Keyboard Auto-Aim " ] . SetBool ( GameSettings : : KeyboardAutoAimEnabled ( ) ) ;
saveSystemFile [ " Controller Icons " ] . SetInt ( int ( GameSettings : : GetIconType ( ) ) ) ;
saveSystemFile [ " VSync " ] . SetBool ( GameSettings : : VSyncEnabled ( ) ) ;
saveSystemFile [ " Window Pos " ] . SetInt ( game - > GetActualWindowPos ( ) . x , 0 ) ;
saveSystemFile [ " Window Pos " ] . SetInt ( game - > GetActualWindowPos ( ) . y , 1 ) ;
saveSystemFile [ " Window Size " ] . SetInt ( game - > GetWindowSize ( ) . x , 0 ) ;
saveSystemFile [ " Window Size " ] . SetInt ( game - > GetWindowSize ( ) . y , 1 ) ;
saveSystemFile [ " Fullscreen " ] . SetBool ( game - > IsFullscreen ( ) ) ;
# pragma endregion
saveFile [ " Hash " ] . SetString ( " " ) ;
utils : : datafile : : Write ( saveFile , " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ) ;
std : : string fileHash = util : : GetHash ( " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ) ;
saveFile [ " Hash " ] . SetString ( fileHash ) ;
utils : : datafile : : Write ( saveFile , " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ) ; //Once the hash has been computed and added, save the file a second time.
utils : : datafile : : Write ( saveSystemFile , " save_file_path " _S + " system.conf " ) ;
utils : : datafile metadata ;
if ( onlineMode ) {
if ( std : : filesystem : : exists ( " save_file_path " _S + " metadata.dat " + " _online " ) ) {
utils : : datafile : : Read ( metadata , " save_file_path " _S + " metadata.dat " + " _online " ) ;
}
} else {
if ( std : : filesystem : : exists ( " save_file_path " _S + " metadata.dat " ) ) {
utils : : datafile : : Read ( metadata , " save_file_path " _S + " metadata.dat " ) ;
}
}
metadata . GetProperty ( std : : format ( " save{} " , saveFileID ) ) . SetReal ( game - > GetRuntime ( ) , 0U ) ;
metadata . GetProperty ( std : : format ( " save{} " , saveFileID ) ) . SetInt ( game - > GetCurrentChapter ( ) , 1U ) ;
metadata . GetProperty ( std : : format ( " save{} " , saveFileID ) ) . SetInt ( game - > GetPlayer ( ) - > Level ( ) , 2U ) ;
metadata . GetProperty ( std : : format ( " save{} " , saveFileID ) ) . SetString ( game - > GetPlayer ( ) - > GetClassName ( ) , 3U ) ;
metadata . GetProperty ( std : : format ( " save{} " , saveFileID ) ) . SetString ( std : : string ( SaveFile : : GetSaveFileName ( ) ) , 4U ) ;
if ( ! onlineMode ) {
utils : : datafile : : Write ( metadata , " save_file_path " _S + " metadata.dat " ) ;
} else {
utils : : datafile : : Write ( metadata , " save_file_path " _S + " metadata.dat " + " _online " ) ;
}
utils : : datafile : : INITIAL_SETUP_COMPLETE = true ;
# ifdef __EMSCRIPTEN__
if ( onlineMode ) {
std : : function < void ( std : : string_view response ) > RetryResponse ;
RetryResponse = [ & ] ( std : : string_view response ) {
if ( response ! = " ERR " ) {
Server_SaveFile ( [ ] ( std : : string_view response ) {
if ( response = = " ERR " ) {
LOG ( " WARNING! Could not save data to server! " ) ;
}
} ) ;
} else {
LOG ( " WARNING! Could not save metadata to server! " ) ;
}
} ;
Server_SaveMetadataFile ( RetryResponse ) ;
} else {
std : : stringstream fileContents ;
std : : ifstream file ( " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ) ;
while ( file . good ( ) ) {
int val = file . get ( ) ;
if ( val ! = - 1 ) {
fileContents < < char ( val ) ;
}
}
std : : string contents = fileContents . str ( ) ;
emscripten_idb_async_store ( " /assets " , ( " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ) . c_str ( ) , contents . data ( ) , contents . length ( ) , 0 , [ ] ( void * arg ) {
LOG ( " Successfully saved save file " < < saveFileID < < " ! " ) ;
} , [ ] ( void * arg ) {
LOG ( " Failed to save save file " < < saveFileID < < " ! " ) ;
} ) ;
file . close ( ) ;
std : : stringstream metafileContents ;
std : : ifstream metafile ( " save_file_path " _S + " metadata.dat " ) ;
while ( metafile . good ( ) ) {
int val = metafile . get ( ) ;
if ( val ! = - 1 ) {
metafileContents < < char ( val ) ;
}
}
std : : string metaContents = metafileContents . str ( ) ;
emscripten_idb_async_store ( " /assets " , ( " save_file_path " _S + " metadata.dat " ) . c_str ( ) , metaContents . data ( ) , metaContents . length ( ) , 0 , [ ] ( void * arg ) {
LOG ( " Successfully saved metafile! " ) ;
} , [ ] ( void * arg ) {
LOG ( " Failed to save save metafile! " ) ;
} ) ;
metafile . close ( ) ;
}
std : : stringstream systemFileContents ;
std : : ifstream systemfile ( " save_file_path " _S + " system.conf " ) ;
while ( systemfile . good ( ) ) {
int val = systemfile . get ( ) ;
if ( val ! = - 1 ) {
systemFileContents < < char ( val ) ;
}
}
std : : string systemContents = systemFileContents . str ( ) ;
emscripten_idb_async_store ( " /assets " , ( " save_file_path " _S + " system.conf " ) . c_str ( ) , systemContents . data ( ) , systemContents . length ( ) , 0 , [ ] ( void * arg ) {
LOG ( " Successfully saved system file! " ) ;
} , [ ] ( void * arg ) {
LOG ( " Failed to save system file! " ) ;
} ) ;
systemfile . close ( ) ;
# endif
}
void SaveFile : : LoadFile ( ) {
utils : : datafile loadFile ;
std : : string loadFilename = " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ;
if ( std : : filesystem : : exists ( loadFilename ) ) {
utils : : datafile : : Read ( loadFile , " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ) ;
if ( ! loadFile . HasProperty ( " Hash " ) ) {
LOG ( std : : format ( " WARNING! Filehash for file {} does not exist! " , " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ) ) ;
return ;
}
if ( loadFile . HasProperty ( " Hash " ) ) {
std : : string expectedFileHash = loadFile [ " Hash " ] . GetString ( ) ;
loadFile [ " Hash " ] . SetString ( " " ) ;
utils : : datafile : : Write ( loadFile , " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ) ;
std : : string fileHash = util : : GetHash ( " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ) ;
if ( expectedFileHash ! = fileHash ) {
LOG ( std : : format ( " WARNING! Filehash for file {} was not identified as proper! Will not load this file! " , " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ) ) ;
return ;
}
loadFile [ " Hash " ] . SetString ( expectedFileHash ) ; //Now write the hash back into the file since we tampered with it.
utils : : datafile : : Write ( loadFile , " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ) ;
}
game - > ResetGame ( ) ;
for ( auto & [ key , data ] : loadFile [ " Items " ] . GetOrderedKeys ( ) ) {
std : : weak_ptr < Item > newItem = Inventory : : AddItem ( data [ " Item Name " ] . GetString ( ) , data [ " Amt " ] . GetInt ( ) ) ;
newItem . lock ( ) - > enhancementLevel = data [ " Enhancement Level " ] . GetInt ( ) ;
if ( loadFile . GetProperty ( std : : format ( " Items.{} " , key ) ) . HasProperty ( " Attributes " ) ) {
for ( auto & [ attr , data ] : loadFile . GetProperty ( std : : format ( " Items.{}.Attributes " , key ) ) . GetOrderedKeys ( ) ) {
newItem . lock ( ) - > randomizedStats . A ( attr ) = data . GetReal ( ) ;
}
}
if ( data . HasProperty ( " LoadoutSlot " ) ) {
uint8_t loadoutSlot = data [ " LoadoutSlot " ] . GetInt ( ) ;
if ( loadoutSlot ! = 255 ) {
game - > SetLoadoutItem ( loadoutSlot , newItem . lock ( ) - > ActualName ( ) ) ;
}
}
EquipSlot slot = EquipSlot ( loadFile . GetProperty ( std : : format ( " Items.{}.Equip Slot " , key ) ) . GetInt ( ) ) ;
if ( slot ! = EquipSlot : : NONE ) { //This should be equipped somewhere!
Inventory : : EquipItem ( newItem , slot ) ;
}
if ( loadFile . GetProperty ( std : : format ( " Items.{} " , key ) ) . HasProperty ( " Locked " ) ) {
bool locked = loadFile . GetProperty ( std : : format ( " Items.{} " , key ) ) [ " Locked " ] . GetBool ( ) ;
if ( locked ) {
newItem . lock ( ) - > Lock ( ) ;
}
}
}
game - > ChangePlayerClass ( classutils : : StringToClass ( loadFile [ " Player " ] [ " Class " ] . GetString ( ) ) ) ;
game - > GetPlayer ( ) - > SetLevel ( loadFile [ " Player " ] [ " Level " ] . GetInt ( ) ) ;
game - > GetPlayer ( ) - > SetMoney ( loadFile [ " Player " ] [ " Money " ] . GetInt ( ) ) ;
game - > GetPlayer ( ) - > SetXP ( loadFile [ " Player " ] [ " Current EXP " ] . GetInt ( ) ) ;
game - > GetPlayer ( ) - > SetTotalXPEarned ( loadFile [ " Player " ] [ " Total EXP " ] . GetInt ( ) ) ;
for ( auto & [ key , data ] : loadFile [ " Player " ] [ " Base Stats " ] . GetOrderedKeys ( ) ) {
game - > GetPlayer ( ) - > SetBaseStat ( key , data . GetReal ( ) ) ;
}
if ( loadFile . HasProperty ( " Unlocks " ) ) {
for ( const auto & [ key , data ] : loadFile [ " Unlocks " ] . GetOrderedKeys ( ) ) {
Unlock : : UnlockArea ( key ) ;
if ( data . GetValueCount ( ) > 1 & & data . GetBool ( 1U ) ) {
auto opt_cp = State_OverworldMap : : ConnectionPointFromString ( key ) ;
if ( ! opt_cp ) continue ; //Harmless, it just means the connection point used to exist and doesn't anymore.
else opt_cp . value ( ) - > SetVisited ( ) ;
}
}
}
if ( State_OverworldMap : : HasStageMarker ( loadFile [ " Overworld Map Location " ] . GetString ( ) ) ) {
State_OverworldMap : : SetStageMarker ( loadFile [ " Overworld Map Location " ] . GetString ( ) ) ;
}
State_OverworldMap : : UpdateCurrentConnectionPoint ( const_cast < ConnectionPoint & > ( State_OverworldMap : : GetCurrentConnectionPoint ( ) ) ) ;
game - > SetChapter ( loadFile [ " Chapter " ] . GetInt ( ) ) ;
SaveFile : : SetSaveFileName ( loadFile [ " Save Name " ] . GetString ( ) ) ;
game - > SetRuntime ( loadFile [ " Game Time " ] . GetReal ( ) ) ;
game - > GetPlayer ( ) - > RecalculateEquipStats ( ) ;
if ( loadFile . HasProperty ( " TravelingMerchant " ) ) Merchant : : SetTravelingMerchant ( loadFile [ " TravelingMerchant " ] . GetString ( ) ) ;
# pragma region Load Keyboard / Controller mappings
//NOTE: We are shadowing code from InputKeyboardWindow! If at some point the retrival method for getting input displays changes, we likely will be changing the code here as well!
const int ingameControlsRowCount = DATA . GetProperty ( " Inputs.Gameplay Input Names " ) . GetValueCount ( ) % 2 = = 0 ? DATA . GetProperty ( " Inputs.Gameplay Input Names " ) . GetValueCount ( ) / 2 : DATA . GetProperty ( " Inputs.Gameplay Input Names " ) . GetValueCount ( ) / 2 + 1 ;
const int ingameControlsColCount = 2 ;
for ( int row = 0 ; row < ingameControlsRowCount ; row + + ) {
for ( int col = 0 ; col < ingameControlsColCount ; col + + ) {
int inputID = row * ingameControlsColCount + col ;
if ( DATA . GetProperty ( " Inputs.Gameplay Input Names " ) . GetValueCount ( ) % 2 = = 1 & & col = = 1 & & row = = ingameControlsRowCount - 1 ) continue ; //We only continue on a blank space when we have an odd number of elements.
std : : string keyName = " Gameplay Keyboard Input_ " + " Inputs.Gameplay Input Names " _s [ inputID ] ;
if ( loadFile . HasProperty ( keyName ) ) {
InputGroup & group = Component < InputDisplayComponent > ( INPUT_KEY_DISPLAY , std : : format ( " Input_{}_{} Gameplay Input Displayer " , row , col ) ) - > GetInput ( ) ;
group . SetNewPrimaryKeybind ( { KEY , loadFile [ keyName ] . GetInt ( ) } ) ;
}
keyName = " Gameplay Controller Input_ " + " Inputs.Gameplay Input Names " _s [ inputID ] ;
if ( loadFile . HasProperty ( keyName ) ) {
InputGroup & group = Component < InputDisplayComponent > ( INPUT_KEY_DISPLAY , std : : format ( " Input_{}_{} Gameplay Input Displayer " , row , col ) ) - > GetInput ( ) ;
group . SetNewPrimaryKeybind ( { CONTROLLER , loadFile [ keyName ] . GetInt ( ) } ) ;
}
}
}
# pragma endregion
if ( loadFile . HasProperty ( " Tutorial " ) ) {
for ( auto & [ key , size ] : loadFile [ " Tutorial " ] . GetKeys ( ) ) {
if ( loadFile [ " Tutorial " ] [ key ] . GetBool ( ) ) {
Tutorial : : CompleteTask ( TutorialTaskName ( stoi ( key ) ) ) ;
}
}
}
# pragma region Achievement-related loading
if ( loadFile . HasProperty ( " Achievement.KillCount " ) ) {
Unlock : : SetKillCount ( loadFile . GetProperty ( " Achievement.KillCount " ) . GetInt ( ) ) ;
}
# pragma endregion
GameState : : ChangeState ( States : : OVERWORLD_MAP , 0.5f ) ;
} else {
LOG ( std : : format ( " WARNING! File {} does not exist for loading! " , " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ) ) ;
}
} ;
const void SaveFile : : LoadGame ( ) {
std : : filesystem : : create_directories ( " save_file_path " _S ) ;
# ifdef __EMSCRIPTEN__
if ( onlineMode ) {
Server_GetFile ( [ & ] ( std : : string_view response ) {
if ( response ! = " ERR " ) {
std : : ofstream file ( " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ) ;
file < < response ;
file . close ( ) ;
LoadFile ( ) ;
} else {
LOG ( " WARNING! Could not load save file! " ) ;
}
} ) ;
} else {
emscripten_idb_async_load ( " /assets " , ( " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ) . c_str ( ) , 0 , [ ] ( void * arg , void * data , int length ) {
LOG ( " Loaded Save File " < < saveFileID < < " successfully! " ) ;
std : : string rawMetadata = ( char * ) data ;
std : : ofstream file ( " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ) ;
for ( int i = 0 ; i < length ; i + + ) {
file < < rawMetadata [ i ] ;
}
file . close ( ) ;
LoadFile ( ) ;
} , [ ] ( void * arg ) {
LOG ( " Failed to load Save File " < < saveFileID < < " ! " ) ;
} ) ;
}
# else
LoadFile ( ) ;
# endif
}
const std : : string_view SaveFile : : GetSaveFileName ( ) {
return saveFileName ;
}
const void SaveFile : : SetSaveFileName ( std : : string_view saveFileName ) {
SaveFile : : saveFileName = saveFileName ;
}
const void SaveFile : : SetSaveFileID ( size_t saveFileID ) {
SaveFile : : saveFileID = saveFileID ;
}
const void SaveFile : : SetSaveFileOfflineID_TransitionToOverworldMap ( ) {
# ifdef __EMSCRIPTEN__
emscripten_idb_async_load ( " /assets " , ( " save_file_path " _S + " metadata.dat " ) . c_str ( ) , 0 , [ ] ( void * arg , void * data , int length ) {
std : : string rawMetadata = ( char * ) data ;
std : : ofstream file ( " save_file_path " _S + " metadata.dat " ) ;
for ( int i = 0 ; i < length ; i + + ) {
file < < rawMetadata [ i ] ;
}
file . close ( ) ;
utils : : datafile metadata ;
utils : : datafile : : Read ( metadata , " save_file_path " _S + " metadata.dat " ) ;
const size_t saveFileCount = metadata . GetKeys ( ) . size ( ) ;
SaveFile : : saveFileID = saveFileCount ;
GameState : : ChangeState ( States : : OVERWORLD_MAP ) ;
} , [ ] ( void * arg ) {
LOG ( " Failed to load metadata! Initializing as save file 0. " ) ;
SaveFile : : saveFileID = 0 ; //Since we couldn't find metadata, we are assuming we start at save file 0.
GameState : : ChangeState ( States : : OVERWORLD_MAP ) ;
} ) ;
# else
ERR ( " WARNING! Calling wrong save file ID setting function! Use SetSaveFileID() instead! " )
# endif
}
const void SaveFile : : UpdateSaveGameData ( std : : function < void ( ) > afterSaveGameDataUpdate ) {
SaveFile : : afterSaveGameDataUpdate = afterSaveGameDataUpdate ;
std : : filesystem : : create_directories ( " save_file_path " _S ) ;
auto LoadMetadataFile = [ ] ( ) {
auto gameFilesList = Component < ScrollableWindowComponent > ( LOAD_GAME , " Game Files List " ) ;
auto onlineGameFilesList = Component < ScrollableWindowComponent > ( LOAD_GAME , " Online Game Files List " ) ;
gameFilesList - > RemoveAllComponents ( ) ;
onlineGameFilesList - > RemoveAllComponents ( ) ;
const size_t saveFileCount = GetSaveFileCount ( ) ;
const size_t onlineSaveFileCount = GetOnlineSaveFileCount ( ) ;
utils : : datafile metadata , online_metadata ;
if ( ! std : : filesystem : : exists ( " save_file_path " _S + " metadata.dat " ) ) {
utils : : datafile : : Write ( metadata , " save_file_path " _S + " metadata.dat " ) ;
}
if ( ! std : : filesystem : : exists ( " save_file_path " _S + " metadata.dat " + " _online " ) ) {
utils : : datafile : : Write ( online_metadata , " save_file_path " _S + " metadata.dat " + " _online " ) ;
}
std : : string saveGameFilename = " save_file_path " _S + " metadata.dat " ;
std : : string onlineSaveGameFilename = " save_file_path " _S + " metadata.dat " + " _online " ;
utils : : datafile : : Read ( metadata , saveGameFilename ) ;
utils : : datafile : : Read ( online_metadata , onlineSaveGameFilename ) ;
float offsetY = 0 ;
for ( size_t i = 0 ; i < saveFileCount ; i + + ) {
if ( metadata . HasProperty ( std : : format ( " save{} " , i ) ) ) {
gameFilesList - > ADD ( std : : format ( " Load File Button - Save {} " , i ) , LoadFileButton ) ( geom2d : : rect < float > { { 0 , offsetY } , { gameFilesList - > GetSize ( ) . x - 13 , 48 } } , metadata [ std : : format ( " save{} " , i ) ] , i , [ ] ( MenuFuncData data ) {
std : : weak_ptr < LoadFileButton > comp = DYNAMIC_POINTER_CAST < LoadFileButton > ( data . component . lock ( ) ) ;
saveFileID = comp . lock ( ) - > getSaveFileID ( ) ;
SaveFile : : LoadGame ( ) ;
return true ;
} , ButtonAttr : : NONE ) END ;
offsetY + = 49 ;
}
}
offsetY = 0 ;
for ( size_t i = 0 ; i < onlineSaveFileCount ; i + + ) {
if ( online_metadata . HasProperty ( std : : format ( " save{} " , i ) ) ) {
onlineGameFilesList - > ADD ( std : : format ( " Online Load File Button - Save {} " , i ) , LoadFileButton ) ( geom2d : : rect < float > { { 0 , offsetY } , { onlineGameFilesList - > GetSize ( ) . x - 13 , 48 } } , online_metadata [ std : : format ( " save{} " , i ) ] , i , [ ] ( MenuFuncData data ) {
std : : weak_ptr < LoadFileButton > comp = DYNAMIC_POINTER_CAST < LoadFileButton > ( data . component . lock ( ) ) ;
saveFileID = comp . lock ( ) - > getSaveFileID ( ) ;
SaveFile : : LoadGame ( ) ;
return true ;
} , ButtonAttr : : NONE ) END ;
offsetY + = 49 ;
}
}
} ;
auto LoadMetadataFromDB = [ & ] ( ) {
auto gameFilesList = Component < ScrollableWindowComponent > ( LOAD_GAME , " Game Files List " ) ;
gameFilesList - > RemoveAllComponents ( ) ;
# ifdef __EMSCRIPTEN__
emscripten_idb_async_load ( " /assets " , ( " save_file_path " _S + " metadata.dat " ) . c_str ( ) , 0 , [ ] ( void * arg , void * data , int length ) {
LOG ( " Loaded metadata successfully! " ) ;
auto gameFilesList = Component < ScrollableWindowComponent > ( LOAD_GAME , " Game Files List " ) ;
std : : string rawMetadata = ( char * ) data ;
std : : ofstream file ( " save_file_path " _S + " metadata.dat " ) ;
for ( int i = 0 ; i < length ; i + + ) {
file < < rawMetadata [ i ] ;
}
file . close ( ) ;
utils : : datafile metadata ;
utils : : datafile : : Read ( metadata , " save_file_path " _S + " metadata.dat " ) ;
const size_t saveFileCount = metadata . GetKeys ( ) . size ( ) ;
float offsetY = 0 ;
for ( size_t i = 0 ; i < saveFileCount ; i + + ) {
if ( metadata . HasProperty ( std : : format ( " save{} " , i ) ) ) {
gameFilesList - > ADD ( std : : format ( " Load File Button - Save {} " , i ) , LoadFileButton ) ( geom2d : : rect < float > { { 0 , offsetY } , { gameFilesList - > GetSize ( ) . x - 13 , 48 } } , metadata [ std : : format ( " save{} " , i ) ] , i , [ ] ( MenuFuncData data ) {
std : : weak_ptr < LoadFileButton > comp = DYNAMIC_POINTER_CAST < LoadFileButton > ( data . component . lock ( ) ) ;
saveFileID = comp . lock ( ) - > getSaveFileID ( ) ;
SaveFile : : LoadGame ( ) ;
return true ;
} , ButtonAttr : : NONE ) END ;
offsetY + = 49 ;
}
}
SaveFile : : afterSaveGameDataUpdate ( ) ;
} , [ ] ( void * arg ) {
LOG ( " Failed to load metadata! " ) ;
SaveFile : : afterSaveGameDataUpdate ( ) ;
} ) ;
# endif
} ;
# ifdef __EMSCRIPTEN__
if ( onlineMode ) {
Server_GetLoadInfo ( [ & ] ( std : : string_view response ) {
if ( response ! = " ERR " ) {
std : : ofstream file ( " save_file_path " _S + " metadata.dat " + " _online " ) ;
std : : stringstream str ;
file < < response ;
file . close ( ) ;
LoadMetadataFile ( ) ;
utils : : datafile metadata ;
utils : : datafile : : Read ( metadata , " save_file_path " _S + " metadata.dat " + " _online " ) ;
const size_t saveFileCount = metadata . GetKeys ( ) . size ( ) ;
SaveFile : : saveFileID = saveFileCount ;
SaveFile : : afterSaveGameDataUpdate ( ) ;
}
} ) ;
} else {
LoadMetadataFromDB ( ) ;
}
# else
LoadMetadataFile ( ) ;
afterSaveGameDataUpdate ( ) ;
# endif
}
const std : : string SaveFile : : CreateServerRequest ( const SaveFileOperation : : Operation operation , std : : string_view data ) {
auto CalculateChecksum = [ ] ( std : : string_view operation , std : : string_view data ) {
return username . length ( ) * 8 + data . length ( ) * 2 + operation . length ( ) ;
} ;
auto EncodeURI = [ ] ( std : : string str ) {
return std : : accumulate ( str . begin ( ) , str . end ( ) , " " s , [ ] ( std : : string str , const char & c ) {
/*
* Implementation Source : https : //262.ecma-international.org/5.1/#sec-15.1.3.4
uriReserved : : : one of
; / ? : @ & = + $ ,
uriMark : : : one of
- _ . ! ~ * ' ( )
Let unescapedURISet be a String containing one instance of each character valid in uriReserved and uriUnescaped plus <EFBFBD> # <EFBFBD> .
*/
const std : : array uriReserved = { ' ; ' , ' / ' , ' ? ' , ' : ' , ' @ ' , ' & ' , ' = ' , ' + ' , ' $ ' , ' , ' , ' - ' , ' _ ' , ' . ' , ' ! ' , ' ~ ' , ' * ' , ' \' ' , ' ( ' , ' ) ' , ' # ' } ;
if ( c > = ' A ' & & c < = ' Z ' | | c > = ' a ' & & c < = ' z ' | | c > = ' 0 ' & & c < = ' 9 ' | | std : : find ( uriReserved . begin ( ) , uriReserved . end ( ) , c ) ! = uriReserved . end ( ) ) return std : : move ( str ) + c ;
std : : string convertedChar = std : : format ( " %{:02x} " , c ) ;
std : : for_each ( convertedChar . begin ( ) , convertedChar . end ( ) , [ ] ( char & c ) { c = char ( std : : toupper ( c ) ) ; } ) ;
return std : : move ( str ) + convertedChar ;
} ) ;
} ;
std : : string dataString = std : : format ( " \" username \" : \" {} \" " , EncodeURI ( username ) ) ;
std : : string operationName = " " ;
switch ( operation ) {
case SaveFileOperation : : GET_LOAD_FILES : {
operationName = std : : format ( " GET_LOAD_FILES " ) ;
//Data should be blank.
} break ;
case SaveFileOperation : : GET_FILE : {
operationName = std : : format ( " GET_FILE " ) ;
//Data should contain the file ID we want.
} break ;
case SaveFileOperation : : SAVE_FILE : {
operationName = std : : format ( " SAVE_FILE " ) ;
//Data should contain the entire contents of our save file.
} break ;
case SaveFileOperation : : SAVE_METADATA_FILE : {
operationName = std : : format ( " SAVE_METADATA_FILE " ) ;
//Data should contain the entire contents of our metadata save file.
} break ;
}
dataString + = " , \" operation \" : \" " + EncodeURI ( operationName ) + " \" " ;
dataString + = " , \" checksum \" : \" " + EncodeURI ( std : : to_string ( CalculateChecksum ( operationName , data ) ) ) + " \" " ;
dataString + = " , \" data \" : \" " + EncodeURI ( std : : string ( data ) ) + " \" " ;
return " { " + dataString + " } " ;
}
const void SaveFile : : Server_GetLoadInfo ( std : : function < void ( std : : string_view ) > respCallbackFunc ) {
game - > SendRequest ( " save_server " _S , CreateServerRequest ( SaveFileOperation : : GET_LOAD_FILES , " 0 " ) ) ;
game - > responseCallback = respCallbackFunc ;
}
const void SaveFile : : Server_GetFile ( std : : function < void ( std : : string_view ) > respCallbackFunc ) {
game - > SendRequest ( " save_server " _S , CreateServerRequest ( SaveFileOperation : : GET_FILE , std : : to_string ( saveFileID ) ) ) ;
game - > responseCallback = respCallbackFunc ;
}
const void SaveFile : : Server_SaveFile ( std : : function < void ( std : : string_view ) > respCallbackFunc ) {
std : : stringstream fileContents ;
std : : ifstream file ( " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ) ;
while ( file . good ( ) ) {
int val = file . get ( ) ;
if ( val ! = - 1 ) {
fileContents < < char ( val ) ;
}
}
game - > SendRequest ( " save_server " _S , CreateServerRequest ( SaveFileOperation : : SAVE_FILE , std : : to_string ( saveFileID ) + " | " + fileContents . str ( ) ) ) ;
game - > responseCallback = respCallbackFunc ;
}
const void SaveFile : : Server_SaveMetadataFile ( std : : function < void ( std : : string_view ) > respCallbackFunc ) {
std : : stringstream fileContents ;
std : : ifstream file ( " save_file_path " _S + " metadata.dat " + " _online " ) ;
while ( file . good ( ) ) {
int val = file . get ( ) ;
if ( val ! = - 1 ) {
fileContents < < char ( val ) ;
}
}
std : : string contents = fileContents . str ( ) ;
# ifdef __EMSCRIPTEN__
emscripten_idb_async_store ( " /assets " , ( " save_file_path " _S + " metadata.dat " + " _online " ) . c_str ( ) , contents . data ( ) , contents . length ( ) , 0 , [ ] ( void * arg ) {
LOG ( " Saved metadata successfully! " ) ;
} , [ ] ( void * arg ) {
LOG ( " Failed to save metadata! " ) ;
} ) ;
# endif
game - > SendRequest ( " save_server " _S , CreateServerRequest ( SaveFileOperation : : SAVE_METADATA_FILE , fileContents . str ( ) ) ) ;
game - > responseCallback = respCallbackFunc ;
}
const std : : string_view SaveFile : : GetUserID ( ) {
return SaveFile : : username ;
}
const void SaveFile : : SetUserID ( std : : string_view userID ) {
SaveFile : : username = userID ;
}
const bool SaveFile : : IsOnline ( ) {
return onlineMode ;
}
void SaveFile : : SetOnlineMode ( bool online ) {
onlineMode = online ;
Component < Checkbox > ( CLASS_SELECTION , " Online Character Checkbox " ) - > SetChecked ( onlineMode ) ;
}