# 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 <EFBFBD> 2023 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_game
size_t SaveFile : : saveFileID = 0 ;
std : : string SaveFile : : saveFileName = " " ;
std : : string SaveFile : : username = " " ;
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 void SaveFile : : SaveGame ( ) {
std : : filesystem : : create_directories ( " save_file_path " _S ) ;
utils : : datafile saveFile ;
utils : : datafile : : INITIAL_SETUP_COMPLETE = false ;
for ( size_t itemCount = 0 ; auto & [ cat , items ] : Inventory : : sortedInv ) {
for ( std : : shared_ptr < Item > & item : items ) {
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 ) ) ) ;
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 ) {
saveFile [ " Unlocks " ] [ unlockName ] . SetString ( " True " ) ;
if ( unlockName = = " WORLD_MAP " ) continue ; //This is a special exception, because the world map is not an actual stage.
auto opt_cp = State_OverworldMap : : ConnectionPointFromString ( unlockName ) ;
if ( ! opt_cp . has_value ( ) ) ERR ( std : : format ( " WARNING! Could not find connection point {}! THIS SHOULD NOT BE HAPPENING! Potential invalid unlock name. " , unlockName ) ) ;
if ( opt_cp . value ( ) - > Visited ( ) ) {
saveFile [ " Unlocks " ] [ unlockName ] . SetString ( " True " , 1U ) ;
} else {
saveFile [ " Unlocks " ] [ unlockName ] . SetString ( " False " , 1U ) ;
}
}
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 ( ) ) ;
utils : : datafile : : Write ( saveFile , " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ) ;
utils : : datafile metadata ;
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 ) ;
utils : : datafile : : Write ( metadata , " save_file_path " _S + " metadata.dat " ) ;
utils : : datafile : : INITIAL_SETUP_COMPLETE = true ;
# ifdef __EMSCRIPTEN__
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 " ) {
std : : cout < < " WARNING! Could not save data to server! " < < std : : endl ;
}
} ) ;
} else {
std : : cout < < " WARNING! Could not save metadata to server! " < < std : : endl ;
}
} ;
Server_SaveMetadataFile ( RetryResponse ) ;
# endif
}
const void SaveFile : : LoadGame ( ) {
std : : filesystem : : create_directories ( " save_file_path " _S ) ;
auto LoadFile = [ & ] ( ) {
utils : : datafile loadFile ;
if ( std : : filesystem : : exists ( " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ) ) {
utils : : datafile : : Read ( 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 ) ;
}
}
game - > ChangePlayerClass ( classutils : : StringToClass ( loadFile [ " Player " ] [ " Class " ] . GetString ( ) ) ) ;
game - > GetPlayer ( ) - > level = loadFile [ " Player " ] [ " Level " ] . GetInt ( ) ;
game - > GetPlayer ( ) - > SetMoney ( loadFile [ " Player " ] [ " Money " ] . GetInt ( ) ) ;
game - > GetPlayer ( ) - > currentLevelXP = loadFile [ " Player " ] [ " Current EXP " ] . GetInt ( ) ;
game - > GetPlayer ( ) - > totalXPEarned = loadFile [ " Player " ] [ " Total EXP " ] . GetInt ( ) ;
for ( auto & [ key , data ] : loadFile [ " Player " ] [ " Base Stats " ] . GetOrderedKeys ( ) ) {
game - > GetPlayer ( ) - > SetBaseStat ( key , data . GetReal ( ) ) ;
}
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 . has_value ( ) ) ERR ( std : : format ( " WARNING! Could not find connection point {}! THIS SHOULD NOT BE HAPPENING! Potential invalid unlock name. " , key ) ) ;
opt_cp . value ( ) - > SetVisited ( ) ;
}
}
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 ( ) ;
GameState : : ChangeState ( States : : OVERWORLD_MAP , 0.5f ) ;
} else {
std : : cout < < std : : format ( " WARNING! File {} does not exist for loading! " , " save_file_path " _S + std : : format ( " save.{:04} " , saveFileID ) ) < < std : : endl ;
}
} ;
# ifdef __EMSCRIPTEN__
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 {
std : : cout < < " WARNING! Could not load save file! " < < std : : endl ;
}
} ) ;
# 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 : : UpdateSaveGameData ( ) {
std : : filesystem : : create_directories ( " save_file_path " _S ) ;
auto LoadMetadataFile = [ ] ( ) {
auto gameFilesList = Component < ScrollableWindowComponent > ( LOAD_GAME , " Game Files List " ) ;
gameFilesList - > RemoveAllComponents ( ) ;
const size_t saveFileCount = GetSaveFileCount ( ) ;
utils : : datafile metadata ;
if ( ! std : : filesystem : : exists ( " save_file_path " _S + " metadata.dat " ) ) {
utils : : datafile : : Write ( metadata , " save_file_path " _S + " metadata.dat " ) ;
}
utils : : datafile : : Read ( metadata , " save_file_path " _S + " metadata.dat " ) ;
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 ;
}
}
} ;
LoadMetadataFile ( ) ;
# ifdef __EMSCRIPTEN__
Server_GetLoadInfo ( [ & ] ( std : : string_view response ) {
if ( response ! = " ERR " ) {
std : : ofstream file ( " save_file_path " _S + " metadata.dat " ) ;
std : : stringstream str ;
file < < response ;
file . close ( ) ;
LoadMetadataFile ( ) ;
}
} ) ;
# 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 " ) ;
while ( file . good ( ) ) {
int val = file . get ( ) ;
if ( val ! = - 1 ) {
fileContents < < char ( val ) ;
}
}
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 ;
}