From cbe5871372ed4b21f7bbaba9370d7d4f4d0bfbe1 Mon Sep 17 00:00:00 2001 From: "DOMENIS\\jaredmichael" Date: Wed, 16 Apr 2025 13:59:37 +0300 Subject: [PATCH] adding plugin --- .gitignore | 3 +- Config/DefaultEngine.ini | 14 + Config/DefaultInput.ini | 86 + .../Content/ReadMeForZipFile.txt | 9 + .../Resources/Icon128.png | Bin 0 -> 45224 bytes .../VictoryBPLibraryUE55/Resources/Thumbs.db | Bin 0 -> 26112 bytes .../Private/VictoryBPLibrary.cpp | 22 + .../Public/RamaColorPicker.cpp | 102 + .../VictoryBPLibrary/Public/RamaColorPicker.h | 76 + .../VictoryBPLibrary/Public/SJoyColorPicker.h | 47 + .../Public/VictoryBPFunctionLibrary.cpp | 2408 +++++++++++++++++ .../Public/VictoryBPFunctionLibrary.h | 654 +++++ .../Public/VictoryBPFunctionLibrary_WinOS.cpp | 68 + .../Public/VictoryBPLibrary.h | 14 + .../VictoryBPLibrary.Build.cs | 81 + .../VictoryBPLibrary.uplugin | 32 + Tracker_for5.uproject | 11 +- 17 files changed, 3616 insertions(+), 11 deletions(-) create mode 100644 Config/DefaultEngine.ini create mode 100644 Config/DefaultInput.ini create mode 100644 Plugins/VictoryBPLibraryUE55/Content/ReadMeForZipFile.txt create mode 100644 Plugins/VictoryBPLibraryUE55/Resources/Icon128.png create mode 100644 Plugins/VictoryBPLibraryUE55/Resources/Thumbs.db create mode 100644 Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Private/VictoryBPLibrary.cpp create mode 100644 Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/RamaColorPicker.cpp create mode 100644 Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/RamaColorPicker.h create mode 100644 Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/SJoyColorPicker.h create mode 100644 Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/VictoryBPFunctionLibrary.cpp create mode 100644 Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/VictoryBPFunctionLibrary.h create mode 100644 Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/VictoryBPFunctionLibrary_WinOS.cpp create mode 100644 Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/VictoryBPLibrary.h create mode 100644 Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/VictoryBPLibrary.Build.cs create mode 100644 Plugins/VictoryBPLibraryUE55/VictoryBPLibrary.uplugin diff --git a/.gitignore b/.gitignore index f4334a5..987edb9 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ Saved *.sln *.suo *.xcodeproj -*.xcworkspace \ No newline at end of file +*.xcworkspace +*.zip \ No newline at end of file diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini new file mode 100644 index 0000000..e417fa8 --- /dev/null +++ b/Config/DefaultEngine.ini @@ -0,0 +1,14 @@ +[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] +bEnablePlugin=True +bAllowNetworkConnection=True +SecurityToken=4AFB5E884C461B72D08053B81BFFED58 +bIncludeInShipping=False +bAllowExternalStartInShipping=False +bCompileAFSProject=False +bUseCompression=False +bLogFiles=False +bReportStats=False +ConnectionType=USBOnly +bUseManualIPAddress=False +ManualIPAddress= + diff --git a/Config/DefaultInput.ini b/Config/DefaultInput.ini new file mode 100644 index 0000000..1ff89cc --- /dev/null +++ b/Config/DefaultInput.ini @@ -0,0 +1,86 @@ +[/Script/Engine.InputSettings] +-AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) +-AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) +-AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) ++AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseWheelAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_Special_Left_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_Special_Left_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) +bAltEnterTogglesFullscreen=True +bF11TogglesFullscreen=True +bUseMouseForTouch=False +bEnableMouseSmoothing=True +bEnableFOVScaling=True +bCaptureMouseOnLaunch=True +bEnableLegacyInputScales=True +bEnableMotionControls=True +bFilterInputByPlatformUser=False +bEnableInputDeviceSubsystem=True +bShouldFlushPressedKeysOnViewportFocusLost=True +bEnableDynamicComponentInputBinding=True +bAlwaysShowTouchInterface=False +bShowConsoleOnFourFingerTap=True +bEnableGestureRecognizer=False +bUseAutocorrect=False +DefaultViewportMouseCaptureMode=CapturePermanently_IncludingInitialMouseDown +DefaultViewportMouseLockMode=LockOnCapture +FOVScale=0.011110 +DoubleClickTime=0.200000 +DefaultPlayerInputClass=/Script/EnhancedInput.EnhancedPlayerInput +DefaultInputComponentClass=/Script/EnhancedInput.EnhancedInputComponent +DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.DefaultVirtualJoysticks +-ConsoleKeys=Tilde ++ConsoleKeys=Tilde + diff --git a/Plugins/VictoryBPLibraryUE55/Content/ReadMeForZipFile.txt b/Plugins/VictoryBPLibraryUE55/Content/ReadMeForZipFile.txt new file mode 100644 index 0000000..e2b94fd --- /dev/null +++ b/Plugins/VictoryBPLibraryUE55/Content/ReadMeForZipFile.txt @@ -0,0 +1,9 @@ +Create Static Mesh Assets From Dynamic Mesh Actor BP Constructor Scripts! + +Required Plugin (Comes With UE5) + +You will want to make sure you have the Geometry Script Plugin enabled for your project, before unzipping and loading this content! + +♥ + +Rama diff --git a/Plugins/VictoryBPLibraryUE55/Resources/Icon128.png b/Plugins/VictoryBPLibraryUE55/Resources/Icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..155480accb23c218df27aeee0a01f22b5fc37455 GIT binary patch literal 45224 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 : NAME_None; + + //Update Picker to JoyColor property change! + if (PropertyName == TEXT("JoyColor")) + { + SetJoyColor(JoyColor,true); + } +} +#endif + +void URamaColorPicker::SetJoyColor(FLinearColor NewColor, bool SkipAnimation) +{ + if(!MySlateColorPicker.IsValid()) + { + return; + } + + //Skip Anim? + if(SkipAnimation) + { + MySlateColorPicker->InstantColor = NewColor; + MySlateColorPicker->Animation_SkipToFinalForOneTick = true; //See SJoyColorPicker.h + } + else + { + //Set! + MySlateColorPicker->SetColorRGB(NewColor); + } +} + +void URamaColorPicker::ColorUpdated(FLinearColor NewValue) +{ + JoyColor = NewValue; + + if(OnColorChanged.IsBound()) + { + OnColorChanged.Broadcast(JoyColor); + } +} + +TSharedRef URamaColorPicker::RebuildWidget() +{ + MySlateColorPicker = SNew( SJoyColorPicker ) + .TargetColorAttribute( JoyColor ) + .OnColorCommitted( FOnLinearColorValueChanged::CreateUObject( this, &URamaColorPicker::ColorUpdated) ); + + return MySlateColorPicker.ToSharedRef(); +} + +//Release +void URamaColorPicker::ReleaseSlateResources(bool bReleaseChildren) +{ + Super::ReleaseSlateResources(bReleaseChildren); + + if(MySlateColorPicker.IsValid()) + { + MySlateColorPicker.Reset(); + } +} + +void URamaColorPicker::SynchronizeProperties() +{ + Super::SynchronizeProperties(); + + SetJoyColor(JoyColor,true); +} + + +///////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/RamaColorPicker.h b/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/RamaColorPicker.h new file mode 100644 index 0000000..c4e0028 --- /dev/null +++ b/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/RamaColorPicker.h @@ -0,0 +1,76 @@ +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Styling/SlateColor.h" +#include "Styling/SlateTypes.h" +#include "Widgets/SWidget.h" +#include "Components/Widget.h" + +#include "RamaColorPicker.generated.h" + +class SJoyColorPicker; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnJoyColorChangedEvent, const FLinearColor&, NewColor); + +/** + * Rama Color Picker For You! ♥ Rama + */ +UCLASS() +class VICTORYBPLIBRARY_API URamaColorPicker : public UWidget +{ + GENERATED_UCLASS_BODY() + +public: + + /** The currently Chosen Color for this Color Picker! ♥ Rama*/ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Rama Color Picker",meta=(Keywords="getcolor")) + FLinearColor JoyColor = FLinearColor::Red; + + /** Called whenever the color is changed programmatically or interactively by the user */ + UPROPERTY(BlueprintAssignable, Category="Rama Color Picker", meta=(DisplayName="OnColorChanged (Rama Color Picker)")) + FOnJoyColorChangedEvent OnColorChanged; + + /** + * Directly sets the current color, for saving user preferences of chosen color, or loading existing color of an in-game clicked actor! + * @param InColor The color to assign to the widget + */ + UFUNCTION(BlueprintCallable, Category = "Rama Color Picker",meta=(Keywords="setcolor")) + void SetJoyColor(FLinearColor NewColor, bool SkipAnimation=false); + +public: + void ColorUpdated(FLinearColor NewValue); + +#if WITH_EDITOR +public: + + // UWidget interface + //virtual const FSlateBrush* GetEditorIcon() override; + virtual const FText GetPaletteCategory() override; + // End UWidget interface + + // UObject interface + virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; + // End of UObject interface + +#endif + +public: + + //~ Begin UWidget Interface + virtual void SynchronizeProperties() override; + //~ End UWidget Interface + +protected: + //~ Begin UWidget Interface + virtual TSharedRef RebuildWidget() override; + // End of UWidget + + // UVisual interface + virtual void ReleaseSlateResources(bool bReleaseChildren) override; + // End of UVisual interface + +protected: + TSharedPtr MySlateColorPicker; + +}; diff --git a/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/SJoyColorPicker.h b/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/SJoyColorPicker.h new file mode 100644 index 0000000..20be5a2 --- /dev/null +++ b/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/SJoyColorPicker.h @@ -0,0 +1,47 @@ +/* + By Rama for You + + You are welcome to use this code anywhere as long as you include this notice. +*/ + +#pragma once + +//UE5 Color Picker +// Requires module in build.cs +// APPFRAMEWORK +#include "Runtime/AppFramework/Public/Widgets/Colors/SColorPicker.h" + +class SJoyColorPicker + : public SColorPicker +{ + typedef SColorPicker Super; + +public: //! <~~~~~ + FORCEINLINE void SetColorRGB(const FLinearColor& NewColor) + { + //This is protected in SColorPicker + // so can't call it from UMG component + SetNewTargetColorRGB( NewColor, true ); //Force Update + } + +//Animation +public: + + FLinearColor InstantColor; + bool Animation_SkipToFinalForOneTick = false; + virtual void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) override + { + //Skip to final, then resume normal animation behavior + if(Animation_SkipToFinalForOneTick) + { + Animation_SkipToFinalForOneTick = false; + Super::Tick(AllottedGeometry, InCurrentTime, 10000); //<~~~ because all the required vars like CurrentTime are private :) + SetColorRGB(InstantColor); + return; + //~~~~ + } + + //Animate normally! + Super::Tick(AllottedGeometry, InCurrentTime, InDeltaTime); + } +}; diff --git a/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/VictoryBPFunctionLibrary.cpp b/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/VictoryBPFunctionLibrary.cpp new file mode 100644 index 0000000..f2efbfb --- /dev/null +++ b/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/VictoryBPFunctionLibrary.cpp @@ -0,0 +1,2408 @@ +// Copyright Rama All Rights Reserved. + +#include "VictoryBPFunctionLibrary.h" +#include "VictoryBPLibrary.h" + +//For GEngine-> +#include "Engine/Engine.h" + +//My Favorites! <3 Rama +#include "UObject/UObjectIterator.h" +#include "EngineUtils.h" + +//UE UI +#include "Framework/Application/SlateApplication.h" + +//Input +#include "UnrealClient.h" +#include "GameFramework/PlayerInput.h" + +//Yippeee! +#include "Kismet/GameplayStatics.h" + +//OS +#include "Misc/Paths.h" +#include "Misc/FileHelper.h" +#include "HAL/FileManager.h" +#include "HAL/PlatformFilemanager.h" +#include "HAL/PlatformApplicationMisc.h" + +//Capture2D +#include "Engine/Texture2D.h" +#include "Components/SceneCaptureComponent2D.h" +#include "Engine/TextureRenderTarget2D.h" +#include "Engine/SceneCapture2D.h" + +//Char +#include "GameFramework/Character.h" + +//Skel +#include "Engine/SkeletalMesh.h" +#include "Components/SkeletalMeshComponent.h" + +//Static Mesh Vertex Positions and Other Geometry Info +#include "Engine/StaticMesh.h" +#include "StaticMeshDescription.h" +#include "Components/StaticMeshComponent.h" + +//~~~ CreateStaticMeshAssetFromDynamicMesh ~~~ +#include "Runtime/GeometryFramework/Public/Components/DynamicMeshComponent.h" + +//Runtime +//Engine\Plugins\Runtime\MeshModelingToolset\Source\ModelingComponents\Public\ModelingObjectsCreationAPI.h +#include "ModelingObjectsCreationAPI.h" + +//Editor +#if WITH_EDITOR + //"ModelingComponentsEditorOnly" in build.cs ♥ Rama + #include "AssetUtils/CreateStaticMeshUtil.h" +#endif +//~~~~ End CreateStaticMeshAssetFromDynamicMesh ~~~ + + +#include "TextureResource.h" + +//~~~ Image Wrapper ~~~ +#include "ImageUtils.h" +#include "IImageWrapper.h" +#include "IImageWrapperModule.h" +//~~~ Image Wrapper ~~~ + +//World +#include "Engine/LevelStreaming.h" + +//Config +#include "Misc/ConfigCacheIni.h" + +//For PIE error messages +#include "Runtime/Core/Public/Logging/MessageLog.h" +#define LOCTEXT_NAMESPACE "VictoryBPLibrary" + + +bool URamaVictoryPluginCreateProcessPipe::CreatePipe() +{ + if(PipeIsValid()) + { + //Ignore repeat creates without a close inbetween <3 Rama + return true; + } + return FPlatformProcess::CreatePipe( ReadPipe, WritePipe ); +} +void URamaVictoryPluginCreateProcessPipe::ClosePipe() +{ + if(PipeIsValid()) + { + FPlatformProcess::ClosePipe(ReadPipe, WritePipe); + ReadPipe = nullptr; + WritePipe = nullptr; + } +} +bool URamaVictoryPluginCreateProcessPipe::ReadFromPipe(FString& PipeContents) +{ + PipeContents = ""; + + if(!PipeIsValid()) + { + return false; + } + PipeContents = FPlatformProcess::ReadPipe(ReadPipe); + return true; +} +bool URamaVictoryPluginCreateProcessPipe::PipeIsValid() +{ + return ReadPipe != nullptr && WritePipe != nullptr; +} + +void URamaVictoryPluginCreateProcessPipe::BeginDestroy() +{ + Super::BeginDestroy(); + //~~~~~~~~~~~~~~~~~~~~ + + //Close pipe if it was still open! ♥ Rama + ClosePipe(); +} + + +/* + ~~~ Rama File Operations CopyRight ~~~ + + If you use any of my file operation code below, + please credit me somewhere appropriate as "Rama" +*/ +template +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 +PlatformFileFunctor MakeDirectoryVisitor(Functor&& FunctorInstance) +{ + return PlatformFileFunctor(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); +} + +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(const FObjectInitializer& ObjectInitializer) +: Super(ObjectInitializer) +{ + +} + + +//~~~~~~~~~~~~~~~~~~~ +// Joy Geo +//~~~~~~~~~~~~~~~~~~~ + +UStaticMesh* UVictoryBPFunctionLibrary::CreateStaticMeshAssetFromDynamicMesh( + FString ContentFolderPath, + UDynamicMeshComponent* DynamicMeshComp, + FString& Status, + FString& NewAssetFilePath, + bool& Success +){ + NewAssetFilePath = ""; + + #if WITH_EDITOR + + //Comp? + if(!DynamicMeshComp || !DynamicMeshComp->GetDynamicMesh()) + { + Status = "No valid Dynamic Mesh Component was supplied!"; + Success = false; + return nullptr; + } + + //No Triangles? + if(DynamicMeshComp->GetDynamicMesh()->IsEmpty()) + { + Status = "Dynamic Mesh has no triangles!"; + Success = false; + return nullptr; + } + + //Make sure to remove extension if user added it ♥ Rama + + //File without any extension + ContentFolderPath.ReplaceInline(TEXT(".uasset"),TEXT("")); + + //~~~ Create possibly numbered new filename, if supplied exists! ♥ Rama + FString FinalRelativePath = ""; + bool FolderTreeCreated = UVictoryBPFunctionLibrary::GenerateUniqueContentRelativeFileName(ContentFolderPath + ".uasset",FinalRelativePath,NewAssetFilePath); + + if(!FolderTreeCreated) + { + Status = "Could not create the specified directory tree"; + Success = false; + return nullptr; + } + + //Remove ext before sending to AssetUtils + //// Path is now Relative with no extension, possibly with 1,2,3 added for each create event in editor! <3 Rama + FinalRelativePath.ReplaceInline(TEXT(".uasset"),TEXT("")); + + //~~~ End of file path input handling ♥ Rama ~~~ + + //Create Mesh Base Params + FCreateMeshObjectParams CreateMeshParams; + + CreateMeshParams.BaseName = FinalRelativePath; + CreateMeshParams.TargetWorld = nullptr; + + //~~~ Materials ~~~ + DynamicMeshComp->ValidateMaterialSlots(); + + for(int32 v = 0; v < DynamicMeshComp->GetNumMaterials() ; v++) + { + CreateMeshParams.Materials.Add(DynamicMeshComp->GetMaterial(v)); + } + + //Set from FDynamicMesh3 (the actual mesh herself, not from a MeshDescription) + CreateMeshParams.SetMesh(DynamicMeshComp->GetMesh()); + + //Ensure Set to DynamicMesh + CreateMeshParams.MeshType = ECreateMeshObjectSourceMeshType::DynamicMesh; + + //~~~ + //~~~ + //~~~ + + //~~~~~~~~~~~~~~~~~ + // Code from UE_5.0\Engine\Plugins\Runtime\MeshModelingToolset\Source\ModelingComponentsEditorOnly\PublicEditorModelingObjectsCreationAPI.cpp + + //Static Mesh! + //CreateMeshObjectResult = EditorCreateMeshAPI->CreateStaticMeshAsset(CreateMeshParams); + + //Static Asset Options + UE::AssetUtils::FStaticMeshAssetOptions AssetOptions; + AssetOptions.NewAssetPath = "/Game/" + CreateMeshParams.BaseName; + + //Ensure no // + FPaths::RemoveDuplicateSlashes(AssetOptions.NewAssetPath); + + AssetOptions.NumSourceModels = 1; + AssetOptions.NumMaterialSlots = CreateMeshParams.Materials.Num(); + + //Got rid of FilterMaterials part <3 Rama + AssetOptions.AssetMaterials = (CreateMeshParams.AssetMaterials.Num() == AssetOptions.NumMaterialSlots) + ? CreateMeshParams.AssetMaterials + : CreateMeshParams.Materials; + + AssetOptions.bEnableRecomputeNormals = CreateMeshParams.bEnableRecomputeNormals; + AssetOptions.bEnableRecomputeTangents = CreateMeshParams.bEnableRecomputeTangents; + AssetOptions.bGenerateNaniteEnabledMesh = CreateMeshParams.bEnableNanite; + //AssetOptions.NaniteProxyTrianglePercent = CreateMeshParams.NaniteProxyTrianglePercent; + + AssetOptions.bCreatePhysicsBody = CreateMeshParams.bEnableCollision; + AssetOptions.CollisionType = CreateMeshParams.CollisionMode; + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //Dynamic Mesh! ♥ Rama + FDynamicMesh3* DynamicMesh = &CreateMeshParams.DynamicMesh.GetValue(); + AssetOptions.SourceMeshes.DynamicMeshes.Add(DynamicMesh); + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + //Static Mesh Result + UE::AssetUtils::FStaticMeshResults ResultData; + + //========== + //CREATE!!! + UE::AssetUtils::ECreateStaticMeshResult AssetResult = UE::AssetUtils::CreateStaticMeshAsset(AssetOptions, ResultData); + //========== + + if (AssetResult != UE::AssetUtils::ECreateStaticMeshResult::Ok) + { + Status = "UE::AssetUtils::ECreateStaticMeshResult is ECreateModelingObjectResult::Failed_AssetCreationFailed"; + Success = false; + return nullptr; + } + + // End of code from PublicEditorModelingObjectsCreationAPI.cpp + //~~~~~~~~~~~~~~~~~ + + Status = "Victory!"; + Success = true; + return ResultData.StaticMesh; + #endif + + Status = "This node is for Editor Builds only, but does create static mesh assets that can ship with your packaged game! ♥ Rama"; + Success = false; + return nullptr; +} + +bool UVictoryBPFunctionLibrary::GetStaticMeshVertexLocations(UStaticMeshComponent* Comp, TArray& VertexPositions, int32 LodIndex) +{ + if(!Comp) + { + return false; + } + + TObjectPtr MeshPtr = Comp->GetStaticMesh(); + if(!MeshPtr) + { + return false; + } + + UStaticMeshDescription* Desc = MeshPtr->GetStaticMeshDescription(LodIndex); + if(!Desc) + { + return false; + } + + FTransform WorldTransform = Comp->GetComponentTransform(); + FVertexArray& Verts = Desc->Vertices(); + for (FVertexID EachVertId : Verts.GetElementIDs()) + { + VertexPositions.Add( + WorldTransform.TransformPosition( Desc->GetVertexPosition(EachVertId) ) + ); + } + + return true; +} + + +//~~~~~~~~~~~~~~~~~~~ +// Load Object +//~~~~~~~~~~~~~~~~~~~ + +UObject* UVictoryBPFunctionLibrary::LoadObjectFromAssetPath(TSubclassOf 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; + //~~~~~~~~~~~~~~~~~~~~~~~~~ + + FSoftObjectPath ThePath = FSoftObjectPath(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); +} + +//~~~~~~ +// Math +//~~~~~~ +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; +} + + +//~~~~~~~~~~~~ +// UMG +//~~~~~~~~~~~~ +UUserWidget* UVictoryBPFunctionLibrary::GetFirstWidgetOfClass(UObject* WorldContextObject, TSubclassOf 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 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) + { + //only add top level widgets + if(LiveWidget->IsInViewport()) + { + ResultWidget = LiveWidget; + break; + } + } + else + { + ResultWidget = LiveWidget; + break; + } + } + + return ResultWidget; +} + +void UVictoryBPFunctionLibrary::WidgetGetChildrenOfClass(UWidget* ParentWidget, TArray& ChildWidgets, TSubclassOf WidgetClass, bool bImmediateOnly) +{ + ChildWidgets.Empty(); + + if (ParentWidget && WidgetClass) + { + // Current set of widgets to check + TInlineComponentArray WidgetsToCheck; + + // Set of all widgets we have checked + TInlineComponentArray 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 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(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; + } + } + } + } +} + + +UUserWidget* UVictoryBPFunctionLibrary::WidgetGetParentOfClass(UWidget* ChildWidget, TSubclassOf 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(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(PossibleParent->GetOuter()); + if (WidgetTree) + { + NextPossibleParent = Cast(WidgetTree->GetOuter()); + } + } + + PossibleParent = NextPossibleParent; + } + } + + return ResultParent; +} + + +UWidget* UVictoryBPFunctionLibrary::GetWidgetFromName(UUserWidget* ParentUserWidget, const FName& Name) +{ + UWidget* ResultWidget = nullptr; + + if (ParentUserWidget && (Name != NAME_None)) + { + ResultWidget = ParentUserWidget->GetWidgetFromName(Name); + } + + return ResultWidget; +} + + +bool UVictoryBPFunctionLibrary::IsWidgetOfClassInViewport(UObject* WorldContextObject, TSubclassOf WidgetClass) +{ + if(!WidgetClass) return false; + if(!WorldContextObject) return false; + + UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull); + if(!World) return false; + //~~~~~~~~~~~ + + for(TObjectIterator Itr; Itr; ++Itr) + { + if(Itr->GetWorld() != World) continue; + //~~~~~~~~~~~~~~~~~~~~~ + + if( ! Itr->IsA(WidgetClass)) continue; + //~~~~~~~~~~~~~~~~~~~ + + if(Itr->IsInViewport()) //IsInViewport in 4.6 + { + return true; + } + } + + return false; +} + +void UVictoryBPFunctionLibrary::RemoveAllWidgetsOfClass(UObject* WorldContextObject, TSubclassOf WidgetClass) +{ + if(!WidgetClass) return; + if(!WorldContextObject) return; + + UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull); + if(!World) return; + //~~~~~~~~~~~ + + for(TObjectIterator 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->RemoveFromParent(); + } + } +} + +void UVictoryBPFunctionLibrary::VictorySimulateMouseWheel(const float& Delta) +{ + FSlateApplication::Get().OnMouseWheel(int32(Delta)); +} +void UVictoryBPFunctionLibrary::VictorySimulateKeyPress(APlayerController* ThePC, FKey Key, EInputEvent EventType) +{ + if (!ThePC) return; + + FInputKeyParams Params(Key, EventType, double(1.f), false); //amount depressed, bGamepad + ThePC->InputKey(Params); + //! 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); + } + } +} +//~~~~~~~~~~~~~~~~ +// Pixels! +//~~~~~~~~~~~~~~~~ + +//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_Pixels(const FString& FullFilePath,EJoyImageFormats ImageFormat,bool& IsValid, int32& Width, int32& Height, TArray& OutPixels) +{ + //Clear any previous data + OutPixels.Empty(); + + IsValid = false; + UTexture2D* LoadedT2D = NULL; + + IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked(FName("ImageWrapper")); + TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(GetJoyImageFormat(ImageFormat)); + + //Load From File + TArray RawFileData; + if (!FFileHelper::LoadFileToArray(RawFileData, *FullFilePath)) return NULL; + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + //Create T2D! + if (ImageWrapper.IsValid() && ImageWrapper->SetCompressed(RawFileData.GetData(), RawFileData.Num())) + { + TArray UncompressedRGBA ; + if (ImageWrapper->GetRaw(ERGBFormat::RGBA, 8, UncompressedRGBA)) + { + LoadedT2D = UTexture2D::CreateTransient(ImageWrapper->GetWidth(), ImageWrapper->GetHeight(), PF_R8G8B8A8); + + //Valid? + if(!LoadedT2D || !LoadedT2D->GetPlatformData()) return NULL; + //~~~~~~~~~~~~~~ + + //Out! + Width = ImageWrapper->GetWidth(); + Height = ImageWrapper->GetHeight(); + + const TArray& 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->GetPlatformData()->Mips[0].BulkData.Lock(LOCK_READ_WRITE); + FMemory::Memcpy(TextureData, UncompressedRGBA.GetData(), UncompressedRGBA.Num()); + LoadedT2D->GetPlatformData()->Mips[0].BulkData.Unlock(); + + //Update! + LoadedT2D->UpdateResource(); + } + } + + // Success! + IsValid = true; + return LoadedT2D; + +} +bool UVictoryBPFunctionLibrary::Victory_Get_Pixel(const TArray& 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& 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 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 CompressedPNG; + FImageUtils::PNGCompressImageArray( + 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); + } +} + +bool UVictoryBPFunctionLibrary::Victory_GetPixelFromT2D(UTexture2D* T2D, int32 X, int32 Y, FLinearColor& PixelColor) +{ + if(!T2D || !T2D->GetPlatformData()) + { + return false; + } + + if(X <= -1 || Y <= -1) + { + return false; + } + + + //~~~ + if(T2D->CompressionSettings != TC_VectorDisplacementmap) + { + #if WITH_EDITOR + FMessageLog("PIE").Error(FText::Format(LOCTEXT("Victory_GetPixelFromT2D", "UVictoryBPFunctionLibrary::Victory_GetPixelFromT2D >> Texture Compression must be VectorDisplacementmap <3 Rama: {0}'"), FText::FromString(T2D->GetName()))); + #endif // WITH_EDITOR + return false; + } + + + //~~~ + + T2D->SRGB = false; + + T2D->CompressionSettings = TC_VectorDisplacementmap; + + //Update settings + T2D->UpdateResource(); + + FTexture2DMipMap& MipsMap = T2D->GetPlatformData()->Mips[0]; + int32 TextureWidth = MipsMap.SizeX; + int32 TextureHeight = MipsMap.SizeY; + + //Safety check! + if (X >= TextureWidth || Y >= TextureHeight) + { + #if WITH_EDITOR + FMessageLog("PIE").Error(FText::Format(LOCTEXT("Victory_GetPixelFromT2D", "UVictoryBPFunctionLibrary::Victory_GetPixelFromT2D >> X or Y is outside of texture bounds! <3 Rama: {0}'"), FText::FromString(FString::FromInt(TextureWidth) + " x " + FString::FromInt(TextureHeight) ))); + #endif // WITH_EDITOR + return false; + } + + FByteBulkData* RawImageData = &MipsMap.BulkData; + + if(!RawImageData) + { + return false; + } + + int32 TotalCount = RawImageData->GetElementCount(); + if(TotalCount < 1) + { + return false; + } + + uint8* RawByteArray = (uint8*)RawImageData->Lock(LOCK_READ_ONLY); + + //TC_VectorDisplacementmap UMETA(DisplayName="VectorDisplacementmap (RGBA8)"), + //! 4 because includes alpha <3 Rama + /* + for(int32 v = 0; v < TextureWidth * TextureHeight * RawImageData->GetElementSize() * 4; v++) + { + DebugString += FString::FromInt(RawByteArray[v]) + " "; + } + */ + + //Texture.cpp + /* + else if (FormatSettings.CompressionSettings == TC_VectorDisplacementmap) + { + TextureFormatName = NameBGRA8; + } + */ + + //Get!, converting FColor to FLinearColor + FColor ByteColor; + ByteColor.B = RawByteArray[Y * TextureWidth * 4 + (X * 4) ]; + ByteColor.G = RawByteArray[Y * TextureWidth * 4 + (X * 4) + 1]; + ByteColor.R = RawByteArray[Y * TextureWidth * 4 + (X * 4) + 2]; + ByteColor.A = RawByteArray[Y * TextureWidth * 4 + (X * 4) + 3]; + + //Set! + PixelColor = ByteColor.ReinterpretAsLinear(); + + RawImageData->Unlock(); + + return true; +} + + +bool UVictoryBPFunctionLibrary::Victory_GetPixelsArrayFromT2D(UTexture2D* T2D, int32& TextureWidth, int32& TextureHeight,TArray& PixelArray) +{ + + if(!T2D || !T2D->GetPlatformData()) + { + return false; + } + + if(T2D->CompressionSettings != TC_VectorDisplacementmap) + { + #if WITH_EDITOR + FMessageLog("PIE").Error(FText::Format(LOCTEXT("Victory_GetPixelFromT2D", "UVictoryBPFunctionLibrary::Victory_GetPixelFromT2D >> Texture Compression must be VectorDisplacementmap <3 Rama: {0}'"), FText::FromString(T2D->GetName()))); + #endif // WITH_EDITOR + 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->GetPlatformData()->Mips[0]; + TextureWidth = MyMipMap.SizeX; + TextureHeight = MyMipMap.SizeY; + + FByteBulkData* RawImageData = &MyMipMap.BulkData; + + if(!RawImageData) + { + return false; + } + + uint8* RawByteArray = (uint8*)RawImageData->Lock(LOCK_READ_ONLY); + + + for(int32 y = 0; y < TextureHeight; y++) + { + for(int32 x = 0; x < TextureWidth; x++) + { + FColor ByteColor; + ByteColor.B = RawByteArray[y * TextureWidth * 4 + (x * 4) ]; + ByteColor.G = RawByteArray[y * TextureWidth * 4 + (x * 4) + 1]; + ByteColor.R = RawByteArray[y * TextureWidth * 4 + (x * 4) + 2]; + ByteColor.A = RawByteArray[y * TextureWidth * 4 + (x * 4) + 3]; + + PixelArray.Add(ByteColor.ReinterpretAsLinear()); + } + } + + RawImageData->Unlock(); + return true; +} + +//~~~~~~~~~~~~~~~~ +// Viewport! +//~~~~~~~~~~~~~~~~ + +//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(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; +} + +bool UVictoryBPFunctionLibrary::Viewport__GetCenterOfViewport(const APlayerController * ThePC, float & PosX, float & PosY) +{ + if (!ThePC) return false; + //~~~~~~~~~~~~~ + + //Get Player + const ULocalPlayer * VictoryPlayer = Cast(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(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; +} + +//~~~~~~~~~~~~~~~~ +// World! +//~~~~~~~~~~~~~~~~ +void UVictoryBPFunctionLibrary::GetNamesOfLoadedLevels(UObject* WorldContextObject, TArray& 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->GetStreamingLevels()) + { + if( ! EachLevel) continue; + //~~~~~~~~~~~~~~~~ + + ULevel* LevelPtr = EachLevel->GetLoadedLevel(); + + //Valid? + if(!LevelPtr) continue; + + //Is This Level Visible? + if(!LevelPtr->bIsVisible) continue; + //~~~~~~~~~~~~~~~~~~~ + + NamesOfLoadedLevels.Add(EachLevel->GetWorldAssetPackageFName().ToString()); + } +} + +//~~~~~~~~~~~~~~~~ +// Strings! +//~~~~~~~~~~~~~~~~ + +bool UVictoryBPFunctionLibrary::FileIO__SaveStringTextToFile( + FString SaveDirectory, + FString JoyfulFileName, + FString SaveText, + bool AllowOverWriting, + bool AllowAppend +){ + 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; + } + } + + if (AllowAppend) + { + SaveText += "\n"; + return FFileHelper::SaveStringToFile(SaveText, * SaveDirectory, + FFileHelper::EEncodingOptions::AutoDetect,&IFileManager::Get(), EFileWrite::FILEWRITE_Append); + } + return FFileHelper::SaveStringToFile(SaveText, * SaveDirectory); +} +bool UVictoryBPFunctionLibrary::FileIO__SaveStringArrayToFile(FString SaveDirectory, FString JoyfulFileName, TArray SaveText, bool AllowOverWriting, bool AllowAppend) +{ + //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; + } + + if (AllowAppend) + { + FinalStr += "\n"; + return FFileHelper::SaveStringToFile(FinalStr, * SaveDirectory, + FFileHelper::EEncodingOptions::AutoDetect,&IFileManager::Get(), EFileWrite::FILEWRITE_Append); + } + + return FFileHelper::SaveStringToFile(FinalStr, * SaveDirectory); + +} + + +bool UVictoryBPFunctionLibrary::LoadStringFromFile(FString& Result, FString FullFilePath) +{ + return FFileHelper::LoadFileToString( Result, *FullFilePath); +} + +bool UVictoryBPFunctionLibrary::LoadStringArrayFromFile(TArray& StringArray, int32& ArraySize, FString FullFilePath, bool ExcludeEmptyLines) +{ + ArraySize = 0; + + if(FullFilePath == "" || FullFilePath == " ") return false; + + //Empty any previous contents! + StringArray.Empty(); + + TArray 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; +} + +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; +} + +//~~~~~~~~~~~~~~~~ +// Image Files! +//~~~~~~~~~~~~~~~~ + +static TSharedPtr GetImageWrapperByExtention(const FString InImagePath) +{ + //EndsWith is not case sensitive by default, see unrealstring.h btw -Rama + + IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked(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; +} + +static UTexture2D* LoadTexture2D(const FString& FullFilePath, TSharedPtr ImageWrapper, bool& IsValid, int32& Width, int32& Height) +{ + IsValid = false; + UTexture2D* LoadedT2D = NULL; + + + //Load From File + TArray RawFileData; + if (!FFileHelper::LoadFileToArray(RawFileData, *FullFilePath)) + { + return NULL; + } + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + //Create T2D! + if (ImageWrapper.IsValid() && ImageWrapper->SetCompressed(RawFileData.GetData(), RawFileData.Num())) + { + TArray UncompressedBGRA; + if (ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, UncompressedBGRA)) + { + LoadedT2D = UTexture2D::CreateTransient(ImageWrapper->GetWidth(), ImageWrapper->GetHeight(), PF_B8G8R8A8); + + //Valid? + if(!LoadedT2D || !LoadedT2D->GetPlatformData()) + { + return NULL; + } + //~~~~~~~~~~~~~~ + + //Out! + Width = ImageWrapper->GetWidth(); + Height = ImageWrapper->GetHeight(); + + //Copy! + void* TextureData = LoadedT2D->GetPlatformData()->Mips[0].BulkData.Lock(LOCK_READ_WRITE); + FMemory::Memcpy(TextureData, UncompressedBGRA.GetData(), UncompressedBGRA.Num()); + LoadedT2D->GetPlatformData()->Mips[0].BulkData.Unlock(); + + //Update! + LoadedT2D->UpdateResource(); + } + } + + // Success! + IsValid = true; + return LoadedT2D; +} + +UTexture2D* UVictoryBPFunctionLibrary::Victory_LoadTexture2D_FromFileByExtension(const FString& FullFilePath,EJoyImageFormats ImageFormat,bool& IsValid, int32& Width, int32& Height) +{ + IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked(FName("ImageWrapper")); + TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(GetJoyImageFormat(ImageFormat)); + + return LoadTexture2D(FullFilePath,ImageWrapper,IsValid,Width,Height); +} + +UTexture2D* UVictoryBPFunctionLibrary::Victory_LoadTexture2D_FromFile(const FString& FullFilePath,bool& IsValid, int32& Width, int32& Height) +{ + TSharedPtr ImageWrapper = GetImageWrapperByExtention(FullFilePath); + + return LoadTexture2D(FullFilePath,ImageWrapper,IsValid,Width,Height); +} + +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; +} + +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)) + { + #if WITH_EDITOR + FMessageLog("PIE").Error(FText::Format(LOCTEXT("CaptureComponent2D_SaveImage", "UVictoryBPFunctionLibrary::CaptureComponent2D_SaveImage >> Please supply a Capture Component with a valid T2D Render Target :) <3 Rama {0}'"), FText::FromString(ImagePath))); + #endif // WITH_EDITOR + + return false; + } + + FRenderTarget* RenderTarget = Target->TextureTarget->GameThread_GetRenderTargetResource(); + if (RenderTarget == nullptr) + { + return false; + } + + TArray RawPixels; + + // Format not supported - use PF_B8G8R8A8. + if (Target->TextureTarget->GetFormat() != PF_B8G8R8A8) + { + #if WITH_EDITOR + FMessageLog("PIE").Error(FText::Format(LOCTEXT("CaptureComponent2D_SaveImage", "UVictoryBPFunctionLibrary::CaptureComponent2D_SaveImage >> The Texture Render Target (the texture render asset itself) format is not supported - use PF_B8G8R8A8 <3 Rama & Kris: {0}'"), FText::FromString(ImagePath))); + #endif // WITH_EDITOR + + 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 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)) + { + return FFileHelper::SaveArrayToFile(ImageWrapper->GetCompressed(), *ImagePath); + } + + return false; +} + +bool UVictoryBPFunctionLibrary::Capture2D_SaveImage(class ASceneCapture2D* Target, const FString ImagePath, const FLinearColor ClearColour) +{ + return (Target) ? CaptureComponent2D_SaveImage(Target->GetCaptureComponent2D(), ImagePath, ClearColour) : false; +} + + +//~~~~~~~~~~ +// Core +//~~~~~~~~~~ +void UVictoryBPFunctionLibrary::VictoryCreateProc(int32& ProcessId, FString FullPathOfProgramToRun,TArray CommandlineArgs,bool Detach,bool Hidden, int32 Priority, FString OptionalWorkingDirectory, URamaVictoryPluginCreateProcessPipe* ReadPipeObject) +{ + //Please note ProcessId should really be uint32 but that is not supported by BP yet + + if(ReadPipeObject) + { + ReadPipeObject->CreatePipe(); + } + + 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, + (ReadPipeObject && ReadPipeObject->PipeIsValid()) ? ReadPipeObject->WritePipe : nullptr + ); + + //Not sure what to do to expose UINT32 to BP + ProcessId = NeedBPUINT32; +} +//~~~~~~~~~~ +// Misc +//~~~~~~~~~~ + +static int32 GetChildBones(const FReferenceSkeleton& ReferenceSkeleton, int32 ParentBoneIndex, TArray & Children) +{ + Children.Empty(); + + const int32 NumBones = ReferenceSkeleton.GetNum(); + for(int32 ChildIndex=ParentBoneIndex+1; ChildIndex& ChildBoneNames) +{ + TArray BoneIndicies; + GetChildBones(SkeletalMeshComp->GetSkeletalMeshAsset()->GetRefSkeleton(), 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); + } +} + + +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 ); +} + +void UVictoryBPFunctionLibrary::ServerTravel(UObject* WorldContextObject, FString MapName,bool bSkipNotifyPlayers) +{ + if(!WorldContextObject) return; + + UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull); + if(!World) return; + //~~~~~~~~~~~ + + World->ServerTravel(MapName,false,bSkipNotifyPlayers); //abs //notify players +} + +int32 UVictoryBPFunctionLibrary::GetAllBoneNamesBelowBone( USkeletalMeshComponent* SkeletalMeshComp, FName StartingBoneName, TArray& BoneNames ) +{ + BoneNames.Empty(); + + if(!SkeletalMeshComp || !SkeletalMeshComp->GetSkeletalMeshAsset()) + { + return -1; + //~~~~ + } + + int32 StartingBoneIndex = SkeletalMeshComp->GetBoneIndex(StartingBoneName); + + //Recursive + GetChildBoneNames_Recursive(SkeletalMeshComp, StartingBoneIndex, BoneNames); + + return BoneNames.Num(); +} + +//~~~ 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; +} + + +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; +} + + +bool UVictoryBPFunctionLibrary::Animation__GetAimOffsets(AActor* AnimBPOwner, float& Pitch, float& Yaw) +{ + //Get Owning Character + ACharacter * TheCharacter = Cast(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(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; +} + +APlayerController * UVictoryBPFunctionLibrary::Accessor__GetPlayerController( + AActor * TheCharacter, + bool & IsValid +) +{ + IsValid = false; + + //Cast to Character + ACharacter * AsCharacter = Cast(TheCharacter); + if (!AsCharacter) return NULL; + + //cast to PC + APlayerController * ThePC = Cast < APlayerController > (AsCharacter->GetController()); + + if (!ThePC ) return NULL; + + IsValid = true; + return ThePC; +} + +//~~~~~~~~~~ +// File IO +//~~~~~~~~~~ + +bool UVictoryBPFunctionLibrary::GenerateUniqueContentRelativeFileName(FString ContentRelativeFilePath, FString& ContentRelativeNewFileName, FString& AbsolutePath, bool CreateFolderTree) +{ + //UE User-Input Assistance (inline) ♥ Rama + FPaths::NormalizeFilename(ContentRelativeFilePath); + FPaths::RemoveDuplicateSlashes(ContentRelativeFilePath); + + FString AbsContentPath = FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir()); + + //Extension + FString FileExt = FPaths::GetExtension(ContentRelativeFilePath, true); //include . + + //File without any extension + FString AssetFile = FPaths::GetBaseFilename(ContentRelativeFilePath); + + //Everything but the file + FString BasePath = FPaths::GetPath(ContentRelativeFilePath); + + + if(ContentRelativeFilePath.Contains("/")) + { + //Absolute Path + BasePath = AbsContentPath + BasePath; + + if(CreateFolderTree) + { + //Folder? + if(!FPlatformFileManager::Get().GetPlatformFile().CreateDirectoryTree(*BasePath)) + { + //Info out to user about what was attempted + AbsolutePath = BasePath; + return false; + //~~~~~~~~~~~~~~~~~~~~~~ + } + } + } + + //~~~ + // Make path with extension + if(BasePath != "") + { + AbsolutePath = BasePath + "/" + AssetFile; + } + else + { + AbsolutePath = AbsContentPath + AssetFile; + } + + //Check if file exists already, increment int as needed, ♥ Rama + + //Absolute Path + File, Still No Extension yet + BasePath = AbsolutePath; + + int32 FileNameInt = 1; + AbsolutePath = BasePath + FileExt; + while(FPlatformFileManager::Get().GetPlatformFile().FileExists( *AbsolutePath)) + { + FileNameInt++; + AbsolutePath = BasePath + FString::FromInt(FileNameInt) + FileExt; + } + + FString Left; + + //Make Relative + AbsolutePath.Split(TEXT("/Content/"),&Left,&ContentRelativeNewFileName); + + return true; +} + +FDateTime UVictoryBPFunctionLibrary::Victory_GetFileTimeStamp(const FString& File) +{ + return FPlatformFileManager::Get().GetPlatformFile().GetTimeStamp(*File); +} +void UVictoryBPFunctionLibrary::Victory_SetTimeStamp(const FString& File, const FDateTime& TimeStamp) +{ + FPlatformFileManager::Get().GetPlatformFile().SetTimeStamp(*File,TimeStamp); +} + +bool UVictoryBPFunctionLibrary::Victory_GetFiles(TArray& 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::Victory_GetFilesInRootAndAllSubFolders(TArray& 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; +} + +//~~~~~~~~~~ +// OS +//~~~~~~~~~~ + +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::ClientWindow__GameWindowIsForeGroundInOS() +{ + return FPlatformApplicationMisc::IsThisApplicationForeground(); +} + +AActor* UVictoryBPFunctionLibrary::GetClosestActorOfClassInRadiusOfLocation( + UObject* WorldContextObject, + TSubclassOf 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 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 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 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; +} + + + + +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 + ); +} + +bool UVictoryBPFunctionLibrary::ClosestPointsOnTwoLines(FVector& closestPointLine1, FVector& closestPointLine2, FVector linePoint1, FVector lineVec1, FVector linePoint2, FVector lineVec2) +{ + float a = FVector::DotProduct(lineVec1, lineVec1); + float b = FVector::DotProduct(lineVec1, lineVec2); + float e = FVector::DotProduct(lineVec2, lineVec2); + + float d = a*e - b*b; + + //lines are not parallel + if (d != 0.0f) + { + FVector r = linePoint1 - linePoint2; + float c = FVector::DotProduct(lineVec1, r); + float f = FVector::DotProduct(lineVec2, r); + + float s = (b*f - c*e) / d; + float t = (a*f - c*b) / d; + + closestPointLine1 = linePoint1 + lineVec1 * s; + closestPointLine2 = linePoint2 + lineVec2 * t; + + return true; + } + else + { + return false; + } +} + +//see: #define LOCTEXT_NAMESPACE "VictoryBPLibrary" +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/VictoryBPFunctionLibrary.h b/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/VictoryBPFunctionLibrary.h new file mode 100644 index 0000000..9ccaf57 --- /dev/null +++ b/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/VictoryBPFunctionLibrary.h @@ -0,0 +1,654 @@ +// Copyright Rama All Rights Reserved. + +#pragma once + +//Kinda Important (used once in a while) +#include "UObject/Object.h" +#include "Templates/SubclassOf.h" + +//UMG +#include "Blueprint/UserWidget.h" +#include "Blueprint/WidgetTree.h" + +#include "Kismet/BlueprintFunctionLibrary.h" +#include "VictoryBPFunctionLibrary.generated.h" + +/** + Made With Love By Rama for Use with @VictoryCreateProc + So that you can receive feedback from your processes. + + ♥ + + Rama +*/ +UCLASS(Blueprintable,BlueprintType) +class URamaVictoryPluginCreateProcessPipe : public UObject +{ + GENERATED_BODY() +public: + + UFUNCTION(BlueprintCallable, Category = "Joy Flow") + bool CreatePipe(); + + UFUNCTION(BlueprintCallable, Category = "Joy Flow") + void ClosePipe(); + + /** + This has exec pins because it is an expensive action and the output is saved/cached on the output pin, whereas a Pure node would repeat the action many times, each time node is accessed. + + @Return false if the pipes were not created yet + + ♥ Rama + */ + UFUNCTION(BlueprintCallable, Category = "Joy Flow") + bool ReadFromPipe(FString& PipeContents); + + UFUNCTION(BlueprintPure, Category = "Joy Flow") + bool PipeIsValid(); + +public: + void* ReadPipe = nullptr; + void* WritePipe = nullptr; + + virtual void BeginDestroy() override; +}; + +UENUM(BlueprintType) +enum EJoyImageFormats +{ + /** + * Example of a comment inside a custom enum <3 Rama + * @see FWalkableSlopeOverride::WalkableSlopeAngle + */ + JPG UMETA(DisplayName="JPG "), + + PNG UMETA(DisplayName="PNG "), + BMP UMETA(DisplayName="BMP "), + ICO UMETA(DisplayName="ICO "), + EXR UMETA(DisplayName="EXR "), + ICNS UMETA(DisplayName="ICNS "), + + JoyImageFormats_Max UMETA(Hidden), +}; + +/* + Victory to You! <3 Rama +*/ +UCLASS() +class UVictoryBPFunctionLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_UCLASS_BODY() + + //~~~~~~~~~~~~~~ + // Joy Geo + //~~~~~~~~~~~~~~ + //~~~ + /** Yes!!! For Real!!! + ♥ Rama + PS: If you find the new asset has a pivot point far from itself, I recommend moving the mesh to world origin (0,0,0) in level viewport before saving + + PSS: Only works in Editor Builds by the way, see UE5 C++ Source file: + UE_5.0\Engine\Plugins\Runtime\MeshModelingToolset\Source\ModelingComponentsEditorOnly\PublicEditorModelingObjectsCreationAPI.h + for more info! + + @param ContentFolderPath If this is just a name (there is no need to add a ".uasset" file extension to the name), the Static Mesh Asset will be created in your game's Content root directory (folder)! If the path is YourFolder/YourAssetName, then the asset will be created in your chosen folder (within Content Folder), I create the folder if necessary, using GenericPlatformFile::CreateDirectoryTree, my very first UE Engine Github C++ code gifts! ♥ Rama + @param Status more info about reason for operation not succeeding, or a special Success Message! + */ + UFUNCTION(BlueprintCallable,Category=RamaCode) + static UStaticMesh* CreateStaticMeshAssetFromDynamicMesh(FString ContentFolderPath, UDynamicMeshComponent* DynamicMeshComp, FString& Status, FString& NewAssetFilePath, bool& Success); + + /** Obtain the scaled,rotated, and translated vertex positions for any static mesh! Returns false if operation could not occur because the comp or static mesh asset was invalid. <3 Rama */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Actor") + static bool GetStaticMeshVertexLocations(UStaticMeshComponent* Comp, TArray& VertexPositions, int32 LodIndex = 0); + + //~~~~~~~~~~~~~~~~~~~ + // Load Object + //~~~~~~~~~~~~~~~~~~~ + + /** The FName that is expected is the exact same format as when you right click on asset -> Copy Reference! You can directly paste copied references into this node! IsValid lets you know if the path was correct or not and I was able to load the object. MAKE SURE TO SAVE THE RETURNED OBJECT AS A VARIABLE. Otherwise your shiny new asset will get garbage collected. I recommend you cast the return value to the appropriate object and then promote it to a variable :) -Rama */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Misc", Meta = (DeterminesOutputType = "ObjectClass")) + static UObject* LoadObjectFromAssetPath(TSubclassOf ObjectClass, FName Path, bool& IsValid); + + /** Uses the same format as I use for LoadObjectFromAssetPath! Use this node to get the asset path of objects in the world! -Rama */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|Misc") + static FName GetObjectPath(UObject* Obj); + + //~~~~~~~~~~~~ + // Math + //~~~~~~~~~~~~ + + /** Easily add to an integer! <3 Rama*/ + UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "+=",Keywords = "increment integer"), Category = "Victory BP Library|Math|Integer") + static void VictoryIntPlusEquals(UPARAM(ref) int32& Int, int32 Add, int32& IntOut); + + /** Easily subtract from an integer! <3 Rama*/ + UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "-=",Keywords = "decrement integer"), Category = "Victory BP Library|Math|Integer") + static void VictoryIntMinusEquals(UPARAM(ref) int32& Int, int32 Sub, int32& IntOut); + + /** Easily add to a float! <3 Rama*/ + UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "+=",Keywords = "increment float"), Category = "Victory BP Library|Math|Float") + static void VictoryFloatPlusEquals(UPARAM(ref) float& Float, float Add, float& FloatOut); + + /** Easily subtract from a float! <3 Rama*/ + UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "-=",Keywords = "decrement float"), Category = "Victory BP Library|Math|Float") + static void VictoryFloatMinusEquals(UPARAM(ref) float& Float, float Sub, float& FloatOut); + + /** Sort an integer array, smallest value will be at index 0 after sorting. Modifies the input array, no new data created. <3 Rama */ + UFUNCTION(BlueprintCallable, meta = (Keywords = "sort integer array"), Category = "Victory BP Library|Array") + static void VictorySortIntArray(UPARAM(ref) TArray& IntArray, TArray& IntArrayRef) + { + IntArray.Sort(); + IntArrayRef = IntArray; + } + + /** Sort a float array, smallest value will be at index 0 after sorting. Modifies the input array, no new data created. */ + UFUNCTION(BlueprintCallable, meta = (Keywords = "sort float array"), Category = "Victory BP Library|Array") + static void VictorySortFloatArray(UPARAM(ref) TArray& FloatArray, TArray& FloatArrayRef) + { + FloatArray.Sort(); + FloatArrayRef = FloatArray; + } + + /** Sort a string array alphabetically! */ + UFUNCTION(BlueprintCallable, meta = (Keywords = "sort string array"), Category = "Victory BP Library|Array") + static void VictorySortStringArray(UPARAM(ref) TArray& StringArray, TArray& StringArrayRef) + { + StringArray.Sort(); + StringArrayRef = StringArray; + } + + //~~~~~~~~~~~~ + // UMG + //~~~~~~~~~~~~ + /** + * Find first widget of a certain class and return it. + * @param WidgetClass The widget class to filter by. + * @param TopLevelOnly Only a widget that is a direct child of the viewport will be returned. + */ + UFUNCTION(Category = "Victory BP Library|UMG", BlueprintCallable, BlueprintCosmetic, Meta = (WorldContext = "WorldContextObject", DeterminesOutputType = "WidgetClass")) + static UUserWidget* GetFirstWidgetOfClass(UObject* WorldContextObject, TSubclassOf WidgetClass, bool TopLevelOnly); + + UFUNCTION(Category = "Victory BP Library|UMG", BlueprintCallable, BlueprintCosmetic, Meta = (DefaultToSelf = "ParentWidget", DeterminesOutputType = "WidgetClass", DynamicOutputParam = "ChildWidgets")) + static void WidgetGetChildrenOfClass(UWidget* ParentWidget, TArray& ChildWidgets, TSubclassOf WidgetClass, bool bImmediateOnly); + + /** + * Recurses up the list of parents until it finds a widget of WidgetClass. + * @return widget that is Parent of ChildWidget that matches WidgetClass. + */ + UFUNCTION(Category = "Victory BP Library|UMG", BlueprintCallable, BlueprintCosmetic, Meta = (DefaultToSelf = "ChildWidget", DeterminesOutputType = "WidgetClass")) + static UUserWidget* WidgetGetParentOfClass(UWidget* ChildWidget, TSubclassOf WidgetClass); + + UFUNCTION(Category = "Victory BP Library|UMG", BlueprintCallable, BlueprintCosmetic, Meta = (DefaultToSelf = "ParentUserWidget")) + static UWidget* GetWidgetFromName(UUserWidget* ParentUserWidget, const FName& Name); + + UFUNCTION(BlueprintPure, Category = "Victory BP Library|UMG", meta = (HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject")) + static bool IsWidgetOfClassInViewport(UObject* WorldContextObject, TSubclassOf WidgetClass); + + /** Remove all widgets of a certain class from viewport! */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|UMG", meta = (HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject")) + static void RemoveAllWidgetsOfClass(UObject* WorldContextObject, TSubclassOf WidgetClass); + + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Input") + static void VictorySimulateMouseWheel(const float& Delta); + + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Input") + static void VictorySimulateKeyPress(APlayerController* ThePC, FKey Key, EInputEvent EventType); + + //~~~~~~~~~~~~~~~~ + // Image Files! + //~~~~~~~~~~~~~~~~ + + /** Load a Texture2D from a JPG,PNG,BMP,ICO,EXR,ICNS file! IsValid tells you if file path was valid or not. Enjoy! -Rama */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Load Texture From File",meta=(Keywords="image png jpg jpeg bmp bitmap ico icon exr icns")) + static UTexture2D* Victory_LoadTexture2D_FromFileByExtension(const FString& FullFilePath,EJoyImageFormats ImageFormat,bool& IsValid, int32& Width, int32& Height); + + /** Load a Texture2D from a JPG,PNG,BMP,ICO,EXR,ICNS file! IsValid tells you if file path was valid or not. The image type is assumed from an extension such as .jpg, .png, .bmp. Enjoy! -Rama */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Load Texture From File",meta=(Keywords="image png jpg jpeg bmp bitmap ico icon exr icns")) + static UTexture2D* Victory_LoadTexture2D_FromFile(const FString& FullFilePath,bool& IsValid, int32& Width, int32& Height); + + //~~~~~~~~~~~~ + // Capture 2D + //~~~~~~~~~~~~ + + /** Contributed by Community Member Kris! */ + UFUNCTION(Category = "Victory BP Library|SceneCapture", BlueprintPure) + static bool CaptureComponent2D_Project(class USceneCaptureComponent2D* Target, FVector Location, FVector2D& OutPixelLocation); + + /** Contributed by Community Member Kris! */ + UFUNCTION(Category = "Victory BP Library|SceneCapture", BlueprintPure, Meta = (DefaultToSelf = "Target")) + static bool Capture2D_Project(class ASceneCapture2D* Target, FVector Location, FVector2D& OutPixelLocation); + + /** + I highly recommend that your Texture Render Target Format be "RTF RGB8 SRGB" both so it is compatible, and so it looks the same as in-game + <3 Rama. + + Make sure to include the appropriate image extension in your file path! Recommended: .bmp, .jpg, .png. Contributed by Community Member Kris! + */ + UFUNCTION(Category = "Victory BP Library|SceneCapture", BlueprintCallable) + static bool CaptureComponent2D_SaveImage(class USceneCaptureComponent2D* Target, const FString ImagePath, const FLinearColor ClearColour); + + /** + I highly recommend that your Texture Render Target Format be "RTF RGB8 SRGB" both so it is compatible, and so it looks the same as in-game + <3 Rama. + + Make sure to include the appropriate image extension in your file path! Recommended: .bmp, .jpg, .png. Contributed by Community Member Kris! + */ + UFUNCTION(Category = "Victory BP Library|SceneCapture", BlueprintCallable, Meta = (DefaultToSelf = "Target")) + static bool Capture2D_SaveImage(class ASceneCapture2D* Target, const FString ImagePath, const FLinearColor ClearColour); + + //~~~~~~~~~~~~ + // Misc + //~~~~~~~~~~~~ + + /** Is this game logic running in the Editor world? */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Victory BP Library|System", meta = (HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject")) + static bool WorldType__InEditorWorld(UObject* WorldContextObject); + + /** Is this game logic running in the PIE world? */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Victory BP Library|System", meta = (HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject")) + static bool WorldType__InPIEWorld(UObject* WorldContextObject); + + /** Is this game logic running in an actual independent game instance? */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Victory BP Library|System", meta = (HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject")) + static bool WorldType__InGameInstanceWorld(UObject* WorldContextObject); + + /** Server Travel! This is an async load level process which allows you to put up a UMG widget while the level loading occurs! */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|System",meta=(WorldContext="WorldContextObject")) + static void ServerTravel(UObject* WorldContextObject,FString MapName, bool bSkipNotifyPlayers=false); + + /** + * Get All Bone Names Below Bone, requires a physics asset, by Rama + * + * @param StartingBoneName The name of the bone to find all bones below. + * + * @param BoneNames , all of the bone names below the starting bone. + * + * @return total number of bones found + */ + UFUNCTION(BlueprintCallable, Category="Victory BP Library|Components|SkinnedMesh") + static int32 GetAllBoneNamesBelowBone(USkeletalMeshComponent* SkeletalMeshComp, FName StartingBoneName, TArray& BoneNames ); + + /* Addition of strings (A + B) with pins. Contributed by KeyToTruth */ + //UFUNCTION(BlueprintPure, meta = (DisplayName = "Append Multiple", Keywords = "concatenate combine append strings", CommutativeAssociativeBinaryOperator = "true"), Category = "Victory BP Library|String") + static FString AppendMultiple(FString A, FString B); + + /** + * Transforms the viewport position into a world space origin and direction. + * + * @param WorldContextObject World context. + * @param ViewportPosition Local space of viewport from GetViewportPosition() or similar. + * @param OutWorldOrigin Corresponding 3D location in world space. + * @param OutWorldDirection World space direction vector away from the camera at the given 2d point. + * + * @return false if something went wrong during the deproject process. + */ + UFUNCTION(Category = "Victory BP Library|Game|Viewport", BlueprintCallable, Meta = (WorldContext="WorldContextObject")) + static bool ViewportPositionDeproject(UObject* WorldContextObject, const FVector2D& ViewportPosition, FVector& OutWorldOrigin, FVector& OutWorldDirection); + + /** AnimBPOwner - Must be a Character, Conversion Internally For Convenience.\n\nRetrieves the Aim Offsets Pitch & Yaw Based On the Rotation of the Controller of The Character Owning The Anim Instance.\n\nThe Pitch and Yaw are meant to be used with a Blend Space going from -90,-90 to 90,90.\n Returns true if function filled the pitch and yaw vars successfully */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Aim Offset") + static bool Animation__GetAimOffsets(AActor* AnimBPOwner, float& Pitch, float& Yaw); + + /** AnimBPOwner - Must be a Character, Conversion Internally For Convenience.\n\nRetrieves the Aim Offsets Pitch & Yaw for the AnimBPOwner Based On the supplied Rotation.\n\nThe Pitch and Yaw are meant to be used with a Blend Space going from -90,-90 to 90,90.\n Returns true if function filled the pitch and yaw vars successfully */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Aim Offset") + static bool Animation__GetAimOffsetsFromRotation(AActor * AnimBPOwner, const FRotator & TheRotation, float & Pitch, float & Yaw); + + /** Get Player Character's Player Controller. Requires: The Passed in Actor must be a character and it must be a player controlled character. IsValid will tell you if the return value is valid, make sure to do a Branch to confirm this! */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|Character") + static APlayerController* Accessor__GetPlayerController(AActor* TheCharacter, bool&IsValid); + + UFUNCTION(BlueprintPure, Category = "Victory BP Library|Conversion", meta=(AdvancedDisplay = "1")) + static float Text_ToFloat(const FText& Text, bool UseDotForThousands=false) + { + //because commas lead to string number being truncated, FText 10,000 becomes 10 for FString + FString StrFloat = Text.ToString(); + TextNumFormat(StrFloat,UseDotForThousands); + return FCString::Atof(*StrFloat); + } + + UFUNCTION(BlueprintPure, Category = "Victory BP Library|Conversion", meta=(AdvancedDisplay = "1")) + static int32 Text_ToInt(const FText& Text, bool UseDotForThousands=false) + { + //because commas lead to string number being truncated, FText 10,000 becomes 10 for FString + FString StrInt = Text.ToString(); + TextNumFormat(StrInt,UseDotForThousands); + return FCString::Atoi(*StrInt); + } + + static void TextNumFormat(FString& StrNum, bool UseDotForThousands) + { + //10.000.000,997 + if(UseDotForThousands) + { + StrNum.ReplaceInline(TEXT("."),TEXT("")); //no dots as they truncate + StrNum.ReplaceInline(TEXT(","),TEXT(".")); //commas become decimal + } + + //10,000,000.997 + else + { + StrNum.ReplaceInline(TEXT(","),TEXT("")); //decimal can stay, commas would truncate so remove + } + } + + //~~~~~~~~~~~~ + // File I/O + //~~~~~~~~~~~~ + + /** + @CreateFolderTree I make sure the path you have supplied, relative to Project Content Folder, is created if you set this to true! ♥ Rama + @return false if Folder Tree could not be created, see "AbsolutePath" to see what folder tree structure was attempted. + Creates an unused filename, Given a basefile name that is relative to Content Folder of Project (So if you supply "YourFolder/BaseFile.EXT", this file would be actually be located in YourProjectFolder/Content/YourFolder/BaseFile.EXT), adding a number BaseFile1, BaseFile2, BaseFile3, as needed until a unique filename is generated! + */ + UFUNCTION(BlueprintCallable,Category="Victory BP Library|File IO") + static bool GenerateUniqueContentRelativeFileName(FString ContentRelativeFilePath, FString& ContentRelativeNewFileName, FString& AbsolutePath, bool CreateFolderTree = true); + + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|File IO") + static FDateTime Victory_GetFileTimeStamp(const FString& File); + + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|File IO") + static void Victory_SetTimeStamp(const FString& File, const FDateTime& TimeStamp); + + /** Obtain all files in a provided directory, with optional extension filter. All files are returned if Ext is left blank. Returns false if operation could not occur. */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|File IO") + static bool Victory_GetFiles(TArray& Files, FString RootFolderFullPath, FString Ext); + + /** Obtain all files in a provided root directory, including all subdirectories, with optional extension filter. All files are returned if Ext is left blank. The full file path is returned because the file could be in any subdirectory. Returns false if operation could not occur. */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|File IO") + static bool Victory_GetFilesInRootAndAllSubFolders(TArray& Files, FString RootFolderFullPath, FString Ext); + + //~~~~~~~~~~ + // Core + //~~~~~~~~~~ + + /** + Launch a new process, if it is not set to be detached, UE4 will not fully close until the other process completes. + + The new process id is returned! + + @param Detach Ensure completion before UE4 closes or not? Detach = UE4 can close and process will keep going / possibly never stop running as there is no one left to stop the process now ♥ Rama + + @param Priority Priority options: -2 idle, -1 low, 0 normal, 1 high, 2 higher + + @param ReadPipeObject Construct a new object of class URamaVictoryPluginCreateProcessPipe if you want to capture the output (STDOUT or STDERR) of this process! ♥♥♥ Yes ♥♥♥ !!!! (call ReadFromPipe on the object over time, in a timer, after creating the procedure, remember to nullptr Your Object Reference after closing the pipe, so that UE4 will garbage-collect the object! ) + + ♥ Rama + */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|System") + static void VictoryCreateProc(int32& ProcessId, FString FullPathOfProgramToRun,TArray CommandlineArgs,bool Detach,bool Hidden, int32 Priority=0, FString OptionalWorkingDirectory="", URamaVictoryPluginCreateProcessPipe* ReadPipeObject=nullptr); + + /** You can obtain ProcessID from processes you initiate via VictoryCreateProc */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|System") + static FString VictoryGetApplicationName(int32 ProcessId) + { + //Please note it should really be uint32 but that is not supported by BP yet + return FPlatformProcess::GetApplicationName(ProcessId); + } + + /** You can obtain ProcessID from processes you initiate via VictoryCreateProc */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|System") + static bool VictoryIsApplicationRunning( int32 ProcessId ) + { + //Please note it should really be uint32 but that is not supported by BP yet + return FPlatformProcess::IsApplicationRunning(ProcessId); + } + /* Blueprints does not support int64 so at some pt in future int32 will not be enough, probably by then dolphins will rule the world, or UE4 BP will support int64, or both! <3 Rama*/ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|System") + static int64 GetUnixTimeStamp(const FDateTime& UTCTime) + { + //Please note it should really be int64 but that is not supported by BP yet + return UTCTime.ToUnixTimestamp(); + } + /* Blueprints does not support int64 so at some pt in future int32 will not be enough, probably by then dolphins will rule the world, or UE4 BP will support int64, or both! <3 Rama*/ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|System") + static void GetUTCFromUnixTimeStamp(int64 UnixTimeStamp, FDateTime& UTCTime) + { + //Please note it should really be int64 but that is not supported by BP yet + UTCTime = FDateTime::FromUnixTimestamp( UnixTimeStamp ); + } + + UFUNCTION(BlueprintPure, Category = "Rama Save System|File IO") + static void UTCToLocal(const FDateTime& UTCTime, FDateTime& LocalTime) + { + //Turn UTC into local ♥ Rama + FTimespan UTCOffset = FDateTime::Now() - FDateTime::UtcNow(); + LocalTime = UTCTime; + LocalTime += UTCOffset; + //♥ Rama + } + + //~~~~~~~~~~ + // Pixels + //~~~~~~~~~~ + + /** Load a Texture2D from a JPG,PNG,BMP,ICO,EXR,ICNS file! IsValid tells you if file path was valid or not. Enjoy! -Rama + CommunityTip from DarkFlash007: Make sure “Mip Gen Setting” is set to “Nomipmaps” */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|Load Texture From File",meta=(Keywords="image png jpg jpeg bmp bitmap ico icon exr icns")) + static UTexture2D* Victory_LoadTexture2D_FromFile_Pixels(const FString& FullFilePath,EJoyImageFormats ImageFormat,bool& IsValid, int32& Width, int32& Height, TArray& OutPixels); + + /** Retrieve a pixel color value given the pixel array, the image height, and the coordinates. Returns false if the coordinates were not valid. Pixel coordinates start from upper left corner as 0,0. X= horizontal, Y = vertical + CommunityTip from DarkFlash007: Make sure “Mip Gen Setting” is set to “Nomipmaps” */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|Load Texture From File",meta=(Keywords="image coordinate index map value")) + static bool Victory_Get_Pixel(const TArray& Pixels, int32 ImageHeight, int32 x, int32 y, FLinearColor& FoundColor); + + /** Save an array of pixels to disk as a PNG! It is very important that you supply the curret width and height of the image! Returns false if Width * Height != Array length or file could not be saved to the location specified. I return an ErrorString to clarify what the exact issue was. -Rama + CommunityTip from DarkFlash007: Make sure “Mip Gen Setting” is set to “Nomipmaps” */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Load Texture From File",meta=(Keywords="create image png jpg jpeg bmp bitmap ico icon exr icns")) + static bool Victory_SavePixels(const FString& FullFilePath,int32 Width, int32 Height, const TArray& ImagePixels, bool SaveAsBMP, bool sRGB, FString& ErrorString); + + /** This will modify the original T2D to remove sRGB and change compression to VectorDisplacementMap to ensure accurate pixel reading. -Rama + CommunityTip from DarkFlash007: Make sure “Mip Gen Setting” is set to “Nomipmaps” */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Load Texture From File",meta=(Keywords="create image png jpg jpeg bmp bitmap ico icon exr icns"))//, DeprecatedFunction, DeprecationMessage="This function will not work until I figure out how to update it to 4.25, if you need it urgently, please post in my ue4 forum thread for this plugin")) + static bool Victory_GetPixelFromT2D(UTexture2D* T2D, int32 X, int32 Y, FLinearColor& PixelColor); + + /** This will modify the original T2D to remove sRGB and change compression to VectorDisplacementMap to ensure accurate pixel reading. -Rama + CommunityTip from DarkFlash007: Make sure “Mip Gen Setting” is set to “Nomipmaps”*/ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Load Texture From File",meta=(Keywords="create image png jpg jpeg bmp bitmap ico icon exr icns"))//, DeprecatedFunction, DeprecationMessage="This function will not work until I figure out how to update it to 4.25, if you need it urgently, please post in my ue4 forum thread for this plugin")) + static bool Victory_GetPixelsArrayFromT2D(UTexture2D* T2D, int32& TextureWidth, int32& TextureHeight,TArray& PixelArray); + + //~~~~~~~~~~ + // Viewport + //~~~~~~~~~~ + + /** SET the Mouse Position! Returns false if the operation could not occur */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Input") + static bool Viewport__SetMousePosition(const APlayerController* ThePC, const float& PosX, const float& PosY); + + /** Get the Cursor Position within the Player's Viewport. This will return a result consistent with SET Mouse Position Returns false if the operation could not occur */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|Game Window") + static bool Viewport__GetMousePosition(const APlayerController* ThePC, float& PosX, float& PosY); + + + /** Get the coordinates of the center of the player's screen / viewport. Returns false if the operation could not occur */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|Game Window") + static bool Viewport__GetCenterOfViewport(const APlayerController* ThePC, float& PosX, float& PosY); + + //~~~~~~~~~~ + // World + //~~~~~~~~~~ + /** Get the names of all currently loaded and visible levels! */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|System", meta = (HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject")) + static void GetNamesOfLoadedLevels(UObject* WorldContextObject, TArray& NamesOfLoadedLevels); + + //~~~~~~~~~~ + // Strings + //~~~~~~~~~~ + + /** Saves text to filename of your choosing, make sure include whichever file extension you want in the filename, ex: SelfNotes.txt . Make sure to include the entire file path in the save directory, ex: C:\MyGameDir\BPSavedTextFiles */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|File IO") + static bool FileIO__SaveStringTextToFile(FString SaveDirectory, FString JoyfulFileName, FString SaveText, bool AllowOverWriting = false, bool AllowAppend = false); + + /** Saves multiple Strings to filename of your choosing, with each string on its own line! Make sure include whichever file extension you want in the filename, ex: SelfNotes.txt . Make sure to include the entire file path in the save directory, ex: C:\MyGameDir\BPSavedTextFiles */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|File IO") + static bool FileIO__SaveStringArrayToFile(FString SaveDirectory, FString JoyfulFileName, TArray SaveText, bool AllowOverWriting = false, bool AllowAppend = false); + + /** Loads a text file from hard disk and parses it into a String array, where each entry in the string array is 1 line from the text file. Option to exclude lines that are only whitespace characters or '\n'. Returns the size of the final String Array that was created. Returns false if the file could be loaded from hard disk. */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|File IO") + static bool LoadStringArrayFromFile(TArray& StringArray, int32& ArraySize, FString FullFilePath = "Enter Full File Path", bool ExcludeEmptyLines = false); + + /** Load a text file to a single string that you can use ParseIntoArray on newline characters if you want same format as LoadStringArrayFromFile. This version supports unicode characters! */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|File IO") + static bool LoadStringFromFile(FString& Result, FString FullFilePath = "Enter Full File Path"); + + /** + * Returns whether or not the SearchIn string contains the supplied Substring. + * Ex: "cat" is a contained within "concatenation" as a substring. + * @param SearchIn The string to search within + * @param Substring The string to look for in the SearchIn string + * @param bUseCase Whether or not to be case-sensitive + * @param bSearchFromEnd Whether or not to start the search from the end of the string instead of the beginning + */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|String") + static bool HasSubstring(const FString& SearchIn, const FString& Substring, ESearchCase::Type SearchCase = ESearchCase::IgnoreCase, ESearchDir::Type SearchDir = ESearchDir::FromStart); + + /** Combines two strings together! The Separator and the Labels are optional*/ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|String") + static FString String__CombineStrings(FString StringFirst, FString StringSecond, FString Separator = "", FString StringFirstLabel = "", FString StringSecondLabel = ""); + + /** Separator is always a space */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|String", meta=( Keywords = "concatenate append", CommutativeAssociativeBinaryOperator = "true")) + static FString String__CombineStrings_Multi(FString A, FString B); + + //~~~~~~~~~~ + // OS + //~~~~~~~~~~ + + UFUNCTION(BlueprintPure, Category = "Victory BP Library|String") + static bool IsAlphaNumeric(const FString& String); + + UFUNCTION(BlueprintPure, Category = "Victory BP Library|String") + static void Victory_GetStringFromOSClipboard(FString& FromClipboard); + + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|String") + static void Victory_SaveStringToOSClipboard(const FString& ToClipboard); + + /** Is the Current Game Window the Foreground window in the OS, or in the Editor? This will be accurate in standalone running of the game as well as in the editor PIE */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|Game Window") + static bool ClientWindow__GameWindowIsForeGroundInOS(); + + //~~~~~~~~~~~~~~ + // AI + //~~~~~~~~~~~~~~ + + UFUNCTION(BlueprintPure, Category = "Victory BP Library|AI",meta=(WorldContext="WorldContextObject")) + static AActor* GetClosestActorOfClassInRadiusOfLocation(UObject* WorldContextObject, TSubclassOf ActorClass, FVector Center, float Radius, bool& IsValid); + + UFUNCTION(BlueprintPure, Category = "Victory BP Library|AI",meta=(WorldContext="WorldContextObject")) + static AActor* GetClosestActorOfClassInRadiusOfActor(UObject* WorldContextObject, TSubclassOf ActorClass, AActor* ActorCenter, float Radius, bool& IsValid); + + + //~~~~~~~~~~~~~~ + // Windows OS + //~~~~~~~~~~~~~~ + + /** Flashes the game on the windows OS task bar! Please note this won't look the best in PIE, flashing is smoother in Standalone or packaged game. You can use GameWindowIsForeGroundInOS to see if there is a need to get the user's attention! <3 Rama */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Game Window") + static void FlashGameOnTaskBar(APlayerController* PC, bool FlashContinuous=false, int32 MaxFlashCount = 3, int32 FlashFrequencyMilliseconds=500); + + + //~~~~~~~~~~~~~~~~~ + // Config Vars + //~~~~~~~~~~~~~~~~~ + /** Get Custom Config Var! These are stored in Saved/Config/Windows/Game.ini */ + //UFUNCTION(BlueprintPure, Category = "Victory BP Library|Custom Config Vars!") + //static uint8 Victory_ConvertStringToByte(UEnum* Enum,FString String); + //! not working yet, always getting 255 + + /** Get Custom Config Var! These are stored in Saved/Config/Windows/Game.ini */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|Custom Config Vars") + static bool VictoryGetCustomConfigVar_Bool(FString SectionName, FString VariableName, bool& IsValid); + + /** Get Custom Config Var! These are stored in Saved/Config/Windows/Game.ini */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|Custom Config Vars") + static int32 VictoryGetCustomConfigVar_Int(FString SectionName, FString VariableName, bool& IsValid); + + /** Get Custom Config Var! These are stored in Saved/Config/Windows/Game.ini */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|Custom Config Vars") + static float VictoryGetCustomConfigVar_Float(FString SectionName, FString VariableName, bool& IsValid); + + /** Get Custom Config Var! These are stored in Saved/Config/Windows/Game.ini */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|Custom Config Vars") + static FVector VictoryGetCustomConfigVar_Vector(FString SectionName, FString VariableName, bool& IsValid); + + /** Get Custom Config Var! These are stored in Saved/Config/Windows/Game.ini */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|Custom Config Vars") + static FRotator VictoryGetCustomConfigVar_Rotator(FString SectionName, FString VariableName, bool& IsValid); + + /** Get Custom Config Var! These are stored in Saved/Config/Windows/Game.ini */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|Custom Config Vars") + static FLinearColor VictoryGetCustomConfigVar_Color(FString SectionName, FString VariableName, bool& IsValid); + + /** Get Custom Config Var! These are stored in Saved/Config/Windows/Game.ini */ + UFUNCTION(BlueprintPure, Category = "Victory BP Library|Custom Config Vars") + static FString VictoryGetCustomConfigVar_String(FString SectionName, FString VariableName, bool& IsValid); + + UFUNCTION(BlueprintPure, Category = "Victory BP Library|Custom Config Vars") + static FVector2D VictoryGetCustomConfigVar_Vector2D(FString SectionName, FString VariableName, bool& IsValid); + + //~~~~~~~~~~~~~~~~~~~~ + + /** Set Custom Config Var! These are stored in Saved/Config/Windows/Game.ini */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Custom Config Vars") + static void VictorySetCustomConfigVar_Vector2D(FString SectionName, FString VariableName, FVector2D Value); + + /** Set Custom Config Var! These are stored in Saved/Config/Windows/Game.ini */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Custom Config Vars") + static void VictorySetCustomConfigVar_Bool(FString SectionName, FString VariableName, bool Value); + + /** Set Custom Config Var! These are stored in Saved/Config/Windows/Game.ini */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Custom Config Vars") + static void VictorySetCustomConfigVar_Int(FString SectionName, FString VariableName, int32 Value); + + /** Set Custom Config Var! These are stored in Saved/Config/Windows/Game.ini */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Custom Config Vars") + static void VictorySetCustomConfigVar_Float(FString SectionName, FString VariableName, float Value); + + /** Set Custom Config Var! These are stored in Saved/Config/Windows/Game.ini */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Custom Config Vars") + static void VictorySetCustomConfigVar_Vector(FString SectionName, FString VariableName, FVector Value); + + /** Set Custom Config Var! These are stored in Saved/Config/Windows/Game.ini */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Custom Config Vars") + static void VictorySetCustomConfigVar_Rotator(FString SectionName, FString VariableName, FRotator Value); + + /** Set Custom Config Var! These are stored in Saved/Config/Windows/Game.ini */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Custom Config Vars") + static void VictorySetCustomConfigVar_Color(FString SectionName, FString VariableName, FLinearColor Value); + + + /** Set Custom Config Var! These are stored in Saved/Config/Windows/Game.ini */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Custom Config Vars") + static void VictorySetCustomConfigVar_String(FString SectionName, FString VariableName, FString Value); + + //~~~~~~~~~~~~~~~~~ + // TK Math Library! + //~~~~~~~~~~~~~~~~~ + + /** + * Find closest points between 2 line segments. + * @param (Line1Start, Line1End) defines the first line segment. + * @param (Line2Start, Line2End) defines the second line segment. + * @param LinePoint1 Closest point on segment 1 to segment 2. + * @param LinePoint2 Closest point on segment 2 to segment 1. + */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|TK-Master Math|Vector|Intersection") + static void ClosestPointsOfLineSegments(FVector Line1Start, FVector Line1End, FVector Line2Start, FVector Line2End, FVector& LinePoint1, FVector& LinePoint2) + { + FMath::SegmentDistToSegmentSafe(Line1Start, Line1End, Line2Start, Line2End, LinePoint1, LinePoint2); + } + + /* + * Calculate the closest points between two infinitely long lines. + * If lines are intersecting (not parallel) it will return false! Use Line To Line Intersection instead. + * @param closestPointLine1 The closest point of line1 to line2. Returns zero if the lines intersect. + * @param closestPointLine2 The closest point of line2 to line1. Returns zero if the lines intersect. + * @param linePoint1 Line point of the first line. + * @param lineVec1 Line direction (normal) of the first line. Should be a normalized vector. + * @param linePoint2 Line point of the second line. + * @param lineVec2 Line direction (normal) of the second line. Should be a normalized vector. + * @return true if lines are parallel, false otherwise. + */ + UFUNCTION(BlueprintCallable, Category = "Victory BP Library|TK-Master Math|Vector") + static bool ClosestPointsOnTwoLines(FVector& closestPointLine1, FVector& closestPointLine2, FVector linePoint1, FVector lineVec1, FVector linePoint2, FVector lineVec2); + +}; diff --git a/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/VictoryBPFunctionLibrary_WinOS.cpp b/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/VictoryBPFunctionLibrary_WinOS.cpp new file mode 100644 index 0000000..8327013 --- /dev/null +++ b/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/VictoryBPFunctionLibrary_WinOS.cpp @@ -0,0 +1,68 @@ +// Copyright Rama All Rights Reserved. + +#include "VictoryBPFunctionLibrary.h" +#include "VictoryBPLibrary.h" + +#include "Widgets/SWindow.h" +#include "GenericPlatform/GenericWindow.h" +#include "GameFramework/PlayerController.h" +#include "Engine/GameViewportClient.h" + +#if PLATFORM_WINDOWS +#include "Runtime/Core/Public/Windows/AllowWindowsPlatformTypes.h" +//#include "AdditionalWindowsHeaders.h" +#endif + +void UVictoryBPFunctionLibrary::FlashGameOnTaskBar(APlayerController* PC, bool FlashContinuous, int32 MaxFlashCount, int32 FlashFrequencyMilliseconds) +{ + #if PLATFORM_WINDOWS + if(!PC) return; + + //Local Player + ULocalPlayer* VictoryPlayer = Cast(PC->Player); + if(!VictoryPlayer) return; + + //Game Viewport Client + UGameViewportClient* GameViewport = Cast(VictoryPlayer->ViewportClient); + if(!GameViewport) return; + + //Slate Game Window + TSharedPtr< SWindow > GWSlate = GameViewport->GetWindow(); + if(!GWSlate.IsValid()) return; + + //Native OS Window + TSharedPtr GW = GWSlate->GetNativeWindow(); + if(!GW.IsValid()) return; + + //Windows + FLASHWINFO fi; + fi.cbSize = sizeof(FLASHWINFO); + fi.hwnd = (HWND)GW->GetOSWindowHandle(); + fi.dwFlags = FLASHW_ALL; + + //Continuous? <3 Rama + if(FlashContinuous) + { + fi.dwFlags |= FLASHW_TIMERNOFG; + fi.uCount = 0; + fi.dwTimeout = 0; + } + else + { + fi.uCount = MaxFlashCount; + fi.dwTimeout = FlashFrequencyMilliseconds; + } + + FlashWindowEx(&fi); + #endif + + //<3 Rama +} + + + + + +#if PLATFORM_WINDOWS +#include "Runtime/Core/Public/Windows/HideWindowsPlatformTypes.h" +#endif diff --git a/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/VictoryBPLibrary.h b/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/VictoryBPLibrary.h new file mode 100644 index 0000000..7b9cb69 --- /dev/null +++ b/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/Public/VictoryBPLibrary.h @@ -0,0 +1,14 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Modules/ModuleManager.h" + +class FVictoryBPLibraryModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/VictoryBPLibrary.Build.cs b/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/VictoryBPLibrary.Build.cs new file mode 100644 index 0000000..306fb34 --- /dev/null +++ b/Plugins/VictoryBPLibraryUE55/Source/VictoryBPLibrary/VictoryBPLibrary.Build.cs @@ -0,0 +1,81 @@ +// Some copyright should be here... + +using UnrealBuildTool; + +public class VictoryBPLibrary : ModuleRules +{ + public VictoryBPLibrary(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Rama Joy Geo + // (You can remove these and the MeshModelingToolset in the .uplugin + // if you don't need The CreateStaticMeshAssetFromDynamicMesh or GetStaticMeshVertexLocations) + if(Target.bBuildEditor) + { + //CreateStaticMeshAssetFromDynamicMesh + PublicDependencyModuleNames.AddRange( + new string[] + { + "ModelingComponentsEditorOnly" + } + ); + } // End Editor Only + + PublicDependencyModuleNames.AddRange( + new string[] + { + //GetStaticMeshVertexLocations + "Core", + "MeshDescription", + + //CreateStaticMeshAssetFromDynamicMesh + "GeometryFramework", + "ModelingComponents", + } + ); + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "InputCore", + "RHI", + "Slate", + "SlateCore", + "ApplicationCore", + "AppFramework", //For Color Picker! ♥ Rama + "UMG", "Slate", "SlateCore", + + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Plugins/VictoryBPLibraryUE55/VictoryBPLibrary.uplugin b/Plugins/VictoryBPLibraryUE55/VictoryBPLibrary.uplugin new file mode 100644 index 0000000..7af8618 --- /dev/null +++ b/Plugins/VictoryBPLibraryUE55/VictoryBPLibrary.uplugin @@ -0,0 +1,32 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "VictoryBPLibrary", + "Description": "Joy!", + "Category": "Rama", + "CreatedBy": "Rama", + "CreatedByURL": "", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "EngineVersion": "5.5.0", + "CanContainContent": true, + "Installed": true, + "Modules": [ + { + "Name": "VictoryBPLibrary", + "Type": "Runtime", + "LoadingPhase": "PreLoadingScreen", + "PlatformAllowList": [ + "Win64" + ] + } + ], + "Plugins": [ + { + "Name": "MeshModelingToolset", + "Enabled": true + } + ] +} \ No newline at end of file diff --git a/Tracker_for5.uproject b/Tracker_for5.uproject index 1a37f35..f6bc2fc 100644 --- a/Tracker_for5.uproject +++ b/Tracker_for5.uproject @@ -1,6 +1,6 @@ { "FileVersion": 3, - "EngineAssociation": "5.5", + "EngineAssociation": "{356A5F04-40C2-B818-AF32-F5BE45FE0398}", "Category": "", "Description": "", "Plugins": [ @@ -36,15 +36,6 @@ { "Name": "VictoryBPLibrary", "Enabled": true - }, - { - "Name": "OculusXR", - "Enabled": true, - "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/8313d8d7e7cf4e03a33e79eb757bccba", - "SupportedTargetPlatforms": [ - "Win64", - "Android" - ] } ], "TargetPlatforms": [