/*
* Neural Tanks is a game created by Eddie O'Hagan that leverages Neural Networking
* to use for AI. This was created in Unreal Engine 4 which is developed by Epic Games.
* (Copyright Epic Games, Inc. All Rights Reserved.)
*
* Smart Tanks was originally developed by Mat Buckland in 2002 as an example
* on how Neural Networking can be used to train a set of minesweepers
* to collect mines. I (Eddie O'Hagan) updated this project in 2024 to use more
* Object Oriented Programming and modern practices. To see the original tutorial;
* visit <see href="http://www.ai-junkie.com/ann/evolved/nnt1.html">Neural Networking Tutorial</see>
* and <see href="http://www.ai-junkie.com/ga/intro/gat1.html">Genetic Algorithm Tutorial</see>
*/
#include "NeuralTanksGameInstance.h"
#include "NeuralTanksGameMode.h"
#include "NeuralTanksCheatManager.h"
#include "Utils/NeuralTanksUtils.h"
#include "UI/NeuralTanksLoadingScreen.h"
#include "Gameplay/AchievementManager.h"
#include <AudioDevice.h>
#include <Kismet/GameplayStatics.h>
#include <HAL/IConsoleManager.h>
#include <Blueprint/UserWidget.h>
#include <Serialization/ObjectAndNameAsStringProxyArchive.h>
#include <MoviePlayer/Public/MoviePlayer.h>

/// <summary>
/// Default constructor.
/// </summary>
UNeuralTanksGameInstance::UNeuralTanksGameInstance() : Super()
{
	myUserIndex = 0;
	mySaveSlotIndex = 0;
	mySaveFileNameForMetaData = "SaveFileMetaData_SaveSlotName";
	mySaveFileNameForOptionsData = "SaveFileOptions_SaveSlotName";
}

/// <summary>
/// Initializes the Neural Tanks Game Instance. This handles registering 
/// callbacks for loading screen, initializing achievements, applying options 
/// data, and saving/loading.
/// </summary>
void UNeuralTanksGameInstance::Init()
{
	Super::Init();

	FCoreUObjectDelegates::PreLoadMap.AddUObject(this, &UNeuralTanksGameInstance::OnBeginLoadingScreen);
	FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &UNeuralTanksGameInstance::OnEndLoadingScreen);

	myCheatManager = NewObject<UNeuralTanksCheatManager>();
	myCheatManager->OnCheatToggled_Event.AddDynamic(this, &UNeuralTanksGameInstance::OnCheatToggled);

	//Initialize/Query the Achievement manager.
	UAchievementManager::QueryAchievements();

	mySaveFileMetaData = LoadMetaData();
	myOptionsData = LoadOptions();
	mySaveSlotIndex = mySaveFileMetaData->ToalNumberOfSaveFiles;

	if (mySaveFileMetaData->ToalNumberOfSaveFiles > 0)
	{
		UNeuralTanksUtils::WriteToLog(FString::Printf(TEXT("UNeuralTanksGameInstance::Init() Detected %d previously saved games..."), mySaveFileMetaData->ToalNumberOfSaveFiles));
	}

	//Apply options from loaded options data.
	if (GEngine->Exec(GetWorld(), *FString::Printf(TEXT("sg.AntiAliasingQuality %d"), myOptionsData->AntiAliasingQuality)) == false)
	{
		UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::Init() Error: Failed to set AntiAliasingQuality from options data.");
	}

	if (GEngine->Exec(GetWorld(), *FString::Printf(TEXT("sg.EffectsQuality %d"), myOptionsData->EffectsQuality)) == false)
	{
		UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::Init() Error: Failed to set EffectsQuality from options data.");
	}

	if (GEngine->Exec(GetWorld(), *FString::Printf(TEXT("sg.PostProcessQuality %d"), myOptionsData->PostProcessQuality)) == false)
	{
		UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::Init() Error: Failed to set PostProcessQuality from options data.");
	}

	if (GEngine->Exec(GetWorld(), *FString::Printf(TEXT("sg.ShadowQuality %d"), myOptionsData->ShadowQuality)) == false)
	{
		UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::Init() Error: Failed to set ShadowQuality from options data.");
	}

	if (GEngine->Exec(GetWorld(), *FString::Printf(TEXT("sg.TextureQuality %d"), myOptionsData->TextureQuality)) == false)
	{
		UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::Init() Error: Failed to set TextureQuality from options data.");
	}

	if (GEngine->Exec(GetWorld(), *FString::Printf(TEXT("sg.ViewDistanceQuality %d"), myOptionsData->ViewDistanceQuality)) == false)
	{
		UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::Init() Error: Failed to set ViewDistanceQuality from options data.");
	}
}

