/*
	By Rama
*/
    
#include "VictoryBPLibraryPrivatePCH.h"
 
#include "VictoryBPFunctionLibrary.h"

//Foreground Window check, clipboard copy/paste
#include "Runtime/ApplicationCore/Public/HAL/PlatformApplicationMisc.h"

//FGPUDriverInfo GPU 
#include "Runtime/Core/Public/GenericPlatform/GenericPlatformDriver.h"
 
//MD5 Hash
#include "Runtime/Core/Public/Misc/SecureHash.h"

#include "StaticMeshResources.h"

#include "HeadMountedDisplay.h"
 
#include "GenericTeamAgentInterface.h"

//For PIE error messages
#include "MessageLog.h"
#define LOCTEXT_NAMESPACE "Fun BP Lib"

//Use MessasgeLog like this: (see GameplayStatics.cpp
/*
#if WITH_EDITOR
		FMessageLog("PIE").Error(FText::Format(LOCTEXT("SpawnObjectWrongClass", "SpawnObject wrong class: {0}'"), FText::FromString(GetNameSafe(*ObjectClass))));
#endif // WITH_EDITOR
*/

// To be able to perform regex operatins on level stream info package name
#if WITH_EDITOR
	#include "Runtime/Core/Public/Internationalization/Regex.h"
#endif

//~~~ Image Wrapper ~~~
#include "ImageUtils.h"
#include "IImageWrapper.h"
#include "IImageWrapperModule.h"
//~~~ Image Wrapper ~~~

//Body Setup
#include "PhysicsEngine/BodySetup.h"

#include "DestructibleComponent.h"


//Apex issues, can add iOS here  <3 Rama
#if PLATFORM_ANDROID || PLATFORM_HTML5 || PLATFORM_IOS
#ifdef WITH_APEX
#undef WITH_APEX
#endif
#define WITH_APEX 0
#endif //APEX EXCLUSIONS

//~~~ PhysX ~~~
#include "PhysXIncludes.h"
#include "PhysXPublic.h"		//For the ptou conversions

//For Scene Locking using Epic's awesome helper macros like SCOPED_SCENE_READ_LOCK
#include "Runtime/Engine/Private/PhysicsEngine/PhysXSupport.h"
//~~~~~~~~~~~

#include "IXRTrackingSystem.h"

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//									Saxon Rah Random Nodes
// Chrono and Random

//Order Matters, 
//		has to be after PhysX includes to avoid isfinite name definition issues
#include <chrono>
#include <random>
 
#include <string>
   
/*
	~~~ Rama File Operations CopyRight ~~~ 
	
	If you use any of my file operation code below, 
	please credit me somewhere appropriate as "Rama"
*/
template <class FunctorType>
class PlatformFileFunctor : public IPlatformFile::FDirectoryVisitor	//GenericPlatformFile.h
{
public:
	
	virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override
	{
		return Functor(FilenameOrDirectory, bIsDirectory);
	}

	PlatformFileFunctor(FunctorType&& FunctorInstance)
		: Functor(MoveTemp(FunctorInstance))
	{
	}

private:
	FunctorType Functor;
};

template <class Functor>
PlatformFileFunctor<Functor> MakeDirectoryVisitor(Functor&& FunctorInstance)
{
	return PlatformFileFunctor<Functor>(MoveTemp(FunctorInstance));
}

static FDateTime GetFileTimeStamp(const FString& File)
{
	return FPlatformFileManager::Get().GetPlatformFile().GetTimeStamp(*File);
}
static void SetTimeStamp(const FString& File, const FDateTime& TimeStamp)
{
	FPlatformFileManager::Get().GetPlatformFile().SetTimeStamp(*File,TimeStamp);
}	
	
//Radial Result Struct
struct FFileStampSort
{
	FString* FileName;
	FDateTime FileStamp;
	
	FFileStampSort(FString* IN_Name, FDateTime Stamp)
		: FileName(IN_Name)
		, FileStamp(Stamp)
	{}
};

//For Array::Sort()
FORCEINLINE bool operator< (const FFileStampSort& Left, const FFileStampSort& Right)
{
	return Left.FileStamp < Right.FileStamp; 
}

//Written by Rama, please credit me if you use this code elsewhere
static bool GetFiles(const FString& FullPathOfBaseDir, TArray<FString>& FilenamesOut, bool Recursive=false, const FString& FilterByExtension = "")
{
	//Format File Extension, remove the "." if present
	const FString FileExt = FilterByExtension.Replace(TEXT("."),TEXT("")).ToLower();
	
	FString Str;
	auto FilenamesVisitor = MakeDirectoryVisitor(
		[&](const TCHAR* FilenameOrDirectory, bool bIsDirectory) 
		{
			//Files
			if ( ! bIsDirectory)
			{
				//Filter by Extension
				if(FileExt != "")
				{
					Str = FPaths::GetCleanFilename(FilenameOrDirectory);
				
					//Filter by Extension
					if(FPaths::GetExtension(Str).ToLower() == FileExt) 
					{
						if(Recursive) 
						{
							FilenamesOut.Push(FilenameOrDirectory); //need whole path for recursive
						}
						else 
						{
							FilenamesOut.Push(Str);
						}
					}
				}
				
				//Include All Filenames!
				else
				{
					//Just the Directory
					Str = FPaths::GetCleanFilename(FilenameOrDirectory);
					
					if(Recursive) 
					{
						FilenamesOut.Push(FilenameOrDirectory); //need whole path for recursive
					}
					else 
					{
						FilenamesOut.Push(Str);
					}
				}
			}
			return true;
		}
	);
	if(Recursive) 
	{
		return FPlatformFileManager::Get().GetPlatformFile().IterateDirectoryRecursively(*FullPathOfBaseDir, FilenamesVisitor);
	}
	else 
	{
		return FPlatformFileManager::Get().GetPlatformFile().IterateDirectory(*FullPathOfBaseDir, FilenamesVisitor);
	}
}	

static bool FileExists(const FString& File)
{
	return FPlatformFileManager::Get().GetPlatformFile().FileExists(*File);
}
static bool FolderExists(const FString& Dir)
{
	return FPlatformFileManager::Get().GetPlatformFile().DirectoryExists(*Dir);
}
static bool RenameFile(const FString& Dest, const FString& Source)
{
	//Make sure file modification time gets updated!
	SetTimeStamp(Source,FDateTime::Now());
	
	return FPlatformFileManager::Get().GetPlatformFile().MoveFile(*Dest,*Source);
}
	
//Create Directory, Creating Entire Structure as Necessary
//		so if JoyLevels and Folder1 do not exist in JoyLevels/Folder1/Folder2
//			they will be created so that Folder2 can be created!

//This is my solution for fact that trying to create a directory fails 
//		if its super directories do not exist
static bool VCreateDirectory(FString FolderToMake) //not const so split can be used, and does not damage input
{
	if(FolderExists(FolderToMake))
	{
		return true;
	}
	
	// Normalize all / and \ to TEXT("/") and remove any trailing TEXT("/") if the character before that is not a TEXT("/") or a colon
	FPaths::NormalizeDirectoryName(FolderToMake);
	
	//Normalize removes the last "/", but is needed by algorithm
	//  Guarantees While loop will end in a timely fashion.
	FolderToMake += "/";
	
	FString Base;
	FString Left;
	FString Remaining;
	
	//Split off the Root
	FolderToMake.Split(TEXT("/"),&Base,&Remaining);
	Base += "/"; //add root text and Split Text to Base
	
	while(Remaining != "")
	{
		Remaining.Split(TEXT("/"),&Left,&Remaining);
		
		//Add to the Base
		Base += Left + FString("/"); //add left and split text to Base
		
		//Create Incremental Directory Structure!
		if ( ! FPlatformFileManager::Get().GetPlatformFile().CreateDirectory(*Base) && 
			! FPlatformFileManager::Get().GetPlatformFile().DirectoryExists(*Base) )
		{
			return false;
			//~~~~~
		}
	}
	
	return true;
}
/*
	~~~ Rama File Operations CopyRight ~~~ 
	
	If you use any of my file operation code above, 
	please credit me somewhere appropriate as "Rama"
*/



//////////////////////////////////////////////////////////////////////////
// UVictoryBPFunctionLibrary

UVictoryBPFunctionLibrary::UVictoryBPFunctionLibrary(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	
}
 
//~~~~~~~~~~~~~~~~~~
// 	Level Generation
//~~~~~~~~~~~~~~~~~~

/*
	CHANGE RETURN TYPE TO KISMET (or remove the kismet part)
		AND THEN GIVE OPTION TO ADD OR SET ROTATION OF A KISMET
	
	LoadedLevel->LevelTransform.SetRotation(FRotator(0, 120, 0).Quaternion());
	 
	//Trigger update!
	GetWorld()->UpdateLevelStreaming();
		
*/
ULevelStreaming* UVictoryBPFunctionLibrary::VictoryLoadLevelInstance(
	UObject* WorldContextObject, 
	FString MapFolderOffOfContent, 
	FString LevelName, 
	int32 InstanceNumber,
	FVector Location, FRotator Rotation,bool& Success
){ 
	Success = false; 
    if(!WorldContextObject) return nullptr;
	 
	UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return nullptr;
	//~~~~~~~~~~~
 
	//Full Name
	FString FullName = "/Game/" + MapFolderOffOfContent + "/" + LevelName;
	  
	FName LevelFName = FName(*FullName);
    FString PackageFileName = FullName;   
	
    ULevelStreamingKismet* StreamingLevel = NewObject<ULevelStreamingKismet>(World, ULevelStreamingKismet::StaticClass(), NAME_None, RF_Transient, NULL);
	
	if(!StreamingLevel)
	{
		return nullptr;
	}
	
	//Long Package Name
	FString LongLevelPackageName = FPackageName::FilenameToLongPackageName(PackageFileName);
	
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Here is where a unique name is chosen for the new level asset
	// 	Ensure unique names to gain ability to have multiple instances of same level!
	//			<3 Rama
	
	//Create Unique Name based on BP-supplied instance value
	FString UniqueLevelPackageName = LongLevelPackageName;
	UniqueLevelPackageName += "_VictoryInstance_" + FString::FromInt(InstanceNumber);
     
    //Set!
    StreamingLevel->SetWorldAssetByPackageName(FName(*UniqueLevelPackageName));
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	 
    if (World->IsPlayInEditor())
    {
        FWorldContext WorldContext = GEngine->GetWorldContextFromWorldChecked(World);
        StreamingLevel->RenameForPIE(WorldContext.PIEInstance);
    }
 
    StreamingLevel->LevelColor = FColor::MakeRandomColor();
    StreamingLevel->bShouldBeLoaded = true;
    StreamingLevel->bShouldBeVisible = true;
    StreamingLevel->bShouldBlockOnLoad = false;
    StreamingLevel->bInitiallyLoaded = true;
    StreamingLevel->bInitiallyVisible = true;
 
	//Transform
    StreamingLevel->LevelTransform = FTransform(Rotation,Location);
 
    StreamingLevel->PackageNameToLoad = LevelFName;
          
    if (!FPackageName::DoesPackageExist(StreamingLevel->PackageNameToLoad.ToString(), NULL, &PackageFileName))
    {        
        return nullptr;
    }
  
	//~~~
	
	//Actual map package to load
	StreamingLevel->PackageNameToLoad = FName(*LongLevelPackageName);
	
	//~~~
	
    // Add the new level to world.
    World->StreamingLevels.Add(StreamingLevel);
      
	Success = true;
    return StreamingLevel;
 }	
	
//~~~~~~~
//		AI
//~~~~~~~ 
EPathFollowingRequestResult::Type UVictoryBPFunctionLibrary::Victory_AI_MoveToWithFilter(
	APawn* Pawn, 
	const FVector& Dest, 
	TSubclassOf<UNavigationQueryFilter> FilterClass ,
	float AcceptanceRadius , 
	bool bProjectDestinationToNavigation ,
	bool bStopOnOverlap ,
	bool bCanStrafe 
){
	if(!Pawn) 
	{
		return EPathFollowingRequestResult::Failed;
	}
	
	AAIController* AIControl = Cast<AAIController>(Pawn->GetController());
	if(!AIControl) 
	{
		return EPathFollowingRequestResult::Failed;
	} 
	
	return AIControl->MoveToLocation(
		Dest, 
		AcceptanceRadius,
		bStopOnOverlap, 	//bStopOnOverlap
		true,						//bUsePathfinding 
		bProjectDestinationToNavigation, 
		bCanStrafe,			//bCanStrafe
		FilterClass			//<~~~
	);
}
	
//~~~~~~
//GPU
//~~~~~~ 
void UVictoryBPFunctionLibrary::Victory_GetGPUInfo(FString& DeviceDescription, FString& Provider, FString& DriverVersion, FString& DriverDate )
{   
	FGPUDriverInfo GPUDriverInfo = FPlatformMisc::GetGPUDriverInfo(GRHIAdapterName);
	 
	DeviceDescription 	= GPUDriverInfo.DeviceDescription;
	Provider 			= GPUDriverInfo.ProviderName;
	DriverVersion 		= GPUDriverInfo.UserDriverVersion;
	DriverDate 			= GPUDriverInfo.DriverDate;
}

//~~~~~~
//Core
//~~~~~~ 
 
//~~~~~~
//Physics
//~~~~~~ 
float UVictoryBPFunctionLibrary::GetDistanceToCollision(UPrimitiveComponent* CollisionComponent, const FVector& Point, FVector& ClosestPointOnCollision)
{
	if(!CollisionComponent) return -1;
	 
	return CollisionComponent->GetDistanceToCollision(Point,ClosestPointOnCollision);
}

float UVictoryBPFunctionLibrary::GetDistanceBetweenComponentSurfaces(UPrimitiveComponent* CollisionComponent1, UPrimitiveComponent* CollisionComponent2, FVector& PointOnSurface1, FVector& PointOnSurface2)
{
	if(!CollisionComponent1 || !CollisionComponent2) return -1;
 
	//Closest Point on 2 to 1
	CollisionComponent2->GetDistanceToCollision(CollisionComponent1->GetComponentLocation(), PointOnSurface2);
  
	//Closest Point on 1 to closest point on surface of 2
	return CollisionComponent1->GetDistanceToCollision(PointOnSurface2, PointOnSurface1);
}



void UVictoryBPFunctionLibrary::VictoryCreateProc(int32& ProcessId, FString FullPathOfProgramToRun,TArray<FString> CommandlineArgs,bool Detach,bool Hidden, int32 Priority, FString OptionalWorkingDirectory)
{   
	//Please note ProcessId should really be uint32 but that is not supported by BP yet
	 
	FString Args = "";
	if(CommandlineArgs.Num() > 1)
	{
		Args = CommandlineArgs[0]; 
		for(int32 v = 1; v < CommandlineArgs.Num(); v++)
		{
			Args += " " + CommandlineArgs[v];
		}
	}
	else if(CommandlineArgs.Num() > 0)
	{
		Args = CommandlineArgs[0];
	}
	
	uint32 NeedBPUINT32 = 0;
	FProcHandle ProcHandle = FPlatformProcess::CreateProc( 
		*FullPathOfProgramToRun, 
		*Args, 
		Detach,//* @param bLaunchDetached		if true, the new process will have its own window
		false,//* @param bLaunchHidded			if true, the new process will be minimized in the task bar
		Hidden,//* @param bLaunchReallyHidden	if true, the new process will not have a window or be in the task bar
		&NeedBPUINT32, 
		Priority, 
		(OptionalWorkingDirectory != "") ? *OptionalWorkingDirectory : nullptr,//const TCHAR* OptionalWorkingDirectory, 
		nullptr
	);
	 
	//Not sure what to do to expose UINT32 to BP
	ProcessId = NeedBPUINT32; 
}
	
bool UVictoryBPFunctionLibrary::CompareMD5Hash(FString MD5HashFile1, FString MD5HashFile2 )
{
	//Load Hash Files
	TArray<uint8> TheBinaryArray;
	if (!FFileHelper::LoadFileToArray(TheBinaryArray, *MD5HashFile1))
	{
		UE_LOG(LogTemp,Error,TEXT("First hash file invalid %s"), *MD5HashFile1);
		return false;
		//~~
	}
	FMemoryReader FromBinary = FMemoryReader(TheBinaryArray, true); //true, free data after done
	FMD5Hash FirstHash;
	FromBinary << FirstHash;
	
	TheBinaryArray.Empty();
	if (!FFileHelper::LoadFileToArray(TheBinaryArray, *MD5HashFile2))
	{
		UE_LOG(LogTemp,Error,TEXT("second hash file invalid %s"), *MD5HashFile2);
		return false;
		//~~
	}
	
	FMemoryReader FromBinarySecond = FMemoryReader(TheBinaryArray, true); //true, free data after done
	FMD5Hash SecondHash;
	FromBinarySecond << SecondHash;
	 
	return FirstHash == SecondHash;
}
bool UVictoryBPFunctionLibrary::CreateMD5Hash(FString FileToHash, FString FileToStoreHashTo )
{
	if(!FPlatformFileManager::Get().GetPlatformFile().FileExists(*FileToHash))
	{
		UE_LOG(LogTemp,Error,TEXT("File to hash not found %d"), *FileToHash);
		return false;
	}
	
	int64 SizeOfFileToHash = FPlatformFileManager::Get().GetPlatformFile().FileSize(*FileToHash);
	if(SizeOfFileToHash > 2 * 1000000000)
	{
		UE_LOG(LogTemp,Warning,TEXT("File is >2gb, hashing will be very slow %d"), SizeOfFileToHash);
	}
	
	FMD5Hash FileHash = FMD5Hash::HashFile(*FileToHash);
	
	//write to file
	FBufferArchive ToBinary;
	ToBinary << FileHash;
	
	if (FFileHelper::SaveArrayToFile(ToBinary, * FileToStoreHashTo)) 
	{
		// Free Binary Array 	
		ToBinary.FlushCache();
		ToBinary.Empty();
	}
	else
	{ 
		UE_LOG(LogTemp,Warning,TEXT("File hashed successfully but could not be saved to disk, file IO error %s"), *FileToHash);
		return false;
	}
	 
	return true;
}

bool UVictoryBPFunctionLibrary::VictoryPhysics_UpdateAngularDamping(UPrimitiveComponent* CompToUpdate, float NewAngularDamping)
{
	if(!CompToUpdate) return false;
	
	FBodyInstance* Body = CompToUpdate->GetBodyInstance();
	if(!Body) return false;
	
	//Deep safety check
	if(!Body->IsValidBodyInstance()) return false;
	
	//Set!
	Body->AngularDamping = NewAngularDamping;
	
	//~~~~~~~~~~~~~~~~~
	Body->UpdateDampingProperties();
	//~~~~~~~~~~~~~~~~~
	
	return true;
}
	 
bool UVictoryBPFunctionLibrary::VictoryDestructible_DestroyChunk(UDestructibleComponent* DestructibleComp, int32 HitItem)
{   
	#if WITH_APEX
	if(!DestructibleComp) 
	{
		return false;
	}
	  
	//Visibility
	DestructibleComp->SetChunkVisible( HitItem, false );
	 
	//Collision
	physx::PxShape** PShapes;
	const physx::PxU32 PShapeCount = DestructibleComp->ApexDestructibleActor->getChunkPhysXShapes(PShapes, HitItem);
	if (PShapeCount > 0)
	{    
		PxFilterData PQueryFilterData,PSimFilterData; //null data
		  
		for(uint32 ShapeIndex = 0; ShapeIndex < PShapeCount; ++ShapeIndex)
		{ 
			PxShape* Shape = PShapes[ShapeIndex];
			if(!Shape) continue;
			
			{ 
				SCOPED_SCENE_WRITE_LOCK(Shape->getActor()->getScene());
				
				Shape->setQueryFilterData(PQueryFilterData); //null data
				Shape->setSimulationFilterData(PSimFilterData); //null data
				Shape->setFlag(PxShapeFlag::eSCENE_QUERY_SHAPE, false);
				Shape->setFlag(PxShapeFlag::eSIMULATION_SHAPE, false);
				Shape->setFlag(PxShapeFlag::eVISUALIZATION, false);	
			}
		}
	}
	return true;
	#endif //WITH_APEX
	 
	UE_LOG(LogTemp,Error,TEXT("UVictoryBPFunctionLibrary::VictoryDestructible_DestroyChunk ~ Current Platform does not support APEX"));
	return false;
}

static int32 GetChildBones(const FReferenceSkeleton& ReferenceSkeleton, int32 ParentBoneIndex, TArray<int32> & Children)
{ 
	Children.Empty();

	const int32 NumBones = ReferenceSkeleton.GetNum();
	for(int32 ChildIndex=ParentBoneIndex+1; ChildIndex<NumBones; ChildIndex++)
	{
		if ( ParentBoneIndex == ReferenceSkeleton.GetParentIndex(ChildIndex) )
		{
			Children.Add(ChildIndex);
		}
	}

	return Children.Num();
}
static void GetChildBoneNames_Recursive(USkeletalMeshComponent* SkeletalMeshComp, int32 ParentBoneIndex, TArray<FName>& ChildBoneNames)
{	
	TArray<int32> BoneIndicies;  
	GetChildBones(SkeletalMeshComp->SkeletalMesh->RefSkeleton, ParentBoneIndex, BoneIndicies);
	   
	if(BoneIndicies.Num() < 1)
	{
		//Stops the recursive skeleton search
		return;
	}
	 
	for(const int32& BoneIndex : BoneIndicies)
	{
		FName ChildBoneName = SkeletalMeshComp->GetBoneName(BoneIndex);
		ChildBoneNames.Add(ChildBoneName);
		 
		//Recursion
		GetChildBoneNames_Recursive(SkeletalMeshComp, BoneIndex,ChildBoneNames);
	}
}

int32 UVictoryBPFunctionLibrary::GetAllBoneNamesBelowBone( USkeletalMeshComponent* SkeletalMeshComp, FName StartingBoneName,  TArray<FName>& BoneNames )
{
	BoneNames.Empty();
	
	if(!SkeletalMeshComp || !SkeletalMeshComp->SkeletalMesh)
	{
		return -1;
		//~~~~
	}
	 
	int32 StartingBoneIndex = SkeletalMeshComp->GetBoneIndex(StartingBoneName);
	 
	//Recursive
	GetChildBoneNames_Recursive(SkeletalMeshComp, StartingBoneIndex, BoneNames);
	     
	return BoneNames.Num();
}


