diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..c4c27d0 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,12 @@ +{ + "permissions": { + "allow": [ + "mcp__unreal-mcp__describe_toolset", + "mcp__unreal-mcp__call_tool", + "Bash(python *)" + ] + }, + "enabledMcpjsonServers": [ + "unreal-mcp" + ] +} diff --git a/.gitignore b/.gitignore index 715b38f..401a659 100644 --- a/.gitignore +++ b/.gitignore @@ -1,76 +1,16 @@ -# ---> UnrealEngine -# Visual Studio 2015 user specific files -.vs/ - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app -*.ipa - -# These project files can be generated by the engine -*.xcodeproj -*.xcworkspace +# Unreal Engine intermediate/build artifacts +Binaries/ +Build/ +DerivedDataCache/ +Intermediate/ +Saved/ +Content/Config/completedMissions.xml +Content/Config/savedSettings.xml +.vs +.vscode/ *.sln *.suo -*.opensdf -*.sdf *.VC.db *.VC.opendb - -# Precompiled Assets -SourceArt/**/*.png -SourceArt/**/*.tga - -# Binary Files -Binaries/* -Plugins/**/Binaries/* - -# Builds -Build/* - -# Whitelist PakBlacklist-.txt files -!Build/*/ -Build/*/** -!Build/*/PakBlacklist*.txt - -# Don't ignore icon files in Build -!Build/**/*.ico - -# Built data for maps -*_BuiltData.uasset - -# Configuration files generated by the Editor -Saved/* - -# Compiled source files for the engine to use -Intermediate/* -Plugins/**/Intermediate/* - -# Cache files for the editor to use -DerivedDataCache/* - +/Content/__ExternalActors__ +/Content/__ExternalObjects__ diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..d2e3ee5 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,10 @@ +{ + "mcpServers": + { + "unreal-mcp": + { + "type": "http", + "url": "http://127.0.0.1:8000/mcp" + } + } +} \ No newline at end of file diff --git a/.vsconfig b/.vsconfig new file mode 100644 index 0000000..93c1dfa --- /dev/null +++ b/.vsconfig @@ -0,0 +1,17 @@ +{ + "version": "1.0", + "components": [ + "Component.Unreal.Debugger", + "Component.Unreal.Ide", + "Microsoft.Net.Component.4.6.2.TargetingPack", + "Microsoft.VisualStudio.Component.VC.14.44.17.14.ATL", + "Microsoft.VisualStudio.Component.VC.14.44.17.14.x86.x64", + "Microsoft.VisualStudio.Component.VC.Llvm.Clang", + "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", + "Microsoft.VisualStudio.Component.Windows11SDK.22621", + "Microsoft.VisualStudio.Workload.CoreEditor", + "Microsoft.VisualStudio.Workload.ManagedDesktop", + "Microsoft.VisualStudio.Workload.NativeDesktop", + "Microsoft.VisualStudio.Workload.NativeGame" + ] +} diff --git a/Automation_KingshearthLegacy.slnx b/Automation_KingshearthLegacy.slnx new file mode 100644 index 0000000..f60c4cb --- /dev/null +++ b/Automation_KingshearthLegacy.slnx @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Config/DefaultEditor.ini b/Config/DefaultEditor.ini new file mode 100644 index 0000000..79b70b5 --- /dev/null +++ b/Config/DefaultEditor.ini @@ -0,0 +1,9 @@ +[UnrealEd.SimpleMap] +SimpleMapName=/Game/ThirdPersonCPP/Maps/ThirdPersonExampleMap + +[EditoronlyBP] +bAllowClassAndBlueprintPinMatching=true +bReplaceBlueprintWithClass= true +bDontLoadBlueprintOutsideEditor= true +bBlueprintIsNotBlueprintType= true + diff --git a/Config/DefaultEditorPerProjectUserSettings.ini b/Config/DefaultEditorPerProjectUserSettings.ini new file mode 100644 index 0000000..4dcc526 --- /dev/null +++ b/Config/DefaultEditorPerProjectUserSettings.ini @@ -0,0 +1,2 @@ +[ContentBrowser] +ContentBrowserTab1.SelectedPaths=/Game/ThirdPersonCPP \ No newline at end of file diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini new file mode 100644 index 0000000..870fdc0 --- /dev/null +++ b/Config/DefaultEngine.ini @@ -0,0 +1,95 @@ +[/Script/EngineSettings.GameMapsSettings] +GameDefaultMap=/Game/ThirdPerson/Lvl_ThirdPerson.Lvl_ThirdPerson +EditorStartupMap=/Game/ThirdPerson/Lvl_ThirdPerson.Lvl_ThirdPerson +GlobalDefaultGameMode=/Game/ThirdPerson/Blueprints/BP_ThirdPersonGameMode.BP_ThirdPersonGameMode_C +bOffsetPlayerGamepadIds=False + +[/Script/Engine.RendererSettings] +r.ReflectionMethod=1 +r.GenerateMeshDistanceFields=True +r.DynamicGlobalIlluminationMethod=1 +r.Lumen.TraceMeshSDFs=0 +r.Shadow.Virtual.Enable=1 +r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True +r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=true +r.AllowStaticLighting=False + +r.SkinCache.CompileShaders=True + +r.RayTracing=True + +r.RayTracing.RayTracingProxies.ProjectEnabled=True + +r.Substrate=True + +r.Substrate.ProjectGBufferFormat=0 + +r.DefaultFeature.LocalExposure.HighlightContrastScale=0.8 + +r.DefaultFeature.LocalExposure.ShadowContrastScale=0.8 + +[/Script/WindowsTargetPlatform.WindowsTargetSettings] +DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 +DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 +-D3D12TargetedShaderFormats=PCD3D_SM5 ++D3D12TargetedShaderFormats=PCD3D_SM6 +-D3D11TargetedShaderFormats=PCD3D_SM5 ++D3D11TargetedShaderFormats=PCD3D_SM5 +Compiler=Default +AudioSampleRate=48000 +AudioCallbackBufferFrameSize=1024 +AudioNumBuffersToEnqueue=1 +AudioMaxChannels=0 +AudioNumSourceWorkers=4 +SpatializationPlugin= +SourceDataOverridePlugin= +ReverbPlugin= +OcclusionPlugin= +CompressionOverrides=(bOverrideCompressionTimes=False,DurationThreshold=5.000000,MaxNumRandomBranches=0,SoundCueQualityIndex=0) +CacheSizeKB=65536 +MaxChunkSizeOverrideKB=0 +bResampleForDevice=False +MaxSampleRate=48000.000000 +HighSampleRate=32000.000000 +MedSampleRate=24000.000000 +LowSampleRate=12000.000000 +MinSampleRate=8000.000000 +CompressionQualityModifier=1.000000 +AutoStreamingThreshold=0.000000 +SoundCueCookQualityIndex=-1 + +[/Script/LinuxTargetPlatform.LinuxTargetSettings] +-TargetedRHIs=SF_VULKAN_SM5 ++TargetedRHIs=SF_VULKAN_SM6 + +[/Script/MacTargetPlatform.MacTargetSettings] +-TargetedRHIs=SF_METAL_SM5 ++TargetedRHIs=SF_METAL_SM6 + +[/Script/HardwareTargeting.HardwareTargetingSettings] +TargetedHardwareClass=Desktop +AppliedTargetedHardwareClass=Desktop +DefaultGraphicsPerformance=Maximum +AppliedDefaultGraphicsPerformance=Maximum + +[/Script/Engine.Engine] ++ActiveGameNameRedirects=(OldGameName="TP_ThirdPerson",NewGameName="/Script/KingshearthLegacy") ++ActiveGameNameRedirects=(OldGameName="/Script/TP_ThirdPerson",NewGameName="/Script/KingshearthLegacy") ++ActiveClassRedirects=(OldClassName="TP_ThirdPersonPlayerController",NewClassName="KingshearthLegacyPlayerController") ++ActiveClassRedirects=(OldClassName="TP_ThirdPersonGameMode",NewClassName="KingshearthLegacyGameMode") ++ActiveClassRedirects=(OldClassName="TP_ThirdPersonCharacter",NewClassName="KingshearthLegacyCharacter") + +[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] +bEnablePlugin=True +bAllowNetworkConnection=True +SecurityToken=37944E7D407F8DAAEB4D25A7D45B8F52 +bIncludeInShipping=False +bAllowExternalStartInShipping=False +bCompileAFSProject=False +bUseCompression=False +bLogFiles=False +bReportStats=False +ConnectionType=USBOnly +bUseManualIPAddress=False +ManualIPAddress= + diff --git a/Config/DefaultGame.ini b/Config/DefaultGame.ini new file mode 100644 index 0000000..a9f6caa --- /dev/null +++ b/Config/DefaultGame.ini @@ -0,0 +1,8 @@ +[/Script/EngineSettings.GeneralProjectSettings] +ProjectID=CE954E444D2A872317AAC38182169D03 +ProjectName=Third Person Game Template + +[ConsoleVariables] +CommonUI.CheckKeyboardFocusAndParentage=1 +CommonUI.DisallowUserFocusedWidgetForPendingFocusRecipient=1 +CommonUI.FallbackToDesiredOnAutoRestoreFailure=1 diff --git a/Config/DefaultInput.ini b/Config/DefaultInput.ini new file mode 100644 index 0000000..ad7bb00 --- /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="MouseY",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="Mouse2D",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++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="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 +bShouldFlushPressedKeysOnViewportFocusLost=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=None +-ConsoleKeys=Tilde ++ConsoleKeys=Tilde + diff --git a/Content/Characters/Mannequins/Anims/Death/MM_Death_Back_01.uasset b/Content/Characters/Mannequins/Anims/Death/MM_Death_Back_01.uasset new file mode 100644 index 0000000..917cff8 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Death/MM_Death_Back_01.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Death/MM_Death_Front_01.uasset b/Content/Characters/Mannequins/Anims/Death/MM_Death_Front_01.uasset new file mode 100644 index 0000000..ccfd8a9 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Death/MM_Death_Front_01.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Death/MM_Death_Front_02.uasset b/Content/Characters/Mannequins/Anims/Death/MM_Death_Front_02.uasset new file mode 100644 index 0000000..a5ccfc5 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Death/MM_Death_Front_02.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Death/MM_Death_Front_03.uasset b/Content/Characters/Mannequins/Anims/Death/MM_Death_Front_03.uasset new file mode 100644 index 0000000..d31ec0f Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Death/MM_Death_Front_03.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Death/MM_Death_Left_01.uasset b/Content/Characters/Mannequins/Anims/Death/MM_Death_Left_01.uasset new file mode 100644 index 0000000..2ad429e Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Death/MM_Death_Left_01.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Death/MM_Death_Right_01.uasset b/Content/Characters/Mannequins/Anims/Death/MM_Death_Right_01.uasset new file mode 100644 index 0000000..bce3365 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Death/MM_Death_Right_01.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Aim/AO_Pistol.uasset b/Content/Characters/Mannequins/Anims/Pistol/Aim/AO_Pistol.uasset new file mode 100644 index 0000000..cc21f78 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Aim/AO_Pistol.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Aim/MF_Pistol_Idle_ADS1.uasset b/Content/Characters/Mannequins/Anims/Pistol/Aim/MF_Pistol_Idle_ADS1.uasset new file mode 100644 index 0000000..09c54fa Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Aim/MF_Pistol_Idle_ADS1.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Aim/MF_Pistol_Idle_ADS_AO_CD.uasset b/Content/Characters/Mannequins/Anims/Pistol/Aim/MF_Pistol_Idle_ADS_AO_CD.uasset new file mode 100644 index 0000000..94e36c7 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Aim/MF_Pistol_Idle_ADS_AO_CD.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Aim/MF_Pistol_Idle_ADS_AO_CU.uasset b/Content/Characters/Mannequins/Anims/Pistol/Aim/MF_Pistol_Idle_ADS_AO_CU.uasset new file mode 100644 index 0000000..f8cd07f Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Aim/MF_Pistol_Idle_ADS_AO_CU.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Bwd.uasset b/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Bwd.uasset new file mode 100644 index 0000000..c5bc3a0 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Bwd.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Bwd_Left.uasset b/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Bwd_Left.uasset new file mode 100644 index 0000000..1c0d6a0 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Bwd_Left.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Bwd_Right.uasset b/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Bwd_Right.uasset new file mode 100644 index 0000000..e934901 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Bwd_Right.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Fwd.uasset b/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Fwd.uasset new file mode 100644 index 0000000..cc14f22 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Fwd.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Fwd_Left.uasset b/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Fwd_Left.uasset new file mode 100644 index 0000000..955f71d Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Fwd_Left.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Fwd_Right.uasset b/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Fwd_Right.uasset new file mode 100644 index 0000000..bcd6de6 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Fwd_Right.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Left.uasset b/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Left.uasset new file mode 100644 index 0000000..d5f15aa Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Left.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Right.uasset b/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Right.uasset new file mode 100644 index 0000000..e98c2d4 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Jog/MF_Pistol_Jog_Right.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Jump/MM_Pistol_Jump_Fall_Loop.uasset b/Content/Characters/Mannequins/Anims/Pistol/Jump/MM_Pistol_Jump_Fall_Loop.uasset new file mode 100644 index 0000000..7ca0d7c Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Jump/MM_Pistol_Jump_Fall_Loop.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Jump/MM_Pistol_Jump_RecoveryAdditive.uasset b/Content/Characters/Mannequins/Anims/Pistol/Jump/MM_Pistol_Jump_RecoveryAdditive.uasset new file mode 100644 index 0000000..9903187 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Jump/MM_Pistol_Jump_RecoveryAdditive.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Jump/MM_Pistol_Jump_Start.uasset b/Content/Characters/Mannequins/Anims/Pistol/Jump/MM_Pistol_Jump_Start.uasset new file mode 100644 index 0000000..2e47a86 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Jump/MM_Pistol_Jump_Start.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/MF_Pistol_Idle_ADS.uasset b/Content/Characters/Mannequins/Anims/Pistol/MF_Pistol_Idle_ADS.uasset new file mode 100644 index 0000000..5829066 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/MF_Pistol_Idle_ADS.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/MM_Pistol_DryFire.uasset b/Content/Characters/Mannequins/Anims/Pistol/MM_Pistol_DryFire.uasset new file mode 100644 index 0000000..d04d2f5 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/MM_Pistol_DryFire.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/MM_Pistol_Equip.uasset b/Content/Characters/Mannequins/Anims/Pistol/MM_Pistol_Equip.uasset new file mode 100644 index 0000000..3c416f2 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/MM_Pistol_Equip.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/MM_Pistol_Fire.uasset b/Content/Characters/Mannequins/Anims/Pistol/MM_Pistol_Fire.uasset new file mode 100644 index 0000000..7120917 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/MM_Pistol_Fire.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/MM_Pistol_Fire_Montage.uasset b/Content/Characters/Mannequins/Anims/Pistol/MM_Pistol_Fire_Montage.uasset new file mode 100644 index 0000000..75d7c8c Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/MM_Pistol_Fire_Montage.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/MM_Pistol_Reload.uasset b/Content/Characters/Mannequins/Anims/Pistol/MM_Pistol_Reload.uasset new file mode 100644 index 0000000..077674e Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/MM_Pistol_Reload.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Bwd.uasset b/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Bwd.uasset new file mode 100644 index 0000000..2cd3e66 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Bwd.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Bwd_Left.uasset b/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Bwd_Left.uasset new file mode 100644 index 0000000..eb16ba5 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Bwd_Left.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Bwd_Right.uasset b/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Bwd_Right.uasset new file mode 100644 index 0000000..c0e647d Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Bwd_Right.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Fwd.uasset b/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Fwd.uasset new file mode 100644 index 0000000..dee4cde Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Fwd.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Fwd_Left.uasset b/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Fwd_Left.uasset new file mode 100644 index 0000000..06eee3a Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Fwd_Left.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Fwd_Right.uasset b/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Fwd_Right.uasset new file mode 100644 index 0000000..2ff4293 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Fwd_Right.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Left.uasset b/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Left.uasset new file mode 100644 index 0000000..c277b2c Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Left.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Right.uasset b/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Right.uasset new file mode 100644 index 0000000..885473d Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Pistol/Walk/MF_Pistol_Walk_Right.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/AIM/AO_Rifle.uasset b/Content/Characters/Mannequins/Anims/Rifle/AIM/AO_Rifle.uasset new file mode 100644 index 0000000..1b85a18 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/AIM/AO_Rifle.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/AIM/MM_Rifle_Idle_ADS_AO_CC.uasset b/Content/Characters/Mannequins/Anims/Rifle/AIM/MM_Rifle_Idle_ADS_AO_CC.uasset new file mode 100644 index 0000000..95292ac Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/AIM/MM_Rifle_Idle_ADS_AO_CC.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/AIM/MM_Rifle_Idle_ADS_AO_CD.uasset b/Content/Characters/Mannequins/Anims/Rifle/AIM/MM_Rifle_Idle_ADS_AO_CD.uasset new file mode 100644 index 0000000..c5c06f9 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/AIM/MM_Rifle_Idle_ADS_AO_CD.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/AIM/MM_Rifle_Idle_ADS_AO_CU.uasset b/Content/Characters/Mannequins/Anims/Rifle/AIM/MM_Rifle_Idle_ADS_AO_CU.uasset new file mode 100644 index 0000000..eb8a080 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/AIM/MM_Rifle_Idle_ADS_AO_CU.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Back_Med_01.uasset b/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Back_Med_01.uasset new file mode 100644 index 0000000..74eb352 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Back_Med_01.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Hvy_01.uasset b/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Hvy_01.uasset new file mode 100644 index 0000000..396e9f3 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Hvy_01.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Lgt_01.uasset b/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Lgt_01.uasset new file mode 100644 index 0000000..a6846ff Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Lgt_01.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Lgt_02.uasset b/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Lgt_02.uasset new file mode 100644 index 0000000..d0bef37 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Lgt_02.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Lgt_03.uasset b/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Lgt_03.uasset new file mode 100644 index 0000000..dca4bc6 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Lgt_03.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Lgt_04.uasset b/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Lgt_04.uasset new file mode 100644 index 0000000..d134adb Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Lgt_04.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Med_01.uasset b/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Med_01.uasset new file mode 100644 index 0000000..70302cf Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Med_01.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Med_02.uasset b/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Med_02.uasset new file mode 100644 index 0000000..fe6d7d6 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/HitReact/MM_HitReact_Front_Med_02.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Bwd.uasset b/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Bwd.uasset new file mode 100644 index 0000000..cc1c3a2 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Bwd.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Bwd_Left.uasset b/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Bwd_Left.uasset new file mode 100644 index 0000000..23a8ebe Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Bwd_Left.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Bwd_Right.uasset b/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Bwd_Right.uasset new file mode 100644 index 0000000..2cd5292 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Bwd_Right.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Fwd.uasset b/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Fwd.uasset new file mode 100644 index 0000000..0cc1859 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Fwd.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Fwd_Left.uasset b/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Fwd_Left.uasset new file mode 100644 index 0000000..6aa7b51 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Fwd_Left.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Fwd_Right.uasset b/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Fwd_Right.uasset new file mode 100644 index 0000000..924600c Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Fwd_Right.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Left.uasset b/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Left.uasset new file mode 100644 index 0000000..edbb191 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Left.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Right.uasset b/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Right.uasset new file mode 100644 index 0000000..84e67fd Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Jog/MF_Rifle_Jog_Right.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Jump/MM_Rifle_Jump_Apex.uasset b/Content/Characters/Mannequins/Anims/Rifle/Jump/MM_Rifle_Jump_Apex.uasset new file mode 100644 index 0000000..858c4c5 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Jump/MM_Rifle_Jump_Apex.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Jump/MM_Rifle_Jump_Fall_Land.uasset b/Content/Characters/Mannequins/Anims/Rifle/Jump/MM_Rifle_Jump_Fall_Land.uasset new file mode 100644 index 0000000..b411ecc Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Jump/MM_Rifle_Jump_Fall_Land.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Jump/MM_Rifle_Jump_Fall_Loop.uasset b/Content/Characters/Mannequins/Anims/Rifle/Jump/MM_Rifle_Jump_Fall_Loop.uasset new file mode 100644 index 0000000..826ae53 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Jump/MM_Rifle_Jump_Fall_Loop.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Jump/MM_Rifle_Jump_RecoveryAdditive.uasset b/Content/Characters/Mannequins/Anims/Rifle/Jump/MM_Rifle_Jump_RecoveryAdditive.uasset new file mode 100644 index 0000000..9baadd3 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Jump/MM_Rifle_Jump_RecoveryAdditive.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Jump/MM_Rifle_Jump_Start.uasset b/Content/Characters/Mannequins/Anims/Rifle/Jump/MM_Rifle_Jump_Start.uasset new file mode 100644 index 0000000..513b51e Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Jump/MM_Rifle_Jump_Start.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Jump/MM_Rifle_Jump_Start_Loop.uasset b/Content/Characters/Mannequins/Anims/Rifle/Jump/MM_Rifle_Jump_Start_Loop.uasset new file mode 100644 index 0000000..fc1774b Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Jump/MM_Rifle_Jump_Start_Loop.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/MF_Rifle_Idle_ADS.uasset b/Content/Characters/Mannequins/Anims/Rifle/MF_Rifle_Idle_ADS.uasset new file mode 100644 index 0000000..f569392 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/MF_Rifle_Idle_ADS.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/MM_Rifle_DryFire.uasset b/Content/Characters/Mannequins/Anims/Rifle/MM_Rifle_DryFire.uasset new file mode 100644 index 0000000..8db0457 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/MM_Rifle_DryFire.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/MM_Rifle_Equip.uasset b/Content/Characters/Mannequins/Anims/Rifle/MM_Rifle_Equip.uasset new file mode 100644 index 0000000..faf20c7 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/MM_Rifle_Equip.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/MM_Rifle_Fire.uasset b/Content/Characters/Mannequins/Anims/Rifle/MM_Rifle_Fire.uasset new file mode 100644 index 0000000..3120009 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/MM_Rifle_Fire.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/MM_Rifle_Reload.uasset b/Content/Characters/Mannequins/Anims/Rifle/MM_Rifle_Reload.uasset new file mode 100644 index 0000000..ebee502 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/MM_Rifle_Reload.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Bwd.uasset b/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Bwd.uasset new file mode 100644 index 0000000..29f95fd Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Bwd.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Bwd_Left.uasset b/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Bwd_Left.uasset new file mode 100644 index 0000000..86ed9ac Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Bwd_Left.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Bwd_Right.uasset b/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Bwd_Right.uasset new file mode 100644 index 0000000..aed754e Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Bwd_Right.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Fwd.uasset b/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Fwd.uasset new file mode 100644 index 0000000..18f0501 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Fwd.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Fwd_Left.uasset b/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Fwd_Left.uasset new file mode 100644 index 0000000..9ec1dc5 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Fwd_Left.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Fwd_Right.uasset b/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Fwd_Right.uasset new file mode 100644 index 0000000..de08560 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Fwd_Right.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Left.uasset b/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Left.uasset new file mode 100644 index 0000000..e421d11 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Left.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Right.uasset b/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Right.uasset new file mode 100644 index 0000000..f324682 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Rifle/Walk/MF_Rifle_Walk_Right.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/ABP_Unarmed.uasset b/Content/Characters/Mannequins/Anims/Unarmed/ABP_Unarmed.uasset new file mode 100644 index 0000000..0ac838a Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/ABP_Unarmed.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Attack/MM_Attack_01.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Attack/MM_Attack_01.uasset new file mode 100644 index 0000000..58bd156 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Attack/MM_Attack_01.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Attack/MM_Attack_02.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Attack/MM_Attack_02.uasset new file mode 100644 index 0000000..ec5f1e3 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Attack/MM_Attack_02.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Attack/MM_Attack_03.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Attack/MM_Attack_03.uasset new file mode 100644 index 0000000..88efb9a Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Attack/MM_Attack_03.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Attack/MM_ChargedAttack.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Attack/MM_ChargedAttack.uasset new file mode 100644 index 0000000..a6dc463 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Attack/MM_ChargedAttack.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/BS_Idle_Walk_Run.uasset b/Content/Characters/Mannequins/Anims/Unarmed/BS_Idle_Walk_Run.uasset new file mode 100644 index 0000000..de46f07 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/BS_Idle_Walk_Run.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Bwd.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Bwd.uasset new file mode 100644 index 0000000..21c9a0f Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Bwd.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Bwd_Left.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Bwd_Left.uasset new file mode 100644 index 0000000..34a4c9d Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Bwd_Left.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Bwd_Right.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Bwd_Right.uasset new file mode 100644 index 0000000..c0c75e5 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Bwd_Right.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Fwd.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Fwd.uasset new file mode 100644 index 0000000..0a5fa59 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Fwd.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Fwd_Left.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Fwd_Left.uasset new file mode 100644 index 0000000..c6c5368 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Fwd_Left.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Fwd_Right.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Fwd_Right.uasset new file mode 100644 index 0000000..112223b Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Fwd_Right.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Left.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Left.uasset new file mode 100644 index 0000000..f9fe418 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Left.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Right.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Right.uasset new file mode 100644 index 0000000..e455a2e Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Jog/MF_Unarmed_Jog_Right.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Jump/MM_Dash.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Jump/MM_Dash.uasset new file mode 100644 index 0000000..69df391 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Jump/MM_Dash.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Jump/MM_Fall_Loop.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Jump/MM_Fall_Loop.uasset new file mode 100644 index 0000000..86c58a1 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Jump/MM_Fall_Loop.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Jump/MM_Jump.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Jump/MM_Jump.uasset new file mode 100644 index 0000000..5229aa9 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Jump/MM_Jump.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Jump/MM_Land.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Jump/MM_Land.uasset new file mode 100644 index 0000000..9560d54 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Jump/MM_Land.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Jump/MM_WallJump.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Jump/MM_WallJump.uasset new file mode 100644 index 0000000..73a2af9 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Jump/MM_WallJump.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/MM_Idle.uasset b/Content/Characters/Mannequins/Anims/Unarmed/MM_Idle.uasset new file mode 100644 index 0000000..0f10045 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/MM_Idle.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Bwd.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Bwd.uasset new file mode 100644 index 0000000..03db74a Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Bwd.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Bwd_Left.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Bwd_Left.uasset new file mode 100644 index 0000000..f7b1a96 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Bwd_Left.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Bwd_Right.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Bwd_Right.uasset new file mode 100644 index 0000000..3a03801 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Bwd_Right.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Fwd.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Fwd.uasset new file mode 100644 index 0000000..d780a3a Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Fwd.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Fwd_Left.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Fwd_Left.uasset new file mode 100644 index 0000000..331e673 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Fwd_Left.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Fwd_Right.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Fwd_Right.uasset new file mode 100644 index 0000000..b715bbe Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Fwd_Right.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Left.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Left.uasset new file mode 100644 index 0000000..5b5f188 Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Left.uasset differ diff --git a/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Right.uasset b/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Right.uasset new file mode 100644 index 0000000..6d0ba3f Binary files /dev/null and b/Content/Characters/Mannequins/Anims/Unarmed/Walk/MF_Unarmed_Walk_Right.uasset differ diff --git a/Content/Characters/Mannequins/Materials/M_Mannequin.uasset b/Content/Characters/Mannequins/Materials/M_Mannequin.uasset new file mode 100644 index 0000000..a384381 Binary files /dev/null and b/Content/Characters/Mannequins/Materials/M_Mannequin.uasset differ diff --git a/Content/Characters/Mannequins/Materials/Manny/MI_Manny_01_New.uasset b/Content/Characters/Mannequins/Materials/Manny/MI_Manny_01_New.uasset new file mode 100644 index 0000000..6316baf Binary files /dev/null and b/Content/Characters/Mannequins/Materials/Manny/MI_Manny_01_New.uasset differ diff --git a/Content/Characters/Mannequins/Materials/Manny/MI_Manny_02_New.uasset b/Content/Characters/Mannequins/Materials/Manny/MI_Manny_02_New.uasset new file mode 100644 index 0000000..5c98ea5 Binary files /dev/null and b/Content/Characters/Mannequins/Materials/Manny/MI_Manny_02_New.uasset differ diff --git a/Content/Characters/Mannequins/Materials/Quinn/MI_Quinn_01.uasset b/Content/Characters/Mannequins/Materials/Quinn/MI_Quinn_01.uasset new file mode 100644 index 0000000..b2d7eec Binary files /dev/null and b/Content/Characters/Mannequins/Materials/Quinn/MI_Quinn_01.uasset differ diff --git a/Content/Characters/Mannequins/Materials/Quinn/MI_Quinn_02.uasset b/Content/Characters/Mannequins/Materials/Quinn/MI_Quinn_02.uasset new file mode 100644 index 0000000..74bbb66 Binary files /dev/null and b/Content/Characters/Mannequins/Materials/Quinn/MI_Quinn_02.uasset differ diff --git a/Content/Characters/Mannequins/Meshes/SKM_Manny_Simple.uasset b/Content/Characters/Mannequins/Meshes/SKM_Manny_Simple.uasset new file mode 100644 index 0000000..077e1f4 Binary files /dev/null and b/Content/Characters/Mannequins/Meshes/SKM_Manny_Simple.uasset differ diff --git a/Content/Characters/Mannequins/Meshes/SKM_Quinn_Simple.uasset b/Content/Characters/Mannequins/Meshes/SKM_Quinn_Simple.uasset new file mode 100644 index 0000000..df9c636 Binary files /dev/null and b/Content/Characters/Mannequins/Meshes/SKM_Quinn_Simple.uasset differ diff --git a/Content/Characters/Mannequins/Meshes/SK_Mannequin.uasset b/Content/Characters/Mannequins/Meshes/SK_Mannequin.uasset new file mode 100644 index 0000000..60f16f0 Binary files /dev/null and b/Content/Characters/Mannequins/Meshes/SK_Mannequin.uasset differ diff --git a/Content/Characters/Mannequins/Rigs/CR_Mannequin_Body.uasset b/Content/Characters/Mannequins/Rigs/CR_Mannequin_Body.uasset new file mode 100644 index 0000000..6032287 Binary files /dev/null and b/Content/Characters/Mannequins/Rigs/CR_Mannequin_Body.uasset differ diff --git a/Content/Characters/Mannequins/Rigs/CR_Mannequin_FootIK.uasset b/Content/Characters/Mannequins/Rigs/CR_Mannequin_FootIK.uasset new file mode 100644 index 0000000..b4c4426 Binary files /dev/null and b/Content/Characters/Mannequins/Rigs/CR_Mannequin_FootIK.uasset differ diff --git a/Content/Characters/Mannequins/Rigs/CR_Mannequin_Procedural.uasset b/Content/Characters/Mannequins/Rigs/CR_Mannequin_Procedural.uasset new file mode 100644 index 0000000..0b2c38d Binary files /dev/null and b/Content/Characters/Mannequins/Rigs/CR_Mannequin_Procedural.uasset differ diff --git a/Content/Characters/Mannequins/Rigs/PA_Mannequin.uasset b/Content/Characters/Mannequins/Rigs/PA_Mannequin.uasset new file mode 100644 index 0000000..3518d39 Binary files /dev/null and b/Content/Characters/Mannequins/Rigs/PA_Mannequin.uasset differ diff --git a/Content/Characters/Mannequins/Textures/Manny/T_Manny_01_BN.uasset b/Content/Characters/Mannequins/Textures/Manny/T_Manny_01_BN.uasset new file mode 100644 index 0000000..2c4145b Binary files /dev/null and b/Content/Characters/Mannequins/Textures/Manny/T_Manny_01_BN.uasset differ diff --git a/Content/Characters/Mannequins/Textures/Manny/T_Manny_01_D.uasset b/Content/Characters/Mannequins/Textures/Manny/T_Manny_01_D.uasset new file mode 100644 index 0000000..2e236b4 Binary files /dev/null and b/Content/Characters/Mannequins/Textures/Manny/T_Manny_01_D.uasset differ diff --git a/Content/Characters/Mannequins/Textures/Manny/T_Manny_01_MRA.uasset b/Content/Characters/Mannequins/Textures/Manny/T_Manny_01_MRA.uasset new file mode 100644 index 0000000..faa8537 Binary files /dev/null and b/Content/Characters/Mannequins/Textures/Manny/T_Manny_01_MRA.uasset differ diff --git a/Content/Characters/Mannequins/Textures/Manny/T_Manny_02_BN.uasset b/Content/Characters/Mannequins/Textures/Manny/T_Manny_02_BN.uasset new file mode 100644 index 0000000..fbee995 Binary files /dev/null and b/Content/Characters/Mannequins/Textures/Manny/T_Manny_02_BN.uasset differ diff --git a/Content/Characters/Mannequins/Textures/Manny/T_Manny_02_D.uasset b/Content/Characters/Mannequins/Textures/Manny/T_Manny_02_D.uasset new file mode 100644 index 0000000..56262e6 Binary files /dev/null and b/Content/Characters/Mannequins/Textures/Manny/T_Manny_02_D.uasset differ diff --git a/Content/Characters/Mannequins/Textures/Manny/T_Manny_02_MRA.uasset b/Content/Characters/Mannequins/Textures/Manny/T_Manny_02_MRA.uasset new file mode 100644 index 0000000..f2f4f83 Binary files /dev/null and b/Content/Characters/Mannequins/Textures/Manny/T_Manny_02_MRA.uasset differ diff --git a/Content/Characters/Mannequins/Textures/Manny/T_Manny_02_N.uasset b/Content/Characters/Mannequins/Textures/Manny/T_Manny_02_N.uasset new file mode 100644 index 0000000..e8e61da Binary files /dev/null and b/Content/Characters/Mannequins/Textures/Manny/T_Manny_02_N.uasset differ diff --git a/Content/Characters/Mannequins/Textures/Quinn/T_Quinn_01_D.uasset b/Content/Characters/Mannequins/Textures/Quinn/T_Quinn_01_D.uasset new file mode 100644 index 0000000..d2e0ab2 Binary files /dev/null and b/Content/Characters/Mannequins/Textures/Quinn/T_Quinn_01_D.uasset differ diff --git a/Content/Characters/Mannequins/Textures/Quinn/T_Quinn_01_MRA.uasset b/Content/Characters/Mannequins/Textures/Quinn/T_Quinn_01_MRA.uasset new file mode 100644 index 0000000..8244da9 Binary files /dev/null and b/Content/Characters/Mannequins/Textures/Quinn/T_Quinn_01_MRA.uasset differ diff --git a/Content/Characters/Mannequins/Textures/Quinn/T_Quinn_01_N.uasset b/Content/Characters/Mannequins/Textures/Quinn/T_Quinn_01_N.uasset new file mode 100644 index 0000000..44bd3c8 Binary files /dev/null and b/Content/Characters/Mannequins/Textures/Quinn/T_Quinn_01_N.uasset differ diff --git a/Content/Characters/Mannequins/Textures/Quinn/T_Quinn_02_D.uasset b/Content/Characters/Mannequins/Textures/Quinn/T_Quinn_02_D.uasset new file mode 100644 index 0000000..63bd3b7 Binary files /dev/null and b/Content/Characters/Mannequins/Textures/Quinn/T_Quinn_02_D.uasset differ diff --git a/Content/Characters/Mannequins/Textures/Quinn/T_Quinn_02_MRA.uasset b/Content/Characters/Mannequins/Textures/Quinn/T_Quinn_02_MRA.uasset new file mode 100644 index 0000000..6c4a979 Binary files /dev/null and b/Content/Characters/Mannequins/Textures/Quinn/T_Quinn_02_MRA.uasset differ diff --git a/Content/Characters/Mannequins/Textures/Quinn/T_Quinn_02_N.uasset b/Content/Characters/Mannequins/Textures/Quinn/T_Quinn_02_N.uasset new file mode 100644 index 0000000..3ba5513 Binary files /dev/null and b/Content/Characters/Mannequins/Textures/Quinn/T_Quinn_02_N.uasset differ diff --git a/Content/Characters/Mannequins/Textures/Shared/T_UE_Logo_M.uasset b/Content/Characters/Mannequins/Textures/Shared/T_UE_Logo_M.uasset new file mode 100644 index 0000000..1cc7ef6 Binary files /dev/null and b/Content/Characters/Mannequins/Textures/Shared/T_UE_Logo_M.uasset differ diff --git a/Content/Input/Actions/IA_Jump.uasset b/Content/Input/Actions/IA_Jump.uasset new file mode 100644 index 0000000..49d209b Binary files /dev/null and b/Content/Input/Actions/IA_Jump.uasset differ diff --git a/Content/Input/Actions/IA_Look.uasset b/Content/Input/Actions/IA_Look.uasset new file mode 100644 index 0000000..d87d291 Binary files /dev/null and b/Content/Input/Actions/IA_Look.uasset differ diff --git a/Content/Input/Actions/IA_MouseLook.uasset b/Content/Input/Actions/IA_MouseLook.uasset new file mode 100644 index 0000000..6161fb0 Binary files /dev/null and b/Content/Input/Actions/IA_MouseLook.uasset differ diff --git a/Content/Input/Actions/IA_Move.uasset b/Content/Input/Actions/IA_Move.uasset new file mode 100644 index 0000000..892d571 Binary files /dev/null and b/Content/Input/Actions/IA_Move.uasset differ diff --git a/Content/Input/IMC_Default.uasset b/Content/Input/IMC_Default.uasset new file mode 100644 index 0000000..4dddf94 Binary files /dev/null and b/Content/Input/IMC_Default.uasset differ diff --git a/Content/Input/IMC_MouseLook.uasset b/Content/Input/IMC_MouseLook.uasset new file mode 100644 index 0000000..1216180 Binary files /dev/null and b/Content/Input/IMC_MouseLook.uasset differ diff --git a/Content/Input/Touch/BPI_TouchInterface.uasset b/Content/Input/Touch/BPI_TouchInterface.uasset new file mode 100644 index 0000000..f627245 Binary files /dev/null and b/Content/Input/Touch/BPI_TouchInterface.uasset differ diff --git a/Content/Input/Touch/UI_Thumbstick.uasset b/Content/Input/Touch/UI_Thumbstick.uasset new file mode 100644 index 0000000..f597a90 Binary files /dev/null and b/Content/Input/Touch/UI_Thumbstick.uasset differ diff --git a/Content/Input/Touch/UI_TouchSimple.uasset b/Content/Input/Touch/UI_TouchSimple.uasset new file mode 100644 index 0000000..3b6cb2b Binary files /dev/null and b/Content/Input/Touch/UI_TouchSimple.uasset differ diff --git a/Content/LevelPrototyping/Interactable/Door/BP_DoorFrame.uasset b/Content/LevelPrototyping/Interactable/Door/BP_DoorFrame.uasset new file mode 100644 index 0000000..88f7170 Binary files /dev/null and b/Content/LevelPrototyping/Interactable/Door/BP_DoorFrame.uasset differ diff --git a/Content/LevelPrototyping/Interactable/Door/Meshes/SM_Door.uasset b/Content/LevelPrototyping/Interactable/Door/Meshes/SM_Door.uasset new file mode 100644 index 0000000..9f5a3e1 Binary files /dev/null and b/Content/LevelPrototyping/Interactable/Door/Meshes/SM_Door.uasset differ diff --git a/Content/LevelPrototyping/Interactable/Door/Meshes/SM_DoorFrame_Corner.uasset b/Content/LevelPrototyping/Interactable/Door/Meshes/SM_DoorFrame_Corner.uasset new file mode 100644 index 0000000..65c50ba Binary files /dev/null and b/Content/LevelPrototyping/Interactable/Door/Meshes/SM_DoorFrame_Corner.uasset differ diff --git a/Content/LevelPrototyping/Interactable/Door/Meshes/SM_DoorFrame_Edge.uasset b/Content/LevelPrototyping/Interactable/Door/Meshes/SM_DoorFrame_Edge.uasset new file mode 100644 index 0000000..4497526 Binary files /dev/null and b/Content/LevelPrototyping/Interactable/Door/Meshes/SM_DoorFrame_Edge.uasset differ diff --git a/Content/LevelPrototyping/Interactable/JumpPad/Assets/Materials/MI_GlowNT.uasset b/Content/LevelPrototyping/Interactable/JumpPad/Assets/Materials/MI_GlowNT.uasset new file mode 100644 index 0000000..df55d5c Binary files /dev/null and b/Content/LevelPrototyping/Interactable/JumpPad/Assets/Materials/MI_GlowNT.uasset differ diff --git a/Content/LevelPrototyping/Interactable/JumpPad/Assets/Materials/M_GradientGlow.uasset b/Content/LevelPrototyping/Interactable/JumpPad/Assets/Materials/M_GradientGlow.uasset new file mode 100644 index 0000000..0db8b14 Binary files /dev/null and b/Content/LevelPrototyping/Interactable/JumpPad/Assets/Materials/M_GradientGlow.uasset differ diff --git a/Content/LevelPrototyping/Interactable/JumpPad/Assets/Materials/M_SimpleGlow.uasset b/Content/LevelPrototyping/Interactable/JumpPad/Assets/Materials/M_SimpleGlow.uasset new file mode 100644 index 0000000..8b7eaba Binary files /dev/null and b/Content/LevelPrototyping/Interactable/JumpPad/Assets/Materials/M_SimpleGlow.uasset differ diff --git a/Content/LevelPrototyping/Interactable/JumpPad/Assets/Meshes/SM_CircularBand.uasset b/Content/LevelPrototyping/Interactable/JumpPad/Assets/Meshes/SM_CircularBand.uasset new file mode 100644 index 0000000..4fe6d9a Binary files /dev/null and b/Content/LevelPrototyping/Interactable/JumpPad/Assets/Meshes/SM_CircularBand.uasset differ diff --git a/Content/LevelPrototyping/Interactable/JumpPad/Assets/Meshes/SM_CircularGlow.uasset b/Content/LevelPrototyping/Interactable/JumpPad/Assets/Meshes/SM_CircularGlow.uasset new file mode 100644 index 0000000..86cecae Binary files /dev/null and b/Content/LevelPrototyping/Interactable/JumpPad/Assets/Meshes/SM_CircularGlow.uasset differ diff --git a/Content/LevelPrototyping/Interactable/JumpPad/Assets/NS_JumpPad.uasset b/Content/LevelPrototyping/Interactable/JumpPad/Assets/NS_JumpPad.uasset new file mode 100644 index 0000000..9981a30 Binary files /dev/null and b/Content/LevelPrototyping/Interactable/JumpPad/Assets/NS_JumpPad.uasset differ diff --git a/Content/LevelPrototyping/Interactable/JumpPad/BP_JumpPad.uasset b/Content/LevelPrototyping/Interactable/JumpPad/BP_JumpPad.uasset new file mode 100644 index 0000000..541de5c Binary files /dev/null and b/Content/LevelPrototyping/Interactable/JumpPad/BP_JumpPad.uasset differ diff --git a/Content/LevelPrototyping/Interactable/Target/Assets/SM_TargetBaseMesh.uasset b/Content/LevelPrototyping/Interactable/Target/Assets/SM_TargetBaseMesh.uasset new file mode 100644 index 0000000..f154a0e Binary files /dev/null and b/Content/LevelPrototyping/Interactable/Target/Assets/SM_TargetBaseMesh.uasset differ diff --git a/Content/LevelPrototyping/Interactable/Target/BP_WobbleTarget.uasset b/Content/LevelPrototyping/Interactable/Target/BP_WobbleTarget.uasset new file mode 100644 index 0000000..115c622 Binary files /dev/null and b/Content/LevelPrototyping/Interactable/Target/BP_WobbleTarget.uasset differ diff --git a/Content/LevelPrototyping/Materials/MF_ProcGrid.uasset b/Content/LevelPrototyping/Materials/MF_ProcGrid.uasset new file mode 100644 index 0000000..f3e6461 Binary files /dev/null and b/Content/LevelPrototyping/Materials/MF_ProcGrid.uasset differ diff --git a/Content/LevelPrototyping/Materials/MI_DefaultColorway.uasset b/Content/LevelPrototyping/Materials/MI_DefaultColorway.uasset new file mode 100644 index 0000000..76da848 Binary files /dev/null and b/Content/LevelPrototyping/Materials/MI_DefaultColorway.uasset differ diff --git a/Content/LevelPrototyping/Materials/MI_PrototypeGrid_Gray.uasset b/Content/LevelPrototyping/Materials/MI_PrototypeGrid_Gray.uasset new file mode 100644 index 0000000..63a5bfa Binary files /dev/null and b/Content/LevelPrototyping/Materials/MI_PrototypeGrid_Gray.uasset differ diff --git a/Content/LevelPrototyping/Materials/MI_PrototypeGrid_Gray_02.uasset b/Content/LevelPrototyping/Materials/MI_PrototypeGrid_Gray_02.uasset new file mode 100644 index 0000000..2accd35 Binary files /dev/null and b/Content/LevelPrototyping/Materials/MI_PrototypeGrid_Gray_02.uasset differ diff --git a/Content/LevelPrototyping/Materials/MI_PrototypeGrid_Gray_Round.uasset b/Content/LevelPrototyping/Materials/MI_PrototypeGrid_Gray_Round.uasset new file mode 100644 index 0000000..954d27f Binary files /dev/null and b/Content/LevelPrototyping/Materials/MI_PrototypeGrid_Gray_Round.uasset differ diff --git a/Content/LevelPrototyping/Materials/MI_PrototypeGrid_TopDark.uasset b/Content/LevelPrototyping/Materials/MI_PrototypeGrid_TopDark.uasset new file mode 100644 index 0000000..bc39915 Binary files /dev/null and b/Content/LevelPrototyping/Materials/MI_PrototypeGrid_TopDark.uasset differ diff --git a/Content/LevelPrototyping/Materials/M_FlatCol.uasset b/Content/LevelPrototyping/Materials/M_FlatCol.uasset new file mode 100644 index 0000000..a182f0c Binary files /dev/null and b/Content/LevelPrototyping/Materials/M_FlatCol.uasset differ diff --git a/Content/LevelPrototyping/Materials/M_PrototypeGrid.uasset b/Content/LevelPrototyping/Materials/M_PrototypeGrid.uasset new file mode 100644 index 0000000..edb0c7f Binary files /dev/null and b/Content/LevelPrototyping/Materials/M_PrototypeGrid.uasset differ diff --git a/Content/LevelPrototyping/Meshes/SM_ChamferCube.uasset b/Content/LevelPrototyping/Meshes/SM_ChamferCube.uasset new file mode 100644 index 0000000..f1df33d Binary files /dev/null and b/Content/LevelPrototyping/Meshes/SM_ChamferCube.uasset differ diff --git a/Content/LevelPrototyping/Meshes/SM_Cube.uasset b/Content/LevelPrototyping/Meshes/SM_Cube.uasset new file mode 100644 index 0000000..064d510 Binary files /dev/null and b/Content/LevelPrototyping/Meshes/SM_Cube.uasset differ diff --git a/Content/LevelPrototyping/Meshes/SM_Cylinder.uasset b/Content/LevelPrototyping/Meshes/SM_Cylinder.uasset new file mode 100644 index 0000000..c9507f3 Binary files /dev/null and b/Content/LevelPrototyping/Meshes/SM_Cylinder.uasset differ diff --git a/Content/LevelPrototyping/Meshes/SM_Plane.uasset b/Content/LevelPrototyping/Meshes/SM_Plane.uasset new file mode 100644 index 0000000..db5527a Binary files /dev/null and b/Content/LevelPrototyping/Meshes/SM_Plane.uasset differ diff --git a/Content/LevelPrototyping/Meshes/SM_QuarterCylinder.uasset b/Content/LevelPrototyping/Meshes/SM_QuarterCylinder.uasset new file mode 100644 index 0000000..2e5bcee Binary files /dev/null and b/Content/LevelPrototyping/Meshes/SM_QuarterCylinder.uasset differ diff --git a/Content/LevelPrototyping/Meshes/SM_QuarterCylinderOuter.uasset b/Content/LevelPrototyping/Meshes/SM_QuarterCylinderOuter.uasset new file mode 100644 index 0000000..fcab6f9 Binary files /dev/null and b/Content/LevelPrototyping/Meshes/SM_QuarterCylinderOuter.uasset differ diff --git a/Content/LevelPrototyping/Meshes/SM_Ramp.uasset b/Content/LevelPrototyping/Meshes/SM_Ramp.uasset new file mode 100644 index 0000000..e1293ee Binary files /dev/null and b/Content/LevelPrototyping/Meshes/SM_Ramp.uasset differ diff --git a/Content/LevelPrototyping/Textures/T_GridChecker_A.uasset b/Content/LevelPrototyping/Textures/T_GridChecker_A.uasset new file mode 100644 index 0000000..e901d24 Binary files /dev/null and b/Content/LevelPrototyping/Textures/T_GridChecker_A.uasset differ diff --git a/Content/Materials/MI_CubeBlue.uasset b/Content/Materials/MI_CubeBlue.uasset new file mode 100644 index 0000000..3c3c891 Binary files /dev/null and b/Content/Materials/MI_CubeBlue.uasset differ diff --git a/Content/Materials/MI_CubeGreen.uasset b/Content/Materials/MI_CubeGreen.uasset new file mode 100644 index 0000000..4a14181 Binary files /dev/null and b/Content/Materials/MI_CubeGreen.uasset differ diff --git a/Content/Materials/MI_CubeRed.uasset b/Content/Materials/MI_CubeRed.uasset new file mode 100644 index 0000000..7a210a2 Binary files /dev/null and b/Content/Materials/MI_CubeRed.uasset differ diff --git a/Content/Materials/MI_CubeYellow.uasset b/Content/Materials/MI_CubeYellow.uasset new file mode 100644 index 0000000..05ea2fe Binary files /dev/null and b/Content/Materials/MI_CubeYellow.uasset differ diff --git a/Content/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.uasset b/Content/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.uasset new file mode 100644 index 0000000..ce3b0a3 Binary files /dev/null and b/Content/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.uasset differ diff --git a/Content/ThirdPerson/Blueprints/BP_ThirdPersonGameMode.uasset b/Content/ThirdPerson/Blueprints/BP_ThirdPersonGameMode.uasset new file mode 100644 index 0000000..649362b Binary files /dev/null and b/Content/ThirdPerson/Blueprints/BP_ThirdPersonGameMode.uasset differ diff --git a/Content/ThirdPerson/Blueprints/BP_ThirdPersonPlayerController.uasset b/Content/ThirdPerson/Blueprints/BP_ThirdPersonPlayerController.uasset new file mode 100644 index 0000000..c0d4fc0 Binary files /dev/null and b/Content/ThirdPerson/Blueprints/BP_ThirdPersonPlayerController.uasset differ diff --git a/Content/ThirdPerson/Lvl_ThirdPerson.umap b/Content/ThirdPerson/Lvl_ThirdPerson.umap new file mode 100644 index 0000000..4846f4d Binary files /dev/null and b/Content/ThirdPerson/Lvl_ThirdPerson.umap differ diff --git a/Content/ThirdPerson/MI_ThirdPersonColWay.uasset b/Content/ThirdPerson/MI_ThirdPersonColWay.uasset new file mode 100644 index 0000000..a215863 Binary files /dev/null and b/Content/ThirdPerson/MI_ThirdPersonColWay.uasset differ diff --git a/Content/UI/M_HatchPattern.uasset b/Content/UI/M_HatchPattern.uasset new file mode 100644 index 0000000..9ba9ee4 Binary files /dev/null and b/Content/UI/M_HatchPattern.uasset differ diff --git a/Content/UI/M_HatchPattern_Inst.uasset b/Content/UI/M_HatchPattern_Inst.uasset new file mode 100644 index 0000000..887fbe4 Binary files /dev/null and b/Content/UI/M_HatchPattern_Inst.uasset differ diff --git a/Content/UI/WBP_HealthBar.uasset b/Content/UI/WBP_HealthBar.uasset new file mode 100644 index 0000000..c4dd259 Binary files /dev/null and b/Content/UI/WBP_HealthBar.uasset differ diff --git a/Content/UI/WBP_PlayerHUD.uasset b/Content/UI/WBP_PlayerHUD.uasset new file mode 100644 index 0000000..653c9ed Binary files /dev/null and b/Content/UI/WBP_PlayerHUD.uasset differ diff --git a/Content/UI/WBP_SegmentedBar.uasset b/Content/UI/WBP_SegmentedBar.uasset new file mode 100644 index 0000000..21ef12d Binary files /dev/null and b/Content/UI/WBP_SegmentedBar.uasset differ diff --git a/Content/UI/WBP_StaminaBar.uasset b/Content/UI/WBP_StaminaBar.uasset new file mode 100644 index 0000000..1442030 Binary files /dev/null and b/Content/UI/WBP_StaminaBar.uasset differ diff --git a/Content/Variant_Combat/Anims/ABP_Manny_Combat.uasset b/Content/Variant_Combat/Anims/ABP_Manny_Combat.uasset new file mode 100644 index 0000000..3c308d1 Binary files /dev/null and b/Content/Variant_Combat/Anims/ABP_Manny_Combat.uasset differ diff --git a/Content/Variant_Combat/Anims/AM_ChargedAttack.uasset b/Content/Variant_Combat/Anims/AM_ChargedAttack.uasset new file mode 100644 index 0000000..56daa84 Binary files /dev/null and b/Content/Variant_Combat/Anims/AM_ChargedAttack.uasset differ diff --git a/Content/Variant_Combat/Anims/AM_ComboAttack.uasset b/Content/Variant_Combat/Anims/AM_ComboAttack.uasset new file mode 100644 index 0000000..df8067e Binary files /dev/null and b/Content/Variant_Combat/Anims/AM_ComboAttack.uasset differ diff --git a/Content/Variant_Combat/Blueprints/AI/BP_CombatAIController.uasset b/Content/Variant_Combat/Blueprints/AI/BP_CombatAIController.uasset new file mode 100644 index 0000000..a3f6660 Binary files /dev/null and b/Content/Variant_Combat/Blueprints/AI/BP_CombatAIController.uasset differ diff --git a/Content/Variant_Combat/Blueprints/AI/BP_CombatEnemy.uasset b/Content/Variant_Combat/Blueprints/AI/BP_CombatEnemy.uasset new file mode 100644 index 0000000..73f95d6 Binary files /dev/null and b/Content/Variant_Combat/Blueprints/AI/BP_CombatEnemy.uasset differ diff --git a/Content/Variant_Combat/Blueprints/AI/BP_CombatEnemySpawner.uasset b/Content/Variant_Combat/Blueprints/AI/BP_CombatEnemySpawner.uasset new file mode 100644 index 0000000..ba3573f Binary files /dev/null and b/Content/Variant_Combat/Blueprints/AI/BP_CombatEnemySpawner.uasset differ diff --git a/Content/Variant_Combat/Blueprints/AI/EnvQuery_Evade.uasset b/Content/Variant_Combat/Blueprints/AI/EnvQuery_Evade.uasset new file mode 100644 index 0000000..912ba02 Binary files /dev/null and b/Content/Variant_Combat/Blueprints/AI/EnvQuery_Evade.uasset differ diff --git a/Content/Variant_Combat/Blueprints/AI/EnvQuery_Fallback.uasset b/Content/Variant_Combat/Blueprints/AI/EnvQuery_Fallback.uasset new file mode 100644 index 0000000..681bf0d Binary files /dev/null and b/Content/Variant_Combat/Blueprints/AI/EnvQuery_Fallback.uasset differ diff --git a/Content/Variant_Combat/Blueprints/AI/EnvQuery_Flank.uasset b/Content/Variant_Combat/Blueprints/AI/EnvQuery_Flank.uasset new file mode 100644 index 0000000..18724ef Binary files /dev/null and b/Content/Variant_Combat/Blueprints/AI/EnvQuery_Flank.uasset differ diff --git a/Content/Variant_Combat/Blueprints/AI/ST_CombatEnemy.uasset b/Content/Variant_Combat/Blueprints/AI/ST_CombatEnemy.uasset new file mode 100644 index 0000000..78e1e9b Binary files /dev/null and b/Content/Variant_Combat/Blueprints/AI/ST_CombatEnemy.uasset differ diff --git a/Content/Variant_Combat/Blueprints/BP_CameraShake_Hit_Enemy.uasset b/Content/Variant_Combat/Blueprints/BP_CameraShake_Hit_Enemy.uasset new file mode 100644 index 0000000..4d81109 Binary files /dev/null and b/Content/Variant_Combat/Blueprints/BP_CameraShake_Hit_Enemy.uasset differ diff --git a/Content/Variant_Combat/Blueprints/BP_CameraShake_Hit_Player.uasset b/Content/Variant_Combat/Blueprints/BP_CameraShake_Hit_Player.uasset new file mode 100644 index 0000000..ae20d4b Binary files /dev/null and b/Content/Variant_Combat/Blueprints/BP_CameraShake_Hit_Player.uasset differ diff --git a/Content/Variant_Combat/Blueprints/BP_CombatCharacter.uasset b/Content/Variant_Combat/Blueprints/BP_CombatCharacter.uasset new file mode 100644 index 0000000..acd95a1 Binary files /dev/null and b/Content/Variant_Combat/Blueprints/BP_CombatCharacter.uasset differ diff --git a/Content/Variant_Combat/Blueprints/BP_CombatGameMode.uasset b/Content/Variant_Combat/Blueprints/BP_CombatGameMode.uasset new file mode 100644 index 0000000..0ca1252 Binary files /dev/null and b/Content/Variant_Combat/Blueprints/BP_CombatGameMode.uasset differ diff --git a/Content/Variant_Combat/Blueprints/BP_CombatPlayerController.uasset b/Content/Variant_Combat/Blueprints/BP_CombatPlayerController.uasset new file mode 100644 index 0000000..e24902e Binary files /dev/null and b/Content/Variant_Combat/Blueprints/BP_CombatPlayerController.uasset differ diff --git a/Content/Variant_Combat/Blueprints/Interactables/BP_CombatActivationVolume.uasset b/Content/Variant_Combat/Blueprints/Interactables/BP_CombatActivationVolume.uasset new file mode 100644 index 0000000..90dd2c2 Binary files /dev/null and b/Content/Variant_Combat/Blueprints/Interactables/BP_CombatActivationVolume.uasset differ diff --git a/Content/Variant_Combat/Blueprints/Interactables/BP_CombatCheckpointVolume.uasset b/Content/Variant_Combat/Blueprints/Interactables/BP_CombatCheckpointVolume.uasset new file mode 100644 index 0000000..9682ec7 Binary files /dev/null and b/Content/Variant_Combat/Blueprints/Interactables/BP_CombatCheckpointVolume.uasset differ diff --git a/Content/Variant_Combat/Blueprints/Interactables/BP_CombatDamageableBox.uasset b/Content/Variant_Combat/Blueprints/Interactables/BP_CombatDamageableBox.uasset new file mode 100644 index 0000000..e0d89a8 Binary files /dev/null and b/Content/Variant_Combat/Blueprints/Interactables/BP_CombatDamageableBox.uasset differ diff --git a/Content/Variant_Combat/Blueprints/Interactables/BP_CombatDummy.uasset b/Content/Variant_Combat/Blueprints/Interactables/BP_CombatDummy.uasset new file mode 100644 index 0000000..e687bb9 Binary files /dev/null and b/Content/Variant_Combat/Blueprints/Interactables/BP_CombatDummy.uasset differ diff --git a/Content/Variant_Combat/Blueprints/Interactables/BP_CombatLavaFloor.uasset b/Content/Variant_Combat/Blueprints/Interactables/BP_CombatLavaFloor.uasset new file mode 100644 index 0000000..fb931fd Binary files /dev/null and b/Content/Variant_Combat/Blueprints/Interactables/BP_CombatLavaFloor.uasset differ diff --git a/Content/Variant_Combat/Input/Actions/IA_ChargedAttack.uasset b/Content/Variant_Combat/Input/Actions/IA_ChargedAttack.uasset new file mode 100644 index 0000000..665e2b1 Binary files /dev/null and b/Content/Variant_Combat/Input/Actions/IA_ChargedAttack.uasset differ diff --git a/Content/Variant_Combat/Input/Actions/IA_ComboAttack.uasset b/Content/Variant_Combat/Input/Actions/IA_ComboAttack.uasset new file mode 100644 index 0000000..4a6fdfa Binary files /dev/null and b/Content/Variant_Combat/Input/Actions/IA_ComboAttack.uasset differ diff --git a/Content/Variant_Combat/Input/Actions/IA_ToggleCameraSide.uasset b/Content/Variant_Combat/Input/Actions/IA_ToggleCameraSide.uasset new file mode 100644 index 0000000..12595e9 Binary files /dev/null and b/Content/Variant_Combat/Input/Actions/IA_ToggleCameraSide.uasset differ diff --git a/Content/Variant_Combat/Input/BPI_TouchInterface_Combat.uasset b/Content/Variant_Combat/Input/BPI_TouchInterface_Combat.uasset new file mode 100644 index 0000000..744ad76 Binary files /dev/null and b/Content/Variant_Combat/Input/BPI_TouchInterface_Combat.uasset differ diff --git a/Content/Variant_Combat/Input/IMC_Combat.uasset b/Content/Variant_Combat/Input/IMC_Combat.uasset new file mode 100644 index 0000000..d58afcf Binary files /dev/null and b/Content/Variant_Combat/Input/IMC_Combat.uasset differ diff --git a/Content/Variant_Combat/Input/UI_TouchInterface_Combat.uasset b/Content/Variant_Combat/Input/UI_TouchInterface_Combat.uasset new file mode 100644 index 0000000..8be9c1b Binary files /dev/null and b/Content/Variant_Combat/Input/UI_TouchInterface_Combat.uasset differ diff --git a/Content/Variant_Combat/Lvl_Combat.umap b/Content/Variant_Combat/Lvl_Combat.umap new file mode 100644 index 0000000..caa3ad0 Binary files /dev/null and b/Content/Variant_Combat/Lvl_Combat.umap differ diff --git a/Content/Variant_Combat/Materials/MI_Box_Destroyed.uasset b/Content/Variant_Combat/Materials/MI_Box_Destroyed.uasset new file mode 100644 index 0000000..a690dfa Binary files /dev/null and b/Content/Variant_Combat/Materials/MI_Box_Destroyed.uasset differ diff --git a/Content/Variant_Combat/Materials/M_Lava.uasset b/Content/Variant_Combat/Materials/M_Lava.uasset new file mode 100644 index 0000000..2005b6c Binary files /dev/null and b/Content/Variant_Combat/Materials/M_Lava.uasset differ diff --git a/Content/Variant_Combat/UI/UI_LifeBar.uasset b/Content/Variant_Combat/UI/UI_LifeBar.uasset new file mode 100644 index 0000000..a192e4e Binary files /dev/null and b/Content/Variant_Combat/UI/UI_LifeBar.uasset differ diff --git a/Content/Variant_Combat/VFX/NS_Damage.uasset b/Content/Variant_Combat/VFX/NS_Damage.uasset new file mode 100644 index 0000000..a1d8ecb Binary files /dev/null and b/Content/Variant_Combat/VFX/NS_Damage.uasset differ diff --git a/Content/Variant_Platforming/Anims/ABP_Manny_Platforming.uasset b/Content/Variant_Platforming/Anims/ABP_Manny_Platforming.uasset new file mode 100644 index 0000000..9efeee9 Binary files /dev/null and b/Content/Variant_Platforming/Anims/ABP_Manny_Platforming.uasset differ diff --git a/Content/Variant_Platforming/Anims/AM_Dash.uasset b/Content/Variant_Platforming/Anims/AM_Dash.uasset new file mode 100644 index 0000000..6477cbc Binary files /dev/null and b/Content/Variant_Platforming/Anims/AM_Dash.uasset differ diff --git a/Content/Variant_Platforming/Blueprints/BP_PlatformingCharacter.uasset b/Content/Variant_Platforming/Blueprints/BP_PlatformingCharacter.uasset new file mode 100644 index 0000000..974e422 Binary files /dev/null and b/Content/Variant_Platforming/Blueprints/BP_PlatformingCharacter.uasset differ diff --git a/Content/Variant_Platforming/Blueprints/BP_PlatformingGameMode.uasset b/Content/Variant_Platforming/Blueprints/BP_PlatformingGameMode.uasset new file mode 100644 index 0000000..484626b Binary files /dev/null and b/Content/Variant_Platforming/Blueprints/BP_PlatformingGameMode.uasset differ diff --git a/Content/Variant_Platforming/Blueprints/BP_PlatformingPlayerController.uasset b/Content/Variant_Platforming/Blueprints/BP_PlatformingPlayerController.uasset new file mode 100644 index 0000000..4294554 Binary files /dev/null and b/Content/Variant_Platforming/Blueprints/BP_PlatformingPlayerController.uasset differ diff --git a/Content/Variant_Platforming/Input/Actions/IA_Dash.uasset b/Content/Variant_Platforming/Input/Actions/IA_Dash.uasset new file mode 100644 index 0000000..690cbd5 Binary files /dev/null and b/Content/Variant_Platforming/Input/Actions/IA_Dash.uasset differ diff --git a/Content/Variant_Platforming/Input/BPI_TouchInterface_Platforming.uasset b/Content/Variant_Platforming/Input/BPI_TouchInterface_Platforming.uasset new file mode 100644 index 0000000..293820a Binary files /dev/null and b/Content/Variant_Platforming/Input/BPI_TouchInterface_Platforming.uasset differ diff --git a/Content/Variant_Platforming/Input/IMC_Platforming.uasset b/Content/Variant_Platforming/Input/IMC_Platforming.uasset new file mode 100644 index 0000000..05ac570 Binary files /dev/null and b/Content/Variant_Platforming/Input/IMC_Platforming.uasset differ diff --git a/Content/Variant_Platforming/Input/UI_TouchInterface_Platforming.uasset b/Content/Variant_Platforming/Input/UI_TouchInterface_Platforming.uasset new file mode 100644 index 0000000..ec067ea Binary files /dev/null and b/Content/Variant_Platforming/Input/UI_TouchInterface_Platforming.uasset differ diff --git a/Content/Variant_Platforming/Lvl_Platforming.umap b/Content/Variant_Platforming/Lvl_Platforming.umap new file mode 100644 index 0000000..3814014 Binary files /dev/null and b/Content/Variant_Platforming/Lvl_Platforming.umap differ diff --git a/Content/Variant_Platforming/VFX/NS_Jump_Trail.uasset b/Content/Variant_Platforming/VFX/NS_Jump_Trail.uasset new file mode 100644 index 0000000..3cfe81a Binary files /dev/null and b/Content/Variant_Platforming/VFX/NS_Jump_Trail.uasset differ diff --git a/Content/Variant_SideScrolling/Anims/ABP_Manny_SideScroller.uasset b/Content/Variant_SideScrolling/Anims/ABP_Manny_SideScroller.uasset new file mode 100644 index 0000000..2f401c0 Binary files /dev/null and b/Content/Variant_SideScrolling/Anims/ABP_Manny_SideScroller.uasset differ diff --git a/Content/Variant_SideScrolling/Blueprints/AI/BP_SideScrollingAIController.uasset b/Content/Variant_SideScrolling/Blueprints/AI/BP_SideScrollingAIController.uasset new file mode 100644 index 0000000..313522c Binary files /dev/null and b/Content/Variant_SideScrolling/Blueprints/AI/BP_SideScrollingAIController.uasset differ diff --git a/Content/Variant_SideScrolling/Blueprints/AI/BP_SideScrollingNPC.uasset b/Content/Variant_SideScrolling/Blueprints/AI/BP_SideScrollingNPC.uasset new file mode 100644 index 0000000..757eb16 Binary files /dev/null and b/Content/Variant_SideScrolling/Blueprints/AI/BP_SideScrollingNPC.uasset differ diff --git a/Content/Variant_SideScrolling/Blueprints/AI/ST_SideScrollingNPC.uasset b/Content/Variant_SideScrolling/Blueprints/AI/ST_SideScrollingNPC.uasset new file mode 100644 index 0000000..f55c594 Binary files /dev/null and b/Content/Variant_SideScrolling/Blueprints/AI/ST_SideScrollingNPC.uasset differ diff --git a/Content/Variant_SideScrolling/Blueprints/BP_SideScrollingCameraManager.uasset b/Content/Variant_SideScrolling/Blueprints/BP_SideScrollingCameraManager.uasset new file mode 100644 index 0000000..b1fcdb0 Binary files /dev/null and b/Content/Variant_SideScrolling/Blueprints/BP_SideScrollingCameraManager.uasset differ diff --git a/Content/Variant_SideScrolling/Blueprints/BP_SideScrollingCharacter.uasset b/Content/Variant_SideScrolling/Blueprints/BP_SideScrollingCharacter.uasset new file mode 100644 index 0000000..1285a38 Binary files /dev/null and b/Content/Variant_SideScrolling/Blueprints/BP_SideScrollingCharacter.uasset differ diff --git a/Content/Variant_SideScrolling/Blueprints/BP_SideScrollingGameMode.uasset b/Content/Variant_SideScrolling/Blueprints/BP_SideScrollingGameMode.uasset new file mode 100644 index 0000000..c68e1b5 Binary files /dev/null and b/Content/Variant_SideScrolling/Blueprints/BP_SideScrollingGameMode.uasset differ diff --git a/Content/Variant_SideScrolling/Blueprints/BP_SideScrollingPlayerController.uasset b/Content/Variant_SideScrolling/Blueprints/BP_SideScrollingPlayerController.uasset new file mode 100644 index 0000000..23deb13 Binary files /dev/null and b/Content/Variant_SideScrolling/Blueprints/BP_SideScrollingPlayerController.uasset differ diff --git a/Content/Variant_SideScrolling/Blueprints/Items/BP_SideScrollingMovingPlatform.uasset b/Content/Variant_SideScrolling/Blueprints/Items/BP_SideScrollingMovingPlatform.uasset new file mode 100644 index 0000000..516bcc0 Binary files /dev/null and b/Content/Variant_SideScrolling/Blueprints/Items/BP_SideScrollingMovingPlatform.uasset differ diff --git a/Content/Variant_SideScrolling/Blueprints/Items/BP_SideScrollingPickup.uasset b/Content/Variant_SideScrolling/Blueprints/Items/BP_SideScrollingPickup.uasset new file mode 100644 index 0000000..d67ff7d Binary files /dev/null and b/Content/Variant_SideScrolling/Blueprints/Items/BP_SideScrollingPickup.uasset differ diff --git a/Content/Variant_SideScrolling/Blueprints/Items/BP_SideScrollingSoftPlatform.uasset b/Content/Variant_SideScrolling/Blueprints/Items/BP_SideScrollingSoftPlatform.uasset new file mode 100644 index 0000000..fc1fa1c Binary files /dev/null and b/Content/Variant_SideScrolling/Blueprints/Items/BP_SideScrollingSoftPlatform.uasset differ diff --git a/Content/Variant_SideScrolling/Input/Actions/IA_Drop.uasset b/Content/Variant_SideScrolling/Input/Actions/IA_Drop.uasset new file mode 100644 index 0000000..6d918bb Binary files /dev/null and b/Content/Variant_SideScrolling/Input/Actions/IA_Drop.uasset differ diff --git a/Content/Variant_SideScrolling/Input/Actions/IA_Interact.uasset b/Content/Variant_SideScrolling/Input/Actions/IA_Interact.uasset new file mode 100644 index 0000000..4d4760a Binary files /dev/null and b/Content/Variant_SideScrolling/Input/Actions/IA_Interact.uasset differ diff --git a/Content/Variant_SideScrolling/Input/Actions/IA_Mover.uasset b/Content/Variant_SideScrolling/Input/Actions/IA_Mover.uasset new file mode 100644 index 0000000..383f490 Binary files /dev/null and b/Content/Variant_SideScrolling/Input/Actions/IA_Mover.uasset differ diff --git a/Content/Variant_SideScrolling/Input/BPI_TouchInterface_SideScrolling.uasset b/Content/Variant_SideScrolling/Input/BPI_TouchInterface_SideScrolling.uasset new file mode 100644 index 0000000..219a795 Binary files /dev/null and b/Content/Variant_SideScrolling/Input/BPI_TouchInterface_SideScrolling.uasset differ diff --git a/Content/Variant_SideScrolling/Input/IMC_SideScroller.uasset b/Content/Variant_SideScrolling/Input/IMC_SideScroller.uasset new file mode 100644 index 0000000..069d802 Binary files /dev/null and b/Content/Variant_SideScrolling/Input/IMC_SideScroller.uasset differ diff --git a/Content/Variant_SideScrolling/Input/UI_TouchInterface_SideScrolling.uasset b/Content/Variant_SideScrolling/Input/UI_TouchInterface_SideScrolling.uasset new file mode 100644 index 0000000..5a3e3d0 Binary files /dev/null and b/Content/Variant_SideScrolling/Input/UI_TouchInterface_SideScrolling.uasset differ diff --git a/Content/Variant_SideScrolling/Lvl_SideScrolling.umap b/Content/Variant_SideScrolling/Lvl_SideScrolling.umap new file mode 100644 index 0000000..c411b58 Binary files /dev/null and b/Content/Variant_SideScrolling/Lvl_SideScrolling.umap differ diff --git a/Content/Variant_SideScrolling/UI/UI_SideScrolling.uasset b/Content/Variant_SideScrolling/UI/UI_SideScrolling.uasset new file mode 100644 index 0000000..93a290a Binary files /dev/null and b/Content/Variant_SideScrolling/UI/UI_SideScrolling.uasset differ diff --git a/KingshearthLegacy.slnx b/KingshearthLegacy.slnx new file mode 100644 index 0000000..9659a26 --- /dev/null +++ b/KingshearthLegacy.slnx @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/KingshearthLegacy.uproject b/KingshearthLegacy.uproject new file mode 100644 index 0000000..e45cb86 --- /dev/null +++ b/KingshearthLegacy.uproject @@ -0,0 +1,47 @@ +{ + "FileVersion": 3, + "EngineAssociation": "5.8", + "Category": "", + "Description": "", + "Modules": [ + { + "Name": "KingshearthLegacy", + "Type": "Runtime", + "LoadingPhase": "Default", + "AdditionalDependencies": [ + "Engine", + "AIModule", + "UMG" + ] + } + ], + "Plugins": [ + { + "Name": "ModelingToolsEditorMode", + "Enabled": true, + "TargetAllowList": [ + "Editor" + ] + }, + { + "Name": "StateTree", + "Enabled": true + }, + { + "Name": "GameplayStateTree", + "Enabled": true + }, + { + "Name": "ModelContextProtocol", + "Enabled": true + }, + { + "Name": "Terminal", + "Enabled": true + }, + { + "Name": "EditorToolset", + "Enabled": true + } + ] +} \ No newline at end of file diff --git a/Source/KingshearthLegacy.Target.cs b/Source/KingshearthLegacy.Target.cs new file mode 100644 index 0000000..e15969c --- /dev/null +++ b/Source/KingshearthLegacy.Target.cs @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class KingshearthLegacyTarget : TargetRules +{ + public KingshearthLegacyTarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Game; + DefaultBuildSettings = BuildSettingsVersion.V7; + IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_8; + ExtraModuleNames.Add("KingshearthLegacy"); + } +} diff --git a/Source/KingshearthLegacy/HealthComponent.cpp b/Source/KingshearthLegacy/HealthComponent.cpp new file mode 100644 index 0000000..dfbedda --- /dev/null +++ b/Source/KingshearthLegacy/HealthComponent.cpp @@ -0,0 +1,65 @@ +#include "HealthComponent.h" + +UHealthComponent::UHealthComponent() +{ + PrimaryComponentTick.bCanEverTick = false; +} + +void UHealthComponent::BeginPlay() +{ + Super::BeginPlay(); + + CurrentHealth = MaxHealth; +} + +float UHealthComponent::GetHealth() const +{ + return CurrentHealth; +} + +float UHealthComponent::GetMaxHealth() const +{ + return MaxHealth; +} + +void UHealthComponent::SetMaxHealth(float NewMaxHealth) +{ + MaxHealth = FMath::Max(NewMaxHealth, 0.0f); + + CurrentHealth = FMath::Clamp(CurrentHealth, 0.0f, MaxHealth); +} + +void UHealthComponent::SetHealth(float NewHealth) +{ + const float PreviousHealth = CurrentHealth; + + CurrentHealth = FMath::Clamp(NewHealth, 0.0f, MaxHealth); + + OnHealthChanged.Broadcast(CurrentHealth); + + // Only trigger death when crossing from alive to dead. + if (PreviousHealth > 0.0f && CurrentHealth <= 0.0f) + { + OnDeath.Broadcast(); + } +} + +void UHealthComponent::AddHealth(float Amount) +{ + if (Amount <= 0.0f) + { + return; + } + + SetHealth(CurrentHealth + Amount); +} + +void UHealthComponent::RemoveHealth(float Amount) +{ + if (Amount <= 0.0f) + { + return; + } + + SetHealth(CurrentHealth - Amount); +} \ No newline at end of file diff --git a/Source/KingshearthLegacy/HealthComponent.h b/Source/KingshearthLegacy/HealthComponent.h new file mode 100644 index 0000000..c904a30 --- /dev/null +++ b/Source/KingshearthLegacy/HealthComponent.h @@ -0,0 +1,54 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "HealthComponent.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChanged, float, NewHealth); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeath); + +UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) +class KINGSHEARTHLEGACY_API UHealthComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UHealthComponent(); + +protected: + virtual void BeginPlay() override; + +public: + // Health + UFUNCTION(BlueprintPure, Category = "Health") + float GetHealth() const; + + UFUNCTION(BlueprintPure, Category = "Health") + float GetMaxHealth() const; + + UFUNCTION(BlueprintCallable, Category = "Health") + void SetMaxHealth(float NewMaxHealth); + + UFUNCTION(BlueprintCallable, Category = "Health") + void SetHealth(float NewHealth); + + UFUNCTION(BlueprintCallable, Category = "Health") + void AddHealth(float Amount); + + UFUNCTION(BlueprintCallable, Category = "Health") + void RemoveHealth(float Amount); + +protected: + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Health") + float MaxHealth = 100.0f; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Health") + float CurrentHealth = 100.0f; + +public: + UPROPERTY(BlueprintAssignable, Category = "Health") + FOnHealthChanged OnHealthChanged; + + UPROPERTY(BlueprintAssignable, Category = "Health") + FOnDeath OnDeath; +}; \ No newline at end of file diff --git a/Source/KingshearthLegacy/KingshearthLegacy.Build.cs b/Source/KingshearthLegacy/KingshearthLegacy.Build.cs new file mode 100644 index 0000000..cb0abdb --- /dev/null +++ b/Source/KingshearthLegacy/KingshearthLegacy.Build.cs @@ -0,0 +1,51 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class KingshearthLegacy : ModuleRules +{ + public KingshearthLegacy(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new string[] { + "Core", + "CoreUObject", + "Engine", + "InputCore", + "EnhancedInput", + "AIModule", + "StateTreeModule", + "GameplayStateTreeModule", + "UMG", + "Slate" + }); + + PrivateDependencyModuleNames.AddRange(new string[] { }); + + PublicIncludePaths.AddRange(new string[] { + "KingshearthLegacy", + "KingshearthLegacy/Variant_Platforming", + "KingshearthLegacy/Variant_Platforming/Animation", + "KingshearthLegacy/Variant_Combat", + "KingshearthLegacy/Variant_Combat/AI", + "KingshearthLegacy/Variant_Combat/Animation", + "KingshearthLegacy/Variant_Combat/Gameplay", + "KingshearthLegacy/Variant_Combat/Interfaces", + "KingshearthLegacy/Variant_Combat/UI", + "KingshearthLegacy/Variant_SideScrolling", + "KingshearthLegacy/Variant_SideScrolling/AI", + "KingshearthLegacy/Variant_SideScrolling/Gameplay", + "KingshearthLegacy/Variant_SideScrolling/Interfaces", + "KingshearthLegacy/Variant_SideScrolling/UI" + }); + + // Uncomment if you are using Slate UI + // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); + + // Uncomment if you are using online features + // PrivateDependencyModuleNames.Add("OnlineSubsystem"); + + // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true + } +} diff --git a/Source/KingshearthLegacy/KingshearthLegacy.cpp b/Source/KingshearthLegacy/KingshearthLegacy.cpp new file mode 100644 index 0000000..a9f8e92 --- /dev/null +++ b/Source/KingshearthLegacy/KingshearthLegacy.cpp @@ -0,0 +1,8 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "KingshearthLegacy.h" +#include "Modules/ModuleManager.h" + +IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, KingshearthLegacy, "KingshearthLegacy" ); + +DEFINE_LOG_CATEGORY(LogKingshearthLegacy) \ No newline at end of file diff --git a/Source/KingshearthLegacy/KingshearthLegacy.h b/Source/KingshearthLegacy/KingshearthLegacy.h new file mode 100644 index 0000000..392c0fd --- /dev/null +++ b/Source/KingshearthLegacy/KingshearthLegacy.h @@ -0,0 +1,8 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +/** Main log category used across the project */ +DECLARE_LOG_CATEGORY_EXTERN(LogKingshearthLegacy, Log, All); \ No newline at end of file diff --git a/Source/KingshearthLegacy/KingshearthLegacyCharacter.cpp b/Source/KingshearthLegacy/KingshearthLegacyCharacter.cpp new file mode 100644 index 0000000..9519f1b --- /dev/null +++ b/Source/KingshearthLegacy/KingshearthLegacyCharacter.cpp @@ -0,0 +1,133 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "KingshearthLegacyCharacter.h" +#include "Engine/LocalPlayer.h" +#include "Camera/CameraComponent.h" +#include "Components/CapsuleComponent.h" +#include "GameFramework/CharacterMovementComponent.h" +#include "GameFramework/SpringArmComponent.h" +#include "GameFramework/Controller.h" +#include "EnhancedInputComponent.h" +#include "EnhancedInputSubsystems.h" +#include "InputActionValue.h" +#include "KingshearthLegacy.h" + +AKingshearthLegacyCharacter::AKingshearthLegacyCharacter() +{ + // Set size for collision capsule + GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f); + + // Don't rotate when the controller rotates. Let that just affect the camera. + bUseControllerRotationPitch = false; + bUseControllerRotationYaw = false; + bUseControllerRotationRoll = false; + + // Configure character movement + GetCharacterMovement()->bOrientRotationToMovement = true; + GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f); + + // Note: For faster iteration times these variables, and many more, can be tweaked in the Character Blueprint + // instead of recompiling to adjust them + GetCharacterMovement()->JumpZVelocity = 500.f; + GetCharacterMovement()->AirControl = 0.35f; + GetCharacterMovement()->MaxWalkSpeed = 500.f; + GetCharacterMovement()->MinAnalogWalkSpeed = 20.f; + GetCharacterMovement()->BrakingDecelerationWalking = 2000.f; + GetCharacterMovement()->BrakingDecelerationFalling = 1500.0f; + + // Create a camera boom (pulls in towards the player if there is a collision) + CameraBoom = CreateDefaultSubobject(TEXT("CameraBoom")); + CameraBoom->SetupAttachment(RootComponent); + CameraBoom->TargetArmLength = 400.0f; + CameraBoom->bUsePawnControlRotation = true; + + // Create a follow camera + FollowCamera = CreateDefaultSubobject(TEXT("FollowCamera")); + FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); + FollowCamera->bUsePawnControlRotation = false; + + // Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character) + // are set in the derived blueprint asset named ThirdPersonCharacter (to avoid direct content references in C++) +} + +void AKingshearthLegacyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) +{ + // Set up action bindings + if (UEnhancedInputComponent* EnhancedInputComponent = Cast(PlayerInputComponent)) { + + // Jumping + EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump); + EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping); + + // Moving + EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AKingshearthLegacyCharacter::Move); + EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this, &AKingshearthLegacyCharacter::Look); + + // Looking + EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AKingshearthLegacyCharacter::Look); + } + else + { + UE_LOG(LogKingshearthLegacy, Error, TEXT("'%s' Failed to find an Enhanced Input component! This template is built to use the Enhanced Input system. If you intend to use the legacy system, then you will need to update this C++ file."), *GetNameSafe(this)); + } +} + +void AKingshearthLegacyCharacter::Move(const FInputActionValue& Value) +{ + // input is a Vector2D + FVector2D MovementVector = Value.Get(); + + // route the input + DoMove(MovementVector.X, MovementVector.Y); +} + +void AKingshearthLegacyCharacter::Look(const FInputActionValue& Value) +{ + // input is a Vector2D + FVector2D LookAxisVector = Value.Get(); + + // route the input + DoLook(LookAxisVector.X, LookAxisVector.Y); +} + +void AKingshearthLegacyCharacter::DoMove(float Right, float Forward) +{ + if (GetController() != nullptr) + { + // find out which way is forward + const FRotator Rotation = GetController()->GetControlRotation(); + const FRotator YawRotation(0, Rotation.Yaw, 0); + + // get forward vector + const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X); + + // get right vector + const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y); + + // add movement + AddMovementInput(ForwardDirection, Forward); + AddMovementInput(RightDirection, Right); + } +} + +void AKingshearthLegacyCharacter::DoLook(float Yaw, float Pitch) +{ + if (GetController() != nullptr) + { + // add yaw and pitch input to controller + AddControllerYawInput(Yaw); + AddControllerPitchInput(Pitch); + } +} + +void AKingshearthLegacyCharacter::DoJumpStart() +{ + // signal the character to jump + Jump(); +} + +void AKingshearthLegacyCharacter::DoJumpEnd() +{ + // signal the character to stop jumping + StopJumping(); +} diff --git a/Source/KingshearthLegacy/KingshearthLegacyCharacter.h b/Source/KingshearthLegacy/KingshearthLegacyCharacter.h new file mode 100644 index 0000000..91cec80 --- /dev/null +++ b/Source/KingshearthLegacy/KingshearthLegacyCharacter.h @@ -0,0 +1,96 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Character.h" +#include "Logging/LogMacros.h" +#include "KingshearthLegacyCharacter.generated.h" + +class USpringArmComponent; +class UCameraComponent; +class UInputAction; +struct FInputActionValue; + +DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All); + +/** + * A simple player-controllable third person character + * Implements a controllable orbiting camera + */ +UCLASS(abstract) +class AKingshearthLegacyCharacter : public ACharacter +{ + GENERATED_BODY() + + /** Camera boom positioning the camera behind the character */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) + USpringArmComponent* CameraBoom; + + /** Follow camera */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) + UCameraComponent* FollowCamera; + +protected: + + /** Jump Input Action */ + UPROPERTY(EditAnywhere, Category="Input") + UInputAction* JumpAction; + + /** Move Input Action */ + UPROPERTY(EditAnywhere, Category="Input") + UInputAction* MoveAction; + + /** Look Input Action */ + UPROPERTY(EditAnywhere, Category="Input") + UInputAction* LookAction; + + /** Mouse Look Input Action */ + UPROPERTY(EditAnywhere, Category="Input") + UInputAction* MouseLookAction; + +public: + + /** Constructor */ + AKingshearthLegacyCharacter(); + +protected: + + /** Initialize input action bindings */ + virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; + +protected: + + /** Called for movement input */ + void Move(const FInputActionValue& Value); + + /** Called for looking input */ + void Look(const FInputActionValue& Value); + +public: + + /** Handles move inputs from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoMove(float Right, float Forward); + + /** Handles look inputs from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoLook(float Yaw, float Pitch); + + /** Handles jump pressed inputs from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoJumpStart(); + + /** Handles jump pressed inputs from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoJumpEnd(); + +public: + + /** Returns CameraBoom subobject **/ + FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; } + + /** Returns FollowCamera subobject **/ + FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; } +}; + diff --git a/Source/KingshearthLegacy/KingshearthLegacyGameMode.cpp b/Source/KingshearthLegacy/KingshearthLegacyGameMode.cpp new file mode 100644 index 0000000..017076b --- /dev/null +++ b/Source/KingshearthLegacy/KingshearthLegacyGameMode.cpp @@ -0,0 +1,8 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "KingshearthLegacyGameMode.h" + +AKingshearthLegacyGameMode::AKingshearthLegacyGameMode() +{ + // stub +} diff --git a/Source/KingshearthLegacy/KingshearthLegacyGameMode.h b/Source/KingshearthLegacy/KingshearthLegacyGameMode.h new file mode 100644 index 0000000..7014c27 --- /dev/null +++ b/Source/KingshearthLegacy/KingshearthLegacyGameMode.h @@ -0,0 +1,24 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/GameModeBase.h" +#include "KingshearthLegacyGameMode.generated.h" + +/** + * Simple GameMode for a third person game + */ +UCLASS(abstract) +class AKingshearthLegacyGameMode : public AGameModeBase +{ + GENERATED_BODY() + +public: + + /** Constructor */ + AKingshearthLegacyGameMode(); +}; + + + diff --git a/Source/KingshearthLegacy/KingshearthLegacyPlayerController.cpp b/Source/KingshearthLegacy/KingshearthLegacyPlayerController.cpp new file mode 100644 index 0000000..f53545f --- /dev/null +++ b/Source/KingshearthLegacy/KingshearthLegacyPlayerController.cpp @@ -0,0 +1,98 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "KingshearthLegacyPlayerController.h" +#include "EnhancedInputSubsystems.h" +#include "Engine/LocalPlayer.h" +#include "InputMappingContext.h" +#include "Blueprint/UserWidget.h" +#include "KingshearthLegacy.h" +#include "Widgets/Input/SVirtualJoystick.h" +#include "UI/PlayerHUDWidget.h" +#include "HealthComponent.h" + +void AKingshearthLegacyPlayerController::BeginPlay() +{ + Super::BeginPlay(); + + // only spawn touch controls on local player controllers + if (IsLocalPlayerController() && ShouldUseTouchControls()) + { + // spawn the mobile controls widget + MobileControlsWidget = CreateWidget(this, MobileControlsWidgetClass); + + if (MobileControlsWidget) + { + // add the controls to the player screen + MobileControlsWidget->AddToPlayerScreen(0); + + } else { + + UE_LOG(LogKingshearthLegacy, Error, TEXT("Could not spawn mobile controls widget.")); + + } + + } +} + +void AKingshearthLegacyPlayerController::SetupInputComponent() +{ + Super::SetupInputComponent(); + + // only add IMCs for local player controllers + if (IsLocalPlayerController()) + { + // Add Input Mapping Contexts + if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem(GetLocalPlayer())) + { + for (UInputMappingContext* CurrentContext : DefaultMappingContexts) + { + Subsystem->AddMappingContext(CurrentContext, 0); + } + + // only add these IMCs if we're not using mobile touch input + if (!ShouldUseTouchControls()) + { + for (UInputMappingContext* CurrentContext : MobileExcludedMappingContexts) + { + Subsystem->AddMappingContext(CurrentContext, 0); + } + } + } + } +} + +void AKingshearthLegacyPlayerController::OnPossess(APawn* InPawn) +{ + Super::OnPossess(InPawn); + + if (!IsLocalPlayerController() || !PlayerHUDClass || !InPawn) + { + return; + } + + if (!PlayerHUD) + { + PlayerHUD = CreateWidget(this, PlayerHUDClass); + + if (PlayerHUD) + { + PlayerHUD->AddToViewport(); + } + else + { + UE_LOG(LogKingshearthLegacy, Error, TEXT("Could not spawn player HUD widget.")); + } + } + + if (PlayerHUD) + { + PlayerHUD->InitializeHUD(InPawn->FindComponentByClass()); + } +} + +bool AKingshearthLegacyPlayerController::ShouldUseTouchControls() const +{ + // are we on a mobile platform? Should we force touch? + return SVirtualJoystick::ShouldDisplayTouchInterface() || bForceTouchControls; +} diff --git a/Source/KingshearthLegacy/KingshearthLegacyPlayerController.h b/Source/KingshearthLegacy/KingshearthLegacyPlayerController.h new file mode 100644 index 0000000..66311b5 --- /dev/null +++ b/Source/KingshearthLegacy/KingshearthLegacyPlayerController.h @@ -0,0 +1,64 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/PlayerController.h" +#include "KingshearthLegacyPlayerController.generated.h" + +class UInputMappingContext; +class UUserWidget; +class UPlayerHUDWidget; + +/** + * Basic PlayerController class for a third person game + * Manages input mappings + */ +UCLASS(abstract) +class AKingshearthLegacyPlayerController : public APlayerController +{ + GENERATED_BODY() + +protected: + + /** Input Mapping Contexts */ + UPROPERTY(EditAnywhere, Category ="Input|Input Mappings") + TArray DefaultMappingContexts; + + /** Input Mapping Contexts */ + UPROPERTY(EditAnywhere, Category="Input|Input Mappings") + TArray MobileExcludedMappingContexts; + + /** Mobile controls widget to spawn */ + UPROPERTY(EditAnywhere, Category="Input|Touch Controls") + TSubclassOf MobileControlsWidgetClass; + + /** Pointer to the mobile controls widget */ + UPROPERTY() + TObjectPtr MobileControlsWidget; + + /** If true, the player will use UMG touch controls even if not playing on mobile platforms */ + UPROPERTY(EditAnywhere, Config, Category = "Input|Touch Controls") + bool bForceTouchControls = false; + + /** Player HUD widget to spawn (health bar, stamina bar, ...). Left unset in variants that provide their own UI. */ + UPROPERTY(EditAnywhere, Category = "UI") + TSubclassOf PlayerHUDClass; + + /** Pointer to the spawned player HUD widget */ + UPROPERTY(BlueprintReadOnly, Category = "UI") + TObjectPtr PlayerHUD; + + /** Gameplay initialization */ + virtual void BeginPlay() override; + + /** Input mapping context setup */ + virtual void SetupInputComponent() override; + + /** Spawns/initializes the player HUD once a pawn (and its HealthComponent) is available */ + virtual void OnPossess(APawn* InPawn) override; + + /** Returns true if the player should use UMG touch controls */ + bool ShouldUseTouchControls() const; + +}; diff --git a/Source/KingshearthLegacy/UI/HealthBarWidget.cpp b/Source/KingshearthLegacy/UI/HealthBarWidget.cpp new file mode 100644 index 0000000..dee33a6 --- /dev/null +++ b/Source/KingshearthLegacy/UI/HealthBarWidget.cpp @@ -0,0 +1,42 @@ +#include "HealthBarWidget.h" +#include "HealthComponent.h" + +void UHealthBarWidget::InitializeHealthBar(UHealthComponent* InHealthComponent) +{ + if (!InHealthComponent) + { + return; + } + + if (HealthComponent) + { + HealthComponent->OnHealthChanged.RemoveDynamic(this, &UHealthBarWidget::HandleHealthChanged); + HealthComponent->OnDeath.RemoveDynamic(this, &UHealthBarWidget::HandleDeath); + } + + HealthComponent = InHealthComponent; + + HealthComponent->OnHealthChanged.AddDynamic(this, &UHealthBarWidget::HandleHealthChanged); + HealthComponent->OnDeath.AddDynamic(this, &UHealthBarWidget::HandleDeath); + + SetStatValue(HealthComponent->GetHealth(), HealthComponent->GetMaxHealth()); + SetMaxHealthDebuff(DefaultMaxHealthDebuffPercent); +} + +void UHealthBarWidget::SetMaxHealthDebuff(float InDebuffPercent) +{ + SetPenaltyPercent(InDebuffPercent); +} + +void UHealthBarWidget::HandleHealthChanged(float NewHealth) +{ + if (HealthComponent) + { + SetStatValue(NewHealth, HealthComponent->GetMaxHealth()); + } +} + +void UHealthBarWidget::HandleDeath() +{ + OnHealthDepleted(); +} diff --git a/Source/KingshearthLegacy/UI/HealthBarWidget.h b/Source/KingshearthLegacy/UI/HealthBarWidget.h new file mode 100644 index 0000000..2631c3c --- /dev/null +++ b/Source/KingshearthLegacy/UI/HealthBarWidget.h @@ -0,0 +1,43 @@ +#pragma once + +#include "CoreMinimal.h" +#include "UI/StatBarWidget.h" +#include "HealthBarWidget.generated.h" + +class UHealthComponent; + +/** + * Stat bar bound to a live UHealthComponent. Call InitializeHealthBar once the + * component is known (typically from the owning PlayerController on possession). + */ +UCLASS(abstract) +class UHealthBarWidget : public UStatBarWidget +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Category = "Health") + void InitializeHealthBar(UHealthComponent* InHealthComponent); + + /** Hook for a future debuff/status-effect system: 0-1 fraction of max health currently locked/unusable. */ + UFUNCTION(BlueprintCallable, Category = "Health") + void SetMaxHealthDebuff(float InDebuffPercent); + +protected: + UFUNCTION() + void HandleHealthChanged(float NewHealth); + + UFUNCTION() + void HandleDeath(); + + /** Hook for Blueprint-side "you died" bar visuals. Unused for now. */ + UFUNCTION(BlueprintImplementableEvent, Category = "Health") + void OnHealthDepleted(); + + UPROPERTY(BlueprintReadOnly, Category = "Health") + TObjectPtr HealthComponent; + + /** 0-1 starting max-health debuff applied in InitializeHealthBar, e.g. 0.1 = 10% of max health locked/unusable. Defaults to 0 since this bar reflects a live component. */ + UPROPERTY(EditDefaultsOnly, Category = "Health") + float DefaultMaxHealthDebuffPercent = 0.0f; +}; diff --git a/Source/KingshearthLegacy/UI/PlayerHUDWidget.cpp b/Source/KingshearthLegacy/UI/PlayerHUDWidget.cpp new file mode 100644 index 0000000..c73553b --- /dev/null +++ b/Source/KingshearthLegacy/UI/PlayerHUDWidget.cpp @@ -0,0 +1,13 @@ +#include "PlayerHUDWidget.h" +#include "UI/HealthBarWidget.h" +#include "UI/StaminaBarWidget.h" + +void UPlayerHUDWidget::InitializeHUD(UHealthComponent* InHealthComponent) +{ + if (HealthBar) + { + HealthBar->InitializeHealthBar(InHealthComponent); + } + + // StaminaBar self-initializes from hardcoded defaults in its own NativeConstruct. +} diff --git a/Source/KingshearthLegacy/UI/PlayerHUDWidget.h b/Source/KingshearthLegacy/UI/PlayerHUDWidget.h new file mode 100644 index 0000000..234c5ac --- /dev/null +++ b/Source/KingshearthLegacy/UI/PlayerHUDWidget.h @@ -0,0 +1,31 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "PlayerHUDWidget.generated.h" + +class UHealthBarWidget; +class UStaminaBarWidget; +class UHealthComponent; + +/** + * Top-level player HUD. Owns a health bar and a stamina bar and forwards + * initialization to them. Add further stat bars here (e.g. mana, posture) + * the same way: a BindWidgetOptional pointer + a line in InitializeHUD. + */ +UCLASS(abstract) +class UPlayerHUDWidget : public UUserWidget +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Category = "HUD") + void InitializeHUD(UHealthComponent* InHealthComponent); + +protected: + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true)) + TObjectPtr HealthBar; + + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true)) + TObjectPtr StaminaBar; +}; diff --git a/Source/KingshearthLegacy/UI/SegmentedBarWidget.cpp b/Source/KingshearthLegacy/UI/SegmentedBarWidget.cpp new file mode 100644 index 0000000..6632250 --- /dev/null +++ b/Source/KingshearthLegacy/UI/SegmentedBarWidget.cpp @@ -0,0 +1,157 @@ +#include "SegmentedBarWidget.h" +#include "Animation/WidgetAnimation.h" +#include "Blueprint/WidgetBlueprintGeneratedClass.h" +#include "Components/Border.h" +#include "Components/HorizontalBoxSlot.h" +#include "Components/TextBlock.h" + +void USegmentedBarWidget::NativePreConstruct() +{ + Super::NativePreConstruct(); + ApplyStaticAppearance(); + ApplyAnimatedFractions(DisplayedCurrentFraction, DisplayedPenaltyFraction); +} + +void USegmentedBarWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + Super::NativeTick(MyGeometry, InDeltaTime); + + const bool bCurrentConverged = FMath::IsNearlyEqual(DisplayedCurrentFraction, TargetCurrentFraction, KINDA_SMALL_NUMBER); + const bool bPenaltyConverged = FMath::IsNearlyEqual(DisplayedPenaltyFraction, TargetPenaltyFraction, KINDA_SMALL_NUMBER); + + if (bCurrentConverged && bPenaltyConverged) + { + return; + } + + DisplayedCurrentFraction = FMath::FInterpTo(DisplayedCurrentFraction, TargetCurrentFraction, InDeltaTime, InterpSpeed); + DisplayedPenaltyFraction = FMath::FInterpTo(DisplayedPenaltyFraction, TargetPenaltyFraction, InDeltaTime, InterpSpeed); + + ApplyAnimatedFractions(DisplayedCurrentFraction, DisplayedPenaltyFraction); +} + +void USegmentedBarWidget::SetSegments(float CurrentFraction, float PenaltyFraction) +{ + const float ClampedPenalty = FMath::Clamp(PenaltyFraction, 0.0f, 1.0f); + const float ClampedCurrent = FMath::Clamp(FMath::Min(CurrentFraction, 1.0f - ClampedPenalty), 0.0f, 1.0f); + + if (bHasReceivedFirstUpdate && ClampedCurrent < TargetCurrentFraction) + { + PlayFlash(); + } + + TargetCurrentFraction = ClampedCurrent; + TargetPenaltyFraction = ClampedPenalty; + + if (!bHasReceivedFirstUpdate) + { + bHasReceivedFirstUpdate = true; + DisplayedCurrentFraction = ClampedCurrent; + DisplayedPenaltyFraction = ClampedPenalty; + ApplyAnimatedFractions(DisplayedCurrentFraction, DisplayedPenaltyFraction); + } +} + +void USegmentedBarWidget::SetValueText(const FText& InText) +{ + if (ValueText) + { + ValueText->SetText(InText); + } +} + +void USegmentedBarWidget::SetColors(FLinearColor InCurrentColorLow, FLinearColor InCurrentColorHigh, FLinearColor InEmptyColor, FLinearColor InPenaltyColor) +{ + CurrentColorLow = InCurrentColorLow; + CurrentColor = InCurrentColorHigh; + EmptyColor = InEmptyColor; + PenaltyColor = InPenaltyColor; + ApplyStaticAppearance(); + ApplyAnimatedFractions(DisplayedCurrentFraction, DisplayedPenaltyFraction); +} + +void USegmentedBarWidget::PlayFlash() +{ + if (FlashAnimationName.IsNone()) + { + return; + } + + const UWidgetBlueprintGeneratedClass* GeneratedClass = Cast(GetClass()); + if (!GeneratedClass) + { + return; + } + + for (UWidgetAnimation* Animation : GeneratedClass->Animations) + { + if (Animation && Animation->GetFName() == FlashAnimationName) + { + PlayAnimation(Animation); + break; + } + } +} + +FLinearColor USegmentedBarWidget::GetGradientColor(float Fraction) const +{ + return FLinearColor::LerpUsingHSV(CurrentColorLow, CurrentColor, FMath::Clamp(Fraction, 0.0f, 1.0f)); +} + +void USegmentedBarWidget::ApplyStaticAppearance() +{ + ApplySegmentAppearance(EmptyFill, EmptyMaterial, EmptyColor); + ApplySegmentAppearance(PenaltyFill, PenaltyMaterial, PenaltyColor); + + if (CurrentFill && CurrentMaterial) + { + CurrentFill->SetBrushFromMaterial(CurrentMaterial); + } +} + +void USegmentedBarWidget::ApplyAnimatedFractions(float DisplayCurrentFraction, float DisplayPenaltyFraction) +{ + const float EmptyFraction = FMath::Max(0.0f, 1.0f - DisplayCurrentFraction - DisplayPenaltyFraction); + + SetSegmentFraction(CurrentFill, DisplayCurrentFraction); + SetSegmentFraction(EmptyFill, EmptyFraction); + SetSegmentFraction(PenaltyFill, DisplayPenaltyFraction); + + if (CurrentFill) + { + CurrentFill->SetBrushColor(GetGradientColor(DisplayCurrentFraction)); + } +} + +void USegmentedBarWidget::ApplySegmentAppearance(UBorder* Segment, UMaterialInterface* Material, FLinearColor Color) +{ + if (!Segment) + { + return; + } + + if (Material) + { + Segment->SetBrushFromMaterial(Material); + } + + Segment->SetBrushColor(Color); +} + +void USegmentedBarWidget::SetSegmentFraction(UBorder* Segment, float Fraction) +{ + if (!Segment) + { + return; + } + + if (UHorizontalBoxSlot* HBoxSlot = Cast(Segment->Slot)) + { + FSlateChildSize NewSize; + NewSize.SizeRule = ESlateSizeRule::Fill; + NewSize.Value = FMath::Max(Fraction, 0.0f); + HBoxSlot->SetSize(NewSize); + } + + Segment->SetVisibility(Fraction > 0.0f ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Collapsed); +} diff --git a/Source/KingshearthLegacy/UI/SegmentedBarWidget.h b/Source/KingshearthLegacy/UI/SegmentedBarWidget.h new file mode 100644 index 0000000..086d932 --- /dev/null +++ b/Source/KingshearthLegacy/UI/SegmentedBarWidget.h @@ -0,0 +1,116 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "SegmentedBarWidget.generated.h" + +class UBorder; +class UMaterialInterface; +class UTextBlock; + +/** + * Reusable 3-segment bar (current / empty / penalty) built once as WBP_SegmentedBar + * and shared by every UStatBarWidget subclass (health, stamina, ...), so the + * Horizontal Box + Border visual tree doesn't need to be duplicated per stat bar. + * + * All appearance properties (colors, materials, interpolation speed, flash animation + * name) are EditAnywhere, so each place a WBP_SegmentedBar instance is dropped into a + * parent widget (e.g. WBP_HealthBar, WBP_StaminaBar) exposes its own settings in that + * parent's Designer, without needing a separate Blueprint or C++ change per bar. + * + * The current-value segment's color is a gradient between CurrentColorLow (at 0%) and + * CurrentColor (at 100%), e.g. blue at full down to a "velvet" tone near empty. Segment + * sizes and this gradient both animate smoothly toward their target via NativeTick + * (InterpSpeed controls how fast) rather than snapping instantly. A drop in the current + * value also triggers FlashAnimationName (a UMG Widget Animation built in this asset's + * own Animations panel) via PlayAnimationByName, if one by that name exists - so a hit + * flash needs to be authored once, here, not per stat bar. + */ +UCLASS() +class USegmentedBarWidget : public UUserWidget +{ + GENERATED_BODY() + +public: + /** CurrentFraction/PenaltyFraction are both 0-1 fractions of the bar's total width. Penalty is anchored to the right edge. Sets the animation target - does not apply instantly. */ + UFUNCTION(BlueprintCallable, Category = "Segmented Bar") + void SetSegments(float CurrentFraction, float PenaltyFraction); + + UFUNCTION(BlueprintCallable, Category = "Segmented Bar") + void SetValueText(const FText& InText); + + /** Overrides EmptyColor/PenaltyColor at runtime and re-applies them immediately. */ + UFUNCTION(BlueprintCallable, Category = "Appearance") + void SetColors(FLinearColor InCurrentColorLow, FLinearColor InCurrentColorHigh, FLinearColor InEmptyColor, FLinearColor InPenaltyColor); + + /** Plays FlashAnimationName immediately, if a Widget Animation by that name exists on this asset. */ + UFUNCTION(BlueprintCallable, Category = "Appearance") + void PlayFlash(); + + /** Current-value segment color at 0% (e.g. a "velvet" low-value tone). */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Appearance") + FLinearColor CurrentColorLow = FLinearColor(0.35f, 0.02f, 0.16f, 1.0f); + + /** Current-value segment color at 100% (e.g. blue). */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Appearance") + FLinearColor CurrentColor = FLinearColor(0.0f, 0.53f, 1.0f, 1.0f); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Appearance") + FLinearColor EmptyColor = FLinearColor(0.05f, 0.05f, 0.05f, 1.0f); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Appearance") + FLinearColor PenaltyColor = FLinearColor(0.8f, 0.1f, 0.1f, 1.0f); + + /** Optional per-instance material override for the current-value segment. Must be a "User Interface" domain material. Falls back to a flat color brush when unset. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Appearance") + TObjectPtr CurrentMaterial; + + /** Optional per-instance material override for the empty segment. Must be a "User Interface" domain material. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Appearance") + TObjectPtr EmptyMaterial; + + /** Optional per-instance material override for the penalty segment (e.g. a hatched pattern). Must be a "User Interface" domain material. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Appearance") + TObjectPtr PenaltyMaterial; + + /** How fast the bar's width and gradient color glide toward a new value. Higher = faster. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation") + float InterpSpeed = 8.0f; + + /** Name of a Widget Animation (built in this asset's Animations panel) to play via PlayAnimationByName whenever the current value drops. Leave as None to disable. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation") + FName FlashAnimationName = TEXT("FlashAnim"); + +protected: + virtual void NativePreConstruct() override; + virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + + /** Bound to a Border named "CurrentFill" inside a Horizontal Box, if present. Sized via its HorizontalBoxSlot. */ + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true)) + TObjectPtr CurrentFill; + + /** Bound to a Border named "EmptyFill" inside the same Horizontal Box, if present. */ + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true)) + TObjectPtr EmptyFill; + + /** Bound to a Border named "PenaltyFill" inside the same Horizontal Box, if present. Anchored to the right edge by being the last child. */ + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true)) + TObjectPtr PenaltyFill; + + /** Bound to a TextBlock named "ValueText" in the widget's designer tree, if present. */ + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true)) + TObjectPtr ValueText; + +private: + void ApplyStaticAppearance(); + void ApplyAnimatedFractions(float DisplayCurrentFraction, float DisplayPenaltyFraction); + FLinearColor GetGradientColor(float Fraction) const; + static void ApplySegmentAppearance(UBorder* Segment, UMaterialInterface* Material, FLinearColor Color); + static void SetSegmentFraction(UBorder* Segment, float Fraction); + + float TargetCurrentFraction = 1.0f; + float TargetPenaltyFraction = 0.0f; + float DisplayedCurrentFraction = 1.0f; + float DisplayedPenaltyFraction = 0.0f; + bool bHasReceivedFirstUpdate = false; +}; diff --git a/Source/KingshearthLegacy/UI/StaminaBarWidget.cpp b/Source/KingshearthLegacy/UI/StaminaBarWidget.cpp new file mode 100644 index 0000000..ace3dac --- /dev/null +++ b/Source/KingshearthLegacy/UI/StaminaBarWidget.cpp @@ -0,0 +1,16 @@ +#include "StaminaBarWidget.h" + +void UStaminaBarWidget::NativeConstruct() +{ + Super::NativeConstruct(); + + SetStatValue(DefaultCurrentStamina, DefaultMaxStamina); + SetFatigue(DefaultFatigue); +} + +void UStaminaBarWidget::SetFatigue(float NewFatigue) +{ + Fatigue = FMath::Clamp(NewFatigue, 0.0f, 1.0f); + SetPenaltyPercent(Fatigue); + OnFatigueChanged(Fatigue); +} diff --git a/Source/KingshearthLegacy/UI/StaminaBarWidget.h b/Source/KingshearthLegacy/UI/StaminaBarWidget.h new file mode 100644 index 0000000..aa8bc09 --- /dev/null +++ b/Source/KingshearthLegacy/UI/StaminaBarWidget.h @@ -0,0 +1,51 @@ +#pragma once + +#include "CoreMinimal.h" +#include "UI/StatBarWidget.h" +#include "StaminaBarWidget.generated.h" + +/** + * Stat bar for stamina. Currently self-initializes from hardcoded defaults, + * since there is no stamina/fatigue gameplay component (DataModel) yet. + * + * TODO: once a UStaminaComponent exists (mirroring UHealthComponent), replace + * the hardcoded NativeConstruct initialization with an InitializeStaminaBar( + * UStaminaComponent* InStaminaComponent) call bound to that component's own + * OnStaminaChanged/OnFatigueChanged delegates, following the exact pattern + * used by UHealthBarWidget::InitializeHealthBar. SetFatigue/OnFatigueChanged + * below are already shaped for that hookup. + */ +UCLASS(abstract) +class UStaminaBarWidget : public UStatBarWidget +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Category = "Fatigue") + void SetFatigue(float NewFatigue); + + UFUNCTION(BlueprintPure, Category = "Fatigue") + float GetFatigue() const { return Fatigue; } + +protected: + virtual void NativeConstruct() override; + + /** Hook for Blueprint-side fatigue visuals (e.g. a darkened "locked" portion of the bar). Unused until a real fatigue value is fed in. */ + UFUNCTION(BlueprintImplementableEvent, Category = "Fatigue") + void OnFatigueChanged(float NewFatigue); + + /** Placeholder starting values until a real UStaminaComponent DataModel exists. */ + UPROPERTY(EditDefaultsOnly, Category = "Stamina") + float DefaultCurrentStamina = 75.0f; + + UPROPERTY(EditDefaultsOnly, Category = "Stamina") + float DefaultMaxStamina = 100.0f; + + /** 0-1 starting fatigue applied in NativeConstruct, e.g. 0.1 = 10% of the bar locked/unusable. */ + UPROPERTY(EditDefaultsOnly, Category = "Stamina") + float DefaultFatigue = 0.1f; + + /** 0-1 exhaustion amount. Drives the bar's penalty segment via SetPenaltyPercent. */ + UPROPERTY(BlueprintReadOnly, Category = "Fatigue") + float Fatigue = 0.0f; +}; diff --git a/Source/KingshearthLegacy/UI/StatBarWidget.cpp b/Source/KingshearthLegacy/UI/StatBarWidget.cpp new file mode 100644 index 0000000..b8355d2 --- /dev/null +++ b/Source/KingshearthLegacy/UI/StatBarWidget.cpp @@ -0,0 +1,36 @@ +#include "StatBarWidget.h" +#include "UI/SegmentedBarWidget.h" + +void UStatBarWidget::SetStatValue(float InCurrent, float InMax) +{ + MaxValue = FMath::Max(InMax, 0.0f); + CurrentValue = FMath::Clamp(InCurrent, 0.0f, MaxValue); + + UpdateBar(); + + OnStatValueChanged(GetPercent()); +} + +void UStatBarWidget::SetPenaltyPercent(float InPenaltyPercent) +{ + PenaltyPercent = FMath::Clamp(InPenaltyPercent, 0.0f, 1.0f); + UpdateBar(); +} + +float UStatBarWidget::GetPercent() const +{ + return MaxValue > 0.0f ? CurrentValue / MaxValue : 0.0f; +} + +void UStatBarWidget::UpdateBar() +{ + if (!Bar) + { + return; + } + + Bar->SetSegments(GetPercent(), PenaltyPercent); + Bar->SetValueText(FText::Format(NSLOCTEXT("StatBarWidget", "ValueFormat", "{0} / {1}"), + FText::AsNumber(FMath::RoundToInt(CurrentValue)), + FText::AsNumber(FMath::RoundToInt(MaxValue)))); +} diff --git a/Source/KingshearthLegacy/UI/StatBarWidget.h b/Source/KingshearthLegacy/UI/StatBarWidget.h new file mode 100644 index 0000000..5998a07 --- /dev/null +++ b/Source/KingshearthLegacy/UI/StatBarWidget.h @@ -0,0 +1,61 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "StatBarWidget.generated.h" + +class USegmentedBarWidget; + +/** + * Generic "current / max" bar widget (health, stamina, mana, posture, ...), + * with a third "penalty" value (fatigue, a max-value debuff, ...) that eats + * into the bar from the right edge. Subclasses feed CurrentValue/MaxValue in + * via SetStatValue and PenaltyPercent via SetPenaltyPercent, either from a + * live gameplay component or from hardcoded defaults. + * + * The actual 3-segment visual is delegated to a single bound USegmentedBarWidget + * (WBP_SegmentedBar) so that visual tree is only built once, not duplicated + * across every stat bar. + */ +UCLASS(abstract) +class UStatBarWidget : public UUserWidget +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Category = "Stat Bar") + void SetStatValue(float InCurrent, float InMax); + + UFUNCTION(BlueprintCallable, Category = "Stat Bar") + void SetPenaltyPercent(float InPenaltyPercent); + + UFUNCTION(BlueprintPure, Category = "Stat Bar") + float GetPercent() const; + + UFUNCTION(BlueprintPure, Category = "Stat Bar") + float GetCurrentValue() const { return CurrentValue; } + + UFUNCTION(BlueprintPure, Category = "Stat Bar") + float GetMaxValue() const { return MaxValue; } + + UFUNCTION(BlueprintPure, Category = "Stat Bar") + float GetPenaltyPercent() const { return PenaltyPercent; } + +protected: + /** Bound to a widget named "Bar" (a WBP_SegmentedBar instance) in the designer tree, if present. */ + UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true)) + TObjectPtr Bar; + + /** Called after CurrentValue/MaxValue/PenaltyPercent and Bar are updated. Hook for Blueprint-side visual polish. */ + UFUNCTION(BlueprintImplementableEvent, Category = "Stat Bar") + void OnStatValueChanged(float Percent); + + float CurrentValue = 0.0f; + float MaxValue = 1.0f; + + /** 0-1 fraction of Max currently locked/unusable (fatigue, a max-value debuff, ...). */ + float PenaltyPercent = 0.0f; + +private: + void UpdateBar(); +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/AI/CombatAIController.cpp b/Source/KingshearthLegacy/Variant_Combat/AI/CombatAIController.cpp new file mode 100644 index 0000000..16d3af2 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/AI/CombatAIController.cpp @@ -0,0 +1,28 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "CombatAIController.h" +#include "Components/StateTreeAIComponent.h" + +ACombatAIController::ACombatAIController() +{ + // create the StateTree AI Component + StateTreeAI = CreateDefaultSubobject(TEXT("StateTreeAI")); + check(StateTreeAI); + + // ensure we start the StateTree when we possess the pawn + bStartAILogicOnPossess = false; + StateTreeAI->SetStartLogicAutomatically(false); + + // ensure we're attached to the possessed character. + // this is necessary for EnvQueries to work correctly + bAttachToPawn = true; +} + +void ACombatAIController::OnPossess(APawn* InPawn) +{ + Super::OnPossess(InPawn); + + // start AI logic + StateTreeAI->StartLogic(); +} diff --git a/Source/KingshearthLegacy/Variant_Combat/AI/CombatAIController.h b/Source/KingshearthLegacy/Variant_Combat/AI/CombatAIController.h new file mode 100644 index 0000000..9daa014 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/AI/CombatAIController.h @@ -0,0 +1,32 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AIController.h" +#include "CombatAIController.generated.h" + +class UStateTreeAIComponent; + +/** + * A basic AI Controller capable of running StateTree + */ +UCLASS(abstract) +class ACombatAIController : public AAIController +{ + GENERATED_BODY() + + /** StateTree Component */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true")) + UStateTreeAIComponent* StateTreeAI; + +public: + + /** Constructor */ + ACombatAIController(); + +protected: + + /** Pawn Initialization */ + virtual void OnPossess(APawn* InPawn) override; +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/AI/CombatEnemy.cpp b/Source/KingshearthLegacy/Variant_Combat/AI/CombatEnemy.cpp new file mode 100644 index 0000000..393957f --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/AI/CombatEnemy.cpp @@ -0,0 +1,343 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "CombatEnemy.h" +#include "Components/CapsuleComponent.h" +#include "GameFramework/CharacterMovementComponent.h" +#include "CombatAIController.h" +#include "Components/WidgetComponent.h" +#include "Engine/DamageEvents.h" +#include "CombatLifeBar.h" +#include "TimerManager.h" +#include "Components/SkeletalMeshComponent.h" +#include "Animation/AnimInstance.h" + +ACombatEnemy::ACombatEnemy() +{ + PrimaryActorTick.bCanEverTick = true; + + // bind the attack montage ended delegate + OnAttackMontageEnded.BindUObject(this, &ACombatEnemy::AttackMontageEnded); + + // set the AI Controller class by default + AIControllerClass = ACombatAIController::StaticClass(); + + // use an AI Controller regardless of whether we're placed or spawned + AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned; + + // ignore the controller's yaw rotation + bUseControllerRotationYaw = false; + + // create the life bar + LifeBar = CreateDefaultSubobject(TEXT("LifeBar")); + LifeBar->SetupAttachment(RootComponent); + + // set the collision capsule size + GetCapsuleComponent()->SetCapsuleSize(35.0f, 90.0f); + + // set the character movement properties + GetCharacterMovement()->bUseControllerDesiredRotation = true; + + // reset HP to maximum + CurrentHP = MaxHP; +} + +void ACombatEnemy::DoAIComboAttack() +{ + // ignore if we're already playing an attack animation + if (bIsAttacking) + { + return; + } + + // raise the attacking flag + bIsAttacking = true; + + // choose how many times we're going to attack + TargetComboCount = FMath::RandRange(1, ComboSectionNames.Num() - 1); + + // reset the attack counter + CurrentComboAttack = 0; + + // play the attack montage + if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance()) + { + const float MontageLength = AnimInstance->Montage_Play(ComboAttackMontage, 1.0f, EMontagePlayReturnType::MontageLength, 0.0f, true); + + // subscribe to montage completed and interrupted events + if (MontageLength > 0.0f) + { + // set the end delegate for the montage + AnimInstance->Montage_SetEndDelegate(OnAttackMontageEnded, ComboAttackMontage); + } + } +} + +void ACombatEnemy::DoAIChargedAttack() +{ + // ignore if we're already playing an attack animation + if (bIsAttacking) + { + return; + } + + // raise the attacking flag + bIsAttacking = true; + + // choose how many loops are we going to charge for + TargetChargeLoops = FMath::RandRange(MinChargeLoops, MaxChargeLoops); + + // reset the charge loop counter + CurrentChargeLoop = 0; + + // play the attack montage + if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance()) + { + const float MontageLength = AnimInstance->Montage_Play(ChargedAttackMontage, 1.0f, EMontagePlayReturnType::MontageLength, 0.0f, true); + + // subscribe to montage completed and interrupted events + if (MontageLength > 0.0f) + { + // set the end delegate for the montage + AnimInstance->Montage_SetEndDelegate(OnAttackMontageEnded, ChargedAttackMontage); + } + } +} + +void ACombatEnemy::AttackMontageEnded(UAnimMontage* Montage, bool bInterrupted) +{ + // reset the attacking flag + bIsAttacking = false; + + // call the attack completed delegate so the StateTree can continue execution + OnAttackCompleted.ExecuteIfBound(); +} + +const FVector& ACombatEnemy::GetLastDangerLocation() const +{ + return LastDangerLocation; +} + +float ACombatEnemy::GetLastDangerTime() const +{ + return LastDangerTime; +} + +void ACombatEnemy::DoAttackTrace(FName DamageSourceBone) +{ + // sweep for objects in front of the character to be hit by the attack + TArray OutHits; + + // start at the provided socket location, sweep forward + const FVector TraceStart = GetMesh()->GetSocketLocation(DamageSourceBone); + const FVector TraceEnd = TraceStart + (GetActorForwardVector() * MeleeTraceDistance); + + // enemies only affect Pawn collision objects; they don't knock back boxes + FCollisionObjectQueryParams ObjectParams; + ObjectParams.AddObjectTypesToQuery(ECC_Pawn); + + // use a sphere shape for the sweep + FCollisionShape CollisionShape; + CollisionShape.SetSphere(MeleeTraceRadius); + + // ignore self + FCollisionQueryParams QueryParams; + QueryParams.AddIgnoredActor(this); + + if (GetWorld()->SweepMultiByObjectType(OutHits, TraceStart, TraceEnd, FQuat::Identity, ObjectParams, CollisionShape, QueryParams)) + { + // iterate over each object hit + for (const FHitResult& CurrentHit : OutHits) + { + /** does the actor have the player tag? */ + if (CurrentHit.GetActor()->ActorHasTag(FName("Player"))) + { + // check if the actor is damageable + ICombatDamageable* Damageable = Cast(CurrentHit.GetActor()); + + if (Damageable) + { + // knock upwards and away from the impact normal + const FVector Impulse = (CurrentHit.ImpactNormal * -MeleeKnockbackImpulse) + (FVector::UpVector * MeleeLaunchImpulse); + + // pass the damage event to the actor + Damageable->ApplyDamage(MeleeDamage, this, CurrentHit.ImpactPoint, Impulse); + + } + } + } + } +} + +void ACombatEnemy::CheckCombo() +{ + // increase the combo counter + ++CurrentComboAttack; + + // do we still have attacks to play in this string? + if (CurrentComboAttack < TargetComboCount) + { + // jump to the next attack section + if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance()) + { + AnimInstance->Montage_JumpToSection(ComboSectionNames[CurrentComboAttack], ComboAttackMontage); + } + } +} + +void ACombatEnemy::CheckChargedAttack() +{ + // increase the charge loop counter + ++CurrentChargeLoop; + + // jump to either the loop or attack section of the montage depending on whether we hit the loop target + if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance()) + { + AnimInstance->Montage_JumpToSection(CurrentChargeLoop >= TargetChargeLoops ? ChargeAttackSection : ChargeLoopSection, ChargedAttackMontage); + } +} + +void ACombatEnemy::ApplyDamage(float Damage, AActor* DamageCauser, const FVector& DamageLocation, const FVector& DamageImpulse) +{ + + // pass the damage event to the actor + FDamageEvent DamageEvent; + const float ActualDamage = TakeDamage(Damage, DamageEvent, nullptr, DamageCauser); + + // only process knockback and effects if we received nonzero damage + if (ActualDamage > 0.0f) + { + // apply the knockback impulse + GetCharacterMovement()->AddImpulse(DamageImpulse, true); + + // is the character ragdolling? + if (GetMesh()->IsSimulatingPhysics()) + { + // apply an impulse to the ragdoll + GetMesh()->AddImpulseAtLocation(DamageImpulse * GetMesh()->GetMass(), DamageLocation); + } + + // stop the attack montages to interrupt the attack + if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance()) + { + AnimInstance->Montage_Stop(0.1f, ComboAttackMontage); + AnimInstance->Montage_Stop(0.1f, ChargedAttackMontage); + } + + // pass control to BP to play effects, etc. + ReceivedDamage(ActualDamage, DamageLocation, DamageImpulse.GetSafeNormal()); + } +} + +void ACombatEnemy::HandleDeath() +{ + // hide the life bar + LifeBar->SetHiddenInGame(true); + + // disable the collision capsule to avoid being hit again while dead + GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision); + + // disable character movement + GetCharacterMovement()->DisableMovement(); + + // enable full ragdoll physics + GetMesh()->SetSimulatePhysics(true); + + // call the died delegate to notify any subscribers + OnEnemyDied.Broadcast(); + + // set up the death timer + GetWorld()->GetTimerManager().SetTimer(DeathTimer, this, &ACombatEnemy::RemoveFromLevel, DeathRemovalTime); +} + +void ACombatEnemy::ApplyHealing(float Healing, AActor* Healer) +{ + // stub +} + +void ACombatEnemy::NotifyDanger(const FVector& DangerLocation, AActor* DangerSource) +{ + // ensure we're being attacked by the player + if (DangerSource && DangerSource->ActorHasTag(FName("Player"))) + { + // save the danger location and game time + LastDangerLocation = DangerLocation; + LastDangerTime = GetWorld()->GetTimeSeconds(); + } +} + +void ACombatEnemy::RemoveFromLevel() +{ + // destroy this actor + Destroy(); +} + +float ACombatEnemy::TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) +{ + // only process damage if the character is still alive + if (CurrentHP <= 0.0f) + { + return 0.0f; + } + + // reduce the current HP + CurrentHP -= Damage; + + // have we run out of HP? + if (CurrentHP <= 0.0f) + { + // die + HandleDeath(); + } + else + { + // update the life bar + LifeBarWidget->SetLifePercentage(CurrentHP / MaxHP); + + // enable partial ragdoll physics, but keep the pelvis vertical + GetMesh()->SetPhysicsBlendWeight(0.5f); + GetMesh()->SetBodySimulatePhysics(PelvisBoneName, false); + } + + // return the received damage amount + return Damage; +} + +void ACombatEnemy::Landed(const FHitResult& Hit) +{ + Super::Landed(Hit); + + // is the character still alive? + if (CurrentHP >= 0.0f) + { + // disable ragdoll physics + GetMesh()->SetPhysicsBlendWeight(0.0f); + } + + // call the landed Delegate for StateTree + OnEnemyLanded.ExecuteIfBound(); +} + +void ACombatEnemy::BeginPlay() +{ + // reset HP to maximum + CurrentHP = MaxHP; + + // we top the HP before BeginPlay so StateTree picks it up at the right value + Super::BeginPlay(); + + // get the life bar widget from the widget comp + LifeBarWidget = Cast(LifeBar->GetUserWidgetObject()); + check(LifeBarWidget); + + // fill the life bar + LifeBarWidget->SetLifePercentage(1.0f); +} + +void ACombatEnemy::EndPlay(EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + // clear the death timer + GetWorld()->GetTimerManager().ClearTimer(DeathTimer); +} diff --git a/Source/KingshearthLegacy/Variant_Combat/AI/CombatEnemy.h b/Source/KingshearthLegacy/Variant_Combat/AI/CombatEnemy.h new file mode 100644 index 0000000..1a4a99d --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/AI/CombatEnemy.h @@ -0,0 +1,232 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Character.h" +#include "CombatAttacker.h" +#include "CombatDamageable.h" +#include "Animation/AnimMontage.h" +#include "Engine/TimerHandle.h" +#include "CombatEnemy.generated.h" + +class UWidgetComponent; +class UCombatLifeBar; +class UAnimMontage; + +/** Completed attack animation delegate for StateTree */ +DECLARE_DELEGATE(FOnEnemyAttackCompleted); + +/** Landed delegate for StateTree */ +DECLARE_DELEGATE(FOnEnemyLanded); + +/** Enemy died delegate */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnEnemyDied); + +/** + * An AI-controlled character with combat capabilities. + * Its bundled AI Controller runs logic through StateTree + */ +UCLASS(abstract) +class ACombatEnemy : public ACharacter, public ICombatAttacker, public ICombatDamageable +{ + GENERATED_BODY() + + /** Life bar widget component */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) + UWidgetComponent* LifeBar; + +public: + + /** Constructor */ + ACombatEnemy(); + +protected: + + /** Max amount of HP the character will have on respawn */ + UPROPERTY(EditAnywhere, Category="Damage") + float MaxHP = 3.0f; + +public: + + /** Current amount of HP the character has */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Damage", meta = (ClampMin = 0, ClampMax = 100)) + float CurrentHP = 0.0f; + +protected: + + /** Name of the pelvis bone, for damage ragdoll physics */ + UPROPERTY(EditAnywhere, Category="Damage") + FName PelvisBoneName; + + /** Pointer to the life bar widget */ + UPROPERTY(EditAnywhere, Category="Damage") + UCombatLifeBar* LifeBarWidget; + + /** If true, the character is currently playing an attack animation */ + bool bIsAttacking = false; + + /** Distance ahead of the character that melee attack sphere collision traces will extend */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Trace", meta = (ClampMin = 0, ClampMax = 500, Units = "cm")) + float MeleeTraceDistance = 75.0f; + + /** Radius of the sphere trace for melee attacks */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Trace", meta = (ClampMin = 0, ClampMax = 500, Units = "cm")) + float MeleeTraceRadius = 50.0f; + + /** Amount of damage a melee attack will deal */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Damage", meta = (ClampMin = 0, ClampMax = 100)) + float MeleeDamage = 1.0f; + + /** Amount of knockback impulse a melee attack will apply */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Damage", meta = (ClampMin = 0, ClampMax = 1000, Units = "cm/s")) + float MeleeKnockbackImpulse = 150.0f; + + /** Amount of upwards impulse a melee attack will apply */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Damage", meta = (ClampMin = 0, ClampMax = 1000, Units = "cm/s")) + float MeleeLaunchImpulse = 350.0f; + + /** AnimMontage that will play for combo attacks */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Combo") + UAnimMontage* ComboAttackMontage; + + /** Names of the AnimMontage sections that correspond to each stage of the combo attack */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Combo") + TArray ComboSectionNames; + + /** Target number of attacks in the combo attack string we're playing */ + int32 TargetComboCount = 0; + + /** Index of the current stage of the melee attack combo */ + int32 CurrentComboAttack = 0; + + /** AnimMontage that will play for charged attacks */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Charged") + UAnimMontage* ChargedAttackMontage; + + /** Name of the AnimMontage section that corresponds to the charge loop */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Charged") + FName ChargeLoopSection; + + /** Name of the AnimMontage section that corresponds to the attack */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Charged") + FName ChargeAttackSection; + + /** Minimum number of charge animation loops that will be played by the AI */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Charged", meta = (ClampMin = 1, ClampMax = 20)) + int32 MinChargeLoops = 2; + + /** Maximum number of charge animation loops that will be played by the AI */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Charged", meta = (ClampMin = 1, ClampMax = 20)) + int32 MaxChargeLoops = 5; + + /** Target number of charge animation loops to play in this charged attack */ + int32 TargetChargeLoops = 0; + + /** Number of charge animation loop currently playing */ + int32 CurrentChargeLoop = 0; + + /** Time to wait before removing this character from the level after it dies */ + UPROPERTY(EditAnywhere, Category="Death") + float DeathRemovalTime = 5.0f; + + /** Enemy death timer */ + FTimerHandle DeathTimer; + + /** Attack montage ended delegate */ + FOnMontageEnded OnAttackMontageEnded; + + /** Last recorded location we're being attacked from */ + FVector LastDangerLocation = FVector::ZeroVector; + + /** Last recorded game time we were attacked */ + float LastDangerTime = -1000.0f; + +public: + /** Attack completed internal delegate to notify StateTree tasks */ + FOnEnemyAttackCompleted OnAttackCompleted; + + /** Landed internal delegate to notify StateTree tasks. We use this instead of the built-in Landed delegate so we can bind to a Lambda in StateTree tasks */ + FOnEnemyLanded OnEnemyLanded; + + /** Enemy died delegate. Allows external subscribers to respond to enemy death */ + UPROPERTY(BlueprintAssignable, Category="Events") + FOnEnemyDied OnEnemyDied; + +public: + + /** Performs an AI-initiated combo attack. Number of hits will be decided by this character */ + void DoAIComboAttack(); + + /** Performs an AI-initiated charged attack. Charge time will be decided by this character */ + void DoAIChargedAttack(); + + /** Called from a delegate when the attack montage ends */ + void AttackMontageEnded(UAnimMontage* Montage, bool bInterrupted); + + /** Returns the last recorded location we were attacked from */ + const FVector& GetLastDangerLocation() const; + + /** Returns the last game time we were attacked */ + float GetLastDangerTime() const; + +public: + + // ~begin ICombatAttacker interface + + /** Performs an attack's collision check */ + virtual void DoAttackTrace(FName DamageSourceBone) override; + + /** Performs a combo attack's check to continue the string */ + UFUNCTION(BlueprintCallable, Category="Attacker") + virtual void CheckCombo() override; + + /** Performs a charged attack's check to loop the charge animation */ + UFUNCTION(BlueprintCallable, Category="Attacker") + virtual void CheckChargedAttack() override; + + // ~end ICombatAttacker interface + + // ~begin ICombatDamageable interface + + /** Handles damage and knockback events */ + virtual void ApplyDamage(float Damage, AActor* DamageCauser, const FVector& DamageLocation, const FVector& DamageImpulse) override; + + /** Handles death events */ + virtual void HandleDeath() override; + + /** Handles healing events */ + virtual void ApplyHealing(float Healing, AActor* Healer) override; + + /** Allows the enemy to react to incoming attacks */ + virtual void NotifyDanger(const FVector& DangerLocation, AActor* DangerSource) override; + + // ~end ICombatDamageable interface + +protected: + + /** Removes this character from the level after it dies */ + void RemoveFromLevel(); + +public: + + /** Overrides the default TakeDamage functionality */ + virtual float TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override; + + /** Overrides landing to reset damage ragdoll physics */ + virtual void Landed(const FHitResult& Hit) override; + +protected: + + /** Blueprint handler to play damage received effects */ + UFUNCTION(BlueprintImplementableEvent, Category="Combat") + void ReceivedDamage(float Damage, const FVector& ImpactPoint, const FVector& DamageDirection); + +protected: + + /** Gameplay initialization */ + virtual void BeginPlay() override; + + /** EndPlay cleanup */ + virtual void EndPlay(EEndPlayReason::Type EndPlayReason) override; +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/AI/CombatEnemySpawner.cpp b/Source/KingshearthLegacy/Variant_Combat/AI/CombatEnemySpawner.cpp new file mode 100644 index 0000000..b29a858 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/AI/CombatEnemySpawner.cpp @@ -0,0 +1,126 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "CombatEnemySpawner.h" +#include "Engine/World.h" +#include "Components/SceneComponent.h" +#include "Components/CapsuleComponent.h" +#include "Components/ArrowComponent.h" +#include "TimerManager.h" +#include "CombatEnemy.h" + +ACombatEnemySpawner::ACombatEnemySpawner() +{ + PrimaryActorTick.bCanEverTick = false; + + // create the root + RootComponent = CreateDefaultSubobject(TEXT("Root")); + + // create the reference spawn capsule + SpawnCapsule = CreateDefaultSubobject(TEXT("Spawn Capsule")); + SpawnCapsule->SetupAttachment(RootComponent); + + SpawnCapsule->SetRelativeLocation(FVector(0.0f, 0.0f, 90.0f)); + SpawnCapsule->SetCapsuleSize(35.0f, 90.0f); + SpawnCapsule->SetCollisionProfileName(FName("NoCollision")); + + SpawnDirection = CreateDefaultSubobject(TEXT("Spawn Direction")); + SpawnDirection->SetupAttachment(RootComponent); +} + +void ACombatEnemySpawner::BeginPlay() +{ + Super::BeginPlay(); + + // should we spawn an enemy right away? + if (bShouldSpawnEnemiesImmediately) + { + // schedule the first enemy spawn + GetWorld()->GetTimerManager().SetTimer(SpawnTimer, this, &ACombatEnemySpawner::SpawnEnemy, InitialSpawnDelay); + } + +} + +void ACombatEnemySpawner::EndPlay(EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + // clear the spawn timer + GetWorld()->GetTimerManager().ClearTimer(SpawnTimer); +} + +void ACombatEnemySpawner::SpawnEnemy() +{ + // ensure the enemy class is valid + if (IsValid(EnemyClass)) + { + // spawn the enemy at the reference capsule's transform + FActorSpawnParameters SpawnParams; + SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn; + + ACombatEnemy* SpawnedEnemy = GetWorld()->SpawnActor(EnemyClass, SpawnCapsule->GetComponentTransform(), SpawnParams); + + // was the enemy successfully created? + if (SpawnedEnemy) + { + // subscribe to the death delegate + SpawnedEnemy->OnEnemyDied.AddDynamic(this, &ACombatEnemySpawner::OnEnemyDied); + } + } +} + +void ACombatEnemySpawner::OnEnemyDied() +{ + // decrease the spawn counter + --SpawnCount; + + // is this the last enemy we should spawn? + if (SpawnCount <= 0) + { + // schedule the activation on depleted message + GetWorld()->GetTimerManager().SetTimer(SpawnTimer, this, &ACombatEnemySpawner::SpawnerDepleted, ActivationDelay); + return; + } + + // schedule the next enemy spawn + GetWorld()->GetTimerManager().SetTimer(SpawnTimer, this, &ACombatEnemySpawner::SpawnEnemy, RespawnDelay); +} + +void ACombatEnemySpawner::SpawnerDepleted() +{ + // process the actors to activate list + for (AActor* CurrentActor : ActorsToActivateWhenDepleted) + { + // check if the actor is activatable + if (ICombatActivatable* CombatActivatable = Cast(CurrentActor)) + { + // activate the actor + CombatActivatable->ActivateInteraction(this); + } + } +} + +void ACombatEnemySpawner::ToggleInteraction(AActor* ActivationInstigator) +{ + // stub +} + +void ACombatEnemySpawner::ActivateInteraction(AActor* ActivationInstigator) +{ + // ensure we're only activated once, and only if we've deferred enemy spawning + if (bHasBeenActivated || bShouldSpawnEnemiesImmediately) + { + return; + } + + // raise the activation flag + bHasBeenActivated = true; + + // spawn the first enemy + SpawnEnemy(); +} + +void ACombatEnemySpawner::DeactivateInteraction(AActor* ActivationInstigator) +{ + // stub +} diff --git a/Source/KingshearthLegacy/Variant_Combat/AI/CombatEnemySpawner.h b/Source/KingshearthLegacy/Variant_Combat/AI/CombatEnemySpawner.h new file mode 100644 index 0000000..ca9eae5 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/AI/CombatEnemySpawner.h @@ -0,0 +1,109 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "CombatActivatable.h" +#include "CombatEnemySpawner.generated.h" + +class UCapsuleComponent; +class UArrowComponent; +class ACombatEnemy; + +/** + * A basic Actor in charge of spawning Enemy Characters and monitoring their deaths. + * Enemies will be spawned one by one, and the spawner will wait until the enemy dies before spawning a new one. + * The spawner can be remotely activated through the ICombatActivatable interface + * When the last spawned enemy dies, the spawner can also activate other ICombatActivatables + */ +UCLASS(abstract) +class ACombatEnemySpawner : public AActor, public ICombatActivatable +{ + GENERATED_BODY() + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) + UCapsuleComponent* SpawnCapsule; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true")) + UArrowComponent* SpawnDirection; + +protected: + + /** Type of enemy to spawn */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Enemy Spawner") + TSubclassOf EnemyClass; + + /** If true, the first enemy will be spawned as soon as the game starts */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Enemy Spawner") + bool bShouldSpawnEnemiesImmediately = true; + + /** Time to wait before spawning the first enemy on game start */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Enemy Spawner", meta = (ClampMin = 0, ClampMax = 10)) + float InitialSpawnDelay = 5.0f; + + /** Number of enemies to spawn */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Enemy Spawner", meta = (ClampMin = 0, ClampMax = 100)) + int32 SpawnCount = 1; + + /** Time to wait before spawning the next enemy after the current one dies */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Enemy Spawner", meta = (ClampMin = 0, ClampMax = 10)) + float RespawnDelay = 5.0f; + + /** Time to wait after this spawner is depleted before activating the actor list */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Activation", meta = (ClampMin = 0, ClampMax = 10)) + float ActivationDelay = 1.0f; + + /** List of actors to activate after the last enemy dies */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Activation") + TArray ActorsToActivateWhenDepleted; + + /** Flag to ensure this is only activated once */ + bool bHasBeenActivated = false; + + /** Timer to spawn enemies after a delay */ + FTimerHandle SpawnTimer; + +public: + + /** Constructor */ + ACombatEnemySpawner(); + +public: + + /** Initialization */ + virtual void BeginPlay() override; + + /** Cleanup */ + virtual void EndPlay(EEndPlayReason::Type EndPlayReason) override; + +protected: + + /** Spawn an enemy and subscribe to its death event */ + void SpawnEnemy(); + + /** Called when the spawned enemy has died */ + UFUNCTION() + void OnEnemyDied(); + + /** Called after the last spawned enemy has died */ + void SpawnerDepleted(); + +public: + + // ~begin ICombatActivatable interface + + /** Toggles the Spawner */ + UFUNCTION(BlueprintCallable, Category="Activatable") + virtual void ToggleInteraction(AActor* ActivationInstigator) override; + + /** Activates the Spawner */ + UFUNCTION(BlueprintCallable, Category="Activatable") + virtual void ActivateInteraction(AActor* ActivationInstigator) override; + + /** Deactivates the Spawner */ + UFUNCTION(BlueprintCallable, Category="Activatable") + virtual void DeactivateInteraction(AActor* ActivationInstigator) override; + + // ~end IActivatable interface +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/AI/CombatStateTreeUtility.cpp b/Source/KingshearthLegacy/Variant_Combat/AI/CombatStateTreeUtility.cpp new file mode 100644 index 0000000..4bd8a96 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/AI/CombatStateTreeUtility.cpp @@ -0,0 +1,316 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "CombatStateTreeUtility.h" +#include "StateTreeExecutionContext.h" +#include "StateTreeExecutionTypes.h" +#include "Engine/World.h" +#include "GameFramework/Character.h" +#include "GameFramework/CharacterMovementComponent.h" +#include "AIController.h" +#include "CombatEnemy.h" +#include "Kismet/GameplayStatics.h" +#include "StateTreeAsyncExecutionContext.h" + +bool FStateTreeCharacterGroundedCondition::TestCondition(FStateTreeExecutionContext& Context) const +{ + const FInstanceDataType& InstanceData = Context.GetInstanceData(*this); + + // is the character currently grounded? + bool bCondition = InstanceData.Character->GetMovementComponent()->IsMovingOnGround(); + + return InstanceData.bMustBeOnAir ? !bCondition : bCondition; +} + +#if WITH_EDITOR +FText FStateTreeCharacterGroundedCondition::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting /*= EStateTreeNodeFormatting::Text*/) const +{ + return FText::FromString("Is Character Grounded"); +} +#endif // WITH_EDITOR + +//////////////////////////////////////////////////////////////////// + +bool FStateTreeIsInDangerCondition::TestCondition(FStateTreeExecutionContext& Context) const +{ + const FInstanceDataType& InstanceData = Context.GetInstanceData(*this); + + // ensure we have a valid enemy character + if (InstanceData.Character) + { + // is the last detected danger event within the reaction threshold? + const float ReactionDelta = InstanceData.Character->GetWorld()->GetTimeSeconds() - InstanceData.Character->GetLastDangerTime(); + + if (ReactionDelta < InstanceData.MaxReactionTime && ReactionDelta > InstanceData.MinReactionTime) + { + // do a dot product check to determine if the danger location is within the character's detection cone + const FVector DangerDir = (InstanceData.Character->GetLastDangerLocation() - InstanceData.Character->GetActorLocation()).GetSafeNormal2D(); + + const float DangerDot = FVector::DotProduct(DangerDir, InstanceData.Character->GetActorForwardVector()); + const float ConeAngleCos = FMath::Cos(FMath::DegreesToRadians(InstanceData.DangerSightConeAngle)); + + return DangerDot > ConeAngleCos; + } + } + + return false; +} + +#if WITH_EDITOR +FText FStateTreeIsInDangerCondition::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting /*= EStateTreeNodeFormatting::Text*/) const +{ + return FText::FromString("Is Character In Danger"); +} +#endif // WITH_EDITOR + +//////////////////////////////////////////////////////////////////// + +EStateTreeRunStatus FStateTreeComboAttackTask::EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const +{ + // get the instance data + FInstanceDataType& InstanceData = Context.GetInstanceData(*this); + + // bind to the on attack completed delegate + InstanceData.Character->OnAttackCompleted.BindLambda( + [WeakContext = Context.MakeWeakExecutionContext()]() + { + WeakContext.FinishTask(EStateTreeFinishTaskType::Succeeded); + } + ); + + + // tell the character to do a combo attack + InstanceData.Character->DoAIComboAttack(); + + return EStateTreeRunStatus::Running; +} + +void FStateTreeComboAttackTask::ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const +{ + // get the instance data + FInstanceDataType& InstanceData = Context.GetInstanceData(*this); + + // unbind the on attack completed delegate + InstanceData.Character->OnAttackCompleted.Unbind(); +} + +#if WITH_EDITOR +FText FStateTreeComboAttackTask::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting /*= EStateTreeNodeFormatting::Text*/) const +{ + return FText::FromString("Do Combo Attack"); +} +#endif // WITH_EDITOR + +//////////////////////////////////////////////////////////////////// + +EStateTreeRunStatus FStateTreeChargedAttackTask::EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const +{ + // get the instance data + FInstanceDataType& InstanceData = Context.GetInstanceData(*this); + + // bind to the on attack completed delegate + InstanceData.Character->OnAttackCompleted.BindLambda( + [WeakContext = Context.MakeWeakExecutionContext()]() + { + WeakContext.FinishTask(EStateTreeFinishTaskType::Succeeded); + } + ); + + // tell the character to do a charged attack + InstanceData.Character->DoAIChargedAttack(); + + return EStateTreeRunStatus::Running; +} + +void FStateTreeChargedAttackTask::ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const +{ + // get the instance data + FInstanceDataType& InstanceData = Context.GetInstanceData(*this); + + // unbind the on attack completed delegate + InstanceData.Character->OnAttackCompleted.Unbind(); +} + +#if WITH_EDITOR +FText FStateTreeChargedAttackTask::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting /*= EStateTreeNodeFormatting::Text*/) const +{ + return FText::FromString("Do Charged Attack"); +} +#endif // WITH_EDITOR + +//////////////////////////////////////////////////////////////////// + +EStateTreeRunStatus FStateTreeWaitForLandingTask::EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const +{ + // get the instance data + FInstanceDataType& InstanceData = Context.GetInstanceData(*this); + + // bind to the on enemy landed delegate + InstanceData.Character->OnEnemyLanded.BindLambda( + [WeakContext = Context.MakeWeakExecutionContext()]() + { + WeakContext.FinishTask(EStateTreeFinishTaskType::Succeeded); + } + ); + + return EStateTreeRunStatus::Running; +} + +void FStateTreeWaitForLandingTask::ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const +{ + // get the instance data + FInstanceDataType& InstanceData = Context.GetInstanceData(*this); + + // unbind the on enemy landed delegate + InstanceData.Character->OnEnemyLanded.Unbind(); +} + +#if WITH_EDITOR +FText FStateTreeWaitForLandingTask::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting /*= EStateTreeNodeFormatting::Text*/) const +{ + return FText::FromString("Wait for Landing"); +} +#endif // WITH_EDITOR + +//////////////////////////////////////////////////////////////////// + +EStateTreeRunStatus FStateTreeFaceActorTask::EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const +{ + // get the instance data + FInstanceDataType& InstanceData = Context.GetInstanceData(*this); + + // set the AI Controller's focus + InstanceData.Controller->SetFocus(InstanceData.ActorToFaceTowards); + + return EStateTreeRunStatus::Running; +} + +void FStateTreeFaceActorTask::ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const +{ + // get the instance data + FInstanceDataType& InstanceData = Context.GetInstanceData(*this); + + // clear the AI Controller's focus + InstanceData.Controller->ClearFocus(EAIFocusPriority::Gameplay); +} + +#if WITH_EDITOR +FText FStateTreeFaceActorTask::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting /*= EStateTreeNodeFormatting::Text*/) const +{ + return FText::FromString("Face Towards Actor"); +} +#endif // WITH_EDITOR + +//////////////////////////////////////////////////////////////////// + +EStateTreeRunStatus FStateTreeFaceLocationTask::EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const +{ + // get the instance data + FInstanceDataType& InstanceData = Context.GetInstanceData(*this); + + // set the AI Controller's focus + InstanceData.Controller->SetFocalPoint(InstanceData.FaceLocation); + + return EStateTreeRunStatus::Running; +} + +void FStateTreeFaceLocationTask::ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const +{ + // get the instance data + FInstanceDataType& InstanceData = Context.GetInstanceData(*this); + + // clear the AI Controller's focus + InstanceData.Controller->ClearFocus(EAIFocusPriority::Gameplay); +} + +#if WITH_EDITOR +FText FStateTreeFaceLocationTask::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting /*= EStateTreeNodeFormatting::Text*/) const +{ + return FText::FromString("Face Towards Location"); +} +#endif // WITH_EDITOR + +//////////////////////////////////////////////////////////////////// + +EStateTreeRunStatus FStateTreeSetCharacterSpeedTask::EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const +{ + // get the instance data + FInstanceDataType& InstanceData = Context.GetInstanceData(*this); + + // set the character's max ground speed + InstanceData.Character->GetCharacterMovement()->MaxWalkSpeed = InstanceData.Speed; + + return EStateTreeRunStatus::Running; +} + +#if WITH_EDITOR +FText FStateTreeSetCharacterSpeedTask::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting /*= EStateTreeNodeFormatting::Text*/) const +{ + return FText::FromString("Set Character Speed"); +} +#endif // WITH_EDITOR + +//////////////////////////////////////////////////////////////////// + +EStateTreeRunStatus FStateTreeGetPlayerInfoTask::EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const +{ + // get the instance data + FInstanceDataType& InstanceData = Context.GetInstanceData(*this); + + // reset the selected target + ACharacter* SelectedTarget = nullptr; + + // iterate through each local player + const int32 NumPlayers = UGameplayStatics::GetNumLocalPlayerControllers(InstanceData.Character); + + for (int32 i = 0; i < NumPlayers; ++i) + { + if (ACharacter* Current = Cast(UGameplayStatics::GetPlayerPawn(InstanceData.Character, i))) + { + // compute the distance to the target + const float TargetDist = (Current->GetActorLocation() - InstanceData.Character->GetActorLocation()).Size(); + + // is this target within range? + if (TargetDist < InstanceData.MaxRange) + { + // have we selected a valid target already? + if (SelectedTarget) + { + // randomly switch to the new target + if (FMath::RandBool()) + { + SelectedTarget = Current; + } + } + else + { + // no valid target yet, so choose this one + SelectedTarget = Current; + } + } + } + } + + // set the new target + InstanceData.TargetPlayerCharacter = SelectedTarget; + + // if the target is not valid, fail the task + if (!SelectedTarget) + { + return EStateTreeRunStatus::Failed; + } + + // set the target location and distance + InstanceData.TargetPlayerLocation = SelectedTarget->GetActorLocation(); + InstanceData.DistanceToTarget = (SelectedTarget->GetActorLocation() - InstanceData.Character->GetActorLocation()).Size(); + + // succeed + return EStateTreeRunStatus::Succeeded; +} + +#if WITH_EDITOR +FText FStateTreeGetPlayerInfoTask::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting /*= EStateTreeNodeFormatting::Text*/) const +{ + return FText::FromString("Get Player Info"); +} +#endif // WITH_EDITOR \ No newline at end of file diff --git a/Source/KingshearthLegacy/Variant_Combat/AI/CombatStateTreeUtility.h b/Source/KingshearthLegacy/Variant_Combat/AI/CombatStateTreeUtility.h new file mode 100644 index 0000000..b8fcc80 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/AI/CombatStateTreeUtility.h @@ -0,0 +1,437 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "StateTreeTaskBase.h" +#include "StateTreeConditionBase.h" + +#include "CombatStateTreeUtility.generated.h" + +class ACharacter; +class AAIController; +class ACombatEnemy; + +/** + * Instance data struct for the FStateTreeCharacterGroundedCondition condition + */ +USTRUCT() +struct FStateTreeCharacterGroundedConditionInstanceData +{ + GENERATED_BODY() + + /** Character to check grounded status on */ + UPROPERTY(EditAnywhere, Category = "Context") + TObjectPtr Character; + + /** If true, the condition passes if the character is not grounded instead */ + UPROPERTY(EditAnywhere, Category = "Condition") + bool bMustBeOnAir = false; +}; + +/** + * StateTree condition to check if the character is grounded + */ +USTRUCT(DisplayName = "Character is Grounded") +struct FStateTreeCharacterGroundedCondition : public FStateTreeConditionCommonBase +{ + GENERATED_BODY() + + /** Set the instance data type */ + using FInstanceDataType = FStateTreeCharacterGroundedConditionInstanceData; + virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); } + + /** Default constructor */ + FStateTreeCharacterGroundedCondition() = default; + + /** Tests the StateTree condition */ + virtual bool TestCondition(FStateTreeExecutionContext& Context) const override; + +#if WITH_EDITOR + + /** Provides the description string */ + virtual FText GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting = EStateTreeNodeFormatting::Text) const override; +#endif + +}; + +//////////////////////////////////////////////////////////////////// + +/** + * Instance data struct for the FStateTreeIsInDangerCondition condition + */ +USTRUCT() +struct FStateTreeIsInDangerConditionInstanceData +{ + GENERATED_BODY() + + /** Character to check danger status on */ + UPROPERTY(EditAnywhere, Category = "Context") + TObjectPtr Character; + + /** Minimum time to wait before reacting to the danger event */ + UPROPERTY(EditAnywhere, Category = "Parameters", meta = (Units = "s")) + float MinReactionTime = 0.35f; + + /** Maximum time to wait before ignoring the danger event */ + UPROPERTY(EditAnywhere, Category = "Parameters", meta = (Units = "s")) + float MaxReactionTime = 0.75f; + + /** Line of sight half angle for detecting incoming danger, in degrees*/ + UPROPERTY(EditAnywhere, Category = "Parameters", meta = (Units = "degrees")) + float DangerSightConeAngle = 120.0f; +}; + +/** + * StateTree condition to check if the character is about to be hit by an attack + */ +USTRUCT(DisplayName = "Character is in Danger") +struct FStateTreeIsInDangerCondition : public FStateTreeConditionCommonBase +{ + GENERATED_BODY() + + /** Set the instance data type */ + using FInstanceDataType = FStateTreeIsInDangerConditionInstanceData; + virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); } + + /** Default constructor */ + FStateTreeIsInDangerCondition() = default; + + /** Tests the StateTree condition */ + virtual bool TestCondition(FStateTreeExecutionContext& Context) const override; + +#if WITH_EDITOR + + /** Provides the description string */ + virtual FText GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting = EStateTreeNodeFormatting::Text) const override; +#endif + +}; + +//////////////////////////////////////////////////////////////////// + +/** + * Instance data struct for the Combat StateTree tasks + */ +USTRUCT() +struct FStateTreeAttackInstanceData +{ + GENERATED_BODY() + + /** Character that will perform the attack */ + UPROPERTY(EditAnywhere, Category = Context) + TObjectPtr Character; +}; + +/** + * StateTree task to perform a combo attack + */ +USTRUCT(meta=(DisplayName="Combo Attack", Category="Combat")) +struct FStateTreeComboAttackTask : public FStateTreeTaskCommonBase +{ + GENERATED_BODY() + + /** Constructor */ + FStateTreeComboAttackTask() + { + // disable tick + bShouldCallTick = false; + + // skip state change events if this is sustained + bShouldStateChangeOnReselect = false; + } + + /* Ensure we're using the correct instance data struct */ + using FInstanceDataType = FStateTreeAttackInstanceData; + virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); } + + /** Runs when the owning state is entered */ + virtual EStateTreeRunStatus EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override; + + /** Runs when the owning state is ended */ + virtual void ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override; + +#if WITH_EDITOR + virtual FText GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting = EStateTreeNodeFormatting::Text) const override; +#endif // WITH_EDITOR +}; + +/** + * StateTree task to perform a charged attack + */ +USTRUCT(meta=(DisplayName="Charged Attack", Category="Combat")) +struct FStateTreeChargedAttackTask : public FStateTreeTaskCommonBase +{ + GENERATED_BODY() + + /** Constructor */ + FStateTreeChargedAttackTask() + { + // disable tick + bShouldCallTick = false; + + // skip state change events if this is sustained + bShouldStateChangeOnReselect = false; + } + + /* Ensure we're using the correct instance data struct */ + using FInstanceDataType = FStateTreeAttackInstanceData; + virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); } + + /** Runs when the owning state is entered */ + virtual EStateTreeRunStatus EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override; + + /** Runs when the owning state is ended */ + virtual void ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override; + +#if WITH_EDITOR + virtual FText GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting = EStateTreeNodeFormatting::Text) const override; +#endif // WITH_EDITOR +}; + +/** + * StateTree task to wait for the character to land + */ +USTRUCT(meta=(DisplayName="Wait for Landing", Category="Combat")) +struct FStateTreeWaitForLandingTask : public FStateTreeTaskCommonBase +{ + GENERATED_BODY() + + /** Constructor */ + FStateTreeWaitForLandingTask() + { + // disable tick + bShouldCallTick = false; + + // skip state change events if this is sustained + bShouldStateChangeOnReselect = false; + } + + /* Ensure we're using the correct instance data struct */ + using FInstanceDataType = FStateTreeAttackInstanceData; + virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); } + + /** Runs when the owning state is entered */ + virtual EStateTreeRunStatus EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override; + + /** Runs when the owning state is ended */ + virtual void ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override; + +#if WITH_EDITOR + virtual FText GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting = EStateTreeNodeFormatting::Text) const override; +#endif // WITH_EDITOR +}; + +//////////////////////////////////////////////////////////////////// + +/** + * Instance data struct for the Face Towards Actor StateTree task + */ +USTRUCT() +struct FStateTreeFaceActorInstanceData +{ + GENERATED_BODY() + + /** AI Controller that will determine the focused actor */ + UPROPERTY(EditAnywhere, Category = Context) + TObjectPtr Controller; + + /** Actor that will be faced towards */ + UPROPERTY(EditAnywhere, Category = Input) + TObjectPtr ActorToFaceTowards; +}; + +/** + * StateTree task to face an AI-Controlled Pawn towards an Actor + */ +USTRUCT(meta=(DisplayName="Face Towards Actor", Category="Combat")) +struct FStateTreeFaceActorTask : public FStateTreeTaskCommonBase +{ + GENERATED_BODY() + + /** Constructor */ + FStateTreeFaceActorTask() + { + // disable tick + bShouldCallTick = false; + + // skip state change events if this is sustained + bShouldStateChangeOnReselect = false; + } + + /* Ensure we're using the correct instance data struct */ + using FInstanceDataType = FStateTreeFaceActorInstanceData; + virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); } + + /** Runs when the owning state is entered */ + virtual EStateTreeRunStatus EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override; + + /** Runs when the owning state is ended */ + virtual void ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override; + +#if WITH_EDITOR + virtual FText GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting = EStateTreeNodeFormatting::Text) const override; +#endif // WITH_EDITOR +}; + +//////////////////////////////////////////////////////////////////// + +/** + * Instance data struct for the Face Towards Location StateTree task + */ +USTRUCT() +struct FStateTreeFaceLocationInstanceData +{ + GENERATED_BODY() + + /** AI Controller that will determine the focused location */ + UPROPERTY(EditAnywhere, Category = Context) + TObjectPtr Controller; + + /** Location that will be faced towards */ + UPROPERTY(EditAnywhere, Category = Parameter) + FVector FaceLocation = FVector::ZeroVector; +}; + +/** + * StateTree task to face an AI-Controlled Pawn towards a world location + */ +USTRUCT(meta=(DisplayName="Face Towards Location", Category="Combat")) +struct FStateTreeFaceLocationTask : public FStateTreeTaskCommonBase +{ + GENERATED_BODY() + + /** Constructor */ + FStateTreeFaceLocationTask() + { + // disable tick + bShouldCallTick = false; + + // skip state change events if this is sustained + bShouldStateChangeOnReselect = false; + } + + /* Ensure we're using the correct instance data struct */ + using FInstanceDataType = FStateTreeFaceLocationInstanceData; + virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); } + + /** Runs when the owning state is entered */ + virtual EStateTreeRunStatus EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override; + + /** Runs when the owning state is ended */ + virtual void ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override; + +#if WITH_EDITOR + virtual FText GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting = EStateTreeNodeFormatting::Text) const override; +#endif // WITH_EDITOR +}; + +//////////////////////////////////////////////////////////////////// + +/** + * Instance data struct for the Set Character Speed StateTree task + */ +USTRUCT() +struct FStateTreeSetCharacterSpeedInstanceData +{ + GENERATED_BODY() + + /** Character that will be affected */ + UPROPERTY(EditAnywhere, Category = Context) + TObjectPtr Character; + + /** Max ground speed to set for the character */ + UPROPERTY(EditAnywhere, Category = Parameter) + float Speed = 600.0f; +}; + +/** + * StateTree task to change a Character's ground speed + */ +USTRUCT(meta=(DisplayName="Set Character Speed", Category="Combat")) +struct FStateTreeSetCharacterSpeedTask : public FStateTreeTaskCommonBase +{ + GENERATED_BODY() + + /** Constructor */ + FStateTreeSetCharacterSpeedTask() + { + // disable tick + bShouldCallTick = false; + + // skip state change events if this is sustained + bShouldStateChangeOnReselect = false; + } + + /* Ensure we're using the correct instance data struct */ + using FInstanceDataType = FStateTreeSetCharacterSpeedInstanceData; + virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); } + + /** Runs when the owning state is entered */ + virtual EStateTreeRunStatus EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override; + +#if WITH_EDITOR + virtual FText GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting = EStateTreeNodeFormatting::Text) const override; +#endif // WITH_EDITOR +}; + +//////////////////////////////////////////////////////////////////// + +/** + * Instance data struct for the Get Player Info task + */ +USTRUCT() +struct FStateTreeGetPlayerInfoInstanceData +{ + GENERATED_BODY() + + /** Character that owns this task */ + UPROPERTY(EditAnywhere, Category = "Context") + TObjectPtr Character; + + /** Character that owns this task */ + UPROPERTY(VisibleAnywhere, Category="Output") + TObjectPtr TargetPlayerCharacter; + + /** Last known location for the target */ + UPROPERTY(VisibleAnywhere, Category="Output") + FVector TargetPlayerLocation = FVector::ZeroVector; + + /** Distance to the target */ + UPROPERTY(VisibleAnywhere, Category="Output") + float DistanceToTarget = 0.0f; + + /** Maximum allowed targeting range */ + UPROPERTY(VisibleAnywhere, Category="Parameter", meta = (ClampMin = 0, ClampMax = 10000, Units = "cm")) + float MaxRange = 2500.0f; +}; + +/** + * StateTree task to get information about the player character + */ +USTRUCT(meta=(DisplayName="GetPlayerInfo", Category="Combat")) +struct FStateTreeGetPlayerInfoTask : public FStateTreeTaskCommonBase +{ + GENERATED_BODY() + + /** Constructor */ + FStateTreeGetPlayerInfoTask() + { + // disable tick + bShouldCallTick = false; + + // skip state change events if this is sustained + bShouldStateChangeOnReselect = false; + } + + /* Ensure we're using the correct instance data struct */ + using FInstanceDataType = FStateTreeGetPlayerInfoInstanceData; + virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); } + + /** Runs when the owning state is entered */ + virtual EStateTreeRunStatus EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override; + +#if WITH_EDITOR + virtual FText GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting = EStateTreeNodeFormatting::Text) const override; +#endif // WITH_EDITOR +}; \ No newline at end of file diff --git a/Source/KingshearthLegacy/Variant_Combat/AI/EnvQueryContext_Danger.cpp b/Source/KingshearthLegacy/Variant_Combat/AI/EnvQueryContext_Danger.cpp new file mode 100644 index 0000000..f356711 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/AI/EnvQueryContext_Danger.cpp @@ -0,0 +1,17 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "Variant_Combat/AI/EnvQueryContext_Danger.h" +#include "Variant_Combat/AI/CombatEnemy.h" +#include "EnvironmentQuery/EnvQueryTypes.h" +#include "EnvironmentQuery/Items/EnvQueryItemType_Point.h" + +void UEnvQueryContext_Danger::ProvideContext(FEnvQueryInstance& QueryInstance, FEnvQueryContextData& ContextData) const +{ + // get the querying enemy + if (ACombatEnemy* QuerierActor = Cast(QueryInstance.Owner.Get())) + { + // add the last recorded danger location to the context + UEnvQueryItemType_Point::SetContextHelper(ContextData, QuerierActor->GetLastDangerLocation()); + } +} \ No newline at end of file diff --git a/Source/KingshearthLegacy/Variant_Combat/AI/EnvQueryContext_Danger.h b/Source/KingshearthLegacy/Variant_Combat/AI/EnvQueryContext_Danger.h new file mode 100644 index 0000000..d792034 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/AI/EnvQueryContext_Danger.h @@ -0,0 +1,23 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "EnvironmentQuery/EnvQueryContext.h" +#include "EnvQueryContext_Danger.generated.h" + +/** + * UEnvQueryContext_Danger + * Returns the enemy character's last known danger location + */ +UCLASS() +class KINGSHEARTHLEGACY_API UEnvQueryContext_Danger : public UEnvQueryContext +{ + GENERATED_BODY() + +public: + + /** Provides the context locations or actors for this EnvQuery */ + virtual void ProvideContext(FEnvQueryInstance& QueryInstance, FEnvQueryContextData& ContextData) const override; + +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/AI/EnvQueryContext_Player.cpp b/Source/KingshearthLegacy/Variant_Combat/AI/EnvQueryContext_Player.cpp new file mode 100644 index 0000000..46ca669 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/AI/EnvQueryContext_Player.cpp @@ -0,0 +1,18 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "EnvQueryContext_Player.h" +#include "Kismet/GameplayStatics.h" +#include "EnvironmentQuery/EnvQueryTypes.h" +#include "EnvironmentQuery/Items/EnvQueryItemType_Actor.h" +#include "GameFramework/Pawn.h" + +void UEnvQueryContext_Player::ProvideContext(FEnvQueryInstance& QueryInstance, FEnvQueryContextData& ContextData) const +{ + // get the player pawn for the first local player + AActor* PlayerPawn = UGameplayStatics::GetPlayerPawn(QueryInstance.Owner.Get(), 0); + check(PlayerPawn); + + // add the actor data to the context + UEnvQueryItemType_Actor::SetContextHelper(ContextData, PlayerPawn); +} diff --git a/Source/KingshearthLegacy/Variant_Combat/AI/EnvQueryContext_Player.h b/Source/KingshearthLegacy/Variant_Combat/AI/EnvQueryContext_Player.h new file mode 100644 index 0000000..dc43895 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/AI/EnvQueryContext_Player.h @@ -0,0 +1,22 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "EnvironmentQuery/EnvQueryContext.h" +#include "EnvQueryContext_Player.generated.h" + +/** + * UEnvQueryContext_Player + * Basic EnvQuery Context that returns the first local player + */ +UCLASS() +class UEnvQueryContext_Player : public UEnvQueryContext +{ + GENERATED_BODY() + +public: + + /** Provides the context locations or actors for this EnvQuery */ + virtual void ProvideContext(FEnvQueryInstance& QueryInstance, FEnvQueryContextData& ContextData) const override; +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/Animation/AnimNotify_CheckChargedAttack.cpp b/Source/KingshearthLegacy/Variant_Combat/Animation/AnimNotify_CheckChargedAttack.cpp new file mode 100644 index 0000000..dbc5f12 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Animation/AnimNotify_CheckChargedAttack.cpp @@ -0,0 +1,21 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "AnimNotify_CheckChargedAttack.h" +#include "CombatAttacker.h" +#include "Components/SkeletalMeshComponent.h" + +void UAnimNotify_CheckChargedAttack::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) +{ + // cast the owner to the attacker interface + if (ICombatAttacker* AttackerInterface = Cast(MeshComp->GetOwner())) + { + // tell the actor to check for a charged attack loop + AttackerInterface->CheckChargedAttack(); + } +} + +FString UAnimNotify_CheckChargedAttack::GetNotifyName_Implementation() const +{ + return FString("Check Charged Attack"); +} diff --git a/Source/KingshearthLegacy/Variant_Combat/Animation/AnimNotify_CheckChargedAttack.h b/Source/KingshearthLegacy/Variant_Combat/Animation/AnimNotify_CheckChargedAttack.h new file mode 100644 index 0000000..98b1722 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Animation/AnimNotify_CheckChargedAttack.h @@ -0,0 +1,24 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Animation/AnimNotifies/AnimNotify.h" +#include "AnimNotify_CheckChargedAttack.generated.h" + +/** + * AnimNotify to perform a charged attack hold check. + */ +UCLASS() +class UAnimNotify_CheckChargedAttack : public UAnimNotify +{ + GENERATED_BODY() + +public: + + /** Perform the Anim Notify */ + virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) override; + + /** Get the notify name */ + virtual FString GetNotifyName_Implementation() const override; +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/Animation/AnimNotify_CheckCombo.cpp b/Source/KingshearthLegacy/Variant_Combat/Animation/AnimNotify_CheckCombo.cpp new file mode 100644 index 0000000..117dbf7 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Animation/AnimNotify_CheckCombo.cpp @@ -0,0 +1,21 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "AnimNotify_CheckCombo.h" +#include "CombatAttacker.h" +#include "Components/SkeletalMeshComponent.h" + +void UAnimNotify_CheckCombo::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) +{ + // cast the owner to the attacker interface + if (ICombatAttacker* AttackerInterface = Cast(MeshComp->GetOwner())) + { + // tell the actor to check for combo string + AttackerInterface->CheckCombo(); + } +} + +FString UAnimNotify_CheckCombo::GetNotifyName_Implementation() const +{ + return FString("Check Combo String"); +} diff --git a/Source/KingshearthLegacy/Variant_Combat/Animation/AnimNotify_CheckCombo.h b/Source/KingshearthLegacy/Variant_Combat/Animation/AnimNotify_CheckCombo.h new file mode 100644 index 0000000..cce0f6c --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Animation/AnimNotify_CheckCombo.h @@ -0,0 +1,24 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Animation/AnimNotifies/AnimNotify.h" +#include "AnimNotify_CheckCombo.generated.h" + +/** + * AnimNotify to perform a combo string check. + */ +UCLASS() +class UAnimNotify_CheckCombo : public UAnimNotify +{ + GENERATED_BODY() + +public: + + /** Perform the Anim Notify */ + virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) override; + + /** Get the notify name */ + virtual FString GetNotifyName_Implementation() const override; +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/Animation/AnimNotify_DoAttackTrace.cpp b/Source/KingshearthLegacy/Variant_Combat/Animation/AnimNotify_DoAttackTrace.cpp new file mode 100644 index 0000000..13bd38f --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Animation/AnimNotify_DoAttackTrace.cpp @@ -0,0 +1,20 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "AnimNotify_DoAttackTrace.h" +#include "CombatAttacker.h" +#include "Components/SkeletalMeshComponent.h" + +void UAnimNotify_DoAttackTrace::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) +{ + // cast the owner to the attacker interface + if (ICombatAttacker* AttackerInterface = Cast(MeshComp->GetOwner())) + { + AttackerInterface->DoAttackTrace(AttackBoneName); + } +} + +FString UAnimNotify_DoAttackTrace::GetNotifyName_Implementation() const +{ + return FString("Do Attack Trace"); +} \ No newline at end of file diff --git a/Source/KingshearthLegacy/Variant_Combat/Animation/AnimNotify_DoAttackTrace.h b/Source/KingshearthLegacy/Variant_Combat/Animation/AnimNotify_DoAttackTrace.h new file mode 100644 index 0000000..e9d7a4d --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Animation/AnimNotify_DoAttackTrace.h @@ -0,0 +1,30 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Animation/AnimNotifies/AnimNotify.h" +#include "AnimNotify_DoAttackTrace.generated.h" + +/** + * AnimNotify to tell the actor to perform an attack trace check to look for targets to damage. + */ +UCLASS() +class UAnimNotify_DoAttackTrace : public UAnimNotify +{ + GENERATED_BODY() + +protected: + + /** Source bone for the attack trace */ + UPROPERTY(EditAnywhere, Category="Attack") + FName AttackBoneName; + +public: + + /** Perform the Anim Notify */ + virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) override; + + /** Get the notify name */ + virtual FString GetNotifyName_Implementation() const override; +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/CombatCharacter.cpp b/Source/KingshearthLegacy/Variant_Combat/CombatCharacter.cpp new file mode 100644 index 0000000..1dc04bd --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/CombatCharacter.cpp @@ -0,0 +1,568 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "CombatCharacter.h" +#include "Components/CapsuleComponent.h" +#include "Components/WidgetComponent.h" +#include "GameFramework/CharacterMovementComponent.h" +#include "GameFramework/SpringArmComponent.h" +#include "Components/SkeletalMeshComponent.h" +#include "Camera/CameraComponent.h" +#include "EnhancedInputSubsystems.h" +#include "EnhancedInputComponent.h" +#include "CombatLifeBar.h" +#include "Engine/DamageEvents.h" +#include "TimerManager.h" +#include "Engine/LocalPlayer.h" +#include "CombatPlayerController.h" + +ACombatCharacter::ACombatCharacter() +{ + PrimaryActorTick.bCanEverTick = true; + + // bind the attack montage ended delegate + OnAttackMontageEnded.BindUObject(this, &ACombatCharacter::AttackMontageEnded); + + // Set size for collision capsule + GetCapsuleComponent()->InitCapsuleSize(35.0f, 90.0f); + + // Configure character movement + GetCharacterMovement()->MaxWalkSpeed = 400.0f; + + // create the camera boom + CameraBoom = CreateDefaultSubobject(TEXT("CameraBoom")); + CameraBoom->SetupAttachment(RootComponent); + + CameraBoom->TargetArmLength = DefaultCameraDistance; + CameraBoom->bUsePawnControlRotation = true; + CameraBoom->bEnableCameraLag = true; + CameraBoom->bEnableCameraRotationLag = true; + + // create the orbiting camera + FollowCamera = CreateDefaultSubobject(TEXT("FollowCamera")); + FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); + FollowCamera->bUsePawnControlRotation = false; + + // create the life bar widget component + LifeBar = CreateDefaultSubobject(TEXT("LifeBar")); + LifeBar->SetupAttachment(RootComponent); + + // set the player tag + Tags.Add(FName("Player")); +} + +void ACombatCharacter::Move(const FInputActionValue& Value) +{ + // input is a Vector2D + FVector2D MovementVector = Value.Get(); + + // route the input + DoMove(MovementVector.X, MovementVector.Y); +} + +void ACombatCharacter::Look(const FInputActionValue& Value) +{ + FVector2D LookAxisVector = Value.Get(); + + // route the input + DoLook(LookAxisVector.X, LookAxisVector.Y); +} + +void ACombatCharacter::ComboAttackPressed() +{ + // route the input + DoComboAttackStart(); +} + +void ACombatCharacter::ChargedAttackPressed() +{ + // route the input + DoChargedAttackStart(); +} + +void ACombatCharacter::ChargedAttackReleased() +{ + // route the input + DoChargedAttackEnd(); +} + +void ACombatCharacter::ToggleCamera() +{ + // call the BP hook + BP_ToggleCamera(); +} + +void ACombatCharacter::DoMove(float Right, float Forward) +{ + if (GetController() != nullptr) + { + // find out which way is forward + const FRotator Rotation = GetController()->GetControlRotation(); + const FRotator YawRotation(0, Rotation.Yaw, 0); + + // get forward vector + const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X); + + // get right vector + const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y); + + // add movement + AddMovementInput(ForwardDirection, Forward); + AddMovementInput(RightDirection, Right); + } +} + +void ACombatCharacter::DoLook(float Yaw, float Pitch) +{ + if (GetController() != nullptr) + { + // add yaw and pitch input to controller + AddControllerYawInput(Yaw); + AddControllerPitchInput(Pitch); + } +} + +void ACombatCharacter::DoComboAttackStart() +{ + // are we already playing an attack animation? + if (bIsAttacking) + { + // cache the input time so we can check it later + CachedAttackInputTime = GetWorld()->GetTimeSeconds(); + + return; + } + + // perform a combo attack + ComboAttack(); +} + +void ACombatCharacter::DoComboAttackEnd() +{ + // stub +} + +void ACombatCharacter::DoChargedAttackStart() +{ + // raise the charging attack flag + bIsChargingAttack = true; + + if (bIsAttacking) + { + // do not attack if the charge animation hasn't looped at least once + if (!bHasLoopedChargedAttack) + { + bHasReleasedChargedAttack = false; + } + + // cache the input time so we can check it later + CachedAttackInputTime = GetWorld()->GetTimeSeconds(); + + return; + } + + ChargedAttack(); +} + +void ACombatCharacter::DoChargedAttackEnd() +{ + // lower the charging attack flag + bIsChargingAttack = false; + + // have we done the charge loop at least once and haven't released the button yet? + if (bHasLoopedChargedAttack && !bHasReleasedChargedAttack) + { + // release the charge and resolve the attack + bHasReleasedChargedAttack = true; + + LoopOrResolveChargedAttack(); + } +} + +void ACombatCharacter::ResetHP() +{ + // reset the current HP total + CurrentHP = MaxHP; + + // update the life bar + LifeBarWidget->SetLifePercentage(1.0f); +} + +void ACombatCharacter::ComboAttack() +{ + // raise the attacking flag + bIsAttacking = true; + + // reset the combo count + ComboCount = 0; + + // notify enemies they are about to be attacked + NotifyEnemiesOfIncomingAttack(); + + // play the attack montage + if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance()) + { + const float MontageLength = AnimInstance->Montage_Play(ComboAttackMontage, 1.0f, EMontagePlayReturnType::MontageLength, 0.0f, true); + + // subscribe to montage completed and interrupted events + if (MontageLength > 0.0f) + { + // set the end delegate for the montage + AnimInstance->Montage_SetEndDelegate(OnAttackMontageEnded, ComboAttackMontage); + } + } + +} + +void ACombatCharacter::ChargedAttack() +{ + // raise the attacking flag + bIsAttacking = true; + + // reset the charge loop flag + bHasLoopedChargedAttack = false; + + // reset the charge release flag + bHasReleasedChargedAttack = false; + + // notify enemies they are about to be attacked + NotifyEnemiesOfIncomingAttack(); + + // play the charged attack montage + if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance()) + { + const float MontageLength = AnimInstance->Montage_Play(ChargedAttackMontage, 1.0f, EMontagePlayReturnType::MontageLength, 0.0f, true); + + // subscribe to montage completed and interrupted events + if (MontageLength > 0.0f) + { + // set the end delegate for the montage + AnimInstance->Montage_SetEndDelegate(OnAttackMontageEnded, ChargedAttackMontage); + } + } +} + +void ACombatCharacter::AttackMontageEnded(UAnimMontage* Montage, bool bInterrupted) +{ + // reset the attacking flag + bIsAttacking = false; + + // check if we have a non-stale cached input + if (GetWorld()->GetTimeSeconds() - CachedAttackInputTime <= AttackInputCacheTimeTolerance) + { + // are we holding the charged attack button? + if (bIsChargingAttack) + { + // do a charged attack + ChargedAttack(); + } + else + { + // do a regular attack + ComboAttack(); + } + } +} + +void ACombatCharacter::DoAttackTrace(FName DamageSourceBone) +{ + // sweep for objects in front of the character to be hit by the attack + TArray OutHits; + + // start at the provided socket location, sweep forward + const FVector TraceStart = GetMesh()->GetSocketLocation(DamageSourceBone); + const FVector TraceEnd = TraceStart + (GetActorForwardVector() * MeleeTraceDistance); + + // check for pawn and world dynamic collision object types + FCollisionObjectQueryParams ObjectParams; + ObjectParams.AddObjectTypesToQuery(ECC_Pawn); + ObjectParams.AddObjectTypesToQuery(ECC_WorldDynamic); + + // use a sphere shape for the sweep + FCollisionShape CollisionShape; + CollisionShape.SetSphere(MeleeTraceRadius); + + // ignore self + FCollisionQueryParams QueryParams; + QueryParams.AddIgnoredActor(this); + + if (GetWorld()->SweepMultiByObjectType(OutHits, TraceStart, TraceEnd, FQuat::Identity, ObjectParams, CollisionShape, QueryParams)) + { + // iterate over each object hit + for (const FHitResult& CurrentHit : OutHits) + { + // check if we've hit a damageable actor + ICombatDamageable* Damageable = Cast(CurrentHit.GetActor()); + + if (Damageable) + { + // knock upwards and away from the impact normal + const FVector Impulse = (CurrentHit.ImpactNormal * -MeleeKnockbackImpulse) + (FVector::UpVector * MeleeLaunchImpulse); + + // pass the damage event to the actor + Damageable->ApplyDamage(MeleeDamage, this, CurrentHit.ImpactPoint, Impulse); + + // call the BP handler to play effects, etc. + DealtDamage(MeleeDamage, CurrentHit.ImpactPoint); + } + } + } +} + +void ACombatCharacter::CheckCombo() +{ + // are we playing a non-charge attack animation? + if (bIsAttacking && !bIsChargingAttack) + { + // is the last attack input not stale? + if (GetWorld()->GetTimeSeconds() - CachedAttackInputTime <= ComboInputCacheTimeTolerance) + { + // consume the attack input so we don't accidentally trigger it twice + CachedAttackInputTime = 0.0f; + + // increase the combo counter + ++ComboCount; + + // do we still have a combo section to play? + if (ComboCount < ComboSectionNames.Num()) + { + // notify enemies they are about to be attacked + NotifyEnemiesOfIncomingAttack(); + + // jump to the next combo section + if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance()) + { + AnimInstance->Montage_JumpToSection(ComboSectionNames[ComboCount], ComboAttackMontage); + } + } + } + } +} + +void ACombatCharacter::CheckChargedAttack() +{ + // raise the looped charged attack flag + bHasLoopedChargedAttack = true; + + // set the input release flag from the input. This will determine if we loop or resolve + bHasReleasedChargedAttack = !bIsChargingAttack; + + // resolve the charge loop + LoopOrResolveChargedAttack(); +} + +void ACombatCharacter::LoopOrResolveChargedAttack() +{ + // jump to either the loop or the attack section depending on whether we've released the charge + if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance()) + { + AnimInstance->Montage_JumpToSection(bHasReleasedChargedAttack ? ChargeAttackSection : ChargeLoopSection , ChargedAttackMontage); + } +} + +void ACombatCharacter::NotifyEnemiesOfIncomingAttack() +{ + // sweep for objects in front of the character to be hit by the attack + TArray OutHits; + + // start at the actor location, sweep forward + const FVector TraceStart = GetActorLocation(); + const FVector TraceEnd = TraceStart + (GetActorForwardVector() * DangerTraceDistance); + + // check for pawn object types only + FCollisionObjectQueryParams ObjectParams; + ObjectParams.AddObjectTypesToQuery(ECC_Pawn); + + // use a sphere shape for the sweep + FCollisionShape CollisionShape; + CollisionShape.SetSphere(DangerTraceRadius); + + // ignore self + FCollisionQueryParams QueryParams; + QueryParams.AddIgnoredActor(this); + + if (GetWorld()->SweepMultiByObjectType(OutHits, TraceStart, TraceEnd, FQuat::Identity, ObjectParams, CollisionShape, QueryParams)) + { + // iterate over each object hit + for (const FHitResult& CurrentHit : OutHits) + { + // check if we've hit a damageable actor + ICombatDamageable* Damageable = Cast(CurrentHit.GetActor()); + + if (Damageable) + { + // notify the enemy + Damageable->NotifyDanger(GetActorLocation(), this); + } + } + } +} + +void ACombatCharacter::ApplyDamage(float Damage, AActor* DamageCauser, const FVector& DamageLocation, const FVector& DamageImpulse) +{ + // pass the damage event to the actor + FDamageEvent DamageEvent; + const float ActualDamage = TakeDamage(Damage, DamageEvent, nullptr, DamageCauser); + + // only process knockback and effects if we received nonzero damage + if (ActualDamage > 0.0f) + { + // apply the knockback impulse + GetCharacterMovement()->AddImpulse(DamageImpulse, true); + + // is the character ragdolling? + if (GetMesh()->IsSimulatingPhysics()) + { + // apply an impulse to the ragdoll + GetMesh()->AddImpulseAtLocation(DamageImpulse * GetMesh()->GetMass(), DamageLocation); + } + + // pass control to BP to play effects, etc. + ReceivedDamage(ActualDamage, DamageLocation, DamageImpulse.GetSafeNormal()); + } + +} + +void ACombatCharacter::HandleDeath() +{ + // disable movement while we're dead + GetCharacterMovement()->DisableMovement(); + + // enable full ragdoll physics + GetMesh()->SetSimulatePhysics(true); + + // hide the life bar + LifeBar->SetHiddenInGame(true); + + // pull back the camera + GetCameraBoom()->TargetArmLength = DeathCameraDistance; + + // schedule respawning + GetWorld()->GetTimerManager().SetTimer(RespawnTimer, this, &ACombatCharacter::RespawnCharacter, RespawnTime, false); +} + +void ACombatCharacter::ApplyHealing(float Healing, AActor* Healer) +{ + // stub +} + +void ACombatCharacter::NotifyDanger(const FVector& DangerLocation, AActor* DangerSource) +{ + // stub +} + +void ACombatCharacter::RespawnCharacter() +{ + // destroy the character and let it be respawned by the Player Controller + Destroy(); +} + +float ACombatCharacter::TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) +{ + // only process damage if the character is still alive + if (CurrentHP <= 0.0f) + { + return 0.0f; + } + + // reduce the current HP + CurrentHP -= Damage; + + // have we run out of HP? + if (CurrentHP <= 0.0f) + { + // die + HandleDeath(); + } + else + { + // update the life bar + LifeBarWidget->SetLifePercentage(CurrentHP / MaxHP); + + // enable partial ragdoll physics, but keep the pelvis vertical + GetMesh()->SetPhysicsBlendWeight(0.5f); + GetMesh()->SetBodySimulatePhysics(PelvisBoneName, false); + } + + // return the received damage amount + return Damage; +} + +void ACombatCharacter::Landed(const FHitResult& Hit) +{ + Super::Landed(Hit); + + // is the character still alive? + if (CurrentHP >= 0.0f) + { + // disable ragdoll physics + GetMesh()->SetPhysicsBlendWeight(0.0f); + } +} + +void ACombatCharacter::BeginPlay() +{ + Super::BeginPlay(); + + // get the life bar from the widget component + LifeBarWidget = Cast(LifeBar->GetUserWidgetObject()); + check(LifeBarWidget); + + // initialize the camera + GetCameraBoom()->TargetArmLength = DefaultCameraDistance; + + // save the relative transform for the mesh so we can reset the ragdoll later + MeshStartingTransform = GetMesh()->GetRelativeTransform(); + + // set the life bar color + LifeBarWidget->SetBarColor(LifeBarColor); + + // reset HP to maximum + ResetHP(); +} + +void ACombatCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + // clear the respawn timer + GetWorld()->GetTimerManager().ClearTimer(RespawnTimer); +} + +void ACombatCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) +{ + Super::SetupPlayerInputComponent(PlayerInputComponent); + + // Set up action bindings + if (UEnhancedInputComponent* EnhancedInputComponent = Cast(PlayerInputComponent)) + { + // Moving + EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ACombatCharacter::Move); + + // Looking + EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ACombatCharacter::Look); + EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this, &ACombatCharacter::Look); + + // Combo Attack + EnhancedInputComponent->BindAction(ComboAttackAction, ETriggerEvent::Started, this, &ACombatCharacter::ComboAttackPressed); + + // Charged Attack + EnhancedInputComponent->BindAction(ChargedAttackAction, ETriggerEvent::Started, this, &ACombatCharacter::ChargedAttackPressed); + EnhancedInputComponent->BindAction(ChargedAttackAction, ETriggerEvent::Completed, this, &ACombatCharacter::ChargedAttackReleased); + + // Camera Side Toggle + EnhancedInputComponent->BindAction(ToggleCameraAction, ETriggerEvent::Triggered, this, &ACombatCharacter::ToggleCamera); + } +} + +void ACombatCharacter::NotifyControllerChanged() +{ + Super::NotifyControllerChanged(); + + // update the respawn transform on the Player Controller + if (ACombatPlayerController* PC = Cast(GetController())) + { + PC->SetRespawnTransform(GetActorTransform()); + } +} + diff --git a/Source/KingshearthLegacy/Variant_Combat/CombatCharacter.h b/Source/KingshearthLegacy/Variant_Combat/CombatCharacter.h new file mode 100644 index 0000000..5e5f56b --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/CombatCharacter.h @@ -0,0 +1,340 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Character.h" +#include "CombatAttacker.h" +#include "CombatDamageable.h" +#include "Animation/AnimInstance.h" +#include "CombatCharacter.generated.h" + +class USpringArmComponent; +class UCameraComponent; +class UInputAction; +struct FInputActionValue; +class UCombatLifeBar; +class UWidgetComponent; + +DECLARE_LOG_CATEGORY_EXTERN(LogCombatCharacter, Log, All); + +/** + * An enhanced Third Person Character with melee combat capabilities: + * - Combo attack string + * - Press and hold charged attack + * - Damage dealing and reaction + * - Death + * - Respawning + */ +UCLASS(abstract) +class ACombatCharacter : public ACharacter, public ICombatAttacker, public ICombatDamageable +{ + GENERATED_BODY() + + /** Camera boom positioning the camera behind the character */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) + USpringArmComponent* CameraBoom; + + /** Follow camera */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) + UCameraComponent* FollowCamera; + + /** Life bar widget component */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) + UWidgetComponent* LifeBar; + +protected: + + /** Jump Input Action */ + UPROPERTY(EditAnywhere, Category ="Input") + UInputAction* JumpAction; + + /** Move Input Action */ + UPROPERTY(EditAnywhere, Category ="Input") + UInputAction* MoveAction; + + /** Look Input Action */ + UPROPERTY(EditAnywhere, Category ="Input") + UInputAction* LookAction; + + /** Mouse Look Input Action */ + UPROPERTY(EditAnywhere, Category="Input") + UInputAction* MouseLookAction; + + /** Combo Attack Input Action */ + UPROPERTY(EditAnywhere, Category ="Input") + UInputAction* ComboAttackAction; + + /** Charged Attack Input Action */ + UPROPERTY(EditAnywhere, Category ="Input") + UInputAction* ChargedAttackAction; + + /** Toggle Camera Side Input Action */ + UPROPERTY(EditAnywhere, Category ="Input") + UInputAction* ToggleCameraAction; + + /** Max amount of HP the character will have on respawn */ + UPROPERTY(EditAnywhere, Category="Damage", meta = (ClampMin = 0, ClampMax = 100)) + float MaxHP = 5.0f; + + /** Current amount of HP the character has */ + UPROPERTY(VisibleAnywhere, Category="Damage") + float CurrentHP = 0.0f; + + /** Life bar widget fill color */ + UPROPERTY(EditAnywhere, Category="Damage") + FLinearColor LifeBarColor; + + /** Name of the pelvis bone, for damage ragdoll physics */ + UPROPERTY(EditAnywhere, Category="Damage") + FName PelvisBoneName; + + /** Pointer to the life bar widget */ + UPROPERTY(EditAnywhere, Category="Damage") + TObjectPtr LifeBarWidget; + + /** Max amount of time that may elapse for a non-combo attack input to not be considered stale */ + UPROPERTY(EditAnywhere, Category="Melee Attack", meta = (ClampMin = 0, ClampMax = 5, Units = "s")) + float AttackInputCacheTimeTolerance = 1.0f; + + /** Time at which an attack button was last pressed */ + float CachedAttackInputTime = 0.0f; + + /** If true, the character is currently playing an attack animation */ + bool bIsAttacking = false; + + /** Distance ahead of the character that melee attack sphere collision traces will extend */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Trace", meta = (ClampMin = 0, ClampMax = 500, Units="cm")) + float MeleeTraceDistance = 75.0f; + + /** Radius of the sphere trace for melee attacks */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Trace", meta = (ClampMin = 0, ClampMax = 200, Units = "cm")) + float MeleeTraceRadius = 75.0f; + + /** Distance ahead of the character that enemies will be notified of incoming attacks */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Trace", meta = (ClampMin = 0, ClampMax = 500, Units="cm")) + float DangerTraceDistance = 300.0f; + + /** Radius of the sphere trace to notify enemies of incoming attacks */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Trace", meta = (ClampMin = 0, ClampMax = 200, Units = "cm")) + float DangerTraceRadius = 100.0f; + + /** Amount of damage a melee attack will deal */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Damage", meta = (ClampMin = 0, ClampMax = 100)) + float MeleeDamage = 1.0f; + + /** Amount of knockback impulse a melee attack will apply */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Damage", meta = (ClampMin = 0, ClampMax = 1000, Units = "cm/s")) + float MeleeKnockbackImpulse = 250.0f; + + /** Amount of upwards impulse a melee attack will apply */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Damage", meta = (ClampMin = 0, ClampMax = 1000, Units = "cm/s")) + float MeleeLaunchImpulse = 300.0f; + + /** AnimMontage that will play for combo attacks */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Combo") + UAnimMontage* ComboAttackMontage; + + /** Names of the AnimMontage sections that correspond to each stage of the combo attack */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Combo") + TArray ComboSectionNames; + + /** Max amount of time that may elapse for a combo attack input to not be considered stale */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Combo", meta = (ClampMin = 0, ClampMax = 5, Units = "s")) + float ComboInputCacheTimeTolerance = 0.45f; + + /** Index of the current stage of the melee attack combo */ + int32 ComboCount = 0; + + /** AnimMontage that will play for charged attacks */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Charged") + UAnimMontage* ChargedAttackMontage; + + /** Name of the AnimMontage section that corresponds to the charge loop */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Charged") + FName ChargeLoopSection; + + /** Name of the AnimMontage section that corresponds to the attack */ + UPROPERTY(EditAnywhere, Category="Melee Attack|Charged") + FName ChargeAttackSection; + + /** Flag that determines if the player is currently holding the charged attack input */ + bool bIsChargingAttack = false; + + /** If true, the charged attack hold check has been tested at least once */ + bool bHasLoopedChargedAttack = false; + + /** If true, the character wants to release and resolve the charged attack. */ + bool bHasReleasedChargedAttack = false; + + /** Camera boom length while the character is dead */ + UPROPERTY(EditAnywhere, Category="Camera", meta = (ClampMin = 0, ClampMax = 1000, Units = "cm")) + float DeathCameraDistance = 400.0f; + + /** Camera boom length when the character respawns */ + UPROPERTY(EditAnywhere, Category="Camera", meta = (ClampMin = 0, ClampMax = 1000, Units = "cm")) + float DefaultCameraDistance = 100.0f; + + /** Time to wait before respawning the character */ + UPROPERTY(EditAnywhere, Category="Respawn", meta = (ClampMin = 0, ClampMax = 10, Units = "s")) + float RespawnTime = 3.0f; + + /** Attack montage ended delegate */ + FOnMontageEnded OnAttackMontageEnded; + + /** Character respawn timer */ + FTimerHandle RespawnTimer; + + /** Copy of the mesh's transform so we can reset it after ragdoll animations */ + FTransform MeshStartingTransform; + +public: + + /** Constructor */ + ACombatCharacter(); + +protected: + + /** Called for movement input */ + void Move(const FInputActionValue& Value); + + /** Called for looking input */ + void Look(const FInputActionValue& Value); + + /** Called for combo attack input */ + void ComboAttackPressed(); + + /** Called for combo attack input pressed */ + void ChargedAttackPressed(); + + /** Called for combo attack input released */ + void ChargedAttackReleased(); + + /** Called for toggle camera side input */ + void ToggleCamera(); + + /** BP hook to animate the camera side switch */ + UFUNCTION(BlueprintImplementableEvent, Category="Combat") + void BP_ToggleCamera(); + +public: + + /** Handles move inputs from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoMove(float Right, float Forward); + + /** Handles look inputs from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoLook(float Yaw, float Pitch); + + /** Handles combo attack pressed from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoComboAttackStart(); + + /** Handles combo attack released from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoComboAttackEnd(); + + /** Handles charged attack pressed from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoChargedAttackStart(); + + /** Handles charged attack released from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoChargedAttackEnd(); + +protected: + + /** Resets the character's current HP to maximum */ + void ResetHP(); + + /** Performs a combo attack */ + void ComboAttack(); + + /** Performs a charged attack */ + void ChargedAttack(); + + /** Called from a delegate when the attack montage ends */ + void AttackMontageEnded(UAnimMontage* Montage, bool bInterrupted); + + +public: + + // ~begin CombatAttacker interface + + /** Performs the collision check for an attack */ + virtual void DoAttackTrace(FName DamageSourceBone) override; + + /** Performs the combo string check */ + virtual void CheckCombo() override; + + /** Performs the charged attack hold check */ + virtual void CheckChargedAttack() override; + + /** Loops or resolves the charged attack animation */ + void LoopOrResolveChargedAttack(); + + // ~end CombatAttacker interface + + // ~begin CombatDamageable interface + + /** Notifies nearby enemies that an attack is coming so they can react */ + void NotifyEnemiesOfIncomingAttack(); + + /** Handles damage and knockback events */ + virtual void ApplyDamage(float Damage, AActor* DamageCauser, const FVector& DamageLocation, const FVector& DamageImpulse) override; + + /** Handles death events */ + virtual void HandleDeath() override; + + /** Handles healing events */ + virtual void ApplyHealing(float Healing, AActor* Healer) override; + + /** Allows reaction to incoming attacks */ + virtual void NotifyDanger(const FVector& DangerLocation, AActor* DangerSource) override; + + // ~end CombatDamageable interface + + /** Called from the respawn timer to destroy and re-create the character */ + void RespawnCharacter(); + +public: + + /** Overrides the default TakeDamage functionality */ + virtual float TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override; + + /** Overrides landing to reset damage ragdoll physics */ + virtual void Landed(const FHitResult& Hit) override; + +protected: + + /** Blueprint handler to play damage dealt effects */ + UFUNCTION(BlueprintImplementableEvent, Category="Combat") + void DealtDamage(float Damage, const FVector& ImpactPoint); + + /** Blueprint handler to play damage received effects */ + UFUNCTION(BlueprintImplementableEvent, Category="Combat") + void ReceivedDamage(float Damage, const FVector& ImpactPoint, const FVector& DamageDirection); + +protected: + + /** Initialization */ + virtual void BeginPlay() override; + + /** Cleanup */ + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + /** Handles input bindings */ + virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; + + /** Handles possessed initialization */ + virtual void NotifyControllerChanged() override; + +public: + + /** Returns CameraBoom subobject **/ + FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; } + + /** Returns FollowCamera subobject **/ + FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; } +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/CombatGameMode.cpp b/Source/KingshearthLegacy/Variant_Combat/CombatGameMode.cpp new file mode 100644 index 0000000..8dcad3b --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/CombatGameMode.cpp @@ -0,0 +1,53 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "Variant_Combat/CombatGameMode.h" +#include "Kismet/GameplayStatics.h" +#include "GameFramework/PlayerStart.h" +#include "Engine/World.h" + +ACombatGameMode::ACombatGameMode() +{ + // stub +} + +void ACombatGameMode::BeginPlay() +{ + Super::BeginPlay(); + + // create each additional local player. + // Player 0 will be created automatically as part of regular game init + for (int32 i = 2; i <= NumberOfLocalPlayers; ++i) + { + UGameplayStatics::CreatePlayer(GetWorld(), -1, true); + } +} + +AActor* ACombatGameMode::ChoosePlayerStart_Implementation(AController* Player) +{ + // build the current player tag + FName PlayerTag = FName(*FString::Printf(TEXT("Player%d"), CurrentPlayerStartAssignment)); + + // find all player starts with the matching player tag + TArray PlayerStarts; + + UGameplayStatics::GetAllActorsOfClassWithTag(GetWorld(), APlayerStart::StaticClass(), PlayerTag, PlayerStarts); + + // increment the player start assignment index + ++CurrentPlayerStartAssignment; + + // if no PlayerStarts were found, default to all PlayerStarts instead + if (PlayerStarts.IsEmpty()) + { + UGameplayStatics::GetAllActorsOfClass(GetWorld(), APlayerStart::StaticClass(), PlayerStarts); + } + + // have we found at least one PlayerStart? + if (!PlayerStarts.IsEmpty()) + { + return PlayerStarts[ FMath::RandRange(0, PlayerStarts.Num() - 1) ]; + } + + // no PlayerStarts in the level + return nullptr; +} diff --git a/Source/KingshearthLegacy/Variant_Combat/CombatGameMode.h b/Source/KingshearthLegacy/Variant_Combat/CombatGameMode.h new file mode 100644 index 0000000..7e4e187 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/CombatGameMode.h @@ -0,0 +1,38 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/GameModeBase.h" +#include "CombatGameMode.generated.h" + +/** + * Simple GameMode for a third person combat game + */ +UCLASS(abstract) +class ACombatGameMode : public AGameModeBase +{ + GENERATED_BODY() + +public: + + /** Constructor */ + ACombatGameMode(); + +protected: + + /** Initialization */ + virtual void BeginPlay() override; + + /** Assigns a PlayerStart to a specific player */ + virtual AActor* ChoosePlayerStart_Implementation(AController* Player) override; + +protected: + + /** Determines how many local players should be spawned on game start */ + UPROPERTY(EditDefaultsOnly, Category="Local Multiplayer", meta = (ClampMin = 1, ClampMax = 4)) + int32 NumberOfLocalPlayers = 1; + + /** Used to assign players to different PlayerStarts in the level */ + int32 CurrentPlayerStartAssignment = 0; +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/CombatPlayerController.cpp b/Source/KingshearthLegacy/Variant_Combat/CombatPlayerController.cpp new file mode 100644 index 0000000..8de7eda --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/CombatPlayerController.cpp @@ -0,0 +1,95 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "Variant_Combat/CombatPlayerController.h" +#include "EnhancedInputSubsystems.h" +#include "InputMappingContext.h" +#include "Kismet/GameplayStatics.h" +#include "GameFramework/PlayerStart.h" +#include "CombatCharacter.h" +#include "Engine/LocalPlayer.h" +#include "Engine/World.h" +#include "Blueprint/UserWidget.h" +#include "KingshearthLegacy.h" +#include "Widgets/Input/SVirtualJoystick.h" + +void ACombatPlayerController::BeginPlay() +{ + Super::BeginPlay(); +} + +void ACombatPlayerController::SetupInputComponent() +{ + Super::SetupInputComponent(); + + // only add IMCs for local player controllers + if (IsLocalPlayerController()) + { + // add the input mapping context + if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem(GetLocalPlayer())) + { + for (UInputMappingContext* CurrentContext : DefaultMappingContexts) + { + Subsystem->AddMappingContext(CurrentContext, 0); + } + + // only add these IMCs if we're not using mobile touch input + if (!ShouldUseTouchControls()) + { + for (UInputMappingContext* CurrentContext : MobileExcludedMappingContexts) + { + Subsystem->AddMappingContext(CurrentContext, 0); + } + } + } + } + + // only spawn touch controls on local player controllers + if (IsLocalPlayerController() && ShouldUseTouchControls()) + { + // spawn the mobile controls widget + MobileControlsWidget = CreateWidget(this, MobileControlsWidgetClass); + + if (MobileControlsWidget) + { + // add the controls to the player screen + MobileControlsWidget->AddToPlayerScreen(0); + + } else { + + UE_LOG(LogKingshearthLegacy, Error, TEXT("Could not spawn mobile controls widget.")); + + } + + } +} + +void ACombatPlayerController::OnPossess(APawn* InPawn) +{ + Super::OnPossess(InPawn); + + // subscribe to the pawn's OnDestroyed delegate + InPawn->OnDestroyed.AddDynamic(this, &ACombatPlayerController::OnPawnDestroyed); +} + +void ACombatPlayerController::SetRespawnTransform(const FTransform& NewRespawn) +{ + // save the new respawn transform + RespawnTransform = NewRespawn; +} + +void ACombatPlayerController::OnPawnDestroyed(AActor* DestroyedActor) +{ + // spawn a new character at the respawn transform + if (ACombatCharacter* RespawnedCharacter = GetWorld()->SpawnActor(CharacterClass, RespawnTransform)) + { + // possess the character + Possess(RespawnedCharacter); + } +} + +bool ACombatPlayerController::ShouldUseTouchControls() const +{ + // are we on a mobile platform? Should we force touch? + return SVirtualJoystick::ShouldDisplayTouchInterface() || bForceTouchControls; +} diff --git a/Source/KingshearthLegacy/Variant_Combat/CombatPlayerController.h b/Source/KingshearthLegacy/Variant_Combat/CombatPlayerController.h new file mode 100644 index 0000000..2e1ac16 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/CombatPlayerController.h @@ -0,0 +1,76 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/PlayerController.h" +#include "CombatPlayerController.generated.h" + +class UInputMappingContext; +class ACombatCharacter; + +/** + * Simple Player Controller for a third person combat game + * Manages input mappings + * Respawns the player character at the checkpoint when it's destroyed + */ +UCLASS(abstract, Config="Game") +class ACombatPlayerController : public APlayerController +{ + GENERATED_BODY() + +protected: + + /** Input mapping context for this player */ + UPROPERTY(EditAnywhere, Category="Input|Input Mappings") + TArray DefaultMappingContexts; + + /** Input Mapping Contexts */ + UPROPERTY(EditAnywhere, Category="Input|Input Mappings") + TArray MobileExcludedMappingContexts; + + /** Mobile controls widget to spawn */ + UPROPERTY(EditAnywhere, Category="Input|Touch Controls") + TSubclassOf MobileControlsWidgetClass; + + /** Pointer to the mobile controls widget */ + UPROPERTY() + TObjectPtr MobileControlsWidget; + + /** If true, the player will use UMG touch controls even if not playing on mobile platforms */ + UPROPERTY(EditAnywhere, Config, Category = "Input|Touch Controls") + bool bForceTouchControls = false; + + /** Character class to respawn when the possessed pawn is destroyed */ + UPROPERTY(EditAnywhere, Category="Respawn") + TSubclassOf CharacterClass; + + /** Transform to respawn the character at. Can be set to create checkpoints */ + FTransform RespawnTransform; + +protected: + + /** Gameplay initialization */ + virtual void BeginPlay() override; + + /** Initialize input bindings */ + virtual void SetupInputComponent() override; + + /** Pawn initialization */ + virtual void OnPossess(APawn* InPawn) override; + +public: + + /** Updates the character respawn transform */ + void SetRespawnTransform(const FTransform& NewRespawn); + +protected: + + /** Called if the possessed pawn is destroyed */ + UFUNCTION() + void OnPawnDestroyed(AActor* DestroyedActor); + + /** Returns true if the player should use UMG touch controls */ + bool ShouldUseTouchControls() const; + +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatActivationVolume.cpp b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatActivationVolume.cpp new file mode 100644 index 0000000..2d8d762 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatActivationVolume.cpp @@ -0,0 +1,49 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "CombatActivationVolume.h" +#include "Components/BoxComponent.h" +#include "GameFramework/Character.h" +#include "CombatActivatable.h" + +ACombatActivationVolume::ACombatActivationVolume() +{ + PrimaryActorTick.bCanEverTick = false; + + // create the box volume + RootComponent = Box = CreateDefaultSubobject(TEXT("Box")); + check(Box); + + // set the box's extent + Box->SetBoxExtent(FVector(500.0f, 500.0f, 500.0f)); + + // set the default collision profile to overlap all dynamic + Box->SetCollisionProfileName(FName("OverlapAllDynamic")); + + // bind the begin overlap + Box->OnComponentBeginOverlap.AddDynamic(this, &ACombatActivationVolume::OnOverlap); +} + +void ACombatActivationVolume::OnOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) +{ + // has a Character entered the volume? + ACharacter* PlayerCharacter = Cast(OtherActor); + + if (PlayerCharacter) + { + // is the Character controlled by a player + if (PlayerCharacter->IsPlayerControlled()) + { + // process the actors to activate list + for (AActor* CurrentActor : ActorsToActivate) + { + // is the referenced actor activatable? + if(ICombatActivatable* Activatable = Cast(CurrentActor)) + { + Activatable->ActivateInteraction(PlayerCharacter); + } + } + } + } + +} \ No newline at end of file diff --git a/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatActivationVolume.h b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatActivationVolume.h new file mode 100644 index 0000000..9d63ede --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatActivationVolume.h @@ -0,0 +1,40 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "CombatActivationVolume.generated.h" + +class UBoxComponent; + +/** + * A simple volume that activates a list of actors when the player pawn enters. + */ +UCLASS() +class ACombatActivationVolume : public AActor +{ + GENERATED_BODY() + + /** Collision box volume */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category ="Components", meta = (AllowPrivateAccess = "true")) + UBoxComponent* Box; + +protected: + + /** List of actors to activate when this volume is entered */ + UPROPERTY(EditAnywhere, Category="Activation Volume") + TArray ActorsToActivate; + +public: + + /** Constructor */ + ACombatActivationVolume(); + +protected: + + /** Handles overlaps with the box volume */ + UFUNCTION() + void OnOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); + +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatCheckpointVolume.cpp b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatCheckpointVolume.cpp new file mode 100644 index 0000000..b24b659 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatCheckpointVolume.cpp @@ -0,0 +1,38 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "CombatCheckpointVolume.h" +#include "CombatCharacter.h" +#include "CombatPlayerController.h" + +ACombatCheckpointVolume::ACombatCheckpointVolume() +{ + // create the box volume + RootComponent = Box = CreateDefaultSubobject(TEXT("Box")); + check(Box); + + // set the box's extent + Box->SetBoxExtent(FVector(500.0f, 500.0f, 500.0f)); + + // set the default collision profile to overlap all dynamic + Box->SetCollisionProfileName(FName("OverlapAllDynamic")); + + // bind the begin overlap + Box->OnComponentBeginOverlap.AddDynamic(this, &ACombatCheckpointVolume::OnOverlap); +} + +void ACombatCheckpointVolume::OnOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) +{ + // has the player entered this volume? + ACombatCharacter* PlayerCharacter = Cast(OtherActor); + + if (PlayerCharacter) + { + if (ACombatPlayerController* PC = Cast(PlayerCharacter->GetController())) + { + // update the player's respawn checkpoint + PC->SetRespawnTransform(PlayerCharacter->GetActorTransform()); + } + + } +} diff --git a/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatCheckpointVolume.h b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatCheckpointVolume.h new file mode 100644 index 0000000..ae8ecb4 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatCheckpointVolume.h @@ -0,0 +1,29 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "Components/BoxComponent.h" +#include "CombatCheckpointVolume.generated.h" + +UCLASS(abstract) +class ACombatCheckpointVolume : public AActor +{ + GENERATED_BODY() + + /** Collision box volume */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Components, meta = (AllowPrivateAccess = "true")) + UBoxComponent* Box; + +public: + + /** Constructor */ + ACombatCheckpointVolume(); + +protected: + + /** Handles overlaps with the box volume */ + UFUNCTION() + void OnOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatDamageableBox.cpp b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatDamageableBox.cpp new file mode 100644 index 0000000..2448ea7 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatDamageableBox.cpp @@ -0,0 +1,83 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "CombatDamageableBox.h" +#include "Components/StaticMeshComponent.h" +#include "TimerManager.h" +#include "Engine/World.h" + +ACombatDamageableBox::ACombatDamageableBox() +{ + PrimaryActorTick.bCanEverTick = false; + + // create the mesh + RootComponent = Mesh = CreateDefaultSubobject(TEXT("Mesh")); + + // set the collision properties + Mesh->SetCollisionProfileName(FName("BlockAllDynamic")); + + // enable physics + Mesh->SetSimulatePhysics(true); + + // disable navigation relevance so boxes don't affect NavMesh generation + Mesh->bNavigationRelevant = false; +} + +void ACombatDamageableBox::RemoveFromLevel() +{ + // destroy this actor + Destroy(); +} + +void ACombatDamageableBox::EndPlay(EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + // clear the death timer + GetWorld()->GetTimerManager().ClearTimer(DeathTimer); +} + +void ACombatDamageableBox::ApplyDamage(float Damage, AActor* DamageCauser, const FVector& DamageLocation, const FVector& DamageImpulse) +{ + // only process damage if we still have HP + if (CurrentHP > 0.0f) + { + // apply the damage + CurrentHP -= Damage; + + // are we dead? + if (CurrentHP <= 0.0f) + { + HandleDeath(); + } + + // apply a physics impulse to the box, ignoring its mass + Mesh->AddImpulseAtLocation(DamageImpulse * Mesh->GetMass(), DamageLocation); + + // call the BP handler to play effects, etc. + OnBoxDamaged(DamageLocation, DamageImpulse); + } +} + +void ACombatDamageableBox::HandleDeath() +{ + // change the collision object type to Visibility so we ignore most interactions but still retain physics collisions + Mesh->SetCollisionObjectType(ECC_Visibility); + + // call the BP handler to play effects, etc. + OnBoxDestroyed(); + + // set up the death cleanup timer + GetWorld()->GetTimerManager().SetTimer(DeathTimer, this, &ACombatDamageableBox::RemoveFromLevel, DeathDelayTime); +} + +void ACombatDamageableBox::ApplyHealing(float Healing, AActor* Healer) +{ + // stub +} + +void ACombatDamageableBox::NotifyDanger(const FVector& DangerLocation, AActor* DangerSource) +{ + // stub +} + diff --git a/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatDamageableBox.h b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatDamageableBox.h new file mode 100644 index 0000000..d0cb5c0 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatDamageableBox.h @@ -0,0 +1,71 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "CombatDamageable.h" +#include "CombatDamageableBox.generated.h" + +/** + * A simple physics box that reacts to damage through the ICombatDamageable interface + */ +UCLASS(abstract) +class ACombatDamageableBox : public AActor, public ICombatDamageable +{ + GENERATED_BODY() + + /** Damageable box mesh */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true")) + UStaticMeshComponent* Mesh; + +public: + + /** Constructor */ + ACombatDamageableBox(); + +protected: + + /** Amount of HP this box starts with. */ + UPROPERTY(EditAnywhere, Category="Damage") + float CurrentHP = 3.0f; + + /** Time to wait before we remove this box from the level. */ + UPROPERTY(EditAnywhere, Category="Damage", meta = (ClampMin = 0, ClampMax = 10, Units = "s")) + float DeathDelayTime = 6.0f; + + /** Timer to defer destruction of this box after its HP are depleted */ + FTimerHandle DeathTimer; + + /** Blueprint damage handler for effect playback */ + UFUNCTION(BlueprintImplementableEvent, Category="Damage") + void OnBoxDamaged(const FVector& DamageLocation, const FVector& DamageImpulse); + + /** Blueprint destruction handler for effect playback */ + UFUNCTION(BlueprintImplementableEvent, Category="Damage") + void OnBoxDestroyed(); + + /** Timer callback to remove the box from the level after it dies */ + void RemoveFromLevel(); + +public: + + /** EndPlay cleanup */ + void EndPlay(EEndPlayReason::Type EndPlayReason) override; + + // ~Begin CombatDamageable interface + + /** Handles damage and knockback events */ + virtual void ApplyDamage(float Damage, AActor* DamageCauser, const FVector& DamageLocation, const FVector& DamageImpulse) override; + + /** Handles death events */ + virtual void HandleDeath() override; + + /** Handles healing events */ + virtual void ApplyHealing(float Healing, AActor* Healer) override; + + /** Allows reaction to incoming attacks */ + virtual void NotifyDanger(const FVector& DangerLocation, AActor* DangerSource) override; + + // ~End CombatDamageable interface +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatDummy.cpp b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatDummy.cpp new file mode 100644 index 0000000..6a38a3f --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatDummy.cpp @@ -0,0 +1,56 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "CombatDummy.h" +#include "Components/SceneComponent.h" +#include "Components/StaticMeshComponent.h" +#include "PhysicsEngine/PhysicsConstraintComponent.h" + +ACombatDummy::ACombatDummy() +{ + PrimaryActorTick.bCanEverTick = true; + + // create the root + Root = CreateDefaultSubobject(TEXT("Root")); + SetRootComponent(Root); + + // create the base plate + BasePlate = CreateDefaultSubobject(TEXT("Base Plate")); + BasePlate->SetupAttachment(RootComponent); + + // create the dummy + Dummy = CreateDefaultSubobject(TEXT("Dummy")); + Dummy->SetupAttachment(RootComponent); + + Dummy->SetSimulatePhysics(true); + + // create the physics constraint + PhysicsConstraint = CreateDefaultSubobject(TEXT("Physics Constraint")); + PhysicsConstraint->SetupAttachment(RootComponent); + + PhysicsConstraint->SetConstrainedComponents(BasePlate, NAME_None, Dummy, NAME_None); +} + +void ACombatDummy::ApplyDamage(float Damage, AActor* DamageCauser, const FVector& DamageLocation, const FVector& DamageImpulse) +{ + // apply impulse to the dummy + Dummy->AddImpulseAtLocation(DamageImpulse, DamageLocation); + + // call the BP handler + BP_OnDummyDamaged(DamageLocation, DamageImpulse.GetSafeNormal()); +} + +void ACombatDummy::HandleDeath() +{ + // unused +} + +void ACombatDummy::ApplyHealing(float Healing, AActor* Healer) +{ + // unused +} + +void ACombatDummy::NotifyDanger(const FVector& DangerLocation, AActor* DangerSource) +{ + // unused +} diff --git a/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatDummy.h b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatDummy.h new file mode 100644 index 0000000..5359506 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatDummy.h @@ -0,0 +1,63 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "CombatDamageable.h" +#include "CombatDummy.generated.h" + +class UStaticMeshComponent; +class UPhysicsConstraintComponent; + +/** + * A simple invincible combat training dummy + */ +UCLASS(abstract) +class ACombatDummy : public AActor, public ICombatDamageable +{ + GENERATED_BODY() + + /** Root component */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true")) + USceneComponent* Root; + + /** Static base plate */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true")) + UStaticMeshComponent* BasePlate; + + /** Physics enabled dummy mesh */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true")) + UStaticMeshComponent* Dummy; + + /** Physics constraint holding the dummy and base plate together */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true")) + UPhysicsConstraintComponent* PhysicsConstraint; + +public: + + /** Constructor */ + ACombatDummy(); + + // ~Begin CombatDamageable interface + + /** Handles damage and knockback events */ + virtual void ApplyDamage(float Damage, AActor* DamageCauser, const FVector& DamageLocation, const FVector& DamageImpulse) override; + + /** Handles death events */ + virtual void HandleDeath() override; + + /** Handles healing events */ + virtual void ApplyHealing(float Healing, AActor* Healer) override; + + /** Allows reaction to incoming attacks */ + virtual void NotifyDanger(const FVector& DangerLocation, AActor* DangerSource) override; + + // ~End CombatDamageable interface + +protected: + + /** Blueprint handle to apply damage effects */ + UFUNCTION(BlueprintImplementableEvent, Category="Combat", meta = (DisplayName = "On Dummy Damaged")) + void BP_OnDummyDamaged(const FVector& Location, const FVector& Direction); +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatLavaFloor.cpp b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatLavaFloor.cpp new file mode 100644 index 0000000..816663b --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatLavaFloor.cpp @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "CombatLavaFloor.h" +#include "CombatDamageable.h" +#include "Components/StaticMeshComponent.h" + +ACombatLavaFloor::ACombatLavaFloor() +{ + PrimaryActorTick.bCanEverTick = false; + + // create the mesh + RootComponent = Mesh = CreateDefaultSubobject(TEXT("Mesh")); + + // bind the hit handler + Mesh->OnComponentHit.AddDynamic(this, &ACombatLavaFloor::OnFloorHit); +} + +void ACombatLavaFloor::OnFloorHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit) +{ + // check if the hit actor is damageable by casting to the interface + if (ICombatDamageable* Damageable = Cast(OtherActor)) + { + // damage the actor + Damageable->ApplyDamage(Damage, this, Hit.ImpactPoint, FVector::ZeroVector); + } +} diff --git a/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatLavaFloor.h b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatLavaFloor.h new file mode 100644 index 0000000..813e33a --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Gameplay/CombatLavaFloor.h @@ -0,0 +1,40 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "CombatLavaFloor.generated.h" + +class UStaticMeshComponent; +class UPrimitiveComponent; + +/** + * A basic actor that applies damage on contact through the ICombatDamageable interface. + */ +UCLASS(abstract) +class ACombatLavaFloor : public AActor +{ + GENERATED_BODY() + + /** Floor mesh */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) + UStaticMeshComponent* Mesh; + +protected: + + /** Amount of damage to deal on contact */ + UPROPERTY(EditAnywhere, Category="Damage") + float Damage = 10000.0f; + +public: + + /** Constructor */ + ACombatLavaFloor(); + +protected: + + /** Blocking hit handler */ + UFUNCTION() + void OnFloorHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit); +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/Interfaces/CombatActivatable.cpp b/Source/KingshearthLegacy/Variant_Combat/Interfaces/CombatActivatable.cpp new file mode 100644 index 0000000..5a9f567 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Interfaces/CombatActivatable.cpp @@ -0,0 +1,4 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "CombatActivatable.h" \ No newline at end of file diff --git a/Source/KingshearthLegacy/Variant_Combat/Interfaces/CombatActivatable.h b/Source/KingshearthLegacy/Variant_Combat/Interfaces/CombatActivatable.h new file mode 100644 index 0000000..b7dafd4 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Interfaces/CombatActivatable.h @@ -0,0 +1,36 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" +#include "CombatActivatable.generated.h" + +/** + * Interactable Interface + * Provides a context-agnostic way of activating, deactivating or toggling actors + */ +UINTERFACE(MinimalAPI, NotBlueprintable) +class UCombatActivatable : public UInterface +{ + GENERATED_BODY() +}; + +class ICombatActivatable +{ + GENERATED_BODY() + +public: + + /** Toggles the Interactable Actor */ + UFUNCTION(BlueprintCallable, Category="Activatable") + virtual void ToggleInteraction(AActor* ActivationInstigator) = 0; + + /** Activates the Interactable Actor */ + UFUNCTION(BlueprintCallable, Category="Activatable") + virtual void ActivateInteraction(AActor* ActivationInstigator) = 0; + + /** Deactivates the Interactable Actor */ + UFUNCTION(BlueprintCallable, Category="Activatable") + virtual void DeactivateInteraction(AActor* ActivationInstigator) = 0; +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/Interfaces/CombatAttacker.cpp b/Source/KingshearthLegacy/Variant_Combat/Interfaces/CombatAttacker.cpp new file mode 100644 index 0000000..2abd0a2 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Interfaces/CombatAttacker.cpp @@ -0,0 +1,4 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "CombatAttacker.h" diff --git a/Source/KingshearthLegacy/Variant_Combat/Interfaces/CombatAttacker.h b/Source/KingshearthLegacy/Variant_Combat/Interfaces/CombatAttacker.h new file mode 100644 index 0000000..503a081 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Interfaces/CombatAttacker.h @@ -0,0 +1,36 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" +#include "CombatAttacker.generated.h" + +/** + * CombatAttacker Interface + * Provides common functionality to trigger attack animation events. + */ +UINTERFACE(MinimalAPI, NotBlueprintable) +class UCombatAttacker : public UInterface +{ + GENERATED_BODY() +}; + +class ICombatAttacker +{ + GENERATED_BODY() + +public: + + /** Performs an attack's collision check. Usually called from a montage's AnimNotify */ + UFUNCTION(BlueprintCallable, Category="Attacker") + virtual void DoAttackTrace(FName DamageSourceBone) = 0; + + /** Performs a combo attack's check to continue the string. Usually called from a montage's AnimNotify */ + UFUNCTION(BlueprintCallable, Category="Attacker") + virtual void CheckCombo() = 0; + + /** Performs a charged attack's check to loop the charge animation. Usually called from a montage's AnimNotify */ + UFUNCTION(BlueprintCallable, Category="Attacker") + virtual void CheckChargedAttack() = 0; +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/Interfaces/CombatDamageable.cpp b/Source/KingshearthLegacy/Variant_Combat/Interfaces/CombatDamageable.cpp new file mode 100644 index 0000000..5c82792 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Interfaces/CombatDamageable.cpp @@ -0,0 +1,6 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "CombatDamageable.h" + +// Add default functionality here for any ICombatDamageable functions that are not pure virtual. diff --git a/Source/KingshearthLegacy/Variant_Combat/Interfaces/CombatDamageable.h b/Source/KingshearthLegacy/Variant_Combat/Interfaces/CombatDamageable.h new file mode 100644 index 0000000..8f2a09c --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/Interfaces/CombatDamageable.h @@ -0,0 +1,41 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" +#include "CombatDamageable.generated.h" + +/** + * CombatDamageable interface + * Provides functionality to handle damage, healing, knockback and death + * Also provides functionality to warn characters of incoming sources of damage + */ +UINTERFACE(MinimalAPI, NotBlueprintable) +class UCombatDamageable : public UInterface +{ + GENERATED_BODY() +}; + +class ICombatDamageable +{ + GENERATED_BODY() + +public: + + /** Handles damage and knockback events */ + UFUNCTION(BlueprintCallable, Category="Damageable") + virtual void ApplyDamage(float Damage, AActor* DamageCauser, const FVector& DamageLocation, const FVector& DamageImpulse) = 0; + + /** Handles death events */ + UFUNCTION(BlueprintCallable, Category="Damageable") + virtual void HandleDeath() = 0; + + /** Handles healing events */ + UFUNCTION(BlueprintCallable, Category="Damageable") + virtual void ApplyHealing(float Healing, AActor* Healer) = 0; + + /** Notifies the actor of impending danger such as an incoming hit, allowing it to react. */ + UFUNCTION(BlueprintCallable, Category="Damageable") + virtual void NotifyDanger(const FVector& DangerLocation, AActor* DangerSource) = 0; +}; diff --git a/Source/KingshearthLegacy/Variant_Combat/UI/CombatLifeBar.cpp b/Source/KingshearthLegacy/Variant_Combat/UI/CombatLifeBar.cpp new file mode 100644 index 0000000..ed9c23e --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/UI/CombatLifeBar.cpp @@ -0,0 +1,5 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "CombatLifeBar.h" + diff --git a/Source/KingshearthLegacy/Variant_Combat/UI/CombatLifeBar.h b/Source/KingshearthLegacy/Variant_Combat/UI/CombatLifeBar.h new file mode 100644 index 0000000..9a2fcbe --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Combat/UI/CombatLifeBar.h @@ -0,0 +1,26 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "CombatLifeBar.generated.h" + +/** + * A basic life bar user widget. + */ +UCLASS(abstract) +class UCombatLifeBar : public UUserWidget +{ + GENERATED_BODY() + +public: + + /** Sets the life bar to the provided 0-1 percentage value*/ + UFUNCTION(BlueprintImplementableEvent, Category="Life Bar") + void SetLifePercentage(float Percent); + + // Sets the life bar fill color + UFUNCTION(BlueprintImplementableEvent, Category="Life Bar") + void SetBarColor(FLinearColor Color); +}; diff --git a/Source/KingshearthLegacy/Variant_Platforming/Animation/AnimNotify_EndDash.cpp b/Source/KingshearthLegacy/Variant_Platforming/Animation/AnimNotify_EndDash.cpp new file mode 100644 index 0000000..cdf4041 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Platforming/Animation/AnimNotify_EndDash.cpp @@ -0,0 +1,21 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "AnimNotify_EndDash.h" +#include "PlatformingCharacter.h" +#include "Components/SkeletalMeshComponent.h" + +void UAnimNotify_EndDash::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) +{ + // cast the owner to the attacker interface + if (APlatformingCharacter* PlatformingCharacter = Cast(MeshComp->GetOwner())) + { + // tell the actor to end the dash + PlatformingCharacter->EndDash(); + } +} + +FString UAnimNotify_EndDash::GetNotifyName_Implementation() const +{ + return FString("End Dash"); +} diff --git a/Source/KingshearthLegacy/Variant_Platforming/Animation/AnimNotify_EndDash.h b/Source/KingshearthLegacy/Variant_Platforming/Animation/AnimNotify_EndDash.h new file mode 100644 index 0000000..596620f --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Platforming/Animation/AnimNotify_EndDash.h @@ -0,0 +1,24 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Animation/AnimNotifies/AnimNotify.h" +#include "AnimNotify_EndDash.generated.h" + +/** + * AnimNotify to finish the dash animation and restore player control + */ +UCLASS() +class UAnimNotify_EndDash : public UAnimNotify +{ + GENERATED_BODY() + +public: + + /** Perform the Anim Notify */ + virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) override; + + /** Get the notify name */ + virtual FString GetNotifyName_Implementation() const override; +}; diff --git a/Source/KingshearthLegacy/Variant_Platforming/PlatformingCharacter.cpp b/Source/KingshearthLegacy/Variant_Platforming/PlatformingCharacter.cpp new file mode 100644 index 0000000..53b8fea --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Platforming/PlatformingCharacter.cpp @@ -0,0 +1,373 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "PlatformingCharacter.h" + +#include "Components/CapsuleComponent.h" +#include "Engine/World.h" +#include "GameFramework/CharacterMovementComponent.h" +#include "GameFramework/SpringArmComponent.h" +#include "Components/SkeletalMeshComponent.h" +#include "Camera/CameraComponent.h" +#include "EnhancedInputSubsystems.h" +#include "EnhancedInputComponent.h" +#include "TimerManager.h" +#include "Engine/LocalPlayer.h" + +APlatformingCharacter::APlatformingCharacter() +{ + PrimaryActorTick.bCanEverTick = true; + + // initialize the flags + bHasWallJumped = false; + bHasDoubleJumped = false; + bHasDashed = false; + bIsDashing = false; + + // bind the dash montage ended delegate + OnDashMontageEnded.BindUObject(this, &APlatformingCharacter::DashMontageEnded); + + // enable press and hold jump + JumpMaxHoldTime = 0.4f; + + // set the jump max count to 3 so we can double jump and check for coyote time jumps + JumpMaxCount = 3; + + // Set size for collision capsule + GetCapsuleComponent()->InitCapsuleSize(35.0f, 90.0f); + + // don't rotate the mesh when the controller rotates + bUseControllerRotationYaw = false; + + // Configure character movement + GetCharacterMovement()->GravityScale = 2.5f; + GetCharacterMovement()->MaxAcceleration = 1500.0f; + GetCharacterMovement()->BrakingFrictionFactor = 1.0f; + GetCharacterMovement()->bUseSeparateBrakingFriction = true; + + GetCharacterMovement()->GroundFriction = 4.0f; + GetCharacterMovement()->MaxWalkSpeed = 750.0f; + GetCharacterMovement()->MinAnalogWalkSpeed = 20.0f; + GetCharacterMovement()->BrakingDecelerationWalking = 2500.0f; + GetCharacterMovement()->PerchRadiusThreshold = 15.0f; + + GetCharacterMovement()->JumpZVelocity = 350.0f; + GetCharacterMovement()->BrakingDecelerationFalling = 750.0f; + GetCharacterMovement()->AirControl = 1.0f; + + GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f); + GetCharacterMovement()->bOrientRotationToMovement = true; + + GetCharacterMovement()->NavAgentProps.AgentRadius = 42.0; + GetCharacterMovement()->NavAgentProps.AgentHeight = 192.0; + + // create the camera boom + CameraBoom = CreateDefaultSubobject(TEXT("CameraBoom")); + CameraBoom->SetupAttachment(RootComponent); + + CameraBoom->TargetArmLength = 400.0f; + CameraBoom->bUsePawnControlRotation = true; + CameraBoom->bEnableCameraLag = true; + CameraBoom->CameraLagSpeed = 8.0f; + CameraBoom->bEnableCameraRotationLag = true; + CameraBoom->CameraRotationLagSpeed = 8.0f; + + // create the orbiting camera + FollowCamera = CreateDefaultSubobject(TEXT("FollowCamera")); + FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); + FollowCamera->bUsePawnControlRotation = false; +} + +void APlatformingCharacter::Move(const FInputActionValue& Value) +{ + FVector2D MovementVector = Value.Get(); + + // route the input + DoMove(MovementVector.X, MovementVector.Y); +} + +void APlatformingCharacter::Look(const FInputActionValue& Value) +{ + FVector2D LookAxisVector = Value.Get(); + + // route the input + DoLook(LookAxisVector.X, LookAxisVector.Y); +} + + +void APlatformingCharacter::Dash() +{ + // route the input + DoDash(); +} + +void APlatformingCharacter::MultiJump() +{ + // ignore jumps while dashing + if(bIsDashing) + return; + + // are we already in the air? + if (GetCharacterMovement()->IsFalling()) + { + + // have we already wall jumped? + if (!bHasWallJumped) + { + // run a sphere sweep to check if we're in front of a wall + FHitResult OutHit; + + const FVector TraceStart = GetActorLocation(); + const FVector TraceEnd = TraceStart + (GetActorForwardVector() * WallJumpTraceDistance); + const FCollisionShape TraceShape = FCollisionShape::MakeSphere(WallJumpTraceRadius); + + FCollisionQueryParams QueryParams; + QueryParams.AddIgnoredActor(this); + + if (GetWorld()->SweepSingleByChannel(OutHit, TraceStart, TraceEnd, FQuat(), ECollisionChannel::ECC_Visibility, TraceShape, QueryParams)) + { + // rotate the character to face away from the wall, so we're correctly oriented for the next wall jump + FRotator WallOrientation = OutHit.ImpactNormal.ToOrientationRotator(); + WallOrientation.Pitch = 0.0f; + WallOrientation.Roll = 0.0f; + + SetActorRotation(WallOrientation); + + // apply a launch impulse to the character to perform the actual wall jump + const FVector WallJumpImpulse = (OutHit.ImpactNormal * WallJumpBounceImpulse) + (FVector::UpVector * WallJumpVerticalImpulse); + + LaunchCharacter(WallJumpImpulse, true, true); + + // enable the jump trail + SetJumpTrailState(true); + + // raise the wall jump flag to prevent an immediate second wall jump + bHasWallJumped = true; + + GetWorld()->GetTimerManager().SetTimer(WallJumpTimer, this, &APlatformingCharacter::ResetWallJump, DelayBetweenWallJumps, false); + } + // no wall jump, try a double jump next + else + { + // are we still within coyote time frames? + if (GetWorld()->GetTimeSeconds() - LastFallTime < MaxCoyoteTime) + { + UE_LOG(LogTemp, Warning, TEXT("Coyote Jump")); + + // use the built-in CMC functionality to do the jump + Jump(); + + // enable the jump trail + SetJumpTrailState(true); + + // no coyote time jump + } else { + + // only double jump once while we're in the air + if (!bHasDoubleJumped) + { + bHasDoubleJumped = true; + + // use the built-in CMC functionality to do the double jump + Jump(); + + // enable the jump trail + SetJumpTrailState(true); + } + + } + + + } + } + + } + else + { + // we're grounded so just do a regular jump + Jump(); + + // activate the jump trail + SetJumpTrailState(true); + } +} + +void APlatformingCharacter::ResetWallJump() +{ + // reset the wall jump input lock + bHasWallJumped = false; +} + +void APlatformingCharacter::DoMove(float Right, float Forward) +{ + if (GetController() != nullptr) + { + // momentarily disable movement inputs if we've just wall jumped + if (!bHasWallJumped) + { + // find out which way is forward + const FRotator Rotation = GetController()->GetControlRotation(); + const FRotator YawRotation(0, Rotation.Yaw, 0); + + // get forward vector + const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X); + + // get right vector + const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y); + + // add movement + AddMovementInput(ForwardDirection, Forward); + AddMovementInput(RightDirection, Right); + } + } +} + +void APlatformingCharacter::DoLook(float Yaw, float Pitch) +{ + if (GetController() != nullptr) + { + // add yaw and pitch input to controller + AddControllerYawInput(Yaw); + AddControllerPitchInput(Pitch); + } +} + +void APlatformingCharacter::DoDash() +{ + // ignore the input if we've already dashed and have yet to reset + if (bHasDashed) + return; + + // raise the dash flags + bIsDashing = true; + bHasDashed = true; + + // disable gravity while dashing + GetCharacterMovement()->GravityScale = 0.0f; + + // reset the character velocity so we don't carry momentum into the dash + GetCharacterMovement()->Velocity = FVector::ZeroVector; + + // enable the jump trails + SetJumpTrailState(true); + + // play the dash montage + if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance()) + { + const float MontageLength = AnimInstance->Montage_Play(DashMontage, 1.0f, EMontagePlayReturnType::MontageLength, 0.0f, true); + + // has the montage played successfully? + if (MontageLength > 0.0f) + { + AnimInstance->Montage_SetEndDelegate(OnDashMontageEnded, DashMontage); + } + } +} + +void APlatformingCharacter::DoJumpStart() +{ + // handle special jump cases + MultiJump(); +} + +void APlatformingCharacter::DoJumpEnd() +{ + // stop jumping + StopJumping(); +} + +void APlatformingCharacter::DashMontageEnded(UAnimMontage* Montage, bool bInterrupted) +{ + // Avoid resetting the dash if the previous ground dash interrupted the montage. + if (bInterrupted && bIsDashing) + { + return; + } + + // end the dash + EndDash(); +} + +void APlatformingCharacter::EndDash() +{ + // restore gravity + GetCharacterMovement()->GravityScale = 2.5f; + + // reset the dashing flag + bIsDashing = false; + + // are we grounded after the dash? + if (GetCharacterMovement()->IsMovingOnGround()) + { + // reset the dash usage flag, since we won't receive a landed event + bHasDashed = false; + + // deactivate the jump trails + SetJumpTrailState(false); + } +} + +bool APlatformingCharacter::HasDoubleJumped() const +{ + return bHasDoubleJumped; +} + +bool APlatformingCharacter::HasWallJumped() const +{ + return bHasWallJumped; +} + +void APlatformingCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + // clear the wall jump reset timer + GetWorld()->GetTimerManager().ClearTimer(WallJumpTimer); +} + +void APlatformingCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) +{ + // Set up action bindings + if (UEnhancedInputComponent* EnhancedInputComponent = Cast(PlayerInputComponent)) + { + + // Jumping + EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &APlatformingCharacter::DoJumpStart); + EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &APlatformingCharacter::DoJumpEnd); + + // Moving + EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &APlatformingCharacter::Move); + EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this, &APlatformingCharacter::Look); + + // Looking + EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &APlatformingCharacter::Look); + + // Dashing + EnhancedInputComponent->BindAction(DashAction, ETriggerEvent::Triggered, this, &APlatformingCharacter::Dash); + } +} + +void APlatformingCharacter::Landed(const FHitResult& Hit) +{ + Super::Landed(Hit); + + // reset the double jump and dash flags + bHasDoubleJumped = false; + bHasDashed = false; + + // deactivate the jump trail + SetJumpTrailState(false); +} + +void APlatformingCharacter::OnMovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode /*= 0*/) +{ + Super::OnMovementModeChanged(PrevMovementMode, PreviousCustomMode); + + // are we falling? + if (GetCharacterMovement()->MovementMode == EMovementMode::MOVE_Falling) + { + // save the game time when we started falling, so we can check it later for coyote time jumps + LastFallTime = GetWorld()->GetTimeSeconds(); + } +} + diff --git a/Source/KingshearthLegacy/Variant_Platforming/PlatformingCharacter.h b/Source/KingshearthLegacy/Variant_Platforming/PlatformingCharacter.h new file mode 100644 index 0000000..1ba2a69 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Platforming/PlatformingCharacter.h @@ -0,0 +1,194 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Character.h" +#include "Animation/AnimInstance.h" +#include "PlatformingCharacter.generated.h" + + +class USpringArmComponent; +class UCameraComponent; +class UInputAction; +struct FInputActionValue; +class UAnimMontage; + +/** + * An enhanced Third Person Character with the following functionality: + * - Platforming game character movement physics + * - Press and Hold Jump + * - Double Jump + * - Wall Jump + * - Dash + */ +UCLASS(abstract) +class APlatformingCharacter : public ACharacter +{ + GENERATED_BODY() + + /** Camera boom positioning the camera behind the character */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) + USpringArmComponent* CameraBoom; + + /** Follow camera */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) + UCameraComponent* FollowCamera; + +protected: + + /** Jump Input Action */ + UPROPERTY(EditAnywhere, Category="Input") + UInputAction* JumpAction; + + /** Move Input Action */ + UPROPERTY(EditAnywhere, Category="Input") + UInputAction* MoveAction; + + /** Look Input Action */ + UPROPERTY(EditAnywhere, Category="Input") + UInputAction* LookAction; + + /** Mouse Look Input Action */ + UPROPERTY(EditAnywhere, Category="Input") + UInputAction* MouseLookAction; + + /** Dash Input Action */ + UPROPERTY(EditAnywhere, Category="Input") + UInputAction* DashAction; + +public: + + /** Constructor */ + APlatformingCharacter(); + +protected: + + /** Called for movement input */ + void Move(const FInputActionValue& Value); + + /** Called for looking input */ + void Look(const FInputActionValue& Value); + + /** Called for dash input */ + void Dash(); + + /** Called for jump pressed to check for advanced multi-jump conditions */ + void MultiJump(); + + /** Resets the wall jump input lock */ + void ResetWallJump(); + +public: + + /** Handles move inputs from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoMove(float Right, float Forward); + + /** Handles look inputs from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoLook(float Yaw, float Pitch); + + /** Handles dash inputs from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoDash(); + + /** Handles jump pressed inputs from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoJumpStart(); + + /** Handles jump pressed inputs from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoJumpEnd(); + +protected: + + /** Called from a delegate when the dash montage ends */ + void DashMontageEnded(UAnimMontage* Montage, bool bInterrupted); + + /** Passes control to Blueprint to enable or disable jump trails */ + UFUNCTION(BlueprintImplementableEvent, Category="Platforming") + void SetJumpTrailState(bool bEnabled); + +public: + + /** Ends the dash state */ + void EndDash(); + +public: + + /** Returns true if the character has just double jumped */ + UFUNCTION(BlueprintPure, Category="Platforming") + bool HasDoubleJumped() const; + + /** Returns true if the character has just wall jumped */ + UFUNCTION(BlueprintPure, Category="Platforming") + bool HasWallJumped() const; + +public: + + /** EndPlay cleanup */ + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + /** Sets up input action bindings */ + virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; + + /** Handle landings to reset dash and advanced jump state */ + virtual void Landed(const FHitResult& Hit) override; + + /** Handle movement mode changes to keep track of coyote time jumps */ + virtual void OnMovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode = 0) override; + +protected: + + /** movement state flag bits, packed into a uint8 for memory efficiency */ + uint8 bHasWallJumped : 1; + uint8 bHasDoubleJumped : 1; + uint8 bHasDashed : 1; + uint8 bIsDashing : 1; + + /** timer for wall jump input reset */ + FTimerHandle WallJumpTimer; + + /** Dash montage ended delegate */ + FOnMontageEnded OnDashMontageEnded; + + /** Distance to trace ahead of the character to look for walls to jump from */ + UPROPERTY(EditAnywhere, Category="Wall Jump", meta = (ClampMin = 0, ClampMax = 1000, Units = "cm")) + float WallJumpTraceDistance = 50.0f; + + /** Radius of the wall jump sphere trace check */ + UPROPERTY(EditAnywhere, Category="Wall Jump", meta = (ClampMin = 0, ClampMax = 100, Units = "cm")) + float WallJumpTraceRadius = 25.0f; + + /** Impulse to apply away from the wall when wall jumping */ + UPROPERTY(EditAnywhere, Category="Wall Jump", meta = (ClampMin = 0, ClampMax = 10000, Units = "cm/s")) + float WallJumpBounceImpulse = 800.0f; + + /** Vertical impulse to apply when wall jumping */ + UPROPERTY(EditAnywhere, Category="Wall Jump", meta = (ClampMin = 0, ClampMax = 10000, Units = "cm/s")) + float WallJumpVerticalImpulse = 900.0f; + + /** Time to ignore jump inputs after a wall jump */ + UPROPERTY(EditAnywhere, Category="Wall Jump", meta = (ClampMin = 0, ClampMax = 5, Units = "s")) + float DelayBetweenWallJumps = 0.1f; + + /** AnimMontage to use for the Dash action */ + UPROPERTY(EditAnywhere, Category="Dash") + UAnimMontage* DashMontage; + + /** Last recorded time when this character started falling */ + float LastFallTime = 0.0f; + + /** Max amount of time that can pass since we started falling when we allow a regular jump */ + UPROPERTY(EditAnywhere, Category="Coyote Time", meta = (ClampMin = 0, ClampMax = 5, Units = "s")) + float MaxCoyoteTime = 0.16f; + +public: + /** Returns CameraBoom subobject **/ + FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; } + + /** Returns FollowCamera subobject **/ + FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; } + +}; diff --git a/Source/KingshearthLegacy/Variant_Platforming/PlatformingGameMode.cpp b/Source/KingshearthLegacy/Variant_Platforming/PlatformingGameMode.cpp new file mode 100644 index 0000000..1b2530b --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Platforming/PlatformingGameMode.cpp @@ -0,0 +1,53 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "Variant_Platforming/PlatformingGameMode.h" +#include "Kismet/GameplayStatics.h" +#include "GameFramework/PlayerStart.h" +#include "Engine/World.h" + +APlatformingGameMode::APlatformingGameMode() +{ + // stub +} + +void APlatformingGameMode::BeginPlay() +{ + Super::BeginPlay(); + + // create each additional local player. + // Player 0 will be created automatically as part of regular game init + for (int32 i = 2; i <= NumberOfLocalPlayers; ++i) + { + UGameplayStatics::CreatePlayer(GetWorld(), -1, true); + } +} + +AActor* APlatformingGameMode::ChoosePlayerStart_Implementation(AController* Player) +{ + // build the current player tag + FName PlayerTag = FName(*FString::Printf(TEXT("Player%d"), CurrentPlayerStartAssignment)); + + // find all player starts with the matching player tag + TArray PlayerStarts; + + UGameplayStatics::GetAllActorsOfClassWithTag(GetWorld(), APlayerStart::StaticClass(), PlayerTag, PlayerStarts); + + // increment the player start assignment index + ++CurrentPlayerStartAssignment; + + // if no PlayerStarts were found, default to all PlayerStarts instead + if (PlayerStarts.IsEmpty()) + { + UGameplayStatics::GetAllActorsOfClass(GetWorld(), APlayerStart::StaticClass(), PlayerStarts); + } + + // have we found at least one PlayerStart? + if (!PlayerStarts.IsEmpty()) + { + return PlayerStarts[ FMath::RandRange(0, PlayerStarts.Num() - 1) ]; + } + + // no PlayerStarts in the level + return nullptr; +} diff --git a/Source/KingshearthLegacy/Variant_Platforming/PlatformingGameMode.h b/Source/KingshearthLegacy/Variant_Platforming/PlatformingGameMode.h new file mode 100644 index 0000000..ca49475 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Platforming/PlatformingGameMode.h @@ -0,0 +1,38 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/GameModeBase.h" +#include "PlatformingGameMode.generated.h" + +/** + * Simple GameMode for a third person platforming game + */ +UCLASS() +class APlatformingGameMode : public AGameModeBase +{ + GENERATED_BODY() + +public: + + /** Constructor */ + APlatformingGameMode(); + +protected: + + /** Initialization */ + virtual void BeginPlay() override; + + /** Assigns a PlayerStart to a specific player */ + virtual AActor* ChoosePlayerStart_Implementation(AController* Player) override; + +protected: + + /** Determines how many local players should be spawned on game start */ + UPROPERTY(EditDefaultsOnly, Category="Local Multiplayer", meta = (ClampMin = 1, ClampMax = 4)) + int32 NumberOfLocalPlayers = 1; + + /** Used to assign players to different PlayerStarts in the level */ + int32 CurrentPlayerStartAssignment = 0; +}; diff --git a/Source/KingshearthLegacy/Variant_Platforming/PlatformingPlayerController.cpp b/Source/KingshearthLegacy/Variant_Platforming/PlatformingPlayerController.cpp new file mode 100644 index 0000000..6de57a7 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Platforming/PlatformingPlayerController.cpp @@ -0,0 +1,98 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "Variant_Platforming/PlatformingPlayerController.h" +#include "EnhancedInputSubsystems.h" +#include "InputMappingContext.h" +#include "Kismet/GameplayStatics.h" +#include "GameFramework/PlayerStart.h" +#include "PlatformingCharacter.h" +#include "Engine/LocalPlayer.h" +#include "Engine/World.h" +#include "Blueprint/UserWidget.h" +#include "KingshearthLegacy.h" +#include "Widgets/Input/SVirtualJoystick.h" + +void APlatformingPlayerController::BeginPlay() +{ + Super::BeginPlay(); +} + +void APlatformingPlayerController::SetupInputComponent() +{ + Super::SetupInputComponent(); + + // only add IMCs for local player controllers + if (IsLocalPlayerController()) + { + // add the input mapping context + if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem(GetLocalPlayer())) + { + for (UInputMappingContext* CurrentContext : DefaultMappingContexts) + { + Subsystem->AddMappingContext(CurrentContext, 0); + } + + // only add these IMCs if we're not using mobile touch input + if (!ShouldUseTouchControls()) + { + for (UInputMappingContext* CurrentContext : MobileExcludedMappingContexts) + { + Subsystem->AddMappingContext(CurrentContext, 0); + } + } + } + } + + // only spawn touch controls on local player controllers + if (IsLocalPlayerController() && ShouldUseTouchControls()) + { + // spawn the mobile controls widget + MobileControlsWidget = CreateWidget(this, MobileControlsWidgetClass); + + if (MobileControlsWidget) + { + // add the controls to the player screen + MobileControlsWidget->AddToPlayerScreen(0); + + } else { + + UE_LOG(LogKingshearthLegacy, Error, TEXT("Could not spawn mobile controls widget.")); + + } + + } +} + +void APlatformingPlayerController::OnPossess(APawn* InPawn) +{ + Super::OnPossess(InPawn); + + // subscribe to the pawn's OnDestroyed delegate + InPawn->OnDestroyed.AddDynamic(this, &APlatformingPlayerController::OnPawnDestroyed); +} + +void APlatformingPlayerController::OnPawnDestroyed(AActor* DestroyedActor) +{ + // find the player start + TArray ActorList; + UGameplayStatics::GetAllActorsOfClass(GetWorld(), APlayerStart::StaticClass(), ActorList); + + if (ActorList.Num() > 0) + { + // spawn a character at the player start + const FTransform SpawnTransform = ActorList[0]->GetActorTransform(); + + if (APlatformingCharacter* RespawnedCharacter = GetWorld()->SpawnActor(CharacterClass, SpawnTransform)) + { + // possess the character + Possess(RespawnedCharacter); + } + } +} + +bool APlatformingPlayerController::ShouldUseTouchControls() const +{ + // are we on a mobile platform? Should we force touch? + return SVirtualJoystick::ShouldDisplayTouchInterface() || bForceTouchControls; +} diff --git a/Source/KingshearthLegacy/Variant_Platforming/PlatformingPlayerController.h b/Source/KingshearthLegacy/Variant_Platforming/PlatformingPlayerController.h new file mode 100644 index 0000000..64f8420 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_Platforming/PlatformingPlayerController.h @@ -0,0 +1,65 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/PlayerController.h" +#include "PlatformingPlayerController.generated.h" + +class UInputMappingContext; +class APlatformingCharacter; + +/** + * Simple Player Controller for a third person platforming game + * Manages input mappings + * Respawns the player character at the Player Start when it's destroyed + */ +UCLASS(abstract, Config="Game") +class APlatformingPlayerController : public APlayerController +{ + GENERATED_BODY() + +protected: + + /** Input mapping context for this player */ + UPROPERTY(EditAnywhere, Category="Input|Input Mappings") + TArray DefaultMappingContexts; + + /** Input Mapping Contexts */ + UPROPERTY(EditAnywhere, Category="Input|Input Mappings") + TArray MobileExcludedMappingContexts; + + /** Mobile controls widget to spawn */ + UPROPERTY(EditAnywhere, Category="Input|Touch Controls") + TSubclassOf MobileControlsWidgetClass; + + /** Pointer to the mobile controls widget */ + UPROPERTY() + TObjectPtr MobileControlsWidget; + + /** If true, the player will use UMG touch controls even if not playing on mobile platforms */ + UPROPERTY(EditAnywhere, Config, Category = "Input|Touch Controls") + bool bForceTouchControls = false; + + /** Character class to respawn when the possessed pawn is destroyed */ + UPROPERTY(EditAnywhere, Category="Respawn") + TSubclassOf CharacterClass; + +protected: + + /** Gameplay initialization */ + virtual void BeginPlay() override; + + /** Initialize input bindings */ + virtual void SetupInputComponent() override; + + /** Pawn initialization */ + virtual void OnPossess(APawn* InPawn) override; + + /** Called if the possessed pawn is destroyed */ + UFUNCTION() + void OnPawnDestroyed(AActor* DestroyedActor); + + /** Returns true if the player should use UMG touch controls */ + bool ShouldUseTouchControls() const; +}; diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/AI/SideScrollingAIController.cpp b/Source/KingshearthLegacy/Variant_SideScrolling/AI/SideScrollingAIController.cpp new file mode 100644 index 0000000..e025986 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/AI/SideScrollingAIController.cpp @@ -0,0 +1,19 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "SideScrollingAIController.h" +#include "GameplayStateTreeModule/Public/Components/StateTreeAIComponent.h" + +ASideScrollingAIController::ASideScrollingAIController() +{ + // create the StateTree AI Component + StateTreeAI = CreateDefaultSubobject(TEXT("StateTreeAI")); + check(StateTreeAI); + + // ensure we start the StateTree when we possess the pawn + bStartAILogicOnPossess = true; + + // ensure we're attached to the possessed character. + // this is necessary for EnvQueries to work correctly + bAttachToPawn = true; +} diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/AI/SideScrollingAIController.h b/Source/KingshearthLegacy/Variant_SideScrolling/AI/SideScrollingAIController.h new file mode 100644 index 0000000..f314e90 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/AI/SideScrollingAIController.h @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AIController.h" +#include "SideScrollingAIController.generated.h" + +class UStateTreeAIComponent; + +/** + * A basic AI Controller capable of running StateTree + */ +UCLASS(abstract) +class ASideScrollingAIController : public AAIController +{ + GENERATED_BODY() + + /** StateTree Component */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI", meta = (AllowPrivateAccess = "true")) + UStateTreeAIComponent* StateTreeAI; + +public: + + /** Constructor */ + ASideScrollingAIController(); +}; diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/AI/SideScrollingNPC.cpp b/Source/KingshearthLegacy/Variant_SideScrolling/AI/SideScrollingNPC.cpp new file mode 100644 index 0000000..1b5b26c --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/AI/SideScrollingNPC.cpp @@ -0,0 +1,53 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "SideScrollingNPC.h" +#include "Engine/World.h" +#include "GameFramework/CharacterMovementComponent.h" +#include "TimerManager.h" + +ASideScrollingNPC::ASideScrollingNPC() +{ + PrimaryActorTick.bCanEverTick = true; + + GetCharacterMovement()->MaxWalkSpeed = 150.0f; +} + +void ASideScrollingNPC::EndPlay(EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + // clear the deactivation timer + GetWorld()->GetTimerManager().ClearTimer(DeactivationTimer); +} + +void ASideScrollingNPC::Interaction(AActor* Interactor) +{ + // ignore if this NPC has already been deactivated + if (bDeactivated) + { + return; + } + + // reset the deactivation flag + bDeactivated = true; + + // stop character movement immediately + GetCharacterMovement()->StopMovementImmediately(); + + // launch the NPC away from the interactor + FVector LaunchVector = Interactor->GetActorForwardVector() * LaunchImpulse; + LaunchVector.Y = 0.0f; + LaunchVector.Z = LaunchVerticalImpulse; + + LaunchCharacter(LaunchVector, true, true); + + // set up a timer to schedule reactivation + GetWorld()->GetTimerManager().SetTimer(DeactivationTimer, this, &ASideScrollingNPC::ResetDeactivation, DeactivationTime, false); +} + +void ASideScrollingNPC::ResetDeactivation() +{ + // reset the deactivation flag + bDeactivated = false; +} diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/AI/SideScrollingNPC.h b/Source/KingshearthLegacy/Variant_SideScrolling/AI/SideScrollingNPC.h new file mode 100644 index 0000000..ee47560 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/AI/SideScrollingNPC.h @@ -0,0 +1,64 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Character.h" +#include "SideScrollingInteractable.h" +#include "SideScrollingNPC.generated.h" + +/** + * Simple platforming NPC + * Its behaviors will be dictated by a possessing AI Controller + * It can be temporarily deactivated through Actor interactions + */ +UCLASS(abstract) +class ASideScrollingNPC : public ACharacter, public ISideScrollingInteractable +{ + GENERATED_BODY() + +protected: + + /** Horizontal impulse to apply to the NPC when it's interacted with */ + UPROPERTY(EditAnywhere, Category="NPC", meta = (ClampMin = 0, ClampMax = 10000, Units="cm/s")) + float LaunchImpulse = 500.0f; + + /** Vertical impulse to apply to the NPC when it's interacted with */ + UPROPERTY(EditAnywhere, Category="NPC", meta = (ClampMin = 0, ClampMax = 10000, Units="cm/s")) + float LaunchVerticalImpulse = 500.0f; + + /** Time that the NPC remains deactivated after being interacted with */ + UPROPERTY(EditAnywhere, Category="NPC", meta = (ClampMin = 0, ClampMax = 10, Units="s")) + float DeactivationTime = 3.0f; + +public: + + /** If true, this NPC is deactivated and will not be interacted with */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="NPC") + bool bDeactivated = false; + + /** Timer to reactivate the NPC */ + FTimerHandle DeactivationTimer; + +public: + + /** Constructor */ + ASideScrollingNPC(); + +public: + + /** Cleanup */ + virtual void EndPlay(EEndPlayReason::Type EndPlayReason) override; + +public: + +// ~begin IInteractable interface + + /** Performs an interaction triggered by another actor */ + virtual void Interaction(AActor* Interactor) override; + +// ~end IInteractable interface + + /** Reactivates the NPC */ + void ResetDeactivation(); +}; diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/AI/SideScrollingStateTreeUtility.cpp b/Source/KingshearthLegacy/Variant_SideScrolling/AI/SideScrollingStateTreeUtility.cpp new file mode 100644 index 0000000..bd3baca --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/AI/SideScrollingStateTreeUtility.cpp @@ -0,0 +1,59 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "SideScrollingStateTreeUtility.h" +#include "StateTreeExecutionContext.h" +#include "StateTreeExecutionTypes.h" +#include "AIController.h" +#include "Kismet/GameplayStatics.h" + +EStateTreeRunStatus FStateTreeGetPlayerTask::EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const +{ + // get the instance data + FInstanceDataType& InstanceData = Context.GetInstanceData(*this); + + // reset the selected target + APawn* SelectedTarget = nullptr; + float ClosestDistance = 1000000000000000000.0f; + + // iterate through each local player + const int32 NumPlayers = UGameplayStatics::GetNumLocalPlayerControllers(InstanceData.Controller.Get()); + + for (int32 i = 0; i < NumPlayers; ++i) + { + if (APawn* Current = UGameplayStatics::GetPlayerPawn(InstanceData.Controller.Get(), i)) + { + // compute the distance to the target + const float TargetDist = (Current->GetActorLocation() - InstanceData.NPC->GetActorLocation()).Size(); + + // if we haven't selected a target, or this one is closer to the current one, choose this pawn as target + if (!SelectedTarget || TargetDist < ClosestDistance) + { + SelectedTarget = Current; + ClosestDistance = TargetDist; + } + } + } + + // set the selected target, assume out of range by default + InstanceData.TargetPlayer = SelectedTarget; + InstanceData.bValidTarget = false; + + if (SelectedTarget) + { + // consider this a valid target if it's within range + const float TargetDist = (SelectedTarget->GetActorLocation() - InstanceData.NPC->GetActorLocation()).Size(); + + InstanceData.bValidTarget = TargetDist < InstanceData.RangeMax; + } + + // succeed or fail depending on target validity + return InstanceData.bValidTarget ? EStateTreeRunStatus::Succeeded : EStateTreeRunStatus::Failed; +} + +#if WITH_EDITOR +FText FStateTreeGetPlayerTask::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting /*= EStateTreeNodeFormatting::Text*/) const +{ + return FText::FromString("Get Player"); +} +#endif // WITH_EDITOR \ No newline at end of file diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/AI/SideScrollingStateTreeUtility.h b/Source/KingshearthLegacy/Variant_SideScrolling/AI/SideScrollingStateTreeUtility.h new file mode 100644 index 0000000..bca6267 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/AI/SideScrollingStateTreeUtility.h @@ -0,0 +1,68 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "StateTreeTaskBase.h" + +#include "SideScrollingStateTreeUtility.generated.h" + +class AAIController; + +/** + * Instance data for the FStateTreeGetPlayerTask task + */ +USTRUCT() +struct FStateTreeGetPlayerInstanceData +{ + GENERATED_BODY() + + /** NPC owning this task */ + UPROPERTY(VisibleAnywhere, Category="Context") + TObjectPtr NPC; + + /** Holds the found player pawn */ + UPROPERTY(VisibleAnywhere, Category="Context") + TObjectPtr Controller; + + /** Holds the found player pawn */ + UPROPERTY(VisibleAnywhere, Category="Output") + TObjectPtr TargetPlayer; + + /** Is the pawn close enough to be considered a valid target? */ + UPROPERTY(VisibleAnywhere, Category="Output") + bool bValidTarget = false; + + /** Max distance to be considered a valid target */ + UPROPERTY(EditAnywhere, Category="Parameter", meta = (ClampMin = 0, ClampMax = 10000, Units = "cm")) + float RangeMax = 1000.0f; +}; + +/** + * StateTree task to get the player-controlled character + */ +USTRUCT(meta=(DisplayName="Get Player", Category="Side Scrolling")) +struct FStateTreeGetPlayerTask : public FStateTreeTaskCommonBase +{ + GENERATED_BODY() + + FStateTreeGetPlayerTask() + { + // disable tick + bShouldCallTick = false; + + // skip state change events if this is sustained + bShouldStateChangeOnReselect = false; + } + + /* Ensure we're using the correct instance data struct */ + using FInstanceDataType = FStateTreeGetPlayerInstanceData; + virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); } + + /** Runs when the owning state is entered */ + virtual EStateTreeRunStatus EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override; + +#if WITH_EDITOR + virtual FText GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting = EStateTreeNodeFormatting::Text) const override; +#endif // WITH_EDITOR +}; \ No newline at end of file diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingJumpPad.cpp b/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingJumpPad.cpp new file mode 100644 index 0000000..d98c665 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingJumpPad.cpp @@ -0,0 +1,46 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "SideScrollingJumpPad.h" +#include "Components/BoxComponent.h" +#include "GameFramework/Character.h" +#include "GameFramework/CharacterMovementComponent.h" +#include "Components/SceneComponent.h" + +ASideScrollingJumpPad::ASideScrollingJumpPad() +{ + PrimaryActorTick.bCanEverTick = false; + + // create the root comp + RootComponent = CreateDefaultSubobject(TEXT("Root")); + + // create the bounding box + Box = CreateDefaultSubobject(TEXT("Box")); + Box->SetupAttachment(RootComponent); + + // configure the bounding box + Box->SetBoxExtent(FVector(115.0f, 90.0f, 20.0f), false); + Box->SetRelativeLocation(FVector(0.0f, 0.0f, 16.0f)); + + Box->SetCollisionObjectType(ECC_WorldDynamic); + Box->SetCollisionEnabled(ECollisionEnabled::QueryOnly); + Box->SetCollisionResponseToAllChannels(ECR_Ignore); + Box->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap); + + // add the overlap handler + OnActorBeginOverlap.AddDynamic(this, &ASideScrollingJumpPad::BeginOverlap); +} + +void ASideScrollingJumpPad::BeginOverlap(AActor* OverlappedActor, AActor* OtherActor) +{ + // were we overlapped by a character? + if (ACharacter* OverlappingCharacter = Cast(OtherActor)) + { + // force the character to jump + OverlappingCharacter->Jump(); + + // launch the character to override its vertical velocity + FVector LaunchVelocity = FVector::UpVector * ZStrength; + OverlappingCharacter->LaunchCharacter(LaunchVelocity, false, true); + } +} diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingJumpPad.h b/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingJumpPad.h new file mode 100644 index 0000000..c503b4b --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingJumpPad.h @@ -0,0 +1,39 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "SideScrollingJumpPad.generated.h" + +class UBoxComponent; + +/** + * A simple jump pad that launches characters into the air + */ +UCLASS(abstract) +class ASideScrollingJumpPad : public AActor +{ + GENERATED_BODY() + + /** Jump pad bounding box */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) + UBoxComponent* Box; + +protected: + + /** Vertical velocity to set the character to when they use the jump pad */ + UPROPERTY(EditAnywhere, Category="Jump Pad", meta = (ClampMin=0, ClampMax=10000, Units="cm/s")) + float ZStrength = 1000.0f; + +public: + + /** Constructor */ + ASideScrollingJumpPad(); + +protected: + + UFUNCTION() + void BeginOverlap(AActor* OverlappedActor, AActor* OtherActor); + +}; diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingMovingPlatform.cpp b/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingMovingPlatform.cpp new file mode 100644 index 0000000..7c5b7a8 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingMovingPlatform.cpp @@ -0,0 +1,40 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "SideScrollingMovingPlatform.h" +#include "Components/SceneComponent.h" + +ASideScrollingMovingPlatform::ASideScrollingMovingPlatform() +{ + PrimaryActorTick.bCanEverTick = false; + + // create the root comp + RootComponent = CreateDefaultSubobject(TEXT("Root")); +} + +void ASideScrollingMovingPlatform::Interaction(AActor* Interactor) +{ + // ignore interactions if we're already moving + if (bMoving) + { + return; + } + + // raise the movement flag + bMoving = true; + + // pass control to BP for the actual movement + BP_MoveToTarget(); +} + +void ASideScrollingMovingPlatform::ResetInteraction() +{ + // ignore if this is a one-shot platform + if (bOneShot) + { + return; + } + + // reset the movement flag + bMoving = false; +} diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingMovingPlatform.h b/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingMovingPlatform.h new file mode 100644 index 0000000..6b89732 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingMovingPlatform.h @@ -0,0 +1,60 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "SideScrollingInteractable.h" +#include "SideScrollingMovingPlatform.generated.h" + +/** + * Simple moving platform that can be triggered through interactions by other actors. + * The actual movement is performed by Blueprint code through latent execution nodes. + */ +UCLASS(abstract) +class ASideScrollingMovingPlatform : public AActor, public ISideScrollingInteractable +{ + GENERATED_BODY() + +public: + + /** Constructor */ + ASideScrollingMovingPlatform(); + +protected: + + /** If this is true, the platform is mid-movement and will ignore further interactions */ + bool bMoving = false; + + /** Destination of the platform in world space */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Moving Platform") + FVector PlatformTarget; + + /** Time for the platform to move to the destination */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Moving Platform", meta = (ClampMin = 0, ClampMax = 10, Units="s")) + float MoveDuration = 5.0f; + + /** If this is true, the platform will only move once. */ + UPROPERTY(EditAnywhere, Category="Moving Platform") + bool bOneShot = false; + +public: + +// ~begin IInteractable interface + + /** Performs an interaction triggered by another actor */ + virtual void Interaction(AActor* Interactor) override; + +// ~end IInteractable interface + + /** Resets the interaction state. Must be called from BP code to reset the platform */ + UFUNCTION(BlueprintCallable, Category="Moving Platform") + virtual void ResetInteraction(); + +protected: + + /** Allows Blueprint code to do the actual platform movement */ + UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category="Moving Platform", meta = (DisplayName="Move to Target")) + void BP_MoveToTarget(); + +}; diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingPickup.cpp b/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingPickup.cpp new file mode 100644 index 0000000..bbe3753 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingPickup.cpp @@ -0,0 +1,55 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "SideScrollingPickup.h" +#include "GameFramework/Character.h" +#include "SideScrollingGameMode.h" +#include "Components/SphereComponent.h" +#include "Components/SceneComponent.h" +#include "Engine/World.h" + +ASideScrollingPickup::ASideScrollingPickup() +{ + PrimaryActorTick.bCanEverTick = false; + + // create the root comp + RootComponent = CreateDefaultSubobject(TEXT("Root")); + + // create the bounding sphere + Sphere = CreateDefaultSubobject(TEXT("Collision")); + Sphere->SetupAttachment(RootComponent); + + Sphere->SetSphereRadius(100.0f); + + Sphere->SetCollisionObjectType(ECC_WorldDynamic); + Sphere->SetCollisionEnabled(ECollisionEnabled::QueryOnly); + Sphere->SetCollisionResponseToAllChannels(ECR_Ignore); + Sphere->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap); + + // add the overlap handler + OnActorBeginOverlap.AddDynamic(this, &ASideScrollingPickup::BeginOverlap); +} + +void ASideScrollingPickup::BeginOverlap(AActor* OverlappedActor, AActor* OtherActor) +{ + // have we collided against a character? + if (ACharacter* OverlappedCharacter = Cast(OtherActor)) + { + // is this the player character? + if (OverlappedCharacter->IsPlayerControlled()) + { + // get the game mode + if (ASideScrollingGameMode* GM = Cast(GetWorld()->GetAuthGameMode())) + { + // tell the game mode to process a pickup + GM->ProcessPickup(); + + // disable collision so we don't get picked up again + SetActorEnableCollision(false); + + // Call the BP handler. It will be responsible for destroying the pickup + BP_OnPickedUp(); + } + } + } +} \ No newline at end of file diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingPickup.h b/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingPickup.h new file mode 100644 index 0000000..80e80d0 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingPickup.h @@ -0,0 +1,38 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "SideScrollingPickup.generated.h" + +class USphereComponent; + +/** + * A simple side scrolling game pickup + * Increments a counter on the GameMode + */ +UCLASS(abstract) +class ASideScrollingPickup : public AActor +{ + GENERATED_BODY() + + /** Pickup bounding sphere */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category ="Components", meta = (AllowPrivateAccess = "true")) + USphereComponent* Sphere; + +public: + + /** Constructor */ + ASideScrollingPickup(); + +protected: + + /** Handles pickup collision */ + UFUNCTION() + void BeginOverlap(AActor* OverlappedActor, AActor* OtherActor); + + /** Passes control to BP to play effects on pickup */ + UFUNCTION(BlueprintImplementableEvent, Category="Pickup", meta = (DisplayName = "On Picked Up")) + void BP_OnPickedUp(); +}; diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingSoftPlatform.cpp b/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingSoftPlatform.cpp new file mode 100644 index 0000000..81f9139 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingSoftPlatform.cpp @@ -0,0 +1,59 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "SideScrollingSoftPlatform.h" +#include "Components/SceneComponent.h" +#include "Components/StaticMeshComponent.h" +#include "Components/BoxComponent.h" +#include "SideScrollingCharacter.h" + +ASideScrollingSoftPlatform::ASideScrollingSoftPlatform() +{ + PrimaryActorTick.bCanEverTick = true; + + // create the root component + RootComponent = Root = CreateDefaultSubobject(TEXT("Root")); + + // create the mesh + Mesh = CreateDefaultSubobject(TEXT("Mesh")); + Mesh->SetupAttachment(Root); + + Mesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); + Mesh->SetCollisionObjectType(ECC_WorldStatic); + Mesh->SetCollisionResponseToAllChannels(ECR_Block); + + // create the collision check box + CollisionCheckBox = CreateDefaultSubobject(TEXT("Collision Check Box")); + CollisionCheckBox->SetupAttachment(Mesh); + + CollisionCheckBox->SetRelativeLocation(FVector(0.0f, 0.0f, -40.0f)); + CollisionCheckBox->SetCollisionEnabled(ECollisionEnabled::QueryOnly); + CollisionCheckBox->SetCollisionObjectType(ECC_WorldDynamic); + CollisionCheckBox->SetCollisionResponseToAllChannels(ECR_Ignore); + CollisionCheckBox->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap); + + // subscribe to the overlap events + CollisionCheckBox->OnComponentBeginOverlap.AddDynamic(this, &ASideScrollingSoftPlatform::OnSoftCollisionOverlap); +} + +void ASideScrollingSoftPlatform::OnSoftCollisionOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) +{ + // have we overlapped a character? + if (ASideScrollingCharacter* Char = Cast(OtherActor)) + { + // disable the soft collision channel + Char->SetSoftCollision(true); + } +} + +void ASideScrollingSoftPlatform::NotifyActorEndOverlap(AActor* OtherActor) +{ + Super::NotifyActorEndOverlap(OtherActor); + + // have we overlapped a character? + if (ASideScrollingCharacter* Char = Cast(OtherActor)) + { + // enable the soft collision channel + Char->SetSoftCollision(false); + } +} diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingSoftPlatform.h b/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingSoftPlatform.h new file mode 100644 index 0000000..04ed012 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/Gameplay/SideScrollingSoftPlatform.h @@ -0,0 +1,46 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "SideScrollingSoftPlatform.generated.h" + +class USceneComponent; +class UStaticMeshComponent; +class UBoxComponent; + +/** + * A side scrolling game platform that the character can jump or drop through. + */ +UCLASS(abstract) +class ASideScrollingSoftPlatform : public AActor +{ + GENERATED_BODY() + + /** Root component */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category ="Components", meta = (AllowPrivateAccess = "true")) + USceneComponent* Root; + + /** Platform mesh. The part we collide against and see */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category ="Components", meta = (AllowPrivateAccess = "true")) + UStaticMeshComponent* Mesh; + + /** Collision volume that toggles soft collision on the character when they're below the platform. */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category ="Components", meta = (AllowPrivateAccess = "true")) + UBoxComponent* CollisionCheckBox; + +public: + + /** Constructor */ + ASideScrollingSoftPlatform(); + +protected: + + /** Handles soft collision check box overlaps */ + UFUNCTION() + void OnSoftCollisionOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); + + /** Restores soft collision state when overlap ends */ + virtual void NotifyActorEndOverlap(AActor* OtherActor) override; +}; diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/Interfaces/SideScrollingInteractable.cpp b/Source/KingshearthLegacy/Variant_SideScrolling/Interfaces/SideScrollingInteractable.cpp new file mode 100644 index 0000000..d44da2b --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/Interfaces/SideScrollingInteractable.cpp @@ -0,0 +1,6 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "SideScrollingInteractable.h" + +// Add default functionality here for any IInteractable functions that are not pure virtual. diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/Interfaces/SideScrollingInteractable.h b/Source/KingshearthLegacy/Variant_SideScrolling/Interfaces/SideScrollingInteractable.h new file mode 100644 index 0000000..05d43d1 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/Interfaces/SideScrollingInteractable.h @@ -0,0 +1,31 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" +#include "SideScrollingInteractable.generated.h" + +/** + * + */ +UINTERFACE(MinimalAPI, NotBlueprintable) +class USideScrollingInteractable : public UInterface +{ + GENERATED_BODY() +}; + +/** + * Simple interface to allow Actors to interact without having knowledge of their internal implementation. + */ +class ISideScrollingInteractable +{ + GENERATED_BODY() + +public: + + /** Triggers an interaction by the provided Actor */ + UFUNCTION(BlueprintCallable, Category="Interactable") + virtual void Interaction(AActor* Interactor) = 0; + +}; diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingCameraManager.cpp b/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingCameraManager.cpp new file mode 100644 index 0000000..9aa30ac --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingCameraManager.cpp @@ -0,0 +1,105 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "SideScrollingCameraManager.h" +#include "GameFramework/Pawn.h" +#include "Engine/HitResult.h" +#include "CollisionQueryParams.h" +#include "Engine/World.h" + +void ASideScrollingCameraManager::UpdateViewTarget(FTViewTarget& OutVT, float DeltaTime) +{ + // ensure the view target is a pawn + APawn* TargetPawn = Cast(OutVT.Target); + + // is our target valid? + if (IsValid(TargetPawn)) + { + // set the view target FOV and rotation + OutVT.POV.Rotation = FRotator(0.0f, -90.0f, 0.0f); + OutVT.POV.FOV = 65.0f; + + // cache the current location + FVector CurrentActorLocation = OutVT.Target->GetActorLocation(); + + // copy the current camera location + FVector CurrentCameraLocation = GetCameraLocation(); + + // calculate the "zoom distance" - in reality the distance we want to keep to the target + float CurrentY = CurrentZoom + CurrentActorLocation.Y; + + // do first-time setup + if (bSetup) + { + // lower the setup flag + bSetup = false; + + // initialize the camera viewpoint and return + OutVT.POV.Location.X = CurrentActorLocation.X; + OutVT.POV.Location.Y = CurrentY; + OutVT.POV.Location.Z = CurrentActorLocation.Z + CameraZOffset; + + // save the current camera height + CurrentZ = OutVT.POV.Location.Z; + + // skip the rest of the calculations + return; + } + + // check if the camera needs to update its height + bool bZUpdate = false; + + // is the character moving vertically? + if (FMath::IsNearlyZero(TargetPawn->GetVelocity().Z)) + { + // determine if we need to do a height update + bZUpdate = FMath::IsNearlyEqual(CurrentZ, CurrentCameraLocation.Z, 25.0f); + + } else { + + // run a trace below the character to determine if we need to do a height update + FHitResult OutHit; + + const FVector End = CurrentActorLocation + FVector(0.0f, 0.0f, -1000.0f); + + FCollisionQueryParams QueryParams; + QueryParams.AddIgnoredActor(TargetPawn); + + // only update height if we're not about to hit ground + bZUpdate = !GetWorld()->LineTraceSingleByChannel(OutHit, CurrentActorLocation, End, ECC_Visibility, QueryParams); + + } + + // do we need to do a height update? + if (bZUpdate) + { + + // set the height goal from the actor location + CurrentZ = CurrentActorLocation.Z; + + } else { + + // are we close enough to the target height? + if (FMath::IsNearlyEqual(CurrentZ, CurrentActorLocation.Z, 100.0f)) + { + // set the height goal from the actor location + CurrentZ = CurrentActorLocation.Z; + + } else { + + // blend the height towards the actor location + CurrentZ = FMath::FInterpTo(CurrentZ, CurrentActorLocation.Z, DeltaTime, 2.0f); + + } + + } + + // clamp the X axis to the min and max camera bounds + float CurrentX = FMath::Clamp(CurrentActorLocation.X, CameraXMinBounds, CameraXMaxBounds); + + // blend towards the new camera location and update the output + FVector TargetCameraLocation(CurrentX, CurrentY, CurrentZ); + + OutVT.POV.Location = FMath::VInterpTo(CurrentCameraLocation, TargetCameraLocation, DeltaTime, 2.0f); + } +} \ No newline at end of file diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingCameraManager.h b/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingCameraManager.h new file mode 100644 index 0000000..7286306 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingCameraManager.h @@ -0,0 +1,47 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Camera/PlayerCameraManager.h" +#include "SideScrollingCameraManager.generated.h" + +/** + * Simple side scrolling camera with smooth scrolling and horizontal bounds + */ +UCLASS() +class ASideScrollingCameraManager : public APlayerCameraManager +{ + GENERATED_BODY() + +public: + + /** Overrides the default camera view target calculation */ + virtual void UpdateViewTarget(FTViewTarget& OutVT, float DeltaTime) override; + +public: + + /** How close we want to stay to the view target */ + UPROPERTY(EditAnywhere, Category="Side Scrolling Camera", meta=(ClampMin=0, ClampMax=10000, Units="cm")) + float CurrentZoom = 1000.0f; + + /** How far above the target do we want the camera to focus */ + UPROPERTY(EditAnywhere, Category="Side Scrolling Camera", meta=(ClampMin=0, ClampMax=10000, Units="cm")) + float CameraZOffset = 100.0f; + + /** Minimum camera scrolling bounds in world space */ + UPROPERTY(EditAnywhere, Category="Side Scrolling Camera", meta=(ClampMin=-100000, ClampMax=100000, Units="cm")) + float CameraXMinBounds = -400.0f; + + /** Maximum camera scrolling bounds in world space */ + UPROPERTY(EditAnywhere, Category="Side Scrolling Camera", meta=(ClampMin=-100000, ClampMax=100000, Units="cm")) + float CameraXMaxBounds = 10000.0f; + +protected: + + /** Last cached camera vertical location. The camera only adjusts its height if necessary. */ + float CurrentZ = 0.0f; + + /** First-time update camera setup flag */ + bool bSetup = true; +}; diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingCharacter.cpp b/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingCharacter.cpp new file mode 100644 index 0000000..b11faa5 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingCharacter.cpp @@ -0,0 +1,350 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "SideScrollingCharacter.h" +#include "GameFramework/CharacterMovementComponent.h" +#include "Components/CapsuleComponent.h" +#include "Camera/CameraComponent.h" +#include "Components/InputComponent.h" +#include "InputActionValue.h" +#include "EnhancedInputComponent.h" +#include "InputAction.h" +#include "Engine/World.h" +#include "SideScrollingInteractable.h" +#include "Kismet/KismetMathLibrary.h" +#include "TimerManager.h" + +ASideScrollingCharacter::ASideScrollingCharacter() +{ + PrimaryActorTick.bCanEverTick = true; + + // create the camera component + Camera = CreateDefaultSubobject(TEXT("Camera")); + Camera->SetupAttachment(RootComponent); + + Camera->SetRelativeLocationAndRotation(FVector(0.0f, 300.0f, 0.0f), FRotator(0.0f, -90.0f, 0.0f)); + + // configure the collision capsule + GetCapsuleComponent()->SetCapsuleSize(35.0f, 90.0f); + + // configure the Pawn properties + bUseControllerRotationYaw = false; + + // configure the character movement component + GetCharacterMovement()->GravityScale = 1.75f; + GetCharacterMovement()->MaxAcceleration = 1500.0f; + GetCharacterMovement()->BrakingFrictionFactor = 1.0f; + GetCharacterMovement()->bUseSeparateBrakingFriction = true; + GetCharacterMovement()->Mass = 500.0f; + + GetCharacterMovement()->SetWalkableFloorAngle(75.0f); + GetCharacterMovement()->MaxWalkSpeed = 500.0f; + GetCharacterMovement()->MinAnalogWalkSpeed = 20.0f; + GetCharacterMovement()->BrakingDecelerationWalking = 2000.0f; + GetCharacterMovement()->bIgnoreBaseRotation = true; + + GetCharacterMovement()->PerchRadiusThreshold = 15.0f; + GetCharacterMovement()->LedgeCheckThreshold = 6.0f; + + GetCharacterMovement()->JumpZVelocity = 750.0f; + GetCharacterMovement()->AirControl = 1.0f; + + GetCharacterMovement()->RotationRate = FRotator(0.0f, 750.0f, 0.0f); + GetCharacterMovement()->bOrientRotationToMovement = true; + + GetCharacterMovement()->SetPlaneConstraintNormal(FVector(0.0f, 1.0f, 0.0f)); + GetCharacterMovement()->bConstrainToPlane = true; + + // enable double jump and coyote time + JumpMaxCount = 3; +} + +void ASideScrollingCharacter::EndPlay(EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + // clear the wall jump timer + GetWorld()->GetTimerManager().ClearTimer(WallJumpTimer); +} + +void ASideScrollingCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) +{ + Super::SetupPlayerInputComponent(PlayerInputComponent); + + // Set up action bindings + if (UEnhancedInputComponent* EnhancedInputComponent = Cast(PlayerInputComponent)) + { + // Jumping + EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ASideScrollingCharacter::DoJumpStart); + EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ASideScrollingCharacter::DoJumpEnd); + + // Interacting + EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Triggered, this, &ASideScrollingCharacter::DoInteract); + + // Moving + EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ASideScrollingCharacter::Move); + + // Dropping from platform + EnhancedInputComponent->BindAction(DropAction, ETriggerEvent::Triggered, this, &ASideScrollingCharacter::Drop); + EnhancedInputComponent->BindAction(DropAction, ETriggerEvent::Completed, this, &ASideScrollingCharacter::DropReleased); + + } +} + +void ASideScrollingCharacter::NotifyHit(class UPrimitiveComponent* MyComp, AActor* Other, class UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit) +{ + Super::NotifyHit(MyComp, Other, OtherComp, bSelfMoved, HitLocation, HitNormal, NormalImpulse, Hit); + + // only apply push impulse if we're falling + if (!GetCharacterMovement()->IsFalling()) + { + return; + } + + // ensure the colliding component is valid + if (OtherComp) + { + // ensure the component is movable and simulating physics + if (OtherComp->Mobility == EComponentMobility::Movable && OtherComp->IsSimulatingPhysics()) + { + const FVector PushDir = FVector(ActionValueY > 0.0f ? 1.0f : -1.0f, 0.0f, 0.0f); + + // push the component away + OtherComp->AddImpulse(PushDir * JumpPushImpulse, NAME_None, true); + } + } +} + +void ASideScrollingCharacter::Landed(const FHitResult& Hit) +{ + // reset the double jump + bHasDoubleJumped = false; +} + +void ASideScrollingCharacter::OnMovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode /*= 0*/) +{ + Super::OnMovementModeChanged(PrevMovementMode, PreviousCustomMode); + + // are we falling? + if (GetCharacterMovement()->MovementMode == EMovementMode::MOVE_Falling) + { + // save the game time when we started falling, so we can check it later for coyote time jumps + LastFallTime = GetWorld()->GetTimeSeconds(); + } +} + +void ASideScrollingCharacter::Move(const FInputActionValue& Value) +{ + FVector2D MoveVector = Value.Get(); + + // route the input + DoMove(MoveVector.Y); +} + +void ASideScrollingCharacter::Drop(const FInputActionValue& Value) +{ + // route the input + DoDrop(Value.Get()); +} + +void ASideScrollingCharacter::DropReleased(const FInputActionValue& Value) +{ + // reset the input + DoDrop(0.0f); +} + +void ASideScrollingCharacter::DoMove(float Forward) +{ + // is movement temporarily disabled after wall jumping? + if (!bHasWallJumped) + { + // save the movement values + ActionValueY = Forward; + + // figure out the movement direction + const FVector MoveDir = FVector(1.0f, Forward > 0.0f ? 0.1f : -0.1f, 0.0f); + + // apply the movement input + AddMovementInput(MoveDir, Forward); + } +} + +void ASideScrollingCharacter::DoDrop(float Value) +{ + // save the movement value + DropValue = Value; +} + +void ASideScrollingCharacter::DoJumpStart() +{ + // handle advanced jump behaviors + MultiJump(); +} + +void ASideScrollingCharacter::DoJumpEnd() +{ + StopJumping(); +} + +void ASideScrollingCharacter::DoInteract() +{ + // do a sphere trace to look for interactive objects + FHitResult OutHit; + + const FVector Start = GetActorLocation(); + const FVector End = Start + FVector(100.0f, 0.0f, 0.0f); + + FCollisionShape ColSphere; + ColSphere.SetSphere(InteractionRadius); + + FCollisionObjectQueryParams ObjectParams; + ObjectParams.AddObjectTypesToQuery(ECC_Pawn); + ObjectParams.AddObjectTypesToQuery(ECC_WorldDynamic); + + FCollisionQueryParams QueryParams; + QueryParams.AddIgnoredActor(this); + + if (GetWorld()->SweepSingleByObjectType(OutHit, Start, End, FQuat::Identity, ObjectParams, ColSphere, QueryParams)) + { + // have we hit an interactable? + if (ISideScrollingInteractable* Interactable = Cast(OutHit.GetActor())) + { + // interact + Interactable->Interaction(this); + } + } +} + +void ASideScrollingCharacter::MultiJump() +{ + // does the user want to drop to a lower platform? + if (DropValue > 0.0f) + { + CheckForSoftCollision(); + return; + } + + // reset the drop value + DropValue = 0.0f; + + // if we're grounded, disregard advanced jump logic + if (!GetCharacterMovement()->IsFalling()) + { + Jump(); + return; + } + + // if we have a horizontal input, try for wall jump first + if (!bHasWallJumped && !FMath::IsNearlyZero(ActionValueY)) + { + // trace ahead of the character for walls + FHitResult OutHit; + + const FVector Start = GetActorLocation(); + const FVector End = Start + (FVector(ActionValueY > 0.0f ? 1.0f : -1.0f, 0.0f, 0.0f) * WallJumpTraceDistance); + + FCollisionQueryParams QueryParams; + QueryParams.AddIgnoredActor(this); + + GetWorld()->LineTraceSingleByChannel(OutHit, Start, End, ECC_Visibility, QueryParams); + + if (OutHit.bBlockingHit) + { + // rotate to the bounce direction + const FRotator BounceRot = UKismetMathLibrary::MakeRotFromX(OutHit.ImpactNormal); + SetActorRotation(FRotator(0.0f, BounceRot.Yaw, 0.0f)); + + // calculate the impulse vector + FVector WallJumpImpulse = OutHit.ImpactNormal * WallJumpHorizontalImpulse; + WallJumpImpulse.Z = GetCharacterMovement()->JumpZVelocity * WallJumpVerticalMultiplier; + + // launch the character away from the wall + LaunchCharacter(WallJumpImpulse, true, true); + + // enable wall jump lockout for a bit + bHasWallJumped = true; + + // schedule wall jump lockout reset + GetWorld()->GetTimerManager().SetTimer(WallJumpTimer, this, &ASideScrollingCharacter::ResetWallJump, DelayBetweenWallJumps, false); + + return; + } + } + + + + // test for double jump only if we haven't already tested for wall jump + if (!bHasWallJumped) + { + // are we still within coyote time frames? + if (GetWorld()->GetTimeSeconds() - LastFallTime < MaxCoyoteTime) + { + UE_LOG(LogTemp, Warning, TEXT("Coyote Jump")); + + // use the built-in CMC functionality to do the jump + Jump(); + + // no coyote time jump + } else { + + // The movement component handles double jump but we still need to manage the flag for animation + if (!bHasDoubleJumped) + { + // raise the double jump flag + bHasDoubleJumped = true; + + // let the CMC handle jump + Jump(); + } + } + } +} + +void ASideScrollingCharacter::CheckForSoftCollision() +{ + // reset the drop value + DropValue = 0.0f; + + // trace down + FHitResult OutHit; + + const FVector Start = GetActorLocation(); + const FVector End = Start + (FVector::DownVector * SoftCollisionTraceDistance); + + FCollisionObjectQueryParams ObjectParams; + ObjectParams.AddObjectTypesToQuery(SoftCollisionObjectType); + + FCollisionQueryParams QueryParams; + QueryParams.AddIgnoredActor(this); + + GetWorld()->LineTraceSingleByObjectType(OutHit, Start, End, ObjectParams, QueryParams); + + // did we hit a soft floor? + if (OutHit.GetActor()) + { + // drop through the floor + SetSoftCollision(true); + } +} + +void ASideScrollingCharacter::ResetWallJump() +{ + // reset the wall jump flag + bHasWallJumped = false; +} + +void ASideScrollingCharacter::SetSoftCollision(bool bEnabled) +{ + // enable or disable collision response to the soft collision channel + GetCapsuleComponent()->SetCollisionResponseToChannel(SoftCollisionObjectType, bEnabled ? ECR_Ignore : ECR_Block); +} + +bool ASideScrollingCharacter::HasDoubleJumped() const +{ + return bHasDoubleJumped; +} + +bool ASideScrollingCharacter::HasWallJumped() const +{ + return bHasWallJumped; +} diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingCharacter.h b/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingCharacter.h new file mode 100644 index 0000000..9815e8b --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingCharacter.h @@ -0,0 +1,180 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Character.h" +#include "SideScrollingCharacter.generated.h" + +class UCameraComponent; +class UInputAction; +struct FInputActionValue; + +/** + * A player-controllable character side scrolling game + */ +UCLASS(abstract) +class ASideScrollingCharacter : public ACharacter +{ + GENERATED_BODY() + + /** Player camera */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category ="Camera", meta = (AllowPrivateAccess = "true")) + UCameraComponent* Camera; + +protected: + + /** Move Input Action */ + UPROPERTY(EditAnywhere, Category="Input") + UInputAction* MoveAction; + + /** Jump Input Action */ + UPROPERTY(EditAnywhere, Category="Input") + UInputAction* JumpAction; + + /** Drop from Platform Action */ + UPROPERTY(EditAnywhere, Category="Input") + UInputAction* DropAction; + + /** Interact Input Action */ + UPROPERTY(EditAnywhere, Category="Input") + UInputAction* InteractAction; + + /** Impulse to manually push physics objects while we're in midair */ + UPROPERTY(EditAnywhere, Category="Side Scrolling|Jump") + float JumpPushImpulse = 600.0f; + + /** Max distance that interactive objects can be triggered */ + UPROPERTY(EditAnywhere, Category="Side Scrolling|Interaction") + float InteractionRadius = 200.0f; + + /** Time to disable input after a wall jump to preserve momentum */ + UPROPERTY(EditAnywhere, Category="Side Scrolling|Wall Jump") + float DelayBetweenWallJumps = 0.3f; + + /** Distance to trace ahead of the character for wall jumps */ + UPROPERTY(EditAnywhere, Category="Side Scrolling|Wall Jump") + float WallJumpTraceDistance = 50.0f; + + /** Horizontal impulse to apply to the character during wall jumps */ + UPROPERTY(EditAnywhere, Category="Side Scrolling|Wall Jump") + float WallJumpHorizontalImpulse = 500.0f; + + /** Multiplies the jump Z velocity for wall jumps. */ + UPROPERTY(EditAnywhere, Category="Side Scrolling|Wall Jump") + float WallJumpVerticalMultiplier = 1.4f; + + /** Collision object type to use for soft collision traces (dropping down floors) */ + UPROPERTY(EditAnywhere, Category="Side Scrolling|Soft Platforms") + TEnumAsByte SoftCollisionObjectType; + + /** Distance to trace down during soft collision checks */ + UPROPERTY(EditAnywhere, Category="Side Scrolling|Soft Platforms") + float SoftCollisionTraceDistance = 1000.0f; + + /** Last recorded time when this character started falling */ + float LastFallTime = 0.0f; + + /** Max amount of time that can pass since we started falling when we allow a regular jump */ + UPROPERTY(EditAnywhere, Category="Side Scrolling|Coyote Time", meta = (ClampMin = 0, ClampMax = 5, Units = "s")) + float MaxCoyoteTime = 0.16f; + + /** Wall jump lockout timer */ + FTimerHandle WallJumpTimer; + + /** Last captured horizontal movement input value */ + float ActionValueY = 0.0f; + + /** Last captured platform drop axis value */ + float DropValue = 0.0f; + + /** If true, this character has already wall jumped */ + bool bHasWallJumped = false; + + /** If true, this character has already double jumped */ + bool bHasDoubleJumped = false; + + /** If true, this character is moving along the side scrolling axis */ + bool bMovingHorizontally = false; + +public: + + /** Constructor */ + ASideScrollingCharacter(); + +protected: + + /** Gameplay cleanup */ + virtual void EndPlay(EEndPlayReason::Type EndPlayReason) override; + + /** Initialize input action bindings */ + virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; + + /** Collision handling */ + virtual void NotifyHit(class UPrimitiveComponent* MyComp, AActor* Other, class UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit) override; + + /** Landing handling */ + virtual void Landed(const FHitResult& Hit) override; + + /** Handle movement mode changes to keep track of coyote time jumps */ + virtual void OnMovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode = 0) override; + +protected: + + /** Called for movement input */ + void Move(const FInputActionValue& Value); + + /** Called for drop from platform input */ + void Drop(const FInputActionValue& Value); + + /** Called for drop from platform input release */ + void DropReleased(const FInputActionValue& Value); + +public: + + /** Handles move inputs from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoMove(float Forward); + + /** Handles drop inputs from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoDrop(float Value); + + /** Handles jump pressed inputs from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoJumpStart(); + + /** Handles jump pressed inputs from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoJumpEnd(); + + /** Handles interact inputs from either controls or UI interfaces */ + UFUNCTION(BlueprintCallable, Category="Input") + virtual void DoInteract(); + +protected: + + /** Handles advanced jump logic */ + void MultiJump(); + + /** Checks for soft collision with platforms */ + void CheckForSoftCollision(); + + /** Resets wall jump lockout. Called from timer after a wall jump */ + void ResetWallJump(); + +public: + + /** Sets the soft collision response. True passes, False blocks */ + void SetSoftCollision(bool bEnabled); + +public: + + /** Returns true if the character has just double jumped */ + UFUNCTION(BlueprintPure, Category="Side Scrolling") + bool HasDoubleJumped() const; + + /** Returns true if the character has just wall jumped */ + UFUNCTION(BlueprintPure, Category="Side Scrolling") + bool HasWallJumped() const; +}; diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingGameMode.cpp b/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingGameMode.cpp new file mode 100644 index 0000000..a233540 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingGameMode.cpp @@ -0,0 +1,77 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "SideScrollingGameMode.h" +#include "Kismet/GameplayStatics.h" +#include "Blueprint/UserWidget.h" +#include "SideScrollingUI.h" +#include "SideScrollingPickup.h" +#include "Kismet/GameplayStatics.h" +#include "GameFramework/PlayerStart.h" +#include "Engine/World.h" + +void ASideScrollingGameMode::BeginPlay() +{ + Super::BeginPlay(); + + // create the game UI + APlayerController* OwningPlayer = UGameplayStatics::GetPlayerController(GetWorld(), 0); + + UserInterface = CreateWidget(OwningPlayer, UserInterfaceClass); + + // create each additional local player. + // Player 0 will be created automatically as part of regular game init + for (int32 i = 2; i <= NumberOfLocalPlayers; ++i) + { + UGameplayStatics::CreatePlayer(GetWorld(), -1, true); + } +} + +AActor* ASideScrollingGameMode::ChoosePlayerStart_Implementation(AController* Player) +{ + // build the current player tag + FName PlayerTag = FName(*FString::Printf(TEXT("Player%d"), CurrentPlayerStartAssignment)); + + // find all player starts with the matching player tag + TArray PlayerStarts; + + UGameplayStatics::GetAllActorsOfClassWithTag(GetWorld(), APlayerStart::StaticClass(), PlayerTag, PlayerStarts); + + // increment the player start assignment index + ++CurrentPlayerStartAssignment; + + // if no PlayerStarts were found, default to all PlayerStarts instead + if (PlayerStarts.IsEmpty()) + { + UGameplayStatics::GetAllActorsOfClass(GetWorld(), APlayerStart::StaticClass(), PlayerStarts); + } + + // have we found at least one PlayerStart? + if (!PlayerStarts.IsEmpty()) + { + return PlayerStarts[ FMath::RandRange(0, PlayerStarts.Num() - 1) ]; + } + + // no PlayerStarts in the level + return nullptr; +} + +void ASideScrollingGameMode::ProcessPickup() +{ + // increment the pickups counter + ++PickupsCollected; + + if (UserInterface) + { + // if this is the first pickup we collect, show the UI + if (PickupsCollected == 1) + { + + UserInterface->AddToViewport(0); + } + + // update the pickups counter on the UI + UserInterface->UpdatePickups(PickupsCollected); + } + +} \ No newline at end of file diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingGameMode.h b/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingGameMode.h new file mode 100644 index 0000000..8ec65bb --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingGameMode.h @@ -0,0 +1,56 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/GameModeBase.h" +#include "SideScrollingGameMode.generated.h" + +class USideScrollingUI; + +/** + * Simple Side Scrolling Game Mode + * Spawns and manages the game UI + * Counts pickups collected by the player + */ +UCLASS(abstract) +class ASideScrollingGameMode : public AGameModeBase +{ + GENERATED_BODY() + +protected: + + /** Class of UI widget to spawn when the game starts */ + UPROPERTY(EditAnywhere, Category="UI") + TSubclassOf UserInterfaceClass; + + /** User interface widget for the game */ + UPROPERTY(BlueprintReadOnly, Category="UI") + TObjectPtr UserInterface; + + /** Number of pickups collected by the player */ + UPROPERTY(BlueprintReadOnly, Category="Pickups") + int32 PickupsCollected = 0; + +protected: + + /** Initialization */ + virtual void BeginPlay() override; + + /** Assigns a PlayerStart to a specific player */ + virtual AActor* ChoosePlayerStart_Implementation(AController* Player) override; + +protected: + + /** Determines how many local players should be spawned on game start */ + UPROPERTY(EditDefaultsOnly, Category="Local Multiplayer", meta = (ClampMin = 1, ClampMax = 4)) + int32 NumberOfLocalPlayers = 1; + + /** Used to assign players to different PlayerStarts in the level */ + int32 CurrentPlayerStartAssignment = 0; + +public: + + /** Receives an interaction event from another actor */ + virtual void ProcessPickup(); +}; diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingPlayerController.cpp b/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingPlayerController.cpp new file mode 100644 index 0000000..63da137 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingPlayerController.cpp @@ -0,0 +1,98 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "SideScrollingPlayerController.h" +#include "EnhancedInputSubsystems.h" +#include "InputMappingContext.h" +#include "Kismet/GameplayStatics.h" +#include "GameFramework/PlayerStart.h" +#include "SideScrollingCharacter.h" +#include "Engine/LocalPlayer.h" +#include "Engine/World.h" +#include "Blueprint/UserWidget.h" +#include "KingshearthLegacy.h" +#include "Widgets/Input/SVirtualJoystick.h" + +void ASideScrollingPlayerController::BeginPlay() +{ + Super::BeginPlay(); +} + +void ASideScrollingPlayerController::SetupInputComponent() +{ + Super::SetupInputComponent(); + + // only add IMCs for local player controllers + if (IsLocalPlayerController()) + { + // add the input mapping context + if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem(GetLocalPlayer())) + { + for (UInputMappingContext* CurrentContext : DefaultMappingContexts) + { + Subsystem->AddMappingContext(CurrentContext, 0); + } + + // only add these IMCs if we're not using mobile touch input + if (!ShouldUseTouchControls()) + { + for (UInputMappingContext* CurrentContext : MobileExcludedMappingContexts) + { + Subsystem->AddMappingContext(CurrentContext, 0); + } + } + } + } + + // only spawn touch controls on local player controllers + if (IsLocalPlayerController() && ShouldUseTouchControls()) + { + // spawn the mobile controls widget + MobileControlsWidget = CreateWidget(this, MobileControlsWidgetClass); + + if (MobileControlsWidget) + { + // add the controls to the player screen + MobileControlsWidget->AddToPlayerScreen(0); + + } else { + + UE_LOG(LogKingshearthLegacy, Error, TEXT("Could not spawn mobile controls widget.")); + + } + + } +} + +void ASideScrollingPlayerController::OnPossess(APawn* InPawn) +{ + Super::OnPossess(InPawn); + + // subscribe to the pawn's OnDestroyed delegate + InPawn->OnDestroyed.AddDynamic(this, &ASideScrollingPlayerController::OnPawnDestroyed); +} + +void ASideScrollingPlayerController::OnPawnDestroyed(AActor* DestroyedActor) +{ + // find the player start + TArray ActorList; + UGameplayStatics::GetAllActorsOfClass(GetWorld(), APlayerStart::StaticClass(), ActorList); + + if (ActorList.Num() > 0) + { + // spawn a character at the player start + const FTransform SpawnTransform = ActorList[0]->GetActorTransform(); + + if (ASideScrollingCharacter* RespawnedCharacter = GetWorld()->SpawnActor(CharacterClass, SpawnTransform)) + { + // possess the character + Possess(RespawnedCharacter); + } + } +} + +bool ASideScrollingPlayerController::ShouldUseTouchControls() const +{ + // are we on a mobile platform? Should we force touch? + return SVirtualJoystick::ShouldDisplayTouchInterface() || bForceTouchControls; +} diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingPlayerController.h b/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingPlayerController.h new file mode 100644 index 0000000..115029e --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/SideScrollingPlayerController.h @@ -0,0 +1,67 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/PlayerController.h" +#include "EnhancedInput/Public/InputAction.h" +#include "SideScrollingPlayerController.generated.h" + +class ASideScrollingCharacter; +class UInputMappingContext; + +/** + * A simple Side Scrolling Player Controller + * Manages input mappings + * Respawns the player pawn at the player start if it is destroyed + */ +UCLASS(abstract, Config="Game") +class ASideScrollingPlayerController : public APlayerController +{ + GENERATED_BODY() + +protected: + + /** Input mapping context for this player */ + UPROPERTY(EditAnywhere, Category="Input|Input Mappings") + TArray DefaultMappingContexts; + + /** Input Mapping Contexts */ + UPROPERTY(EditAnywhere, Category="Input|Input Mappings") + TArray MobileExcludedMappingContexts; + + /** Mobile controls widget to spawn */ + UPROPERTY(EditAnywhere, Category="Input|Touch Controls") + TSubclassOf MobileControlsWidgetClass; + + /** Pointer to the mobile controls widget */ + UPROPERTY() + TObjectPtr MobileControlsWidget; + + /** If true, the player will use UMG touch controls even if not playing on mobile platforms */ + UPROPERTY(EditAnywhere, Config, Category = "Input|Touch Controls") + bool bForceTouchControls = false; + + /** Character class to respawn when the possessed pawn is destroyed */ + UPROPERTY(EditAnywhere, Category="Respawn") + TSubclassOf CharacterClass; + +protected: + + /** Gameplay initialization */ + virtual void BeginPlay() override; + + /** Initialize input bindings */ + virtual void SetupInputComponent() override; + + /** Pawn initialization */ + virtual void OnPossess(APawn* InPawn) override; + + /** Called if the possessed pawn is destroyed */ + UFUNCTION() + void OnPawnDestroyed(AActor* DestroyedActor); + + /** Returns true if the player should use UMG touch controls */ + bool ShouldUseTouchControls() const; + +}; diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/UI/SideScrollingUI.cpp b/Source/KingshearthLegacy/Variant_SideScrolling/UI/SideScrollingUI.cpp new file mode 100644 index 0000000..0a4e042 --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/UI/SideScrollingUI.cpp @@ -0,0 +1,5 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "SideScrollingUI.h" + diff --git a/Source/KingshearthLegacy/Variant_SideScrolling/UI/SideScrollingUI.h b/Source/KingshearthLegacy/Variant_SideScrolling/UI/SideScrollingUI.h new file mode 100644 index 0000000..a19cafd --- /dev/null +++ b/Source/KingshearthLegacy/Variant_SideScrolling/UI/SideScrollingUI.h @@ -0,0 +1,23 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "SideScrollingUI.generated.h" + +/** + * Simple Side Scrolling game UI + * Displays and manages a pickup counter + */ +UCLASS(abstract) +class USideScrollingUI : public UUserWidget +{ + GENERATED_BODY() + +public: + + /** Update the widget's pickup counter */ + UFUNCTION(BlueprintImplementableEvent, Category="UI") + void UpdatePickups(int32 Amount); +}; diff --git a/Source/KingshearthLegacyEditor.Target.cs b/Source/KingshearthLegacyEditor.Target.cs new file mode 100644 index 0000000..7e19579 --- /dev/null +++ b/Source/KingshearthLegacyEditor.Target.cs @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class KingshearthLegacyEditorTarget : TargetRules +{ + public KingshearthLegacyEditorTarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Editor; + DefaultBuildSettings = BuildSettingsVersion.V7; + IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_8; + ExtraModuleNames.Add("KingshearthLegacy"); + } +}