/// <summary>
/// Called when the game is loading something (like a level) and requires a loading screen.
/// 
/// The way loading screens work in UE4 is a little funky, we have to make a simple SLATE
/// GUI for the loading screen (see <c>SNeuralTanksLoadingScreen<c>) and then pass it to
/// the engine's movie player. This allows the game to detect when it is loading something
/// and then place our loading screen in while its doing the loading.
/// </summary>
/// <param name="inMapName">The name of the level/scene/map we are loading into.</param>
void UNeuralTanksGameInstance::OnBeginLoadingScreen(const FString& inMapName)
{
	FLoadingScreenAttributes loadingScreen;
	TSharedPtr<SNeuralTanksLoadingScreen> theLoadingScreenWidget;

	UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::OnBeginLoadingScreen()");

	if (myLoadingScreenTips.Num() > 0)
	{
		theLoadingScreenWidget = SNew(SNeuralTanksLoadingScreen)
			.LoadingScreenTip(myLoadingScreenTips[FMath::RandRange(0, myLoadingScreenTips.Num() - 1)]);
	}
	else
	{
		theLoadingScreenWidget = SNew(SNeuralTanksLoadingScreen);
	}
	
	loadingScreen.bAutoCompleteWhenLoadingCompletes = false;
	loadingScreen.WidgetLoadingScreen = theLoadingScreenWidget;

	GetMoviePlayer()->SetupLoadingScreen(loadingScreen);
}

/// <summary>
/// Called once loading has finished and the loading screen is unloaded.
/// </summary>
/// <param name="inLoadedWorld">A reference to the world pointer of the level/scene/map we just loaded into.</param>
void UNeuralTanksGameInstance::OnEndLoadingScreen(UWorld* inLoadedWorld)
{
	UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::OnEndLoadingScreen()");
}

/// <summary>
/// Called when a cheat is turned on or off in the cheats menu.
/// </summary>
/// <param name="cheatName">The cheat that was enabled/disabled</param>
/// <param name="isEnabled">true if we are enabling the provided cheat, false if we are disabling it.</param>
void UNeuralTanksGameInstance::OnCheatToggled(const ECheatName cheatName, const bool isEnabled)
{
	if (isEnabled == true)
	{
		if (cheatName == ECheatName::VE_ALLGOLDARMOR)
		{
			UTankItemData* theGoldBodyArmor = FindTankItemDataFromRepository(TEXT("Armor_GoldTankBody"));
			UTankItemData* theGoldCannonArmor = FindTankItemDataFromRepository(TEXT("Armor_GoldTankCannon"));
			UTankItemData* theGoldGearArmor = FindTankItemDataFromRepository(TEXT("Armor_GoldTankGear"));
			UTankItemData* theGoldTrackArmor = FindTankItemDataFromRepository(TEXT("Armor_GoldTankTrack"));
			UTankItemData* theGoldTurretArmor = FindTankItemDataFromRepository(TEXT("Armor_GoldTankTurret"));
			UTankItemData* theGoldWheelArmor = FindTankItemDataFromRepository(TEXT("Armor_GoldTankWheel"));

			if (myPlayerProgressionSaveGame->AddItemToInventory(theGoldBodyArmor) == true)
			{
				myPlayerProgressionSaveGame->EquipItem(theGoldBodyArmor);
			}

			if (myPlayerProgressionSaveGame->AddItemToInventory(theGoldCannonArmor) == true)
			{
				myPlayerProgressionSaveGame->EquipItem(theGoldCannonArmor);
			}

			if (myPlayerProgressionSaveGame->AddItemToInventory(theGoldGearArmor) == true)
			{
				myPlayerProgressionSaveGame->EquipItem(theGoldGearArmor);
			}

			if (myPlayerProgressionSaveGame->AddItemToInventory(theGoldTrackArmor) == true)
			{
				myPlayerProgressionSaveGame->EquipItem(theGoldTrackArmor);
			}

			if (myPlayerProgressionSaveGame->AddItemToInventory(theGoldTurretArmor) == true)
			{
				myPlayerProgressionSaveGame->EquipItem(theGoldTurretArmor);
			}

			if (myPlayerProgressionSaveGame->AddItemToInventory(theGoldWheelArmor) == true)
			{
				myPlayerProgressionSaveGame->EquipItem(theGoldWheelArmor);
			}

		}
		else if (cheatName == ECheatName::VE_ALLLEVELSUNLOCKED)
		{
			myPlayerProgressionSaveGame->SetNumLevelsCompleted(6);
		}
		else //if (cheatName == ECheatName::VE_HASMAXCASH)
		{
			myPlayerProgressionSaveGame->SetAmountOfMoney(MAX_int32);
		}
	}
}