//~~~~~~
// File IO
//~~~~~~ 
bool UVictoryBPFunctionLibrary::JoyFileIO_GetFiles(TArray<FString>& Files, FString RootFolderFullPath, FString Ext)
{
	if(RootFolderFullPath.Len() < 1) return false;
	
	FPaths::NormalizeDirectoryName(RootFolderFullPath);
	
	IFileManager& FileManager = IFileManager::Get();
	 
	if(!Ext.Contains(TEXT("*")))
	{
		if(Ext == "") 
		{
			Ext = "*.*";
		}
		else
		{
			Ext = (Ext.Left(1) == ".") ? "*" + Ext : "*." + Ext;
		}
	}
	
	FString FinalPath = RootFolderFullPath + "/" + Ext;
	
	FileManager.FindFiles(Files, *FinalPath, true, false);
	return true;				  
}
bool UVictoryBPFunctionLibrary::JoyFileIO_GetFilesInRootAndAllSubFolders(TArray<FString>& Files, FString RootFolderFullPath, FString Ext)
{
	if(RootFolderFullPath.Len() < 1) return false;
	
	FPaths::NormalizeDirectoryName(RootFolderFullPath);
	
	IFileManager& FileManager = IFileManager::Get();
	 
	if(!Ext.Contains(TEXT("*")))
	{
		if(Ext == "") 
		{
			Ext = "*.*";
		}
		else
		{
			Ext = (Ext.Left(1) == ".") ? "*" + Ext : "*." + Ext;
		}
	}
	
	FileManager.FindFilesRecursive(Files, *RootFolderFullPath, *Ext, true, false);
	return true;
}

bool UVictoryBPFunctionLibrary::ScreenShots_Rename_Move_Most_Recent(
	FString& OriginalFileName,
	FString NewName, 
	FString NewAbsoluteFolderPath, 
	bool HighResolution
){ 
	OriginalFileName = "None";

	//File Name given?
	if(NewName.Len() <= 0) return false;
	
	//Normalize
	FPaths::NormalizeDirectoryName(NewAbsoluteFolderPath);
	
	//Ensure target directory exists, 
	//		_or can be created!_ <3 Rama
	if(!VCreateDirectory(NewAbsoluteFolderPath)) return false;
	
	FString ScreenShotsDir = VictoryPaths__ScreenShotsDir();
	
	//Find all screenshots
	TArray<FString> Files;									//false = not recursive, not expecting subdirectories
	bool Success = GetFiles(ScreenShotsDir, Files, false);
	
	if(!Success)
	{
		return false;
	}
	
	//Filter
	TArray<FString> ToRemove; //16 bytes each, more stable than ptrs though since RemoveSwap is involved
	for(FString& Each : Files)
	{
		if(HighResolution)
		{
			//remove those that dont have it
			if(Each.Left(4) != "High")
			{
				ToRemove.Add(Each);
			}
		}
		else
		{ 
			//Remove those that have it!
			if(Each.Left(4) == "High")
			{
				ToRemove.Add(Each);
			}
		}
	}
	
	//Remove!
	for(FString& Each : ToRemove)
	{
		Files.RemoveSwap(Each); //Fast remove! Does not preserve order
	}
	
	//No files?
	if(Files.Num() < 1)
	{ 
		return false;
	}
	
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Rama's Sort Files by Time Stamp Feature
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//Sort the file names by time stamp
	//  This is my custom c++ struct to do this,
	//  	combined with my operator< and UE4's
	//			TArray::Sort() function!
	TArray<FFileStampSort> FileSort;
	for(FString& Each : Files)
	{
		FileSort.Add(FFileStampSort(&Each,GetFileTimeStamp(Each)));
		
	}

	//Sort all the file names by their Time Stamp!
	FileSort.Sort();
	
	//Biggest value = last entry
	OriginalFileName = *FileSort.Last().FileName;
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	    
	//Generate new Full File Path!
	FString NewFullFilePath = NewAbsoluteFolderPath + "/" + NewName + ".png";
	 
	//Move File!
	return RenameFile(NewFullFilePath, ScreenShotsDir + "/" + OriginalFileName);
}

void UVictoryBPFunctionLibrary::VictoryISM_GetAllVictoryISMActors(UObject* WorldContextObject, TArray<AVictoryISM*>& Out)
{
	if(!WorldContextObject) return;
	 
	UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return;
	//~~~~~~~~~~~
	
	Out.Empty();
	for(TActorIterator<AVictoryISM> Itr(World); Itr; ++Itr)
	{
		Out.Add(*Itr);
	}
}
	
void UVictoryBPFunctionLibrary::VictoryISM_ConvertToVictoryISMActors(
	UObject* WorldContextObject, 
	TSubclassOf<AActor> ActorClass, 
	TArray<AVictoryISM*>& CreatedISMActors, 
	bool DestroyOriginalActors,
	int32 MinCountToCreateISM
){
	//User Input Safety
	if(MinCountToCreateISM < 1) MinCountToCreateISM = 1; //require for array access safety
	
	CreatedISMActors.Empty();
	
	if(!WorldContextObject) return;
	 
	UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return;
	//~~~~~~~~~~~
	
	//I want one array of actors for each unique static mesh asset!  -Rama
	TMap< UStaticMesh*,TArray<AActor*> > VictoryISMMap;
	
	//Note the ActorClass filter on the Actor Iterator! -Rama
	for (TActorIterator<AActor> Itr(World, ActorClass); Itr; ++Itr)
	{
		//Get Static Mesh Component!
		UStaticMeshComponent* Comp = Itr->FindComponentByClass<UStaticMeshComponent>();
		if(!Comp) continue; 
		if(!Comp->IsValidLowLevel()) continue;
		//~~~~~~~~~
		
		//Add Key if not present!
		if(!VictoryISMMap.Contains(Comp->GetStaticMesh()))
		{
			VictoryISMMap.Add(Comp->GetStaticMesh());
			VictoryISMMap[Comp->GetStaticMesh()].Empty(); //ensure array is properly initialized
		}
		
		//Add the actor!
		VictoryISMMap[Comp->GetStaticMesh()].Add(*Itr);
	}
	  
	//For each Static Mesh Asset in the Victory ISM Map
	for (TMap< UStaticMesh*,TArray<AActor*> >::TIterator It(VictoryISMMap); It; ++It)
	{
		//Get the Actor Array for this particular Static Mesh Asset!
		TArray<AActor*>& ActorArray = It.Value();
		
		//No entries?
		if(ActorArray.Num() < MinCountToCreateISM) continue;
		//~~~~~~~~~~~~~~~~~~
		  
		//Get the Root
		UStaticMeshComponent* RootSMC = ActorArray[0]->FindComponentByClass<UStaticMeshComponent>();
		if(!RootSMC) continue;
		//~~~~~~~~~~
		
		//Gather transforms!
		TArray<FTransform> WorldTransforms;
		for(AActor* Each : ActorArray)
		{
			WorldTransforms.Add(Each->GetTransform());
			 
			//Destroy original?
			if(DestroyOriginalActors)
			{
				Each->Destroy();
			}
		}
		  
		//Create Victory ISM
		FActorSpawnParameters SpawnInfo;
		//SpawnInfo.bNoCollisionFail 		= true; //always create!
		SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
		SpawnInfo.bDeferConstruction 	= false;
		 
		AVictoryISM* NewISM = World->SpawnActor<AVictoryISM>(
			AVictoryISM::StaticClass(), 
			RootSMC->GetComponentLocation() ,
			RootSMC->GetComponentRotation(), 
			SpawnInfo 
		);
		
		if(!NewISM) continue;
		//~~~~~~~~~~
		
		//Mesh
		NewISM->Mesh->SetStaticMesh(RootSMC->GetStaticMesh());
	
		//Materials
		const int32 MatTotal = RootSMC->GetNumMaterials();
		for(int32 v = 0; v < MatTotal; v++)
		{
			NewISM->Mesh->SetMaterial(v,RootSMC->GetMaterial(v));
		}
		 
		//Set Transforms!
		for(const FTransform& Each : WorldTransforms)
		{
			NewISM->Mesh->AddInstanceWorldSpace(Each);
		}
		
		//Add new ISM!
		CreatedISMActors.Add(NewISM);
	}
	
	//Clear memory
	VictoryISMMap.Empty();
}
	 
	
	 
void UVictoryBPFunctionLibrary::SaveGameObject_GetAllSaveSlotFileNames(TArray<FString>& FileNames)
{
	FileNames.Empty();
	FString Path = VictoryPaths__SavedDir() + "SaveGames";
	GetFiles(Path,FileNames); //see top of this file, my own file IO code - Rama
}

//~~~ Victory Paths ~~~

FString UVictoryBPFunctionLibrary::VictoryPaths__Win64Dir_BinaryLocation()
{
	return FString(FPlatformProcess::BaseDir());
}

FString UVictoryBPFunctionLibrary::VictoryPaths__WindowsNoEditorDir()
{
	return FPaths::ConvertRelativePathToFull(FPaths::RootDir());
}

FString UVictoryBPFunctionLibrary::VictoryPaths__GameRootDirectory()
{
	return FPaths::ConvertRelativePathToFull(FPaths::ProjectDir());
}

FString UVictoryBPFunctionLibrary::VictoryPaths__SavedDir()
{
	return FPaths::ConvertRelativePathToFull(FPaths::ProjectSavedDir());
}
FString UVictoryBPFunctionLibrary::VictoryPaths__ConfigDir()
{
	return FPaths::ConvertRelativePathToFull(FPaths::ProjectConfigDir());
}

FString UVictoryBPFunctionLibrary::VictoryPaths__ScreenShotsDir()
{
	return FPaths::ConvertRelativePathToFull(FPaths::ScreenShotDir());
}
 
FString UVictoryBPFunctionLibrary::VictoryPaths__LogsDir()
{
	return FPaths::ConvertRelativePathToFull(FPaths::ProjectLogDir());
}


//~~~~~~~~~~~~~~~~~
	
	
FVector2D UVictoryBPFunctionLibrary::Vector2DInterpTo(FVector2D Current, FVector2D Target, float DeltaTime, float InterpSpeed)
{
	return FMath::Vector2DInterpTo( Current, Target, DeltaTime, InterpSpeed );
}
FVector2D UVictoryBPFunctionLibrary::Vector2DInterpTo_Constant(FVector2D Current, FVector2D Target, float DeltaTime, float InterpSpeed)
{
	return FMath::Vector2DInterpConstantTo( Current, Target, DeltaTime, InterpSpeed );
}

float UVictoryBPFunctionLibrary::MapRangeClamped(float Value, float InRangeA, float InRangeB, float OutRangeA, float OutRangeB)
{ 
	return FMath::GetMappedRangeValueClamped(FVector2D(InRangeA,InRangeB),FVector2D(OutRangeA,OutRangeB),Value);
}

FVictoryInput UVictoryBPFunctionLibrary::VictoryGetVictoryInput(const FKeyEvent& KeyEvent)
{
	FVictoryInput VInput;
	 
	VInput.Key 			= KeyEvent.GetKey();
	VInput.KeyAsString 	= VInput.Key.GetDisplayName().ToString();
	
	VInput.bAlt 		= KeyEvent.IsAltDown();
	VInput.bCtrl 		= KeyEvent.IsControlDown();
	VInput.bShift 	= KeyEvent.IsShiftDown();
	VInput.bCmd 		= KeyEvent.IsCommandDown();
	
	return VInput;
}
FVictoryInputAxis UVictoryBPFunctionLibrary::VictoryGetVictoryInputAxis(const FKeyEvent& KeyEvent)
{
	FVictoryInputAxis VInput;
	 
	VInput.Key 			= KeyEvent.GetKey();
	VInput.KeyAsString 	= VInput.Key.GetDisplayName().ToString();
	
	VInput.Scale = 1;
	
	return VInput;
}

void UVictoryBPFunctionLibrary::VictoryGetAllAxisKeyBindings(TArray<FVictoryInputAxis>& Bindings)
{
	Bindings.Empty();
	 
	const UInputSettings* Settings = GetDefault<UInputSettings>();
	if(!Settings) return;
	
	const TArray<FInputAxisKeyMapping>& Axi = Settings->AxisMappings;
	
	for(const FInputAxisKeyMapping& Each : Axi)
	{
		Bindings.Add(FVictoryInputAxis(Each));
	}
}
void UVictoryBPFunctionLibrary::VictoryRemoveAxisKeyBind(FVictoryInputAxis ToRemove)
{  
	//GetMutableDefault
	UInputSettings* Settings = GetMutableDefault<UInputSettings>();
	if(!Settings) return;
	
	TArray<FInputAxisKeyMapping>& Axi = Settings->AxisMappings;
	  
	bool Found = false;
	for(int32 v = 0; v < Axi.Num(); v++)
	{
		if(Axi[v].Key == ToRemove.Key)
		{
			Found = true;
			Axi.RemoveAt(v);
			v = 0;
			continue;
		}
	}
	 
	if(Found)
	{
		//SAVES TO DISK
		Settings->SaveKeyMappings();
		   
		//REBUILDS INPUT, creates modified config in Saved/Config/Windows/Input.ini
		for (TObjectIterator<UPlayerInput> It; It; ++It)
		{
			It->ForceRebuildingKeyMaps(true);
		}
	}
}

void UVictoryBPFunctionLibrary::VictoryGetAllActionKeyBindings(TArray<FVictoryInput>& Bindings)
{
	Bindings.Empty();
	 
	const UInputSettings* Settings = GetDefault<UInputSettings>();
	if(!Settings) return;
	
	const TArray<FInputActionKeyMapping>& Actions = Settings->ActionMappings;
	
	for(const FInputActionKeyMapping& Each : Actions)
	{
		Bindings.Add(FVictoryInput(Each));
	}
}

void UVictoryBPFunctionLibrary::VictoryRemoveActionKeyBind(FVictoryInput ToRemove)
{
	//GetMutableDefault
	UInputSettings* Settings = GetMutableDefault<UInputSettings>();
	if(!Settings) return;
	
	TArray<FInputActionKeyMapping>& Actions = Settings->ActionMappings;
	  
	bool Found = false;
	for(int32 v = 0; v < Actions.Num(); v++)
	{
		if(Actions[v].Key == ToRemove.Key)
		{
			Found = true;
			Actions.RemoveAt(v);
			v = 0;
			continue;
		}
	}
	
	if(Found)
	{
		//SAVES TO DISK
		Settings->SaveKeyMappings();
		   
		//REBUILDS INPUT, creates modified config in Saved/Config/Windows/Input.ini
		for (TObjectIterator<UPlayerInput> It; It; ++It)
		{
			It->ForceRebuildingKeyMaps(true);
		}
	}
}

void UVictoryBPFunctionLibrary::VictoryGetAllAxisAndActionMappingsForKey(FKey Key, TArray<FVictoryInput>& ActionBindings, TArray<FVictoryInputAxis>& AxisBindings)
{ 
	ActionBindings.Empty();
	AxisBindings.Empty();
	
		const UInputSettings* Settings = GetDefault<UInputSettings>();
	if(!Settings) return;
	
	const TArray<FInputActionKeyMapping>& Actions = Settings->ActionMappings;
	
	for(const FInputActionKeyMapping& Each : Actions)
	{
		if(Each.Key == Key)
		{
			ActionBindings.Add(FVictoryInput(Each));
		}
	}

	const TArray<FInputAxisKeyMapping>& Axi = Settings->AxisMappings;
	
	for(const FInputAxisKeyMapping& Each : Axi)
	{  
		if(Each.Key == Key)
		{
			AxisBindings.Add(FVictoryInputAxis(Each));
		}
	}
}
bool UVictoryBPFunctionLibrary::VictoryReBindAxisKey(FVictoryInputAxis Original, FVictoryInputAxis NewBinding)
{
	UInputSettings* Settings = const_cast<UInputSettings*>(GetDefault<UInputSettings>());
	if(!Settings) return false;

	TArray<FInputAxisKeyMapping>& Axi = Settings->AxisMappings;
	
	//~~~
	 
	bool Found = false;
	for(FInputAxisKeyMapping& Each : Axi)
	{
		//Search by original
		if(Each.AxisName.ToString() == Original.AxisName &&
			Each.Key == Original.Key
		){   
			//Update to new!
			UVictoryBPFunctionLibrary::UpdateAxisMapping(Each,NewBinding);
			Found = true;
			break;
		}  
	}
	 
	if(Found) 
	{
		//SAVES TO DISK
		const_cast<UInputSettings*>(Settings)->SaveKeyMappings();
		   
		//REBUILDS INPUT, creates modified config in Saved/Config/Windows/Input.ini
		for (TObjectIterator<UPlayerInput> It; It; ++It)
		{
			It->ForceRebuildingKeyMaps(true);
		}
	}
	return Found;
}

bool UVictoryBPFunctionLibrary::VictoryReBindActionKey(FVictoryInput Original, FVictoryInput NewBinding)
{
	UInputSettings* Settings = const_cast<UInputSettings*>(GetDefault<UInputSettings>());
	if(!Settings) return false;

	TArray<FInputActionKeyMapping>& Actions = Settings->ActionMappings;
	
	//~~~
	
	bool Found = false;
	for(FInputActionKeyMapping& Each : Actions)
	{
		//Search by original
		if(Each.ActionName.ToString() == Original.ActionName &&
			Each.Key == Original.Key
		){   
			//Update to new!
			UVictoryBPFunctionLibrary::UpdateActionMapping(Each,NewBinding);
			Found = true;
			break;
		}  
	}
	
	if(Found) 
	{
		//SAVES TO DISK
		const_cast<UInputSettings*>(Settings)->SaveKeyMappings();
		   
		//REBUILDS INPUT, creates modified config in Saved/Config/Windows/Input.ini
		for (TObjectIterator<UPlayerInput> It; It; ++It)
		{
			It->ForceRebuildingKeyMaps(true);
		}
	}
	return Found;
}

void UVictoryBPFunctionLibrary::GetAllWidgetsOfClass(UObject* WorldContextObject, TSubclassOf<UUserWidget> WidgetClass, TArray<UUserWidget*>& FoundWidgets,bool TopLevelOnly)
{
	//Prevent possibility of an ever-growing array if user uses this in a loop
	FoundWidgets.Empty();
	//~~~~~~~~~~~~
	 
	if(!WidgetClass) return;
	if(!WorldContextObject) return;
	 
	UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return;
	//~~~~~~~~~~~
	
	for(TObjectIterator<UUserWidget> Itr; Itr; ++Itr)
	{
		if(Itr->GetWorld() != World) continue;
		//~~~~~~~~~~~~~~~~~~~~~
		
		if( ! Itr->IsA(WidgetClass)) continue;
		//~~~~~~~~~~~~~~~~~~~
		 
		//Top Level?
		if(TopLevelOnly)
		{
			//only add top level widgets
			if(Itr->IsInViewport())			
			{
				FoundWidgets.Add(*Itr);
			}
		}
		else
		{
			//add all internal widgets
			FoundWidgets.Add(*Itr);
		}
	}
} 
void UVictoryBPFunctionLibrary::RemoveAllWidgetsOfClass(UObject* WorldContextObject, TSubclassOf<UUserWidget> WidgetClass)
{
	if(!WidgetClass) return;
	if(!WorldContextObject) return;
	 
	UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return;
	//~~~~~~~~~~~
	 
	for(TObjectIterator<UUserWidget> Itr; Itr; ++Itr)
	{
		if(Itr->GetWorld() != World) continue;
		//~~~~~~~~~~~~~~~~~~~~~
		
		if( ! Itr->IsA(WidgetClass)) continue;
		//~~~~~~~~~~~~~~~~~~~
		 
		//only add top level widgets
		if(Itr->IsInViewport())			//IsInViewport in 4.6
		{
			Itr->RemoveFromViewport();
		}
	}
}

bool UVictoryBPFunctionLibrary::IsWidgetOfClassInViewport(UObject* WorldContextObject, TSubclassOf<UUserWidget> WidgetClass)
{ 
	if(!WidgetClass) return false;
	if(!WorldContextObject) return false;
	 
	UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return false;
	//~~~~~~~~~~~
	  
	for(TObjectIterator<UUserWidget> Itr; Itr; ++Itr)
	{
		if(Itr->GetWorld() != World) continue;
		//~~~~~~~~~~~~~~~~~~~~~
		
		if( ! Itr->IsA(WidgetClass)) continue;
		//~~~~~~~~~~~~~~~~~~~
		    
		if(Itr->GetIsVisible())			//IsInViewport in 4.6
		{
			return true;
		}
	}
	
	return false;
}
 
void UVictoryBPFunctionLibrary::ServerTravel(UObject* WorldContextObject, FString MapName,bool bNotifyPlayers)
{ 
	if(!WorldContextObject) return;
	 
	UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return;
	//~~~~~~~~~~~
	 
	World->ServerTravel(MapName,false,bNotifyPlayers); //abs //notify players
}
APlayerStart* UVictoryBPFunctionLibrary::GetPlayerStart(UObject* WorldContextObject,FString PlayerStartName)
{
	if(!WorldContextObject) return nullptr;
	 
	UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return nullptr;
	//~~~~~~~~~~~
	
	for(TActorIterator<APlayerStart> Itr(World); Itr; ++Itr)
	{
		if(Itr->GetName() == PlayerStartName)
		{
			return *Itr;
		}
	}
	return nullptr;
}

bool UVictoryBPFunctionLibrary::VictorySoundVolumeChange(USoundClass* SoundClassObject, float NewVolume)
 {
	if(!SoundClassObject) 
	{
		return false;
	}
	
	SoundClassObject->Properties.Volume = NewVolume;
	return true; 
	   
	 /*
	FAudioDevice* Device = GEngine->GetAudioDevice();
	if (!Device || !SoundClassObject)
	{
		return false;
	}
	    
	bool bFound = Device->SoundClasses.Contains(SoundClassObject);
	if(bFound)
	{ 
		Device->SetClassVolume(SoundClassObject, NewVolume);
		return true;
	}
	return false;
	*/
	
	/*
		bool SetBaseSoundMix( class USoundMix* SoundMix );
	
	*/
 }
float UVictoryBPFunctionLibrary::VictoryGetSoundVolume(USoundClass* SoundClassObject)
{ 
	if (!SoundClassObject)
	{
		return -1;
	}
	return SoundClassObject->Properties.Volume;
	/*
	FAudioDevice* Device = GEngine->GetMainAudioDevice();
	if (!Device || !SoundClassObject)
	{
		return -1;
	}
	    
	FSoundClassProperties* Props = Device->GetSoundClassCurrentProperties(SoundClassObject);
	if(!Props) return -1;
	return Props->Volume;
	*/
}
 
void UVictoryBPFunctionLibrary::VictoryIntPlusEquals(UPARAM(ref) int32& Int, int32 Add, int32& IntOut)
{  
	Int += Add;
	IntOut = Int; 
} 
void UVictoryBPFunctionLibrary::VictoryIntMinusEquals(UPARAM(ref) int32& Int, int32 Sub, int32& IntOut)
{ 
	Int -= Sub;
	IntOut = Int; 
}

void UVictoryBPFunctionLibrary::VictoryFloatPlusEquals(UPARAM(ref) float& Float, float Add, float& FloatOut)
{
	Float += Add;
	FloatOut = Float;
}
void UVictoryBPFunctionLibrary::VictoryFloatMinusEquals(UPARAM(ref) float& Float, float Sub, float& FloatOut)
{
	Float -= Sub;
	FloatOut = Float; 
}