/// <summary>
/// Finds a specified tank item that is in the world item repository.
/// The repository is used as a collection of items that can potentially
/// be spawned into the level/scene/map.
/// </summary>
/// <param name="tankItemName">The name of the tank item you are looking for.</param>
/// <returns>A pointer to the CDO of the item in the repository.</returns>
AActor* UNeuralTanksGameInstance::RetrieveTankItemFromRepository(const FName tankItemName)
{
	UWorld* theWorld = GetWorld();
	if (theWorld != NULL)
	{
		return Cast<AActor>(myWorldItemRepository[tankItemName]->GetDefaultObject());
	}
	else
	{
		UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::RetrieveTankItemFromRepository() Error: Failed to spawn ATankItem, GetWorld() returned null.");
	}

	return NULL;
}

/// <summary>
/// Finds a specified tank item that is in the world item repository
/// (this uses the ItemName from the provided item data).
/// The repository is used as a collection of items that can potentially
/// be spawned into the level/scene/map.
/// </summary>
/// <param name="theItemData">A pointer to the item data.</param>
/// <returns>A pointer to the CDO of the item in the repository.</returns>
AActor* UNeuralTanksGameInstance::RetrieveTankItemFromData(const UTankItemData* theItemData)
{
	if (theItemData != NULL)
	{
		return RetrieveTankItemFromRepository(FName(theItemData->ItemName));
	}

	UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::RetrieveTankItemFromData(UTankItemData*) Error: theItemData is NULL!");
	return NULL;
}

/// <summary>
/// Instead of spawning the item into the world, we get the item's data using the default object,
/// then create a UTankItemData object containing that data. This allows us to (for example) add
/// items to the inventory without actually spawning them in the current world.
/// </summary>
/// <param name="tankItemName">The item name that corresponds to the ATankItem blueprint.</param>
/// <returns>The corresponding UTankItemData containing the data </returns>
UTankItemData* UNeuralTanksGameInstance::FindTankItemDataFromRepository(const FName tankItemName)
{
	UTankItemData* theTankItemData = NULL;
	ATankItem* tankItemCDO = Cast<ATankItem>(myWorldItemRepository[tankItemName]->GetDefaultObject());

	if (tankItemCDO != NULL)
	{
		theTankItemData = ATankItem::CreateDataFromTankItemActor(tankItemCDO);
	}
	else
	{
		UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::FindTankItemDataFromRepository() Error: Failed to get ATankItem CDO.");
	}

	return theTankItemData;
}