void UVictoryBPFunctionLibrary::VictorySortIntArray(UPARAM(ref) TArray<int32>& IntArray, TArray<int32>& IntArrayRef)
{
	IntArray.Sort();
	IntArrayRef = IntArray;
}
void UVictoryBPFunctionLibrary::VictorySortFloatArray(UPARAM(ref) TArray<float>& FloatArray, TArray<float>& FloatArrayRef)
{
	FloatArray.Sort();
	FloatArrayRef = FloatArray;
}
   
//String Back To Type
void UVictoryBPFunctionLibrary::Conversions__StringToVector(const FString& String, FVector& ConvertedVector, bool& IsValid)
{   
	IsValid = ConvertedVector.InitFromString( String );
}
void UVictoryBPFunctionLibrary::Conversions__StringToRotator(const FString& String, FRotator& ConvertedRotator, bool& IsValid)
{
	IsValid = ConvertedRotator.InitFromString( String );
} 
void UVictoryBPFunctionLibrary::Conversions__StringToColor(const FString& String, FLinearColor& ConvertedColor, bool& IsValid)
{ 
	IsValid = ConvertedColor.InitFromString( String );
}
void UVictoryBPFunctionLibrary::Conversions__ColorToString(const FLinearColor& Color, FString& ColorAsString)
{
	ColorAsString = Color.ToString();
}
 
//String Back To Type


//! not working yet, always getting 255
/*
uint8 UVictoryBPFunctionLibrary::Victory_ConvertStringToByte(UEnum* Enum, FString String)
{  
	if( !Enum ) return 255;
	  
	return Enum->GetIndexByName(*String);
}
*/

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

bool UVictoryBPFunctionLibrary::VictoryGetCustomConfigVar_Bool(FString SectionName,FString VariableName, bool& IsValid)
{
	if(!GConfig) return false;
	//~~~~~~~~~~~
 
	bool Value;
	IsValid = GConfig->GetBool(
		*SectionName,
		*VariableName,
		Value,
		GGameIni
	);
	return Value;
} 
int32 UVictoryBPFunctionLibrary::VictoryGetCustomConfigVar_Int(FString SectionName,FString VariableName, bool& IsValid)
{
	if(!GConfig) return 0;
	//~~~~~~~~~~~
 
	int32 Value;
	IsValid = GConfig->GetInt(
		*SectionName,
		*VariableName,
		Value,
		GGameIni
	);
	return Value;
}
float UVictoryBPFunctionLibrary::VictoryGetCustomConfigVar_Float(FString SectionName,FString VariableName, bool& IsValid)
{
	if(!GConfig) return 0;
	//~~~~~~~~~~~
 
	float Value;
	IsValid = GConfig->GetFloat(
		*SectionName,
		*VariableName,
		Value,
		GGameIni
	);
	return Value;
}
FVector UVictoryBPFunctionLibrary::VictoryGetCustomConfigVar_Vector(FString SectionName,FString VariableName, bool& IsValid)
{
	if(!GConfig) return FVector::ZeroVector;
	//~~~~~~~~~~~
 
	FVector Value;
	IsValid = GConfig->GetVector(
		*SectionName,
		*VariableName,
		Value,
		GGameIni
	);
	return Value;
}
FRotator UVictoryBPFunctionLibrary::VictoryGetCustomConfigVar_Rotator(FString SectionName,FString VariableName, bool& IsValid)
{
	if(!GConfig) return FRotator::ZeroRotator;
	//~~~~~~~~~~~
 
	FRotator Value;
	IsValid = GConfig->GetRotator(
		*SectionName,
		*VariableName,
		Value,
		GGameIni
	);
	return Value;
}
FLinearColor UVictoryBPFunctionLibrary::VictoryGetCustomConfigVar_Color(FString SectionName,FString VariableName, bool& IsValid)
{
	if(!GConfig) return FColor::Black;
	//~~~~~~~~~~~
  
	FColor Value;
	IsValid = GConfig->GetColor(
		*SectionName,
		*VariableName,
		Value,
		GGameIni
	);
	return FLinearColor(Value);
}
FString UVictoryBPFunctionLibrary::VictoryGetCustomConfigVar_String(FString SectionName,FString VariableName, bool& IsValid)
{
	if(!GConfig) return "";
	//~~~~~~~~~~~
 
	FString Value;
	IsValid = GConfig->GetString(
		*SectionName,
		*VariableName,
		Value,
		GGameIni
	);
	return Value;
}

FVector2D UVictoryBPFunctionLibrary::VictoryGetCustomConfigVar_Vector2D(FString SectionName, FString VariableName, bool& IsValid)
{
	if(!GConfig) return FVector2D::ZeroVector;
	//~~~~~~~~~~~
 
	FVector Value;
	IsValid = GConfig->GetVector(
		*SectionName,
		*VariableName,
		Value,
		GGameIni
	);
	return FVector2D(Value.X,Value.Y);
} 
  
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void UVictoryBPFunctionLibrary::VictorySetCustomConfigVar_Vector2D(FString SectionName, FString VariableName, FVector2D Value)
{
	if(!GConfig) return;
	//~~~~~~~~~~~
	
	GConfig->SetVector(
		*SectionName,
		*VariableName,
		FVector(Value.X,Value.Y,0),
		GGameIni
	);
}

void UVictoryBPFunctionLibrary::VictorySetCustomConfigVar_Bool(FString SectionName,FString VariableName, bool Value)
{
	if(!GConfig) return;
	//~~~~~~~~~~~
 
	GConfig->SetBool(
		*SectionName,
		*VariableName,
		Value,
		GGameIni
	);
}
void UVictoryBPFunctionLibrary::VictorySetCustomConfigVar_Int(FString SectionName,FString VariableName, int32 Value)
{
	if(!GConfig) return;
	//~~~~~~~~~~~
 
	GConfig->SetInt(
		*SectionName,
		*VariableName,
		Value,
		GGameIni
	);
}
void UVictoryBPFunctionLibrary::VictorySetCustomConfigVar_Float(FString SectionName,FString VariableName, float Value)
{
	if(!GConfig) return;
	//~~~~~~~~~~~
	
	GConfig->SetFloat(
		*SectionName,
		*VariableName,
		Value,
		GGameIni
	);
}
void UVictoryBPFunctionLibrary::VictorySetCustomConfigVar_Vector(FString SectionName,FString VariableName, FVector Value)
{
	if(!GConfig) return;
	//~~~~~~~~~~~
	
	GConfig->SetVector(
		*SectionName,
		*VariableName,
		Value,
		GGameIni
	);
}
void UVictoryBPFunctionLibrary::VictorySetCustomConfigVar_Rotator(FString SectionName,FString VariableName, FRotator Value)
{
	if(!GConfig) return;
	//~~~~~~~~~~~
	
	GConfig->SetRotator(
		*SectionName,
		*VariableName,
		Value,
		GGameIni
	);
}
void UVictoryBPFunctionLibrary::VictorySetCustomConfigVar_Color(FString SectionName,FString VariableName, FLinearColor Value)
{
	if(!GConfig) return;
	//~~~~~~~~~~~
	 
	GConfig->SetColor(
		*SectionName,
		*VariableName,
		Value.ToFColor(true),
		GGameIni
	);
}
void UVictoryBPFunctionLibrary::VictorySetCustomConfigVar_String(FString SectionName,FString VariableName, FString Value)
{
	if(!GConfig) return;
	//~~~~~~~~~~~
 
	GConfig->SetString(
		*SectionName,
		*VariableName,
		*Value,
		GGameIni
	);
}






//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

FName UVictoryBPFunctionLibrary::GetHeadMountedDisplayDeviceType()
{
	/*
		4.19 
		The IHeadMountedDisplay::GetHMDDeviceType() method has been removed as it was not extensible 
		enough. Code that needs know which XR plugin is currently active should use 
		IXRTrackingSystem::GetSystemName() instead.
	*/
	if(!GEngine) return "None";
	 
	if (GEngine->XRSystem.IsValid() && GEngine->XRSystem->GetHMDDevice())
	{  
		//Actively connected?
		if(!GEngine->XRSystem->GetHMDDevice()->IsHMDConnected())
		{  
			return "None";
		} 
		
		//!See IIdentifiableXRDevice.h
		return GEngine->XRSystem->GetSystemName();
	}
	  
	return "None";
}

















UObject* UVictoryBPFunctionLibrary::LoadObjectFromAssetPath(TSubclassOf<UObject> ObjectClass,FName Path,bool& IsValid)
{
	IsValid = false;
	
	if(Path == NAME_None) return NULL;
	//~~~~~~~~~~~~~~~~~~~~~
	
	UObject* LoadedObj = StaticLoadObject( ObjectClass, NULL,*Path.ToString());
	 
	IsValid = LoadedObj != nullptr;
	
	return LoadedObj;
}
FName UVictoryBPFunctionLibrary::GetObjectPath(UObject* Obj)
{
	if(!Obj) return NAME_None;
	if(!Obj->IsValidLowLevel()) return NAME_None;
	//~~~~~~~~~~~~~~~~~~~~~~~~~
	
	FStringAssetReference ThePath = FStringAssetReference(Obj);
		
	if(!ThePath.IsValid()) return "";
	
	//The Class FString Name For This Object
	FString str=Obj->GetClass()->GetDescription();
	
	//Remove spaces in Material Instance Constant class description!
	str = str.Replace(TEXT(" "),TEXT(""));
	
	str += "'";
	str += ThePath.ToString();
	str += "'";
	
	return FName(*str);
}
int32 UVictoryBPFunctionLibrary::GetPlayerUniqueNetID()
{
	TObjectIterator<APlayerController> ThePC;
	if(!ThePC) 					return -1; 
	if(!ThePC->PlayerState) return -1;
	//~~~~~~~~~~~~~~~~~~~
	
	return ThePC->PlayerState->PlayerId;
}
UObject* UVictoryBPFunctionLibrary::CreateObject(UObject* WorldContextObject,UClass* TheObjectClass)
{
	//See deprecation warning
	//	Deprecation warning makes it no longer appear in context menu as a new node to add
	return nullptr;
	/*
	if(!TheObjectClass) return NULL;
	//~~~~~~~~~~~~~~~~~
	
	//using a context object to get the world!
    UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return NULL;
	//~~~~~~~~~~~
	    
	//Need to submit pull request for custom name and custom class both
	return NewObject<UObject>(World,TheObjectClass);
	*/
}
UPrimitiveComponent* UVictoryBPFunctionLibrary::CreatePrimitiveComponent(
	UObject* WorldContextObject, 
	TSubclassOf<UPrimitiveComponent> CompClass, 
	FName Name,
	FVector Location, 
	FRotator Rotation
){
	if(!CompClass) return NULL;
	//~~~~~~~~~~~~~~~~~
	
	//using a context object to get the world!
    UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return NULL;
	//~~~~~~~~~~~
	 
	UPrimitiveComponent* NewComp = NewObject<UPrimitiveComponent>(World, Name);
	if(!NewComp) return NULL;
	//~~~~~~~~~~~~~
	 
	NewComp->SetWorldLocation(Location);
	NewComp->SetWorldRotation(Rotation);
	NewComp->RegisterComponentWithWorld(World);
	
	return NewComp;
}

AActor* UVictoryBPFunctionLibrary::SpawnActorIntoLevel(UObject* WorldContextObject, TSubclassOf<AActor> ActorClass, FName Level, FVector Location, FRotator Rotation,bool SpawnEvenIfColliding)
{
	if(!ActorClass) return NULL;
	//~~~~~~~~~~~~~~~~~
	
	//using a context object to get the world!
    UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return NULL;
	//~~~~~~~~~~~
	
	FActorSpawnParameters SpawnParameters;
	if (SpawnEvenIfColliding)
	{
		SpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
	}
	
	SpawnParameters.bDeferConstruction = false;
	 
	 
	//Get Level from Name!
	ULevel* FoundLevel = NULL;
	
	for(const ULevelStreaming* EachLevel : World->StreamingLevels)
	{
		if( ! EachLevel) continue;
		//~~~~~~~~~~~~~~~~
	
		ULevel* LevelPtr = EachLevel->GetLoadedLevel();
		
		//Valid?
		if(!LevelPtr) continue;
		
		if(EachLevel->GetWorldAssetPackageFName() == Level)
		{
			FoundLevel = LevelPtr; 
			break;
		} 
	}
	//~~~~~~~~~~~~~~~~~~~~~
	if(FoundLevel)
	{
		SpawnParameters.OverrideLevel = FoundLevel;
	}
	//~~~~~~~~~~~~~~~~~~~~~
	
	return World->SpawnActor( ActorClass, &Location, &Rotation, SpawnParameters);
	
}
void UVictoryBPFunctionLibrary::GetNamesOfLoadedLevels(UObject* WorldContextObject, TArray<FString>& NamesOfLoadedLevels)
{
	
	//using a context object to get the world!
    UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return;
	//~~~~~~~~~~~
	
	NamesOfLoadedLevels.Empty();
	
	//Get Level from Name!
	ULevel* FoundLevel = NULL;
	
	for(const ULevelStreaming* EachLevel : World->StreamingLevels)
	{
		if( ! EachLevel) continue;
		//~~~~~~~~~~~~~~~~
	
		ULevel* LevelPtr = EachLevel->GetLoadedLevel();
		
		//Valid?
		if(!LevelPtr) continue;
		
		//Is This Level Visible?
		if(!LevelPtr->bIsVisible) continue;
		//~~~~~~~~~~~~~~~~~~~
		 
		NamesOfLoadedLevels.Add(EachLevel->GetWorldAssetPackageFName().ToString());
	}
}
		
	
void UVictoryBPFunctionLibrary::Loops_ResetBPRunawayCounter()
{
	//Reset Runaway loop counter (use carefully)
	GInitRunaway();
}

void UVictoryBPFunctionLibrary::GraphicsSettings__SetFrameRateToBeUnbound()
{
	if(!GEngine) return;
	//~~~~~~~~~
	
	GEngine->bSmoothFrameRate = 0; 
}
void UVictoryBPFunctionLibrary::GraphicsSettings__SetFrameRateCap(float NewValue)
{
	if(!GEngine) return;
	//~~~~~~~~~
	   
	GEngine->bSmoothFrameRate = 1; 
	GEngine->SmoothedFrameRateRange = FFloatRange(10,NewValue);
}

FVector2D UVictoryBPFunctionLibrary::ProjectWorldToScreenPosition(const FVector& WorldLocation)
{
	TObjectIterator<APlayerController> ThePC;
	if(!ThePC) return FVector2D::ZeroVector;
	
	ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(ThePC->Player);
	if (LocalPlayer != NULL && LocalPlayer->ViewportClient != NULL && LocalPlayer->ViewportClient->Viewport != NULL)
	{
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		// Create a view family for the game viewport
		FSceneViewFamilyContext ViewFamily( FSceneViewFamily::ConstructionValues(
			LocalPlayer->ViewportClient->Viewport,
			ThePC->GetWorld()->Scene,
			LocalPlayer->ViewportClient->EngineShowFlags )
			.SetRealtimeUpdate(true) );

		// Calculate a view where the player is to update the streaming from the players start location
		FVector ViewLocation;
		FRotator ViewRotation;
		FSceneView* SceneView = LocalPlayer->CalcSceneView( &ViewFamily, /*out*/ ViewLocation, /*out*/ ViewRotation, LocalPlayer->ViewportClient->Viewport );

		//Valid Scene View?
		if (SceneView) 
		{
			//Return
			FVector2D ScreenLocation;
			SceneView->WorldToPixel(WorldLocation,ScreenLocation );
			return ScreenLocation;
		}
	} 
	
	return FVector2D::ZeroVector;
}



bool UVictoryBPFunctionLibrary::GetStaticMeshVertexLocations(UStaticMeshComponent* Comp, TArray<FVector>& VertexPositions)
{
	VertexPositions.Empty();
	 
	if(!Comp) 						
	{
		return false;
	}
	
	if(!Comp->IsValidLowLevel()) 
	{
		return false;
	}
	//~~~~~~~~~~~~~~~~~~~~~~~
	
	//Component Transform
	FTransform RV_Transform = Comp->GetComponentTransform(); 
	
	//Body Setup valid?
	UBodySetup* BodySetup = Comp->GetBodySetup();
	
	if(!BodySetup || !BodySetup->IsValidLowLevel())
	{
		return false;
	}  
	
	for(PxTriangleMesh* EachTriMesh : BodySetup->TriMeshes)
	{
		if (!EachTriMesh)
		{
			return false;
		}
		//~~~~~~~~~~~~~~~~

		//Number of vertices
		PxU32 VertexCount = EachTriMesh->getNbVertices();

		//Vertex array
		const PxVec3* Vertices = EachTriMesh->getVertices();

		//For each vertex, transform the position to match the component Transform 
		for (PxU32 v = 0; v < VertexCount; v++)
		{
			VertexPositions.Add(RV_Transform.TransformPosition(P2UVector(Vertices[v])));
		}
	}
	return true;
	
	/*
	//See this wiki for more info on getting triangles
	//		https://wiki.unrealengine.com/Accessing_mesh_triangles_and_vertex_positions_in_build
	*/
} 


void UVictoryBPFunctionLibrary::AddToActorRotation(AActor* TheActor, FRotator AddRot)
{
	if (!TheActor) return;
	//~~~~~~~~~~~

	FTransform TheTransform = TheActor->GetTransform();
	TheTransform.ConcatenateRotation(AddRot.Quaternion());
	TheTransform.NormalizeRotation();
	TheActor->SetActorTransform(TheTransform);
}




void UVictoryBPFunctionLibrary::DrawCircle(
	UObject* WorldContextObject,
	FVector Center, 
	float Radius, 
	int32 NumPoints,
	float Thickness,
	FLinearColor LineColor,
	FVector YAxis,
	FVector ZAxis,
	float Duration,
	bool PersistentLines
){ 
	
	
	if(!WorldContextObject) return ;
	
	//using a context object to get the world!
    UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return;
	//~~~~~~~~~~~
	 
	/* //FOR PULL REQUEST TO EPIC
	FMatrix TM;
	TM.SetOrigin(Center);
	TM.SetAxis(0, FVector(1,0,0));
	TM.SetAxis(1, YAxis);
	TM.SetAxis(2, ZAxis);
	
	DrawDebugCircle(
		World, 
		TM, 
		Radius, NumPoints, 
		FColor::Red, 
		false, 
		-1.f, 
		0
	);
*/
	 
	// Need at least 4 segments  
	NumPoints = FMath::Max(NumPoints, 4);
	const float AngleStep = 2.f * PI / float(NumPoints);

	float Angle = 0.f;
	for(int32 v = 0; v < NumPoints; v++)
	{  
		const FVector Vertex1 = Center + Radius * (YAxis * FMath::Cos(Angle) + ZAxis * FMath::Sin(Angle));
		Angle += AngleStep;
		const FVector Vertex2 = Center + Radius * (YAxis * FMath::Cos(Angle) + ZAxis * FMath::Sin(Angle));
		   
		DrawDebugLine(
			World, 
			Vertex1, 
			Vertex2,
			LineColor.ToFColor(true),  
			PersistentLines, 
			Duration,
			0, 				//depth  
			Thickness          
		);
	}
}


bool UVictoryBPFunctionLibrary::LoadStringArrayFromFile(TArray<FString>& StringArray, int32& ArraySize, FString FullFilePath, bool ExcludeEmptyLines)
{
	ArraySize = 0;
	
	if(FullFilePath == "" || FullFilePath == " ") return false;
	
	//Empty any previous contents!
	StringArray.Empty();
	
	TArray<FString> FileArray;
	 
	if( ! FFileHelper::LoadANSITextFileToStrings(*FullFilePath, NULL, FileArray))
	{
		return false;
	}

	if(ExcludeEmptyLines)
	{
		for(const FString& Each : FileArray )
		{
			if(Each == "") continue;
			//~~~~~~~~~~~~~
			
			//check for any non whitespace
			bool FoundNonWhiteSpace = false;
			for(int32 v = 0; v < Each.Len(); v++)
			{
				if(Each[v] != ' ' && Each[v] != '\n')
				{
					FoundNonWhiteSpace = true;
					break;
				}
				//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			}
			
			if(FoundNonWhiteSpace)
			{
				StringArray.Add(Each);
			}
		}
	}
	else
	{
		StringArray.Append(FileArray);
	}
	
	ArraySize = StringArray.Num();
	return true; 
}

 
AActor* UVictoryBPFunctionLibrary::GetClosestActorOfClassInRadiusOfLocation(
	UObject* WorldContextObject, 
	TSubclassOf<AActor> ActorClass, 
	FVector Center, 
	float Radius, 
	bool& IsValid
){ 
	IsValid = false;
	
	//using a context object to get the world!
    UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return NULL;
	//~~~~~~~~~~~
	
	AActor* ClosestActor 		= NULL;
	float MinDistanceSq 		= Radius*Radius;	//Max Radius
	
	for (TActorIterator<AActor> Itr(World, ActorClass); Itr; ++Itr)
	{
		const float DistanceSquared = FVector::DistSquared(Center, Itr->GetActorLocation());

		//Is this the closest possible actor within the max radius?
		if (DistanceSquared < MinDistanceSq)
		{
			ClosestActor = *Itr;					//New Output!
			MinDistanceSq = DistanceSquared;		//New Min!
		}
	}

	if (ClosestActor)
	{
		IsValid = true;
	}

	return ClosestActor;
} 

AActor* UVictoryBPFunctionLibrary::GetClosestActorOfClassInRadiusOfActor(
	UObject* WorldContextObject, 
	TSubclassOf<AActor> ActorClass, 
	AActor* ActorCenter, 
	float Radius, 
	bool& IsValid
){ 
	IsValid = false;
	  
	if(!ActorCenter)
	{
		return nullptr;
	}
	
	const FVector Center = ActorCenter->GetActorLocation();
	
	//using a context object to get the world!
    UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return NULL;
	//~~~~~~~~~~~
	
	AActor* ClosestActor 		= NULL;
	float MinDistanceSq 		= Radius*Radius;	//Max Radius
	
	for (TActorIterator<AActor> Itr(World, ActorClass); Itr; ++Itr)
	{
		//Skip ActorCenter!
		if(*Itr == ActorCenter) continue;
		//~~~~~~~~~~~~~~~~~
		
		const float DistanceSquared = FVector::DistSquared(Center, Itr->GetActorLocation());

		//Is this the closest possible actor within the max radius?
		if (DistanceSquared < MinDistanceSq)
		{
			ClosestActor = *Itr;					//New Output!
			MinDistanceSq = DistanceSquared;		//New Min!
		}
	}

	if (ClosestActor)
	{
		IsValid = true;
	}

	return ClosestActor;
}

void UVictoryBPFunctionLibrary::Selection_SelectionBox(UObject* WorldContextObject,TArray<AActor*>& SelectedActors, FVector2D AnchorPoint,FVector2D DraggedPoint,TSubclassOf<AActor> ClassFilter)
{
	if(!WorldContextObject) return ;
	
	
	//using a context object to get the world!
    UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return;
	//~~~~~~~~~~~
	
	SelectedActors.Empty();
	
	FBox2D Box;
	Box+=DraggedPoint;
	Box+=AnchorPoint;
	
	for(TActorIterator<AActor> Itr(World); Itr; ++Itr)
	{
		if(!Itr->IsA(ClassFilter)) continue;
		//~~~~~~~~~~~~~~~~~~
		
		if(Box.IsInside(UVictoryBPFunctionLibrary::ProjectWorldToScreenPosition(Itr->GetActorLocation())))
		{
			SelectedActors.Add(*Itr);
		}
	}
}

bool UVictoryBPFunctionLibrary::PlayerController_GetControllerID(APlayerController* ThePC, int32& ControllerID)
{
	if(!ThePC) return false;
	
	ULocalPlayer * LP = Cast<ULocalPlayer>(ThePC->Player);
    if(!LP) return false;
  
    ControllerID = LP->GetControllerId();
	
	return true;
}
	
bool UVictoryBPFunctionLibrary::PlayerState_GetPlayerID(APlayerController* ThePC, int32& PlayerID)
{
	if(!ThePC) return false;
	
	if(!ThePC->PlayerState) return false;
	
	PlayerID = ThePC->PlayerState->PlayerId;
	
	return true;
}

void UVictoryBPFunctionLibrary::Open_URL_In_Web_Browser(FString TheURL)
{
	FPlatformProcess::LaunchURL( *TheURL, nullptr, nullptr );
}

void UVictoryBPFunctionLibrary::OperatingSystem__GetCurrentPlatform(
	bool& Windows_, 		//some weird bug of making it all caps engine-side
	bool& Mac,
	bool& Linux, 
	bool& iOS, 
	bool& Android,
	bool& Android_ARM,
	bool& Android_Vulkan,
	bool& PS4,
	bool& XBoxOne,
	bool& HTML5,
	bool& Apple
){
	//#define's in UE4 source code
	Windows_ = 				PLATFORM_WINDOWS;
	Mac = 						PLATFORM_MAC;
	Linux = 					PLATFORM_LINUX;
	
	PS4 = 						PLATFORM_PS4;
	XBoxOne = 				PLATFORM_XBOXONE;
	
	iOS = 						PLATFORM_IOS;
	Android = 				PLATFORM_ANDROID;
	Android_ARM  	=		PLATFORM_ANDROID_ARM;
	Android_Vulkan	= 		PLATFORM_ANDROID_VULKAN;
	HTML5 = 					PLATFORM_HTML5;
	 
	Apple =	 			PLATFORM_APPLE;
}

FString UVictoryBPFunctionLibrary::RealWorldTime__GetCurrentOSTime( 
	int32& MilliSeconds,
	int32& Seconds, 
	int32& Minutes, 
	int32& Hours12,
	int32& Hours24,
	int32& Day,
	int32& Month,
	int32& Year
){
	const FDateTime Now = FDateTime::Now();
	
	MilliSeconds = 		Now.GetMillisecond( );
	Seconds = 			Now.GetSecond( );
	Minutes = 				Now.GetMinute( );
	Hours12 = 			Now.GetHour12( );
	Hours24 = 			Now.GetHour( ); //24
	Day = 					Now.GetDay( );
	Month = 				Now.GetMonth( );
	Year = 				Now.GetYear( );
	 
	//MS are not included in FDateTime::ToString(), so adding it
	//The Parse function accepts if MS are present.
	FString NowWithMS = Now.ToString();
	NowWithMS += "." + FString::FromInt(MilliSeconds);
	return NowWithMS;
}

void UVictoryBPFunctionLibrary::RealWorldTime__GetTimePassedSincePreviousTime(
		const FString& PreviousTime,
		float& Milliseconds,
		float& Seconds,
		float& Minutes,
		float& Hours
){
	FDateTime ParsedDateTime;
	FDateTime::Parse(PreviousTime,ParsedDateTime);
	const FTimespan TimeDiff = FDateTime::Now() - ParsedDateTime;
	
	Milliseconds 	= TimeDiff.GetTotalMilliseconds( );
	Seconds 		= TimeDiff.GetTotalSeconds( );
	Minutes 		= TimeDiff.GetTotalMinutes( );
	Hours 			= TimeDiff.GetTotalHours( );
}
	
void UVictoryBPFunctionLibrary::RealWorldTime__GetDifferenceBetweenTimes(
		const FString& PreviousTime1,
		const FString& PreviousTime2,
		float& Milliseconds,
		float& Seconds,
		float& Minutes,
		float& Hours
){
	FDateTime ParsedDateTime1;
	FDateTime::Parse(PreviousTime1,ParsedDateTime1);
	
	FDateTime ParsedDateTime2;
	FDateTime::Parse(PreviousTime2,ParsedDateTime2);
	
	FTimespan TimeDiff; 
	
	if(PreviousTime1 < PreviousTime2)
	{
		TimeDiff = ParsedDateTime2 - ParsedDateTime1;
	}
	else
	{
		TimeDiff = ParsedDateTime1 - ParsedDateTime2;
	}
	
	Milliseconds 	= TimeDiff.GetTotalMilliseconds( );
	Seconds 		= TimeDiff.GetTotalSeconds( );
	Minutes 		= TimeDiff.GetTotalMinutes( );
	Hours 			= TimeDiff.GetTotalHours( );
}



void UVictoryBPFunctionLibrary::MaxOfFloatArray(const TArray<float>& FloatArray, int32& IndexOfMaxValue, float& MaxValue)
{
	MaxValue = UVictoryBPFunctionLibrary::Max(FloatArray,&IndexOfMaxValue);
}

void UVictoryBPFunctionLibrary::MaxOfIntArray(const TArray<int32>& IntArray, int32& IndexOfMaxValue, int32& MaxValue)
{
	MaxValue = UVictoryBPFunctionLibrary::Max(IntArray,&IndexOfMaxValue);
}

void UVictoryBPFunctionLibrary::MinOfFloatArray(const TArray<float>& FloatArray, int32& IndexOfMinValue, float& MinValue)
{
	MinValue = UVictoryBPFunctionLibrary::Min(FloatArray,&IndexOfMinValue);
}

void UVictoryBPFunctionLibrary::MinOfIntArray(const TArray<int32>& IntArray, int32& IndexOfMinValue, int32& MinValue)
{
	MinValue = UVictoryBPFunctionLibrary::Min(IntArray,&IndexOfMinValue);
}



bool UVictoryBPFunctionLibrary::CharacterMovement__SetMaxMoveSpeed(ACharacter* TheCharacter, float NewMaxMoveSpeed)
{
	if(!TheCharacter)
	{
		return false;
	}
	if(!TheCharacter->GetCharacterMovement())
	{
		return false;
	}
	
	TheCharacter->GetCharacterMovement()->MaxWalkSpeed = NewMaxMoveSpeed;
	
	return true;
}
	


int32 UVictoryBPFunctionLibrary::Conversion__FloatToRoundedInteger(float IN_Float)
{
	return FGenericPlatformMath::RoundToInt(IN_Float);
}
 
FString UVictoryBPFunctionLibrary::Victory_SecondsToHoursMinutesSeconds(float Seconds, bool TrimZeroes)
{
	FString Str = FTimespan(0, 0, Seconds).ToString();
	
	if(TrimZeroes)
	{
		FString Left,Right;
		Str.Split(TEXT("."),&Left,&Right);
		Str = Left;
		Str.ReplaceInline(TEXT("00:00"), TEXT("00"));
		
		//Str Count!  
		int32 Count = CountOccurrancesOfSubString(Str,":");
		   
		//Remove Empty Hours
		if(Count >= 2)
		{
			Str.ReplaceInline(TEXT("00:"), TEXT(""));
		} 
	}
 
	return Str;
}

bool UVictoryBPFunctionLibrary::IsAlphaNumeric(const FString& String)
{
	std::string str = (TCHAR_TO_UTF8(*String));
	    
	for ( std::string::iterator it=str.begin(); it!=str.end(); ++it)
	{
		if(!isalnum(*it))
		{   
			return false;
		}
	}
	
	return true;
}

void UVictoryBPFunctionLibrary::Victory_GetStringFromOSClipboard(FString& FromClipboard)
{  
	FPlatformApplicationMisc::ClipboardPaste(FromClipboard);
} 
void UVictoryBPFunctionLibrary::Victory_SaveStringToOSClipboard(const FString& ToClipboard)
{
	FPlatformApplicationMisc::ClipboardCopy(*ToClipboard);
}
	

bool UVictoryBPFunctionLibrary::HasSubstring(const FString& SearchIn, const FString& Substring, ESearchCase::Type SearchCase, ESearchDir::Type SearchDir)
{
	return SearchIn.Contains(Substring, SearchCase, SearchDir);
}

FString UVictoryBPFunctionLibrary::String__CombineStrings(FString StringFirst, FString StringSecond, FString Separator, FString StringFirstLabel, FString StringSecondLabel)
{
	return StringFirstLabel + StringFirst + Separator + StringSecondLabel + StringSecond;
}
FString UVictoryBPFunctionLibrary::String__CombineStrings_Multi(FString A, FString B)
{  
	return A + " " + B;
}

bool UVictoryBPFunctionLibrary::OptionsMenu__GetDisplayAdapterScreenResolutions(TArray<int32>& Widths, TArray<int32>& Heights, TArray<int32>& RefreshRates,bool IncludeRefreshRates)
{
	//Clear any Previous
	Widths.Empty();
	Heights.Empty();
	RefreshRates.Empty();
	
	TArray<FString> Unique;	
	
	FScreenResolutionArray Resolutions;
	if(RHIGetAvailableResolutions(Resolutions, false))
	{
		for (const FScreenResolutionRHI& EachResolution : Resolutions)
		{
			FString Str = "";
			Str += FString::FromInt(EachResolution.Width);
			Str += FString::FromInt(EachResolution.Height);
			
			//Include Refresh Rates?
			if(IncludeRefreshRates)
			{
				Str += FString::FromInt(EachResolution.RefreshRate);
			}		
			
			if(Unique.Contains(Str))
			{
				//Skip! This is duplicate!
				continue;
			}
			else
			{
				//Add to Unique List!
				Unique.AddUnique(Str);
			}
			
			//Add to Actual Data Output!
			Widths.Add(			EachResolution.Width);
			Heights.Add(			EachResolution.Height);
			RefreshRates.Add(	EachResolution.RefreshRate);
		}

		return true;
	}
	return false;
}

void UVictoryBPFunctionLibrary::GetUserDisplayAdapterBrand(bool& IsAMD, bool& IsNvidia, bool& IsIntel, bool& IsUnknown, int32& UnknownId)
{   
	IsAMD 		= IsRHIDeviceAMD();
	IsNvidia 	= IsRHIDeviceNVIDIA();
	IsIntel 	= IsRHIDeviceIntel();
	
	IsUnknown = !IsAMD && !IsNvidia && !IsIntel;
	 
	if(IsUnknown)
	{
		UnknownId = GRHIVendorId;
	}
} 

//Make .h's for these two!
FRotator UVictoryBPFunctionLibrary::TransformVectorToActorSpaceAngle(AActor* Actor, const FVector& InVector)
{
	if(!Actor) return FRotator::ZeroRotator;
	return Actor->ActorToWorld().InverseTransformVector(InVector).Rotation();
}
FVector UVictoryBPFunctionLibrary::TransformVectorToActorSpace(AActor* Actor, const FVector& InVector)
{
	if(!Actor) return FVector::ZeroVector;
	return Actor->ActorToWorld().InverseTransformVector(InVector);
}

AStaticMeshActor* UVictoryBPFunctionLibrary::Clone__StaticMeshActor(UObject* WorldContextObject, bool&IsValid, AStaticMeshActor* ToClone, FVector LocationOffset,FRotator RotationOffset)
{
	IsValid = false;
	if(!ToClone) return NULL;
	if(!ToClone->IsValidLowLevel()) return NULL;
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~
	  
	if(!WorldContextObject) return NULL;
	
	//using a context object to get the world!
    UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return NULL;
	//~~~~~~~~~~~
	
	//For BPS
	UClass* SpawnClass = ToClone->GetClass();
	
	FActorSpawnParameters SpawnInfo;
	SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
	SpawnInfo.Owner 				= ToClone;
	SpawnInfo.Instigator				= NULL;
	SpawnInfo.bDeferConstruction 	= false;
	
	AStaticMeshActor* NewSMA = World->SpawnActor<AStaticMeshActor>(SpawnClass, ToClone->GetActorLocation() + FVector(0,0,512) ,ToClone->GetActorRotation(), SpawnInfo );
	
	if(!NewSMA) return NULL;
	
	//Copy Transform
	NewSMA->SetActorTransform(ToClone->GetTransform());
	
	//Mobility
	NewSMA->GetStaticMeshComponent()->SetMobility(EComponentMobility::Movable	);
	
	//copy static mesh
	NewSMA->GetStaticMeshComponent()->SetStaticMesh(ToClone->GetStaticMeshComponent()->GetStaticMesh());
	
	//~~~
	
	//copy materials
	TArray<UMaterialInterface*> Mats;
	ToClone->GetStaticMeshComponent()->GetUsedMaterials(Mats);
	
	const int32 Total = Mats.Num();
	for(int32 v = 0; v < Total; v++ )
	{
		NewSMA->GetStaticMeshComponent()->SetMaterial(v,Mats[v]);
	}
	
	//~~~
	
	//copy physics state
	if(ToClone->GetStaticMeshComponent()->IsSimulatingPhysics())
	{
		NewSMA->GetStaticMeshComponent()->SetSimulatePhysics(true);
	}
	
	//~~~
	
	//Add Location Offset
	const FVector SpawnLoc = ToClone->GetActorLocation() + LocationOffset;
	NewSMA->SetActorLocation(SpawnLoc);
	
	//Add Rotation offset
	FTransform TheTransform = NewSMA->GetTransform();
	TheTransform.ConcatenateRotation(RotationOffset.Quaternion());
	TheTransform.NormalizeRotation();
	
	//Set Transform
	NewSMA->SetActorTransform(TheTransform);
	
	IsValid = true;
	return NewSMA;
}

bool UVictoryBPFunctionLibrary::Actor__TeleportToActor(AActor* ActorToTeleport, AActor* DestinationActor)
{
	if(!ActorToTeleport) 							return false;
	if(!ActorToTeleport->IsValidLowLevel()) 	return false;
	if(!DestinationActor) 							return false;
	if(!DestinationActor->IsValidLowLevel()) 	return false;
	
	//Set Loc
	ActorToTeleport->SetActorLocation(DestinationActor->GetActorLocation());
	
	//Set Rot
	ActorToTeleport->SetActorRotation(DestinationActor->GetActorRotation());
	
	return true;
}

bool UVictoryBPFunctionLibrary::WorldType__InEditorWorld(UObject* WorldContextObject)
{
	if(!WorldContextObject) return false;
	
	//using a context object to get the world!
    UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return false;
	//~~~~~~~~~~~
	
    return (World->WorldType == EWorldType::Editor );
}

bool UVictoryBPFunctionLibrary::WorldType__InPIEWorld(UObject* WorldContextObject)
{
	if(!WorldContextObject) return false;
	
	//using a context object to get the world!
    UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return false;
	//~~~~~~~~~~~
	
    return (World->WorldType == EWorldType::PIE );
}
bool UVictoryBPFunctionLibrary::WorldType__InGameInstanceWorld(UObject* WorldContextObject)
{
	if(!WorldContextObject) return false;
	
	//using a context object to get the world!
    UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return false;
	//~~~~~~~~~~~
	
    return (World->WorldType == EWorldType::Game );
}
	


bool UVictoryBPFunctionLibrary::DoesMaterialHaveParameter(UMaterialInterface* Mat, FName Parameter)
{    
	if(!Mat || Parameter == NAME_None) 
	{
		return false;
	}
	
	//Lesson, always use the parent of a Material Instance Dynamic, 
	//	for some reason the dynamic version finds parameters that aren't actually valid.
	//		-Rama
	UMaterialInterface* Parent = Mat;
	UMaterialInstance* MatInst = Cast<UMaterialInstance>(Mat);
	if(MatInst)
	{
		Parent = MatInst->Parent;
	}
	
	if(!Parent) 
	{
		return false;
	} 
	
	float ScalarValue;
	if(Parent->GetScalarParameterValue(Parameter,ScalarValue))
	{
		return true;
	}
  
	FLinearColor VectValue;
	if(Parent->GetVectorParameterValue(Parameter,VectValue))
	{
		return true;
	}
 
	UTexture* T2DValue; 
	return Parent->GetTextureParameterValue(Parameter,T2DValue);
}

FString UVictoryBPFunctionLibrary::Accessor__GetNameAsString(const UObject* TheObject)
{
	if (!TheObject) return "";
	return TheObject->GetName();
}

FRotator UVictoryBPFunctionLibrary::Conversions__VectorToRotator(const FVector& TheVector)
{
	return TheVector.Rotation();
}
FVector UVictoryBPFunctionLibrary::Conversions__RotatorToVector(const FRotator& TheRotator)
{
	return TheRotator.Vector();
}
FRotator UVictoryBPFunctionLibrary::Character__GetControllerRotation(AActor * TheCharacter)
{
	ACharacter * AsCharacter = Cast<ACharacter>(TheCharacter);

	if (!AsCharacter) return FRotator::ZeroRotator;
	
	return AsCharacter->GetControlRotation();
}

//Draw From Socket on Character's Mesh
void UVictoryBPFunctionLibrary::Draw__Thick3DLineFromCharacterSocket(AActor* TheCharacter,  const FVector& EndPoint, FName Socket, FLinearColor LineColor, float Thickness, float Duration)
{
	ACharacter * AsCharacter = Cast<ACharacter>(TheCharacter);
	if (!AsCharacter) return;
	if (!AsCharacter->GetMesh()) return;
	//~~~~~~~~~~~~~~~~~~~~
	
	//Get World
	UWorld* TheWorld = AsCharacter->GetWorld();
	if (!TheWorld) return;
	//~~~~~~~~~~~~~~~~~
	
	const FVector SocketLocation = AsCharacter->GetMesh()->GetSocketLocation(Socket);
	DrawDebugLine(
		TheWorld, 
		SocketLocation, 
		EndPoint, 
		LineColor.ToFColor(true), 
		false, 
		Duration, 
		0, 
		Thickness
	);
	
}
/** Draw 3D Line of Chosen Thickness From Mesh Socket to Destination */
void UVictoryBPFunctionLibrary::Draw__Thick3DLineFromSocket(USkeletalMeshComponent* Mesh, const FVector& EndPoint, FName Socket, FLinearColor LineColor, float Thickness, float Duration)
{
	if (!Mesh) return;
	//~~~~~~~~~~~~~~
	
	//Get an actor to GetWorld() from
	TObjectIterator<APlayerController> Itr;
	if (!Itr) return;
	//~~~~~~~~~~~~
	
	//Get World
	UWorld* TheWorld = Itr->GetWorld();
	if (!TheWorld) return;
	//~~~~~~~~~~~~~~~~~
	
	const FVector SocketLocation = Mesh->GetSocketLocation(Socket);
	
	DrawDebugLine(
		TheWorld, 
		SocketLocation, 
		EndPoint, 
		LineColor.ToFColor(true),
		false, 
		Duration, 
		0, 
		Thickness
	);
}
/** Draw 3D Line of Chosen Thickness Between Two Actors */
void UVictoryBPFunctionLibrary::Draw__Thick3DLineBetweenActors(AActor * StartActor, AActor * EndActor, FLinearColor LineColor, float Thickness, float Duration)
{
	if (!StartActor) return;
	if (!EndActor) return;
	//~~~~~~~~~~~~~~~~
	
	DrawDebugLine(
		StartActor->GetWorld(), 
		StartActor->GetActorLocation(), 
		EndActor->GetActorLocation(), 
		LineColor.ToFColor(true),
		false, 
		Duration, 
		0, 
		Thickness
	);
}
	
bool UVictoryBPFunctionLibrary::Animation__GetAimOffsets(AActor* AnimBPOwner, float& Pitch, float& Yaw)
{
	//Get Owning Character
	ACharacter * TheCharacter = Cast<ACharacter>(AnimBPOwner);
	
	if (!TheCharacter) return false;
	//~~~~~~~~~~~~~~~
	
	//Get Owning Controller Rotation
	const FRotator TheCtrlRotation = TheCharacter->GetControlRotation();
	
	const FVector RotationDir = TheCtrlRotation.Vector();
	
	//Inverse of ActorToWorld matrix is Actor to Local Space
		//so this takes the direction vector, the PC or AI rotation
		//and outputs what this dir is 
		//in local actor space &
		
		//local actor space is what we want for aiming offsets
		
	const FVector LocalDir = TheCharacter->ActorToWorld().InverseTransformVectorNoScale(RotationDir);
	const FRotator LocalRotation = LocalDir.Rotation();
		
	//Pass out Yaw and Pitch
	Yaw = LocalRotation.Yaw;
	Pitch = LocalRotation.Pitch;
	
	return true;
}
bool UVictoryBPFunctionLibrary::Animation__GetAimOffsetsFromRotation(AActor * AnimBPOwner, const FRotator & TheRotation, float & Pitch, float & Yaw)
{
	//Get Owning Character
	ACharacter * TheCharacter = Cast<ACharacter>(AnimBPOwner);
	
	if (!TheCharacter) return false;
	//~~~~~~~~~~~~~~~
	
	//using supplied rotation
	const FVector RotationDir = TheRotation.Vector();
	
	//Inverse of ActorToWorld matrix is Actor to Local Space
		//so this takes the direction vector, the PC or AI rotation
		//and outputs what this dir is 
		//in local actor space &
		
		//local actor space is what we want for aiming offsets
		
	const FVector LocalDir = TheCharacter->ActorToWorld().InverseTransformVectorNoScale(RotationDir);
	const FRotator LocalRotation = LocalDir.Rotation();
		
	//Pass out Yaw and Pitch
	Yaw = LocalRotation.Yaw;
	Pitch = LocalRotation.Pitch;
	
	return true;
}

void UVictoryBPFunctionLibrary::Visibility__GetRenderedActors(UObject* WorldContextObject, TArray<AActor*>& CurrentlyRenderedActors, float MinRecentTime)
{
	if(!WorldContextObject) return;
	 
	UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return;
	//~~~~~~~~~~~
	
	//Empty any previous entries
	CurrentlyRenderedActors.Empty();
	
	//Iterate Over Actors
	for ( TActorIterator<AActor> Itr(World); Itr; ++Itr )
	{
		if (World->GetTimeSeconds() - Itr->GetLastRenderTime() <= MinRecentTime)
		{
			CurrentlyRenderedActors.Add( * Itr);
		}
	} 
} 
void UVictoryBPFunctionLibrary::Visibility__GetNotRenderedActors(UObject* WorldContextObject, TArray<AActor*>& CurrentlyNotRenderedActors, float MinRecentTime)
{
	if(!WorldContextObject) return;
	 
	UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!World) return;
	//~~~~~~~~~~~
	
	//Empty any previous entries
	CurrentlyNotRenderedActors.Empty();
	
	//Iterate Over Actors
	for ( TActorIterator<AActor> Itr(World); Itr; ++Itr )
	{
		if (World->GetTimeSeconds() - Itr->GetLastRenderTime() > MinRecentTime)
		{
			CurrentlyNotRenderedActors.Add( * Itr);
		}
	}
}