/// <summary>
/// The SaveGame function always creates a new save game, if you want to overwrite an existing one, see OverwriteGame().
/// Also saves meta data.
/// </summary>
/// <param name="playerProgressionToSave">The provided save file that needs to be serialized/saved.</param>
/// <param name="isAutoSave">true if this save was automatically made by the game, false if user created.</param>
/// <returns>true if successful, false otherwise.</returns>
bool UNeuralTanksGameInstance::SaveGame(UPlayerProgressionSaveGame* playerProgressionToSave, const bool isAutoSave)
{
	bool retVal = false;
	FName saveFileName;
	FDateTime timeOfSave;
	UPlayerProgressionSaveGame* newPlayerProgressionSaveGame;

	//Prevent int overflow by preventing making more then MAX_int32 save files.
	if((mySaveSlotIndex + 1) < MAX_int32)
	{
		//Get the current date and time to use as the display label.
		timeOfSave = FDateTime::Now();

		//Pass the array reference to fill with data from UPlayerProgressionSaveGame during serialization.
		FMemoryWriter theMemoryWriter(playerProgressionToSave->ByteData);

		//Construct the archive to hold the memory writer (byte data buffer).
		FObjectAndNameAsStringProxyArchive saveDataArchive(theMemoryWriter, true);

		//Find only variables with UPROPERTY(SaveGame)
		saveDataArchive.ArIsSaveGame = true;

		//Serialize the save file class with it's data.
		playerProgressionToSave->Serialize(saveDataArchive);

		//Create new save game object and copy over the data.
		newPlayerProgressionSaveGame = Cast<UPlayerProgressionSaveGame>(UGameplayStatics::CreateSaveGameObject(UPlayerProgressionSaveGame::StaticClass()));
		newPlayerProgressionSaveGame->CopyDataFrom(playerProgressionToSave);

		//Generate the save slot name and make sure the slot is not already in use.
		saveFileName = FName(FString::Printf(TEXT("SaveSlot_%d"), mySaveSlotIndex));
		while (UGameplayStatics::DoesSaveGameExist(saveFileName.ToString(), myUserIndex) == true)
		{
			saveFileName = FName(FString::Printf(TEXT("SaveSlot_%d"), mySaveSlotIndex++));
		}

		newPlayerProgressionSaveGame->SetSaveFileName(saveFileName);
		newPlayerProgressionSaveGame->SetDateOfSave(timeOfSave);
		newPlayerProgressionSaveGame->SetIsAutoSave(isAutoSave);

		//Add the new save file to the list(s) for tracking and UI.
		mySaveFileMetaData->SaveFileNamesAndDates.Add(saveFileName, timeOfSave);
		myCurrentSaveGame = newPlayerProgressionSaveGame;

		//Set the current save game we are using to be the newest created one.
		myPlayerProgressionSaveGame = newPlayerProgressionSaveGame;

		//Save to the game slot.
		retVal = UGameplayStatics::SaveGameToSlot(myPlayerProgressionSaveGame, saveFileName.ToString(), myUserIndex);

		SaveMetaData();

		if (retVal == true)
		{
			UNeuralTanksUtils::WriteToLog(FString::Printf(TEXT("UNeuralTanksGameInstance::SaveGame() Game saved successfully at %s."), *myPlayerProgressionSaveGame->GetDateOfSave().ToString()));
		}
		else
		{
			UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::SaveGame() Error: Game failed to save.");
		}
	}
	else
	{
		UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::SaveGame() Error: Game failed to due to reaching the max save count, please delete some save files.");
	}

	return retVal;
}

/// <summary>
/// Overwrites (erases and replaces) a selected save file with the provided save game file.
/// </summary>
/// <param name="nameOfSaveFileToOverwrite">The name of the save file (See SaveFileNamesAndDates)</param>
/// <param name="playerProgressionToSave">The save file to overwrite with.</param>
/// <returns>true if successful, false otherwise.</returns>
bool UNeuralTanksGameInstance::OverwriteGame(const FName nameOfSaveFileToOverwrite, UPlayerProgressionSaveGame* playerProgressionToSave)
{
	FDateTime timeOfSave;

	//Ensure the provided name exists in the save file database.
	if (mySaveFileMetaData->SaveFileNamesAndDates.Contains(nameOfSaveFileToOverwrite) == true)
	{
		//Get the updated time (current time).
		timeOfSave = FDateTime::Now();

		//Update the name of the current to the original name slot name we are taking the place of. 
		playerProgressionToSave->SetSaveFileName(nameOfSaveFileToOverwrite);

		//Update the provided save file with the updated time.
		playerProgressionToSave->SetDateOfSave(timeOfSave);

		//Update the meta data with the updated time.
		mySaveFileMetaData->SaveFileNamesAndDates[nameOfSaveFileToOverwrite] = timeOfSave;

		//Overwrite the slot with the provided data.
		myCurrentSaveGame = playerProgressionToSave;

		//Set the current save game we are using to be the reused one.
		myPlayerProgressionSaveGame = playerProgressionToSave;

		//Save the actual data and save the updated meta data as well.
		if (UGameplayStatics::SaveGameToSlot(myPlayerProgressionSaveGame, nameOfSaveFileToOverwrite.ToString(), myUserIndex) == true)
		{
			//Immediately update the meta data with the changes.
			if (SaveMetaData() == true)
			{
				UNeuralTanksUtils::WriteToLog(FString::Printf(TEXT("UNeuralTanksGameInstance::OverwriteGame(const FName, UPlayerProgressionSaveGame*) Game overwritten successfully at %s."), *myPlayerProgressionSaveGame->GetDateOfSave().ToString()));
				return true;
			}
			else
			{
				UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::OverwriteGame(const FName, UPlayerProgressionSaveGame*) Error: Game failed to overwrite.");
			}
		}
		else
		{
			UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::OverwriteGame(const FName, UPlayerProgressionSaveGame*) Error: Game failed to save to slot.");
		}
	}

	return false;
}

/// <summary>
/// Loads the corresponding save file for the provided <c>saveSlotName</c>.
/// </summary>
/// <param name="saveSlotName">The name of the save file to load.</param>
/// <returns>The corresponding save file.</returns>
UPlayerProgressionSaveGame* UNeuralTanksGameInstance::LoadGameBySlotName(const FName& saveSlotName)
{
	UPlayerProgressionSaveGame* saveGame = NULL;

	if (saveSlotName.IsNone() == false)
	{
		if (UGameplayStatics::DoesSaveGameExist(saveSlotName.ToString(), myUserIndex) == true)
		{
			saveGame = Cast<UPlayerProgressionSaveGame>(UGameplayStatics::LoadGameFromSlot(saveSlotName.ToString(), myUserIndex));
		}
		else
		{
			UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::LoadGame() Error: saveSlotName does not exist.");
		}
	}
	else
	{
		UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::LoadGame() Error: saveSlotName cannot be empty.");
	}

	return saveGame;
}

/// <summary>
/// Serializes and saves the options data to it's save game slot.
/// </summary>
/// <returns>true on success, false otherwise.</returns>
bool UNeuralTanksGameInstance::SaveOptions()
{
	//Copy the current options to save them.
	//myOptionsData->MasterVolume is assigned from blueprint via SetOptionsMasterVolume().
	myOptionsData->AntiAliasingQuality = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("sg.AntiAliasingQuality"))->GetValueOnAnyThread();
	myOptionsData->EffectsQuality = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("sg.EffectsQuality"))->GetValueOnAnyThread();
	myOptionsData->PostProcessQuality = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("sg.PostProcessQuality"))->GetValueOnAnyThread();
	myOptionsData->ShadowQuality = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("sg.ShadowQuality"))->GetValueOnAnyThread();
	myOptionsData->TextureQuality = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("sg.TextureQuality"))->GetValueOnAnyThread();
	myOptionsData->ViewDistanceQuality = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("sg.ViewDistanceQuality"))->GetValueOnAnyThread();

	//Pass the array reference to fill with data from Actor during serialization.
	FMemoryWriter theMemoryWriter(myOptionsData->ByteData);

	//Construct the archive to hold the memory writer (byte data buffer).
	FObjectAndNameAsStringProxyArchive optionsDataArchive(theMemoryWriter, true);

	//Find only variables with UPROPERTY(SaveGame)
	optionsDataArchive.ArIsSaveGame = true;

	//Serialize the save file class with it's data.
	myOptionsData->Serialize(optionsDataArchive);

	//Save the serialized data
	return UGameplayStatics::SaveDataToSlot(myOptionsData->ByteData, mySaveFileNameForOptionsData, myUserIndex);
}