void UVictoryBPFunctionLibrary::Rendering__FreezeGameRendering()
{
	FViewport::SetGameRenderingEnabled(false);
}
void UVictoryBPFunctionLibrary::Rendering__UnFreezeGameRendering()
{
	FViewport::SetGameRenderingEnabled(true);
}
	
bool UVictoryBPFunctionLibrary::ClientWindow__GameWindowIsForeGroundInOS()
{   
	return FPlatformApplicationMisc::IsThisApplicationForeground();
	/*
	//Iterate Over Actors
	UWorld* TheWorld = NULL;
	for ( TObjectIterator<AActor> Itr; Itr; ++Itr )
	{
		TheWorld = Itr->GetWorld();
		if (TheWorld) break;
		//~~~~~~~~~~~~~~~~~~~~~~~
	}
	//Get Player
	ULocalPlayer* VictoryPlayer = 
            TheWorld->GetFirstLocalPlayerFromController(); 

	if (!VictoryPlayer) return false;
	//~~~~~~~~~~~~~~~~~~~~
	
	//get view port ptr
	UGameViewportClient * VictoryViewportClient = 
		Cast < UGameViewportClient > (VictoryPlayer->ViewportClient);
		
	if (!VictoryViewportClient) return false;
	//~~~~~~~~~~~~~~~~~~~~
	 
	FViewport * VictoryViewport = VictoryViewportClient->Viewport;
	
	if (!VictoryViewport) return false;
	//~~~~~~~~~~~~~~~~~~~~
	
    return VictoryViewport->IsForegroundWindow();
	*/
}
bool UVictoryBPFunctionLibrary::FileIO__SaveStringTextToFile(
	FString SaveDirectory, 
	FString JoyfulFileName, 
	FString SaveText,
	bool AllowOverWriting
){
	if(!FPlatformFileManager::Get().GetPlatformFile().CreateDirectoryTree(*SaveDirectory))
	{
		//Could not make the specified directory
		return false;
		//~~~~~~~~~~~~~~~~~~~~~~
	}
	
	//get complete file path
	SaveDirectory += "\\";
	SaveDirectory += JoyfulFileName;
	
	//No over-writing?
	if (!AllowOverWriting)
	{
		//Check if file exists already
		if (FPlatformFileManager::Get().GetPlatformFile().FileExists( * SaveDirectory))
		{
			//no overwriting
			return false;
		}
	}
	
	return FFileHelper::SaveStringToFile(SaveText, * SaveDirectory);
}
bool UVictoryBPFunctionLibrary::FileIO__SaveStringArrayToFile(FString SaveDirectory, FString JoyfulFileName, TArray<FString> SaveText, bool AllowOverWriting)  
{
	//Dir Exists?
	if ( !VCreateDirectory(SaveDirectory))
	{
		//Could not make the specified directory
		return false;
		//~~~~~~~~~~~~~~~~~~~~~~
	}
	
	//get complete file path
	SaveDirectory += "\\";
	SaveDirectory += JoyfulFileName;
	
	//No over-writing?
	if (!AllowOverWriting)
	{
		//Check if file exists already
		if (FPlatformFileManager::Get().GetPlatformFile().FileExists( * SaveDirectory))
		{
			//no overwriting
			return false;
		}
	}
	 
	FString FinalStr = "";
	for(FString& Each : SaveText)
	{
		FinalStr += Each;
		FinalStr += LINE_TERMINATOR;
	}
	


	return FFileHelper::SaveStringToFile(FinalStr, * SaveDirectory);
	
}
float UVictoryBPFunctionLibrary::Calcs__ClosestPointToSourcePoint(const FVector & Source, const TArray<FVector>& OtherPoints, FVector& ClosestPoint)
{
	float CurDist = 0;
	float ClosestDistance = -1;
	int32 ClosestVibe = 0;
	ClosestPoint = FVector::ZeroVector;
	
	if (OtherPoints.Num() <= 0) return ClosestDistance;
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	
	for (int32 Itr = 0; Itr < OtherPoints.Num(); Itr++)
	{
		if (Source == OtherPoints[Itr]) continue;
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		//Dist
		CurDist = FVector::Dist(Source, OtherPoints[Itr]);
		
		//Min
		if (ClosestDistance < 0 || ClosestDistance >= CurDist)
		{
			ClosestVibe = Itr;
			ClosestDistance = CurDist;
		}
	}
	
	//Out
	ClosestPoint = OtherPoints[ClosestVibe];
	return ClosestDistance;
}

bool UVictoryBPFunctionLibrary::Data__GetCharacterBoneLocations(AActor * TheCharacter, TArray<FVector>& BoneLocations)
{
	ACharacter * Source = Cast<ACharacter>(TheCharacter);
	if (!Source) return false;
	
	if (!Source->GetMesh()) return false;
	//~~~~~~~~~~~~~~~~~~~~~~~~~
	TArray<FName> BoneNames;
	
	BoneLocations.Empty();
	
	
	//Get Bone Names
	Source->GetMesh()->GetBoneNames(BoneNames);
	
	//Get Bone Locations
	for (int32 Itr = 0; Itr < BoneNames.Num(); Itr++ )
	{
		BoneLocations.Add(Source->GetMesh()->GetBoneLocation(BoneNames[Itr]));
	}
	
	return true;
}

USkeletalMeshComponent* UVictoryBPFunctionLibrary::Accessor__GetCharacterSkeletalMesh(AActor * TheCharacter, bool& IsValid)
{
	IsValid = false;
	
	ACharacter * AsCharacter = Cast<ACharacter>(TheCharacter);
	if (!AsCharacter) return NULL;
	//~~~~~~~~~~~~~~~~~
	 
	//Is Valid?
	if (AsCharacter->GetMesh())
		if (AsCharacter->GetMesh()->IsValidLowLevel() ) 
			IsValid = true;
			
	return AsCharacter->GetMesh();
}

bool UVictoryBPFunctionLibrary::TraceData__GetTraceDataFromCharacterSocket(
	FVector & TraceStart, //out
	FVector & TraceEnd,	//out
	AActor * TheCharacter,
	const FRotator& TraceRotation, 
	float TraceLength,
	FName Socket, 
	bool DrawTraceData, 
	FLinearColor TraceDataColor, 
	float TraceDataThickness
) {
	ACharacter * AsCharacter = Cast<ACharacter>(TheCharacter);
	if (!AsCharacter) return false;
	
	//Mesh Exists?
	if (!AsCharacter->GetMesh()) return false;
	
	//Socket Exists?
	if (!AsCharacter->GetMesh()->DoesSocketExist(Socket)) return false;
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	
	TraceStart = AsCharacter->GetMesh()->GetSocketLocation(Socket);
	TraceEnd = TraceStart + TraceRotation.Vector() * TraceLength;
	
	if (DrawTraceData) 
	{
		//Get World
		UWorld* TheWorld = AsCharacter->GetWorld();
		if (!TheWorld) return false;
		//~~~~~~~~~~~~~~~~~
	
		DrawDebugLine(
			TheWorld, 
			TraceStart, 
			TraceEnd, 
			TraceDataColor.ToFColor(true),
			false, 
			0.0333, 
			0, 
			TraceDataThickness
		);
	}
	
	return true;
}
bool UVictoryBPFunctionLibrary::TraceData__GetTraceDataFromSkeletalMeshSocket(
	FVector & TraceStart, //out
	FVector & TraceEnd,	//out
	USkeletalMeshComponent * Mesh,
	const FRotator & TraceRotation,	
	float TraceLength,
	FName Socket, 
	bool DrawTraceData, 
	FLinearColor TraceDataColor, 
	float TraceDataThickness
) {
	//Mesh Exists?
	if (!Mesh) return false;
	
	//Socket Exists?
	if (!Mesh->DoesSocketExist(Socket)) return false;
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	TraceStart = Mesh->GetSocketLocation(Socket);
	TraceEnd = TraceStart + TraceRotation.Vector() * TraceLength;
	
	if (DrawTraceData) 
	{
		//Get a PC to GetWorld() from
		TObjectIterator<APlayerController> Itr;
		if (!Itr) return false;
		
		//~~~~~~~~~~~~
		
		//Get World
		UWorld* TheWorld = Itr->GetWorld();
		if (!TheWorld) return false;
		//~~~~~~~~~~~~~~~~~
	
		DrawDebugLine(
			TheWorld, 
			TraceStart, 
			TraceEnd, 
			TraceDataColor.ToFColor(true),
			false, 
			0.0333, 
			0, 
			TraceDataThickness
		);
	}
	
	return true;
}
AActor*  UVictoryBPFunctionLibrary::Traces__CharacterMeshTrace___ClosestBone(
	AActor* TraceOwner,
	const FVector & TraceStart, 
	const FVector & TraceEnd, 
	FVector & OutImpactPoint, 
	FVector & OutImpactNormal, 
	FName & ClosestBoneName,
	FVector & ClosestBoneLocation,
	bool& IsValid
)
{
	IsValid = false;
	AActor * HitActor = NULL;
	//~~~~~~~~~~~~~~~~~~~~~~
	
	//Get a PC to GetWorld() from
	TObjectIterator<APlayerController> Itr;
	if (!Itr) return NULL;
	
	//~~~~~~~~~~~~
	
	//Get World
	UWorld* TheWorld = Itr->GetWorld();
	if (TheWorld == nullptr) return NULL;
	//~~~~~~~~~~~~~~~~~
	
	
	//Simple Trace First
	FCollisionQueryParams TraceParams(FName(TEXT("VictoryBPTrace::CharacterMeshTrace")), true, HitActor);
	TraceParams.bTraceComplex = true;
	TraceParams.bTraceAsyncScene = true;
	TraceParams.bReturnPhysicalMaterial = false;
	TraceParams.AddIgnoredActor(TraceOwner);
	
	//initialize hit info
	FHitResult RV_Hit(ForceInit);
	
	TheWorld->LineTraceSingleByChannel(
		RV_Hit,		//result
		TraceStart, 
		TraceEnd, 
		ECC_Pawn, //collision channel
		TraceParams
	);
		
	//Hit Something!
	if (!RV_Hit.bBlockingHit) return HitActor;
	
	
	//Character?
	HitActor = RV_Hit.GetActor();
	ACharacter * AsCharacter = Cast<ACharacter>(HitActor);
	if (!AsCharacter) return HitActor;
	
	//Mesh
	if (!AsCharacter->GetMesh()) return HitActor;
	
	//Component Trace
	FHitResult Hit;
	IsValid = AsCharacter->GetMesh()->K2_LineTraceComponent(
		TraceStart, 
		TraceEnd, 
		true, 
		false, 
		OutImpactPoint, 
		OutImpactNormal,
		ClosestBoneName,
		Hit
	); 
	
	//Location
	ClosestBoneLocation = AsCharacter->GetMesh()->GetBoneLocation(ClosestBoneName);
	
	return HitActor;
}

AActor* UVictoryBPFunctionLibrary::Traces__CharacterMeshTrace___ClosestSocket(
	UObject* WorldContextObject,
	const AActor * TraceOwner, 
	const FVector & TraceStart, 
	const FVector & TraceEnd, 
	FVector & OutImpactPoint, 
	FVector & OutImpactNormal, 
	FName & ClosestSocketName, 
	FVector & SocketLocation, 
	bool & IsValid
)
{
	IsValid = false;
	AActor * HitActor = NULL;
	//~~~~~~~~~~~~~~~~~~~~~~
	
	if(!WorldContextObject) return nullptr;
	 
	UWorld* const TheWorld = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if(!TheWorld) return nullptr;
	//~~~~~~~~~~~
	 
	//Simple Trace First
	FCollisionQueryParams TraceParams(FName(TEXT("VictoryBPTrace::CharacterMeshSocketTrace")), true, HitActor);
	TraceParams.bTraceComplex = true;
	TraceParams.bTraceAsyncScene = true;
	TraceParams.bReturnPhysicalMaterial = false;
	TraceParams.AddIgnoredActor(TraceOwner);
	
	//initialize hit info
	FHitResult RV_Hit(ForceInit);
	
	TheWorld->LineTraceSingleByChannel(
		RV_Hit,		//result
		TraceStart, 
		TraceEnd, 
		ECC_Pawn, //collision channel
		TraceParams
	);
		
	//Hit Something!
	if (!RV_Hit.bBlockingHit) return HitActor;
	
	
	//Character?
	HitActor = RV_Hit.GetActor();
	ACharacter * AsCharacter = Cast<ACharacter>(HitActor);
	if (!AsCharacter) return HitActor;
	
	//Mesh
	if (!AsCharacter->GetMesh()) return HitActor;
	
	//Component Trace
	FHitResult Hit;
	FName BoneName;
	if (! AsCharacter->GetMesh()->K2_LineTraceComponent(
		TraceStart, 
		TraceEnd, 
		true, 
		false, 
		OutImpactPoint, 
		OutImpactNormal,
		BoneName,
		Hit
	)) return HitActor;
	
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//					Socket Names
	TArray<FComponentSocketDescription> SocketNames;
	
	//Get Bone Names
	AsCharacter->GetMesh()->QuerySupportedSockets(SocketNames);
	
	//						Min
	FVector CurLoc;
	float CurDist = 0;
	float ClosestDistance = -1;
	int32 ClosestVibe = 0;
	
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//Check All Bones Locations
	for (int32 Itr = 0; Itr < SocketNames.Num(); Itr++ )
	{
		//Is this a Bone not a socket?
		if(SocketNames[Itr].Type == EComponentSocketType::Bone) continue;
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		CurLoc = AsCharacter->GetMesh()->GetSocketLocation(SocketNames[Itr].Name);
		
		//Dist
		CurDist = FVector::Dist(OutImpactPoint, CurLoc);
		
		//Min
		if (ClosestDistance < 0 || ClosestDistance >= CurDist)
		{
			ClosestVibe = Itr;
			ClosestDistance = CurDist;
		}
	}
	
	//Name
	ClosestSocketName = SocketNames[ClosestVibe].Name;
	
	//Location
	SocketLocation = AsCharacter->GetMesh()->GetSocketLocation(ClosestSocketName);
	
	//Valid
	IsValid = true;
	
	//Actor
	return HitActor;
}
	
void UVictoryBPFunctionLibrary::VictorySimulateMouseWheel(const float& Delta)
{
	FSlateApplication::Get().OnMouseWheel(int32(Delta));
}
void UVictoryBPFunctionLibrary::VictorySimulateKeyPress(APlayerController* ThePC, FKey Key, EInputEvent EventType)
{
	if (!ThePC) return;
	ThePC->InputKey(Key, EventType, 1, false); //amount depressed, bGamepad
	//! This will fire twice if the event is not handled, for umg widgets place an invisible button in background.
    
	if (Key == EKeys::LeftMouseButton || Key == EKeys::MiddleMouseButton || Key == EKeys::RightMouseButton || Key == EKeys::ThumbMouseButton || Key == EKeys::ThumbMouseButton2)
	{
		EMouseButtons::Type Button = EMouseButtons::Invalid;
		if (Key == EKeys::LeftMouseButton)
		{
			Button = EMouseButtons::Left;
		}
		else if (Key == EKeys::MiddleMouseButton)
		{
			Button = EMouseButtons::Middle;
		}
		else if (Key == EKeys::RightMouseButton)
		{
			Button = EMouseButtons::Right;
		}
		else if (Key == EKeys::ThumbMouseButton)
		{
			Button = EMouseButtons::Thumb01;
		}
		else if (Key == EKeys::ThumbMouseButton2)
		{
			Button = EMouseButtons::Thumb02;
		}


		if (EventType == IE_Pressed)
		{
			FSlateApplication::Get().OnMouseDown(nullptr, Button);
		}
		else if (EventType == IE_Released)
		{
			FSlateApplication::Get().OnMouseUp(Button);
		}
		else if (EventType == IE_DoubleClick)
		{
			FSlateApplication::Get().OnMouseDoubleClick(nullptr, Button);
		}
	}
	else
	{
		const uint32 *KeyCode = 0;
		const uint32 *CharacterCode = 0;
		FInputKeyManager::Get().GetCodesFromKey(Key, KeyCode, CharacterCode);
		uint32 KeyCodeVal = (KeyCode != NULL) ? *KeyCode : -1;
		uint32 CharacterCodeVal = (CharacterCode != NULL) ? *CharacterCode : -1;

		if (EventType == IE_Pressed)
		{
			FSlateApplication::Get().OnKeyDown(KeyCodeVal, CharacterCodeVal, false);
		}
		else if (EventType == IE_Released)
		{
			FSlateApplication::Get().OnKeyUp(KeyCodeVal, CharacterCodeVal, false);
		}
	}
}

bool UVictoryBPFunctionLibrary::Viewport__EnableWorldRendering(const APlayerController* ThePC, bool RenderTheWorld)
{ 
	if (!ThePC) return false;
	//~~~~~~~~~~~~~
	
	//Get Player
	ULocalPlayer * VictoryPlayer = Cast<ULocalPlayer>(ThePC->Player); 
											//PlayerController::Player is UPlayer
           
	if (!VictoryPlayer) return false;
	//~~~~~~~~~~~~~~~~~~~~
	
	//get view port ptr
	UGameViewportClient * VictoryViewportClient = 
		Cast < UGameViewportClient > (VictoryPlayer->ViewportClient);
		
	if (!VictoryViewportClient) return false;
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	    
	VictoryViewportClient->bDisableWorldRendering = !RenderTheWorld;
	return true;
}

//Most HUD stuff is in floats so I do the conversion internally
bool UVictoryBPFunctionLibrary::Viewport__SetMousePosition(const APlayerController* ThePC, const float& PosX, const float& PosY)
{
	if (!ThePC) return false;
	//~~~~~~~~~~~~~
	
	//Get Player
	const ULocalPlayer * VictoryPlayer = Cast<ULocalPlayer>(ThePC->Player); 
											//PlayerController::Player is UPlayer
           
	if (!VictoryPlayer) return false;
	//~~~~~~~~~~~~~~~~~~~~
	
	//get view port ptr
	const UGameViewportClient * VictoryViewportClient = 
		Cast < UGameViewportClient > (VictoryPlayer->ViewportClient);
		
	if (!VictoryViewportClient) return false;
	//~~~~~~~~~~~~~~~~~~~~
	 
	FViewport * VictoryViewport = VictoryViewportClient->Viewport;
	
	if (!VictoryViewport) return false;
	//~~~~~~~~~~~~~~~~~~~~
	
	//Set Mouse
	VictoryViewport->SetMouse(int32(PosX), int32(PosY));
	
	return true;
}

APlayerController * UVictoryBPFunctionLibrary::Accessor__GetPlayerController(
	AActor * TheCharacter, 
	bool & IsValid
)
{
	IsValid = false;
	
	//Cast to Character
	ACharacter * AsCharacter = Cast<ACharacter>(TheCharacter);
	if (!AsCharacter) return NULL;
	
	//cast to PC
	APlayerController * ThePC = Cast < APlayerController > (AsCharacter->GetController());
	
	if (!ThePC ) return NULL;
	
	IsValid = true;
	return ThePC;
}
	
bool UVictoryBPFunctionLibrary::Viewport__GetCenterOfViewport(const APlayerController * ThePC, float & PosX, float & PosY)
{
	if (!ThePC) return false;
	//~~~~~~~~~~~~~
	
	//Get Player
	const ULocalPlayer * VictoryPlayer = Cast<ULocalPlayer>(ThePC->Player); 
											//PlayerController::Player is UPlayer
           
	if (!VictoryPlayer) return false;
	//~~~~~~~~~~~~~~~~~~~~
	
	//get view port ptr
	const UGameViewportClient * VictoryViewportClient = 
		Cast < UGameViewportClient > (VictoryPlayer->ViewportClient);
		
	if (!VictoryViewportClient) return false;
	//~~~~~~~~~~~~~~~~~~~~
	 
	FViewport * VictoryViewport = VictoryViewportClient->Viewport;
	
	if (!VictoryViewport) return false;
	//~~~~~~~~~~~~~~~~~~~~
	
	//Get Size
	FIntPoint Size = VictoryViewport->GetSizeXY();
	
	//Center
	PosX = Size.X / 2;
	PosY = Size.Y / 2;
	
	return true;
}

bool UVictoryBPFunctionLibrary::Viewport__GetMousePosition(const APlayerController * ThePC, float & PosX, float & PosY)
{
	if (!ThePC) return false;
	//~~~~~~~~~~~~~
	
	//Get Player
	const ULocalPlayer * VictoryPlayer = Cast<ULocalPlayer>(ThePC->Player); 
											//PlayerController::Player is UPlayer
           
	if (!VictoryPlayer) return false;
	//~~~~~~~~~~~~~~~~~~~~
	
	//get view port ptr
	const UGameViewportClient * VictoryViewportClient = 
		Cast < UGameViewportClient > (VictoryPlayer->ViewportClient);
		
	if (!VictoryViewportClient) return false;
	//~~~~~~~~~~~~~~~~~~~~
	 
	FViewport * VictoryViewport = VictoryViewportClient->Viewport;
	
	if (!VictoryViewport) return false;
	//~~~~~~~~~~~~~~~~~~~~
	
	PosX = float(VictoryViewport->GetMouseX());
	PosY = float(VictoryViewport->GetMouseY());
	
	return true;
}





bool UVictoryBPFunctionLibrary::Physics__EnterRagDoll(AActor * TheCharacter)
{
	ACharacter * AsCharacter = Cast<ACharacter>(TheCharacter);
	if (!AsCharacter) return false;
	
	//Mesh?
	if (!AsCharacter->GetMesh()) return false;
	
	//Physics Asset?
	if(!AsCharacter->GetMesh()->GetPhysicsAsset()) return false;
	
	//Victory Ragdoll
	AsCharacter->GetMesh()->SetSimulatePhysics(true);

	return true;
}