/// <summary>
/// Loads the options data from it's save game slot. If no data is found,
/// a new <c>UOptionsData</c> object is created.
/// </summary>
/// <returns>The saved options data.</returns>
UOptionsData* UNeuralTanksGameInstance::LoadOptions()
{
	FName uniqueName;
	UOptionsData* retVal = NULL;
	TArray<uint8> serializedData;

	//Attempt to get the raw serialized data from the slot and deserialize it into retVal.
	if (UGameplayStatics::LoadDataFromSlot(serializedData, mySaveFileNameForOptionsData, myUserIndex) == true)
	{
		uniqueName = MakeUniqueObjectName(GetTransientPackage(), UOptionsData::StaticClass(), FName("OptionsData"));
		retVal = NewObject<UOptionsData>(GetTransientPackage(), UOptionsData::StaticClass(), uniqueName);

		//De-Serialize the information from the byte data.
		FMemoryReader theMemoryReader(serializedData);

		//Construct the archive to hold the memory reader.
		FObjectAndNameAsStringProxyArchive dataArchive(theMemoryReader, true);

		retVal->Serialize(dataArchive);
	}
	else
	{
		UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::LoadOptions() Initializing options for the first time...");

		//Initialize meta data for the first time.
		retVal = NewObject<UOptionsData>();
	}

	return retVal;
}

/// <summary>
/// Saves the save file meta data to it's save game slot.
/// </summary>
/// <returns>true on success, false otherwise.</returns>
bool UNeuralTanksGameInstance::SaveMetaData()
{
	//Pass the array reference to fill with data from Actor during serialization.
	FMemoryWriter theMemoryWriter(mySaveFileMetaData->ByteData);

	//Construct the archive to hold the memory writer (byte data buffer).
	FObjectAndNameAsStringProxyArchive saveDataArchive(theMemoryWriter, true);

	//Find only variables with UPROPERTY(SaveGame)
	saveDataArchive.ArIsSaveGame = true;

	//Serialize the save file class with it's data.
	mySaveFileMetaData->Serialize(saveDataArchive);

	//Save the serialized data
	return UGameplayStatics::SaveDataToSlot(mySaveFileMetaData->ByteData, mySaveFileNameForMetaData, myUserIndex);
}

/// <summary>
/// Loads the meta data from it's save game slot.
/// </summary>
/// <returns>The loaded meta data.</returns>
USaveFileMetaData* UNeuralTanksGameInstance::LoadMetaData()
{
	FName uniqueName;
	USaveFileMetaData* retVal = NULL;
	TArray<uint8> serializedData;
	
	//Attempt to get the raw serialized data from the slot and deserialize it into retVal.
	if (UGameplayStatics::LoadDataFromSlot(serializedData, mySaveFileNameForMetaData, myUserIndex) == true)
	{
		uniqueName = MakeUniqueObjectName(GetTransientPackage(), USaveFileMetaData::StaticClass(), FName("SaveFileMetaData"));
		retVal = NewObject<USaveFileMetaData>(GetTransientPackage(), USaveFileMetaData::StaticClass(), uniqueName);

		//De-Serialize the information from the byte data.
		FMemoryReader theMemoryReader(serializedData);

		//Construct the archive to hold the memory reader.
		FObjectAndNameAsStringProxyArchive dataArchive(theMemoryReader, true);

		retVal->Serialize(dataArchive);
	}
	else
	{
		UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::LoadMetaData() Initializing meta data for the first time...");

		//Initialize meta data for the first time.
		retVal = NewObject<USaveFileMetaData>();
	}

	return retVal;
}