bool UVictoryBPFunctionLibrary::Physics__LeaveRagDoll(
	AActor* TheCharacter,
	bool SetToFallingMovementMode,
	float HeightAboveRBMesh,
	const FVector& InitLocation, 
	const FRotator& InitRotation
){
	ACharacter * AsCharacter = Cast<ACharacter>(TheCharacter);
	if (!AsCharacter) return false;
	
	//Mesh?
	if (!AsCharacter->GetMesh()) return false;
	 
	//Set Actor Location to Be Near Ragdolled Mesh
	//Calc Ref Bone Relative Pos for use with IsRagdoll
	TArray<FName> BoneNames;
	AsCharacter->GetMesh()->GetBoneNames(BoneNames);
	if(BoneNames.Num() > 0)
	{
		AsCharacter->SetActorLocation(FVector(0,0,HeightAboveRBMesh) + AsCharacter->GetMesh()->GetBoneLocation(BoneNames[0]));
	}
	
	//Exit Ragdoll
	AsCharacter->GetMesh()->SetSimulatePhysics(false);
	AsCharacter->GetMesh()->AttachToComponent(AsCharacter->GetCapsuleComponent(), FAttachmentTransformRules::KeepRelativeTransform);
            
	//Restore Defaults
	AsCharacter->GetMesh()->SetRelativeRotation(InitRotation);
	AsCharacter->GetMesh()->SetRelativeLocation(InitLocation);
	
	//Set Falling After Final Capsule Relocation
	if(SetToFallingMovementMode)
	{ 
		if(AsCharacter->GetCharacterMovement()) AsCharacter->GetCharacterMovement()->SetMovementMode(MOVE_Falling);	
	}
	
	return true;
}	

bool UVictoryBPFunctionLibrary::Physics__IsRagDoll(AActor* TheCharacter)
{
	ACharacter * AsCharacter = Cast<ACharacter>(TheCharacter);
	if (!AsCharacter) return false;
	
	//Mesh?
	if (!AsCharacter->GetMesh()) return false;
	
	return AsCharacter->GetMesh()->IsAnySimulatingPhysics();
}	

bool UVictoryBPFunctionLibrary::Physics__GetLocationofRagDoll(AActor* TheCharacter, FVector& RagdollLocation)
{
	ACharacter * AsCharacter = Cast<ACharacter>(TheCharacter);
	if (!AsCharacter) return false;
	
	//Mesh?
	if (!AsCharacter->GetMesh()) return false;
	
	TArray<FName> BoneNames;
	AsCharacter->GetMesh()->GetBoneNames(BoneNames);
	if(BoneNames.Num() > 0)
	{
		RagdollLocation = AsCharacter->GetMesh()->GetBoneLocation(BoneNames[0]);
	}
	else return false;
	
	return true;
}

bool UVictoryBPFunctionLibrary::Physics__InitializeVictoryRagDoll(
	AActor* TheCharacter, 
	FVector& InitLocation, 
	FRotator& InitRotation
){
	ACharacter * AsCharacter = Cast<ACharacter>(TheCharacter);
	if (!AsCharacter) return false;
	
	//Mesh?
	if (!AsCharacter->GetMesh()) return false;
	
	InitLocation = AsCharacter->GetMesh()->GetRelativeTransform().GetLocation();
	InitRotation = AsCharacter->GetMesh()->GetRelativeTransform().Rotator();
	
	return true;
}

bool UVictoryBPFunctionLibrary::Physics__UpdateCharacterCameraToRagdollLocation(
	AActor* TheCharacter, 
	float HeightOffset,
	float InterpSpeed
){
	ACharacter * AsCharacter = Cast<ACharacter>(TheCharacter);
	if (!AsCharacter) return false;
	
	//Mesh?
	if (!AsCharacter->GetMesh()) return false;
	
	//Ragdoll?
	if(!AsCharacter->GetMesh()->IsAnySimulatingPhysics()) return false;
	
	FVector RagdollLocation = FVector(0,0,0);
	TArray<FName> BoneNames;
	AsCharacter->GetMesh()->GetBoneNames(BoneNames);
	if(BoneNames.Num() > 0)
	{
		RagdollLocation = AsCharacter->GetMesh()->GetBoneLocation(BoneNames[0]);
	}
	
	//Interp
	RagdollLocation = FMath::VInterpTo(AsCharacter->GetActorLocation(), RagdollLocation + FVector(0,0,HeightOffset), AsCharacter->GetWorld()->DeltaTimeSeconds, InterpSpeed);

	//Set Loc
	AsCharacter->SetActorLocation(RagdollLocation);
	
	return true;
}
/*
bool UVictoryBPFunctionLibrary::Accessor__GetSocketLocalTransform(const USkeletalMeshComponent* Mesh, FTransform& LocalTransform, FName SocketName)
{
	if(!Mesh) return false;
	
	LocalTransform =  Mesh->GetSocketLocalTransform(SocketName);
	
	return true;
}
*/
 
void UVictoryBPFunctionLibrary::StringConversion__GetFloatAsStringWithPrecision(float TheFloat, FString & FloatString, int32 Precision, bool IncludeLeadingZero)
{ 
	FNumberFormattingOptions NumberFormat;					//Text.h
	NumberFormat.MinimumIntegralDigits = (IncludeLeadingZero) ? 1 : 0;
	NumberFormat.MaximumIntegralDigits = 10000;
	NumberFormat.MinimumFractionalDigits = Precision;
	NumberFormat.MaximumFractionalDigits = Precision; 
	FloatString = FText::AsNumber(TheFloat, &NumberFormat).ToString();
}

bool UVictoryBPFunctionLibrary::LensFlare__GetLensFlareOffsets(
	APlayerController* PlayerController,
	AActor* LightSource, 
	float& PitchOffset, 
	float& YawOffset, 
	float& RollOffset
){
	if(!PlayerController) return false;
	if(!LightSource) return false;
	//~~~~~~~~~~~~~~~~~~~
	
	//angle from player to light source
	const FRotator AngleToLightSource = (LightSource->GetActorLocation() - PlayerController->GetFocalLocation()).Rotation();
	
	const FRotator Offsets = AngleToLightSource - PlayerController->GetControlRotation();
	
	PitchOffset = Offsets.Pitch;
	YawOffset = Offsets.Yaw;
	RollOffset = Offsets.Roll;
	return true;
}

//SMA Version
float UVictoryBPFunctionLibrary::DistanceToSurface__DistaceOfPointToMeshSurface(AStaticMeshActor* TheSMA, const FVector& TestPoint, FVector& ClosestSurfacePoint)
{
	if(!TheSMA) return -1;
	if(!TheSMA->GetStaticMeshComponent()) return -1;
	//~~~~~~~~~~
	
	//Dist of pt to Surface, retrieve closest Surface Point to Actor
	return TheSMA->GetStaticMeshComponent()->GetDistanceToCollision(TestPoint, ClosestSurfacePoint);
}

bool UVictoryBPFunctionLibrary::Mobility__SetSceneCompMobility(
	USceneComponent* SceneComp,
	EComponentMobility::Type NewMobility
)
{
	if(!SceneComp) return false;
	//~~~~~~~~~~~
	
	SceneComp->SetMobility(NewMobility);
	
	return true;
}



















//~~~~~~~~~~~~~~~~~~
//		FullScreen
//~~~~~~~~~~~~~~~~~~	
TEnumAsByte<EJoyGraphicsFullScreen::Type> UVictoryBPFunctionLibrary::JoyGraphicsSettings__FullScreen_Get()
{
	return TEnumAsByte<EJoyGraphicsFullScreen::Type>(JoyGraphics_FullScreen_GetFullScreenType());
}
	  
void UVictoryBPFunctionLibrary::JoyGraphicsSettings__FullScreen_Set(TEnumAsByte<EJoyGraphicsFullScreen::Type> NewSetting)
{  
	JoyGraphics_FullScreen_SetFullScreenType(NewSetting.GetValue()); 
}














//~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~
//			  Contributed by Others

	/**
	* Contributed by: SaxonRah
	* Better random numbers. Seeded with a random device. if the random device's entropy is 0; defaults to current time for seed.
	* can override with seed functions;
	*/
//----------------------------------------------------------------------------------------------BeginRANDOM
	std::random_device rd;		
	unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();

	std::mt19937 rand_MT;
	std::default_random_engine rand_DRE;

	/** Construct a random device and set seed for engines dependent on entropy */
	void UVictoryBPFunctionLibrary::constructRand()
	{
		seed = std::chrono::system_clock::now().time_since_epoch().count();

		if (rd.entropy() == 0)
		{
			seedRand(seed);
		}else{
			seedRand(rd());
		}
	}
	/** Set seed for Rand */
	void UVictoryBPFunctionLibrary::seedRand(int32 _seed)
	{
		seed = _seed;
	}

	/** Set seed with time for Rand */
	void UVictoryBPFunctionLibrary::seedRandWithTime()
	{
		seed = std::chrono::system_clock::now().time_since_epoch().count();
	}

	/** Set seed with entropy for Rand */
	void UVictoryBPFunctionLibrary::seedRandWithEntropy()
	{
		seedRand(rd());
	}

	/** Random Bool - Bernoulli distribution */
	bool UVictoryBPFunctionLibrary::RandBool_Bernoulli(float fBias)
	{
		std::bernoulli_distribution dis(fBias);
		return dis(rand_DRE);
	}

	/** Random Integer - Uniform distribution */
	int32 UVictoryBPFunctionLibrary::RandInt_uniDis()
	{
		std::uniform_int_distribution<int32> dis(0, 1);
		return dis(rand_DRE);
	}
	/** Random Integer - Uniform distribution */
	int32 UVictoryBPFunctionLibrary::RandInt_MINMAX_uniDis(int32 iMin, int32 iMax)
	{
		std::uniform_int_distribution<int32> dis(iMin, iMax);
		return dis(rand_DRE);
	}

	/** Random Float - Zero to One Uniform distribution */
	float UVictoryBPFunctionLibrary::RandFloat_uniDis()
	{
		std::uniform_real_distribution<float> dis(0, 1);
		return dis(rand_DRE);
	}
	/** Random Float - MIN to MAX Uniform distribution */
	float UVictoryBPFunctionLibrary::RandFloat_MINMAX_uniDis(float iMin, float iMax)
	{
		std::uniform_real_distribution<float> dis(iMin, iMax);
		return dis(rand_DRE);
	}

	/** Random Bool - Bernoulli distribution  -  Mersenne Twister */
	bool UVictoryBPFunctionLibrary::RandBool_Bernoulli_MT(float fBias)
	{
		std::bernoulli_distribution dis(fBias);
		return dis(rand_MT);
	}

	/** Random Integer - Uniform distribution  -  Mersenne Twister */
	int32 UVictoryBPFunctionLibrary::RandInt_uniDis_MT()
	{
		std::uniform_int_distribution<int32> dis(0, 1);
		return dis(rand_MT);
	}
	/** Random Integer - Uniform distribution  -  Mersenne Twister */
	int32 UVictoryBPFunctionLibrary::RandInt_MINMAX_uniDis_MT(int32 iMin, int32 iMax)
	{
		std::uniform_int_distribution<int32> dis(iMin, iMax);
		return dis(rand_MT);
	}

	/** Random Float - Zero to One Uniform distribution  -  Mersenne Twister */
	float UVictoryBPFunctionLibrary::RandFloat_uniDis_MT()
	{
		std::uniform_real_distribution<float> dis(0, 1);
		return dis(rand_MT);
	}
	/** Random Float - MIN to MAX Uniform distribution  -  Mersenne Twister */
	float UVictoryBPFunctionLibrary::RandFloat_MINMAX_uniDis_MT(float iMin, float iMax)
	{
		std::uniform_real_distribution<float> dis(iMin, iMax);
		return dis(rand_MT);
	}
//----------------------------------------------------------------------------------------------ENDRANDOM



void UVictoryBPFunctionLibrary::String__ExplodeString(TArray<FString>& OutputStrings, FString InputString, FString Separator, int32 limit, bool bTrimElements)
{
	OutputStrings.Empty();
	//~~~~~~~~~~~
	
	if (InputString.Len() > 0 && Separator.Len() > 0) {
		int32 StringIndex = 0;
		int32 SeparatorIndex = 0;

		FString Section = "";
		FString Extra = "";

		int32 PartialMatchStart = -1;

		while (StringIndex < InputString.Len()) {

			if (InputString[StringIndex] == Separator[SeparatorIndex]) {
				if (SeparatorIndex == 0) {
					//A new partial match has started.
					PartialMatchStart = StringIndex;
				}
				Extra.AppendChar(InputString[StringIndex]);
				if (SeparatorIndex == (Separator.Len() - 1)) {
					//We have matched the entire separator.
					SeparatorIndex = 0;
					PartialMatchStart = -1;
					if (bTrimElements == true) {
						OutputStrings.Add(FString(Section).TrimStart().TrimEnd());
					}
					else {
						OutputStrings.Add(FString(Section));
					}

					//if we have reached the limit, stop.
					if (limit > 0 && OutputStrings.Num() >= limit) 
					{
						return;
						//~~~~
					}

					Extra.Empty();
					Section.Empty();
				}
				else {
					++SeparatorIndex;
				}
			}
			else {
				//Not matched.
				//We should revert back to PartialMatchStart+1 (if there was a partial match) and clear away extra.
				if (PartialMatchStart >= 0) {
					StringIndex = PartialMatchStart;
					PartialMatchStart = -1;
					Extra.Empty();
					SeparatorIndex = 0;
				}
				Section.AppendChar(InputString[StringIndex]);
			}

			++StringIndex;
		}

		//If there is anything left in Section or Extra. They should be added as a new entry.
		if (bTrimElements == true) {
			OutputStrings.Add(FString(Section + Extra).TrimStart().TrimEnd());
		}
		else {
			OutputStrings.Add(FString(Section + Extra));
		}

		Section.Empty();
		Extra.Empty();
	}
}

UTexture2D* UVictoryBPFunctionLibrary::LoadTexture2D_FromDDSFile(const FString& FullFilePath)
{
	UTexture2D* Texture = NULL;

	FString TexturePath = FullFilePath;//FPaths::GameContentDir( ) + TEXT( "../Data/" ) + TextureFilename;
	TArray<uint8> FileData;

	/* Load DDS texture */
	if( FFileHelper::LoadFileToArray( FileData, *TexturePath, 0 ) )
	{
		FDDSLoadHelper DDSLoadHelper( &FileData[ 0 ], FileData.Num( ) );
		if( DDSLoadHelper.IsValid2DTexture( ) )
		{
			int32 NumMips = DDSLoadHelper.ComputeMipMapCount( );
			EPixelFormat Format = DDSLoadHelper.ComputePixelFormat( );
			int32 BlockSize = 16;

			if( NumMips == 0 )
			{
				NumMips = 1;
			}

			if( Format == PF_DXT1 )
			{
				BlockSize = 8;
			}

			/* Create transient texture */
			Texture = UTexture2D::CreateTransient( DDSLoadHelper.DDSHeader->dwWidth, DDSLoadHelper.DDSHeader->dwHeight, Format );
			if(!Texture) return NULL;
			Texture->PlatformData->NumSlices = 1;
			Texture->NeverStream = true;
		
			/* Get pointer to actual data */
			uint8* DataPtr = (uint8*) DDSLoadHelper.GetDDSDataPointer( );

			uint32 CurrentWidth = DDSLoadHelper.DDSHeader->dwWidth;
			uint32 CurrentHeight = DDSLoadHelper.DDSHeader->dwHeight;

			/* Iterate through mips */
			for( int32 i = 0; i < NumMips; i++ )
			{
				/* Lock to 1x1 as smallest size */
				CurrentWidth = ( CurrentWidth < 1 ) ? 1 : CurrentWidth;
				CurrentHeight = ( CurrentHeight < 1 ) ? 1 : CurrentHeight;

				/* Get number of bytes to read */
				int32 NumBytes = CurrentWidth * CurrentHeight * 4;
				if( Format == PF_DXT1 || Format == PF_DXT3 || Format == PF_DXT5 )
				{
					/* Compressed formats */
					NumBytes = ( ( CurrentWidth + 3 ) / 4 ) * ( ( CurrentHeight + 3 ) / 4 ) * BlockSize;
				}

				/* Write to existing mip */
				if( i < Texture->PlatformData->Mips.Num( ) )
				{
					FTexture2DMipMap& Mip = Texture->PlatformData->Mips[ i ];

					void* Data = Mip.BulkData.Lock( LOCK_READ_WRITE );
					FMemory::Memcpy( Data, DataPtr, NumBytes );
					Mip.BulkData.Unlock( );
				}

				/* Add new mip */
				else
				{
					FTexture2DMipMap* Mip = new( Texture->PlatformData->Mips ) FTexture2DMipMap( );
					Mip->SizeX = CurrentWidth;
					Mip->SizeY = CurrentHeight;

					Mip->BulkData.Lock( LOCK_READ_WRITE );
					Mip->BulkData.Realloc( NumBytes );
					Mip->BulkData.Unlock( );

					void* Data = Mip->BulkData.Lock( LOCK_READ_WRITE );
					FMemory::Memcpy( Data, DataPtr, NumBytes );
					Mip->BulkData.Unlock( );
				}

				/* Set next mip level */
				CurrentWidth /= 2;
				CurrentHeight /= 2;

				DataPtr += NumBytes;
			}

			Texture->UpdateResource( );
		}
	}

	return Texture;
}


//this is how you can make cpp only internal functions!
static EImageFormat GetJoyImageFormat(EJoyImageFormats JoyFormat)
{
	/*
	ImageWrapper.h
	namespace EImageFormat
	{
	
	Enumerates the types of image formats this class can handle
	
	enum Type
	{
		//Portable Network Graphics
		PNG,

		//Joint Photographic Experts Group 
		JPEG,

		//Single channel jpeg
		GrayscaleJPEG,	

		//Windows Bitmap 
		BMP,

		//Windows Icon resource 
		ICO,

		//OpenEXR (HDR) image file format 
		EXR,

		//Mac icon 
		ICNS
	};
};
	*/
	switch(JoyFormat)
	{
		case EJoyImageFormats::JPG : return EImageFormat::JPEG;
		case EJoyImageFormats::PNG : return EImageFormat::PNG;
		case EJoyImageFormats::BMP : return EImageFormat::BMP;
		case EJoyImageFormats::ICO : return EImageFormat::ICO;
		case EJoyImageFormats::EXR : return EImageFormat::EXR;
		case EJoyImageFormats::ICNS : return EImageFormat::ICNS;
	}
	return EImageFormat::JPEG;
} 

static FString GetJoyImageExtension(EJoyImageFormats JoyFormat)
{
	switch(JoyFormat)
	{
		case EJoyImageFormats::JPG : return ".jpg";
		case EJoyImageFormats::PNG : return ".png";
		case EJoyImageFormats::BMP : return ".bmp";
		case EJoyImageFormats::ICO : return ".ico";
		case EJoyImageFormats::EXR : return ".exr";
		case EJoyImageFormats::ICNS : return ".icns";
	}
	return ".png";
} 
UTexture2D* UVictoryBPFunctionLibrary::Victory_LoadTexture2D_FromFile(const FString& FullFilePath,EJoyImageFormats ImageFormat, bool& IsValid,int32& Width, int32& Height)
{
	
	
	IsValid = false;
	UTexture2D* LoadedT2D = NULL;
	
	IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
	TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(GetJoyImageFormat(ImageFormat));
 
	//Load From File
	TArray<uint8> RawFileData;
	if (!FFileHelper::LoadFileToArray(RawFileData, *FullFilePath)) return NULL;
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	  
	//Create T2D!
	if (ImageWrapper.IsValid() && ImageWrapper->SetCompressed(RawFileData.GetData(), RawFileData.Num()))
	{ 
		const TArray<uint8>* UncompressedBGRA = NULL;
		if (ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, UncompressedBGRA))
		{
			LoadedT2D = UTexture2D::CreateTransient(ImageWrapper->GetWidth(), ImageWrapper->GetHeight(), PF_B8G8R8A8);
			
			//Valid?
			if(!LoadedT2D) return NULL;
			//~~~~~~~~~~~~~~
			
			//Out!
			Width = ImageWrapper->GetWidth();
			Height = ImageWrapper->GetHeight();
			 
			//Copy!
			void* TextureData = LoadedT2D->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE);
			FMemory::Memcpy(TextureData, UncompressedBGRA->GetData(), UncompressedBGRA->Num());
			LoadedT2D->PlatformData->Mips[0].BulkData.Unlock();

			//Update!
			LoadedT2D->UpdateResource();
		}
	}
	 
	// Success!
	IsValid = true;
	return LoadedT2D;
}
UTexture2D* UVictoryBPFunctionLibrary::Victory_LoadTexture2D_FromFile_Pixels(const FString& FullFilePath,EJoyImageFormats ImageFormat,bool& IsValid, int32& Width, int32& Height, TArray<FLinearColor>& OutPixels)
{
	//Clear any previous data
	OutPixels.Empty();
	
	IsValid = false;
	UTexture2D* LoadedT2D = NULL;
	
	IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
	TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(GetJoyImageFormat(ImageFormat));
 
	//Load From File
	TArray<uint8> RawFileData;
	if (!FFileHelper::LoadFileToArray(RawFileData, *FullFilePath)) return NULL;
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	  
	//Create T2D!
	if (ImageWrapper.IsValid() && ImageWrapper->SetCompressed(RawFileData.GetData(), RawFileData.Num()))
	{  
		const TArray<uint8>* UncompressedRGBA = NULL;
		if (ImageWrapper->GetRaw(ERGBFormat::RGBA, 8, UncompressedRGBA))
		{
			LoadedT2D = UTexture2D::CreateTransient(ImageWrapper->GetWidth(), ImageWrapper->GetHeight(), PF_R8G8B8A8);
			
			//Valid?
			if(!LoadedT2D) return NULL;
			//~~~~~~~~~~~~~~
			
			//Out!
			Width = ImageWrapper->GetWidth();
			Height = ImageWrapper->GetHeight();
			
			const TArray<uint8>& ByteArray = *UncompressedRGBA;
			//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			
			for(int32 v = 0; v < ByteArray.Num(); v+=4) 
			{ 
				if(!ByteArray.IsValidIndex(v+3)) 
				{ 
					break;
				}   
				     
				OutPixels.Add(
					FLinearColor(
						ByteArray[v],		//R
						ByteArray[v+1],		//G
						ByteArray[v+2],		//B
						ByteArray[v+3] 		//A
					)
				);
			}
			//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			   
			//Copy!
			void* TextureData = LoadedT2D->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE);
			FMemory::Memcpy(TextureData, UncompressedRGBA->GetData(), UncompressedRGBA->Num());
			LoadedT2D->PlatformData->Mips[0].BulkData.Unlock();

			//Update!
			LoadedT2D->UpdateResource();
		}
	}
	 
	// Success!
	IsValid = true;
	return LoadedT2D;
	
}
bool UVictoryBPFunctionLibrary::Victory_Get_Pixel(const TArray<FLinearColor>& Pixels,int32 ImageHeight, int32 x, int32 y, FLinearColor& FoundColor)
{
	int32 Index = y * ImageHeight + x;
	if(!Pixels.IsValidIndex(Index))
	{
		return false;
	}
	 
	FoundColor = Pixels[Index];
	return true;
}


bool UVictoryBPFunctionLibrary::Victory_SavePixels(
	const FString& FullFilePath
	, int32 Width, int32 Height 
	, const TArray<FLinearColor>& ImagePixels
	, bool SaveAsBMP
	, bool sRGB
	, FString& ErrorString
){
	if(FullFilePath.Len() < 1) 
	{
		ErrorString = "No file path";
		return false;
	}
	//~~~~~~~~~~~~~~~~~
	
	//Ensure target directory exists, 
	//		_or can be created!_ <3 Rama
	FString NewAbsoluteFolderPath = FPaths::GetPath(FullFilePath);
	FPaths::NormalizeDirectoryName(NewAbsoluteFolderPath);
	if(!VCreateDirectory(NewAbsoluteFolderPath)) 
	{
		ErrorString = "Folder could not be created, check read/write permissions~ " + NewAbsoluteFolderPath;
		return false;
	}
	
	//Create FColor version
	TArray<FColor> ColorArray;
	for(const FLinearColor& Each : ImagePixels)
	{  
		ColorArray.Add(Each.ToFColor(sRGB));  
	} 
	  
	if(ColorArray.Num() != Width * Height) 
	{
		ErrorString = "Error ~ height x width is not equal to the total pixel array length!";
		return false;
	}
	  
	//Remove any supplied file extension and/or add accurate one
	FString FinalFilename = FPaths::GetBaseFilename(FullFilePath, false); //false = dont remove path
	FinalFilename += (SaveAsBMP) ? ".bmp" : ".png";  
   
	//~~~
	
	if(SaveAsBMP)
	{ 
		ErrorString = "Success! or if returning false, the saving of file to disk did not succeed for File IO reasons";
		return FFileHelper::CreateBitmap( 
			*FinalFilename, 
			Width, 
			Height,  
			ColorArray.GetData(), //const struct FColor* Data, 
			nullptr,//struct FIntRect* SubRectangle = NULL, 
			&IFileManager::Get(), 
			nullptr, //out filename info only 
			true //bool bInWriteAlpha 
		);
	}
	else
	{
		TArray<uint8> CompressedPNG;
		FImageUtils::CompressImageArray( 
			Width, 
			Height, 
			ColorArray, 
			CompressedPNG
		);
			
		ErrorString = "Success! or if returning false, the saving of file to disk did not succeed for File IO reasons";
		return FFileHelper::SaveArrayToFile(CompressedPNG, *FinalFilename);
	} 
	 
	/*
	//Crashed for JPG, worked great for PNG
	//Maybe also works for BMP so could offer those two as save options?
	
	const int32 x = Width;
	const int32 y = Height;
	if(ColorArray.Num() != x * y) 
	{
		ErrorString = "Error ~ height x width is not equal to the total pixel array length!";
		return false;
	}
	
	//Image Wrapper Module
	IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
	
	//Create Compressor
	IImageWrapperPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(GetJoyImageFormat(ImageFormat));
	
	if(!ImageWrapper.IsValid()) 
	{
		ErrorString = "Error ~ Image wrapper could not be created!";
		return false;
	}
	//~~~~~~~~~~~~~~~~~~~~~~
	      
	if ( ! ImageWrapper->SetRaw(
			(void*)&ColorArray[0], 			//mem address of array start
			sizeof(FColor) * x * y, 			//total size
			x, y, 								//dimensions
			ERGBFormat::BGRA,				//LinearColor == RGBA 
			(sizeof(FColor) / 4) * 8			//Bits per pixel
	)) {
		ErrorString = "ImageWrapper::SetRaw() did not succeed";
		return false;
	}
	
	ErrorString = "Success! or if returning false, the saving of file to disk did not succeed for File IO reasons";
	return FFileHelper::SaveArrayToFile(ImageWrapper->GetCompressed(), *FinalFilename);
	*/
}

bool UVictoryBPFunctionLibrary::Victory_GetPixelFromT2D(UTexture2D* T2D, int32 X, int32 Y, FLinearColor& PixelColor)
{
	if(!T2D) 
	{
		return false;
	}
	 
	if(X <= -1 || Y <= -1) 
	{
		return false;
	}
	 
	T2D->SRGB = false;
	T2D->CompressionSettings = TC_VectorDisplacementmap;
	
	//Update settings
	T2D->UpdateResource();
	 
	FTexture2DMipMap& MipsMap 	= T2D->PlatformData->Mips[0];
	int32 TextureWidth = MipsMap.SizeX;
	int32 TextureHeight = MipsMap.SizeY;
	 
	FByteBulkData* RawImageData 	= &MipsMap.BulkData;
	
	if(!RawImageData) 
	{
		return false;
	}
	
	FColor* RawColorArray = static_cast<FColor*>(RawImageData->Lock(LOCK_READ_ONLY));
	
	//Safety check!
	if (X >= TextureWidth || Y >= TextureHeight)
	{
		return false;
	}
	   
	//Get!, converting FColor to FLinearColor 
	PixelColor = RawColorArray[Y * TextureWidth + X];
  
	RawImageData->Unlock();
	return true;
}
bool UVictoryBPFunctionLibrary::Victory_GetPixelsArrayFromT2DDynamic(UTexture2DDynamic* T2D, int32& TextureWidth, int32& TextureHeight,TArray<FLinearColor>& PixelArray)
{
	if(!T2D) 
	{
		return false;
	}
	
	//To prevent overflow in BP if used in a loop
	PixelArray.Empty();
	
	//~~~~~~~~~~~~~~~~~~~~~~
	// Modifying original here
	T2D->SRGB = false;
	T2D->CompressionSettings = TC_VectorDisplacementmap;
	
	//Update settings
	T2D->UpdateResource();
	//~~~~~~~~~~~~~~~~~~~~~~
	
	//Confused, DDC / platform data is invalid for dynamic, how to get its byte data?
	//FTextureResource from UTexture base class?
	return false;
	
	/*
	FTexturePlatformData** PtrPtr = T2D->GetRunningPlatformData();
	if(!PtrPtr) return false;
	FTexturePlatformData* Ptr = *PtrPtr;
	if(!Ptr) return false;
	 
	FTexture2DMipMap& MyMipMap 	= Ptr->Mips[0];
	TextureWidth = MyMipMap.SizeX;
	TextureHeight = MyMipMap.SizeY;
	 
	FByteBulkData* RawImageData 	= &MyMipMap.BulkData;
	
	if(!RawImageData) 
	{
		return false;
	}
	
	FColor* RawColorArray = static_cast<FColor*>(RawImageData->Lock(LOCK_READ_ONLY));
	
	UE_LOG(LogTemp,Warning,TEXT("Victory Plugin, Get Pixels, tex width for mip %d"), TextureWidth);
	UE_LOG(LogTemp,Warning,TEXT("Victory Plugin, Get Pixels, tex width from T2D ptr %d"), T2D->GetSurfaceWidth());
	 
	for(int32 x = 0; x < TextureWidth; x++)
	{
		for(int32 y = 0; y < TextureHeight; y++)   
		{
			PixelArray.Add(RawColorArray[x * TextureWidth + y]); 
		}
	}
	  
	RawImageData->Unlock();
	*/
	
	return true;
}

bool UVictoryBPFunctionLibrary::Victory_GetPixelsArrayFromT2D(UTexture2D* T2D, int32& TextureWidth, int32& TextureHeight,TArray<FLinearColor>& PixelArray)
{
	if(!T2D) 
	{
		return false;
	}
	
	//To prevent overflow in BP if used in a loop
	PixelArray.Empty();
	
	T2D->SRGB = false;
	T2D->CompressionSettings = TC_VectorDisplacementmap;
	
	//Update settings
	T2D->UpdateResource();
	 
	FTexture2DMipMap& MyMipMap 	= T2D->PlatformData->Mips[0];
	TextureWidth = MyMipMap.SizeX;
	TextureHeight = MyMipMap.SizeY;
	 
	FByteBulkData* RawImageData 	= &MyMipMap.BulkData;
	
	if(!RawImageData) 
	{
		return false;
	}
	
	FColor* RawColorArray = static_cast<FColor*>(RawImageData->Lock(LOCK_READ_ONLY));
	
	for(int32 x = 0; x < TextureWidth; x++)
	{
		for(int32 y = 0; y < TextureHeight; y++)   
		{
			PixelArray.Add(RawColorArray[x * TextureWidth + y]); 
		}
	}
	  
	RawImageData->Unlock();
	return true;
}

class UAudioComponent* UVictoryBPFunctionLibrary::PlaySoundAttachedFromFile(const FString& FilePath, class USceneComponent* AttachToComponent, FName AttachPointName, FVector Location, EAttachLocation::Type LocationType, bool bStopWhenAttachedToDestroyed, float VolumeMultiplier, float PitchMultiplier, float StartTime, class USoundAttenuation* AttenuationSettings)
{	
	USoundWave* sw = GetSoundWaveFromFile(FilePath);

	if (!sw)
		return NULL;

	return UGameplayStatics::SpawnSoundAttached(sw, AttachToComponent, AttachPointName, Location, LocationType, bStopWhenAttachedToDestroyed, VolumeMultiplier, PitchMultiplier, StartTime, AttenuationSettings);
}

void UVictoryBPFunctionLibrary::PlaySoundAtLocationFromFile(UObject* WorldContextObject, const FString& FilePath, FVector Location, float VolumeMultiplier, float PitchMultiplier, float StartTime, class USoundAttenuation* AttenuationSettings)
{
	USoundWave* sw = GetSoundWaveFromFile(FilePath);

	if (!sw)
		return;

	UGameplayStatics::PlaySoundAtLocation(WorldContextObject, sw, Location, VolumeMultiplier, PitchMultiplier, StartTime, AttenuationSettings);
}

class USoundWave* UVictoryBPFunctionLibrary::GetSoundWaveFromFile(const FString& FilePath)
{
	#if PLATFORM_PS4
	UE_LOG(LogTemp, Error, TEXT("UVictoryBPFunctionLibrary::GetSoundWaveFromFile ~ vorbis-method not supported on PS4. See UVictoryBPFunctionLibrary::fillSoundWaveInfo"));
	return nullptr;
	#else
	USoundWave* sw = NewObject<USoundWave>(USoundWave::StaticClass());

	if (!sw)
		return NULL;

	//* If true the song was successfully loaded
	bool loaded = false;

	//* loaded song file (binary, encoded)
	TArray < uint8 > rawFile;

	loaded = FFileHelper::LoadFileToArray(rawFile, FilePath.GetCharArray().GetData());

	if (loaded)
	{
		FByteBulkData* bulkData = &sw->CompressedFormatData.GetFormat(TEXT("OGG"));

		bulkData->Lock(LOCK_READ_WRITE);
		FMemory::Memcpy(bulkData->Realloc(rawFile.Num()), rawFile.GetData(), rawFile.Num());
		bulkData->Unlock();

		loaded = fillSoundWaveInfo(sw, &rawFile) == 0 ? true : false;
	}

	if (!loaded)
		return NULL;

	return sw;
	#endif 
}

#if !PLATFORM_PS4
int32 UVictoryBPFunctionLibrary::fillSoundWaveInfo(class USoundWave* sw, TArray<uint8>* rawFile)
{
    FSoundQualityInfo info; 
    FVorbisAudioInfo vorbis_obj;
    if (!vorbis_obj.ReadCompressedInfo(rawFile->GetData(), rawFile->Num(), &info))
    {
        //Debug("Can't load header");
        return 1;
    }

	if(!sw) return 1;
	sw->SoundGroup = ESoundGroup::SOUNDGROUP_Default;
    sw->NumChannels = info.NumChannels;
    sw->Duration = info.Duration;
    sw->RawPCMDataSize = info.SampleDataSize;
    sw->SampleRate = info.SampleRate;

    return 0;
}

      
int32 UVictoryBPFunctionLibrary::findSource(class USoundWave* sw, class FSoundSource* out_audioSource)
{
	FAudioDevice* device = GEngine ? GEngine->GetMainAudioDevice() : NULL; //gently ask for the audio device

	FActiveSound* activeSound;
	FSoundSource* audioSource;
	FWaveInstance* sw_instance;
	if (!device)
	{
		activeSound = NULL;
		audioSource = NULL;
		out_audioSource = audioSource;
		return -1;
	}

	TArray<FActiveSound*> tmpActualSounds = device->GetActiveSounds();
	if (tmpActualSounds.Num())
	{
		for (auto activeSoundIt(tmpActualSounds.CreateIterator()); activeSoundIt; ++activeSoundIt)
		{
			activeSound = *activeSoundIt;
			for (auto WaveInstanceIt(activeSound->WaveInstances.CreateIterator()); WaveInstanceIt; ++WaveInstanceIt)
			{
				sw_instance = WaveInstanceIt.Value();
				if (sw_instance->WaveData->CompressedDataGuid == sw->CompressedDataGuid)
				{   
					audioSource = device->GetSoundSource(sw_instance); //4.13 onwards, <3 Rama
					out_audioSource = audioSource;
					return 0;
				}
			}
		}
	}

	audioSource = NULL;
	activeSound = NULL;
	out_audioSource = audioSource;
	return -2;
}
#endif //PLATFORM_PS4
 
//~~~ Kris ~~~
 
bool UVictoryBPFunctionLibrary::Array_IsValidIndex(const TArray<int32>& TargetArray, int32 Index)
{
    // We should never hit these!  They're stubs to avoid NoExport on the class.  Call the Generic* equivalent instead
    check(0);
    return false;
}
 
bool UVictoryBPFunctionLibrary::GenericArray_IsValidIndex(void* TargetArray, const UArrayProperty* ArrayProp, int32 Index)
{ 
	bool bResult = false;

	if (TargetArray)
	{
		FScriptArrayHelper ArrayHelper(ArrayProp, TargetArray);
		bResult = ArrayHelper.IsValidIndex(Index);
	}

	return bResult;
}

float UVictoryBPFunctionLibrary::GetCreationTime(const AActor* Target)
{
    return (Target) ? Target->CreationTime : 0.0f;
}
 
float UVictoryBPFunctionLibrary::GetTimeAlive(const AActor* Target)
{
    return (Target) ? (Target->GetWorld()->GetTimeSeconds() - Target->CreationTime) : 0.0f;
}

bool UVictoryBPFunctionLibrary::CaptureComponent2D_Project(class USceneCaptureComponent2D* Target, FVector Location, FVector2D& OutPixelLocation)
{
    if ((Target == nullptr) || (Target->TextureTarget == nullptr))
    {
        return false;
    }
    
    const FTransform& Transform = Target->GetComponentToWorld();
    FMatrix ViewMatrix = Transform.ToInverseMatrixWithScale();
    FVector ViewLocation = Transform.GetTranslation();

    // swap axis st. x=z,y=x,z=y (unreal coord space) so that z is up
    ViewMatrix = ViewMatrix * FMatrix(
        FPlane(0,    0,    1,    0),
        FPlane(1,    0,    0,    0),
        FPlane(0,    1,    0,    0),
        FPlane(0,    0,    0,    1));

    const float FOV = Target->FOVAngle * (float)PI / 360.0f;

    FIntPoint CaptureSize(Target->TextureTarget->GetSurfaceWidth(), Target->TextureTarget->GetSurfaceHeight());
    
    float XAxisMultiplier;
    float YAxisMultiplier;

    if (CaptureSize.X > CaptureSize.Y)
    {
        // if the viewport is wider than it is tall
        XAxisMultiplier = 1.0f;
        YAxisMultiplier = CaptureSize.X / (float)CaptureSize.Y;
    }
    else
    {
        // if the viewport is taller than it is wide
        XAxisMultiplier = CaptureSize.Y / (float)CaptureSize.X;
        YAxisMultiplier = 1.0f;
    }

    FMatrix    ProjectionMatrix = FReversedZPerspectiveMatrix (
        FOV,
        FOV,
        XAxisMultiplier,
        YAxisMultiplier,
        GNearClippingPlane,
        GNearClippingPlane
        );

    FMatrix ViewProjectionMatrix = ViewMatrix * ProjectionMatrix;

    FVector4 ScreenPoint = ViewProjectionMatrix.TransformFVector4(FVector4(Location,1));
    
    if (ScreenPoint.W > 0.0f)
    {
        float InvW = 1.0f / ScreenPoint.W;
        float Y = (GProjectionSignY > 0.0f) ? ScreenPoint.Y : 1.0f - ScreenPoint.Y;
        FIntRect ViewRect = FIntRect(0, 0, CaptureSize.X, CaptureSize.Y);
        OutPixelLocation = FVector2D(
            ViewRect.Min.X + (0.5f + ScreenPoint.X * 0.5f * InvW) * ViewRect.Width(),
            ViewRect.Min.Y + (0.5f - Y * 0.5f * InvW) * ViewRect.Height()
            );
        return true;
    }

    return false;
}    

bool UVictoryBPFunctionLibrary::Capture2D_Project(class ASceneCapture2D* Target, FVector Location, FVector2D& OutPixelLocation)
{
    return (Target) ? CaptureComponent2D_Project(Target->GetCaptureComponent2D(), Location, OutPixelLocation) : false;
}
 
static TSharedPtr<IImageWrapper> GetImageWrapperByExtention(const FString InImagePath)
{
    IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
    if (InImagePath.EndsWith(".png"))
    {
        return ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
    }
    else if (InImagePath.EndsWith(".jpg") || InImagePath.EndsWith(".jpeg"))
    {
        return ImageWrapperModule.CreateImageWrapper(EImageFormat::JPEG);
    }
    else if (InImagePath.EndsWith(".bmp"))
    {
        return ImageWrapperModule.CreateImageWrapper(EImageFormat::BMP);
    }
    else if (InImagePath.EndsWith(".ico"))
    {
        return ImageWrapperModule.CreateImageWrapper(EImageFormat::ICO);
    }
    else if (InImagePath.EndsWith(".exr"))
    {
        return ImageWrapperModule.CreateImageWrapper(EImageFormat::EXR);
    }
    else if (InImagePath.EndsWith(".icns"))
    {
        return ImageWrapperModule.CreateImageWrapper(EImageFormat::ICNS);
    }
    
    return nullptr;
}

bool UVictoryBPFunctionLibrary::CaptureComponent2D_SaveImage(class USceneCaptureComponent2D* Target, const FString ImagePath, const FLinearColor ClearColour)
{
	// Bad scene capture component! No render target! Stay! Stay! Ok, feed!... wait, where was I?
	if ((Target == nullptr) || (Target->TextureTarget == nullptr))
	{
		return false;
	}
	
	FRenderTarget* RenderTarget = Target->TextureTarget->GameThread_GetRenderTargetResource();
	if (RenderTarget == nullptr)
	{
		return false;
	}

	TArray<FColor> RawPixels;
	
	// Format not supported - use PF_B8G8R8A8.
	if (Target->TextureTarget->GetFormat() != PF_B8G8R8A8)
	{
		// TRACEWARN("Format not supported - use PF_B8G8R8A8.");
		return false;
	}

	if (!RenderTarget->ReadPixels(RawPixels))
	{
		return false;
	}

	// Convert to FColor.
	FColor ClearFColour = ClearColour.ToFColor(false); // FIXME - want sRGB or not?

	for (auto& Pixel : RawPixels)
	{
		// Switch Red/Blue changes.
		const uint8 PR = Pixel.R;
		const uint8 PB = Pixel.B;
		Pixel.R = PB;
		Pixel.B = PR;

		// Set alpha based on RGB values of ClearColour.
		Pixel.A = ((Pixel.R == ClearFColour.R) && (Pixel.G == ClearFColour.G) && (Pixel.B == ClearFColour.B)) ? 0 : 255;
	}
	
	TSharedPtr<IImageWrapper> ImageWrapper = GetImageWrapperByExtention(ImagePath);

	const int32 Width = Target->TextureTarget->SizeX;
	const int32 Height = Target->TextureTarget->SizeY;
	
	if (ImageWrapper.IsValid() && ImageWrapper->SetRaw(&RawPixels[0], RawPixels.Num() * sizeof(FColor), Width, Height, ERGBFormat::RGBA, 8))
	{
		FFileHelper::SaveArrayToFile(ImageWrapper->GetCompressed(), *ImagePath);
		return true;
	}
	
	return false;
}

bool UVictoryBPFunctionLibrary::Capture2D_SaveImage(class ASceneCapture2D* Target, const FString ImagePath, const FLinearColor ClearColour)
{
	return (Target) ? CaptureComponent2D_SaveImage(Target->GetCaptureComponent2D(), ImagePath, ClearColour) : false;
}

UTexture2D* UVictoryBPFunctionLibrary::LoadTexture2D_FromFileByExtension(const FString& ImagePath, bool& IsValid, int32& OutWidth, int32& OutHeight)
{
	UTexture2D* Texture = nullptr;
	IsValid = false;

	// To avoid log spam, make sure it exists before doing anything else.
	if (!FPlatformFileManager::Get().GetPlatformFile().FileExists(*ImagePath))
	{
		return nullptr;
	}

	TArray<uint8> CompressedData;
	if (!FFileHelper::LoadFileToArray(CompressedData, *ImagePath))
	{
		return nullptr;
	}
	
	TSharedPtr<IImageWrapper> ImageWrapper = GetImageWrapperByExtention(ImagePath);

	if (ImageWrapper.IsValid() && ImageWrapper->SetCompressed(CompressedData.GetData(), CompressedData.Num()))
	{ 
		const TArray<uint8>* UncompressedRGBA = nullptr;
		
		if (ImageWrapper->GetRaw(ERGBFormat::RGBA, 8, UncompressedRGBA))
		{
			Texture = UTexture2D::CreateTransient(ImageWrapper->GetWidth(), ImageWrapper->GetHeight(), PF_R8G8B8A8);
			
			if (Texture != nullptr)
			{
				IsValid = true;
				
				OutWidth = ImageWrapper->GetWidth();
				OutHeight = ImageWrapper->GetHeight();

				void* TextureData = Texture->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE);
				FMemory::Memcpy(TextureData, UncompressedRGBA->GetData(), UncompressedRGBA->Num());
				Texture->PlatformData->Mips[0].BulkData.Unlock();
				Texture->UpdateResource();
			}
		}
	}

	return Texture;
}

UUserWidget* UVictoryBPFunctionLibrary::GetFirstWidgetOfClass(UObject* WorldContextObject, TSubclassOf<UUserWidget> WidgetClass, bool TopLevelOnly)
{
	if (!WidgetClass || !WorldContextObject)
	{
		return nullptr;
	}

	const UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	if (!World)
	{
		return nullptr;
	}

	UUserWidget* ResultWidget = nullptr;
	for (TObjectIterator<UUserWidget> Itr; Itr; ++Itr)
	{
		UUserWidget* LiveWidget = *Itr;

		// Skip any widget that's not in the current world context.
		if (LiveWidget->GetWorld() != World)
		{
			continue;
		}

		// Skip any widget that is not a child of the class specified.
		if (!LiveWidget->GetClass()->IsChildOf(WidgetClass))
		{
			continue;
		}

		if (!TopLevelOnly || LiveWidget->IsInViewport())
		{
			ResultWidget = LiveWidget;
			break;
		}
	}

	return ResultWidget;
}