/// <summary>
/// Creates an empty save game to store the players progression
/// and uses it as the current save game (for player progression).
/// This also effectively unloads the previous save game.
/// </summary>
void UNeuralTanksGameInstance::CreateDefaultSaveGameData()
{
	//Create empty (default) save game object.
	//This also unloads the previous save game data.
	myPlayerProgressionSaveGame = Cast<UPlayerProgressionSaveGame>(UGameplayStatics::CreateSaveGameObject(UPlayerProgressionSaveGame::StaticClass()));
}

/// <summary>
/// Deletes a corresponding save file based on the provided <c>theSaveGameFileNameToDelete</c>.
/// </summary>
/// <param name="theSaveGameFileNameToDelete">The name of the save file in the meta data collection.</param>
/// <returns>true if successfully deleted, false otherwise.</returns>
bool UNeuralTanksGameInstance::DeleteSaveGame(FName theSaveGameFileNameToDelete)
{
	bool retVal = false;

	if(mySaveFileMetaData->SaveFileNamesAndDates.Contains(theSaveGameFileNameToDelete) == true)
	{
		//Remove the specified save file from the list.
		mySaveFileMetaData->SaveFileNamesAndDates.Remove(theSaveGameFileNameToDelete);

		//Save/Update the meta data to no longer include the removed save.
		SaveMetaData();

		retVal = UGameplayStatics::DeleteGameInSlot(theSaveGameFileNameToDelete.ToString(), myUserIndex);
		if (retVal == false)
		{
			UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::DeleteSaveGame(UPlayerProgressionSaveGame*) Failed to delete game in slot!");
		}

		return retVal;
	}

	UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::DeleteSaveGame(UPlayerProgressionSaveGame*) Failed to find the save game in meta data!");
	return retVal;
}

/// <summary>
/// Attach the players equipment and accessories based on what is saved in the save file.
/// </summary>
/// <param name="pawnNeedingEquipment">The tank needing its equipment (the player).</param>
/// <returns>true on success, false otherwise.</returns>
bool UNeuralTanksGameInstance::EquipLoadout(ATankPawn* pawnNeedingEquipment)
{
	if (pawnNeedingEquipment != NULL)
	{
		if (myPlayerProgressionSaveGame != NULL)
		{
			return myPlayerProgressionSaveGame->EquipLoadout(pawnNeedingEquipment);
		}

		UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::EquipLoadout(ATankPawn*) Error: PlayerProgressionSaveGame is NULL!");
		return false;
	}
	
	UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::EquipLoadout(ATankPawn*) Error: pawnNeedingEquipment is NULL!");
	return false;
}

/// <summary>
/// Apply the effects of the attached equipment to the player (such as increased traction).
/// </summary>
/// <param name="pawnNeedingEquipmentStats">The tank needing the stats from its equipment (the player).</param>
/// <returns>true on success, false otherwise.</returns>
bool UNeuralTanksGameInstance::ApplyEquipmentStats(ATankPawn* pawnNeedingEquipmentStats)
{
	UTankItemData* currEquipmentItem;
	TArray<AActor*> spawnedEquipment;

	if (pawnNeedingEquipmentStats != NULL)
	{
		if (myPlayerProgressionSaveGame != NULL)
		{
			for (int index = 0; index < myPlayerProgressionSaveGame->GetNumberOfEquippedItems(); index++)
			{
				currEquipmentItem = myPlayerProgressionSaveGame->FindItemFromEquipment(index);
				spawnedEquipment.Add(RetrieveTankItemFromData(currEquipmentItem));
			}

			return myPlayerProgressionSaveGame->ApplyEquipmentStats(pawnNeedingEquipmentStats, spawnedEquipment);
		}

		UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::ApplyEquipmentStats(ATankPawn*) Error: myPlayerProgressionSaveGame is NULL!");
		return false;
	}

	UNeuralTanksUtils::WriteToLog("UNeuralTanksGameInstance::ApplyEquipmentStats(ATankPawn*) Error: pawnNeedingEquipmentStats is NULL!");
	return false;
}