bool UVictoryBPFunctionLibrary::WidgetIsChildOf(UWidget* ChildWidget, UWidget* PossibleParent)
{
	return (ChildWidget && PossibleParent) ? ChildWidget->IsChildOf(PossibleParent) : false;
}

UUserWidget* UVictoryBPFunctionLibrary::WidgetGetParentOfClass(UWidget* ChildWidget, TSubclassOf<UUserWidget> WidgetClass)
{
	UUserWidget* ResultParent = nullptr;

	if (ChildWidget && WidgetClass)
	{
		UWidget* PossibleParent = ChildWidget->GetParent();
		UWidget* NextPossibleParent = nullptr;
		int32 count = 0;

		while (PossibleParent != nullptr)
		{
			// Return once we find a parent of the desired class.
			if (PossibleParent->GetClass()->IsChildOf(WidgetClass))
			{
				ResultParent = Cast<UUserWidget>(PossibleParent);
				break;
			}
			
			NextPossibleParent = PossibleParent->GetParent();

			// If we don't have a parent, follow the outer chain until we find another widget, if at all.
			if (NextPossibleParent == nullptr)
			{
				UWidgetTree* WidgetTree = Cast<UWidgetTree>(PossibleParent->GetOuter());
				if (WidgetTree)
				{
					NextPossibleParent = Cast<UWidget>(WidgetTree->GetOuter());
				}
			}

			PossibleParent = NextPossibleParent;
		}
	}

	return ResultParent;
}
 
void UVictoryBPFunctionLibrary::WidgetGetChildrenOfClass(UWidget* ParentWidget, TArray<UUserWidget*>& ChildWidgets, TSubclassOf<UUserWidget> WidgetClass, bool bImmediateOnly)
{
	ChildWidgets.Empty();

	if (ParentWidget && WidgetClass)
	{
		// Current set of widgets to check
		TInlineComponentArray<UWidget*> WidgetsToCheck;

		// Set of all widgets we have checked
		TInlineComponentArray<UWidget*> CheckedWidgets;

		WidgetsToCheck.Push(ParentWidget);

		// While still work left to do
		while (WidgetsToCheck.Num() > 0)
		{
			// Get the next widgets off the queue
			const bool bAllowShrinking = false;
			UWidget* PossibleParent = WidgetsToCheck.Pop(bAllowShrinking);

			// Add it to the 'checked' set, should not already be there!
			if (!CheckedWidgets.Contains(PossibleParent))
			{
				CheckedWidgets.Add(PossibleParent);

				TArray<UWidget*> Widgets;

				UWidgetTree::GetChildWidgets(PossibleParent, Widgets);

				for (UWidget* Widget : Widgets)
				{
					if (!CheckedWidgets.Contains(Widget))
					{
						// Add any widget that is a child of the class specified.
						if (Widget->GetClass()->IsChildOf(WidgetClass))
						{
							ChildWidgets.Add(Cast<UUserWidget>(Widget));
						}

						// If we're not just looking for our immediate children,
						// add this widget to list of widgets to check next.
						if (!bImmediateOnly)
						{
							WidgetsToCheck.Push(Widget);
						}
					}
				}

				if (bImmediateOnly)
				{
					break;
				}
			}
		}
	}
}

UWidget* UVictoryBPFunctionLibrary::GetWidgetFromName(UUserWidget* ParentUserWidget, const FName& Name)
{
	UWidget* ResultWidget = nullptr;

	if (ParentUserWidget && (Name != NAME_None))
	{
		ResultWidget = ParentUserWidget->GetWidgetFromName(Name);
	}

	return ResultWidget;
}

uint8 UVictoryBPFunctionLibrary::GetGenericTeamId(AActor* Target)
{
	IGenericTeamAgentInterface* TeamAgentInterface = nullptr;
	if (Target)
	{
		TeamAgentInterface = Cast<IGenericTeamAgentInterface>(Target);
	}
	return (TeamAgentInterface != nullptr) ? TeamAgentInterface->GetGenericTeamId() : FGenericTeamId::NoTeam;
}

void UVictoryBPFunctionLibrary::SetGenericTeamId(AActor* Target, uint8 NewTeamId)
{
	if (Target)
	{
		IGenericTeamAgentInterface* TeamAgentInterface = Cast<IGenericTeamAgentInterface>(Target);
		if (TeamAgentInterface != nullptr)
		{
			TeamAgentInterface->SetGenericTeamId(NewTeamId);
		}
	}
}

FLevelStreamInstanceInfo::FLevelStreamInstanceInfo(ULevelStreamingKismet* LevelInstance)
{
	PackageName = LevelInstance->GetWorldAssetPackageFName();
	PackageNameToLoad = LevelInstance->PackageNameToLoad;
	Location = LevelInstance->LevelTransform.GetLocation();
	Rotation = LevelInstance->LevelTransform.GetRotation().Rotator();
	bShouldBeLoaded = LevelInstance->bShouldBeLoaded;
	bShouldBeVisible = LevelInstance->bShouldBeVisible;
	bShouldBlockOnLoad = LevelInstance->bShouldBlockOnLoad;
	LODIndex = LevelInstance->LevelLODIndex;
};

FLevelStreamInstanceInfo UVictoryBPFunctionLibrary::GetLevelInstanceInfo(ULevelStreamingKismet* LevelInstance)
{
	return FLevelStreamInstanceInfo(LevelInstance);
}

void UVictoryBPFunctionLibrary::AddToStreamingLevels(UObject* WorldContextObject, const FLevelStreamInstanceInfo& LevelInstanceInfo)
{
	bool bResult = true;

	UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);

	if (World != nullptr)
	{
		bool bAlreadyExists = false;

		for (auto StreamingLevel : World->StreamingLevels)
		{
			if (StreamingLevel->GetWorldAssetPackageFName() == LevelInstanceInfo.PackageName)
			{
				bAlreadyExists = true;
				// KRIS : Would normally log a warning here! Is there a LogVictory?
				break;
			}
		}
		
		if (!bAlreadyExists)
		{
			FName PackageName = LevelInstanceInfo.PackageName;

			// For PIE Networking: remap the packagename to our local PIE packagename
			FString PackageNameStr = PackageName.ToString();
			if (GEngine->NetworkRemapPath(World->GetNetDriver(), PackageNameStr, true))
			{
				PackageName = FName(*PackageNameStr);
			}

			GEngine->DelayGarbageCollection();

			// Setup streaming level object that will load specified map
			ULevelStreamingKismet* StreamingLevel = NewObject<ULevelStreamingKismet>(World, ULevelStreamingKismet::StaticClass(), NAME_None, RF_Transient, nullptr);
			StreamingLevel->SetWorldAssetByPackageName(PackageName);
			StreamingLevel->LevelColor = FColor::MakeRandomColor();
			StreamingLevel->bShouldBeLoaded = LevelInstanceInfo.bShouldBeLoaded;
			StreamingLevel->bShouldBeVisible = LevelInstanceInfo.bShouldBeVisible;
			StreamingLevel->bShouldBlockOnLoad = LevelInstanceInfo.bShouldBlockOnLoad;
			StreamingLevel->bInitiallyLoaded = true;
			StreamingLevel->bInitiallyVisible = true;

			// Transform
			StreamingLevel->LevelTransform = FTransform(LevelInstanceInfo.Rotation, LevelInstanceInfo.Location);

			// Map to Load
			StreamingLevel->PackageNameToLoad = LevelInstanceInfo.PackageNameToLoad;

			// Add the new level to world.
			World->StreamingLevels.Add(StreamingLevel);

			World->FlushLevelStreaming(EFlushLevelStreamingType::Full);
		}
	}
}


void UVictoryBPFunctionLibrary::RemoveFromStreamingLevels(UObject* WorldContextObject, const FLevelStreamInstanceInfo& LevelInstanceInfo)
{

	UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);

	// Check if the world exists and we have a level to unload
	if (World != nullptr && !LevelInstanceInfo.PackageName.IsNone())
	{

#if WITH_EDITOR
		// If we are using the editor we will use this lambda to remove the play in editor string
		auto GetCorrectPackageName = [&]( FName PackageName) {
			FString PackageNameStr = PackageName.ToString();
			if (GEngine->NetworkRemapPath(World->GetNetDriver(), PackageNameStr, true))
			{
				PackageName = FName(*PackageNameStr);
			}

			return PackageName;
		};
#endif

		// Get the package name that we want to check
		FName PackageNameToCheck = LevelInstanceInfo.PackageName;

#if WITH_EDITOR
		// Remove the play in editor string and client id to be able to use it with replication
		PackageNameToCheck = GetCorrectPackageName(PackageNameToCheck);
#endif

		// Find the level to unload
		for (auto StreamingLevel : World->StreamingLevels)
		{

			FName LoadedPackageName = StreamingLevel->GetWorldAssetPackageFName();

#if WITH_EDITOR
			// Remove the play in editor string and client id to be able to use it with replication
			LoadedPackageName = GetCorrectPackageName(LoadedPackageName);
#endif

			// If we find the level unload it and break
			if(PackageNameToCheck == LoadedPackageName)
			{
				// This unload the level
				StreamingLevel->bShouldBeLoaded = false;
				StreamingLevel->bShouldBeVisible = false;
				// This removes the level from the streaming level list
				StreamingLevel->bIsRequestingUnloadAndRemoval = true;
				// Force a refresh of the world
				World->FlushLevelStreaming(EFlushLevelStreamingType::Full);
				break;
			}
		}
	}
}

bool UVictoryBPFunctionLibrary::GenericArray_SortCompare(const UProperty* LeftProperty, void* LeftValuePtr, const UProperty* RightProperty, void* RightValuePtr)
{
	bool bResult = false;

	if (const UNumericProperty *LeftNumericProperty = Cast<const UNumericProperty>(LeftProperty))
	{
		if (LeftNumericProperty->IsFloatingPoint())
		{
			bResult = (LeftNumericProperty->GetFloatingPointPropertyValue(LeftValuePtr) < Cast<const UNumericProperty>(RightProperty)->GetFloatingPointPropertyValue(RightValuePtr));
		}
		else if (LeftNumericProperty->IsInteger())
		{
			bResult = (LeftNumericProperty->GetSignedIntPropertyValue(LeftValuePtr) < Cast<const UNumericProperty>(RightProperty)->GetSignedIntPropertyValue(RightValuePtr));
		}
	}
	else if (const UBoolProperty* LeftBoolProperty = Cast<const UBoolProperty>(LeftProperty))
	{
		bResult = (!LeftBoolProperty->GetPropertyValue(LeftValuePtr) && Cast<const UBoolProperty>(RightProperty)->GetPropertyValue(RightValuePtr));
	}
	else if (const UNameProperty* LeftNameProperty = Cast<const UNameProperty>(LeftProperty))
	{
		bResult = (LeftNameProperty->GetPropertyValue(LeftValuePtr).ToString() < Cast<const UNameProperty>(RightProperty)->GetPropertyValue(RightValuePtr).ToString());
	}
	else if (const UStrProperty* LeftStringProperty = Cast<const UStrProperty>(LeftProperty))
	{
		bResult = (LeftStringProperty->GetPropertyValue(LeftValuePtr) < Cast<const UStrProperty>(RightProperty)->GetPropertyValue(RightValuePtr));
	}
	else if (const UTextProperty* LeftTextProperty = Cast<const UTextProperty>(LeftProperty))
	{
		bResult = (LeftTextProperty->GetPropertyValue(LeftValuePtr).ToString() < Cast<const UTextProperty>(RightProperty)->GetPropertyValue(RightValuePtr).ToString());
	}

	return bResult;
}

void UVictoryBPFunctionLibrary::GenericArray_Sort(void* TargetArray, const UArrayProperty* ArrayProp, bool bAscendingOrder /* = true */, FName VariableName /* = NAME_None */)
{
	if (TargetArray)
	{
		FScriptArrayHelper ArrayHelper(ArrayProp, TargetArray);
		const int32 LastIndex = ArrayHelper.Num();

		if (const UObjectProperty* ObjectProperty = Cast<const UObjectProperty>(ArrayProp->Inner))
		{
			for (int32 i = 0; i < LastIndex; ++i)
			{
				for (int32 j = 0; j < LastIndex - 1 - i; ++j)
				{
					UObject* LeftObject = ObjectProperty->GetObjectPropertyValue(ArrayHelper.GetRawPtr(j));
					UObject* RightObject = ObjectProperty->GetObjectPropertyValue(ArrayHelper.GetRawPtr(j + 1));

					UProperty* LeftProperty = FindField<UProperty>(LeftObject->GetClass(), VariableName);
					UProperty* RightProperty = FindField<UProperty>(RightObject->GetClass(), VariableName);
						
					if (LeftProperty && RightProperty)
					{
						void* LeftValuePtr = LeftProperty->ContainerPtrToValuePtr<void>(LeftObject);
						void* RightValuePtr = RightProperty->ContainerPtrToValuePtr<void>(RightObject);

						if (GenericArray_SortCompare(LeftProperty, LeftValuePtr, RightProperty, RightValuePtr) != bAscendingOrder)
						{
							ArrayHelper.SwapValues(j, j + 1);
						}
					}
				}
			}
		}
		else
		{
			UProperty* Property = nullptr;

			if (const UStructProperty* StructProperty = Cast<const UStructProperty>(ArrayProp->Inner))
			{
				Property = FindField<UProperty>(StructProperty->Struct, VariableName);
			}
			else
			{
				Property = ArrayProp->Inner;
			}

			if (Property)
			{
				for (int32 i = 0; i < LastIndex; ++i)
				{
					for (int32 j = 0; j < LastIndex - 1 - i; ++j)
					{
						void* LeftValuePtr = Property->ContainerPtrToValuePtr<void>(ArrayHelper.GetRawPtr(j));
						void* RightValuePtr = Property->ContainerPtrToValuePtr<void>(ArrayHelper.GetRawPtr(j + 1));

						if (GenericArray_SortCompare(Property, LeftValuePtr, Property, RightValuePtr) != bAscendingOrder)
						{
							ArrayHelper.SwapValues(j, j + 1);
						}
					}
				}
			}
		}
	}
}

void UVictoryBPFunctionLibrary::Array_Sort(const TArray<int32>& TargetArray, bool bAscendingOrder /* = true */, FName VariableName /* = NAME_None */)
{
	// We should never hit these!  They're stubs to avoid NoExport on the class.  Call the Generic* equivalent instead
	check(0);
}

void UVictoryBPFunctionLibrary::Actor_PrestreamTextures(AActor* Target, float Seconds, bool bEnableStreaming /*= true*/, int32 CinematicTextureGroups /*= 0*/)
{
	if (Target != nullptr)
	{
		Target->PrestreamTextures(Seconds, bEnableStreaming, CinematicTextureGroups);
	}
}

void UVictoryBPFunctionLibrary::Component_PrestreamTextures(UMeshComponent* Target, float Seconds, bool bEnableStreaming /*= true*/, int32 CinematicTextureGroups /*= 0*/)
{
	if ((Target != nullptr) && (Target->IsRegistered()))
	{
		float Duration = Seconds;

		if (bEnableStreaming)
		{
			// A Seconds==0.0f, it means infinite (e.g. 30 days)
			Duration = FMath::IsNearlyZero(Seconds) ? (60.0f*60.0f*24.0f*30.0f) : Seconds;
		}

		Target->PrestreamTextures(Duration, false, CinematicTextureGroups);
	}
}

bool UVictoryBPFunctionLibrary::GetViewportPosition(UObject* WorldContextObject, const FVector2D& ScreenPosition, FVector2D& OutViewportPosition)
{
	OutViewportPosition = FVector2D::ZeroVector;

	if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull))
	{
		FVector2D ViewportSize;
		World->GetGameViewport()->GetViewportSize(ViewportSize);
		OutViewportPosition = World->GetGameViewport()->Viewport->VirtualDesktopPixelToViewport(FIntPoint(ScreenPosition.X, ScreenPosition.Y)) * ViewportSize;
	}

	return !OutViewportPosition.IsZero();
}

bool UVictoryBPFunctionLibrary::GetViewportPositionHitResultByChannel(UObject* WorldContextObject, const FVector2D& ViewportPosition, ECollisionChannel TraceChannel, bool bTraceComplex, FHitResult& OutHitResult)
{
	bool bResult = false;

	if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull))
	{
		bResult = World->GetFirstPlayerController()->GetHitResultAtScreenPosition(ViewportPosition, TraceChannel, bTraceComplex, OutHitResult);
	}

	if (!bResult)	// For Blueprint users
	{
		OutHitResult = FHitResult();
	}

	return bResult;
}

bool UVictoryBPFunctionLibrary::ViewportPositionDeproject(UObject* WorldContextObject, const FVector2D& ViewportPosition, FVector& OutWorldOrigin, FVector& OutWorldDirection)
{
	bool bResult = false;

	if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull))
	{
		bResult = UGameplayStatics::DeprojectScreenToWorld(World->GetFirstPlayerController(), ViewportPosition, OutWorldOrigin, OutWorldDirection);
	}

	return bResult;
}

UPanelSlot* UVictoryBPFunctionLibrary::InsertChildAt(UWidget* Parent, int32 Index, UWidget* Content)
{
	UPanelSlot* ResultSlot = nullptr;

	if (Parent && Content)
	{
		if (UPanelWidget* PanelWidget = Cast<UPanelWidget>(Parent))
		{
			Index = FMath::Clamp(Index, 0, FMath::Max(0, PanelWidget->GetChildrenCount() - 1));

			if (PanelWidget->GetChildIndex(Content) != Index)
			{
				ResultSlot = PanelWidget->AddChild(Content);

				UWidget* SlotContent = nullptr;

				for (int32 i = Index; i < PanelWidget->GetChildrenCount(); ++i)
				{
					SlotContent = PanelWidget->GetChildAt(Index);
					if (SlotContent == Content)
					{
						break;
					}
					PanelWidget->RemoveChild(SlotContent);
					PanelWidget->AddChild(SlotContent);
				}
			}
		}
	}

	return ResultSlot;
}

void UVictoryBPFunctionLibrary::FlushPressedKeys(class APlayerController* PlayerController)
{
	if (PlayerController)
	{
		PlayerController->FlushPressedKeys();
	}
}

FVector UVictoryBPFunctionLibrary::GetVectorRelativeLocation(FVector ParentLocation, FRotator ParentRotation, FVector ChildLocation)
{
	return ParentRotation.UnrotateVector(ChildLocation - ParentLocation);
}

FVector UVictoryBPFunctionLibrary::GetComponentRelativeLocation(class USceneComponent* ParentComponent, class USceneComponent* ChildComponent)
{
	return (ParentComponent && ChildComponent) ? GetVectorRelativeLocation(ParentComponent->GetComponentLocation(), ParentComponent->GetComponentRotation(), ChildComponent->GetComponentLocation()) : FVector::ZeroVector;
}

FVector UVictoryBPFunctionLibrary::GetActorRelativeLocation(class AActor* ParentActor, class AActor* ChildActor)
{
	return (ParentActor && ChildActor) ? GetVectorRelativeLocation(ParentActor->GetActorLocation(), ParentActor->GetActorRotation(), ChildActor->GetActorLocation()) : FVector::ZeroVector;
}

FRotator UVictoryBPFunctionLibrary::GetRotatorRelativeRotation(FRotator ParentRotation, FRotator ChildRotation)
{
	const FRotator RelativeRotation = (FQuatRotationMatrix(ChildRotation.Quaternion()) * FQuatRotationMatrix(ParentRotation.Quaternion()).GetTransposed()).Rotator();

	return RelativeRotation;
}

FRotator UVictoryBPFunctionLibrary::GetComponentRelativeRotation(class USceneComponent* ParentComponent, class USceneComponent* ChildComponent)
{
	return (ParentComponent && ChildComponent) ? GetRotatorRelativeRotation(ParentComponent->GetComponentRotation(), ChildComponent->GetComponentRotation())  : FRotator::ZeroRotator;
}

FRotator UVictoryBPFunctionLibrary::GetActorRelativeRotation(class AActor* ParentActor, class AActor* ChildActor)
{
	return (ParentActor && ChildActor) ? GetRotatorRelativeRotation(ParentActor->GetActorRotation(), ChildActor->GetActorRotation()) : FRotator::ZeroRotator;
}

float UVictoryBPFunctionLibrary::HorizontalFOV(float VerticalFOV, float AspectRatio)
{
	VerticalFOV = FMath::DegreesToRadians(VerticalFOV);
	return FMath::RadiansToDegrees(2 * FMath::Atan2(FMath::Tan(VerticalFOV * 0.5f) * AspectRatio, 1));
}

float UVictoryBPFunctionLibrary::VerticalFOV(float HorizontalFOV, float AspectRatio)
{
	HorizontalFOV = FMath::DegreesToRadians(HorizontalFOV);
	return FMath::RadiansToDegrees(2 * FMath::Atan2(FMath::Tan(HorizontalFOV * 0.5f) * (1 / AspectRatio), 1));
}

bool UVictoryBPFunctionLibrary::StringIsEmpty(const FString& Target)
{
	return Target.IsEmpty();
}

//~~~~~~~~~ END OF CONTRIBUTED BY KRIS ~~~~~~~~~~~
 


//~~~ Inspired by Sahkan ~~~
void UVictoryBPFunctionLibrary::Actor__GetAttachedActors(AActor* ParentActor,TArray<AActor*>& ActorsArray)
{
	if(!ParentActor) return;
	//~~~~~~~~~~~~
	
	ActorsArray.Empty(); 
	ParentActor->GetAttachedActors(ActorsArray);
}
 
void UVictoryBPFunctionLibrary::SetBloomIntensity(APostProcessVolume* PostProcessVolume,float Intensity)
{
	if(!PostProcessVolume) return;
	//~~~~~~~~~~~~~~~~
	
	PostProcessVolume->Settings.bOverride_BloomIntensity 	= true;
	PostProcessVolume->Settings.BloomIntensity 				= Intensity;
}


//~~~ Key To Truth ~~~
//.cpp
//Append different text strings with optional pins.
FString UVictoryBPFunctionLibrary::AppendMultiple(FString A, FString B)
{  
    FString Result = "";

	Result += A;
    Result += B;
	 
    return Result;
}

//~~~ Mhousse ~~~


//TESTING
static void TESTINGInternalDrawDebugCircle(const UWorld* InWorld, const FMatrix& TransformMatrix, float Radius, int32 Segments, const FColor& Color, bool bPersistentLines, float LifeTime, uint8 DepthPriority, float Thickness=0)
{
	//this is how you can make cpp only internal functions!
	
}



#undef LOCTEXT_NAMESPACE