/// <summary>
/// Returns the currently loaded player progression save file.
/// </summary>
/// <returns>The currently loaded player progression save file.</returns>
UPlayerProgressionSaveGame* UNeuralTanksGameInstance::GetPlayerProgressionSaveGame()
{
	return myPlayerProgressionSaveGame;
}

/// <summary>
/// Returns the currently loaded options data.
/// </summary>
/// <returns>The currently loaded options data.</returns>
UOptionsData* UNeuralTanksGameInstance::GetOptionsData()
{
	return myOptionsData;
}

/// <summary>
/// Returns a pointer to the cheat manager. Used for enabling/disabling
/// cheats.
/// </summary>
/// <returns>A pointer to the cheat manager.</returns>
UNeuralTanksCheatManager* UNeuralTanksGameInstance::GetCheatManager()
{
	return myCheatManager;
}

/// <summary>
/// Updates/Changes the current player progression save file.
/// </summary>
/// <param name="saveGame">The new save file to start using.</param>
void UNeuralTanksGameInstance::SetPlayerProgressionSaveGame(UPlayerProgressionSaveGame* saveGame)
{
	UNeuralTanksUtils::WriteToLog(FString::Printf(TEXT("UNeuralTanksGameInstance::SetPlayerProgressionSaveGame() loaded save file with time: %s."), *saveGame->GetDateOfSave().ToString()));
	myPlayerProgressionSaveGame = saveGame;
}

/// <summary>
/// Updates/Changes the master volume in the options menu.
/// </summary>
/// <param name="optionsMasterVolume">The new master volume to use.</param>
void UNeuralTanksGameInstance::SetOptionsMasterVolume(const float optionsMasterVolume)
{
	UNeuralTanksUtils::WriteToLog(FString::Printf(TEXT("UNeuralTanksGameInstance::SetOptionsMasterVolume() changing current master volume to %f"), optionsMasterVolume));
	myOptionsData->MasterVolume = optionsMasterVolume;
}

/// <summary>
/// Updates/Changes the music volume in the options menu.
/// </summary>
/// <param name="optionsMusicVolume">The new volume to use for music.</param>
void UNeuralTanksGameInstance::SetOptionsMusicVolume(const float optionsMusicVolume)
{
	UNeuralTanksUtils::WriteToLog(FString::Printf(TEXT("UNeuralTanksGameInstance::SetOptionsMusicVolume() changing current music volume to %f"), optionsMusicVolume));
	myOptionsData->MusicVolume = optionsMusicVolume;
}

/// <summary>
/// Updates/Changes the sound effects volume in the options menu.
/// </summary>
/// <param name="optionsSFXVolume">The new volume to use for sound effects.</param>
void UNeuralTanksGameInstance::SetOptionsSFXVolume(const float optionsSFXVolume)
{
	UNeuralTanksUtils::WriteToLog(FString::Printf(TEXT("UNeuralTanksGameInstance::SetOptionsSFXVolume() changing current SFX volume to %f"), optionsSFXVolume));
	myOptionsData->SFXVolume = optionsSFXVolume;
} 

/// <summary>
/// Updates/Changes the voice volume in the options menu.
/// </summary>
/// <param name="optionsVoiceVolume">The new volume to use for voice lines.</param>
void UNeuralTanksGameInstance::SetOptionsVoiceVolume(const float optionsVoiceVolume)
{
	UNeuralTanksUtils::WriteToLog(FString::Printf(TEXT("UNeuralTanksGameInstance::SetOptionsVoiceVolume() changing current voice volume to %f"), optionsVoiceVolume));
	myOptionsData->VoiceVolume = optionsVoiceVolume;
}