commit fc08cd0c0bd3d900be29cf458427a30373b0aa95 Author: sandr Date: Sat Apr 11 22:17:14 2026 +0200 Initial commit – UE5 DungeonCrawler project mit Planung v4.2 Co-Authored-By: Claude Sonnet 4.6 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..acc14c6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Unreal Engine +Binaries/ +Build/ +DerivedDataCache/ +Intermediate/ +Saved/ +*.sln +*.suo +.vs/ + +# Plugins - Binaries only +Plugins/*/Binaries/ +Plugins/*/Intermediate/ + +# OS +Thumbs.db +Desktop.ini +.DS_Store diff --git a/.vsconfig b/.vsconfig new file mode 100644 index 0000000..b981b2e --- /dev/null +++ b/.vsconfig @@ -0,0 +1,19 @@ +{ + "version": "1.0", + "components": [ + "Component.Unreal.Debugger", + "Component.Unreal.Ide", + "Microsoft.Net.Component.4.6.2.TargetingPack", + "Microsoft.VisualStudio.Component.VC.14.38.17.8.ATL", + "Microsoft.VisualStudio.Component.VC.14.38.17.8.x86.x64", + "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/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..e2f02fc --- /dev/null +++ b/Config/DefaultEngine.ini @@ -0,0 +1,258 @@ +[/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 + +[/Script/Engine.RendererSettings] +r.Mobile.ShadingPath=1 +r.Mobile.AntiAliasing=2 +r.Mobile.FloatPrecisionMode=0 +r.Mobile.AllowDitheredLODTransition=False +r.Mobile.VirtualTextures=False +r.DiscardUnusedQuality=False +r.AllowOcclusionQueries=True +r.MinScreenRadiusForLights=0.030000 +r.MinScreenRadiusForDepthPrepass=0.030000 +r.PrecomputedVisibilityWarning=False +r.TextureStreaming=True +Compat.UseDXT5NormalMaps=False +r.VirtualTextures=True +r.VirtualTexturedLightmaps=False +r.VT.TileSize=128 +r.VT.TileBorderSize=4 +r.VT.AnisotropicFiltering=False +r.VT.EnableAutoImport=False +bEnableVirtualTextureOpacityMask=True +r.MeshPaintVirtualTexture.Support=True +r.MeshPaintVirtualTexture.TileSize=32 +r.MeshPaintVirtualTexture.TileBorderSize=2 +r.MeshPaintVirtualTexture.UseCompression=True +r.StaticMesh.DefaultMeshPaintTextureSupport=True +r.MeshPaintVirtualTexture.DefaultTexelsPerVertex=4 +r.MeshPaintVirtualTexture.MaxTextureSize=4096 +r.vt.rvt.EnableBaseColor=True +r.vt.rvt.EnableBaseColorRoughness=True +r.vt.rvt.EnableBaseColorSpecular=True +r.vt.rvt.EnableMask4=True +r.vt.rvt.EnableWorldHeight=True +r.vt.rvt.EnableDisplacement=True +r.vt.rvt.HighQualityPerPixelHeight=True +WorkingColorSpaceChoice=sRGB +RedChromaticityCoordinate=(X=0.640000,Y=0.330000) +GreenChromaticityCoordinate=(X=0.300000,Y=0.600000) +BlueChromaticityCoordinate=(X=0.150000,Y=0.060000) +WhiteChromaticityCoordinate=(X=0.312700,Y=0.329000) +r.LegacyLuminanceFactors=False +r.ClearCoatNormal=False +r.DynamicGlobalIlluminationMethod=1 +r.ReflectionMethod=1 +r.ReflectionCaptureResolution=128 +r.ReflectionEnvironmentLightmapMixBasedOnRoughness=True +r.Lumen.HardwareRayTracing=True +r.Lumen.HardwareRayTracing.LightingMode=0 +r.Lumen.TranslucencyReflections.FrontLayer.EnableForProject=False +r.Lumen.TraceMeshSDFs=0 +r.Lumen.ScreenTracingSource=0 +r.Lumen.Reflections.HardwareRayTracing.Translucent.Refraction.EnableForProject=True +r.MegaLights.EnableForProject=False +r.RayTracing.Shadows=False +r.Shadow.Virtual.Enable=1 +r.RayTracing=True +r.RayTracing.RayTracingProxies.ProjectEnabled=True +r.RayTracing.UseTextureLod=False +r.PathTracing=True +r.GenerateMeshDistanceFields=True +r.DistanceFields.DefaultVoxelDensity=0.200000 +r.Nanite.ProjectEnabled=True +r.AllowStaticLighting=False +r.NormalMapsForStaticLighting=False +r.ForwardShading=False +r.VertexFoggingForOpaque=True +r.SeparateTranslucency=True +r.TranslucentSortPolicy=0 +TranslucentSortAxis=(X=0.000000,Y=-1.000000,Z=0.000000) +r.LocalFogVolume.ApplyOnTranslucent=False +xr.VRS.FoveationLevel=0 +xr.VRS.DynamicFoveation=False +r.CustomDepth=1 +r.CustomDepthTemporalAAJitter=True +r.PostProcessing.PropagateAlpha=False +r.Deferred.SupportPrimitiveAlphaHoldout=False +r.DefaultFeature.Bloom=True +r.DefaultFeature.AmbientOcclusion=True +r.DefaultFeature.AmbientOcclusionStaticFraction=True +r.DefaultFeature.AutoExposure=False +r.DefaultFeature.AutoExposure.Method=0 +r.DefaultFeature.AutoExposure.Bias=1.000000 +r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True +r.DefaultFeature.LocalExposure.HighlightContrastScale=0.800000 +r.DefaultFeature.LocalExposure.ShadowContrastScale=0.800000 +r.DefaultFeature.MotionBlur=False +r.DefaultFeature.LensFlare=False +r.TemporalAA.Upsampling=True +r.AntiAliasingMethod=0 +r.MSAACount=4 +r.DefaultFeature.LightUnits=1 +r.DefaultBackBufferPixelFormat=4 +r.ScreenPercentage.Default=100.000000 +r.ScreenPercentage.Default.Desktop.Mode=1 +r.ScreenPercentage.Default.Mobile.Mode=0 +r.ScreenPercentage.Default.VR.Mode=0 +r.ScreenPercentage.Default.PathTracer.Mode=0 +r.Shadow.UnbuiltPreviewInGame=True +r.StencilForLODDither=False +r.EarlyZPass=3 +r.EarlyZPassOnlyMaterialMasking=False +r.Shadow.CSMCaching=False +r.DBuffer=True +r.ClearSceneMethod=1 +r.VelocityOutputPass=0 +r.Velocity.EnableVertexDeformation=2 +r.SelectiveBasePassOutputs=False +bDefaultParticleCutouts=False +fx.GPUSimulationTextureSizeX=1024 +fx.GPUSimulationTextureSizeY=1024 +r.AllowGlobalClipPlane=False +r.GBufferFormat=1 +r.MorphTarget.Mode=True +r.MorphTarget.MaxBlendWeight=5.000000 +r.SupportSkyAtmosphere=True +r.SupportSkyAtmosphereAffectsHeightFog=True +r.SupportExpFogMatchesVolumetricFog=False +r.SupportLocalFogVolumes=True +r.SupportCloudShadowOnForwardLitTranslucent=False +r.ClusteredDeferredShading.EnableForProject=True +r.LightFunctionAtlas.Format=0 +r.VolumetricFog.LightFunction=True +r.Deferred.UsesLightFunctionAtlas=True +r.SingleLayerWater.UsesLightFunctionAtlas=False +r.Translucent.UsesLightFunctionAtlas=False +r.Translucent.UsesIESProfiles=False +r.Translucent.UsesRectLights=False +r.Translucent.UsesShadowedLocalLights=False +r.GPUCrashDebugging=False +vr.InstancedStereo=False +r.MobileHDR=True +vr.MobileMultiView=False +r.Mobile.UseHWsRGBEncoding=False +vr.RoundRobinOcclusion=False +r.MeshStreaming=False +r.HeterogeneousVolumes=True +r.HeterogeneousVolumes.Shadows=False +r.Translucency.HeterogeneousVolumes=False +r.WireframeCullThreshold=5.000000 +r.SupportStationarySkylight=True +r.SupportLowQualityLightmaps=True +r.SupportPointLightWholeSceneShadows=True +r.Shadow.TranslucentPerObject.ProjectEnabled=False +r.Water.SingleLayerWater.SupportCloudShadow=False +r.Substrate=True +r.Substrate.ProjectGBufferFormat=0 +r.Substrate.ProjectClosuresPerPixel=4 +r.Substrate.ProjectClosuresPerPixelOverride=0 +r.Substrate.BytesPerPixel=80 +r.Substrate.OpaqueMaterialRoughRefraction=False +r.Refraction.Blur=True +r.Substrate.Debug.AdvancedVisualizationShaders=False +r.Substrate.EnableLayerSupport=False +r.Material.RoughDiffuse=False +r.Material.EnergyConservation=False +r.Material.DefaultAutoMaterialUsage=True +r.OIT.SortedPixels=False +r.HairStrands.LODMode=True +r.SkinCache.CompileShaders=True +r.VRS.Support=True +r.SkinCache.SkipCompilingGPUSkinVF=False +r.SkinCache.DefaultBehavior=1 +r.SkinCache.SceneMemoryLimitInMB=128.000000 +r.Mobile.EnableStaticAndCSMShadowReceivers=True +r.Mobile.EnableMovableLightCSMShaderCulling=True +r.Mobile.Forward.EnableLocalLights=1 +r.Mobile.Forward.EnableClusteredReflections=False +r.Mobile.AllowDistanceFieldShadows=True +r.Mobile.EnableMovableSpotlightsShadow=False +r.GPUSkin.Support16BitBoneIndex=False +r.GPUSkin.Limit2BoneInfluences=False +r.SupportDepthOnlyIndexBuffers=False +r.SupportReversedIndexBuffers=False +r.Mobile.AmbientOcclusion=False +r.Mobile.ScreenSpaceReflections=False +r.Mobile.DBuffer=False +r.Mobile.PropagateAlpha=False +r.GPUSkin.UnlimitedBoneInfluences=False +r.GPUSkin.AlwaysUseDeformerForUnlimitedBoneInfluences=False +r.GPUSkin.UnlimitedBoneInfluencesThreshold=8 +DefaultBoneInfluenceLimit=(Default=0,PerPlatform=()) +MaxSkinBones=(Default=65536,PerPlatform=(("Mobile", 256))) +r.Nanite.Foliage=False +bStreamSkeletalMeshLODs=(Default=False,PerPlatform=()) +bDiscardSkeletalMeshOptionalLODs=(Default=False,PerPlatform=()) +VisualizeCalibrationColorMaterialPath=/Engine/EngineMaterials/PPM_DefaultCalibrationColor.PPM_DefaultCalibrationColor +VisualizeCalibrationCustomMaterialPath=None +VisualizeCalibrationGrayscaleMaterialPath=/Engine/EngineMaterials/PPM_DefaultCalibrationGrayscale.PPM_DefaultCalibrationGrayscale + +[/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=Scalable +AppliedDefaultGraphicsPerformance=Scalable + +[/Script/Engine.Engine] ++ActiveGameNameRedirects=(OldGameName="TP_ThirdPerson",NewGameName="/Script/DungeonCrawlerUE") ++ActiveGameNameRedirects=(OldGameName="/Script/TP_ThirdPerson",NewGameName="/Script/DungeonCrawlerUE") ++ActiveClassRedirects=(OldClassName="TP_ThirdPersonPlayerController",NewClassName="DungeonCrawlerUEPlayerController") ++ActiveClassRedirects=(OldClassName="TP_ThirdPersonGameMode",NewClassName="DungeonCrawlerUEGameMode") ++ActiveClassRedirects=(OldClassName="TP_ThirdPersonCharacter",NewClassName="DungeonCrawlerUECharacter") + +[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] +bEnablePlugin=True +bAllowNetworkConnection=True +SecurityToken=FD31145140D0F138F73838A5D7DE5681 +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..84e6eb4 --- /dev/null +++ b/Config/DefaultGame.ini @@ -0,0 +1,3 @@ +[/Script/EngineSettings.GeneralProjectSettings] +ProjectID=C584E2BD4598153316E45C855624CFAC +ProjectName=Third Person Game Template 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..cc14f2241 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/Dungeon/Enums/E_PathType.uasset b/Content/Dungeon/Enums/E_PathType.uasset new file mode 100644 index 0000000..2b16baf Binary files /dev/null and b/Content/Dungeon/Enums/E_PathType.uasset differ diff --git a/Content/Dungeon/Enums/E_RoomType.uasset b/Content/Dungeon/Enums/E_RoomType.uasset new file mode 100644 index 0000000..03aa81d Binary files /dev/null and b/Content/Dungeon/Enums/E_RoomType.uasset differ diff --git a/Content/Dungeon/Enums/E_SocketStatus.uasset b/Content/Dungeon/Enums/E_SocketStatus.uasset new file mode 100644 index 0000000..a08d859 Binary files /dev/null and b/Content/Dungeon/Enums/E_SocketStatus.uasset differ diff --git a/Content/Dungeon/Generierung/BP_DungeonGenerator.uasset b/Content/Dungeon/Generierung/BP_DungeonGenerator.uasset new file mode 100644 index 0000000..0eece54 Binary files /dev/null and b/Content/Dungeon/Generierung/BP_DungeonGenerator.uasset differ diff --git a/Content/Dungeon/Interfaces/BPI_DungeonRoom.uasset b/Content/Dungeon/Interfaces/BPI_DungeonRoom.uasset new file mode 100644 index 0000000..19b3aca Binary files /dev/null and b/Content/Dungeon/Interfaces/BPI_DungeonRoom.uasset differ diff --git a/Content/Dungeon/Material/DungeonWall/DungeonWall_AO.png b/Content/Dungeon/Material/DungeonWall/DungeonWall_AO.png new file mode 100644 index 0000000..59b7712 Binary files /dev/null and b/Content/Dungeon/Material/DungeonWall/DungeonWall_AO.png differ diff --git a/Content/Dungeon/Material/DungeonWall/DungeonWall_Albedo.png b/Content/Dungeon/Material/DungeonWall/DungeonWall_Albedo.png new file mode 100644 index 0000000..9017a43 Binary files /dev/null and b/Content/Dungeon/Material/DungeonWall/DungeonWall_Albedo.png differ diff --git a/Content/Dungeon/Material/DungeonWall/DungeonWall_Displacement.exr b/Content/Dungeon/Material/DungeonWall/DungeonWall_Displacement.exr new file mode 100644 index 0000000..c2438d8 Binary files /dev/null and b/Content/Dungeon/Material/DungeonWall/DungeonWall_Displacement.exr differ diff --git a/Content/Dungeon/Material/DungeonWall/DungeonWall_Metalness.png b/Content/Dungeon/Material/DungeonWall/DungeonWall_Metalness.png new file mode 100644 index 0000000..8d35f17 Binary files /dev/null and b/Content/Dungeon/Material/DungeonWall/DungeonWall_Metalness.png differ diff --git a/Content/Dungeon/Material/DungeonWall/DungeonWall_Normal.png b/Content/Dungeon/Material/DungeonWall/DungeonWall_Normal.png new file mode 100644 index 0000000..bdda844 Binary files /dev/null and b/Content/Dungeon/Material/DungeonWall/DungeonWall_Normal.png differ diff --git a/Content/Dungeon/Material/DungeonWall/DungeonWall_Roughness.png b/Content/Dungeon/Material/DungeonWall/DungeonWall_Roughness.png new file mode 100644 index 0000000..72f1de6 Binary files /dev/null and b/Content/Dungeon/Material/DungeonWall/DungeonWall_Roughness.png differ diff --git a/Content/Dungeon/Rooms/BP_Room_Corridor.uasset b/Content/Dungeon/Rooms/BP_Room_Corridor.uasset new file mode 100644 index 0000000..cd93239 Binary files /dev/null and b/Content/Dungeon/Rooms/BP_Room_Corridor.uasset differ diff --git a/Content/Dungeon/Rooms/BP_Room_DeadEnd.uasset b/Content/Dungeon/Rooms/BP_Room_DeadEnd.uasset new file mode 100644 index 0000000..7cd17a6 Binary files /dev/null and b/Content/Dungeon/Rooms/BP_Room_DeadEnd.uasset differ diff --git a/Content/Dungeon/Rooms/BP_Room_Entrance.uasset b/Content/Dungeon/Rooms/BP_Room_Entrance.uasset new file mode 100644 index 0000000..e87177c Binary files /dev/null and b/Content/Dungeon/Rooms/BP_Room_Entrance.uasset differ diff --git a/Content/Dungeon/Rooms/BP_Room_Exit.uasset b/Content/Dungeon/Rooms/BP_Room_Exit.uasset new file mode 100644 index 0000000..80213ca Binary files /dev/null and b/Content/Dungeon/Rooms/BP_Room_Exit.uasset differ diff --git a/Content/Dungeon/Rooms/BP_Room_General.uasset b/Content/Dungeon/Rooms/BP_Room_General.uasset new file mode 100644 index 0000000..1557e6f Binary files /dev/null and b/Content/Dungeon/Rooms/BP_Room_General.uasset differ diff --git a/Content/Dungeon/Rooms/BP_Room_PortalIn.uasset b/Content/Dungeon/Rooms/BP_Room_PortalIn.uasset new file mode 100644 index 0000000..f1b6c91 Binary files /dev/null and b/Content/Dungeon/Rooms/BP_Room_PortalIn.uasset differ diff --git a/Content/Dungeon/Rooms/BP_Room_PortalOut.uasset b/Content/Dungeon/Rooms/BP_Room_PortalOut.uasset new file mode 100644 index 0000000..a6e4beb Binary files /dev/null and b/Content/Dungeon/Rooms/BP_Room_PortalOut.uasset differ diff --git a/Content/Dungeon/Rooms/BP_Room_TShape.uasset b/Content/Dungeon/Rooms/BP_Room_TShape.uasset new file mode 100644 index 0000000..5aa0a7e Binary files /dev/null and b/Content/Dungeon/Rooms/BP_Room_TShape.uasset differ diff --git a/Content/Dungeon/Structs/S_OpenSocketData.uasset b/Content/Dungeon/Structs/S_OpenSocketData.uasset new file mode 100644 index 0000000..e6d6277 Binary files /dev/null and b/Content/Dungeon/Structs/S_OpenSocketData.uasset differ diff --git a/Content/Dungeon/Structs/S_PlacedRoomData.uasset b/Content/Dungeon/Structs/S_PlacedRoomData.uasset new file mode 100644 index 0000000..30bff70 Binary files /dev/null and b/Content/Dungeon/Structs/S_PlacedRoomData.uasset differ diff --git a/Content/Dungeon/Structs/S_RoomPoolEntry.uasset b/Content/Dungeon/Structs/S_RoomPoolEntry.uasset new file mode 100644 index 0000000..dac33c2 Binary files /dev/null and b/Content/Dungeon/Structs/S_RoomPoolEntry.uasset differ diff --git a/Content/Dungeon/Structs/S_SocketDefinition.uasset b/Content/Dungeon/Structs/S_SocketDefinition.uasset new file mode 100644 index 0000000..f641d3c Binary files /dev/null and b/Content/Dungeon/Structs/S_SocketDefinition.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/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.uasset b/Content/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.uasset new file mode 100644 index 0000000..b4a8478 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..d913ba6 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/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..614d0da 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..7705d1a 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..2ba0933 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..6ee8e69 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..a18bcbb 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..2f62d75 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..645451f 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..466cfab 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..e8d35a7 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..ea7afa8 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/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/2/AM/9YGDW7FMEY1CZD31VUYO11.uasset b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/2/AM/9YGDW7FMEY1CZD31VUYO11.uasset new file mode 100644 index 0000000..f674678 Binary files /dev/null and b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/2/AM/9YGDW7FMEY1CZD31VUYO11.uasset differ diff --git a/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/3/0N/NTRPN55OWWUSD03K29ZOSH.uasset b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/3/0N/NTRPN55OWWUSD03K29ZOSH.uasset new file mode 100644 index 0000000..0c64034 Binary files /dev/null and b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/3/0N/NTRPN55OWWUSD03K29ZOSH.uasset differ diff --git a/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/8/VS/XX060WNO6GEZU5BZQCLZPP.uasset b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/8/VS/XX060WNO6GEZU5BZQCLZPP.uasset new file mode 100644 index 0000000..02b2d2a Binary files /dev/null and b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/8/VS/XX060WNO6GEZU5BZQCLZPP.uasset differ diff --git a/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/9/5G/29Z6AXUGZPZ0CXHLK2DICG.uasset b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/9/5G/29Z6AXUGZPZ0CXHLK2DICG.uasset new file mode 100644 index 0000000..c0ab27f Binary files /dev/null and b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/9/5G/29Z6AXUGZPZ0CXHLK2DICG.uasset differ diff --git a/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/9/LX/1K6D4FIEJMOF50LSVJKFMP.uasset b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/9/LX/1K6D4FIEJMOF50LSVJKFMP.uasset new file mode 100644 index 0000000..0e740b9 Binary files /dev/null and b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/9/LX/1K6D4FIEJMOF50LSVJKFMP.uasset differ diff --git a/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/C/EE/HZ5WNBFZRJYQPUA6X8TVVT.uasset b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/C/EE/HZ5WNBFZRJYQPUA6X8TVVT.uasset new file mode 100644 index 0000000..4cf25fa Binary files /dev/null and b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/C/EE/HZ5WNBFZRJYQPUA6X8TVVT.uasset differ diff --git a/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/C/HY/Q7CPP7VHOXBAMAJI90W626.uasset b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/C/HY/Q7CPP7VHOXBAMAJI90W626.uasset new file mode 100644 index 0000000..6547e7d Binary files /dev/null and b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/C/HY/Q7CPP7VHOXBAMAJI90W626.uasset differ diff --git a/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/D/AY/KLCRMXKI9WR2P0GDY96C36.uasset b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/D/AY/KLCRMXKI9WR2P0GDY96C36.uasset new file mode 100644 index 0000000..0703185 Binary files /dev/null and b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/D/AY/KLCRMXKI9WR2P0GDY96C36.uasset differ diff --git a/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/D/RI/R8L7GQXCHQGMMKBPTBJTNA.uasset b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/D/RI/R8L7GQXCHQGMMKBPTBJTNA.uasset new file mode 100644 index 0000000..53239f5 Binary files /dev/null and b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/D/RI/R8L7GQXCHQGMMKBPTBJTNA.uasset differ diff --git a/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/E/96/RFQP83S9K0LC7IW8CXS6C0.uasset b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/E/96/RFQP83S9K0LC7IW8CXS6C0.uasset new file mode 100644 index 0000000..b162f95 Binary files /dev/null and b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/E/96/RFQP83S9K0LC7IW8CXS6C0.uasset differ diff --git a/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/E/AW/88MTEWNW9J0HEFNNIBQDCA.uasset b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/E/AW/88MTEWNW9J0HEFNNIBQDCA.uasset new file mode 100644 index 0000000..441f8f2 Binary files /dev/null and b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/E/AW/88MTEWNW9J0HEFNNIBQDCA.uasset differ diff --git a/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/E/PS/RDHZWN8YLPOSCU88GUXZ68.uasset b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/E/PS/RDHZWN8YLPOSCU88GUXZ68.uasset new file mode 100644 index 0000000..467cbd9 Binary files /dev/null and b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/E/PS/RDHZWN8YLPOSCU88GUXZ68.uasset differ diff --git a/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/F/4G/TYFCMQ9MC435YW6FZBF5FS.uasset b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/F/4G/TYFCMQ9MC435YW6FZBF5FS.uasset new file mode 100644 index 0000000..c7f6d06 Binary files /dev/null and b/Content/__ExternalActors__/ThirdPerson/Lvl_ThirdPerson/F/4G/TYFCMQ9MC435YW6FZBF5FS.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/0/09/6VOTF0H77B77LNPRHA1IYO.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/0/09/6VOTF0H77B77LNPRHA1IYO.uasset new file mode 100644 index 0000000..cf0800e Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/0/09/6VOTF0H77B77LNPRHA1IYO.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/0/68/TFF0KCJOKMAFWHCUNS986L.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/0/68/TFF0KCJOKMAFWHCUNS986L.uasset new file mode 100644 index 0000000..a6e00ea Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/0/68/TFF0KCJOKMAFWHCUNS986L.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/0/6T/7MNN6LIAHBUH3LNJA8OD7O.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/0/6T/7MNN6LIAHBUH3LNJA8OD7O.uasset new file mode 100644 index 0000000..4481ff4 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/0/6T/7MNN6LIAHBUH3LNJA8OD7O.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/0/GY/W6UMQ08ZGD02NR2XHXMS7T.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/0/GY/W6UMQ08ZGD02NR2XHXMS7T.uasset new file mode 100644 index 0000000..dc6958d Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/0/GY/W6UMQ08ZGD02NR2XHXMS7T.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/0/PY/D55A2955I299GXFMUK73NN.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/0/PY/D55A2955I299GXFMUK73NN.uasset new file mode 100644 index 0000000..a6e9f2b Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/0/PY/D55A2955I299GXFMUK73NN.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/0/SK/591N2DAZZHFKKFOF53JES4.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/0/SK/591N2DAZZHFKKFOF53JES4.uasset new file mode 100644 index 0000000..4bfa751 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/0/SK/591N2DAZZHFKKFOF53JES4.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/13/96ZEEPCX5RLGE3VS3Z5F83.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/13/96ZEEPCX5RLGE3VS3Z5F83.uasset new file mode 100644 index 0000000..4415720 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/13/96ZEEPCX5RLGE3VS3Z5F83.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/7N/TMU12PWOB3PF1QBMHGAA9P.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/7N/TMU12PWOB3PF1QBMHGAA9P.uasset new file mode 100644 index 0000000..9210218 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/7N/TMU12PWOB3PF1QBMHGAA9P.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/8V/MZ5VD1FM5EWPQ14HOUY9LL.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/8V/MZ5VD1FM5EWPQ14HOUY9LL.uasset new file mode 100644 index 0000000..78f04a9 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/8V/MZ5VD1FM5EWPQ14HOUY9LL.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/9L/RVJQMYYFJ9NF8ATLBRGNE2.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/9L/RVJQMYYFJ9NF8ATLBRGNE2.uasset new file mode 100644 index 0000000..b93a990 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/9L/RVJQMYYFJ9NF8ATLBRGNE2.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/G4/J64XLYHNNKOHM68L3ATL04.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/G4/J64XLYHNNKOHM68L3ATL04.uasset new file mode 100644 index 0000000..cd47ff3 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/G4/J64XLYHNNKOHM68L3ATL04.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/K6/QEH37F755SSQDHM16RP0W6.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/K6/QEH37F755SSQDHM16RP0W6.uasset new file mode 100644 index 0000000..3b388d7 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/K6/QEH37F755SSQDHM16RP0W6.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/MO/FX52NCMV5JFYOIP51Q8D0R.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/MO/FX52NCMV5JFYOIP51Q8D0R.uasset new file mode 100644 index 0000000..34654f3 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/MO/FX52NCMV5JFYOIP51Q8D0R.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/N0/UUHIOS9OWPYF8DRRUOILO0.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/N0/UUHIOS9OWPYF8DRRUOILO0.uasset new file mode 100644 index 0000000..f79361f Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/N0/UUHIOS9OWPYF8DRRUOILO0.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/NZ/2DGYN86OICCF1JK42OM8LH.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/NZ/2DGYN86OICCF1JK42OM8LH.uasset new file mode 100644 index 0000000..c11be32 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/NZ/2DGYN86OICCF1JK42OM8LH.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/Q1/54UJTQSRBUIMXTLWMQD201.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/Q1/54UJTQSRBUIMXTLWMQD201.uasset new file mode 100644 index 0000000..6a67e3b Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/Q1/54UJTQSRBUIMXTLWMQD201.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/QC/I20BJ3V98WLHGTVOPUE3TL.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/QC/I20BJ3V98WLHGTVOPUE3TL.uasset new file mode 100644 index 0000000..e3e167c Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/1/QC/I20BJ3V98WLHGTVOPUE3TL.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/DC/J79FNM591NCL4PRZU30JKH.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/DC/J79FNM591NCL4PRZU30JKH.uasset new file mode 100644 index 0000000..2036af1 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/DC/J79FNM591NCL4PRZU30JKH.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/E2/7WMFIN5511KNW0G94AGVMX.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/E2/7WMFIN5511KNW0G94AGVMX.uasset new file mode 100644 index 0000000..0854943 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/E2/7WMFIN5511KNW0G94AGVMX.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/ES/Y759LYJCXPLAYRH2BHOD2H.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/ES/Y759LYJCXPLAYRH2BHOD2H.uasset new file mode 100644 index 0000000..2185b58 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/ES/Y759LYJCXPLAYRH2BHOD2H.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/PC/QNCY9367JPRDLFY623BAWG.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/PC/QNCY9367JPRDLFY623BAWG.uasset new file mode 100644 index 0000000..476e3e6 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/PC/QNCY9367JPRDLFY623BAWG.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/PN/5DVYKXIO9QXKZ6DD03JHFY.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/PN/5DVYKXIO9QXKZ6DD03JHFY.uasset new file mode 100644 index 0000000..beeb0e2 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/PN/5DVYKXIO9QXKZ6DD03JHFY.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/T8/Z9LBHJJSRJW405KOMZM5LN.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/T8/Z9LBHJJSRJW405KOMZM5LN.uasset new file mode 100644 index 0000000..cff145f Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/T8/Z9LBHJJSRJW405KOMZM5LN.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/UQ/VZBO103EOK3C009CQ3SIA2.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/UQ/VZBO103EOK3C009CQ3SIA2.uasset new file mode 100644 index 0000000..3ed8ccb Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/UQ/VZBO103EOK3C009CQ3SIA2.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/VD/6CQW6ZVH27TP37BOERS3L5.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/VD/6CQW6ZVH27TP37BOERS3L5.uasset new file mode 100644 index 0000000..6c0959f Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/VD/6CQW6ZVH27TP37BOERS3L5.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/XD/KJJ0PM9Q2DJXW333FHZ7R0.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/XD/KJJ0PM9Q2DJXW333FHZ7R0.uasset new file mode 100644 index 0000000..1004878 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/2/XD/KJJ0PM9Q2DJXW333FHZ7R0.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/3E/2AV29D92911SDW8QKQ24BX.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/3E/2AV29D92911SDW8QKQ24BX.uasset new file mode 100644 index 0000000..9d96a5a Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/3E/2AV29D92911SDW8QKQ24BX.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/85/TBH0DY74DPFG02QFP4HVBY.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/85/TBH0DY74DPFG02QFP4HVBY.uasset new file mode 100644 index 0000000..e1f4bca Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/85/TBH0DY74DPFG02QFP4HVBY.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/8W/TGNMGZGKIRPV4FRVB95PUT.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/8W/TGNMGZGKIRPV4FRVB95PUT.uasset new file mode 100644 index 0000000..d57ae47 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/8W/TGNMGZGKIRPV4FRVB95PUT.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/CJ/8IMDJ47RUM6RR9KKJ07RMR.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/CJ/8IMDJ47RUM6RR9KKJ07RMR.uasset new file mode 100644 index 0000000..3374506 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/CJ/8IMDJ47RUM6RR9KKJ07RMR.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/DE/EG6ISGHPR9UK0TU4M6ILXJ.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/DE/EG6ISGHPR9UK0TU4M6ILXJ.uasset new file mode 100644 index 0000000..659aa72 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/DE/EG6ISGHPR9UK0TU4M6ILXJ.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/G1/8K6PTWH7EC700FSPEZEBAI.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/G1/8K6PTWH7EC700FSPEZEBAI.uasset new file mode 100644 index 0000000..5354b3f Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/G1/8K6PTWH7EC700FSPEZEBAI.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/GU/HHTBIYESCLLX4M9TYH2V2O.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/GU/HHTBIYESCLLX4M9TYH2V2O.uasset new file mode 100644 index 0000000..ffdfa22 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/GU/HHTBIYESCLLX4M9TYH2V2O.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/LU/MQL9YZXJP8NMBT4NJS35FE.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/LU/MQL9YZXJP8NMBT4NJS35FE.uasset new file mode 100644 index 0000000..259047f Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/3/LU/MQL9YZXJP8NMBT4NJS35FE.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/2G/4AWBEWEWJUNQ9436Q44MM8.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/2G/4AWBEWEWJUNQ9436Q44MM8.uasset new file mode 100644 index 0000000..29981e8 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/2G/4AWBEWEWJUNQ9436Q44MM8.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/3C/BUY1N1NIXKH5ZMH2B9OHQS.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/3C/BUY1N1NIXKH5ZMH2B9OHQS.uasset new file mode 100644 index 0000000..35c5d59 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/3C/BUY1N1NIXKH5ZMH2B9OHQS.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/4Z/0CC2QQCZ4BB1D3AHY0Q906.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/4Z/0CC2QQCZ4BB1D3AHY0Q906.uasset new file mode 100644 index 0000000..17b591b Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/4Z/0CC2QQCZ4BB1D3AHY0Q906.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/74/O2DL9QU8VK5L7HY1JO6IXG.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/74/O2DL9QU8VK5L7HY1JO6IXG.uasset new file mode 100644 index 0000000..029a404 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/74/O2DL9QU8VK5L7HY1JO6IXG.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/9S/CN0ENC7UB9EH44A15Z9DS5.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/9S/CN0ENC7UB9EH44A15Z9DS5.uasset new file mode 100644 index 0000000..f1b1292 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/9S/CN0ENC7UB9EH44A15Z9DS5.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/C9/WRKC9ED7V99C15LK7N52RX.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/C9/WRKC9ED7V99C15LK7N52RX.uasset new file mode 100644 index 0000000..ea8fa96 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/C9/WRKC9ED7V99C15LK7N52RX.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/DQ/NDUDVEMBDWYTZMRHKUF2S1.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/DQ/NDUDVEMBDWYTZMRHKUF2S1.uasset new file mode 100644 index 0000000..495bb21 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/DQ/NDUDVEMBDWYTZMRHKUF2S1.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/E0/3GAWGDPJRLAX24672Y8QI4.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/E0/3GAWGDPJRLAX24672Y8QI4.uasset new file mode 100644 index 0000000..ff1b2ad Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/E0/3GAWGDPJRLAX24672Y8QI4.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/K0/SB6TUKZ0O9035RUL6J9QPV.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/K0/SB6TUKZ0O9035RUL6J9QPV.uasset new file mode 100644 index 0000000..eb5bd09 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/K0/SB6TUKZ0O9035RUL6J9QPV.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/MK/0B83XTNUO53M03D9U4EFOU.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/MK/0B83XTNUO53M03D9U4EFOU.uasset new file mode 100644 index 0000000..39bb28e Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/MK/0B83XTNUO53M03D9U4EFOU.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/Q3/S3DCYMNYH5AAJ7KBVZMOEP.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/Q3/S3DCYMNYH5AAJ7KBVZMOEP.uasset new file mode 100644 index 0000000..4da33bc Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/Q3/S3DCYMNYH5AAJ7KBVZMOEP.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/RN/Z1AJIFWKGGP237W3JG3GPE.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/RN/Z1AJIFWKGGP237W3JG3GPE.uasset new file mode 100644 index 0000000..d92455a Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/RN/Z1AJIFWKGGP237W3JG3GPE.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/T2/M3Q0393W11ULYHGZPBGWES.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/T2/M3Q0393W11ULYHGZPBGWES.uasset new file mode 100644 index 0000000..d42d8b0 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/4/T2/M3Q0393W11ULYHGZPBGWES.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/1I/K236CQU20O4O3XE1TLBV3U.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/1I/K236CQU20O4O3XE1TLBV3U.uasset new file mode 100644 index 0000000..fe86bec Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/1I/K236CQU20O4O3XE1TLBV3U.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/5P/CWCQ7HWLG4FMXT0ZRC4TI0.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/5P/CWCQ7HWLG4FMXT0ZRC4TI0.uasset new file mode 100644 index 0000000..d05490b Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/5P/CWCQ7HWLG4FMXT0ZRC4TI0.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/5V/2ZKI0MH4J35IQ435354JH3.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/5V/2ZKI0MH4J35IQ435354JH3.uasset new file mode 100644 index 0000000..98faed8 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/5V/2ZKI0MH4J35IQ435354JH3.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/B7/C4HUJNJJBL0Y0DBF5HV1PH.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/B7/C4HUJNJJBL0Y0DBF5HV1PH.uasset new file mode 100644 index 0000000..2753d7b Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/B7/C4HUJNJJBL0Y0DBF5HV1PH.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/BF/BFIXU5567HNRMQP5MJY0O1.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/BF/BFIXU5567HNRMQP5MJY0O1.uasset new file mode 100644 index 0000000..06ef1c9 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/BF/BFIXU5567HNRMQP5MJY0O1.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/EK/9F37QKGF01YU7CHDKL2KO5.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/EK/9F37QKGF01YU7CHDKL2KO5.uasset new file mode 100644 index 0000000..beacd0b Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/EK/9F37QKGF01YU7CHDKL2KO5.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/OV/ZNSNNPGU4O3LUJM2II2DZH.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/OV/ZNSNNPGU4O3LUJM2II2DZH.uasset new file mode 100644 index 0000000..ab1df00 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/5/OV/ZNSNNPGU4O3LUJM2II2DZH.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/6/58/RK3NC5MNSOW06DYLLR8400.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/6/58/RK3NC5MNSOW06DYLLR8400.uasset new file mode 100644 index 0000000..10cbbf4 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/6/58/RK3NC5MNSOW06DYLLR8400.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/6/GZ/GUJXQ0SLRQY9GOI1S9KFYR.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/6/GZ/GUJXQ0SLRQY9GOI1S9KFYR.uasset new file mode 100644 index 0000000..f357334 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/6/GZ/GUJXQ0SLRQY9GOI1S9KFYR.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/6/JG/SVPF4PAWLB95N3A3454HRT.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/6/JG/SVPF4PAWLB95N3A3454HRT.uasset new file mode 100644 index 0000000..1d5e109 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/6/JG/SVPF4PAWLB95N3A3454HRT.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/6/RY/MYVMN2NWF4QNT14AQUADS9.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/6/RY/MYVMN2NWF4QNT14AQUADS9.uasset new file mode 100644 index 0000000..38a4238 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/6/RY/MYVMN2NWF4QNT14AQUADS9.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/6/ZZ/Y9F2IUUMWMKN65LVGL93M3.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/6/ZZ/Y9F2IUUMWMKN65LVGL93M3.uasset new file mode 100644 index 0000000..3fe2b76 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/6/ZZ/Y9F2IUUMWMKN65LVGL93M3.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/3E/FM5O1XVO2BJK8VOAB6TSPC.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/3E/FM5O1XVO2BJK8VOAB6TSPC.uasset new file mode 100644 index 0000000..0929a32 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/3E/FM5O1XVO2BJK8VOAB6TSPC.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/8Q/5HSFA3MSWAHTHR0JJALDVL.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/8Q/5HSFA3MSWAHTHR0JJALDVL.uasset new file mode 100644 index 0000000..cbe3b61 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/8Q/5HSFA3MSWAHTHR0JJALDVL.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/BM/7479BCOZ59Z9SD4ZOM4F9V.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/BM/7479BCOZ59Z9SD4ZOM4F9V.uasset new file mode 100644 index 0000000..3cd5c0e Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/BM/7479BCOZ59Z9SD4ZOM4F9V.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/JK/7MZAAVLMCDJG8N39T8CHSV.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/JK/7MZAAVLMCDJG8N39T8CHSV.uasset new file mode 100644 index 0000000..6739821 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/JK/7MZAAVLMCDJG8N39T8CHSV.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/OM/H0URW02T819PVDV28FETOW.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/OM/H0URW02T819PVDV28FETOW.uasset new file mode 100644 index 0000000..7852e6b Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/OM/H0URW02T819PVDV28FETOW.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/SI/O3Y0LUWOXJ0ISZ4831L7JJ.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/SI/O3Y0LUWOXJ0ISZ4831L7JJ.uasset new file mode 100644 index 0000000..b4579b6 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/SI/O3Y0LUWOXJ0ISZ4831L7JJ.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/UA/57QE41VBC21TQVJDEOPIDD.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/UA/57QE41VBC21TQVJDEOPIDD.uasset new file mode 100644 index 0000000..5565414 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/UA/57QE41VBC21TQVJDEOPIDD.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/XH/LZHLJ4KVLTW803TFRP2L6G.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/XH/LZHLJ4KVLTW803TFRP2L6G.uasset new file mode 100644 index 0000000..a860a54 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/XH/LZHLJ4KVLTW803TFRP2L6G.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/YL/Z1OC45KSRGWRYP06FOLI06.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/YL/Z1OC45KSRGWRYP06FOLI06.uasset new file mode 100644 index 0000000..142e9af Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/YL/Z1OC45KSRGWRYP06FOLI06.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/ZL/CM32V8FT81XRL4H1KIRZ82.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/ZL/CM32V8FT81XRL4H1KIRZ82.uasset new file mode 100644 index 0000000..15bbfbc Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/7/ZL/CM32V8FT81XRL4H1KIRZ82.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/8/DC/S1EW4KLHAUP0J6WDU070LV.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/8/DC/S1EW4KLHAUP0J6WDU070LV.uasset new file mode 100644 index 0000000..10651de Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/8/DC/S1EW4KLHAUP0J6WDU070LV.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/8/EP/MZT58AKN5FYJDSWXBTJ4J5.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/8/EP/MZT58AKN5FYJDSWXBTJ4J5.uasset new file mode 100644 index 0000000..90f9747 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/8/EP/MZT58AKN5FYJDSWXBTJ4J5.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/8/NJ/7JIU4N386J0Y6M6DDYAUM4.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/8/NJ/7JIU4N386J0Y6M6DDYAUM4.uasset new file mode 100644 index 0000000..82b47bb Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/8/NJ/7JIU4N386J0Y6M6DDYAUM4.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/8/VT/E69L6BT44SM44CXN0MTR63.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/8/VT/E69L6BT44SM44CXN0MTR63.uasset new file mode 100644 index 0000000..369623a Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/8/VT/E69L6BT44SM44CXN0MTR63.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/1J/16WYQZ5CWG9P0JJ5N5MEYL.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/1J/16WYQZ5CWG9P0JJ5N5MEYL.uasset new file mode 100644 index 0000000..e16d024 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/1J/16WYQZ5CWG9P0JJ5N5MEYL.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/42/MYS8TTPTRDM4WQJQIA3JBR.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/42/MYS8TTPTRDM4WQJQIA3JBR.uasset new file mode 100644 index 0000000..b22ced0 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/42/MYS8TTPTRDM4WQJQIA3JBR.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/5Y/RL8GWHB7J9LK8ABYT86KFK.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/5Y/RL8GWHB7J9LK8ABYT86KFK.uasset new file mode 100644 index 0000000..e8b0643 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/5Y/RL8GWHB7J9LK8ABYT86KFK.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/7S/4WSAUZ9CKGL1KAB95PLUKB.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/7S/4WSAUZ9CKGL1KAB95PLUKB.uasset new file mode 100644 index 0000000..e684aa3 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/7S/4WSAUZ9CKGL1KAB95PLUKB.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/CT/XOY76XWZ289FAGBPE23VGM.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/CT/XOY76XWZ289FAGBPE23VGM.uasset new file mode 100644 index 0000000..629dfdb Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/CT/XOY76XWZ289FAGBPE23VGM.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/F7/BI9AGK5BZ5HC3OEFD71NDX.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/F7/BI9AGK5BZ5HC3OEFD71NDX.uasset new file mode 100644 index 0000000..aeb3d76 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/F7/BI9AGK5BZ5HC3OEFD71NDX.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/L9/ISAVDUPAU52L9Y0B19ZSVR.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/L9/ISAVDUPAU52L9Y0B19ZSVR.uasset new file mode 100644 index 0000000..60aa407 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/L9/ISAVDUPAU52L9Y0B19ZSVR.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/UK/VIHECZYKQBHOHTQJ5HOP5N.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/UK/VIHECZYKQBHOHTQJ5HOP5N.uasset new file mode 100644 index 0000000..74e0e15 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/UK/VIHECZYKQBHOHTQJ5HOP5N.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/WD/1RTVDV9K0CECLS8KGYNIWK.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/WD/1RTVDV9K0CECLS8KGYNIWK.uasset new file mode 100644 index 0000000..f3c5dc4 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/WD/1RTVDV9K0CECLS8KGYNIWK.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/YB/DGOD6T41WFMLGX79EE8K93.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/YB/DGOD6T41WFMLGX79EE8K93.uasset new file mode 100644 index 0000000..89e7136 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/9/YB/DGOD6T41WFMLGX79EE8K93.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/1C/B670KN293LRVUI9KPDJYZQ.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/1C/B670KN293LRVUI9KPDJYZQ.uasset new file mode 100644 index 0000000..c9e249b Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/1C/B670KN293LRVUI9KPDJYZQ.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/4Y/92E37ZMC15SJHI4WVOAUJF.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/4Y/92E37ZMC15SJHI4WVOAUJF.uasset new file mode 100644 index 0000000..d9746ca Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/4Y/92E37ZMC15SJHI4WVOAUJF.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/6J/4ZIILPFTQ6GFUC2XONSULT.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/6J/4ZIILPFTQ6GFUC2XONSULT.uasset new file mode 100644 index 0000000..43b9975 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/6J/4ZIILPFTQ6GFUC2XONSULT.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/7Y/VJ8782056EN0L3U58SXFHI.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/7Y/VJ8782056EN0L3U58SXFHI.uasset new file mode 100644 index 0000000..c58cb82 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/7Y/VJ8782056EN0L3U58SXFHI.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/IZ/RAFMPUO8GU8O5DPTYQX1R0.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/IZ/RAFMPUO8GU8O5DPTYQX1R0.uasset new file mode 100644 index 0000000..d7103fe Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/IZ/RAFMPUO8GU8O5DPTYQX1R0.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/RX/HCSASZZNTMSQ8CQ8W60SV1.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/RX/HCSASZZNTMSQ8CQ8W60SV1.uasset new file mode 100644 index 0000000..18ec006 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/RX/HCSASZZNTMSQ8CQ8W60SV1.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/T7/0YPDDNSZW9JPKHNAMZOYH6.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/T7/0YPDDNSZW9JPKHNAMZOYH6.uasset new file mode 100644 index 0000000..ed2d534 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/T7/0YPDDNSZW9JPKHNAMZOYH6.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/U6/PB0X7ZBOLQE6CEQQV3DVW5.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/U6/PB0X7ZBOLQE6CEQQV3DVW5.uasset new file mode 100644 index 0000000..cab226d Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/A/U6/PB0X7ZBOLQE6CEQQV3DVW5.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/3C/RADELFHIH52GWIX5947EMP.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/3C/RADELFHIH52GWIX5947EMP.uasset new file mode 100644 index 0000000..1cfc3e4 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/3C/RADELFHIH52GWIX5947EMP.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/41/958IOENSA2728UXMR02FI9.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/41/958IOENSA2728UXMR02FI9.uasset new file mode 100644 index 0000000..cb099af Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/41/958IOENSA2728UXMR02FI9.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/B3/SAWMSAU314L3NNBWR7W4D6.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/B3/SAWMSAU314L3NNBWR7W4D6.uasset new file mode 100644 index 0000000..1b497d1 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/B3/SAWMSAU314L3NNBWR7W4D6.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/FI/RBBFX2WPX0SZJJYN6C8QXA.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/FI/RBBFX2WPX0SZJJYN6C8QXA.uasset new file mode 100644 index 0000000..eb6ef8b Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/FI/RBBFX2WPX0SZJJYN6C8QXA.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/IS/GP5HYM7VCE5DAG4X5BCLTR.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/IS/GP5HYM7VCE5DAG4X5BCLTR.uasset new file mode 100644 index 0000000..317dc87 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/IS/GP5HYM7VCE5DAG4X5BCLTR.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/NJ/5Y1VQUNTF8AFC4KPGYNOPY.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/NJ/5Y1VQUNTF8AFC4KPGYNOPY.uasset new file mode 100644 index 0000000..5374d2a Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/NJ/5Y1VQUNTF8AFC4KPGYNOPY.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/RM/0ZAUNU1TI4K8V3F9W7FWX1.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/RM/0ZAUNU1TI4K8V3F9W7FWX1.uasset new file mode 100644 index 0000000..ba1759a Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/RM/0ZAUNU1TI4K8V3F9W7FWX1.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/S1/K7PEL8F0LO6OYJG2NMIOPV.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/S1/K7PEL8F0LO6OYJG2NMIOPV.uasset new file mode 100644 index 0000000..2d140c9 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/S1/K7PEL8F0LO6OYJG2NMIOPV.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/U6/OYWNWHCBUR6VJ1KQ2YG1EL.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/U6/OYWNWHCBUR6VJ1KQ2YG1EL.uasset new file mode 100644 index 0000000..a0762fc Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/U6/OYWNWHCBUR6VJ1KQ2YG1EL.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/YE/OSR9D90IZ8XS3U3U9JOZMT.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/YE/OSR9D90IZ8XS3U3U9JOZMT.uasset new file mode 100644 index 0000000..6888575 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/B/YE/OSR9D90IZ8XS3U3U9JOZMT.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/0V/XLEE1T2PHH0O58PW97TAGB.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/0V/XLEE1T2PHH0O58PW97TAGB.uasset new file mode 100644 index 0000000..8d930d5 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/0V/XLEE1T2PHH0O58PW97TAGB.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/2I/TKDUF5QTLUB3M3LINOWE54.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/2I/TKDUF5QTLUB3M3LINOWE54.uasset new file mode 100644 index 0000000..f6ecb2a Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/2I/TKDUF5QTLUB3M3LINOWE54.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/52/L0UWST7CZ30C6XW4U9WDUN.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/52/L0UWST7CZ30C6XW4U9WDUN.uasset new file mode 100644 index 0000000..cae2bc8 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/52/L0UWST7CZ30C6XW4U9WDUN.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/5J/S7RNJ6GME3RDR5K4S4BA7C.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/5J/S7RNJ6GME3RDR5K4S4BA7C.uasset new file mode 100644 index 0000000..d84f9cf Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/5J/S7RNJ6GME3RDR5K4S4BA7C.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/CF/CCVPG4FRPH1SRANJGHB4AW.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/CF/CCVPG4FRPH1SRANJGHB4AW.uasset new file mode 100644 index 0000000..f3dedce Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/CF/CCVPG4FRPH1SRANJGHB4AW.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/D3/0IZLBUD8D8FRFZ3X6FS0NL.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/D3/0IZLBUD8D8FRFZ3X6FS0NL.uasset new file mode 100644 index 0000000..68305d1 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/D3/0IZLBUD8D8FRFZ3X6FS0NL.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/DN/PSZEID3KH3UCA6N4JJ7EH5.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/DN/PSZEID3KH3UCA6N4JJ7EH5.uasset new file mode 100644 index 0000000..71dce34 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/DN/PSZEID3KH3UCA6N4JJ7EH5.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/H3/0XSTNQS7WWXGF0W0R0MHYH.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/H3/0XSTNQS7WWXGF0W0R0MHYH.uasset new file mode 100644 index 0000000..b1c7068 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/H3/0XSTNQS7WWXGF0W0R0MHYH.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/IC/82GXHIQ1LBSS75HKJS22Z1.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/IC/82GXHIQ1LBSS75HKJS22Z1.uasset new file mode 100644 index 0000000..ac8b62b Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/IC/82GXHIQ1LBSS75HKJS22Z1.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/OQ/QSHJBZFA3DH4WUMPYR0F5P.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/OQ/QSHJBZFA3DH4WUMPYR0F5P.uasset new file mode 100644 index 0000000..0de6ed8 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/OQ/QSHJBZFA3DH4WUMPYR0F5P.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/R9/L3U9JKOV9CIMSF7EVCJIZH.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/R9/L3U9JKOV9CIMSF7EVCJIZH.uasset new file mode 100644 index 0000000..0b7b236 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/R9/L3U9JKOV9CIMSF7EVCJIZH.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/S5/R35THGWMTZWY37KCLGIB7L.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/S5/R35THGWMTZWY37KCLGIB7L.uasset new file mode 100644 index 0000000..7fe9f81 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/S5/R35THGWMTZWY37KCLGIB7L.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/WL/P9Y29NLS25OX7A4GU66A6A.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/WL/P9Y29NLS25OX7A4GU66A6A.uasset new file mode 100644 index 0000000..56aa35f Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/WL/P9Y29NLS25OX7A4GU66A6A.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/ZI/29MX5OP77Y9OOZ7V1YCBUM.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/ZI/29MX5OP77Y9OOZ7V1YCBUM.uasset new file mode 100644 index 0000000..977cb1f Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/C/ZI/29MX5OP77Y9OOZ7V1YCBUM.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/09/ZZRW8OHLRZDO5O0926MMPR.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/09/ZZRW8OHLRZDO5O0926MMPR.uasset new file mode 100644 index 0000000..efff761 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/09/ZZRW8OHLRZDO5O0926MMPR.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/0N/93KJOJF9EXQN0RAXMTNKDR.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/0N/93KJOJF9EXQN0RAXMTNKDR.uasset new file mode 100644 index 0000000..f2aee30 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/0N/93KJOJF9EXQN0RAXMTNKDR.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/61/ITYUBBWV7Y4IQXOLL3OQJZ.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/61/ITYUBBWV7Y4IQXOLL3OQJZ.uasset new file mode 100644 index 0000000..cbe35ed Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/61/ITYUBBWV7Y4IQXOLL3OQJZ.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/75/S72Z416K7C5SQ26QAZYHIC.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/75/S72Z416K7C5SQ26QAZYHIC.uasset new file mode 100644 index 0000000..30aec5f Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/75/S72Z416K7C5SQ26QAZYHIC.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/AC/AU94FKCJ1ZPAVYN19SWNSR.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/AC/AU94FKCJ1ZPAVYN19SWNSR.uasset new file mode 100644 index 0000000..a888991 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/AC/AU94FKCJ1ZPAVYN19SWNSR.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/B0/ZE8C59CK81PJ77IL1LVLQU.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/B0/ZE8C59CK81PJ77IL1LVLQU.uasset new file mode 100644 index 0000000..79a130c Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/B0/ZE8C59CK81PJ77IL1LVLQU.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/O0/ICNATY20XOMXIO9PWTLC46.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/O0/ICNATY20XOMXIO9PWTLC46.uasset new file mode 100644 index 0000000..1f00b47 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/O0/ICNATY20XOMXIO9PWTLC46.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/OO/N782ZG44Y5GSTV48DK6NHT.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/OO/N782ZG44Y5GSTV48DK6NHT.uasset new file mode 100644 index 0000000..cdcc38c Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/OO/N782ZG44Y5GSTV48DK6NHT.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/QZ/IDA5I0RVDBWJM3XO7DN7BU.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/QZ/IDA5I0RVDBWJM3XO7DN7BU.uasset new file mode 100644 index 0000000..bd7ec85 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/QZ/IDA5I0RVDBWJM3XO7DN7BU.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/RR/E36ZI764OLQ2HQNVTOL4I0.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/RR/E36ZI764OLQ2HQNVTOL4I0.uasset new file mode 100644 index 0000000..2b7c338 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/RR/E36ZI764OLQ2HQNVTOL4I0.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/ZN/AASHM8JVSL0T04BL1PKPVV.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/ZN/AASHM8JVSL0T04BL1PKPVV.uasset new file mode 100644 index 0000000..08bf4ab Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/D/ZN/AASHM8JVSL0T04BL1PKPVV.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/0W/BXUQNB8M3HHSJ6SCAVEGS7.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/0W/BXUQNB8M3HHSJ6SCAVEGS7.uasset new file mode 100644 index 0000000..a8b771f Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/0W/BXUQNB8M3HHSJ6SCAVEGS7.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/27/GZJOI0Z35GOANSWBGT9QY9.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/27/GZJOI0Z35GOANSWBGT9QY9.uasset new file mode 100644 index 0000000..8f73796 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/27/GZJOI0Z35GOANSWBGT9QY9.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/5H/SXKH8U4XMAUA5AFBNY1M9I.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/5H/SXKH8U4XMAUA5AFBNY1M9I.uasset new file mode 100644 index 0000000..d31f2f3 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/5H/SXKH8U4XMAUA5AFBNY1M9I.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/C3/SA8IH98KFTNCIYFK2VCP5C.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/C3/SA8IH98KFTNCIYFK2VCP5C.uasset new file mode 100644 index 0000000..4960131 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/C3/SA8IH98KFTNCIYFK2VCP5C.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/FF/4T2DI6DVP9S9AFO2356BSH.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/FF/4T2DI6DVP9S9AFO2356BSH.uasset new file mode 100644 index 0000000..2c09bc0 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/FF/4T2DI6DVP9S9AFO2356BSH.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/IM/TWHLFKFH4DB824NJJJLC1H.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/IM/TWHLFKFH4DB824NJJJLC1H.uasset new file mode 100644 index 0000000..f77daf7 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/IM/TWHLFKFH4DB824NJJJLC1H.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/J9/Q4B9M2NH0W56UM3XIMZMOZ.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/J9/Q4B9M2NH0W56UM3XIMZMOZ.uasset new file mode 100644 index 0000000..87414aa Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/J9/Q4B9M2NH0W56UM3XIMZMOZ.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/K5/S1VK675CG7GLA667I2TYT5.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/K5/S1VK675CG7GLA667I2TYT5.uasset new file mode 100644 index 0000000..331e0d5 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/K5/S1VK675CG7GLA667I2TYT5.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/KK/RKM41UD17P08BSJT6UZVLJ.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/KK/RKM41UD17P08BSJT6UZVLJ.uasset new file mode 100644 index 0000000..ef3b9f0 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/KK/RKM41UD17P08BSJT6UZVLJ.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/Y5/G83AFQCGFHSD24L1L4S5WO.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/Y5/G83AFQCGFHSD24L1L4S5WO.uasset new file mode 100644 index 0000000..6e390ce Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/Y5/G83AFQCGFHSD24L1L4S5WO.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/YO/13HRCDN18BGJI0B1LGKN1T.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/YO/13HRCDN18BGJI0B1LGKN1T.uasset new file mode 100644 index 0000000..f8da7b8 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/E/YO/13HRCDN18BGJI0B1LGKN1T.uasset differ diff --git a/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/F/5B/VVZB345TJTB0CL0BS349YF.uasset b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/F/5B/VVZB345TJTB0CL0BS349YF.uasset new file mode 100644 index 0000000..6c3f3a5 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Combat/Lvl_Combat/F/5B/VVZB345TJTB0CL0BS349YF.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/4B/I5O7HJ57YPKKYPY4SSFLZZ.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/4B/I5O7HJ57YPKKYPY4SSFLZZ.uasset new file mode 100644 index 0000000..b8799cc Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/4B/I5O7HJ57YPKKYPY4SSFLZZ.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/J0/QVXJ1XUJQIZQ2ONEJOW0EI.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/J0/QVXJ1XUJQIZQ2ONEJOW0EI.uasset new file mode 100644 index 0000000..cb27245 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/J0/QVXJ1XUJQIZQ2ONEJOW0EI.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/JN/C6PH50SPLFLBWA0OBWMQ5F.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/JN/C6PH50SPLFLBWA0OBWMQ5F.uasset new file mode 100644 index 0000000..1f34354 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/JN/C6PH50SPLFLBWA0OBWMQ5F.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/KB/70R3R0AIAG8WCGCAGQ9CY3.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/KB/70R3R0AIAG8WCGCAGQ9CY3.uasset new file mode 100644 index 0000000..646901c Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/KB/70R3R0AIAG8WCGCAGQ9CY3.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/L0/7JFGLNLOCKI0V1IMMACK8B.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/L0/7JFGLNLOCKI0V1IMMACK8B.uasset new file mode 100644 index 0000000..4217d12 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/L0/7JFGLNLOCKI0V1IMMACK8B.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/LN/6XUGMACJHTF9UVGA53PLQ9.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/LN/6XUGMACJHTF9UVGA53PLQ9.uasset new file mode 100644 index 0000000..bc734fb Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/LN/6XUGMACJHTF9UVGA53PLQ9.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/OK/2FZGASC74XU6I8LRJ4B9LO.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/OK/2FZGASC74XU6I8LRJ4B9LO.uasset new file mode 100644 index 0000000..f0f363e Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/OK/2FZGASC74XU6I8LRJ4B9LO.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/Q1/Q5QAZW7H2QSS3YP9WWH1SC.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/Q1/Q5QAZW7H2QSS3YP9WWH1SC.uasset new file mode 100644 index 0000000..cc2e1d9 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/Q1/Q5QAZW7H2QSS3YP9WWH1SC.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/TH/TXPB77A5UO9PLNG838CR59.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/TH/TXPB77A5UO9PLNG838CR59.uasset new file mode 100644 index 0000000..59e591d Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/TH/TXPB77A5UO9PLNG838CR59.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/XS/21O3C30HDVFRXZ0RL7PRMF.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/XS/21O3C30HDVFRXZ0RL7PRMF.uasset new file mode 100644 index 0000000..f0a27c1 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/0/XS/21O3C30HDVFRXZ0RL7PRMF.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/1O/DVDF8QZG4S920O3BQWHRO1.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/1O/DVDF8QZG4S920O3BQWHRO1.uasset new file mode 100644 index 0000000..3331179 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/1O/DVDF8QZG4S920O3BQWHRO1.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/32/9OI6KLDGH7UIFHL3YRPU1R.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/32/9OI6KLDGH7UIFHL3YRPU1R.uasset new file mode 100644 index 0000000..84ea305 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/32/9OI6KLDGH7UIFHL3YRPU1R.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/3E/MP74I7I6B8KHZ3XNPOIUQK.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/3E/MP74I7I6B8KHZ3XNPOIUQK.uasset new file mode 100644 index 0000000..6631f3a Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/3E/MP74I7I6B8KHZ3XNPOIUQK.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/4V/AM9A6SENLG8J1ID4WGVI4L.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/4V/AM9A6SENLG8J1ID4WGVI4L.uasset new file mode 100644 index 0000000..ec7f367 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/4V/AM9A6SENLG8J1ID4WGVI4L.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/51/5Y69443HPJ0BUZGAUHEJK3.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/51/5Y69443HPJ0BUZGAUHEJK3.uasset new file mode 100644 index 0000000..9f41a50 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/51/5Y69443HPJ0BUZGAUHEJK3.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/97/XM7WH6EAJNTGEZ63GE7FJ6.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/97/XM7WH6EAJNTGEZ63GE7FJ6.uasset new file mode 100644 index 0000000..267061f Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/97/XM7WH6EAJNTGEZ63GE7FJ6.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/GA/OEGB3A3ENQT70XN5J4YKQJ.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/GA/OEGB3A3ENQT70XN5J4YKQJ.uasset new file mode 100644 index 0000000..336a02b Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/GA/OEGB3A3ENQT70XN5J4YKQJ.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/L5/QG3Z4YZSPOZSHGICCUH31A.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/L5/QG3Z4YZSPOZSHGICCUH31A.uasset new file mode 100644 index 0000000..b3e699c Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/L5/QG3Z4YZSPOZSHGICCUH31A.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/N4/PELUAFNLTUJDHSXON93S6I.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/N4/PELUAFNLTUJDHSXON93S6I.uasset new file mode 100644 index 0000000..1b58cc0 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/N4/PELUAFNLTUJDHSXON93S6I.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/PU/K4YJRC8WAA2CIGF549BYZR.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/PU/K4YJRC8WAA2CIGF549BYZR.uasset new file mode 100644 index 0000000..1386ae4 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/PU/K4YJRC8WAA2CIGF549BYZR.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/R0/HT23SLNIEQ0O2HXL7LBX4R.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/R0/HT23SLNIEQ0O2HXL7LBX4R.uasset new file mode 100644 index 0000000..59c8d8f Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/R0/HT23SLNIEQ0O2HXL7LBX4R.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/T1/H5NOVVQYGVDZH2QBIXZRI5.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/T1/H5NOVVQYGVDZH2QBIXZRI5.uasset new file mode 100644 index 0000000..3d3365f Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/T1/H5NOVVQYGVDZH2QBIXZRI5.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/U0/YGL0WFXJS6FO10WOKK1V2L.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/U0/YGL0WFXJS6FO10WOKK1V2L.uasset new file mode 100644 index 0000000..7b6dd4d Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/U0/YGL0WFXJS6FO10WOKK1V2L.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/YF/EKAD5DGG3DXWMMY6JCZYDE.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/YF/EKAD5DGG3DXWMMY6JCZYDE.uasset new file mode 100644 index 0000000..4790292 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/YF/EKAD5DGG3DXWMMY6JCZYDE.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/ZP/9I0OZ8R10E9S06ETOOJA63.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/ZP/9I0OZ8R10E9S06ETOOJA63.uasset new file mode 100644 index 0000000..4e73004 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/1/ZP/9I0OZ8R10E9S06ETOOJA63.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/1C/ZUE27ERNTBGXJY0JQR479S.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/1C/ZUE27ERNTBGXJY0JQR479S.uasset new file mode 100644 index 0000000..138e75a Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/1C/ZUE27ERNTBGXJY0JQR479S.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/1R/618POQ41Q2FSSGC1VTL0AJ.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/1R/618POQ41Q2FSSGC1VTL0AJ.uasset new file mode 100644 index 0000000..8386797 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/1R/618POQ41Q2FSSGC1VTL0AJ.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/67/LRD5AFZSX7RLAT58UVOF6S.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/67/LRD5AFZSX7RLAT58UVOF6S.uasset new file mode 100644 index 0000000..ba6998f Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/67/LRD5AFZSX7RLAT58UVOF6S.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/K8/5Z205Q47ZTF15T5Q87JU7E.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/K8/5Z205Q47ZTF15T5Q87JU7E.uasset new file mode 100644 index 0000000..2ca91a4 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/K8/5Z205Q47ZTF15T5Q87JU7E.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/L6/9KHQMN7IUK8SMKHH3VX6M3.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/L6/9KHQMN7IUK8SMKHH3VX6M3.uasset new file mode 100644 index 0000000..5523f9e Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/L6/9KHQMN7IUK8SMKHH3VX6M3.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/OI/MF8ZZS7KVI44SVR0BHY301.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/OI/MF8ZZS7KVI44SVR0BHY301.uasset new file mode 100644 index 0000000..6162cdc Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/OI/MF8ZZS7KVI44SVR0BHY301.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/V9/XCZ24R2H8269E10XXKVHNG.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/V9/XCZ24R2H8269E10XXKVHNG.uasset new file mode 100644 index 0000000..c115efb Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/V9/XCZ24R2H8269E10XXKVHNG.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/Z9/6SNYH6YCML05ATNZJMJNLV.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/Z9/6SNYH6YCML05ATNZJMJNLV.uasset new file mode 100644 index 0000000..39c042c Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/2/Z9/6SNYH6YCML05ATNZJMJNLV.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/11/QC3UE5SH63R7HLDSIARHYP.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/11/QC3UE5SH63R7HLDSIARHYP.uasset new file mode 100644 index 0000000..68174d8 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/11/QC3UE5SH63R7HLDSIARHYP.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/1L/ITUZ7HAI87POYJ5CKEJ6JQ.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/1L/ITUZ7HAI87POYJ5CKEJ6JQ.uasset new file mode 100644 index 0000000..d8611c6 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/1L/ITUZ7HAI87POYJ5CKEJ6JQ.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/5C/472HXGW93Z5Y0DTU24MT05.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/5C/472HXGW93Z5Y0DTU24MT05.uasset new file mode 100644 index 0000000..7fa6a8c Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/5C/472HXGW93Z5Y0DTU24MT05.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/69/49UL41JIT9J3Z82DSEOGNR.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/69/49UL41JIT9J3Z82DSEOGNR.uasset new file mode 100644 index 0000000..c41126f Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/69/49UL41JIT9J3Z82DSEOGNR.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/6F/7UY85E4YUBO3IIQ5WZY2TR.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/6F/7UY85E4YUBO3IIQ5WZY2TR.uasset new file mode 100644 index 0000000..fcb7bd5 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/6F/7UY85E4YUBO3IIQ5WZY2TR.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/6H/7ALASFYN40X69PEEB5RXZ2.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/6H/7ALASFYN40X69PEEB5RXZ2.uasset new file mode 100644 index 0000000..6ea3a06 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/6H/7ALASFYN40X69PEEB5RXZ2.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/AD/OOT0TOS7NDYPON55O43E4B.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/AD/OOT0TOS7NDYPON55O43E4B.uasset new file mode 100644 index 0000000..622eaa9 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/AD/OOT0TOS7NDYPON55O43E4B.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/GD/LA9QY6EIOP5MXBBFTM03KI.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/GD/LA9QY6EIOP5MXBBFTM03KI.uasset new file mode 100644 index 0000000..ac1d2ee Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/GD/LA9QY6EIOP5MXBBFTM03KI.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/J1/ZMFUDHBD9FHM68ZT96IA1C.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/J1/ZMFUDHBD9FHM68ZT96IA1C.uasset new file mode 100644 index 0000000..87e3cb1 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/J1/ZMFUDHBD9FHM68ZT96IA1C.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/JO/XBLLR77AXMIP149OV5ULY9.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/JO/XBLLR77AXMIP149OV5ULY9.uasset new file mode 100644 index 0000000..8ba4374 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/JO/XBLLR77AXMIP149OV5ULY9.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/LR/AXA3SZ8RWC8FDWTY56RLDA.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/LR/AXA3SZ8RWC8FDWTY56RLDA.uasset new file mode 100644 index 0000000..25ebb40 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/LR/AXA3SZ8RWC8FDWTY56RLDA.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/SN/0X78Q6B7RDHXMQZYGFLGXK.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/SN/0X78Q6B7RDHXMQZYGFLGXK.uasset new file mode 100644 index 0000000..8bf92a1 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/SN/0X78Q6B7RDHXMQZYGFLGXK.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/VJ/XUG7C1X903GP7S2ZAQ6UPS.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/VJ/XUG7C1X903GP7S2ZAQ6UPS.uasset new file mode 100644 index 0000000..578fb0f Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/VJ/XUG7C1X903GP7S2ZAQ6UPS.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/ZC/GUWXM26Q7AB4QLO5N6TPIP.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/ZC/GUWXM26Q7AB4QLO5N6TPIP.uasset new file mode 100644 index 0000000..9a2d770 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/ZC/GUWXM26Q7AB4QLO5N6TPIP.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/ZO/2FVRIT79WCE6J17NINDFS5.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/ZO/2FVRIT79WCE6J17NINDFS5.uasset new file mode 100644 index 0000000..f058895 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/3/ZO/2FVRIT79WCE6J17NINDFS5.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/2U/FNC5A4JGOWJW5YKO9E4ZYH.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/2U/FNC5A4JGOWJW5YKO9E4ZYH.uasset new file mode 100644 index 0000000..7c4ce1c Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/2U/FNC5A4JGOWJW5YKO9E4ZYH.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/F1/ROZ0POEXKJ0PKXMW4DSCG1.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/F1/ROZ0POEXKJ0PKXMW4DSCG1.uasset new file mode 100644 index 0000000..3dbc8e5 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/F1/ROZ0POEXKJ0PKXMW4DSCG1.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/LU/26JCC0IS872O4TFGUT9O98.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/LU/26JCC0IS872O4TFGUT9O98.uasset new file mode 100644 index 0000000..56a428a Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/LU/26JCC0IS872O4TFGUT9O98.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/OS/M2J2I7C3QIZMSSVZ2GMVVF.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/OS/M2J2I7C3QIZMSSVZ2GMVVF.uasset new file mode 100644 index 0000000..66a0b45 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/OS/M2J2I7C3QIZMSSVZ2GMVVF.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/UV/ZAF0544EAKPRNRKQIP0O01.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/UV/ZAF0544EAKPRNRKQIP0O01.uasset new file mode 100644 index 0000000..dbec7de Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/UV/ZAF0544EAKPRNRKQIP0O01.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/WL/YEFLQ06YUDR45W9UEV3JM5.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/WL/YEFLQ06YUDR45W9UEV3JM5.uasset new file mode 100644 index 0000000..07a768d Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/WL/YEFLQ06YUDR45W9UEV3JM5.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/XS/U8PM1GZLKWHL4FF18EN1ND.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/XS/U8PM1GZLKWHL4FF18EN1ND.uasset new file mode 100644 index 0000000..91dffb2 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/XS/U8PM1GZLKWHL4FF18EN1ND.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/ZF/WCHFY0EDKV80XWG7UZXXIX.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/ZF/WCHFY0EDKV80XWG7UZXXIX.uasset new file mode 100644 index 0000000..d89fd5d Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/4/ZF/WCHFY0EDKV80XWG7UZXXIX.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/10/G2ESKRA6OA5RY0JRT35GRW.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/10/G2ESKRA6OA5RY0JRT35GRW.uasset new file mode 100644 index 0000000..0a966be Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/10/G2ESKRA6OA5RY0JRT35GRW.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/1I/93DDS3U8RVSY7QM30EHTKV.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/1I/93DDS3U8RVSY7QM30EHTKV.uasset new file mode 100644 index 0000000..f39f986 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/1I/93DDS3U8RVSY7QM30EHTKV.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/91/0Q4R1IE3K2BYJSXIZOQ0QP.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/91/0Q4R1IE3K2BYJSXIZOQ0QP.uasset new file mode 100644 index 0000000..3c1e507 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/91/0Q4R1IE3K2BYJSXIZOQ0QP.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/BA/DGFPSX4DTSKSZQ36SD1Y3K.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/BA/DGFPSX4DTSKSZQ36SD1Y3K.uasset new file mode 100644 index 0000000..3632ab3 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/BA/DGFPSX4DTSKSZQ36SD1Y3K.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/CT/GLPCUDYPOA417QPFT25J2M.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/CT/GLPCUDYPOA417QPFT25J2M.uasset new file mode 100644 index 0000000..935c530 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/CT/GLPCUDYPOA417QPFT25J2M.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/D1/PJ7PHXS1Q9YT4RRK8ASA17.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/D1/PJ7PHXS1Q9YT4RRK8ASA17.uasset new file mode 100644 index 0000000..90709da Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/D1/PJ7PHXS1Q9YT4RRK8ASA17.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/DP/15D95A5TUVKKEPBC5Z3V0K.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/DP/15D95A5TUVKKEPBC5Z3V0K.uasset new file mode 100644 index 0000000..0e553fc Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/DP/15D95A5TUVKKEPBC5Z3V0K.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/IL/XNME8P2GEX8RVJYJ5AS9L9.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/IL/XNME8P2GEX8RVJYJ5AS9L9.uasset new file mode 100644 index 0000000..7cf2c2d Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/IL/XNME8P2GEX8RVJYJ5AS9L9.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/PQ/PI85JOOOY2R6IWIS0B5ZV5.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/PQ/PI85JOOOY2R6IWIS0B5ZV5.uasset new file mode 100644 index 0000000..8a961f6 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/PQ/PI85JOOOY2R6IWIS0B5ZV5.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/VB/DD4B02GSXHYDFMAB03BUYE.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/VB/DD4B02GSXHYDFMAB03BUYE.uasset new file mode 100644 index 0000000..b916782 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/5/VB/DD4B02GSXHYDFMAB03BUYE.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/3U/Y7SCQAHPSUVVCZQCNEIKCJ.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/3U/Y7SCQAHPSUVVCZQCNEIKCJ.uasset new file mode 100644 index 0000000..e9b78cc Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/3U/Y7SCQAHPSUVVCZQCNEIKCJ.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/7U/KFRJ8G1ZYZHNS4N6S74ZMY.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/7U/KFRJ8G1ZYZHNS4N6S74ZMY.uasset new file mode 100644 index 0000000..daad457 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/7U/KFRJ8G1ZYZHNS4N6S74ZMY.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/8P/A5XH9J5QDNE58G2EZQY8EN.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/8P/A5XH9J5QDNE58G2EZQY8EN.uasset new file mode 100644 index 0000000..e716716 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/8P/A5XH9J5QDNE58G2EZQY8EN.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/9G/2YEWP1NRKUKTW7IS6S8YM5.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/9G/2YEWP1NRKUKTW7IS6S8YM5.uasset new file mode 100644 index 0000000..5316eff Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/9G/2YEWP1NRKUKTW7IS6S8YM5.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/FD/BLYOF7Y0BIG7Q9IEJJVLIJ.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/FD/BLYOF7Y0BIG7Q9IEJJVLIJ.uasset new file mode 100644 index 0000000..99fe4f3 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/FD/BLYOF7Y0BIG7Q9IEJJVLIJ.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/IR/DGTM6Z6X9MUIW1R1RM6HEM.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/IR/DGTM6Z6X9MUIW1R1RM6HEM.uasset new file mode 100644 index 0000000..69ad728 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/IR/DGTM6Z6X9MUIW1R1RM6HEM.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/KW/DIOBPSCU4ASCOKUXZZGYBP.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/KW/DIOBPSCU4ASCOKUXZZGYBP.uasset new file mode 100644 index 0000000..4ae5cd5 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/KW/DIOBPSCU4ASCOKUXZZGYBP.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/MR/CIPUCZLV4PC998H04BXE50.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/MR/CIPUCZLV4PC998H04BXE50.uasset new file mode 100644 index 0000000..71f3382 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/MR/CIPUCZLV4PC998H04BXE50.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/UN/V2FXYCRO8TWCQFEW9S7ONL.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/UN/V2FXYCRO8TWCQFEW9S7ONL.uasset new file mode 100644 index 0000000..f21e6bf Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/UN/V2FXYCRO8TWCQFEW9S7ONL.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/V3/KTE510QWYNYYC5GPCZILN2.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/V3/KTE510QWYNYYC5GPCZILN2.uasset new file mode 100644 index 0000000..8cff4e4 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/6/V3/KTE510QWYNYYC5GPCZILN2.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/2L/43POZNB1OZZ0WC9OQQWP32.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/2L/43POZNB1OZZ0WC9OQQWP32.uasset new file mode 100644 index 0000000..0983755 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/2L/43POZNB1OZZ0WC9OQQWP32.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/7N/IHKH3QKKSO2BH68HLQE5HW.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/7N/IHKH3QKKSO2BH68HLQE5HW.uasset new file mode 100644 index 0000000..cfabb3b Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/7N/IHKH3QKKSO2BH68HLQE5HW.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/FF/SREIGLJND5CE6ZHB6ORFDA.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/FF/SREIGLJND5CE6ZHB6ORFDA.uasset new file mode 100644 index 0000000..e241152 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/FF/SREIGLJND5CE6ZHB6ORFDA.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/HC/DMJ0WZMFEPSSU5KV3I06DY.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/HC/DMJ0WZMFEPSSU5KV3I06DY.uasset new file mode 100644 index 0000000..83495f8 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/HC/DMJ0WZMFEPSSU5KV3I06DY.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/I3/2DFXQK7NBEGYK26EMWYIAI.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/I3/2DFXQK7NBEGYK26EMWYIAI.uasset new file mode 100644 index 0000000..f83aaf1 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/I3/2DFXQK7NBEGYK26EMWYIAI.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/KT/1DMK92SYFO52BCWH7YW6X1.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/KT/1DMK92SYFO52BCWH7YW6X1.uasset new file mode 100644 index 0000000..7e7535e Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/KT/1DMK92SYFO52BCWH7YW6X1.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/MJ/5XX5M6PJ0MHJDUL7W6DYUJ.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/MJ/5XX5M6PJ0MHJDUL7W6DYUJ.uasset new file mode 100644 index 0000000..698a17f Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/MJ/5XX5M6PJ0MHJDUL7W6DYUJ.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/WH/A20YQ6G8GBT085UFJFYOFD.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/WH/A20YQ6G8GBT085UFJFYOFD.uasset new file mode 100644 index 0000000..f527399 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/WH/A20YQ6G8GBT085UFJFYOFD.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/XH/NCZ6KTAN4JPVHK2TB6KIWE.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/XH/NCZ6KTAN4JPVHK2TB6KIWE.uasset new file mode 100644 index 0000000..c2ba393 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/7/XH/NCZ6KTAN4JPVHK2TB6KIWE.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/0Y/JZOJQOJ8LAMK8MRFA4Y2CN.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/0Y/JZOJQOJ8LAMK8MRFA4Y2CN.uasset new file mode 100644 index 0000000..5ce387a Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/0Y/JZOJQOJ8LAMK8MRFA4Y2CN.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/16/M2H1K5CDOD4X9UOOKN3G2R.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/16/M2H1K5CDOD4X9UOOKN3G2R.uasset new file mode 100644 index 0000000..bd6ae9b Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/16/M2H1K5CDOD4X9UOOKN3G2R.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/2U/XD95IMXOS47D6E9UJR2SNJ.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/2U/XD95IMXOS47D6E9UJR2SNJ.uasset new file mode 100644 index 0000000..fa29ff3 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/2U/XD95IMXOS47D6E9UJR2SNJ.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/5K/WT2N6PIFH19MI313NH2V8D.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/5K/WT2N6PIFH19MI313NH2V8D.uasset new file mode 100644 index 0000000..f35db8b Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/5K/WT2N6PIFH19MI313NH2V8D.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/6P/E9NE3R4WWBFLO7DM3YVJ25.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/6P/E9NE3R4WWBFLO7DM3YVJ25.uasset new file mode 100644 index 0000000..a7bb879 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/6P/E9NE3R4WWBFLO7DM3YVJ25.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/6U/9N30SLQBL4GCYWZAABJERE.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/6U/9N30SLQBL4GCYWZAABJERE.uasset new file mode 100644 index 0000000..84db8a9 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/6U/9N30SLQBL4GCYWZAABJERE.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/7K/H1IDQB3F7XR0G6D56GBMU6.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/7K/H1IDQB3F7XR0G6D56GBMU6.uasset new file mode 100644 index 0000000..e30d2ea Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/7K/H1IDQB3F7XR0G6D56GBMU6.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/AJ/XJF07VB8ISEFI4M87N4CXF.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/AJ/XJF07VB8ISEFI4M87N4CXF.uasset new file mode 100644 index 0000000..4c5a1f1 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/AJ/XJF07VB8ISEFI4M87N4CXF.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/DH/OC6G68ED5VJZVW8EJQA76B.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/DH/OC6G68ED5VJZVW8EJQA76B.uasset new file mode 100644 index 0000000..30d0a76 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/DH/OC6G68ED5VJZVW8EJQA76B.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/GN/DIDACECEDK7DQHGSZF35H6.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/GN/DIDACECEDK7DQHGSZF35H6.uasset new file mode 100644 index 0000000..ec3b2a2 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/GN/DIDACECEDK7DQHGSZF35H6.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/HS/R85DVK392RC4R0J9AJJSDX.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/HS/R85DVK392RC4R0J9AJJSDX.uasset new file mode 100644 index 0000000..8275e20 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/HS/R85DVK392RC4R0J9AJJSDX.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/L1/PNG8TF54EI2EAW897D3V0R.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/L1/PNG8TF54EI2EAW897D3V0R.uasset new file mode 100644 index 0000000..bf06089 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/L1/PNG8TF54EI2EAW897D3V0R.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/LP/95YG2KJ4S2ZT9TNGF4O1UA.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/LP/95YG2KJ4S2ZT9TNGF4O1UA.uasset new file mode 100644 index 0000000..b5bf80e Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/LP/95YG2KJ4S2ZT9TNGF4O1UA.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/NQ/B9RELJCTYQ8L9Y6YSJCZIA.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/NQ/B9RELJCTYQ8L9Y6YSJCZIA.uasset new file mode 100644 index 0000000..3784403 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/NQ/B9RELJCTYQ8L9Y6YSJCZIA.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/XL/7MKLADN1ZTTQ9FPVTBJGUD.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/XL/7MKLADN1ZTTQ9FPVTBJGUD.uasset new file mode 100644 index 0000000..077447a Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/8/XL/7MKLADN1ZTTQ9FPVTBJGUD.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/1I/FU34616O1QKKFXMD67EPG7.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/1I/FU34616O1QKKFXMD67EPG7.uasset new file mode 100644 index 0000000..27c39af Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/1I/FU34616O1QKKFXMD67EPG7.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/2Z/K9Y7T981NPFQB0F0ODWMT7.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/2Z/K9Y7T981NPFQB0F0ODWMT7.uasset new file mode 100644 index 0000000..4da9bac Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/2Z/K9Y7T981NPFQB0F0ODWMT7.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/40/03OMEJKEKXOLMSAJLN6AF0.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/40/03OMEJKEKXOLMSAJLN6AF0.uasset new file mode 100644 index 0000000..5e2609c Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/40/03OMEJKEKXOLMSAJLN6AF0.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/91/EMMTW3081FS3SV7WH9BV88.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/91/EMMTW3081FS3SV7WH9BV88.uasset new file mode 100644 index 0000000..278ef56 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/91/EMMTW3081FS3SV7WH9BV88.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/DQ/SBFD5M675LVD68N22SGIWH.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/DQ/SBFD5M675LVD68N22SGIWH.uasset new file mode 100644 index 0000000..67bd5f7 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/DQ/SBFD5M675LVD68N22SGIWH.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/HL/KDU8U69V1XMLLWEBDOOFTK.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/HL/KDU8U69V1XMLLWEBDOOFTK.uasset new file mode 100644 index 0000000..2584990 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/HL/KDU8U69V1XMLLWEBDOOFTK.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/HU/PMPASKH2GKH319Q1CFMPRM.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/HU/PMPASKH2GKH319Q1CFMPRM.uasset new file mode 100644 index 0000000..863619c Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/HU/PMPASKH2GKH319Q1CFMPRM.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/IO/VM3C7C9TQPPAFFJU31MCU7.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/IO/VM3C7C9TQPPAFFJU31MCU7.uasset new file mode 100644 index 0000000..ffe92e9 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/IO/VM3C7C9TQPPAFFJU31MCU7.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/JR/THXBS4Q7NOST3UFAAZK5R4.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/JR/THXBS4Q7NOST3UFAAZK5R4.uasset new file mode 100644 index 0000000..57b0f09 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/JR/THXBS4Q7NOST3UFAAZK5R4.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/UW/FS31A2FNT9GW4YINH4S00F.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/UW/FS31A2FNT9GW4YINH4S00F.uasset new file mode 100644 index 0000000..7dd21f1 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/UW/FS31A2FNT9GW4YINH4S00F.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/UZ/8KR57T843NS9S301MPFH8D.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/UZ/8KR57T843NS9S301MPFH8D.uasset new file mode 100644 index 0000000..2695f4b Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/9/UZ/8KR57T843NS9S301MPFH8D.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/37/3MDT8UZULS2IQMUIAZMQYT.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/37/3MDT8UZULS2IQMUIAZMQYT.uasset new file mode 100644 index 0000000..bf01c56 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/37/3MDT8UZULS2IQMUIAZMQYT.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/3T/LQOJ9BAH0TFG9BN6VMPR0L.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/3T/LQOJ9BAH0TFG9BN6VMPR0L.uasset new file mode 100644 index 0000000..e9295fd Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/3T/LQOJ9BAH0TFG9BN6VMPR0L.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/AT/95NOG595COK8YO39FPAVPE.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/AT/95NOG595COK8YO39FPAVPE.uasset new file mode 100644 index 0000000..9b4f0a1 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/AT/95NOG595COK8YO39FPAVPE.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/HZ/SQLM1IFIPOBMEKJGJHIBDL.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/HZ/SQLM1IFIPOBMEKJGJHIBDL.uasset new file mode 100644 index 0000000..9885e34 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/HZ/SQLM1IFIPOBMEKJGJHIBDL.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/NT/1U3K1FIZFLZJD9JECG4J91.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/NT/1U3K1FIZFLZJD9JECG4J91.uasset new file mode 100644 index 0000000..1a96ecb Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/NT/1U3K1FIZFLZJD9JECG4J91.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/P0/2315TBK297NP062364FRJK.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/P0/2315TBK297NP062364FRJK.uasset new file mode 100644 index 0000000..692d368 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/P0/2315TBK297NP062364FRJK.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/YE/8F6N6CWL3Z9YFWFCPBOABN.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/YE/8F6N6CWL3Z9YFWFCPBOABN.uasset new file mode 100644 index 0000000..1922997 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/A/YE/8F6N6CWL3Z9YFWFCPBOABN.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/17/SDPBJ9K67UIPUP22ZH7864.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/17/SDPBJ9K67UIPUP22ZH7864.uasset new file mode 100644 index 0000000..616ef84 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/17/SDPBJ9K67UIPUP22ZH7864.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/4O/8WLC0J43A48EBWDDJ38YJD.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/4O/8WLC0J43A48EBWDDJ38YJD.uasset new file mode 100644 index 0000000..074aa90 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/4O/8WLC0J43A48EBWDDJ38YJD.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/76/E3AHN2FPNWBFJ2AQ6HVNOY.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/76/E3AHN2FPNWBFJ2AQ6HVNOY.uasset new file mode 100644 index 0000000..1d088fb Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/76/E3AHN2FPNWBFJ2AQ6HVNOY.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/BQ/Q372FLA8BD7JIG29PCYFWG.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/BQ/Q372FLA8BD7JIG29PCYFWG.uasset new file mode 100644 index 0000000..95c44d8 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/BQ/Q372FLA8BD7JIG29PCYFWG.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/CT/CFSGXL14SGU01FPIMKSNE1.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/CT/CFSGXL14SGU01FPIMKSNE1.uasset new file mode 100644 index 0000000..3124cac Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/CT/CFSGXL14SGU01FPIMKSNE1.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/DA/6DJ7HJ17WP8CD3I6ZY8HWI.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/DA/6DJ7HJ17WP8CD3I6ZY8HWI.uasset new file mode 100644 index 0000000..2f9a260 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/DA/6DJ7HJ17WP8CD3I6ZY8HWI.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/EX/VH3KW6RCB7L4HSAXAZF3W3.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/EX/VH3KW6RCB7L4HSAXAZF3W3.uasset new file mode 100644 index 0000000..32dc72a Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/EX/VH3KW6RCB7L4HSAXAZF3W3.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/F7/KMRREMHTMPCZQOSLDYDJBS.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/F7/KMRREMHTMPCZQOSLDYDJBS.uasset new file mode 100644 index 0000000..0b3fc2b Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/F7/KMRREMHTMPCZQOSLDYDJBS.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/NE/1IZ3B0QY64Q0RDP9NU2K6H.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/NE/1IZ3B0QY64Q0RDP9NU2K6H.uasset new file mode 100644 index 0000000..64ed730 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/NE/1IZ3B0QY64Q0RDP9NU2K6H.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/SG/ABBQVE4WS4Q8X6QBWDF841.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/SG/ABBQVE4WS4Q8X6QBWDF841.uasset new file mode 100644 index 0000000..1467ccc Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/SG/ABBQVE4WS4Q8X6QBWDF841.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/T3/BMIR1KVDQU7TPX80KNVG2T.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/T3/BMIR1KVDQU7TPX80KNVG2T.uasset new file mode 100644 index 0000000..d409874 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/B/T3/BMIR1KVDQU7TPX80KNVG2T.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/C/2M/BKLMJXKFDR71PC1PWGUPPP.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/C/2M/BKLMJXKFDR71PC1PWGUPPP.uasset new file mode 100644 index 0000000..106002f Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/C/2M/BKLMJXKFDR71PC1PWGUPPP.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/C/4T/2KL36DS4X1S0RM7I09CL33.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/C/4T/2KL36DS4X1S0RM7I09CL33.uasset new file mode 100644 index 0000000..720361e Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/C/4T/2KL36DS4X1S0RM7I09CL33.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/C/7M/BO9IX3WLRNCEHVRDDLEOWT.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/C/7M/BO9IX3WLRNCEHVRDDLEOWT.uasset new file mode 100644 index 0000000..81a7890 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/C/7M/BO9IX3WLRNCEHVRDDLEOWT.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/C/8A/TJ8MW1WQ2KJOMU1L02L2YA.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/C/8A/TJ8MW1WQ2KJOMU1L02L2YA.uasset new file mode 100644 index 0000000..14a7fcf Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/C/8A/TJ8MW1WQ2KJOMU1L02L2YA.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/C/W1/4XPUEFSRF6IJ5DJVQ5HY2N.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/C/W1/4XPUEFSRF6IJ5DJVQ5HY2N.uasset new file mode 100644 index 0000000..1aca37d Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/C/W1/4XPUEFSRF6IJ5DJVQ5HY2N.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/C/YZ/BZJ174LKPK9P48T2FPZ2YD.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/C/YZ/BZJ174LKPK9P48T2FPZ2YD.uasset new file mode 100644 index 0000000..2c23b65 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/C/YZ/BZJ174LKPK9P48T2FPZ2YD.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/D/24/8PNBMJZ6VELIPUADNB9KZY.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/D/24/8PNBMJZ6VELIPUADNB9KZY.uasset new file mode 100644 index 0000000..b1b4b50 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/D/24/8PNBMJZ6VELIPUADNB9KZY.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/D/40/HCM80K2UTKQ44YIMIPPXS8.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/D/40/HCM80K2UTKQ44YIMIPPXS8.uasset new file mode 100644 index 0000000..35233cd Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/D/40/HCM80K2UTKQ44YIMIPPXS8.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/D/EY/PINX4K2U7SAPH6WRJ4H1EL.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/D/EY/PINX4K2U7SAPH6WRJ4H1EL.uasset new file mode 100644 index 0000000..9ffb99f Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/D/EY/PINX4K2U7SAPH6WRJ4H1EL.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/D/LA/T916UMOAT5N27HQFVX6WGS.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/D/LA/T916UMOAT5N27HQFVX6WGS.uasset new file mode 100644 index 0000000..9ab536b Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/D/LA/T916UMOAT5N27HQFVX6WGS.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/E/5L/5SHDOVHTHP7TGMLI05GVV9.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/E/5L/5SHDOVHTHP7TGMLI05GVV9.uasset new file mode 100644 index 0000000..f9a2cec Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/E/5L/5SHDOVHTHP7TGMLI05GVV9.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/E/A5/G6EJWJRQ94MEJ6VYZQCH0P.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/E/A5/G6EJWJRQ94MEJ6VYZQCH0P.uasset new file mode 100644 index 0000000..be9dd9a Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/E/A5/G6EJWJRQ94MEJ6VYZQCH0P.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/E/KF/BM04659BFJO7X0DH4EGU2N.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/E/KF/BM04659BFJO7X0DH4EGU2N.uasset new file mode 100644 index 0000000..2ac3c6e Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/E/KF/BM04659BFJO7X0DH4EGU2N.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/E/L8/O2XSZ7TA8EZ3U0EH412XVT.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/E/L8/O2XSZ7TA8EZ3U0EH412XVT.uasset new file mode 100644 index 0000000..f8aa256 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/E/L8/O2XSZ7TA8EZ3U0EH412XVT.uasset differ diff --git a/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/F/4R/B8IQ24EUB55DKMWKFIM65C.uasset b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/F/4R/B8IQ24EUB55DKMWKFIM65C.uasset new file mode 100644 index 0000000..0c87ba7 Binary files /dev/null and b/Content/__ExternalActors__/Variant_Platforming/Lvl_Platforming/F/4R/B8IQ24EUB55DKMWKFIM65C.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/2I/G1MKF8NNQ54SREG7DDID6O.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/2I/G1MKF8NNQ54SREG7DDID6O.uasset new file mode 100644 index 0000000..20b499a Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/2I/G1MKF8NNQ54SREG7DDID6O.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/51/C41HDKEQ2QWJHE8D8QL4TM.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/51/C41HDKEQ2QWJHE8D8QL4TM.uasset new file mode 100644 index 0000000..6e6d05d Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/51/C41HDKEQ2QWJHE8D8QL4TM.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/55/TSQ6SNX6TODA8EE8PEHVED.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/55/TSQ6SNX6TODA8EE8PEHVED.uasset new file mode 100644 index 0000000..0e1f970 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/55/TSQ6SNX6TODA8EE8PEHVED.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/7N/X1VL3XUO4ZCPBN9ZEYXAU5.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/7N/X1VL3XUO4ZCPBN9ZEYXAU5.uasset new file mode 100644 index 0000000..7b294a8 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/7N/X1VL3XUO4ZCPBN9ZEYXAU5.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/9X/ON50D9MMRRWCOF8MZCJLBN.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/9X/ON50D9MMRRWCOF8MZCJLBN.uasset new file mode 100644 index 0000000..2c03e0b Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/9X/ON50D9MMRRWCOF8MZCJLBN.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/A0/QUB33CZ9GJSQCJ55A8O7SH.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/A0/QUB33CZ9GJSQCJ55A8O7SH.uasset new file mode 100644 index 0000000..ec8fcfe Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/A0/QUB33CZ9GJSQCJ55A8O7SH.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/CE/WO89H04O4WVY06P3ZDQZDY.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/CE/WO89H04O4WVY06P3ZDQZDY.uasset new file mode 100644 index 0000000..2f8a487 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/CE/WO89H04O4WVY06P3ZDQZDY.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/W4/K4XW5H58ZTIXYZPFEU59QY.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/W4/K4XW5H58ZTIXYZPFEU59QY.uasset new file mode 100644 index 0000000..6d2fded Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/W4/K4XW5H58ZTIXYZPFEU59QY.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/XR/DKEGC1B4JI84Q6M99OFPAB.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/XR/DKEGC1B4JI84Q6M99OFPAB.uasset new file mode 100644 index 0000000..2b90c04 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/XR/DKEGC1B4JI84Q6M99OFPAB.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/YZ/8GP970C3DULJJRBDLJCCAT.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/YZ/8GP970C3DULJJRBDLJCCAT.uasset new file mode 100644 index 0000000..8175be3 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/0/YZ/8GP970C3DULJJRBDLJCCAT.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/1/4L/QHE9SS8IL8NAH4LP40QVMA.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/1/4L/QHE9SS8IL8NAH4LP40QVMA.uasset new file mode 100644 index 0000000..7a1a523 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/1/4L/QHE9SS8IL8NAH4LP40QVMA.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/1/AS/7JLJ2FV98VA2GZXKO2SZQ3.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/1/AS/7JLJ2FV98VA2GZXKO2SZQ3.uasset new file mode 100644 index 0000000..d7f3723 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/1/AS/7JLJ2FV98VA2GZXKO2SZQ3.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/1/DZ/I5IBC74KFACATJBFXLDCE0.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/1/DZ/I5IBC74KFACATJBFXLDCE0.uasset new file mode 100644 index 0000000..a25f1aa Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/1/DZ/I5IBC74KFACATJBFXLDCE0.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/1/MM/CNZCO2Y9UH51FLSXM49L4K.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/1/MM/CNZCO2Y9UH51FLSXM49L4K.uasset new file mode 100644 index 0000000..723eadf Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/1/MM/CNZCO2Y9UH51FLSXM49L4K.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/88/W3TC4XVD4B726DXP8IRN6K.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/88/W3TC4XVD4B726DXP8IRN6K.uasset new file mode 100644 index 0000000..2879aad Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/88/W3TC4XVD4B726DXP8IRN6K.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/8N/FCX53BERRMEQY7OWO3KYO6.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/8N/FCX53BERRMEQY7OWO3KYO6.uasset new file mode 100644 index 0000000..285e92a Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/8N/FCX53BERRMEQY7OWO3KYO6.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/8W/JCYX6FJ8GQ2IFZ7BNCK5RE.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/8W/JCYX6FJ8GQ2IFZ7BNCK5RE.uasset new file mode 100644 index 0000000..2b4cef2 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/8W/JCYX6FJ8GQ2IFZ7BNCK5RE.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/BX/L4E2EQEA7T84XTQO1ANHJH.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/BX/L4E2EQEA7T84XTQO1ANHJH.uasset new file mode 100644 index 0000000..7bbdd26 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/BX/L4E2EQEA7T84XTQO1ANHJH.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/QN/5OYUDWJJIFAH2U8EP29EZW.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/QN/5OYUDWJJIFAH2U8EP29EZW.uasset new file mode 100644 index 0000000..41d273e Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/QN/5OYUDWJJIFAH2U8EP29EZW.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/QN/HKCCLAZQSUEJZG9QCZ6C0O.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/QN/HKCCLAZQSUEJZG9QCZ6C0O.uasset new file mode 100644 index 0000000..dd29124 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/QN/HKCCLAZQSUEJZG9QCZ6C0O.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/TS/G8WTY9XQQX37L3VIWLTMJU.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/TS/G8WTY9XQQX37L3VIWLTMJU.uasset new file mode 100644 index 0000000..cee7e01 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/TS/G8WTY9XQQX37L3VIWLTMJU.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/UF/U3AV1VA4PKPVMO06KMZSD1.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/UF/U3AV1VA4PKPVMO06KMZSD1.uasset new file mode 100644 index 0000000..c5d5b1f Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/UF/U3AV1VA4PKPVMO06KMZSD1.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/WS/OVOQ7FE1IFTPQISWGSWKVC.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/WS/OVOQ7FE1IFTPQISWGSWKVC.uasset new file mode 100644 index 0000000..424245f Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/WS/OVOQ7FE1IFTPQISWGSWKVC.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/WV/IGCDSH3F4Y0RC8SENYM91Z.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/WV/IGCDSH3F4Y0RC8SENYM91Z.uasset new file mode 100644 index 0000000..96ffe46 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/2/WV/IGCDSH3F4Y0RC8SENYM91Z.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/3/5S/82ZBWG53S08ZU6XJ2ZSBBE.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/3/5S/82ZBWG53S08ZU6XJ2ZSBBE.uasset new file mode 100644 index 0000000..18fb644 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/3/5S/82ZBWG53S08ZU6XJ2ZSBBE.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/3/GX/6EOALX5Y5HFRQJAGOS1QT7.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/3/GX/6EOALX5Y5HFRQJAGOS1QT7.uasset new file mode 100644 index 0000000..8aa3f13 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/3/GX/6EOALX5Y5HFRQJAGOS1QT7.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/3/LI/I3R0J0JY12SLX2CUDGPK14.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/3/LI/I3R0J0JY12SLX2CUDGPK14.uasset new file mode 100644 index 0000000..ba6dcd8 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/3/LI/I3R0J0JY12SLX2CUDGPK14.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/3/V1/D800HGD5TR8TIRIXI0DPZO.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/3/V1/D800HGD5TR8TIRIXI0DPZO.uasset new file mode 100644 index 0000000..92c1c72 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/3/V1/D800HGD5TR8TIRIXI0DPZO.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/2O/7LLF6A9TYTLPVE3CJBMHZW.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/2O/7LLF6A9TYTLPVE3CJBMHZW.uasset new file mode 100644 index 0000000..e874a23 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/2O/7LLF6A9TYTLPVE3CJBMHZW.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/5P/6P8DZN6JS5T15S43S4A4Q9.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/5P/6P8DZN6JS5T15S43S4A4Q9.uasset new file mode 100644 index 0000000..b75686f Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/5P/6P8DZN6JS5T15S43S4A4Q9.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/AO/V40S5T2E6I4T2O6BBNUR38.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/AO/V40S5T2E6I4T2O6BBNUR38.uasset new file mode 100644 index 0000000..a5630b5 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/AO/V40S5T2E6I4T2O6BBNUR38.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/DJ/F3N5KA328H6KPE36KJLFHV.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/DJ/F3N5KA328H6KPE36KJLFHV.uasset new file mode 100644 index 0000000..12fb2c0 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/DJ/F3N5KA328H6KPE36KJLFHV.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/FE/C6BJR38GS6H4J2SPT0XRZG.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/FE/C6BJR38GS6H4J2SPT0XRZG.uasset new file mode 100644 index 0000000..c4956e4 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/FE/C6BJR38GS6H4J2SPT0XRZG.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/FI/PYW5E1IVFV8U5TT2WHQGHB.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/FI/PYW5E1IVFV8U5TT2WHQGHB.uasset new file mode 100644 index 0000000..832b6ec Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/FI/PYW5E1IVFV8U5TT2WHQGHB.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/S1/V1WOSCNF9Z5E74HYU3V5O1.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/S1/V1WOSCNF9Z5E74HYU3V5O1.uasset new file mode 100644 index 0000000..4f3cbfa Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/S1/V1WOSCNF9Z5E74HYU3V5O1.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/SR/O49WBYGCZC2E5STYR76FC4.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/SR/O49WBYGCZC2E5STYR76FC4.uasset new file mode 100644 index 0000000..b54c727 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/SR/O49WBYGCZC2E5STYR76FC4.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/X1/0K1YU00713QQLQPVSUR3IP.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/X1/0K1YU00713QQLQPVSUR3IP.uasset new file mode 100644 index 0000000..bc3cc3b Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/4/X1/0K1YU00713QQLQPVSUR3IP.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/5/1I/2S73Q6F8UM0GUJ5FUTYL7X.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/5/1I/2S73Q6F8UM0GUJ5FUTYL7X.uasset new file mode 100644 index 0000000..4da170e Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/5/1I/2S73Q6F8UM0GUJ5FUTYL7X.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/5/AN/JLMHPKUN9Z5WTQYDGYP142.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/5/AN/JLMHPKUN9Z5WTQYDGYP142.uasset new file mode 100644 index 0000000..61cf599 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/5/AN/JLMHPKUN9Z5WTQYDGYP142.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/5/BN/36TXURCS5J98HU9ZZ79V1Z.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/5/BN/36TXURCS5J98HU9ZZ79V1Z.uasset new file mode 100644 index 0000000..67d09ca Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/5/BN/36TXURCS5J98HU9ZZ79V1Z.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/5/CY/E7V6YSUV6ZU1RENFIG0S6N.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/5/CY/E7V6YSUV6ZU1RENFIG0S6N.uasset new file mode 100644 index 0000000..169a7c7 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/5/CY/E7V6YSUV6ZU1RENFIG0S6N.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/5/R7/QBO2ZHXPF79ZYF4GINGLIF.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/5/R7/QBO2ZHXPF79ZYF4GINGLIF.uasset new file mode 100644 index 0000000..ff0a791 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/5/R7/QBO2ZHXPF79ZYF4GINGLIF.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/5/RK/SCE75WXEPFZ9ERSYYKKE4A.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/5/RK/SCE75WXEPFZ9ERSYYKKE4A.uasset new file mode 100644 index 0000000..6f23c20 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/5/RK/SCE75WXEPFZ9ERSYYKKE4A.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/6/7N/V8LHYHOZ8T4E0JW1UI8HNH.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/6/7N/V8LHYHOZ8T4E0JW1UI8HNH.uasset new file mode 100644 index 0000000..c9c9631 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/6/7N/V8LHYHOZ8T4E0JW1UI8HNH.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/6/G4/6HOLR0R7EDXS2QUKSQ79J7.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/6/G4/6HOLR0R7EDXS2QUKSQ79J7.uasset new file mode 100644 index 0000000..fc9d2fa Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/6/G4/6HOLR0R7EDXS2QUKSQ79J7.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/6/Y7/WUY3BJM0HMIJDZ6ORO6KDY.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/6/Y7/WUY3BJM0HMIJDZ6ORO6KDY.uasset new file mode 100644 index 0000000..3cad6bf Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/6/Y7/WUY3BJM0HMIJDZ6ORO6KDY.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/6/Z6/A3HQCHNAI3O4WPJZEB47V3.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/6/Z6/A3HQCHNAI3O4WPJZEB47V3.uasset new file mode 100644 index 0000000..3e4c980 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/6/Z6/A3HQCHNAI3O4WPJZEB47V3.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/6/ZZ/JNWPBGUXXO50Z8FMT2C8FK.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/6/ZZ/JNWPBGUXXO50Z8FMT2C8FK.uasset new file mode 100644 index 0000000..318ec9b Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/6/ZZ/JNWPBGUXXO50Z8FMT2C8FK.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/AC/GJKJDMBGHUD0VUN52G0BWX.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/AC/GJKJDMBGHUD0VUN52G0BWX.uasset new file mode 100644 index 0000000..a641fda Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/AC/GJKJDMBGHUD0VUN52G0BWX.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/FI/VOZOS1IVPW2M97OAXQCQV1.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/FI/VOZOS1IVPW2M97OAXQCQV1.uasset new file mode 100644 index 0000000..eb1a186 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/FI/VOZOS1IVPW2M97OAXQCQV1.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/FU/ESKEUK3PO4RFJ7DNJHE95H.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/FU/ESKEUK3PO4RFJ7DNJHE95H.uasset new file mode 100644 index 0000000..ea6bd2c Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/FU/ESKEUK3PO4RFJ7DNJHE95H.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/FZ/TAY0RTVT1I4LEADPG4DMCL.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/FZ/TAY0RTVT1I4LEADPG4DMCL.uasset new file mode 100644 index 0000000..5e5dc98 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/FZ/TAY0RTVT1I4LEADPG4DMCL.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/HG/KNVV0PKQUQGFMXEO4LEB5E.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/HG/KNVV0PKQUQGFMXEO4LEB5E.uasset new file mode 100644 index 0000000..7390381 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/HG/KNVV0PKQUQGFMXEO4LEB5E.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/OJ/3Z4PRDM96FA9PAAA5QN8T6.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/OJ/3Z4PRDM96FA9PAAA5QN8T6.uasset new file mode 100644 index 0000000..75896ba Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/OJ/3Z4PRDM96FA9PAAA5QN8T6.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/Q9/VV1XAARCTH3S9KYJ70JU5U.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/Q9/VV1XAARCTH3S9KYJ70JU5U.uasset new file mode 100644 index 0000000..989c366 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/Q9/VV1XAARCTH3S9KYJ70JU5U.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/RK/BFV42H3EZS6RTU76GYOY42.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/RK/BFV42H3EZS6RTU76GYOY42.uasset new file mode 100644 index 0000000..8a6740c Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/RK/BFV42H3EZS6RTU76GYOY42.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/TK/XOW2H2EFOJBB8E2LG4DQ3A.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/TK/XOW2H2EFOJBB8E2LG4DQ3A.uasset new file mode 100644 index 0000000..cca0144 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/TK/XOW2H2EFOJBB8E2LG4DQ3A.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/VE/PAV8481ITARYRY3QDLFEJZ.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/VE/PAV8481ITARYRY3QDLFEJZ.uasset new file mode 100644 index 0000000..927cdce Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/7/VE/PAV8481ITARYRY3QDLFEJZ.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/0Y/JGIN2GJ35V3PEEV84HFVRV.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/0Y/JGIN2GJ35V3PEEV84HFVRV.uasset new file mode 100644 index 0000000..8bc73dd Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/0Y/JGIN2GJ35V3PEEV84HFVRV.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/BX/0TL7ZIIQQ5DC1IAX1XYJUZ.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/BX/0TL7ZIIQQ5DC1IAX1XYJUZ.uasset new file mode 100644 index 0000000..a0ae9ee Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/BX/0TL7ZIIQQ5DC1IAX1XYJUZ.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/IY/E2HCOQGZZ81FQJZH0Q7EXB.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/IY/E2HCOQGZZ81FQJZH0Q7EXB.uasset new file mode 100644 index 0000000..7940ca4 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/IY/E2HCOQGZZ81FQJZH0Q7EXB.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/QR/0XN8O0QF52ZUXKOZG7BONA.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/QR/0XN8O0QF52ZUXKOZG7BONA.uasset new file mode 100644 index 0000000..0005a27 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/QR/0XN8O0QF52ZUXKOZG7BONA.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/V3/20IIIADDF8349J2BKCJKT0.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/V3/20IIIADDF8349J2BKCJKT0.uasset new file mode 100644 index 0000000..959698d Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/V3/20IIIADDF8349J2BKCJKT0.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/XQ/YVK1F3AJGN2OYUWBYE1175.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/XQ/YVK1F3AJGN2OYUWBYE1175.uasset new file mode 100644 index 0000000..604c384 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/XQ/YVK1F3AJGN2OYUWBYE1175.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/XT/8U4LWVODG9CWTCW2RDG45N.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/XT/8U4LWVODG9CWTCW2RDG45N.uasset new file mode 100644 index 0000000..cb2ff15 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/XT/8U4LWVODG9CWTCW2RDG45N.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/ZO/DNU8CWFVEZ083S87RKPEZ9.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/ZO/DNU8CWFVEZ083S87RKPEZ9.uasset new file mode 100644 index 0000000..e32fa4c Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/8/ZO/DNU8CWFVEZ083S87RKPEZ9.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/0H/L2OPHB6SDBCIRYW1FF8EBT.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/0H/L2OPHB6SDBCIRYW1FF8EBT.uasset new file mode 100644 index 0000000..57e9418 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/0H/L2OPHB6SDBCIRYW1FF8EBT.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/EY/NKSPJALRCCOUADXCNJK3CL.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/EY/NKSPJALRCCOUADXCNJK3CL.uasset new file mode 100644 index 0000000..8a8b664 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/EY/NKSPJALRCCOUADXCNJK3CL.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/FU/3SI6A5UVXA8UU40SO8PXBK.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/FU/3SI6A5UVXA8UU40SO8PXBK.uasset new file mode 100644 index 0000000..0216530 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/FU/3SI6A5UVXA8UU40SO8PXBK.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/IB/TU2042O8WCAXRXICLUU34I.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/IB/TU2042O8WCAXRXICLUU34I.uasset new file mode 100644 index 0000000..c7ec378 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/IB/TU2042O8WCAXRXICLUU34I.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/W1/Y1NMCVOYTP7IVRTZNVIOZN.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/W1/Y1NMCVOYTP7IVRTZNVIOZN.uasset new file mode 100644 index 0000000..460d4c9 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/W1/Y1NMCVOYTP7IVRTZNVIOZN.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/XD/B5ECBUW5KR3KQQF2K7SZ1E.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/XD/B5ECBUW5KR3KQQF2K7SZ1E.uasset new file mode 100644 index 0000000..91e4cd9 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/XD/B5ECBUW5KR3KQQF2K7SZ1E.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/ZQ/9WNDS1QQWL546BM4CI6BNZ.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/ZQ/9WNDS1QQWL546BM4CI6BNZ.uasset new file mode 100644 index 0000000..90ee09f Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/9/ZQ/9WNDS1QQWL546BM4CI6BNZ.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/2J/LZETGQ3X85G5TSJ9FFXCA3.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/2J/LZETGQ3X85G5TSJ9FFXCA3.uasset new file mode 100644 index 0000000..52dc4f4 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/2J/LZETGQ3X85G5TSJ9FFXCA3.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/2S/5B1U9FCQ8M5X53RHEQ6D5J.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/2S/5B1U9FCQ8M5X53RHEQ6D5J.uasset new file mode 100644 index 0000000..d678f90 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/2S/5B1U9FCQ8M5X53RHEQ6D5J.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/89/YLDOGE8ZIHHE1N8EWSSA5X.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/89/YLDOGE8ZIHHE1N8EWSSA5X.uasset new file mode 100644 index 0000000..c253a9b Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/89/YLDOGE8ZIHHE1N8EWSSA5X.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/I2/P09WK19412DF9S1OSOGA9N.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/I2/P09WK19412DF9S1OSOGA9N.uasset new file mode 100644 index 0000000..1f16e86 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/I2/P09WK19412DF9S1OSOGA9N.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/MU/N60BGDPNX9NAECL7P3D62A.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/MU/N60BGDPNX9NAECL7P3D62A.uasset new file mode 100644 index 0000000..9fa85b6 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/MU/N60BGDPNX9NAECL7P3D62A.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/QT/43H2JAMUR02W8525AAT31O.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/QT/43H2JAMUR02W8525AAT31O.uasset new file mode 100644 index 0000000..b5ada80 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/QT/43H2JAMUR02W8525AAT31O.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/R0/PW2O8FXK76Q7QQP5HO12GT.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/R0/PW2O8FXK76Q7QQP5HO12GT.uasset new file mode 100644 index 0000000..43da282 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/R0/PW2O8FXK76Q7QQP5HO12GT.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/RH/6EAZC8A5PICQNUO92BCA8I.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/RH/6EAZC8A5PICQNUO92BCA8I.uasset new file mode 100644 index 0000000..f8ae146 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/RH/6EAZC8A5PICQNUO92BCA8I.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/RR/VFVX5TLBBD4APPS0WIVULU.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/RR/VFVX5TLBBD4APPS0WIVULU.uasset new file mode 100644 index 0000000..1689f64 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/RR/VFVX5TLBBD4APPS0WIVULU.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/SR/9P30BPQ8QVJ0DC2NIS2OC5.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/SR/9P30BPQ8QVJ0DC2NIS2OC5.uasset new file mode 100644 index 0000000..eb62682 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/SR/9P30BPQ8QVJ0DC2NIS2OC5.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/TM/9JUSV7NLY7Q93ZKVXAUORV.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/TM/9JUSV7NLY7Q93ZKVXAUORV.uasset new file mode 100644 index 0000000..65a95a9 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/A/TM/9JUSV7NLY7Q93ZKVXAUORV.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/0Q/K1MY2Z3PO44FGAHJOJSWRF.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/0Q/K1MY2Z3PO44FGAHJOJSWRF.uasset new file mode 100644 index 0000000..1ed059b Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/0Q/K1MY2Z3PO44FGAHJOJSWRF.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/4U/0JQULTZPZO65LXMOXJI8ZH.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/4U/0JQULTZPZO65LXMOXJI8ZH.uasset new file mode 100644 index 0000000..0e244e8 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/4U/0JQULTZPZO65LXMOXJI8ZH.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/C4/G8J8FNL5D83NA6SN3RKUNP.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/C4/G8J8FNL5D83NA6SN3RKUNP.uasset new file mode 100644 index 0000000..14cc1a5 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/C4/G8J8FNL5D83NA6SN3RKUNP.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/DM/E5TLKD3GOGSELPM18VQESJ.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/DM/E5TLKD3GOGSELPM18VQESJ.uasset new file mode 100644 index 0000000..39530f2 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/DM/E5TLKD3GOGSELPM18VQESJ.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/NS/E0F0PNC3IO8QSVU5LCI5I2.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/NS/E0F0PNC3IO8QSVU5LCI5I2.uasset new file mode 100644 index 0000000..88f945e Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/NS/E0F0PNC3IO8QSVU5LCI5I2.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/Q6/JDMX515ZGL66Y7Z28EI0RU.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/Q6/JDMX515ZGL66Y7Z28EI0RU.uasset new file mode 100644 index 0000000..543f9b3 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/Q6/JDMX515ZGL66Y7Z28EI0RU.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/TT/YM92F6PM12D7710U2ZVWWY.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/TT/YM92F6PM12D7710U2ZVWWY.uasset new file mode 100644 index 0000000..6b0de61 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/TT/YM92F6PM12D7710U2ZVWWY.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/UR/JZ7DT0ELPYBVDXM6L7TM0C.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/UR/JZ7DT0ELPYBVDXM6L7TM0C.uasset new file mode 100644 index 0000000..4bf9210 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/B/UR/JZ7DT0ELPYBVDXM6L7TM0C.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/0L/CL3R603WDPFWJLMYLWZVZN.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/0L/CL3R603WDPFWJLMYLWZVZN.uasset new file mode 100644 index 0000000..dfc8af6 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/0L/CL3R603WDPFWJLMYLWZVZN.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/5R/95TDOJQ9Z8E18ROGC7MIX7.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/5R/95TDOJQ9Z8E18ROGC7MIX7.uasset new file mode 100644 index 0000000..b302aec Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/5R/95TDOJQ9Z8E18ROGC7MIX7.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/6U/6T7ODNLR62JH0BULT1J4AE.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/6U/6T7ODNLR62JH0BULT1J4AE.uasset new file mode 100644 index 0000000..bd0c564 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/6U/6T7ODNLR62JH0BULT1J4AE.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/9S/LQQ0H0SH70184OCJX1HNZ4.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/9S/LQQ0H0SH70184OCJX1HNZ4.uasset new file mode 100644 index 0000000..de1e621 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/9S/LQQ0H0SH70184OCJX1HNZ4.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/C4/C5A84ZQFO9N5LUDADATW0V.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/C4/C5A84ZQFO9N5LUDADATW0V.uasset new file mode 100644 index 0000000..3109eb2 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/C4/C5A84ZQFO9N5LUDADATW0V.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/J4/0LD3OOMMA7A1BOXNC9KUEB.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/J4/0LD3OOMMA7A1BOXNC9KUEB.uasset new file mode 100644 index 0000000..a0152dc Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/J4/0LD3OOMMA7A1BOXNC9KUEB.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/LR/BCXS1F48STUAAKWQ5V9AA5.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/LR/BCXS1F48STUAAKWQ5V9AA5.uasset new file mode 100644 index 0000000..23682b3 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/LR/BCXS1F48STUAAKWQ5V9AA5.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/ML/40T5MMM0YXF4J1LHKAELOM.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/ML/40T5MMM0YXF4J1LHKAELOM.uasset new file mode 100644 index 0000000..3eb97f7 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/ML/40T5MMM0YXF4J1LHKAELOM.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/N4/8DRPJ2DF8214RH35JA4GMC.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/N4/8DRPJ2DF8214RH35JA4GMC.uasset new file mode 100644 index 0000000..30a19fa Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/N4/8DRPJ2DF8214RH35JA4GMC.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/O2/81W0XWDLGITVXVMUB25TDV.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/O2/81W0XWDLGITVXVMUB25TDV.uasset new file mode 100644 index 0000000..d9e57ad Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/O2/81W0XWDLGITVXVMUB25TDV.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/P8/NS40YVZO11JHTQKF9JMSXF.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/P8/NS40YVZO11JHTQKF9JMSXF.uasset new file mode 100644 index 0000000..a7de9f6 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/C/P8/NS40YVZO11JHTQKF9JMSXF.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/73/Q8RBKW1UCEJ0BQANAITWX5.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/73/Q8RBKW1UCEJ0BQANAITWX5.uasset new file mode 100644 index 0000000..960bac1 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/73/Q8RBKW1UCEJ0BQANAITWX5.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/7P/SBT259X67MUNBQI7R3T53P.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/7P/SBT259X67MUNBQI7R3T53P.uasset new file mode 100644 index 0000000..1e5e865 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/7P/SBT259X67MUNBQI7R3T53P.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/82/UMH0N4CZQFRU0ME4IZXWI8.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/82/UMH0N4CZQFRU0ME4IZXWI8.uasset new file mode 100644 index 0000000..b0b9089 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/82/UMH0N4CZQFRU0ME4IZXWI8.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/88/TDH4MAYEZP207X3DMEB2OI.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/88/TDH4MAYEZP207X3DMEB2OI.uasset new file mode 100644 index 0000000..7f824bb Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/88/TDH4MAYEZP207X3DMEB2OI.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/8J/EQEW33IHHR667Y7DPDUFKK.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/8J/EQEW33IHHR667Y7DPDUFKK.uasset new file mode 100644 index 0000000..e16403b Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/8J/EQEW33IHHR667Y7DPDUFKK.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/EG/T8AKUCFASX0QNVT22HD30H.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/EG/T8AKUCFASX0QNVT22HD30H.uasset new file mode 100644 index 0000000..ffe5dbe Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/EG/T8AKUCFASX0QNVT22HD30H.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/MO/C4L1DOMPEFG1B1T21Z4RNL.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/MO/C4L1DOMPEFG1B1T21Z4RNL.uasset new file mode 100644 index 0000000..d7d45cf Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/MO/C4L1DOMPEFG1B1T21Z4RNL.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/T2/1CGXTAH2ZI6S335DJWHRJ9.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/T2/1CGXTAH2ZI6S335DJWHRJ9.uasset new file mode 100644 index 0000000..0e19baf Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/T2/1CGXTAH2ZI6S335DJWHRJ9.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/T6/99IJ6BRDKY68PMANM4E0U1.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/T6/99IJ6BRDKY68PMANM4E0U1.uasset new file mode 100644 index 0000000..4de8965 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/T6/99IJ6BRDKY68PMANM4E0U1.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/W0/7ZPJ8M3MEVY6KHBGJC37EZ.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/W0/7ZPJ8M3MEVY6KHBGJC37EZ.uasset new file mode 100644 index 0000000..07a695b Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/D/W0/7ZPJ8M3MEVY6KHBGJC37EZ.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/E/DA/K28CUXOWD7LWSW25P6Z35P.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/E/DA/K28CUXOWD7LWSW25P6Z35P.uasset new file mode 100644 index 0000000..40f7b57 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/E/DA/K28CUXOWD7LWSW25P6Z35P.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/E/E2/LUCPJMX9ZH469ORNA5LMBP.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/E/E2/LUCPJMX9ZH469ORNA5LMBP.uasset new file mode 100644 index 0000000..78f3652 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/E/E2/LUCPJMX9ZH469ORNA5LMBP.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/E/F3/8FPOFB99K2OT9AF8LGCI1G.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/E/F3/8FPOFB99K2OT9AF8LGCI1G.uasset new file mode 100644 index 0000000..6fc42ff Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/E/F3/8FPOFB99K2OT9AF8LGCI1G.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/E/VE/IBZI1HGCXN01TW4JJ5RBOU.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/E/VE/IBZI1HGCXN01TW4JJ5RBOU.uasset new file mode 100644 index 0000000..752892e Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/E/VE/IBZI1HGCXN01TW4JJ5RBOU.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/F/03/7O6UXFI7OYBTT8XLD8LOQX.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/F/03/7O6UXFI7OYBTT8XLD8LOQX.uasset new file mode 100644 index 0000000..3ef2f53 Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/F/03/7O6UXFI7OYBTT8XLD8LOQX.uasset differ diff --git a/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/F/49/VR137PSSEONSZYOJDXKSNI.uasset b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/F/49/VR137PSSEONSZYOJDXKSNI.uasset new file mode 100644 index 0000000..373dc1c Binary files /dev/null and b/Content/__ExternalActors__/Variant_SideScrolling/Lvl_SideScrolling/F/49/VR137PSSEONSZYOJDXKSNI.uasset differ diff --git a/Content/__ExternalObjects__/ThirdPerson/Lvl_ThirdPerson/0/TQ/1UP2MCOIYXC0GTW985R4K0.uasset b/Content/__ExternalObjects__/ThirdPerson/Lvl_ThirdPerson/0/TQ/1UP2MCOIYXC0GTW985R4K0.uasset new file mode 100644 index 0000000..826525e Binary files /dev/null and b/Content/__ExternalObjects__/ThirdPerson/Lvl_ThirdPerson/0/TQ/1UP2MCOIYXC0GTW985R4K0.uasset differ diff --git a/Content/__ExternalObjects__/ThirdPerson/Lvl_ThirdPerson/5/TX/O6OLZU4WSCI3YRJS922SWL.uasset b/Content/__ExternalObjects__/ThirdPerson/Lvl_ThirdPerson/5/TX/O6OLZU4WSCI3YRJS922SWL.uasset new file mode 100644 index 0000000..e53713b Binary files /dev/null and b/Content/__ExternalObjects__/ThirdPerson/Lvl_ThirdPerson/5/TX/O6OLZU4WSCI3YRJS922SWL.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/0/AJ/Q42ZSOSTBUDHJO9I0YFJH8.uasset b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/0/AJ/Q42ZSOSTBUDHJO9I0YFJH8.uasset new file mode 100644 index 0000000..708b75f Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/0/AJ/Q42ZSOSTBUDHJO9I0YFJH8.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/0/QK/03V86HC7QVQDQ0QJ94CYLX.uasset b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/0/QK/03V86HC7QVQDQ0QJ94CYLX.uasset new file mode 100644 index 0000000..8868062 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/0/QK/03V86HC7QVQDQ0QJ94CYLX.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/1/7X/QGWW4SCWICG53A9K5OPS0G.uasset b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/1/7X/QGWW4SCWICG53A9K5OPS0G.uasset new file mode 100644 index 0000000..6c14728 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/1/7X/QGWW4SCWICG53A9K5OPS0G.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/1/K1/7BSJXD4YWZ2V1U8AB8O3Z2.uasset b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/1/K1/7BSJXD4YWZ2V1U8AB8O3Z2.uasset new file mode 100644 index 0000000..8d2f275 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/1/K1/7BSJXD4YWZ2V1U8AB8O3Z2.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/1/SR/0VGOAD2U620LLZDVVPGFFF.uasset b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/1/SR/0VGOAD2U620LLZDVVPGFFF.uasset new file mode 100644 index 0000000..a791eae Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/1/SR/0VGOAD2U620LLZDVVPGFFF.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/2/C9/058CTC6QHX54BNVCVEDMD5.uasset b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/2/C9/058CTC6QHX54BNVCVEDMD5.uasset new file mode 100644 index 0000000..3ac898b Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/2/C9/058CTC6QHX54BNVCVEDMD5.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/2/QF/FZ8IH801CN94E0HZNJIAMB.uasset b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/2/QF/FZ8IH801CN94E0HZNJIAMB.uasset new file mode 100644 index 0000000..9c78b78 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/2/QF/FZ8IH801CN94E0HZNJIAMB.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/5/EA/O3MBSQAXYS6XJUBQR76X9N.uasset b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/5/EA/O3MBSQAXYS6XJUBQR76X9N.uasset new file mode 100644 index 0000000..5c4f082 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/5/EA/O3MBSQAXYS6XJUBQR76X9N.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/7/O5/2TVKAOBOK3OMUSU4BNULM9.uasset b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/7/O5/2TVKAOBOK3OMUSU4BNULM9.uasset new file mode 100644 index 0000000..eb66ab7 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/7/O5/2TVKAOBOK3OMUSU4BNULM9.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/9/SK/PAE64UA62B4I6LK904O50Y.uasset b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/9/SK/PAE64UA62B4I6LK904O50Y.uasset new file mode 100644 index 0000000..461a97f Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/9/SK/PAE64UA62B4I6LK904O50Y.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/A/6N/VD4T3XSWEKR61PNFVRIPB4.uasset b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/A/6N/VD4T3XSWEKR61PNFVRIPB4.uasset new file mode 100644 index 0000000..28335c5 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/A/6N/VD4T3XSWEKR61PNFVRIPB4.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/A/D9/DAOWHPOKN1G4LLVA17BQ4F.uasset b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/A/D9/DAOWHPOKN1G4LLVA17BQ4F.uasset new file mode 100644 index 0000000..a87db0a Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/A/D9/DAOWHPOKN1G4LLVA17BQ4F.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/A/YB/BKATFJPNWZY6410P5E2OS4.uasset b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/A/YB/BKATFJPNWZY6410P5E2OS4.uasset new file mode 100644 index 0000000..8ee0bcb Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/A/YB/BKATFJPNWZY6410P5E2OS4.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/B/4J/TM33UR07N3CL6T1XDN76MR.uasset b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/B/4J/TM33UR07N3CL6T1XDN76MR.uasset new file mode 100644 index 0000000..660eb81 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/B/4J/TM33UR07N3CL6T1XDN76MR.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/B/4P/81CFSVQHA7OU7889MQEIM0.uasset b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/B/4P/81CFSVQHA7OU7889MQEIM0.uasset new file mode 100644 index 0000000..f614464 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/B/4P/81CFSVQHA7OU7889MQEIM0.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/C/7O/MHGK46YY82PZMS60I1H4B5.uasset b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/C/7O/MHGK46YY82PZMS60I1H4B5.uasset new file mode 100644 index 0000000..320a4c0 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/C/7O/MHGK46YY82PZMS60I1H4B5.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/C/BG/14XRTQLQ9HZHEKKS9TDV2B.uasset b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/C/BG/14XRTQLQ9HZHEKKS9TDV2B.uasset new file mode 100644 index 0000000..d7ca902 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/C/BG/14XRTQLQ9HZHEKKS9TDV2B.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/C/PM/J4QSJEZXX4QSU71S2XNSGB.uasset b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/C/PM/J4QSJEZXX4QSU71S2XNSGB.uasset new file mode 100644 index 0000000..f366573 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/C/PM/J4QSJEZXX4QSU71S2XNSGB.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/D/AE/OPO6LLO34IW7V0SYGULM48.uasset b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/D/AE/OPO6LLO34IW7V0SYGULM48.uasset new file mode 100644 index 0000000..234d045 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Combat/Lvl_Combat/D/AE/OPO6LLO34IW7V0SYGULM48.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/0/9D/ODYKWBVQ5YPDXSDWJDSI5Z.uasset b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/0/9D/ODYKWBVQ5YPDXSDWJDSI5Z.uasset new file mode 100644 index 0000000..79df193 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/0/9D/ODYKWBVQ5YPDXSDWJDSI5Z.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/0/HU/BN9XQV23Z2PJ1NNACEOHTX.uasset b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/0/HU/BN9XQV23Z2PJ1NNACEOHTX.uasset new file mode 100644 index 0000000..259ce04 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/0/HU/BN9XQV23Z2PJ1NNACEOHTX.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/0/IT/LAONTWEKWR03FY1W3WGQNC.uasset b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/0/IT/LAONTWEKWR03FY1W3WGQNC.uasset new file mode 100644 index 0000000..e93c511 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/0/IT/LAONTWEKWR03FY1W3WGQNC.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/0/VW/D1QERUEF4BT0SJ7JON4ABJ.uasset b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/0/VW/D1QERUEF4BT0SJ7JON4ABJ.uasset new file mode 100644 index 0000000..9ee5f84 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/0/VW/D1QERUEF4BT0SJ7JON4ABJ.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/1/N4/K32KI0AG7N0DL49GXDSEHL.uasset b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/1/N4/K32KI0AG7N0DL49GXDSEHL.uasset new file mode 100644 index 0000000..1265e1c Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/1/N4/K32KI0AG7N0DL49GXDSEHL.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/2/OE/06E4EHW0RK6BBO6IKZ8MM7.uasset b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/2/OE/06E4EHW0RK6BBO6IKZ8MM7.uasset new file mode 100644 index 0000000..4ae3805 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/2/OE/06E4EHW0RK6BBO6IKZ8MM7.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/5/7Z/29V5FZQ00G4SPGYVRIIHN5.uasset b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/5/7Z/29V5FZQ00G4SPGYVRIIHN5.uasset new file mode 100644 index 0000000..4191898 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/5/7Z/29V5FZQ00G4SPGYVRIIHN5.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/5/B1/01JFRDWADRKIKP06478EQG.uasset b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/5/B1/01JFRDWADRKIKP06478EQG.uasset new file mode 100644 index 0000000..e3c2d97 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/5/B1/01JFRDWADRKIKP06478EQG.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/8/36/KBB8RFBN2RRMBD1EAOZ825.uasset b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/8/36/KBB8RFBN2RRMBD1EAOZ825.uasset new file mode 100644 index 0000000..3e443cf Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/8/36/KBB8RFBN2RRMBD1EAOZ825.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/8/GS/2WS5T7NPSL6P167YENZ0RX.uasset b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/8/GS/2WS5T7NPSL6P167YENZ0RX.uasset new file mode 100644 index 0000000..1600f50 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/8/GS/2WS5T7NPSL6P167YENZ0RX.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/8/Q3/KY42ITXWBEPD99BD50GCCJ.uasset b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/8/Q3/KY42ITXWBEPD99BD50GCCJ.uasset new file mode 100644 index 0000000..e87565c Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/8/Q3/KY42ITXWBEPD99BD50GCCJ.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/9/UZ/KTVOLZ6X4TIVACY98L8NUE.uasset b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/9/UZ/KTVOLZ6X4TIVACY98L8NUE.uasset new file mode 100644 index 0000000..1862173 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/9/UZ/KTVOLZ6X4TIVACY98L8NUE.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/D/QJ/Q8MUZMCPNSAFNQNT1EMPEO.uasset b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/D/QJ/Q8MUZMCPNSAFNQNT1EMPEO.uasset new file mode 100644 index 0000000..4b49471 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/D/QJ/Q8MUZMCPNSAFNQNT1EMPEO.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/D/Y4/TGT919L5P5S1G33ISKEPWO.uasset b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/D/Y4/TGT919L5P5S1G33ISKEPWO.uasset new file mode 100644 index 0000000..163062c Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/D/Y4/TGT919L5P5S1G33ISKEPWO.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/E/AU/ABFDDJXNWORLPCM5L00YZ8.uasset b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/E/AU/ABFDDJXNWORLPCM5L00YZ8.uasset new file mode 100644 index 0000000..df32954 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/E/AU/ABFDDJXNWORLPCM5L00YZ8.uasset differ diff --git a/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/E/VP/CTS3EAYXWWHWS91H39XWNY.uasset b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/E/VP/CTS3EAYXWWHWS91H39XWNY.uasset new file mode 100644 index 0000000..65b49ee Binary files /dev/null and b/Content/__ExternalObjects__/Variant_Platforming/Lvl_Platforming/E/VP/CTS3EAYXWWHWS91H39XWNY.uasset differ diff --git a/Content/__ExternalObjects__/Variant_SideScrolling/Lvl_SideScrolling/1/UG/ZX8RGGTMXVLSKTMZHEWEKR.uasset b/Content/__ExternalObjects__/Variant_SideScrolling/Lvl_SideScrolling/1/UG/ZX8RGGTMXVLSKTMZHEWEKR.uasset new file mode 100644 index 0000000..4a7204f Binary files /dev/null and b/Content/__ExternalObjects__/Variant_SideScrolling/Lvl_SideScrolling/1/UG/ZX8RGGTMXVLSKTMZHEWEKR.uasset differ diff --git a/Content/__ExternalObjects__/Variant_SideScrolling/Lvl_SideScrolling/1/Z1/UMJIL66X80HOZOJFKETNE8.uasset b/Content/__ExternalObjects__/Variant_SideScrolling/Lvl_SideScrolling/1/Z1/UMJIL66X80HOZOJFKETNE8.uasset new file mode 100644 index 0000000..ac7b993 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_SideScrolling/Lvl_SideScrolling/1/Z1/UMJIL66X80HOZOJFKETNE8.uasset differ diff --git a/Content/__ExternalObjects__/Variant_SideScrolling/Lvl_SideScrolling/8/31/NU4AYO6JNDEAFXR2NA7X2O.uasset b/Content/__ExternalObjects__/Variant_SideScrolling/Lvl_SideScrolling/8/31/NU4AYO6JNDEAFXR2NA7X2O.uasset new file mode 100644 index 0000000..090604e Binary files /dev/null and b/Content/__ExternalObjects__/Variant_SideScrolling/Lvl_SideScrolling/8/31/NU4AYO6JNDEAFXR2NA7X2O.uasset differ diff --git a/Content/__ExternalObjects__/Variant_SideScrolling/Lvl_SideScrolling/9/XZ/RWPO76SKN90CVFMFKTDPKE.uasset b/Content/__ExternalObjects__/Variant_SideScrolling/Lvl_SideScrolling/9/XZ/RWPO76SKN90CVFMFKTDPKE.uasset new file mode 100644 index 0000000..9c3e0fd Binary files /dev/null and b/Content/__ExternalObjects__/Variant_SideScrolling/Lvl_SideScrolling/9/XZ/RWPO76SKN90CVFMFKTDPKE.uasset differ diff --git a/Content/__ExternalObjects__/Variant_SideScrolling/Lvl_SideScrolling/A/KQ/NGY0EZTAH2RRKD753U65B5.uasset b/Content/__ExternalObjects__/Variant_SideScrolling/Lvl_SideScrolling/A/KQ/NGY0EZTAH2RRKD753U65B5.uasset new file mode 100644 index 0000000..1e1f485 Binary files /dev/null and b/Content/__ExternalObjects__/Variant_SideScrolling/Lvl_SideScrolling/A/KQ/NGY0EZTAH2RRKD753U65B5.uasset differ diff --git a/DungeonCrawlerUE.uproject b/DungeonCrawlerUE.uproject new file mode 100644 index 0000000..d689ae4 --- /dev/null +++ b/DungeonCrawlerUE.uproject @@ -0,0 +1,35 @@ +{ + "FileVersion": 3, + "EngineAssociation": "5.7", + "Category": "", + "Description": "", + "Modules": [ + { + "Name": "DungeonCrawlerUE", + "Type": "Runtime", + "LoadingPhase": "Default" + } + ], + "Plugins": [ + { + "Name": "ModelingToolsEditorMode", + "Enabled": true + }, + { + "Name": "StateTree", + "Enabled": true + }, + { + "Name": "GameplayStateTree", + "Enabled": true + }, + { + "Name": "VisualStudioTools", + "Enabled": true, + "SupportedTargetPlatforms": [ + "Win64" + ], + "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/362651520df94e4fa65492dbcba44ae2" + } + ] +} \ No newline at end of file diff --git a/Planung/plan_v1.md b/Planung/plan_v1.md new file mode 100644 index 0000000..8c5fc83 --- /dev/null +++ b/Planung/plan_v1.md @@ -0,0 +1,1184 @@ +# Prozeduraler Dungeon Generator – Komplette Neuanleitung v4.2 + +## Überblick + +Snap-Together Dungeon Generator für Unreal Engine 5.7 mit Blueprints. +Vorgefertigte Räume docken an Arrow-Component-Sockets aneinander an. +5–100 Level pro Dungeon, Hauptpfad + optionale Abzweigungen (Branches). + +**Ablauf:** Generate Dungeon → Level für Level bauen (Entrance/PortalIn → MainPath → PortalOut/Exit) → nach Exit: Branches auf allen Leveln bauen → offene Sockets versiegeln → Spieler platzieren → Fertig. + +### Änderungen gegenüber v4 (final) + +1. **Runtime-State zentral im Generator** – unverändert übernommen. +2. **Stabile Socket-IDs** – unverändert übernommen. +3. **LevelVerticalOffset als Variable** – unverändert übernommen. +4. **NEU: Detaillierte Update-Logik für AllTrackedSockets** – Jede Stelle, an der ein Socket-State geändert wird, hat jetzt eine Schritt-für-Schritt Anleitung mit Find-by-Predicate + Set Array Elem. +5. **NEU: BranchFrontier-Kopie** – BuildBranches iteriert über eine Kopie, nicht das Original, um Array-Mutation während Iteration zu vermeiden. +6. **NEU: Socket-Auswahl beim Startraum** – Konkrete Logik, welcher Socket auf MainPath geht. +7. **NEU: Retry-Logik komplett definiert** – RetryLevel als eigenes Custom Event. +8. **NEU: FindCompatibleRoom Socket-Matching** – Konkrete Kriterien für "nutzbarer Eingangs-Socket". +9. **NEU: RegisterRoomSockets ohne TargetArray-Parameter** – Arbeitet direkt auf den Generator-Variablen. +10. **NEU: SnapRoomToSocket 180°-Rotation erklärt** – Warum Combine Rotators mit 180° nötig ist. + +### Bewusst nicht übernommene Vorschläge + +| Vorschlag | Entscheidung | Begründung | +|---|---|---| +| BP_DungeonRoomBase als gemeinsame Elternklasse | Nicht übernommen | UE erlaubt keine Viewport-Änderungen in Child Blueprints. Wir nutzen eigenständige Duplikate + Interface, was flexibler ist für unterschiedliche Raum-Geometrien. | +| Kollision vor Spawn prüfen (CanPlaceRoom) | Nicht übernommen (vorerst) | Spawn-then-Destroy funktioniert für den Prototyp. Optimierung auf Box Trace kommt später wenn die Grundlogik steht. | +| Branches während des MainPath-Baus | Nicht übernommen | Branches nach dem Exit über alle Level ist einfacher zu implementieren und zu debuggen. | +| Erweiterte Data Table (Tags, FootprintExtent, CanBeMainPath etc.) | Nicht übernommen (vorerst) | Die einfache Data Table reicht für den Prototyp. | +| FindCompatibleRoom mit Score-System | Nicht übernommen (vorerst) | Gewichtete Zufallswahl reicht erstmal. | +| E_SocketType (Standard, Large, Stair, Portal) | Nicht übernommen (vorerst) | Alle Räume haben aktuell die gleiche Durchgangsgröße. | +| SealOpenSockets als eigener Schritt | Übernommen | DeadEnds schließen Branches ab. Übrige offene Sockets werden mit WallBlockern verschlossen. | + +--- + +## Teil 1: Datenstrukturen + +### 1.1 Enums + +**E_RoomType** (existiert bereits, ggf. erweitern): +- Entrance, General, Corridor, PortalIn, PortalOut, Exit, DeadEnd + +**E_SocketStatus** (existiert bereits): +- Open, Occupied, Closed + +**E_PathType** (NEU erstellen): +- MainPath, Branch, None + +### 1.2 Structs + +#### S_SocketDefinition (NEU – ersetzt das alte S_SocketData in den Räumen) + +Beschreibt einen Socket am Raum. Wird im Construction Script befüllt. Enthält **keinen** Laufzeit-Zustand. + +| Feld | Typ | Beschreibung | +|---|---|---| +| SocketId | Name | Eindeutiger Name z.B. "Socket_North", "Socket_South" | +| SocketComponent | Arrow Component Ref | Referenz auf die Arrow Component | +| LocalTransform | Transform | Lokale Position/Rotation relativ zum Actor | + +#### S_OpenSocketData (NEU – Runtime-State im Generator) + +Das ist der wichtigste Struct. Der Generator verwaltet damit alle offenen Verbindungspunkte. Ersetzt das alte S_SocketData für Laufzeit-Zwecke. + +| Feld | Typ | Beschreibung | +|---|---|---| +| OwnerRoom | Actor Object Ref | Der Raum dem dieser Socket gehört | +| SocketId | Name | Eindeutige Socket-ID (z.B. "Socket_North") | +| SocketIndex | Integer | Index in der Arrow-Liste (Fallback) | +| PathType | E_PathType | MainPath, Branch oder None | +| LevelIndex | Integer | Auf welchem Level liegt dieser Socket | +| BranchDepth | Integer | Tiefe der Abzweigung (0 = am MainPath) | +| State | E_SocketStatus | Open, Occupied, Closed | + +**WICHTIG:** WorldTransform wird NICHT im Struct gespeichert! Die Position wird immer live über GetSocketWorldTransformById berechnet. + +#### S_PlacedRoomData (NEU – Raum-Tracking im Generator) + +Speichert Informationen über jeden platzierten Raum zentral im Generator. + +| Feld | Typ | Beschreibung | +|---|---|---| +| RoomActor | Actor Object Ref | Referenz auf den gespawnten Actor | +| RoomType | E_RoomType | Typ des Raums | +| PathType | E_PathType | MainPath oder Branch | +| LevelIndex | Integer | Auf welchem Level | +| BranchDepth | Integer | Abzweigungstiefe (0 = MainPath) | + +#### S_RoomPoolEntry (existiert bereits, unverändert) + +| Feld | Typ | Beschreibung | +|---|---|---| +| RoomClass | Actor Class Ref | Blueprint-Klasse des Raums | +| RoomType | E_RoomType | Typ | +| Weight | Float | Gewichtung für Zufallswahl | +| MinLevel | Integer | Ab welchem Level verfügbar | +| MaxLevel | Integer | Bis welchem Level (0 = unbegrenzt) | + +### 1.3 Was wird gelöscht/ersetzt + +- **S_SocketData** bleibt in den Räumen, wird aber auf **S_SocketDefinition** reduziert (nur noch statische Daten) +- Felder die aus S_SocketData entfernt werden: Status, IsOnMainPath, OwnerRoom, WorldLocation, WorldRotation +- Der gesamte Laufzeit-Zustand wandert in **S_OpenSocketData** und **S_PlacedRoomData** im Generator + +--- + +## Teil 2: Stabile Socket-IDs einführen + +### 2.1 Arrow Components benennen + +In **jedem** Raum-Blueprint: Benenne die Arrow Components eindeutig. + +Beispiel für einen 4-Socket-Raum: +- Arrow Component 1 → Name: **Socket_North** +- Arrow Component 2 → Name: **Socket_East** +- Arrow Component 3 → Name: **Socket_South** +- Arrow Component 4 → Name: **Socket_West** + +Beispiel für DeadEnd (1 Socket): +- Arrow Component 1 → Name: **Socket_Entry** + +Beispiel für Corridor (2 Sockets): +- Arrow Component 1 → Name: **Socket_Entry** +- Arrow Component 2 → Name: **Socket_Exit** + +Die Namen müssen innerhalb eines Raums eindeutig sein, dürfen aber zwischen verschiedenen Raumtypen gleich heißen. + +### 2.2 Variablen pro Raum-Blueprint + +Jeder Raum-Blueprint braucht nur die wirklich nötigen Variablen. Der Laufzeit-State liegt **nicht** im Raum, sondern im Generator. + +**Pflicht-Variablen in jedem Raum:** + +| Variable | Typ | Default | Zweck | +|---|---|---|---| +| RoomType | E_RoomType | je nach Raum | Gibt den Typ des Raums zurück | +| SocketDefinitions | Array of S_SocketDefinition | leer | Wird im Construction Script aus den Arrow Components befüllt | + +**Optional, nur wenn du sie wirklich brauchst:** + +| Variable | Typ | Default | Zweck | +|---|---|---|---| +| RoomMesh / zusätzliche Mesh-Referenzen | Static Mesh Component Ref | – | Nur falls du im Graph direkt darauf zugreifen willst | +| BoundingBox | Box Collision Ref | – | Für Overlap/Kollisionsprüfung | +| DebugName | Name oder String | leer | Optional für Prints und Debugging | + +**Nicht mehr im Raum speichern:** + +- ~~Sockets~~ als `Array` +- ~~ConnectedRooms~~ +- ~~IsOnMainPath~~ +- ~~BranchDepth~~ +- ~~SocketStatus / Occupied / Closed~~ +- ~~OwnerRoom~~ +- ~~WorldLocation / WorldRotation als gespeicherte Runtime-Werte~~ + +**RoomType-Defaults je Raum:** + +| Blueprint | RoomType Default | +|---|---| +| BP_DungeonRoom_Entrance | Entrance | +| BP_DungeonRoom_General | General | +| BP_DungeonRoom_Corridor | Corridor | +| BP_DungeonRoom_PortalIn | PortalIn | +| BP_DungeonRoom_PortalOut | PortalOut | +| BP_DungeonRoom_Exit | Exit | +| BP_DungeonRoom_DeadEnd | DeadEnd | + +### 2.3 Construction Script anpassen (alle Räume) + +Das Construction Script befüllt jetzt **S_SocketDefinition** statt S_SocketData: + +1. **Construction Script ▶** → **Clear** `SocketDefinitions` +2. → **Get Components by Class** (Arrow Component, Target: self) +3. → **ForEachLoop** (Array: Return Value) +4. **Loop Body ▶** → **Make S_SocketDefinition**: + - SocketId: Array Element → **Get Display Name** (oder **Get Object Name**) → **Convert to Name** (z.B. "Socket_North") + - SocketComponent: Array Element (die Arrow Component Referenz selbst) + - LocalTransform: Array Element → **Get Relative Transform** +5. → **Add** zu SocketDefinitions Array (Variable im Raum, Typ: Array of S_SocketDefinition) +6. ForEachLoop **Completed ▶** → (Ende) + +**Blueprint-Nodes Schritt für Schritt:** +``` +Construction Script ▶ → Clear (SocketDefinitions) + → Get Components by Class (Arrow Component) + → ForEachLoop + Loop Body ▶ → Make S_SocketDefinition + ├─ SocketId: Array Element → Get Object Name → To Name + ├─ SocketComponent: Array Element + └─ LocalTransform: Array Element → Get Relative Transform + → Add (SocketDefinitions, S_SocketDefinition) + Completed ▶ → (Ende) +``` + +### 2.4 GetSocketWorldTransformById implementieren + +Die Interface-Funktion sucht einen Socket anhand seiner ID und gibt die aktuelle Weltposition zurück: + +**GetSocketWorldTransformById** (NEU im Interface): +- Input: SocketId (Name) +- Output: Location (Vector), Rotation (Rotator), Success (Bool) + +Implementierung in jedem Raum: +1. **SocketDefinitions** Array → **ForEachLoop** +2. → **Branch**: Array Element → Break S_SocketDefinition → SocketId → **Equal (Name)** → Input SocketId +3. → True ▶: Array Element → Break S_SocketDefinition → SocketComponent → **GetWorldLocation** → Set Location +4. → Array Element → Break S_SocketDefinition → SocketComponent → **GetWorldRotation** → Set Rotation +5. → **Return** (Location, Rotation, Success: true) +6. → ForEachLoop Completed ▶: **Return** (Location: 0,0,0, Rotation: 0,0,0, Success: **false**) + +**WICHTIG:** Wir holen Location/Rotation direkt von der **Arrow Component Referenz** (SocketComponent), nicht aus dem gespeicherten LocalTransform. So bekommen wir immer die aktuelle Weltposition, auch wenn der Actor bewegt wurde. + +**GetSocketWorldTransformByIndex** (existiert bereits, behalten als Fallback): +- Input: SocketIndex (Int) +- Output: Location (Vector), Rotation (Rotator) +- Implementierung: Get Components by Class → Get (Index) → GetWorldLocation + GetWorldRotation + +### 2.5 BPI_DungeonRoom Interface aktualisieren + +| Funktion | Inputs | Outputs | +|---|---|---| +| GetSocketDefinitions | – | SocketDefinitions (Array S_SocketDefinition) | +| GetSocketWorldTransformById | SocketId (Name) | Location (Vector), Rotation (Rotator), Success (Bool) | +| GetSocketWorldTransformByIndex | SocketIndex (Int) | Location (Vector), Rotation (Rotator) | +| GetRoomType | – | RoomType (E_RoomType) | + +**Entfernte Funktionen** (Laufzeit-State liegt jetzt im Generator): +- ~~SetIsOnMainPath~~ → wird über S_PlacedRoomData im Generator gesetzt +- ~~GetIsOnMainPath~~ → wird über S_PlacedRoomData im Generator gelesen +- ~~SetBranchDepth~~ → wird über S_PlacedRoomData im Generator gesetzt +- ~~GetBranchDepth~~ → wird über S_PlacedRoomData im Generator gelesen +- ~~GetSockets~~ → ersetzt durch GetSocketDefinitions + +--- + +## Teil 3: BP_DungeonGenerator Variablen + +### 3.1 Dungeon-Parameter (Instance Editable) + +| Variable | Typ | Default | Beschreibung | +|---|---|---|---| +| Difficulty | Float | 0.5 | 0.0–1.0, bestimmt Größe | +| RandomSeed | Integer | 0 | Seed für Reproduzierbarkeit | +| RoomPoolTable | Data Table Ref | DT_RoomPool | Referenz auf die Raum-Tabelle | +| MinRoomsPerLevel | Integer | 5 | Minimum Räume pro Level | +| MaxRoomsPerLevel | Integer | 15 | Maximum Räume pro Level | +| MinLevels | Integer | 5 | Minimum Dungeon-Level | +| MaxLevels | Integer | 20 | Maximum Dungeon-Level | +| MaxRetries | Integer | 5 | Retry-Limit pro Level | +| LevelVerticalOffset | Float | 1000.0 | Z-Abstand zwischen Level-Ebenen | +| MaxBranchDepthMin | Integer | 1 | Minimale Branch-Tiefe (für Random Range) | +| MaxBranchDepthMax | Integer | 5 | Maximale Branch-Tiefe (für Random Range) | + +### 3.2 Laufzeit-Variablen (nicht Instance Editable) + +| Variable | Typ | Default | Beschreibung | +|---|---|---|---| +| TotalLevels | Integer | 0 | Berechnete Level-Anzahl | +| CurrentLevel | Integer | 0 | Aktuelles Level | +| MaxRoomsOfLevel | Integer | 0 | Berechnete Räume pro Level | +| CurrentRoomsOnLevel | Integer | 0 | Zähler platzierte Räume | +| PlacedRooms | Array (S_PlacedRoomData) | leer | Alle platzierten Räume mit Metadaten | +| PlacedRoomActors | Array (Actor Obj Ref) | leer | Alle Raum-Actors (für Destroy bei Retry) | +| AllTrackedSockets | Array (S_OpenSocketData) | leer | **Zentrale Wahrheitsquelle** für alle Sockets aller platzierten Räume | +| MainPathFrontier | Array (S_OpenSocketData) | leer | Offene Sockets am MainPath (Arbeitsliste) | +| BranchFrontier | Array (S_OpenSocketData) | leer | Alle Branch-Socket-Kandidaten (Arbeitsliste) | +| StartRoom | Actor Object Ref | None | Aktueller Startraum | +| EndRoom | Actor Object Ref | None | Aktueller Endraum | +| LastPortalOut | Actor Object Ref | None | Letzter PortalOut | +| RetryCount | Integer | 0 | Aktueller Retry-Zähler | +| SpawnedBlockers | Array (Actor Obj Ref) | leer | Alle WallBlocker | +| CurrentSocket | S_OpenSocketData | – | Temporär: aktuell verarbeiteter Socket | +| OpenSocketsBuffer | Array (S_OpenSocketData) | leer | Wiederverwendbarer Puffer für Socket-Verteilung – immer clearen vor Verwendung | +| RandomPickedIndex | Integer | 0 | Wiederverwendbar für zufälligen Socket-Pick | + +--- + +## Teil 4: BP_DungeonGenerator – Event Graph + +### 4.1 Custom Events erstellen + +1. **GenerateDungeon** – Keine Parameter +2. **BuildLevel** – Keine Parameter +3. **BuildMainPath** – Keine Parameter +4. **PlaceEndRoom** – Keine Parameter +5. **BuildBranches** – Keine Parameter +6. **SealOpenSockets** – Keine Parameter +7. **PlacePlayer** – Keine Parameter +8. **RetryLevel** – Keine Parameter (NEU) + +### 4.2 BeginPlay + +1. **Event BeginPlay ▶** → **GenerateDungeon** + +### 4.3 GenerateDungeon + +1. **GenerateDungeon ▶** +2. → **Set RandomSeed** (Set Random Stream Seed oder Random Integer in Range falls Seed == 0) +3. → **Print String**: "Seed: " + RandomSeed (für Reproduzierbarkeit) +4. → **Lerp (Float)**: A = MinLevels, B = MaxLevels, Alpha = Difficulty → **Round to Int** → **Set TotalLevels** +5. → **Lerp (Float)**: A = MinRoomsPerLevel, B = MaxRoomsPerLevel, Alpha = Difficulty → **Round to Int** → **Set MaxRoomsOfLevel** +6. → **Set CurrentLevel** = 1 +7. → **Set CurrentRoomsOnLevel** = 0 +8. → **Set RetryCount** = 0 +9. → **Clear** PlacedRooms, PlacedRoomActors, AllTrackedSockets, MainPathFrontier, BranchFrontier, SpawnedBlockers +10. → **BuildLevel** + +**Exec-Kette:** Alle Setter und Clears müssen in der Exec-Kette verbunden sein! + +``` +GenerateDungeon ▶ → Set RandomSeed ▶ → Print String ▶ → Lerp+Set TotalLevels ▶ +→ Lerp+Set MaxRoomsOfLevel ▶ → Set CurrentLevel ▶ → Set CurrentRoomsOnLevel ▶ +→ Set RetryCount ▶ → Clear (alle Arrays) ▶ → BuildLevel +``` + +### 4.4 BuildLevel + +1. **BuildLevel ▶** +2. → **Set CurrentRoomsOnLevel** = 0 +3. → **Clear** MainPathFrontier +4. → **Branch**: CurrentLevel == 1? + +**True ▶ (Erstes Level – Entrance):** + +5. → **SpawnActor** BP_DungeonRoom_Entrance, Location: (0, 0, 0), Rotation: (0, 0, 0) +6. → **Set StartRoom** = Return Value +7. → **Add** Return Value zu PlacedRoomActors +8. → **RegisterRoomSockets** aufrufen (Funktion, Room = Return Value, BranchDepth = 0) + - Füllt OpenSocketsBuffer mit allen Sockets (PathType: Branch, State: Open) + - AllTrackedSockets wird noch NICHT befüllt – das passiert nach dem Random Pick + +**Socket-Verteilung für den Startraum (Schritt 9):** + +9. → OpenSocketsBuffer verteilen (kein Filter nötig – Buffer enthält nur Sockets dieses Raums): + - **Schritt 9a:** Zufälligen MainPath-Socket aus OpenSocketsBuffer wählen + - **Schritt 9b:** Gewählten Socket mit PathType: **MainPath** in AllTrackedSockets eintragen → **Add** zu **MainPathFrontier** + - **Schritt 9c:** Alle übrigen mit PathType: **Branch** in AllTrackedSockets eintragen → **Add** zu **BranchFrontier** + + > **Warum zufällig?** "Ersten nehmen" wäre immer derselbe Socket (abhängig von der Reihenfolge der Arrow Components im Blueprint-Viewport), nicht vom Seed gesteuert. Der Zufalls-Pick sorgt dafür, dass der MainPath-Ausgang bei jedem Seed anders liegen kann. + + **Konkret in Blueprints:** + ``` + // Schritt 9a – Zufälligen MainPath-Socket wählen + Branch: OpenSocketsBuffer Length > 0? + True ▶ → Random Integer in Range from Stream + (Stream: RandomStream, Min: 0, Max: OpenSocketsBuffer Length - 1) + → Set RandomPickedIndex + + // Schritt 9b + 9c – Alle Buffer-Einträge in AllTrackedSockets eintragen und verteilen + OpenSocketsBuffer → ForEachLoop + Loop Body ▶ → Branch: Array Index == RandomPickedIndex? + True ▶ → Array Element → Break S_OpenSocketData + → Make S_OpenSocketData + ├─ OwnerRoom, SocketId, SocketIndex, LevelIndex, BranchDepth, State: jeweiliger Pin von Break + └─ PathType: MainPath ← einzige Änderung + → Add zu AllTrackedSockets + → Add zu MainPathFrontier + False ▶ → Array Element → Add zu AllTrackedSockets (PathType bereits Branch, kein Break/Make nötig) + → Array Element → Add zu BranchFrontier + ``` + + **Gemeinsame Variablen (Generator-Ebene, überall wiederverwenden):** + - `OpenSocketsBuffer` – Array of S_OpenSocketData (wird von RegisterRoomSockets geleert und befüllt) + - `RandomPickedIndex` – Integer + +10. → **Make S_PlacedRoomData** (RoomActor: StartRoom, RoomType: Entrance, PathType: MainPath, LevelIndex: CurrentLevel, BranchDepth: 0) → **Add** zu PlacedRooms +11. → **BuildMainPath** + +**False ▶ (Level 2+ – PortalIn):** + +5. → LastPortalOut → **GetActorLocation** → **Subtract** Vector(0, 0, **LevelVerticalOffset**) → **Set** PortalInLocation +6. → **SpawnActor** BP_DungeonRoom_PortalIn, Location: PortalInLocation, Rotation: (0, 0, 0) +7. → **Set StartRoom** = Return Value +8. → **Add** Return Value zu PlacedRoomActors +9. → **RegisterRoomSockets** aufrufen (Funktion, Room = Return Value, BranchDepth = 0) + - Füllt OpenSocketsBuffer mit allen Sockets (PathType: Branch, State: Open) + +**Socket-Verteilung (identisch zu oben):** + +10. → Random Pick aus OpenSocketsBuffer → gewählter Socket mit PathType: MainPath in AllTrackedSockets + MainPathFrontier → Rest mit PathType: Branch in AllTrackedSockets + BranchFrontier (siehe Schritt 9 oben für vollständige Blueprint-Logik) + +11. → **Make S_PlacedRoomData** (RoomActor: StartRoom, RoomType: PortalIn, PathType: MainPath, LevelIndex: CurrentLevel, BranchDepth: 0) → **Add** zu PlacedRooms +12. → **BuildMainPath** + +### 4.5 RegisterRoomSockets (Hilfsfunktion, NEU) + +Diese Funktion liest die Socket-Definitionen eines Raums und füllt **OpenSocketsBuffer** mit S_OpenSocketData-Einträgen. Sie schreibt **nicht** direkt in AllTrackedSockets – das übernimmt der Aufrufer nach dem Random Pick, damit PathType für jeden Socket korrekt gesetzt wird. + +**Umsetzung:** Erstelle eine neue Funktion (nicht Custom Event) im BP_DungeonGenerator: + +**Inputs:** +- Room (Actor Object Ref) +- BranchDepth (Integer, Default: 0) + +**Keine Outputs** (schreibt in OpenSocketsBuffer, clearet ihn vorher). + +**Graph:** + +1. → **Clear** OpenSocketsBuffer +2. → Room → **GetSocketDefinitions (Message)** → SocketDefinitions Array +3. → **Branch**: SocketDefinitions → **Length** > 0? +4. → False ▶: **Print String** "WARNING: Room has no sockets!" → **Return** +5. → True ▶: **ForEachLoop** über SocketDefinitions +6. **Loop Body ▶** → **Make S_OpenSocketData**: + - OwnerRoom: Room (Input) + - SocketId: Array Element → Break S_SocketDefinition → SocketId + - SocketIndex: ForEachLoop → **Array Index** + - PathType: **Branch** (immer – wird nach dem Random Pick für den gewählten Socket auf MainPath korrigiert) + - LevelIndex: CurrentLevel (Generator-Variable) + - BranchDepth: BranchDepth (Input) + - State: **Open** +7. → **Add** zu **OpenSocketsBuffer** +8. ForEachLoop **Completed ▶** → **Return** + +``` +RegisterRoomSockets (Room, BranchDepth) + → Clear OpenSocketsBuffer + → Room → GetSocketDefinitions (Message) → SocketDefinitions + → Branch: Length > 0? + False ▶ → Print "WARNING: Room has no sockets!" → Return + True ▶ → ForEachLoop (SocketDefinitions) + Loop Body ▶ → Make S_OpenSocketData + ├─ OwnerRoom: Room + ├─ SocketId: Element.SocketId + ├─ SocketIndex: Array Index + ├─ PathType: Branch + ├─ LevelIndex: CurrentLevel + ├─ BranchDepth: BranchDepth + └─ State: Open + → Add zu OpenSocketsBuffer + Completed ▶ → Return +``` + +**WICHTIG:** `AllTrackedSockets` ist die einzige vollständige Socket-Datenbank. `OpenSocketsBuffer` ist nur ein temporärer Zwischenpuffer – nach dem Random Pick werden alle Einträge aus dem Buffer mit korrekt gesetztem PathType in AllTrackedSockets eingetragen. `MainPathFrontier` und `BranchFrontier` sind Arbeits-Arrays für aktuell offene Kandidaten. + +### 4.5.1 UpdateSocketState (Hilfsfunktion, NEU) + +Diese Funktion wird an vielen Stellen benötigt. Sie findet einen bestimmten Socket in AllTrackedSockets und setzt seinen State. + +**Umsetzung:** Erstelle eine neue Funktion im BP_DungeonGenerator: + +**Inputs:** +- TargetRoom (Actor Object Ref) – Der Raum dessen Socket geändert wird +- TargetSocketId (Name) – Die Socket-ID die geändert wird +- NewState (E_SocketStatus) – Der neue State (Occupied, Closed, etc.) + +**Keine Outputs.** + +**Graph:** + +1. → **AllTrackedSockets** → **ForEachLoop with Break** +2. **Loop Body ▶** → Array Element → **Break S_OpenSocketData** +3. → **Branch**: OwnerRoom == TargetRoom **AND** SocketId == TargetSocketId? +4. → **True ▶:** + - Array Element → **Break S_OpenSocketData** → alle Felder einzeln rausholen + - **Make S_OpenSocketData** mit allen gleichen Werten, aber **State = NewState** + - **Set Array Elem** (Target: AllTrackedSockets, Index: ForEachLoop Array Index, Item: der neue S_OpenSocketData) + - → **Break** (ForEachLoop beenden) +5. → **False ▶:** → weiter (nächste Iteration) + +``` +UpdateSocketState (TargetRoom, TargetSocketId, NewState) + → AllTrackedSockets → ForEachLoop with Break + Loop Body ▶ → Break S_OpenSocketData + → Branch: OwnerRoom == TargetRoom AND SocketId == TargetSocketId? + True ▶ → Make S_OpenSocketData (alle Felder kopieren, State = NewState) + → Set Array Elem (AllTrackedSockets, Array Index, neuer Eintrag) + → Break + False ▶ → (weiter) +``` + +**UE Blueprint Details für Set Array Elem:** +- Rechtsklick in den Graph → "Set Array Elem" +- Target: AllTrackedSockets (Variable-Getter, als Referenz) +- Index: ForEachLoop → Array Index +- Item: Der neue S_OpenSocketData Struct +- **Size to Fit: NEIN** (der Index existiert bereits) + +### 4.6 BuildMainPath + +**Umsetzung als While-Loop:** + +**While Loop Condition:** +- **AND**: MainPathFrontier **Length > 0** UND CurrentRoomsOnLevel **< MaxRoomsOfLevel** + +**Loop Body ▶:** + +1. → MainPathFrontier **Get (Index 0)** → **Set CurrentSocket** +2. → MainPathFrontier **Remove Index 0** +3. → **FindCompatibleRoom** (OpenSocket: CurrentSocket, ExcludeTypes: [Entrance, Exit, PortalIn, PortalOut, DeadEnd]) + - Details siehe **Teil 5.1** +4. → **Branch** (Success) + +**False ▶ (kein kompatibler Raum gefunden):** +5. → **Print String** "No compatible room found for socket" (Debug) +6. → (While Loop nächste Iteration – der nächste Socket in MainPathFrontier wird versucht) + +**True ▶ (Raum gefunden):** +5. → **SnapRoomToSocket** (OpenSocket: CurrentSocket, NewRoomClass: FoundRoomClass, NewSocketIndex: MatchingSocketIndex, NewSocketId: MatchingSocketId) + - Details siehe **Teil 5.2** +6. → **Branch** (Success) + +**False ▶ (Snap fehlgeschlagen, z.B. Kollision):** +7. → **Print String** "Snap failed (collision)" (Debug) +8. → (While Loop nächste Iteration) + +**True ▶ (Snap erfolgreich):** + +**Schritt 7 – Raum registrieren:** +7. → **Add** SpawnedRoom zu PlacedRoomActors +8. → **CurrentRoomsOnLevel + 1** → **Set CurrentRoomsOnLevel** +9. → **Make S_PlacedRoomData** (RoomActor: SpawnedRoom, RoomType: aus FindCompatibleRoom, PathType: MainPath, LevelIndex: CurrentLevel, BranchDepth: 0) → **Add** zu PlacedRooms + +**Schritt 10 – Benutzten Socket auf Occupied setzen:** +10. → **UpdateSocketState** (TargetRoom: CurrentSocket.OwnerRoom, TargetSocketId: CurrentSocket.SocketId, NewState: **Occupied**) + +**Schritt 11 – Sockets des neuen Raums registrieren und verteilen (KRITISCH):** + +Ohne die folgende Regel verzweigt der MainPath unkontrolliert und wird zu einem Netz statt einem klaren Pfad. Die Regel ist: **Genau 1 Socket geht auf MainPath, der Rest geht auf Branch.** + +> **Warum zufällig?** "Ersten nehmen" wäre immer derselbe Socket (abhängig von der Reihenfolge der Arrow Components im Blueprint-Viewport). Der Zufalls-Pick sorgt dafür, dass der MainPath-Ausgang bei jedem Seed anders liegen kann. Das gilt für alle Räume gleichermaßen – beim Startraum gibt es keinen Eingangs-Socket, bei allen anderen ist genau einer belegt. + +**MatchingSocketId kennen:** +MatchingSocketId ist die SocketId des Sockets am neuen Raum, der für das Andocken benutzt wurde. Du bekommst sie aus FindCompatibleRoom: +- In FindCompatibleRoom hast du den SocketDefinitions-Array des Kandidaten durchsucht +- MatchingSocketIndex ist der Array-Index des gewählten Sockets +- MatchingSocketId = SocketDefinitions[MatchingSocketIndex].SocketId +- **Erweitere FindCompatibleRoom so, dass es auch MatchingSocketId (Name) zurückgibt** (zusätzlich zu MatchingSocketIndex) + +**Umsetzung (gleiches Muster wie Schritt 9, nur mit Eingangs-Socket-Behandlung davor):** + +``` +// Schritt 11a – Buffer füllen (RegisterRoomSockets cleared OpenSocketsBuffer selbst) +RegisterRoomSockets (Room: SpawnedRoom, BranchDepth: 0) + → OpenSocketsBuffer enthält alle Sockets mit PathType: Branch, State: Open + +// Schritt 11b – Eingangs-Socket aus Buffer herausnehmen und als Occupied in AllTrackedSockets eintragen +OpenSocketsBuffer → ForEachLoop with Break + Loop Body ▶ → Branch: Element.SocketId == MatchingSocketId? + True ▶ → Make S_OpenSocketData (alle Felder von Element, State: Occupied, PathType: Branch) + → Add zu AllTrackedSockets + → Remove (OpenSocketsBuffer, Array Index) + → Break + +// Schritt 11c – Zufälligen MainPath-Socket aus verbliebenem Buffer wählen +Branch: OpenSocketsBuffer Length > 0? + False ▶ → Print "WARNING: New room has no free sockets!" → (weiter) + True ▶ → Random Integer in Range from Stream (0, OpenSocketsBuffer Length - 1) + → Set RandomPickedIndex + +// Schritt 11d – Restlichen Buffer in AllTrackedSockets eintragen und verteilen +OpenSocketsBuffer → ForEachLoop + Loop Body ▶ → Branch: Array Index == RandomPickedIndex? + True ▶ → Array Element → Break S_OpenSocketData + → Make S_OpenSocketData + ├─ OwnerRoom, SocketId, SocketIndex, LevelIndex, BranchDepth, State: jeweiliger Pin von Break + └─ PathType: MainPath ← einzige Änderung + → Add zu AllTrackedSockets + → Add zu MainPathFrontier + False ▶ → Array Element → Add zu AllTrackedSockets (PathType bereits Branch, kein Break/Make nötig) + → Array Element → Add zu BranchFrontier +``` + +**Ergebnis pro Raum:** Genau 1 Socket → Occupied (Eingang), genau 1 zufälliger Socket → MainPathFrontier (Ausgang), Rest → BranchFrontier. + +``` +Zusammenfassung Socket-Verteilung (gilt für ALLE Räume): + +Schritt a – RegisterRoomSockets → OpenSocketsBuffer gefüllt (alle Branch, Open), AllTrackedSockets noch leer für diesen Raum +Schritt b – Eingangs-Socket aus Buffer → AllTrackedSockets (Occupied) [entfällt beim Startraum] +Schritt c – Random Pick Index aus verbliebenem Buffer bestimmen +Schritt d – Buffer → AllTrackedSockets + Frontiers: + ├─ Zufälliger Index → PathType: MainPath, MainPathFrontier + └─ Alle anderen → PathType: Branch, BranchFrontier +``` + +**While Loop Completed ▶** → **PlaceEndRoom** + +### 4.7 PlaceEndRoom + +1. **PlaceEndRoom ▶** +2. → **Branch**: MainPathFrontier **Length > 0**? + +**False ▶ (Kein offener MainPath-Socket mehr):** +3. → **Print String** "No open MainPath socket for end room!" +4. → **RetryLevel** (siehe Teil 4.11) + +**True ▶:** +3. → MainPathFrontier **Get (Index 0)** → **Set CurrentSocket** +4. → **Branch**: CurrentLevel == TotalLevels? + +**True ▶ (Letztes Level – Exit):** +5. → **FindCompatibleRoom** (OpenSocket: CurrentSocket, ExcludeTypes: [Entrance, PortalIn, PortalOut, General, Corridor, DeadEnd]) + - Damit bleibt nur **Exit** übrig als gültiger Typ +6. → **Branch** (Success) + - False ▶: **Print String** "No Exit room found!" → **RetryLevel** +7. → True ▶: **SnapRoomToSocket** (OpenSocket: CurrentSocket, NewRoomClass: FoundRoomClass, NewSocketIndex: MatchingSocketIndex, NewSocketId: MatchingSocketId) +8. → **Branch** (Success) + - False ▶: **Print String** "Exit snap failed!" → **RetryLevel** +9. → True ▶: +10. → **Set EndRoom** = SpawnedRoom +11. → **Add** SpawnedRoom zu PlacedRoomActors +12. → **Make S_PlacedRoomData** (PathType: MainPath, RoomType: Exit, LevelIndex: CurrentLevel, BranchDepth: 0) → **Add** zu PlacedRooms +13. → **UpdateSocketState** (CurrentSocket.OwnerRoom, CurrentSocket.SocketId, **Occupied**) + +**Schritt 14 – Alle Sockets des Exit-Raums registrieren:** +14. → **RegisterRoomSockets** (Room: SpawnedRoom, PathType: MainPath, BranchDepth: 0) + - Registriert alle Sockets des Exit-Raums in AllTrackedSockets mit State: Open +15. → Den Eingangs-Socket des Exit-Raums auf Occupied setzen: + → **UpdateSocketState** (TargetRoom: SpawnedRoom, TargetSocketId: **MatchingSocketId**, NewState: **Occupied**) + - MatchingSocketId kommt aus FindCompatibleRoom (Schritt 5) + - Übrige Sockets des Exit-Raums bleiben Open und werden später von SealOpenSockets mit WallBlockern verschlossen +16. → **BuildBranches** + +**False ▶ (Nicht letztes Level – PortalOut):** +5. → **FindCompatibleRoom** (OpenSocket: CurrentSocket, ExcludeTypes: [Entrance, PortalIn, Exit, General, Corridor, DeadEnd]) + - Damit bleibt nur **PortalOut** übrig als gültiger Typ +6. → **Branch** (Success) + - False ▶: **Print String** "No PortalOut room found!" → **RetryLevel** +7. → True ▶: **SnapRoomToSocket** (OpenSocket: CurrentSocket, NewRoomClass: FoundRoomClass, NewSocketIndex: MatchingSocketIndex, NewSocketId: MatchingSocketId) → **Branch** (Success) + - False ▶: → **RetryLevel** +8. → True ▶: +9. → **Set EndRoom** = SpawnedRoom +10. → **Set LastPortalOut** = SpawnedRoom +11. → **Add** SpawnedRoom zu PlacedRoomActors +12. → **Make S_PlacedRoomData** (PathType: MainPath, RoomType: PortalOut, LevelIndex: CurrentLevel, BranchDepth: 0) → **Add** zu PlacedRooms +13. → **UpdateSocketState** (CurrentSocket.OwnerRoom, CurrentSocket.SocketId, **Occupied**) + +**Schritt 14 – Alle Sockets des PortalOut-Raums registrieren:** +14. → **RegisterRoomSockets** (Room: SpawnedRoom, PathType: MainPath, BranchDepth: 0) + - Registriert alle Sockets des PortalOut-Raums in AllTrackedSockets mit State: Open +15. → Den Eingangs-Socket des PortalOut-Raums auf Occupied setzen: + → **UpdateSocketState** (TargetRoom: SpawnedRoom, TargetSocketId: **MatchingSocketId**, NewState: **Occupied**) + - MatchingSocketId kommt aus FindCompatibleRoom (Schritt 5) + - Übrige Sockets des PortalOut-Raums bleiben Open → werden von SealOpenSockets verschlossen +16. → **Set CurrentLevel** = CurrentLevel + 1 +17. → **BuildLevel** (nächstes Level starten) + +**WICHTIG:** Branches werden NICHT nach PortalOut gebaut! Nur nach Exit (letztes Level). Dadurch sammelt BranchFrontier Sockets über ALLE Level hinweg, und BuildBranches baut sie am Ende alle auf einmal. + +### 4.8 BuildBranches + +Wird NUR nach dem Exit-Raum aufgerufen. Baut Abzweigungen über alle Level hinweg. + +1. **BuildBranches ▶** +2. → **Branch**: BranchFrontier **Length > 0**? + - False ▶: **Print String** "No branch sockets available" → **SealOpenSockets** + +**WICHTIG: Array-Kopie erstellen!** BranchFrontier wird während der Iteration verändert (neue Sockets werden hinzugefügt). In UE Blueprints darf man ein Array nicht verändern während man darüber iteriert. + +3. → True ▶: BranchFrontier → **Copy** in lokale Variable **BranchWorkList** (Rechtsklick auf BranchFrontier → Get → in neue lokale Variable ziehen, oder: **Set BranchWorkList** = BranchFrontier, dann **Clear** BranchFrontier) + +**Konkret:** +``` +Set BranchWorkList = BranchFrontier (kopiert das Array) +Clear BranchFrontier (leert das Original – neue Sockets aus Branch-Räumen kommen hier rein) +``` + +4. → **ForEachLoop** über **BranchWorkList** (NICHT über BranchFrontier!) + +**Loop Body ▶ (für jeden Branch-Startpunkt):** + +5. → **Random Integer in Range** (MaxBranchDepthMin, MaxBranchDepthMax) → **Set** MaxBranchDepth (lokale Variable) +6. → **Set** CurrentBranchDepth = 0 (lokale Variable) +7. → Array Element → **Set** CurrentBranchSocket (lokale Variable, Typ: S_OpenSocketData) + +**Innere While-Loop** (Condition: CurrentBranchDepth < MaxBranchDepth): + +8. → **Branch**: CurrentBranchDepth == MaxBranchDepth - 1? + +**True ▶ (Letzter Raum der Branch – DeadEnd platzieren):** + +9. → **SnapRoomToSocket** (OpenSocket: CurrentBranchSocket, NewRoomClass: BP_DungeonRoom_DeadEnd, NewSocketIndex: 0, NewSocketId: "Socket_Entry") + - DeadEnd hat immer genau 1 Socket namens "Socket_Entry", daher Index 0 und Id fest +10. → **Branch** (Success) + +**False ▶ (DeadEnd Snap fehlgeschlagen):** +11. → **Print String** "DeadEnd snap failed" → **Break** innere While-Loop (Branch abbrechen) + +**True ▶ (DeadEnd erfolgreich):** +11. → **Add** SpawnedRoom zu PlacedRoomActors +12. → **Make S_PlacedRoomData** (PathType: Branch, RoomType: DeadEnd, LevelIndex: CurrentBranchSocket.LevelIndex, BranchDepth: CurrentBranchDepth + 1) → **Add** zu PlacedRooms +13. → **UpdateSocketState** (CurrentBranchSocket.OwnerRoom, CurrentBranchSocket.SocketId, **Occupied**) +14. → DeadEnd hat nur 1 Socket (Socket_Entry) → Eingangs-Socket registrieren: + - **Make S_OpenSocketData** (OwnerRoom: SpawnedRoom, SocketId: "Socket_Entry", SocketIndex: 0, PathType: Branch, LevelIndex: CurrentBranchSocket.LevelIndex, BranchDepth: CurrentBranchDepth + 1, State: **Occupied**) → **Add** zu AllTrackedSockets +15. → **Break** innere While-Loop (Branch fertig) + +**False ▶ (Nicht letzter Raum – normaler Branch-Raum):** + +9. → **FindCompatibleRoom** (OpenSocket: CurrentBranchSocket, ExcludeTypes: [Entrance, Exit, PortalIn, PortalOut, DeadEnd]) + - Liefert: FoundRoomClass, MatchingSocketIndex, **MatchingSocketId**, FoundRoomType, Success +10. → **Branch** (Success) + - False ▶: **Print String** "No room for branch" → **Break** innere While-Loop + +11. → True ▶: **SnapRoomToSocket** (OpenSocket: CurrentBranchSocket, NewRoomClass: FoundRoomClass, NewSocketIndex: MatchingSocketIndex, NewSocketId: **MatchingSocketId**) → **Branch** (Success) + - False ▶: **Print String** "Branch snap failed" → **Break** innere While-Loop + +12. → True ▶: +13. → **Add** SpawnedRoom zu PlacedRoomActors +14. → **Make S_PlacedRoomData** (PathType: Branch, RoomType: aus FindCompatibleRoom, LevelIndex: CurrentBranchSocket.LevelIndex, BranchDepth: CurrentBranchDepth + 1) → **Add** zu PlacedRooms + +15. → **UpdateSocketState** (CurrentBranchSocket.OwnerRoom, CurrentBranchSocket.SocketId, **Occupied**) + +16. → Sockets des neuen Branch-Raums registrieren und verteilen: + - SpawnedRoom → **GetSocketDefinitions (Message)** → SocketDefinitions + - Lokaler Bool **BranchContinued** = false + - **ForEachLoop** über SocketDefinitions: + - **Branch**: SocketId == MatchingSocketId? (Eingangs-Socket, MatchingSocketId kommt aus FindCompatibleRoom in Schritt 9) + - True ▶: **Make S_OpenSocketData** (State: **Occupied**) → Add zu AllTrackedSockets + - False ▶: **Branch**: BranchContinued == false? + - True ▶ (erster freier Socket → Branch weiterführen): + - **Make S_OpenSocketData** (PathType: Branch, State: Open, BranchDepth: CurrentBranchDepth + 1) → Add zu AllTrackedSockets + - → **Set CurrentBranchSocket** = dieser neue S_OpenSocketData + - → **Set BranchContinued** = true + - False ▶ (weitere Sockets → zur späteren Verarbeitung): + - **Make S_OpenSocketData** (PathType: Branch, State: Open, BranchDepth: CurrentBranchDepth + 1) → Add zu AllTrackedSockets + - → **Add** zu **BranchFrontier** (wird in einer späteren Iteration verarbeitet, falls gewünscht) + +17. → **CurrentBranchDepth + 1** → Set CurrentBranchDepth +18. → (Innere While-Loop nächste Iteration) + +**ForEachLoop Completed ▶:** + +19. → **Branch**: BranchFrontier **Length > 0**? (Wurden während des Branch-Baus neue Sockets gesammelt?) + - True ▶: Optional: Nochmal eine Runde Branch-Bau starten (Rekursion/Loop), ODER: ignorieren und mit SealOpenSockets verschließen + - False ▶: → **SealOpenSockets** + +**Empfehlung für den Prototyp:** Neue BranchFrontier-Einträge aus dem Branch-Bau NICHT weiter verfolgen, sondern direkt zu SealOpenSockets gehen. Das verhindert unkontrolliertes Wachstum. Also nach ForEachLoop Completed immer → **SealOpenSockets**. + +### 4.9 SealOpenSockets + +Alle übrig gebliebenen offenen Sockets mit WallBlockern verschließen. + +Die Funktion nutzt **AllTrackedSockets** als einzige Wahrheitsquelle. +- **Occupied** = Socket wurde als Verbindung zwischen zwei Räumen benutzt +- **Open** = Socket wurde nie verbunden (braucht WallBlocker) +- **Closed** = Socket wurde bereits mit einem WallBlocker verschlossen + +1. **SealOpenSockets ▶** + +**Schritt 2 – Offene Sockets sammeln:** +2. → Lokales Array **OpenSocketsToSeal** (Typ: Array of S_OpenSocketData) erstellen +3. → **AllTrackedSockets** → **ForEachLoop** +4. → Array Element → Break S_OpenSocketData → State +5. → **Branch**: State == **Open**? +6. → True ▶: **Add** Array Element zu OpenSocketsToSeal +7. → ForEachLoop Completed ▶: weiter mit Schritt 8 + +``` +AllTrackedSockets → ForEachLoop + Loop Body ▶ → Break S_OpenSocketData + → Branch: State == Open? + True ▶ → Add zu OpenSocketsToSeal + Completed ▶ → (weiter) +``` + +**Schritt 8 – WallBlocker spawnen:** +8. → **ForEachLoop** über OpenSocketsToSeal +9. → Array Element → Break S_OpenSocketData → OwnerRoom, SocketId +10. → OwnerRoom → **GetSocketWorldTransformById (Message)** (SocketId) → Location, Rotation, Success +11. → **Branch** (Success) + - False ▶: **Print String** "WARNING: Socket not found for sealing" → weiter +12. → True ▶: **SpawnActor** BP_WallBlocker + - Location: Location vom Socket + - Rotation: Rotation vom Socket +13. → **Add** WallBlocker zu SpawnedBlockers +14. → **UpdateSocketState** (OwnerRoom, SocketId, **Closed**) + +**ForEachLoop Completed ▶** → **PlacePlayer** + +### 4.10 PlacePlayer + +1. **PlacePlayer ▶** +2. → **Get Player Character** (Player Index: 0) +3. → **Branch**: IsValid (Player Character)? + - False ▶: **Print String** "No player character found!" +4. → True ▶: StartRoom → **GetActorLocation** → **Set** StartLocation +5. → **SetActorLocation** (Target: Player Character, New Location: StartLocation) +6. → **Print String** "Dungeon Generation Complete! Levels: " + TotalLevels + " Rooms: " + PlacedRooms Length + +### 4.11 RetryLevel (NEU) + +Wird aufgerufen wenn BuildMainPath oder PlaceEndRoom fehlschlägt. + +1. **RetryLevel ▶** +2. → **RetryCount + 1** → **Set RetryCount** +3. → **Branch**: RetryCount > MaxRetries? + +**True ▶ (Zu viele Retries):** +4. → **Print String** "FATAL: Level generation failed after " + MaxRetries + " retries!" +5. → (Hier enden – optional: gesamten Dungeon neu starten mit GenerateDungeon) + +**False ▶ (Retry möglich):** +4. → **Print String** "Retrying level " + CurrentLevel + " (Attempt " + RetryCount + ")" + +**Schritt 5 – Räume des aktuellen Levels aufräumen:** + +Iteriere direkt über **PlacedRooms** (nicht PlacedRoomActors), weil PlacedRooms den LevelIndex enthält: + +5. → PlacedRooms → **Rückwärts-ForLoop** (Length - 1 → 0, step -1) +6. → PlacedRooms **Get (Index)** → Break S_PlacedRoomData → LevelIndex, RoomActor +7. → **Branch**: LevelIndex == CurrentLevel? +8. → True ▶: RoomActor → **Destroy Actor** → **Remove Index** (PlacedRooms, Index) +9. → ForLoop Completed ▶: + +**Schritt 10 – Weitere Arrays bereinigen:** +10. → PlacedRoomActors: Alle ungültigen Actors entfernen (Rückwärts-ForLoop → IsValid? → False → Remove Index) +11. → AllTrackedSockets: Alle Einträge mit LevelIndex == CurrentLevel entfernen (Rückwärts-ForLoop) +12. → MainPathFrontier → **Clear** +13. → BranchFrontier: Alle Einträge mit LevelIndex == CurrentLevel entfernen (Rückwärts-ForLoop) + +**Schritt 14 – WallBlocker aufräumen (falls bereits Branches gebaut wurden):** +14. → SpawnedBlockers → **ForEachLoop** → **Destroy Actor** → **Clear** SpawnedBlockers + +**Schritt 15 – Level neu starten:** +15. → **BuildLevel** + +**Hinweis zur Array-Bereinigung:** "Alle Einträge mit LevelIndex == X entfernen" ist in Blueprints am einfachsten mit einer **Rückwärts-ForLoop**: +``` +PlacedRooms → Length - 1 → ForLoop (LastIndex to 0, step -1) + Loop Body ▶ → PlacedRooms Get (Index) → Break → LevelIndex + → Branch: LevelIndex == CurrentLevel? + True ▶ → Remove Index (PlacedRooms, Index) +``` +Rückwärts iterieren verhindert Index-Verschiebung beim Entfernen! + +--- + +## Teil 5: Funktionen + +### 5.1 FindCompatibleRoom + +**Umsetzung:** Erstelle eine neue Funktion im BP_DungeonGenerator. + +**Inputs:** +- OpenSocket (S_OpenSocketData) – Der offene Socket an den angedockt werden soll +- ExcludeTypes (Array of E_RoomType) – Raumtypen die ausgeschlossen werden + +**Outputs:** +- FoundRoomClass (Actor Class Ref) – Die Blueprint-Klasse des gewählten Raums +- MatchingSocketIndex (Integer) – Index des Sockets im neuen Raum der als Eingang dient +- MatchingSocketId (Name) – **NEU** – Socket-ID des Eingangs-Sockets (für spätere Zuordnung) +- FoundRoomType (E_RoomType) – **NEU** – Typ des gewählten Raums (für S_PlacedRoomData) +- Success (Bool) + +**Schritt 1 – Gültige Kandidaten aus der Data Table filtern:** + +1. → **Get Data Table Rows** (Table: RoomPoolTable) → AllRows (Array of S_RoomPoolEntry) +2. → Lokales Array **ValidCandidates** (Typ: Array of S_RoomPoolEntry) +3. → **ForEachLoop** über AllRows +4. → Array Element → **Break S_RoomPoolEntry** → RoomType, MinLevel, MaxLevel +5. → **Branch**: Drei Bedingungen müssen ALLE true sein (AND): + - RoomType → **Contains** in ExcludeTypes Array → **NOT** (= RoomType ist NICHT ausgeschlossen) + - MinLevel **<=** CurrentLevel + - MaxLevel **>=** CurrentLevel **OR** MaxLevel == 0 (0 = unbegrenzt) +6. → True ▶: **Add** Array Element zu ValidCandidates +7. → ForEachLoop Completed ▶: **Branch** (ValidCandidates **Length > 0**) +8. → False ▶: **Return** (Success = false) + +``` +Get Data Table Rows → ForEachLoop + → Branch: NOT Contains(ExcludeTypes, RoomType) + AND MinLevel <= CurrentLevel + AND (MaxLevel >= CurrentLevel OR MaxLevel == 0) + True ▶ → Add zu ValidCandidates + Completed ▶ → Branch: Length > 0? + False ▶ → Return (Success = false) + True ▶ → (weiter mit Schritt 9) +``` + +**Schritt 9 – Gewichtete Zufallswahl:** + +9. → **TotalWeight** = 0.0 (lokale Float-Variable) +10. → **ForEachLoop** über ValidCandidates → TotalWeight += Array Element → Weight +11. → **Random Float in Range** (0.0, TotalWeight) → **Set** RandomPick +12. → **Set** RunningWeight = 0.0 +13. → **ForEachLoop** über ValidCandidates +14. → RunningWeight += Array Element → Weight +15. → **Branch**: RunningWeight >= RandomPick? +16. → True ▶: Array Element ist der gewählte Kandidat → **Set** ChosenEntry = Array Element → **Break** + +``` +Gewichtete Zufallswahl: + TotalWeight = Sum aller Weights + RandomPick = Random Float (0, TotalWeight) + RunningWeight = 0 + ForEachLoop: + RunningWeight += Weight + Branch: RunningWeight >= RandomPick? + True ▶ → ChosenEntry = Element → Break +``` + +**Schritt 17 – Socket-Matching (den passenden Eingangs-Socket am Kandidaten finden):** + +Wir müssen herausfinden, welcher Socket des neuen Raums als "Eingang" benutzt wird – also an den offenen Socket des bestehenden Raums andockt. + +**Für den Prototyp** gilt folgende einfache Regel: **Nimm den ersten Socket des Kandidaten.** Für den Prototyp reicht das, weil alle Räume gleich große Durchgänge haben und Richtungs-Matching noch nicht implementiert ist. + +17. → ChosenEntry → Break S_RoomPoolEntry → RoomClass → **SpawnActor** temporär bei (0, 0, 10000) → **Set** TempCandidate + - Position weit oben, damit er nicht mit dem Dungeon kollidiert +18. → TempCandidate → **GetSocketDefinitions (Message)** → SocketDefinitions +19. → **Branch**: SocketDefinitions **Length > 0**? + - False ▶: TempCandidate → **Destroy Actor** → **Return** (Success = false) +20. → True ▶: SocketDefinitions **Get (Index 0)** → Break S_SocketDefinition +21. → **Set MatchingSocketIndex** = 0 +22. → **Set MatchingSocketId** = SocketId (aus dem Break) +23. → **Set FoundRoomType** = ChosenEntry → RoomType +24. → **Set FoundRoomClass** = ChosenEntry → RoomClass +25. → TempCandidate → **Destroy Actor** +26. → **Return** (FoundRoomClass, MatchingSocketIndex, MatchingSocketId, FoundRoomType, Success = true) + +**Spätere Erweiterung – Richtungs-Matching:** +Statt pauschal Socket 0 zu nehmen, könnte man die Richtung des offenen Sockets prüfen und den Socket des Kandidaten wählen, der in die entgegengesetzte Richtung zeigt (Nord → Süd, Ost → West). Das ist aber für den Prototyp nicht nötig. + +### 5.2 SnapRoomToSocket + +Diese Funktion dockt einen neuen Raum an einen offenen Socket eines bestehenden Raums an. + +**Umsetzung:** Erstelle eine neue Funktion im BP_DungeonGenerator. + +**Inputs:** +- OpenSocket (S_OpenSocketData) – Der offene Socket am bestehenden Raum +- NewRoomClass (Actor Class Ref) – Blueprint-Klasse des neuen Raums +- NewSocketIndex (Integer) – Index des Sockets am neuen Raum der als Eingang dient +- NewSocketId (Name) – **NEU** – Socket-ID des Eingangs-Sockets am neuen Raum (für GetSocketWorldTransformById) + +**Outputs:** +- SpawnedRoom (Actor Object Ref) +- Success (Bool) + +**Schritt 1: Socket-Weltposition holen (via Interface)** + +1. → OpenSocket → **Break S_OpenSocketData** → OwnerRoom, SocketId, SocketIndex +2. → OwnerRoom → **GetSocketWorldTransformById (Message)** (SocketId) → Location, Rotation, Success +3. → **Branch** (Success) + - False ▶: OwnerRoom → **GetSocketWorldTransformByIndex (Message)** (SocketIndex) → Location, Rotation (Fallback) +4. → Location → **Set SocketWorldPos** + +**Schritt 2: Ziel-Rotation berechnen (WARUM 180°?)** + +Wenn zwei Räume aneinander andocken, müssen ihre Sockets **einander zugewandt** sein. Ein Socket zeigt "nach außen" aus seinem Raum heraus. Der neue Raum muss also so rotiert werden, dass sein Eingangs-Socket in die **entgegengesetzte** Richtung des Dock-Sockets zeigt. Darum drehen wir um 180° auf der Z-Achse (Yaw). + +5. → Rotation → **Combine Rotators** (A: Rotation, B: (Pitch: 0, Yaw: 180, Roll: 0)) → **Set TargetRotation** + +**Schritt 3: Neuen Raum spawnen** + +6. → **SpawnActor** (Class: NewRoomClass, Location: (0, 0, 0), Rotation: (0, 0, 0)) → **Set TempRoom** + - Wir spawnen bei 0,0,0 und bewegen danach – so können wir den Socket-Offset korrekt berechnen + +**Schritt 4: Offset des neuen Sockets berechnen** + +Der neue Raum wurde bei (0,0,0) ohne Rotation gespawnt. Jetzt berechnen wir, wo sein Eingangs-Socket relativ zum Actor-Origin liegt: + +7. → TempRoom → **GetSocketWorldTransformById (Message)** (MatchingSocketId) → TempSocketLocation, TempSocketRotation, Success + - **Bevorzugt ById** statt ByIndex, weil GetSocketWorldTransformById auf die SocketDefinitions-Variable zugreift (im Construction Script befüllt), während ByIndex über Get Components by Class geht, was auf frisch gespawnten Actors fehlschlagen kann (siehe 7.6) + - Falls ById fehlschlägt (Success == false): Fallback auf **GetSocketWorldTransformByIndex (Message)** (NewSocketIndex) +8. → TempSocketLocation **- (Subtract Vector)** TempRoom → **GetActorLocation** → **Set TempSocketOffset** + - Das ist der Vektor vom Actor-Origin zum Socket, in Weltkoordinaten (bei Rotation 0) + +**Schritt 5: Offset rotieren** + +Da wir den Raum gleich um TargetRotation drehen werden, muss der Offset ebenfalls rotiert werden: + +9. → TempSocketOffset → **Rotate Vector** (Rotator: TargetRotation) → **Set RotatedOffset** + +**Schritt 6: Finale Position berechnen** + +Der Socket des neuen Raums soll genau auf SocketWorldPos liegen. Also: +Finale Position = SocketWorldPos - RotatedOffset + +10. → SocketWorldPos **- (Subtract Vector)** RotatedOffset → **Set FinalPosition** + +**Schritt 7: Raum positionieren** + +11. → TempRoom → **SetActorLocationAndRotation** (Location: FinalPosition, Rotation: TargetRotation, Teleport: **true**) + +**Schritt 8: Kollisionsprüfung** + +12. → TempRoom → **Get Overlapping Actors** (kein Class Filter) → OverlappingActors Array +13. → OverlappingActors → **Remove** (Item: TempRoom) ← **MUSS in der Exec-Kette liegen!** +14. → OverlappingActors → **Length** → **> 0** → **Branch** + +**True ▶ (Kollision mit anderem Raum):** +15. → **Print String** "Collision detected, destroying room" (Debug) +16. → TempRoom → **Destroy Actor** +17. → **Return** (SpawnedRoom: None, Success: **false**) + +**False ▶ (Keine Kollision – Erfolg):** +15. → **Return** (SpawnedRoom: TempRoom, Success: **true**) + +**Exec-Kette (KRITISCH – alle müssen verbunden sein):** +``` +SpawnActor ▶ → GetSocketWorldTransformById ▶ → SetActorLocationAndRotation ▶ +→ Get Overlapping Actors ▶ → Remove ▶ → Branch ▶ → (Destroy oder Return) +``` + +--- + +## Teil 6: Raum-Blueprints + +### 6.1 Vorhandene Räume anpassen + +Für jeden Raum-Blueprint (Entrance, General, Corridor, PortalIn, PortalOut, Exit): + +1. Arrow Components umbenennen (Socket_North, Socket_South, Socket_East, Socket_West, oder Socket_Entry/Socket_Exit für Corridors) +2. Variable `SocketDefinitions` hinzufügen (Typ: Array of S_SocketDefinition) +3. Variable `RoomType` hinzufügen (Typ: E_RoomType, Default: je nach Raum) +4. Construction Script umbauen (siehe Teil 2.3) +5. Interface-Funktionen implementieren: + - **GetSocketDefinitions**: SocketDefinitions Variable → Return Node + - **GetSocketWorldTransformById**: Implementierung siehe Teil 2.4 + - **GetSocketWorldTransformByIndex**: Get Components by Class (Arrow) → Get (Index) → GetWorldLocation + GetWorldRotation → Return + - **GetRoomType**: RoomType Variable → Return Node +6. **Entfernen** (falls vorhanden): + - ~~SetIsOnMainPath~~, ~~GetIsOnMainPath~~ + - ~~SetBranchDepth~~, ~~GetBranchDepth~~ + - ~~GetSockets~~ (ersetzt durch GetSocketDefinitions) + - Variablen ~~IsOnMainPath~~, ~~BranchDepth~~, ~~ConnectedRooms~~ +7. BoundingBox: Etwas kleiner als Boden (10-20 Units eingerückt), flach (nur Bodenhöhe), Collision Preset: **OverlapAllDynamic** + +### 6.2 BP_DungeonRoom_DeadEnd erstellen + +- Neuer Actor Blueprint erstellen +- **Components:** + - DefaultSceneRoot + - RoomMesh (Static Mesh Component – ein Raum mit nur einem Eingang) + - BoundingBox (Box Collision, kleiner als Boden, flach, OverlapAllDynamic) + - **Socket_Entry** (Arrow Component – zeigt zum Eingang hinaus) +- **Variablen:** RoomType = DeadEnd, SocketDefinitions (Array of S_SocketDefinition) +- **Construction Script:** Wie alle anderen Räume (Teil 2.3) +- **Interface implementieren:** Wie alle anderen Räume (Teil 6.1 Schritt 5) +- **In DT_RoomPool eintragen:** RoomClass: BP_DungeonRoom_DeadEnd, RoomType: DeadEnd, Weight: 1.0, MinLevel: 1, MaxLevel: 0 + +### 6.3 BP_WallBlocker erstellen (falls noch nicht vorhanden) + +- Neuer Actor Blueprint +- **Components:** + - DefaultSceneRoot + - WallMesh (Static Mesh Component – eine Wand die den Durchgang blockiert) +- Kein Interface nötig, keine Variablen nötig +- Wird nur von SealOpenSockets gespawnt + +### 6.4 BoundingBox-Regeln + +- Etwas kleiner als der Raumboden (10–20 Units eingerückt auf jeder Seite) +- Nur Bodenhöhe (flach) – Wände sollen NICHT kollidieren, sonst scheitert jeder benachbarte Raum +- Collision Preset: **OverlapAllDynamic** +- Collision Enabled: **Query Only** (keine Physik-Simulation) +- Generate Overlap Events: **Ja** + +--- + +## Teil 7: Wichtige Details und Fallstricke + +### 7.1 Append-Reihenfolge +- **Target (oberer Pin):** Das Array das befüllt wird +- **Source (unterer Pin):** Die neuen Daten +- Vertauschung überschreibt statt anzuhängen! + +### 7.2 Socket-Weltposition live berechnen +**NIEMALS** gespeicherte Positionen verwenden. **IMMER** GetSocketWorldTransformById/ByIndex auf dem OwnerRoom aufrufen. Grund: Wenn ein Actor bewegt wird (z.B. bei SetActorLocationAndRotation), werden gespeicherte Positionen ungültig. + +### 7.3 Destroy Actor Target +Beim Retry: Target = **Array Element** aus PlacedRoomActors, **NICHT** self! "self" würde den Generator selbst zerstören. + +### 7.4 WallBlocker beim Retry aufräumen +SpawnedBlockers Array durchgehen → Destroy Actor → Array Clear. + +### 7.5 PortalIn Positionierung +PortalIn Position = LastPortalOut.GetActorLocation() minus Vector(0, 0, **LevelVerticalOffset**). +Dadurch liegt jedes Level um LevelVerticalOffset tiefer als das vorherige. + +### 7.6 Get Components by Class auf frischen Actors +Kann Length 0 zurückgeben direkt nach SpawnActor, besonders wenn das Construction Script noch nicht gelaufen ist. Deshalb immer Interface-Funktionen verwenden (GetSocketWorldTransformById), die auf die SocketDefinitions-Variable zugreifen. + +### 7.7 Remove in Exec-Kette +Der Remove-Node bei Get Overlapping Actors muss in der Exec-Kette liegen: +``` +SetActorLocationAndRotation ▶ → Get Overlapping Actors ▶ → Remove ▶ → Branch ▶ +``` +Ohne Exec-Verbindung wird Remove möglicherweise nicht ausgeführt bevor die Branch-Bedingung geprüft wird. + +### 7.8 Return Node mit SpawnedRoom verbinden +Im SnapRoomToSocket: Der Success-Return-Node muss **TempRoom** auf dem **SpawnedRoom** Output-Pin haben! Sonst gibt die Funktion None zurück obwohl der Raum erfolgreich gespawnt wurde. + +### 7.9 ForEachLoop mit Break vs. ohne Break +- **ForEachLoop** (Standard): Iteriert über ALLE Elemente, kann nicht vorzeitig beendet werden +- **ForEachLoop with Break**: Hat einen zusätzlichen "Break" Exec-Pin um die Schleife vorzeitig zu beenden +- Verwende **with Break** in: UpdateSocketState, FindCompatibleRoom (gewichtete Wahl), GetSocketWorldTransformById +- Verwende Standard in: RegisterRoomSockets, SealOpenSockets, Socket-Verteilung + +### 7.10 Struct-Vergleiche in Blueprints +UE Blueprints können Structs nicht direkt mit == vergleichen. Um einen bestimmten Socket in AllTrackedSockets zu finden, vergleiche die **einzelnen Felder** (OwnerRoom == X AND SocketId == Y), nicht den gesamten Struct. + +### 7.11 Array-Mutation während Iteration +**NIEMALS** ein Array verändern (Add, Remove, Clear) während du mit ForEachLoop darüber iterierst. Das führt zu undefiniertem Verhalten. Lösung: Vor der Iteration eine **Kopie** des Arrays erstellen und über die Kopie iterieren. + +--- + +## Teil 8: Test-Reihenfolge + +### Phase 1: Grundgerüst +1. GenerateDungeon aufrufen → Print: Seed, TotalLevels, MaxRoomsOfLevel +2. BuildLevel → Entrance spawnt bei (0,0,0) → Print "Entrance spawned" +3. Print SocketDefinitions Length → muss > 0 sein +4. Print MainPathFrontier Length → muss genau 1 sein +5. Print AllTrackedSockets Length → muss == Anzahl Arrow Components am Entrance sein + +### Phase 2: Positionierung +6. BuildMainPath → Erster Raum wird gespawnt → Print FinalPosition +7. Positionen müssen verschieden sein (nicht alle bei 0,0,0) +8. F8 im Play-Mode → Kamera freigeben → Räume visuell prüfen: Docken sie korrekt aneinander? +9. Print CurrentRoomsOnLevel nach jedem Raum → muss hochzählen + +### Phase 3: Level-Übergang +10. PlaceEndRoom → PortalOut spawnt → Print "PortalOut placed" +11. CurrentLevel wird auf 2 erhöht +12. PortalIn spawnt unter PortalOut → Print PortalIn Location (Z muss um LevelVerticalOffset niedriger sein) +13. Zweites Level baut sich → Print Raumanzahl + +### Phase 4: Exit und Branches +14. Letztes Level → Exit spawnt → Print "Exit placed" +15. BuildBranches wird aufgerufen → Print BranchFrontier Length vor dem Bau +16. Branches bauen sich → Print je Branch: Tiefe, Raumtyp +17. DeadEnd am Ende jeder Branch → Print "DeadEnd placed" + +### Phase 5: Abschluss +18. SealOpenSockets → Print Anzahl versiegelter Sockets +19. WallBlocker an offenen Sockets sichtbar +20. PlacePlayer → Spieler steht im Entrance-Raum → Print "Complete!" + +### Phase 6: Retry-Test +21. Künstlich einen Fehler provozieren (z.B. MaxRoomsPerLevel = 1) → RetryLevel muss feuern +22. Print Retry-Zähler → muss hochzählen +23. Nach MaxRetries → Abbruch-Meldung + +### Debug-Empfehlungen +- **Print String** mit RandomSeed ganz am Anfang (für Reproduzierbarkeit) +- **Print String** mit FinalPosition in SnapRoomToSocket (sind die Positionen plausibel?) +- **Print String** mit OwnerRoom → IsValid vor GetSocketWorldTransform (Raum noch vorhanden?) +- **Print String** mit Arrow Count nach Get Components by Class (Sockets vorhanden?) +- **DrawDebugBox** für jeden platzierten Raum an GetActorLocation (optional, zeigt Raumgrenzen) +- **Print String** mit AllTrackedSockets Length nach jedem RegisterRoomSockets (wächst korrekt?) + +--- + +## Anhang: Ablauf-Zusammenfassung + +``` +BeginPlay + └→ GenerateDungeon + ├─ Seed, Levels, Rooms berechnen + └→ BuildLevel (Level 1) + ├─ Spawn Entrance + ├─ RegisterRoomSockets → AllTrackedSockets + ├─ Socket-Verteilung: 1→MainPathFrontier, Rest→BranchFrontier + └→ BuildMainPath + ├─ While (Frontier>0 AND Rooms.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/* diff --git a/Plugins/VisualStudioTools/CODE_OF_CONDUCT.md b/Plugins/VisualStudioTools/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f9ba8cf --- /dev/null +++ b/Plugins/VisualStudioTools/CODE_OF_CONDUCT.md @@ -0,0 +1,9 @@ +# Microsoft Open Source Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). + +Resources: + +- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) +- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) +- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns diff --git a/Plugins/VisualStudioTools/CONTRIBUTING.md b/Plugins/VisualStudioTools/CONTRIBUTING.md new file mode 100644 index 0000000..51f9942 --- /dev/null +++ b/Plugins/VisualStudioTools/CONTRIBUTING.md @@ -0,0 +1,49 @@ +# Contributing + +This project welcomes contributions and suggestions. Most contributions require you to agree to a +Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us +the rights to use your contribution. For details, visit . + +When you submit a pull request, a CLA bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +## Code Style Guide + +The code in the repo follows the existing code conventions described in the Unreal Engine's [Code Standard document](https://docs.unrealengine.com/INT/epic-cplusplus-coding-standard-for-unreal-engine/). The `.editorconfig` file at the source root is used for Visual Studio to check the conventions and report violations. + +## Pull Requests + +When submitting a pull request, make sure that it has a clean build using the instructions below. A core contributor will review your pull request and provide feedback. Once all the feedback is addressed and the PR is approved, we will merge the changes. + +## Build workflow +The plugin source can be built in isolation using the command below (which wrap the RunUAT.bat script) to ensure it's correct for submition to the Unreal Engine Marketplace. + +From a Visual Studio Developer Prompt (or PowerShell Dev Prompt), run the following: + +```cmd +> msbuild -p:UnrealEngine=[path_or_version] -p:OutputPath=[absolute_path] +`````` + +- `UnrealEngine` can be either a path to a source build (e.g. `c:\src\ue`) or a version identifier for an installed engine (e.g. `4.27`, `5.2`). +- `OutputPath` cannot be under the Unreal Engine's folder due to a restriction from `RunUAT.bat`. + +> Note: The contents of `OutputPath` will be overwritten! + +By default the script will disable Unity Builds in the plugin modules to catch errors from cpp files not including all the required headers. It does not affect the build of other targets and modules. + +## Unity build errors + +If you get errors due to unity build problems, you get the same errors in Visual Studio by generating the solution with the command below. This will allow Visual Studio to suggest the includes as code fixes. Note that this will overwrite any existing solution and projects that are already present. + +```powershell +$env:VSTUE_IsCustomDevBuild=1; & "C:\Program Files\Epic Games\UE_5.2\Engine\Build\BatchFiles\Build.bat" -projectfiles -project="full_path_to_game.uproject" -game +``` + +The module rules for the plugin check the enviroment variable above to use the more strict include settings. + + diff --git a/Plugins/VisualStudioTools/Config/FilterPlugin.ini b/Plugins/VisualStudioTools/Config/FilterPlugin.ini new file mode 100644 index 0000000..66565e7 --- /dev/null +++ b/Plugins/VisualStudioTools/Config/FilterPlugin.ini @@ -0,0 +1,9 @@ +[FilterPlugin] +; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and +; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. +; +; Examples: +; /README.txt +; /Extras/... +; /Binaries/ThirdParty/*.dll +/Docs/... diff --git a/Plugins/VisualStudioTools/Docs/Marketplace_Readme.md b/Plugins/VisualStudioTools/Docs/Marketplace_Readme.md new file mode 100644 index 0000000..cdb575c --- /dev/null +++ b/Plugins/VisualStudioTools/Docs/Marketplace_Readme.md @@ -0,0 +1,57 @@ +# Visual Studio Integration Tool + +Visual Studio Integration Tool is an Unreal Engine plugin that works in conjunction with Visual Studio to display information about Blueprints assets in C++ code (requires Visual Studio 2022 17.4 or later). + +## Installing + +### Visual Studio + +The tool requires the `Visual Studio Tools for Unreal Engine` component from Visual Studio to be installed. You can find it under the "Game development with C++" workload in the Visual Studio Installer (figure 1). + +![figure 1](./images/ide_support_ue.png) \ +*Figure 1 - Installing the Visual Studio component* + +### Unreal Engine + You can install the plugin in a couple of ways: + + - Through the Epic Games Launcher: + - Select the "Install to Engine" option within the Launcher. From there, you can select an engine version for installation. + - If you're using the Marketplace website, you can add the plugin to your account and you will have an option to open the Launcher in order to install it as detailed above. + - If you already added the plugin to your account, go Library -> Vault in the and locate the plugin there. + + - Through source distribution: + - If you're unable to use the Marketplace-based distribution (e.g. you're building the Unreal Engine from source), then you can install the plugin manually by following the instructions found at + +## Enabling the plugin + +- Through the Unreal Editor + - Open your project and then use the Plugin Manager to enable "VisualStudioTools". + - See [official documentation](https://docs.unrealengine.com/INT/working-with-plugins-in-unreal-engine/) for more information on how to install and enable plugins. +- (Advanced) Alternatively, you can manually edit the '.uproject' descriptor for your project and add an entry for the "VisualStudioTools" plugin. + +## Usage + +Test discovery in Visual Studio 2022 + +1. Begin by installing and enabling the `Visual Studio Tools for Unreal Engine` plugin. +2. Open your solution in Visual Studio. +3. Click on the Test Explorer to show a pop-up that will display available tests. (figure 3). +4. You can find the logs from the plugin execution in the Tests Output Window. +5. To refresh your filters for test discovery, you can select the "Options > Unreal Engine > Test Adapter" option under the "Tests" menu. (figure 4) + +![figure 2](./images/configuration_page.png) \ +*Figure 2 - Unreal Engine project Configuration Page + +![figure 3](./images/test_explorer.png) \ +*Figure 3 - Menu to rescan the blueprint assets in the game project* + +![figure 4](./images/test_options.png) \ +*Figure 3 - Menu to change options for Tests Discovery + +## Troubleshooting + +If you encounter any issues when setting up Visual Studio in conjunction with the Unreal Editor plugin, please refer to the [Troubleshooting](https://github.com/microsoft/vc-ue-extensions/blob/main/Docs/Troubleshooting.md) guide in the repository. This guide provides solutions for common issues and is periodically updated to ensure that the latest solutions are available. + +## Reporting issues + +To report new issues, provide feedback, or request new features, please use the following options: [Report a Problem](https://aka.ms/feedback/cpp/unrealengine/report) and [Suggest a Feature](https://aka.ms/feedback/cpp/unrealengine/suggest). These options will allow you to submit your issue or feedback directly to our team and help us improve the plugin moving forward. \ No newline at end of file diff --git a/Plugins/VisualStudioTools/Docs/Troubleshooting.md b/Plugins/VisualStudioTools/Docs/Troubleshooting.md new file mode 100644 index 0000000..3f06739 --- /dev/null +++ b/Plugins/VisualStudioTools/Docs/Troubleshooting.md @@ -0,0 +1,55 @@ +# Troubleshooting guide + +This document describes some of the errors that might happen in the integration with the Unreal Engine and potential ways to mitigate them. + +The integration works by Visual Studio being able to invoke the `VisualStudioTools` plugin using the Unreal Editor executable in commandlet mode. That means the following must be true: + +- The `Visual Studio Tools for Unreal Engine` component from Visual Studio must be installed. You can find it under the "Game Development with C++" workload in the VS Installer. +- The game project must be built in a Editor target (e.g., `"Development_Editor"`). +- The `VisualStudioTools` plugin must be enabled for the project, either explicitly in the .uproject descriptor file or be enabled by default to all projects if installed at the as an engine plugin (via the `"EnabledByDefault=true"` entry in the .uplugin file). +- Starting on version 17.5, Visual Studio will wait to scan the game project until a file with the `UCLASS/UPROPERTY/UFUNCTION` macros is opened and the Code Lens hints are requested. +- At the moment, the Code Lens hints will only be displayed for game projects. In particular, the files in the _engine_ project of the solution (with name like "UE4" or "UE5") will not display the hints yet. + +## Code Lens are not visible + +### Verify the required VS component is installed + +In recent versions of UE, the generated solution comes with a `.vsconfig` file, which allows right-clicking on the Solution in VS and selecting "Install Missing Feature(s)". The component is part of the "Game Development with C++" workload. + +You can also see this [help page](https://learn.microsoft.com/en-us/visualstudio/install/install-visual-studio?view=vs-2022#step-4---choose-workloads) about installing features using the Visual Studio installer. + +### Check if the opened documents have any class decorated with the Unreal macros + +For real world projects, scanning the blueprints information might take several seconds and be expensive in terms of machine resources. Visual Studio will only start the operation when the Code Lens are rendered. That means it will wait until a file from the game project with the Unreal macros is opened in the editor. + +### Check if a `cpp.hint` file is redefining the relevant Unreal macros + +Some projects might have a cpp.hint file that includes the `UCLASS`, `UPROPERTY`, `UFUNCTION` macros. That might suppress the new logic in Visual Studio that uses the macros to display the Code Lens hints. + +If that is the case, you can remove those macros from the hint file, save it and try reloading the project. + +Note that other macros in the hint file can be left as-is and do not affect the Code Lens hints. + +### Ensure the C++ Database is enabled + +In Tools > Options > Text Editor > C/C++ > Advanced > Browsing/Navigation, the setting "Disable Database" should be set to "False". This is the default value of this setting. + +## Errors showing up in the Output Window and/or Task Center notification + +### Message "LogInit: Error: VisualStudioToolsCommandlet looked like a commandlet, but we could not find the class." + +Possible causes are the plugin not being installed correctly or installed but not yet enabled for the game project (which is required on installation from the Marketplace). + +- See [this section](../README.md#building-and-installing) for installation instructions. + +- See [this section](../README.md#optional-enabling-the-plugin) for instructions on how to enable the plugin. + +### Message "Command finished with exit code 1" without other errors + +Either the game project or the plugin DLL is not yet built. Rebuilding the project should ensure they are available. Then manually rescan the game project using the `Project > Rescan UE Blueprints` menu. + +### Task Center error: "Your task failed with the message: Could not find a part of the path...' + +This was a known issue when trying to locate the path the Unreal Editor executable, fixed in Visual Studio 17.5-Preview3. This usually happens when the selected Configuration in VS is not one with an "Editor" target. + +A workaround is to switch to such configuration and manually rescan the game project using the `Project > Rescan UE Blueprints` menu. diff --git a/Plugins/VisualStudioTools/Docs/Visual Studio Integration Tool Documentation.pdf b/Plugins/VisualStudioTools/Docs/Visual Studio Integration Tool Documentation.pdf new file mode 100644 index 0000000..edcdfc4 Binary files /dev/null and b/Plugins/VisualStudioTools/Docs/Visual Studio Integration Tool Documentation.pdf differ diff --git a/Plugins/VisualStudioTools/Docs/images/configuration_page.png b/Plugins/VisualStudioTools/Docs/images/configuration_page.png new file mode 100644 index 0000000..d08556b Binary files /dev/null and b/Plugins/VisualStudioTools/Docs/images/configuration_page.png differ diff --git a/Plugins/VisualStudioTools/Docs/images/ide_support_ue.png b/Plugins/VisualStudioTools/Docs/images/ide_support_ue.png new file mode 100644 index 0000000..2216c0c Binary files /dev/null and b/Plugins/VisualStudioTools/Docs/images/ide_support_ue.png differ diff --git a/Plugins/VisualStudioTools/Docs/images/test_explorer.png b/Plugins/VisualStudioTools/Docs/images/test_explorer.png new file mode 100644 index 0000000..e72d9b5 Binary files /dev/null and b/Plugins/VisualStudioTools/Docs/images/test_explorer.png differ diff --git a/Plugins/VisualStudioTools/Docs/images/test_options.png b/Plugins/VisualStudioTools/Docs/images/test_options.png new file mode 100644 index 0000000..3a035e3 Binary files /dev/null and b/Plugins/VisualStudioTools/Docs/images/test_options.png differ diff --git a/Plugins/VisualStudioTools/LICENSE b/Plugins/VisualStudioTools/LICENSE new file mode 100644 index 0000000..41f977a --- /dev/null +++ b/Plugins/VisualStudioTools/LICENSE @@ -0,0 +1,22 @@ + Visual Studio Tools for Unreal Engine + Copyright (c) Microsoft Corporation. + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE diff --git a/Plugins/VisualStudioTools/README.md b/Plugins/VisualStudioTools/README.md new file mode 100644 index 0000000..4fa5176 --- /dev/null +++ b/Plugins/VisualStudioTools/README.md @@ -0,0 +1,116 @@ +# Unreal Engine plugin for Visual Studio + +This project contains an Unreal Editor plugin that works in conjunction with Visual Studio to help discover and run tests in C++ code. + +The plugin can be installed in either the Engine or Game project sources, and it is automatically activated when an Unreal Engine project is opened in Visual Studio. + +## Requirements + +Before you begin, please make sure you have the following software and tools set up: + +1. Visual Studio 2022 has the "Visual Studio Tools for Unreal Engine" component installed. + 1. The component can be found in the "Game development with C++" workload or as an individual component. +2. Unreal Engine, either installed or built from source. + 1. To learn how to install or build Unreal Engine, please refer to the following guide: [Installing Unreal Engine](https://docs.unrealengine.com/5.0/en-US/installing-unreal-engine). + 1. The source code and instructions have been tested on Unreal Engine versions 4.27 and 5.0+. + +## Building and Installing the Plugin + +> If you have Unreal Engine installed and set up through the Epic Games Launcher, and you only want to use the plugin, you can skip the steps below and install it directly from the [Unreal Engine Marketplace](https://aka.ms/vsueplugin). + +The most straightforward way to use the plugin is to clone the repo under the `Plugins` folder of your game project or engine source. If you have multiple projects in the same Visual Studio solution, it is recommended to install the plugin at the engine level and share the binaries across the projects. + +1. Clone the repo by using the following commands: + ```powershell + git clone https://github.com/microsoft/vc-ue-extensions.git + ``` + +2. Build the plugin from source: + ```powershell + msbuild -p:UnrealEngine= + ``` + Note#1: `` can be path to source code folder of the engine or the one installed by `Epic Games Launcher` (e.g `C:\Program Files\Epic Games\UE_5.4`) + Note#2: Alternatevly you can follow [Unreal Engine building plugins](https://dev.epicgames.com/community/learning/tutorials/qz93/unreal-engine-building-plugins) guide. + +3. Clone built plugin. + + 3.1. To Project folder: + ```powershell + move-item -path ./bin -destination \Plugins\VisualStudioTools + ``` + Note: You have to create `Plugins` folder in the root of the game project if it doens't exisist yet. + + 3.2. To Engine folder: + ```powershell + move-item -path ./bin -destination Note: To ensure proper activation of the plugin, make sure the correct plugin is selected or the desired changes are made in the `.uproject` file. + +## Manually invoking the plugin + +The plugin is designed to be used with Visual Studio, and as such, it does not provide any user interfaces, commands, or logs within the Unreal Editor. However, it is still possible to test the plugin's execution by running the **sample** command below: + +```powershell +& "\Engine\Binaries\Win64\UnrealEditor-Cmd.exe" "$Env:UserProfile\Unreal Projects\EmptyProject\EmptyProject.uproject" -run=VisualStudioTools -output "$Env:Temp\vs-ue-tools.json" [-unattended -noshadercompile -nosound -nullrhi -nocpuprofilertrace -nocrashreports -nosplash] +``` + +This command will run the plugin for the specified project and save Unreal Engine Blueprint information in the output file. Optional parameters are included to run the command faster. + +For more information on the specific command line parameters, you can run the following command in the powershell prompt with `-help`: + +```powershell +& "" "" -run=VisualStudioTools -help [-unattended -noshadercompile -nosound -nullrhi -nocpuprofilertrace -nocrashreports -nosplash] +``` + +>Note: The executable name is `UE4Editor-cmd.exe` for UE4.x, located under a similar path. + +## Troubleshooting + +If you encounter any issues when setting up Visual Studio in conjunction with the Unreal Editor plugin, please refer to the [Troubleshooting](https://github.com/microsoft/vc-ue-extensions/blob/main/Docs/Troubleshooting.md) guide in the repository. This guide provides solutions for common issues and is periodically updated to ensure that the latest solutions are available. + +To report new issues, provide feedback, or request new features, please use the following options: [Report a Problem](https://aka.ms/feedback/cpp/unrealengine/report) and [Suggest a Feature](https://aka.ms/feedback/cpp/unrealengine/suggest). These options will allow you to submit your issue or feedback directly to our team and help us improve the plugin moving forward. + +## Contributing +This project welcomes contributions and suggestions. Check out our [contributing guide](CONTRIBUTING.md) for instructions on how to contribute to the project. + +## Trademarks + +This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). +Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. +Any use of third-party trademarks or logos are subject to those third-party's policies. \ No newline at end of file diff --git a/Plugins/VisualStudioTools/SECURITY.md b/Plugins/VisualStudioTools/SECURITY.md new file mode 100644 index 0000000..e138ec5 --- /dev/null +++ b/Plugins/VisualStudioTools/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). + + diff --git a/Plugins/VisualStudioTools/SUPPORT.md b/Plugins/VisualStudioTools/SUPPORT.md new file mode 100644 index 0000000..b7cafca --- /dev/null +++ b/Plugins/VisualStudioTools/SUPPORT.md @@ -0,0 +1,13 @@ +# Support + +## How to file issues and get help + +This project uses the Visual Studio Developer Community to track bugs and feature requests. Please search the existing feedback before filing new ones to avoid duplicates. + +For common issues, please refer to our [Troubleshooting](https://github.com/microsoft/vc-ue-extensions/blob/main/Docs/troubleshooting.md) guide in the repository. We will periodically update the guide to provide solutions for common issues. + +To report issues, provide feedback, and request features, please use one of the following options: [Report a Problem](https://aka.ms/feedback/cpp/unrealengine/report) and [Suggest a Feature](https://aka.ms/feedback/cpp/unrealengine/suggest). + +## Microsoft Support Policy + +Support for this **PROJECT or PRODUCT** is limited to the resources listed above. diff --git a/Plugins/VisualStudioTools/Scripts/Package-Plugin.ps1 b/Plugins/VisualStudioTools/Scripts/Package-Plugin.ps1 new file mode 100644 index 0000000..04650c0 --- /dev/null +++ b/Plugins/VisualStudioTools/Scripts/Package-Plugin.ps1 @@ -0,0 +1,43 @@ +param( + [Parameter(Mandatory=$true)] + [string] + $EnginePath, + [Parameter(Mandatory=$true)] + [string] + $EngineVersion +) + +function New-TemporaryDirectory { + $parent = [System.IO.Path]::GetTempPath() + $name = [System.IO.Path]::GetRandomFileName() + New-Item -ItemType Directory -Path (Join-Path $parent $name) +} + +$PackagePath = New-TemporaryDirectory +& msbuild "-p:UnrealEngine=$EnginePath;OutputPath=$PackagePath;Versioned=true" + +# Add EnabledByDefault property in the descriptor file +Write-Host "Patch plugin descriptor file" +$descriptor = "$PackagePath/VisualStudioTools.uplugin" +$a = Get-Content $descriptor | ConvertFrom-Json +$a | Add-Member -NotePropertyName EnabledByDefault -NotePropertyValue $true -ErrorAction Ignore +$a | ConvertTo-Json -depth 100 | Out-File $descriptor -Encoding utf8 + +Write-Host "Copy Config folder" +Copy-Item -Path Config -Destination $PackagePath/Config -Recurse + +$PublishPath = "publish" +If(!(test-path -PathType Container $PublishPath)) +{ + New-Item -ItemType Directory -Path $PublishPath | Out-Null +} + +Write-Host "Create ZIP package" +$tag = $EngineVersion.Replace(".", "") +$files = Get-ChildItem $PackagePath -Exclude @("Binaries", "Intermediate") +$zip = "$PublishPath/VisualStudioTools_v$($a.VersionName)_ue$tag.zip" +Compress-Archive -Path $files -DestinationPath "$PublishPath/VisualStudioTools_v$($a.VersionName)_ue$tag.zip" -CompressionLevel Fastest + +Remove-Item $PackagePath -Force -Recurse + +Write-Host "Done: $($zip | Resolve-Path)" \ No newline at end of file diff --git a/Plugins/VisualStudioTools/Scripts/SignDetached.proj b/Plugins/VisualStudioTools/Scripts/SignDetached.proj new file mode 100644 index 0000000..0357524 --- /dev/null +++ b/Plugins/VisualStudioTools/Scripts/SignDetached.proj @@ -0,0 +1,19 @@ + + + + + + $(MSBuildThisFileDirectory)../../out/ + + $(BaseOutputDirectory) + $(BaseOutputDirectory) + + + + + Microsoft400 + + + + + diff --git a/Plugins/VisualStudioTools/Scripts/packages.config b/Plugins/VisualStudioTools/Scripts/packages.config new file mode 100644 index 0000000..3fa1e7e --- /dev/null +++ b/Plugins/VisualStudioTools/Scripts/packages.config @@ -0,0 +1,4 @@ + + + + diff --git a/Plugins/VisualStudioTools/Source/.editorconfig b/Plugins/VisualStudioTools/Source/.editorconfig new file mode 100644 index 0000000..613c0eb --- /dev/null +++ b/Plugins/VisualStudioTools/Source/.editorconfig @@ -0,0 +1,91 @@ +[*.{cpp,h}] + +# Naming convention rules (note: currently need to be ordered from more to less specific) + +cpp_naming_rule.aactor_prefixed.symbols = aactor_class +cpp_naming_rule.aactor_prefixed.style = aactor_style + +cpp_naming_rule.swidget_prefixed.symbols = swidget_class +cpp_naming_rule.swidget_prefixed.style = swidget_style + +cpp_naming_rule.uobject_prefixed.symbols = uobject_class +cpp_naming_rule.uobject_prefixed.style = uobject_style + +cpp_naming_rule.booleans_prefixed.symbols = boolean_vars +cpp_naming_rule.booleans_prefixed.style = boolean_style + +cpp_naming_rule.structs_prefixed.symbols = structs +cpp_naming_rule.structs_prefixed.style = unreal_engine_structs + +cpp_naming_rule.enums_prefixed.symbols = enums +cpp_naming_rule.enums_prefixed.style = unreal_engine_enums + +cpp_naming_rule.templates_prefixed.symbols = templates +cpp_naming_rule.templates_prefixed.style = unreal_engine_templates + +cpp_naming_rule.general_names.symbols = all_symbols +cpp_naming_rule.general_names.style = unreal_engine_default + +# Naming convention symbols + +cpp_naming_symbols.aactor_class.applicable_kinds = class +cpp_naming_symbols.aactor_class.applicable_type = AActor + +cpp_naming_symbols.swidget_class.applicable_kinds = class +cpp_naming_symbols.swidget_class.applicable_type = SWidget + +cpp_naming_symbols.uobject_class.applicable_kinds = class +cpp_naming_symbols.uobject_class.applicable_type = UObject + +cpp_naming_symbols.boolean_vars.applicable_kinds = local,parameter,field +cpp_naming_symbols.boolean_vars.applicable_type = bool + +cpp_naming_symbols.enums.applicable_kinds = enum + +cpp_naming_symbols.templates.applicable_kinds = template_class + +cpp_naming_symbols.structs.applicable_kinds = struct + +cpp_naming_symbols.all_symbols.applicable_kinds = * + +# Naming convention styles + +cpp_naming_style.unreal_engine_default.capitalization = pascal_case +cpp_naming_style.unreal_engine_default.required_prefix = +cpp_naming_style.unreal_engine_default.required_suffix = +cpp_naming_style.unreal_engine_default.word_separator = + +cpp_naming_style.unreal_engine_enums.capitalization = pascal_case +cpp_naming_style.unreal_engine_enums.required_prefix = E +cpp_naming_style.unreal_engine_enums.required_suffix = +cpp_naming_style.unreal_engine_enums.word_separator = + +cpp_naming_style.unreal_engine_templates.capitalization = pascal_case +cpp_naming_style.unreal_engine_templates.required_prefix = T +cpp_naming_style.unreal_engine_templates.required_suffix = +cpp_naming_style.unreal_engine_templates.word_separator = + +cpp_naming_style.unreal_engine_structs.capitalization = pascal_case +cpp_naming_style.unreal_engine_structs.required_prefix = F +cpp_naming_style.unreal_engine_structs.required_suffix = +cpp_naming_style.unreal_engine_structs.word_separator = + +cpp_naming_style.uobject_style.capitalization = pascal_case +cpp_naming_style.uobject_style.required_prefix = U +cpp_naming_style.uobject_style.required_suffix = +cpp_naming_style.uobject_style.word_separator = + +cpp_naming_style.aactor_style.capitalization = pascal_case +cpp_naming_style.aactor_style.required_prefix = A +cpp_naming_style.aactor_style.required_suffix = +cpp_naming_style.aactor_style.word_separator = + +cpp_naming_style.swidget_style.capitalization = pascal_case +cpp_naming_style.swidget_style.required_prefix = S +cpp_naming_style.swidget_style.required_suffix = +cpp_naming_style.swidget_style.word_separator = + +cpp_naming_style.boolean_style.capitalization = pascal_case +cpp_naming_style.boolean_style.required_prefix = b +cpp_naming_style.boolean_style.required_suffix = +cpp_naming_style.boolean_style.word_separator = diff --git a/Plugins/VisualStudioTools/Source/VisualStudioBlueprintDebuggerHelper/Private/VisualStudioBlueprintDebuggerHelperModule.cpp b/Plugins/VisualStudioTools/Source/VisualStudioBlueprintDebuggerHelper/Private/VisualStudioBlueprintDebuggerHelperModule.cpp new file mode 100644 index 0000000..5afcac8 --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioBlueprintDebuggerHelper/Private/VisualStudioBlueprintDebuggerHelperModule.cpp @@ -0,0 +1,246 @@ +// Copyright 2022 (c) Microsoft. All rights reserved. + +#include "VisualStudioBlueprintDebuggerHelperModule.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 4 +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +IMPLEMENT_MODULE(FVisualStudioBlueprintDebuggerHelper, VisualStudioBlueprintDebuggerHelper); + +DEFINE_LOG_CATEGORY(LogVisualStudioBlueprintDebuggerHelper); + +#if ENGINE_MAJOR_VERSION >= 5 +#define FCustomBlueprintPropertyInfo TSharedPtr +#else +#define FCustomBlueprintPropertyInfo FDebugInfo +#endif + +struct FVSNodePinRuntimeInformation +{ + UEdGraphPin* Pin; + FCustomBlueprintPropertyInfo Property; + + FVSNodePinRuntimeInformation(UEdGraphPin* InPin, FCustomBlueprintPropertyInfo InProperty) + : Pin(InPin) + , Property(InProperty) + { + } +}; + +struct FVSNodeData +{ + FText NodeName; + TArray> Properties; + int32 ScriptEntryTag; + const UEdGraphNode* Node; +}; + +struct FVSNodesRuntimeInformation +{ + TArray> Nodes; +}; + +struct FVSBlueprintRuntimeInformation +{ + TArray>> RunningBlueprints; +}; + +struct StackTraceHelper +{ + int32 ScriptEntryTag; + FString NodeName; +}; + +// Keep exported so we can read it. +VISUALSTUDIOBLUEPRINTDEBUGGERHELPER_API FVSBlueprintRuntimeInformation BlueprintsRuntimeInformation; + +VISUALSTUDIOBLUEPRINTDEBUGGERHELPER_API std::map StackFrameInformation; + +VISUALSTUDIOBLUEPRINTDEBUGGERHELPER_API const char* DebuggerHelperVersion = "1.0.0"; + +void FVisualStudioBlueprintDebuggerHelper::StartupModule() +{ + CurrentScriptEntryTag = 0; + + FBlueprintContextTracker::OnEnterScriptContext.AddRaw( + this, + &FVisualStudioBlueprintDebuggerHelper::OnEnterScriptContext); + + FBlueprintContextTracker::OnExitScriptContext.AddRaw( + this, + &FVisualStudioBlueprintDebuggerHelper::OnExitScriptContext); + + FBlueprintCoreDelegates::OnScriptException.AddRaw( + this, + &FVisualStudioBlueprintDebuggerHelper::OnScriptException); +} + +void FVisualStudioBlueprintDebuggerHelper::ShutdownModule() +{ + FBlueprintCoreDelegates::OnScriptException.RemoveAll(this); + FBlueprintContextTracker::OnExitScriptContext.RemoveAll(this); + FBlueprintContextTracker::OnEnterScriptContext.RemoveAll(this); +} + +void FVisualStudioBlueprintDebuggerHelper::OnEnterScriptContext( + const struct FBlueprintContextTracker& Context, + const UObject* SourceObject, + const UFunction* Function) +{ + if (!IsInGameThread()) + { + return; + } + + CurrentScriptEntryTag = Context.GetScriptEntryTag(); +} + +void FVisualStudioBlueprintDebuggerHelper::OnExitScriptContext(const struct FBlueprintContextTracker& Context) +{ + if (!IsInGameThread()) + { + return; + } + + for (auto ItRunningBlueprints = BlueprintsRuntimeInformation.RunningBlueprints.CreateIterator(); ItRunningBlueprints; ++ItRunningBlueprints) + { + auto& RunningBlueprint = ItRunningBlueprints->Value; + for (auto ItNodeData = RunningBlueprint->Nodes.CreateIterator(); ItNodeData; ++ItNodeData) + { + if ((*ItNodeData)->ScriptEntryTag == Context.GetScriptEntryTag()) + { + ItNodeData.RemoveCurrent(); + } + } + + if (!RunningBlueprint->Nodes.Num()) + { + ItRunningBlueprints.RemoveCurrent(); + } + } + + for (auto ItStackFrameInfo = StackFrameInformation.begin(); ItStackFrameInfo != StackFrameInformation.end();) + { + if (ItStackFrameInfo->second.ScriptEntryTag == Context.GetScriptEntryTag()) + { + ItStackFrameInfo = StackFrameInformation.erase(ItStackFrameInfo); + } + else + { + ++ItStackFrameInfo; + } + } + + CurrentScriptEntryTag--; +} + +void FVisualStudioBlueprintDebuggerHelper::OnScriptException( + const UObject* Owner, + const struct FFrame& Stack, + const FBlueprintExceptionInfo& ExceptionInfo) +{ + EBlueprintExceptionType::Type ExceptionType = ExceptionInfo.GetType(); + if (ExceptionType != EBlueprintExceptionType::Type::Tracepoint && + ExceptionType != EBlueprintExceptionType::Type::WireTracepoint && + ExceptionType != EBlueprintExceptionType::Type::Breakpoint) + { + return; + } + + UFunction* NodeFunction = Cast(Stack.Node); + if (!NodeFunction) + { + return; + } + + UBlueprintGeneratedClass* BlueprintGeneratedClass = Cast(NodeFunction->GetOuter()); + if (!BlueprintGeneratedClass) + { + return; + } + + UBlueprint* Blueprint = Cast(BlueprintGeneratedClass->ClassGeneratedBy); + if (!Blueprint) + { + return; + } + + const int32 BreakpointOffset = Stack.Code - Stack.Node->Script.GetData() - 1; + const UEdGraphNode* NodeStoppedAt = FKismetDebugUtilities::FindSourceNodeForCodeLocation(Owner, Stack.Node, BreakpointOffset, /*bAllowImpreciseHit=*/ true); + if (!NodeStoppedAt) + { + return; + } + + StackFrameInformation[NodeFunction] = { CurrentScriptEntryTag, FString::Printf(TEXT("%s::%s"), *Blueprint->GetFriendlyName(), *NodeStoppedAt->GetNodeTitle(ENodeTitleType::Type::ListView).ToString()) }; + TTuple>* ExistingNodesRuntimeInformationTuple = BlueprintsRuntimeInformation.RunningBlueprints.FindByPredicate([&Blueprint](const TTuple>& Tuple) { + return Tuple.Key == Blueprint; + }); + + TSharedPtr NodesRuntimeInformation; + if (!ExistingNodesRuntimeInformationTuple) + { + NodesRuntimeInformation = MakeShared(); + BlueprintsRuntimeInformation.RunningBlueprints.Add(MakeTuple(Blueprint, NodesRuntimeInformation)); + } + else + { + NodesRuntimeInformation = ExistingNodesRuntimeInformationTuple->Value; + } + + TSharedPtr CurrentNodeData; + if (NodesRuntimeInformation->Nodes.Num() == 0 || NodeStoppedAt != NodesRuntimeInformation->Nodes.Top()->Node) + { + CurrentNodeData = MakeShared(); + CurrentNodeData->Node = NodeStoppedAt; + CurrentNodeData->NodeName = NodeStoppedAt->GetNodeTitle(ENodeTitleType::Type::ListView); + CurrentNodeData->ScriptEntryTag = CurrentScriptEntryTag; + NodesRuntimeInformation->Nodes.Push(CurrentNodeData); + } + else + { + CurrentNodeData = NodesRuntimeInformation->Nodes.Top(); + } + + FCustomBlueprintPropertyInfo PinInstanceInfo; + for (auto GraphPin : NodeStoppedAt->Pins) + { + FKismetDebugUtilities::EWatchTextResult DebugResult = FKismetDebugUtilities::GetDebugInfo(PinInstanceInfo, Blueprint, (UObject*)Owner, GraphPin); + if (DebugResult != FKismetDebugUtilities::EWTR_Valid) + { + continue; + } + + TSharedPtr* Existing = CurrentNodeData->Properties.FindByPredicate([&GraphPin](TSharedPtr& PinInfo) { + return PinInfo->Pin == GraphPin; + }); + + if (!Existing) + { + CurrentNodeData->Properties.Add(MakeShared(GraphPin, PinInstanceInfo)); + } + else + { + (*Existing)->Property = PinInstanceInfo; + } + } +} diff --git a/Plugins/VisualStudioTools/Source/VisualStudioBlueprintDebuggerHelper/Private/VisualStudioBlueprintDebuggerHelperModule.h b/Plugins/VisualStudioTools/Source/VisualStudioBlueprintDebuggerHelper/Private/VisualStudioBlueprintDebuggerHelperModule.h new file mode 100644 index 0000000..3e19a7d --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioBlueprintDebuggerHelper/Private/VisualStudioBlueprintDebuggerHelperModule.h @@ -0,0 +1,29 @@ +// Copyright 2022 (c) Microsoft. All rights reserved. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DECLARE_LOG_CATEGORY_EXTERN(LogVisualStudioBlueprintDebuggerHelper, Log, All); + +class FVisualStudioBlueprintDebuggerHelper : public FDefaultModuleImpl +{ +private: + void OnScriptException(const UObject* Owner, const struct FFrame& Stack, const FBlueprintExceptionInfo& ExceptionInfo); + void OnEnterScriptContext(const struct FBlueprintContextTracker& Context, const UObject* SourceObject, const UFunction* Function); + void OnExitScriptContext(const struct FBlueprintContextTracker& Context); + + int32 CurrentScriptEntryTag; + +public: + void StartupModule() override; + void ShutdownModule() override; +}; diff --git a/Plugins/VisualStudioTools/Source/VisualStudioBlueprintDebuggerHelper/VisualStudioBlueprintDebuggerHelper.Build.cs b/Plugins/VisualStudioTools/Source/VisualStudioBlueprintDebuggerHelper/VisualStudioBlueprintDebuggerHelper.Build.cs new file mode 100644 index 0000000..260b324 --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioBlueprintDebuggerHelper/VisualStudioBlueprintDebuggerHelper.Build.cs @@ -0,0 +1,31 @@ +// Copyright 2022 (c) Microsoft. All rights reserved. + +using UnrealBuildTool; + +public class VisualStudioBlueprintDebuggerHelper: ModuleRules +{ + public VisualStudioBlueprintDebuggerHelper(ReadOnlyTargetRules Target) : base(Target) + { + OptimizeCode = CodeOptimization.Never; + PrivateDependencyModuleNames.AddRange(new string[] { + "Core", + "ApplicationCore", + "AssetRegistry", + "CoreUObject", + "Engine", + "Json", + "JsonUtilities", + "Kismet", + "UnrealEd", + "Slate", + "SlateCore", + "ToolMenus", + "EditorSubsystem", + "MainFrame", + "BlueprintGraph", + "VisualStudioDTE", + "EditorStyle", + "Projects" + }); + } +} diff --git a/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/BlueprintAssetHelper.cpp b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/BlueprintAssetHelper.cpp new file mode 100644 index 0000000..5eab4fe --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/BlueprintAssetHelper.cpp @@ -0,0 +1,113 @@ +// Copyright 2022 (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +#include "BlueprintAssetHelpers.h" + +#include "AssetRegistry/AssetRegistryModule.h" +#include "Blueprint/BlueprintSupport.h" +#include "Engine/BlueprintCore.h" +#include "Engine/BlueprintGeneratedClass.h" +#include "Engine/Engine.h" +#include "Engine/StreamableManager.h" +#include "Misc/ScopeExit.h" +#include "VisualStudioTools.h" + +namespace VisualStudioTools +{ +namespace AssetHelpers +{ +/* +* These helpers handle the usage of some APIs that were deprecated in 5.1 +* but the replacements are not available in older versions. +* Might be overridden by the `Build.cs` rules +*/ +#if FILTER_ASSETS_BY_CLASS_PATH + +void SetBlueprintClassFilter(FARFilter& InOutFilter) +{ + // UE5.1 deprecated the API to filter using class names + InOutFilter.ClassPaths.Add(UBlueprintCore::StaticClass()->GetClassPathName()); +} + +static FString GetObjectPathString(const FAssetData& InAssetData) +{ + // UE5.1 deprecated 'FAssetData::ObjectPath' in favor of 'FAssetData::GetObjectPathString()' + return InAssetData.GetObjectPathString(); +} + +#else // FILTER_ASSETS_BY_CLASS_PATH + +void SetBlueprintClassFilter(FARFilter& InOutFilter) +{ + InOutFilter.ClassNames.Add(UBlueprintCore::StaticClass()->GetFName()); +} + +static FString GetObjectPathString(const FAssetData& InAssetData) +{ + return InAssetData.ObjectPath.ToString(); +} + +#endif // FILTER_ASSETS_BY_CLASS_PATH + +void ForEachAsset( + const TArray& TargetAssets, + TFunctionRef Callback) +{ + // Show a simpler logging output. + // LogTimes are still useful to tell how long it takes to process each asset. + TGuardValue DisableLogVerbosity(GPrintLogVerbosity, false); + TGuardValue DisableLogCategory(GPrintLogCategory, false); + + // We're about to load the assets which might trigger a ton of log messages + // Temporarily suppress them during this stage. + GEngine->Exec(nullptr, TEXT("log LogVisualStudioTools only")); + ON_SCOPE_EXIT + { + GEngine->Exec(nullptr, TEXT("log reset")); + }; + + FStreamableManager AssetLoader; + + for (int32 Idx = 0; Idx < TargetAssets.Num(); Idx++) + { + const FAssetData AssetData = TargetAssets[Idx]; + FSoftClassPath GenClassPath = AssetData.GetTagValueRef(FBlueprintTags::GeneratedClassPath); + UE_LOG(LogVisualStudioTools, Display, TEXT("Processing blueprints [%d/%d]: %s"), Idx + 1, TargetAssets.Num(), *GenClassPath.ToString()); + + TSharedPtr Handle = AssetLoader.RequestSyncLoad(GenClassPath); + ON_SCOPE_EXIT + { + // We're done, notify an unload. + Handle->ReleaseHandle(); + }; + + if (!Handle.IsValid()) + { + UE_LOG(LogVisualStudioTools, Warning, TEXT("Failed to get a streamable handle for Blueprint. Skipping. GenClassPath: %s"), *GenClassPath.ToString()); + continue; + } + + if (auto BlueprintGeneratedClass = Cast(Handle->GetLoadedAsset())) + { + Callback(BlueprintGeneratedClass, AssetData); + } + else + { + // Log some extra information to help the user understand why the asset failed to load. + + FString ObjectPathString = AssetHelpers::GetObjectPathString(AssetData); + + FString Msg = !GenClassPath.ToString().Contains(ObjectPathString) + ? FString::Printf( + TEXT("ObjectPath is not compatible with GenClassPath, consider re-saving it to avoid future issues. { ObjectPath: %s, GenClassPath: %s }"), + *ObjectPathString, + *GenClassPath.ToString()) + : FString::Printf(TEXT("ClassPath: %s"), *GenClassPath.ToString()); + + UE_LOG(LogVisualStudioTools, Warning, TEXT("Failed to load Blueprint. Skipping. %s"), *Msg); + } + } +} + +} +} \ No newline at end of file diff --git a/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/BlueprintAssetHelpers.h b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/BlueprintAssetHelpers.h new file mode 100644 index 0000000..02b5edd --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/BlueprintAssetHelpers.h @@ -0,0 +1,26 @@ +// Copyright 2022 (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +#pragma once +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" + +class UBlueprintGeneratedClass; + +namespace VisualStudioTools +{ +namespace AssetHelpers +{ +void SetBlueprintClassFilter(FARFilter& InOutFilter); + +/** +* Loads each blueprint asset and invokes the callback with the resulting blueprint generated class. +* Each iteration will load the asset using a FStreamableHandle and verify that is a valid blueprint +* before invoking the callback. +*/ +void ForEachAsset( + const TArray& TargetAssets, + TFunctionRef Callback); + +} // namespace AssetHelpers +} // namespace VisualStudioTools diff --git a/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/BlueprintReferencesCommandlet.cpp b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/BlueprintReferencesCommandlet.cpp new file mode 100644 index 0000000..4aeb046 --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/BlueprintReferencesCommandlet.cpp @@ -0,0 +1,248 @@ +// Copyright 2022 (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +#include "BlueprintReferencesCommandlet.h" + +#include "Algo/Find.h" +#include "Algo/Transform.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "BlueprintAssetHelpers.h" +#include "Engine/BlueprintGeneratedClass.h" +#include "FindInBlueprintManager.h" +#include "JsonObjectConverter.h" +#include "Misc/Paths.h" +#include "Misc/ScopeExit.h" +#include "Policies/CondensedJsonPrintPolicy.h" +#include "VisualStudioTools.h" + +namespace VisualStudioTools +{ +static FString StripClassPrefix(const FString& InClassName) +{ + if (InClassName.IsEmpty()) + { + return InClassName; + } + + size_t PrefixSize = 0; + + const TCHAR ClassPrefixChar = InClassName[0]; + switch (ClassPrefixChar) + { + case TEXT('I'): + case TEXT('A'): + case TEXT('U'): + // If it is a class prefix, check for deprecated class prefix also + if (InClassName.Len() > 12 && FCString::Strncmp(&(InClassName[1]), TEXT("DEPRECATED_"), 11) == 0) + { + PrefixSize = 12; + } + else + { + PrefixSize = 1; + } + break; + case TEXT('F'): + case TEXT('T'): + // Struct prefixes are also fine. + PrefixSize = 1; + break; + default: + PrefixSize = 0; + break; + } + + return InClassName.RightChop(PrefixSize); +} + +/** +* Retrieves the asset data matching the given FindInBlueprints query. +*/ +TArray SearchForCandidateAssets(const FString& SearchQuery) +{ + IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")).Get(); + AssetRegistry.SearchAllAssets(true); + + TArray OutItemsFound; + FStreamSearch StreamSearch(SearchQuery); + while (!StreamSearch.IsComplete()) + { + FFindInBlueprintSearchManager::Get().Tick(0.0); + } + + // Execute the search and get all the assets in the result. + StreamSearch.GetFilteredItems(OutItemsFound); + + + TArray OutTargetAssets; + Algo::Transform(OutItemsFound, OutTargetAssets, + [&](const FSearchResult& Item) + { + // The DisplayText property of the result contains the blueprint's object path + // Use that to find the respective asset in the registry +#if FILTER_ASSETS_BY_CLASS_PATH + return AssetRegistry.GetAssetByObjectPath(FSoftObjectPath(*Item->DisplayText.ToString())); +#else + return AssetRegistry.GetAssetByObjectPath(*Item->DisplayText.ToString()); +#endif // FILTER_ASSETS_BY_CLASS_PATH + }); + + return OutTargetAssets; +} + +/** +* Loads each blueprint asset and filters the collection to items which use the +* target UFunction in their call graph, matching the native class and function names. +*/ +TMap GetConfirmedAssets( + const FString& FunctionName, const FString& ClassNameWithoutPrefix, const TArray& InAssets) +{ + TMap OutResults; + + AssetHelpers::ForEachAsset(InAssets, + [&](UBlueprintGeneratedClass* BlueprintClassName, const FAssetData AssetData) + { + auto MatchingFunction = Algo::FindByPredicate(BlueprintClassName->CalledFunctions, + [&](const UFunction* Fn) + { + return Fn->HasAnyFunctionFlags(EFunctionFlags::FUNC_Native) + && Fn->GetName() == FunctionName + && Fn->GetOwnerClass()->GetName() == ClassNameWithoutPrefix; + }); + + if (MatchingFunction != nullptr) + { + OutResults.Add(BlueprintClassName->GetName(), AssetData); + } + }); + + return OutResults; +} +using JsonWriter = TJsonWriter>; + +static void SerializeBlueprintReference( + TSharedRef& Json, const FString& BlueprintClassName, const FAssetData& Asset) +{ + FString PackageFileName; + FString PackageFile; + FString PackageFilePath; + if (FPackageName::TryConvertLongPackageNameToFilename(Asset.GetPackage()->GetName(), PackageFileName) && + FPackageName::FindPackageFileWithoutExtension(PackageFileName, PackageFile)) + { + PackageFilePath = FPaths::ConvertRelativePathToFull(MoveTemp(PackageFile)); + } + + Json->WriteObjectStart(); + Json->WriteValue(TEXT("name"), BlueprintClassName); + Json->WriteValue(TEXT("path"), PackageFilePath); + Json->WriteObjectEnd(); +} + +static void SerializeBlueprints( + TSharedRef& Json, const TMap& InAssets) +{ + Json->WriteIdentifierPrefix(TEXT("blueprints")); + Json->WriteArrayStart(); + + for (auto& Item : InAssets) + { + const FString& BlueprintClassName = Item.Key; + const FAssetData& Asset = Item.Value; + SerializeBlueprintReference(Json, BlueprintClassName, Asset); + } + + Json->WriteArrayEnd(); +} + +static void SerializeMetadata( + TSharedRef& Json, int TotalAssetCount) +{ + Json->WriteIdentifierPrefix(TEXT("metadata")); + Json->WriteObjectStart(); + { + Json->WriteValue(TEXT("asset_count"), TotalAssetCount); + } + Json->WriteObjectEnd(); +} + +static void SerializeResults( + const TMap& InAssets, + FArchive& OutArchive, + int TotalAssetCount) +{ + TSharedRef Json = JsonWriter::Create(&OutArchive); + Json->WriteObjectStart(); + + SerializeBlueprints(Json, InAssets); + SerializeMetadata(Json, TotalAssetCount); + + Json->WriteObjectEnd(); + Json->Close(); +} +} // namespace VisualStudioTools + +static constexpr auto SymbolParamVal = TEXT("symbol"); + +UVsBlueprintReferencesCommandlet::UVsBlueprintReferencesCommandlet() + : Super() +{ + HelpDescription = TEXT("Commandlet for generating data used by Blueprint support in Visual Studio."); + + HelpParamNames.Add(SymbolParamVal); + HelpParamDescriptions.Add(TEXT("[Optional] Fully qualified symbol to search for in the blueprints.")); + + HelpUsage = TEXT(" -run=VsBlueprintReferences -output= -symbol= [-unattended -noshadercompile -nosound -nullrhi -nocpuprofilertrace -nocrashreports -nosplash]"); +} + +int32 UVsBlueprintReferencesCommandlet::Run( + TArray& Tokens, + TArray& Switches, + TMap& ParamVals, + FArchive& OutArchive) +{ + using namespace VisualStudioTools; + GIsRunning = true; // Required for the blueprint search to work. + + FString* ReferencesSymbol = ParamVals.Find(SymbolParamVal); + if (ReferencesSymbol->IsEmpty()) + { + UE_LOG(LogVisualStudioTools, Error, TEXT("Missing required symbol parameter.")); + PrintHelp(); + return -1; + } + + FString FunctionName; + FString ClassNameNative; + if (!ReferencesSymbol->Split(TEXT("::"), &ClassNameNative, &FunctionName)) + { + UE_LOG(LogVisualStudioTools, Error, TEXT("Reference parameter should be in the qualified 'NativeClassName::MethodName' format.")); + PrintHelp(); + return -1; + } + + // Execute the search in two stages: + // 1. Use FindInBlueprints to get all candidate blueprints with calls to functions that match the requested symbol + // 2. Confirm the blueprints reference the requested function, by matching the target UFunction in their call graph. + // The first step acts as a filter to avoid loading too many blueprints to inspect their call graph. + // The second step is required because the FiB data does not always allow for searching with the function + // qualified with the owned class name, if the function is static. + + FString ClassNameWithoutPrefix = StripClassPrefix(ClassNameNative); + + // Create a FiB search query for function nodes where the native name matches the requested symbol + FString SearchValue = FString::Printf(TEXT("Nodes(\"Native Name\"=+%s & ClassName=K2Node_CallFunction)"), *FunctionName); + + UE_LOG(LogVisualStudioTools, Display, TEXT("Blueprint search query: %s"), *SearchValue); + + // Step 1: Execute the Fib search + TArray TargetAssets = SearchForCandidateAssets(SearchValue); + + // Step 2: Load the assets to confirm they are a match + TMap MatchAssets = GetConfirmedAssets(FunctionName, ClassNameWithoutPrefix, TargetAssets); + + // Finally, write the results back to the output + SerializeResults(MatchAssets, OutArchive, TargetAssets.Num()); + + UE_LOG(LogVisualStudioTools, Display, TEXT("Found %d blueprints."), MatchAssets.Num()); + return 0; +} \ No newline at end of file diff --git a/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/BlueprintReferencesCommandlet.h b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/BlueprintReferencesCommandlet.h new file mode 100644 index 0000000..a0b8b1e --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/BlueprintReferencesCommandlet.h @@ -0,0 +1,25 @@ +// Copyright 2022 (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include "CoreMinimal.h" +#include "VisualStudioToolsCommandletBase.h" + +#include "BlueprintReferencesCommandlet.generated.h" + +UCLASS() +class UVsBlueprintReferencesCommandlet + : public UVisualStudioToolsCommandletBase +{ + GENERATED_BODY() + +public: + UVsBlueprintReferencesCommandlet(); + + int32 Run( + TArray& Tokens, + TArray& Switches, + TMap& ParamVals, + FArchive& OutArchive) override; +}; diff --git a/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/FSmartBSTR.h b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/FSmartBSTR.h new file mode 100644 index 0000000..46772f3 --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/FSmartBSTR.h @@ -0,0 +1,63 @@ +#pragma once + +#include "VisualStudioDTE.h" +#include + +class FSmartBSTR +{ +public: + FSmartBSTR() : data(nullptr) + { + } + + FSmartBSTR(const FSmartBSTR& Other) + { + if (Other.data) data = SysAllocString(Other.data); + else data = nullptr; + } + + FSmartBSTR(FSmartBSTR&& Other) + { + data = std::exchange(Other.data, nullptr); + } + + FSmartBSTR(const FString& Other) + { + data = SysAllocString(*Other); + } + + FSmartBSTR(const OLECHAR *Ptr) + { + if (Ptr) data = SysAllocString(Ptr); + else data = nullptr; + } + + ~FSmartBSTR() + { + if (data) SysFreeString(data); + } + + FSmartBSTR& operator=(const FSmartBSTR& Other) + { + if (this == &Other) return *this; + if (data) SysFreeString(data); + if (Other.data) data = SysAllocString(Other.data); + else data = nullptr; + return *this; + } + + FSmartBSTR& operator=(FSmartBSTR&& Other) + { + if (data) SysFreeString(data); + data = std::exchange(Other.data, nullptr); + return *this; + } + + BSTR operator*() const + { + return data; + } + +private: + BSTR data; +}; \ No newline at end of file diff --git a/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VSServerCommandlet.cpp b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VSServerCommandlet.cpp new file mode 100644 index 0000000..70a55f5 --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VSServerCommandlet.cpp @@ -0,0 +1,118 @@ +// Copyright 2022 (c) Microsoft. All rights reserved. + +#include "VSServerCommandlet.h" +#include "VSTestAdapterCommandlet.h" + +#include "Windows/AllowWindowsPlatformTypes.h" + +#include "HAL/PlatformNamedPipe.h" +#include "Runtime/Core/Public/Async/TaskGraphInterfaces.h" +#include "Runtime/Core/Public/Containers/Ticker.h" +#include "Runtime/Engine/Classes/Engine/World.h" +#include "Runtime/Engine/Public/TimerManager.h" +#include "Runtime/Launch/Resources/Version.h" +#include "Runtime\CoreUObject\Public\UObject\UObjectGlobals.h" +#include +#include +#include +#include +#include +#include + +#include "Windows/HideWindowsPlatformTypes.h" + +#include "VisualStudioTools.h" + +static constexpr auto NamedPipeParam = TEXT("NamedPipe"); +static constexpr auto KillServerParam = TEXT("KillVSServer"); + +UVSServerCommandlet::UVSServerCommandlet() +{ + HelpDescription = TEXT("Commandlet for Unreal Engine server mode."); + HelpUsage = TEXT(" -run=VSServer [-stdout -multiprocess -silent -unattended -AllowStdOutLogVerbosity -NoShaderCompile]"); + + HelpParamNames.Add(NamedPipeParam); + HelpParamDescriptions.Add(TEXT("[Required] The name of the named pipe used to communicate with Visual Studio.")); + + HelpParamNames.Add(KillServerParam); + HelpParamDescriptions.Add(TEXT("[Optional] Quit the server mode commandlet immediately.")); +} + +void UVSServerCommandlet::ExecuteSubCommandlet(FString ueServerNamedPipe) +{ + char buffer[1024]; + DWORD dwRead; + std::string result = "0"; + + // Open the named pipe. + std::wstring pipeName = L"\\\\.\\pipe\\"; + pipeName.append(ueServerNamedPipe.GetCharArray().GetData()); + HANDLE HPipe = CreateFile(pipeName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + if (HPipe != INVALID_HANDLE_VALUE) + { + ConnectNamedPipe(HPipe, NULL); + DWORD dwState; + BOOL bSuccess = GetNamedPipeHandleState(HPipe, &dwState, NULL, NULL, NULL, NULL, 0); + if (bSuccess) + { + // Read data from the named pipe. + ReadFile(HPipe, buffer, sizeof(buffer) - 1, &dwRead, NULL); + buffer[dwRead] = '\0'; + std::string strSubCommandletParams(buffer, dwRead); + FString SubCommandletParams = FString(strSubCommandletParams.c_str()); + + // Determine which sub-commandlet to invoke, and write back result response. + if (SubCommandletParams.Contains("VSTestAdapter")) + { + UVSTestAdapterCommandlet *Commandlet = NewObject(); + try + { + int32 subCommandletResult = Commandlet->Main(SubCommandletParams); + } + catch (const std::exception &ex) + { + UE_LOG(LogVisualStudioTools, Display, TEXT("Exception invoking VSTestAdapter commandlet: %s"), UTF8_TO_TCHAR(ex.what())); + result = "0"; + } + } + else if (SubCommandletParams.Contains("KillVSServer")) + { + // When KillVSServer is passed in, then kill the Unreal Editor process to end server mode. + exit(0); + } + else + { + // If cannot find which sub-commandlet to run, then return error. + result = "1"; + } + + WriteFile(HPipe, result.c_str(), result.size(), &dwRead, NULL); + } + } +} + +int32 UVSServerCommandlet::Main(const FString &ServerParams) +{ + TArray Tokens; + TArray Switches; + TMap ParamVals; + + ParseCommandLine(*ServerParams, Tokens, Switches, ParamVals); + if (ParamVals.Contains(NamedPipeParam)) + { + FString ueServerNamedPipe = ParamVals[NamedPipeParam]; + + // Infinite loop that listens to requests every second. + while (true) + { + std::this_thread::sleep_for(std::chrono::seconds(1)); + ExecuteSubCommandlet(ueServerNamedPipe); + } + } + else + { + UE_LOG(LogVisualStudioTools, Display, TEXT("Missing named pipe parameter.")); + } + + return 1; +} \ No newline at end of file diff --git a/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VSServerCommandlet.h b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VSServerCommandlet.h new file mode 100644 index 0000000..a8c7ef5 --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VSServerCommandlet.h @@ -0,0 +1,29 @@ +// Copyright 2022 (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include "Commandlets/Commandlet.h" +#include + +#include +#include +#include + +#include "VSServerCommandlet.generated.h" + +UCLASS() +class UVSServerCommandlet + : public UCommandlet +{ + GENERATED_BODY() + +public: + UVSServerCommandlet(); + +public: + virtual int32 Main(const FString& Params) override; + +private: + void ExecuteSubCommandlet(FString ueServerNamedPipe); +}; \ No newline at end of file diff --git a/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VSTestAdapterCommandlet.cpp b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VSTestAdapterCommandlet.cpp new file mode 100644 index 0000000..493bfee --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VSTestAdapterCommandlet.cpp @@ -0,0 +1,288 @@ +// Copyright 2022 (c) Microsoft. All rights reserved. + +#include "VSTestAdapterCommandlet.h" + +#include "Runtime/Core/Public/Async/TaskGraphInterfaces.h" +#include "Runtime/Core/Public/Containers/Ticker.h" +#include "Runtime/Launch/Resources/Version.h" +#include +#include + +#include "VisualStudioTools.h" + +static constexpr auto FiltersParam = TEXT("filters"); +static constexpr auto ListTestsParam = TEXT("listtests"); +static constexpr auto RunTestsParam = TEXT("runtests"); +static constexpr auto TestResultsFileParam = TEXT("testresultfile"); +static constexpr auto HelpParam = TEXT("help"); + +static void GetAllTests(TArray& OutTestList) +{ + FAutomationTestFramework& Framework = FAutomationTestFramework::GetInstance(); + Framework.GetValidTestNames(OutTestList); +} + +static void ReadTestsFromFile(const FString& InFile, TArray& OutTestList) +{ + TSet TestCommands; + + // Wrapping in an inner scope to ensure automatic destruction of InStream object without explicitly calling .close(). + { + std::wifstream InStream(*InFile); + if (!InStream.good()) + { + UE_LOG(LogVisualStudioTools, Error, TEXT("Failed to open file at path: %s"), *InFile); + return; + } + + std::wstring Line; + while (std::getline(InStream, Line)) + { + if (Line.length() > 0) + { + TestCommands.Add(FString(Line.c_str())); + } + } + } + + GetAllTests(OutTestList); + for (int32 Idx = OutTestList.Num() - 1; Idx >= 0; Idx--) + { + if (!TestCommands.Contains(OutTestList[Idx].GetTestName())) + { + OutTestList.RemoveAt(Idx); + } + } +} + +static int32 ListTests(const FString& TargetFile) +{ + std::wofstream OutFile(*TargetFile); + if (!OutFile.good()) + { + UE_LOG(LogVisualStudioTools, Error, TEXT("Failed to open file at path: %s"), *TargetFile); + return 1; + } + + FAutomationTestFramework& Framework = FAutomationTestFramework::GetInstance(); + + TArray TestInfos; + GetAllTests(TestInfos); + + for (const auto& TestInfo : TestInfos) + { + const FString TestCommand = TestInfo.GetTestName(); + const FString DisplayName = TestInfo.GetDisplayName(); + const FString SourceFile = TestInfo.GetSourceFile(); + const int32 Line = TestInfo.GetSourceFileLine(); + + OutFile << *TestCommand << TEXT("|") << *DisplayName << TEXT("|") << Line << TEXT("|") << *SourceFile << std::endl; + } + + UE_LOG(LogVisualStudioTools, Display, TEXT("Found %d tests"), TestInfos.Num()); + OutFile.close(); + + return 0; +} + +static int32 RunTests(const FString& TestListFile, const FString& ResultsFile) +{ + std::wofstream OutFile(*ResultsFile); + if (!OutFile.good()) + { + UE_LOG(LogVisualStudioTools, Error, TEXT("Failed to open file at path: %s"), *ResultsFile); + return 1; + } + + TArray TestInfos; + if (TestListFile.Equals(TEXT("All"), ESearchCase::IgnoreCase)) + { + GetAllTests(TestInfos); + } + else + { + ReadTestsFromFile(TestListFile, TestInfos); + } + + bool AllSuccessful = true; + + FAutomationTestFramework& Framework = FAutomationTestFramework::GetInstance(); + + for (const FAutomationTestInfo& TestInfo : TestInfos) + { + const FString TestCommand = TestInfo.GetTestName(); + const FString DisplayName = TestInfo.GetDisplayName(); + + UE_LOG(LogVisualStudioTools, Log, TEXT("Running %s"), *DisplayName); + + const int32 RoleIndex = 0; // always default to "local" role index. Only used for multi-participant tests + Framework.StartTestByName(TestCommand, RoleIndex); + + FDateTime Last = FDateTime::UtcNow(); + + while (!Framework.ExecuteLatentCommands()) + { + // Because we are not 'ticked' by the Engine we need to pump the TaskGraph + FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); + + const FDateTime Now = FDateTime::UtcNow(); + const float Delta = static_cast((Now - Last).GetTotalSeconds()); + + // .. and the core FTicker +#if ENGINE_MAJOR_VERSION >= 5 + FTSTicker::GetCoreTicker().Tick(Delta); +#else + FTicker::GetCoreTicker().Tick(Delta); +#endif + + Last = Now; + } + + FAutomationTestExecutionInfo ExecutionInfo; + const bool CurrentTestSuccessful = Framework.StopTest(ExecutionInfo) && ExecutionInfo.GetErrorTotal() == 0; + AllSuccessful = AllSuccessful && CurrentTestSuccessful; + + const FString Result = CurrentTestSuccessful ? TEXT("OK") : TEXT("FAIL"); + + // [RUNTEST] is part of the protocol, so do not remove. + OutFile << TEXT("[RUNTEST]") << *TestCommand << TEXT("|") << *DisplayName << TEXT("|") << *Result << TEXT("|") << ExecutionInfo.Duration << std::endl; + + if (!CurrentTestSuccessful) + { + for (const auto& Entry : ExecutionInfo.GetEntries()) + { + if (Entry.Event.Type == EAutomationEventType::Error) + { + OutFile << *Entry.Event.Message << std::endl; + UE_LOG(LogVisualStudioTools, Error, TEXT("%s"), *Entry.Event.Message); + } + } + + UE_LOG(LogVisualStudioTools, Log, TEXT("Failed %s"), *DisplayName); + } + + OutFile.flush(); + } + + return AllSuccessful ? 0 : 1; +} + +UVSTestAdapterCommandlet::UVSTestAdapterCommandlet() +{ + HelpDescription = TEXT("Commandlet for generating data used by Blueprint support in Visual Studio."); + HelpUsage = TEXT(" -run=VSTestAdapter [-stdout -multiprocess -silent -unattended -AllowStdOutLogVerbosity -NoShaderCompile]"); + + HelpParamNames.Add(ListTestsParam); + HelpParamDescriptions.Add(TEXT("[Required] The file path to write the test cases retrieved from FAutomationTestFramework")); + + HelpParamNames.Add(RunTestsParam); + HelpParamDescriptions.Add(TEXT("[Required] The test cases that will be sent to FAutomationTestFramework to run.")); + + HelpParamNames.Add(TestResultsFileParam); + HelpParamDescriptions.Add(TEXT("[Required] The output file from running test cases that we parse to retrieve test case results.")); + + HelpParamNames.Add(FiltersParam); + HelpParamDescriptions.Add(TEXT("[Optional] List of test filters to enable separated by '+'. Default is 'application+smoke+product+perf+stress+negative'")); + + HelpParamNames.Add(HelpParam); + HelpParamDescriptions.Add(TEXT("[Optional] Print this help message and quit the commandlet immediately.")); +} + +void UVSTestAdapterCommandlet::PrintHelp() const +{ + UE_LOG(LogVisualStudioTools, Display, TEXT("%s"), *HelpDescription); + UE_LOG(LogVisualStudioTools, Display, TEXT("Usage: %s"), *HelpUsage); + UE_LOG(LogVisualStudioTools, Display, TEXT("Parameters:")); + for (int32 Idx = 0; Idx < HelpParamNames.Num(); ++Idx) + { + UE_LOG(LogVisualStudioTools, Display, TEXT("\t-%s: %s"), *HelpParamNames[Idx], *HelpParamDescriptions[Idx]); + } +} + +int32 UVSTestAdapterCommandlet::Main(const FString& Params) +{ + TArray Tokens; + TArray Switches; + TMap ParamVals; + + // Functionality for Unreal Engine Test Adapter. + ParseCommandLine(*Params, Tokens, Switches, ParamVals); + if (ParamVals.Contains(HelpParam)) + { + PrintHelp(); + return 0; + } + + // Default to all the test filters. + auto filter = EAutomationTestFlags::ProductFilter | EAutomationTestFlags::SmokeFilter | EAutomationTestFlags::PerfFilter | EAutomationTestFlags::EngineFilter; + if (ParamVals.Contains(FiltersParam)) + { + FString filters = ParamVals[FiltersParam]; + if (filters.Contains("smoke")) + { + filter |= EAutomationTestFlags::SmokeFilter; + } + else + { + filter &= ~EAutomationTestFlags::SmokeFilter; + } + + if (filters.Contains("engine")) + { + filter |= EAutomationTestFlags::EngineFilter; + } + else + { + filter &= ~EAutomationTestFlags::EngineFilter; + } + + if (filters.Contains("product")) + { + filter |= EAutomationTestFlags::ProductFilter; + } + else + { + filter &= ~EAutomationTestFlags::ProductFilter; + } + + if (filters.Contains("perf")) + { + filter |= EAutomationTestFlags::PerfFilter; + } + else + { + filter &= ~EAutomationTestFlags::PerfFilter; + } + + if (filters.Contains("stress")) + { + filter |= EAutomationTestFlags::StressFilter; + } + else + { + filter &= ~EAutomationTestFlags::StressFilter; + } + + if (filters.Contains("negative")) + { + filter |= EAutomationTestFlags::NegativeFilter; + } + else + { + filter &= ~EAutomationTestFlags::NegativeFilter; + } + } + + FAutomationTestFramework::GetInstance().SetRequestedTestFilter(filter); + if (ParamVals.Contains(ListTestsParam)) + { + return ListTests(ParamVals[ListTestsParam]); + } + else if (ParamVals.Contains(RunTestsParam) && ParamVals.Contains(TestResultsFileParam)) + { + return RunTests(ParamVals[RunTestsParam], ParamVals[TestResultsFileParam]); + } + + PrintHelp(); + return 1; +} \ No newline at end of file diff --git a/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VSTestAdapterCommandlet.h b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VSTestAdapterCommandlet.h new file mode 100644 index 0000000..299b763 --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VSTestAdapterCommandlet.h @@ -0,0 +1,28 @@ +// Copyright 2022 (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include "Commandlets/Commandlet.h" + +#include +#include +#include + +#include "VSTestAdapterCommandlet.generated.h" + +UCLASS() +class UVSTestAdapterCommandlet + : public UCommandlet +{ + GENERATED_BODY() + +public: + UVSTestAdapterCommandlet(); + +public: + virtual int32 Main(const FString &Params) override; + +private: + void PrintHelp() const; +}; diff --git a/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioTools.cpp b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioTools.cpp new file mode 100644 index 0000000..0c59e55 --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioTools.cpp @@ -0,0 +1,19 @@ +// Copyright 2022 (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +#include "VisualStudioTools.h" + +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" + +DEFINE_LOG_CATEGORY(LogVisualStudioTools); + +class FVisualStudioToolsModule : public IModuleInterface +{ +public: + /** IModuleInterface implementation */ + virtual void StartupModule() override {} + virtual void ShutdownModule() override {} +}; + +IMPLEMENT_MODULE(FVisualStudioToolsModule, VisualStudioTools) diff --git a/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioToolsBlueprintBreakpointExtension.cpp b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioToolsBlueprintBreakpointExtension.cpp new file mode 100644 index 0000000..9062eac --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioToolsBlueprintBreakpointExtension.cpp @@ -0,0 +1,576 @@ +#include "VisualStudioToolsBlueprintBreakpointExtension.h" +#include "FSmartBSTR.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if ENGINE_MAJOR_VERSION < 5 +#include +#include +#endif + +DEFINE_LOG_CATEGORY(LogUVisualStudioToolsBlueprintBreakpointExtension); + +static const FName GraphEditorModuleName(TEXT("GraphEditor")); + +void UVisualStudioToolsBlueprintBreakpointExtension::Initialize(FSubsystemCollectionBase& Collection) +{ + FGraphEditorModule& GraphEditorModule = FModuleManager::LoadModuleChecked(GraphEditorModuleName); + GraphEditorModule.GetAllGraphEditorContextMenuExtender().Add( + FGraphEditorModule::FGraphEditorMenuExtender_SelectedNode::CreateUObject(this, &ThisClass::HandleOnExtendGraphEditorContextMenu)); +} + +void UVisualStudioToolsBlueprintBreakpointExtension::Deinitialize() +{ + FGraphEditorModule* GraphEditorModule = FModuleManager::GetModulePtr(GraphEditorModuleName); + if (!GraphEditorModule) + { + return; + } + + GraphEditorModule->GetAllGraphEditorContextMenuExtender().RemoveAll( + [](const FGraphEditorModule::FGraphEditorMenuExtender_SelectedNode& Delegate) { + FName LocalFunction = GET_FUNCTION_NAME_CHECKED(ThisClass, HandleOnExtendGraphEditorContextMenu); + return Delegate.TryGetBoundFunctionName() == LocalFunction; + }); +} + +TSharedRef UVisualStudioToolsBlueprintBreakpointExtension::HandleOnExtendGraphEditorContextMenu( + const TSharedRef CommandList, + const UEdGraph* Graph, + const UEdGraphNode* Node, + const UEdGraphPin* Pin, + bool /* bIsConst */) +{ + TSharedRef Extender = MakeShared(); + if (!CanAddVisualStudioBreakpoint(Node, nullptr, nullptr)) + { + return Extender; + } + + const FName ExtensionHook(TEXT("EdGraphSchemaNodeActions")); + Extender->AddMenuExtension( + ExtensionHook, + EExtensionHook::After, + CommandList, + FMenuExtensionDelegate::CreateUObject(this, &ThisClass::AddVisualStudioBlueprintBreakpointMenuOption, Node)); + + return Extender; +} + +void UVisualStudioToolsBlueprintBreakpointExtension::AddVisualStudioBlueprintBreakpointMenuOption(FMenuBuilder& MenuBuilder, const UEdGraphNode *Node) +{ + MenuBuilder.BeginSection(TEXT("VisualStudioTools"), FText::FromString("Visual Studio Tools")); + MenuBuilder.AddMenuEntry( + FText::FromString("Set breakpoint in Visual Studio"), + FText::FromString("This will set a breakpoint in Visual Studio so the native debugger can break the execution"), + FSlateIcon(), + FUIAction(FExecuteAction::CreateUObject(this, &ThisClass::AddVisualStudioBreakpoint, Node))); + MenuBuilder.EndSection(); +} + +FString UVisualStudioToolsBlueprintBreakpointExtension::GetProjectPath(const FString &ProjectDir) +{ + FString ProjectPath; + if (!FFileHelper::LoadFileToString(ProjectPath, *(FPaths::EngineIntermediateDir() / TEXT("ProjectFiles") / TEXT("PrimaryProjectPath.txt")))) + { + const FProjectDescriptor* CurrentProject = IProjectManager::Get().GetCurrentProject(); + + if ((CurrentProject == nullptr || CurrentProject->Modules.Num() == 0) || !FUProjectDictionary::GetDefault().IsForeignProject(ProjectDir)) + { + ProjectPath = FPaths::RootDir() / TEXT("UE5"); + } + else + { + const FString BaseName = FApp::HasProjectName() ? FApp::GetProjectName() : FPaths::GetBaseFilename(ProjectDir); + ProjectPath = ProjectDir / BaseName; + } + } + + ProjectPath = ProjectPath + TEXT(".sln"); + + FPaths::NormalizeFilename(ProjectPath); + + return ProjectPath; +} + +bool UVisualStudioToolsBlueprintBreakpointExtension::GetRunningVisualStudioDTE(TComPtr& OutDTE) +{ + IRunningObjectTable* RunningObjectTable; + bool bResult = false; + FString ProjectDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir()); + FPaths::NormalizeDirectoryName(ProjectDir); + FString SolutionPath = GetProjectPath(ProjectDir); + + if (SUCCEEDED(GetRunningObjectTable(0, &RunningObjectTable)) && RunningObjectTable) + { + IEnumMoniker* MonikersTable; + if (SUCCEEDED(RunningObjectTable->EnumRunning(&MonikersTable))) + { + MonikersTable->Reset(); + + // Look for all visual studio instances in the ROT + IMoniker* CurrentMoniker; + while (MonikersTable->Next(1, &CurrentMoniker, NULL) == S_OK) + { + IBindCtx* BindContext; + LPOLESTR OutName; + if (SUCCEEDED(CreateBindCtx(0, &BindContext)) && SUCCEEDED(CurrentMoniker->GetDisplayName(BindContext, NULL, &OutName))) + { + TComPtr ComObject; + if (SUCCEEDED(RunningObjectTable->GetObject(CurrentMoniker, &ComObject))) + { + TComPtr TempDTE; + if (SUCCEEDED(TempDTE.FromQueryInterface(__uuidof(EnvDTE::_DTE), ComObject))) + { + TComPtr Solution; + BSTR OutPath = nullptr; + if (SUCCEEDED(TempDTE->get_Solution(&Solution)) && + SUCCEEDED(Solution->get_FullName(&OutPath))) + { + FString Filename(OutPath); + FPaths::NormalizeFilename(Filename); + if (Filename == SolutionPath || Filename == ProjectDir) + { + OutDTE = TempDTE; + bResult = true; + } + } + else + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Could not get solution from DTE")); + } + + SysFreeString(OutPath); + } + } + } + else + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Could not get display name for moniker")); + } + BindContext->Release(); + CurrentMoniker->Release(); + if (bResult) break; + } + MonikersTable->Release(); + RunningObjectTable->Release(); + } + else + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Could not enumerate Running Object Table")); + } + } + else + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Could not get Running Object Table")); + } + + return bResult; +} + +bool UVisualStudioToolsBlueprintBreakpointExtension::CanAddVisualStudioBreakpoint(const UEdGraphNode* Node, UClass **OutOwnerClass, UFunction **OutFunction) +{ + const UK2Node_CallFunction* K2Node = Cast(Node); + if (!K2Node) + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, TEXT("Node is not a UK2Node_CallFunction")); + return false; + } + + UFunction* Function = K2Node->GetTargetFunction(); + if (!Function || !Function->IsNative()) + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, TEXT("Function is not native")); + return false; + } + + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, TEXT("Trying to get function definition for %s"), *Function->GetName()); + + UClass* OwnerClass = Function->GetOwnerClass(); + if (!OwnerClass->HasAllClassFlags(CLASS_Native)) + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, TEXT("Owning class is not native")); + return false; + } + + if (OutOwnerClass) *OutOwnerClass = OwnerClass; + if (OutFunction) *OutFunction = Function; + return true; +} + +#if ENGINE_MAJOR_VERSION < 5 + +#define PRINT_PLATFORM_ERROR_MSG(_TXT) \ + do { \ + TCHAR _ErrorBuffer[MAX_SPRINTF] = { 0 }; \ + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("" #_TXT ": [%s]"), \ + FPlatformMisc::GetSystemErrorMessage(_ErrorBuffer, MAX_SPRINTF, 0)); \ + } while (0) + +bool UVisualStudioToolsBlueprintBreakpointExtension::PreloadModule(HANDLE ProcessHandle, HMODULE ModuleHandle, const FString& RemoteStorage) +{ + int32 ErrorCode = 0; + MODULEINFO ModuleInfo = { 0 }; + WCHAR ModuleName[FProgramCounterSymbolInfo::MAX_NAME_LENGTH] = { 0 }; + WCHAR ImageName[FProgramCounterSymbolInfo::MAX_NAME_LENGTH] = { 0 }; +#if PLATFORM_64BITS + static_assert(sizeof(MODULEINFO) == 24, "Broken alignment for 64bit Windows include."); +#else + static_assert(sizeof(MODULEINFO) == 12, "Broken alignment for 32bit Windows include."); +#endif + + if (!GetModuleInformation(ProcessHandle, ModuleHandle, &ModuleInfo, sizeof(ModuleInfo))) + { + PRINT_PLATFORM_ERROR_MSG("Could not read GetModuleInformation"); + return false; + } + + IMAGEHLP_MODULE64 ImageHelpModule = { 0 }; + ImageHelpModule.SizeOfStruct = sizeof(ImageHelpModule); + if (!SymGetModuleInfo64(ProcessHandle, (DWORD64)ModuleInfo.EntryPoint, &ImageHelpModule)) + { + PRINT_PLATFORM_ERROR_MSG("Could not SymGetModuleInfo64 from module"); + return false; + } + + if (ImageHelpModule.SymType != SymDeferred && ImageHelpModule.SymType != SymNone) + { + return true; + } + + if (!GetModuleFileNameExW(ProcessHandle, ModuleHandle, ImageName, 1024)) + { + PRINT_PLATFORM_ERROR_MSG("Could not GetModuleFileNameExW"); + return false; + } + + if (!GetModuleBaseNameW(ProcessHandle, ModuleHandle, ModuleName, 1024)) + { + PRINT_PLATFORM_ERROR_MSG("Could not GetModuleBaseNameW"); + return false; + } + + WCHAR SearchPath[MAX_PATH] = { 0 }; + WCHAR* FileName = NULL; + const auto Result = GetFullPathNameW(ImageName, MAX_PATH, SearchPath, &FileName); + + FString SearchPathList; + if (Result != 0 && Result < MAX_PATH) + { + *FileName = 0; + SearchPathList = SearchPath; + } + + if (!RemoteStorage.IsEmpty()) + { + if (!SearchPathList.IsEmpty()) + { + SearchPathList.AppendChar(TEXT(';')); + } + SearchPathList.Append(RemoteStorage); + } + + if (!SymSetSearchPathW(ProcessHandle, *SearchPathList)) + { + PRINT_PLATFORM_ERROR_MSG("Could not SymSetSearchPathW"); + return false; + } + + const DWORD64 BaseAddress = SymLoadModuleExW( + ProcessHandle, + ModuleHandle, + ImageName, + ModuleName, + (DWORD64)ModuleInfo.lpBaseOfDll, + ModuleInfo.SizeOfImage, + NULL, + 0); + if (!BaseAddress) + { + PRINT_PLATFORM_ERROR_MSG("Could not load the module"); + return false; + } + + return true; +} + +bool UVisualStudioToolsBlueprintBreakpointExtension::GetFunctionDefinitionLocation(const FString& FunctionSymbolName, const FString& FunctionModuleName, FString& SourceFilePath, uint32& SourceLineNumber) +{ + const HANDLE ProcessHandle = GetCurrentProcess(); + HMODULE ModuleHandle = GetModuleHandle(*FunctionModuleName); + if (!ModuleHandle || !PreloadModule(ProcessHandle, ModuleHandle, FPlatformStackWalk::GetDownstreamStorage())) + { + return false; + } + + ANSICHAR SymbolInfoBuffer[sizeof(IMAGEHLP_SYMBOL64) + MAX_SYM_NAME]; + PIMAGEHLP_SYMBOL64 SymbolInfoPtr = reinterpret_cast(SymbolInfoBuffer); + SymbolInfoPtr->SizeOfStruct = sizeof(SymbolInfoBuffer); + SymbolInfoPtr->MaxNameLength = MAX_SYM_NAME; + + FString FullyQualifiedSymbolName = FunctionSymbolName; + if (!FunctionModuleName.IsEmpty()) + { + FullyQualifiedSymbolName = FString::Printf(TEXT("%s!%s"), *FunctionModuleName, *FunctionSymbolName); + } + + if (!SymGetSymFromName64(ProcessHandle, TCHAR_TO_ANSI(*FullyQualifiedSymbolName), SymbolInfoPtr)) + { + PRINT_PLATFORM_ERROR_MSG("Could not load module symbol information"); + return false; + } + + IMAGEHLP_LINE64 FileAndLineInfo; + FileAndLineInfo.SizeOfStruct = sizeof(FileAndLineInfo); + + uint32 SourceColumnNumber = 0; + if (!SymGetLineFromAddr64(ProcessHandle, SymbolInfoPtr->Address, (::DWORD*)&SourceColumnNumber, &FileAndLineInfo)) + { + PRINT_PLATFORM_ERROR_MSG("Could not query module file and line number"); + return false; + } + + SourceLineNumber = FileAndLineInfo.LineNumber; + SourceFilePath = FString((const ANSICHAR*)(FileAndLineInfo.FileName)); + return true; +} + +#endif + +bool UVisualStudioToolsBlueprintBreakpointExtension::GetFunctionDefinitionLocation(const UEdGraphNode* Node, FString& SourceFilePath, FString& SymbolName, uint32& SourceLineNumber) +{ + UClass* OwningClass; + UFunction* Function; + if (!CanAddVisualStudioBreakpoint(Node, &OwningClass, &Function)) + { + return false; + } + + FString ModuleName; + + // Find module name for class + if (!FSourceCodeNavigation::FindClassModuleName(OwningClass, ModuleName)) + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to find module name for class")); + return false; + } + + SymbolName = FString::Printf( + TEXT("%s%s::%s"), + OwningClass->GetPrefixCPP(), + *OwningClass->GetName(), + *Function->GetName()); + + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, TEXT("Symbol %s is defined in module %s"), *SymbolName, *ModuleName); + +#if ENGINE_MAJOR_VERSION >= 5 + uint32 SourceColumnNumber = 0; + return FPlatformStackWalk::GetFunctionDefinitionLocation( + SymbolName, + ModuleName, + SourceFilePath, + SourceLineNumber, + SourceColumnNumber); +#else + return GetFunctionDefinitionLocation(SymbolName, ModuleName, SourceFilePath, SourceLineNumber); +#endif +} + +bool UVisualStudioToolsBlueprintBreakpointExtension::GetProcessById(const TComPtr& Processes, DWORD CurrentProcessId, TComPtr& OutProcess) +{ + long Count = 0; + if (FAILED(Processes->get_Count(&Count))) + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Could not get the process count")); + return false; + } + + TComPtr Process; + for (long i = 1; i <= Count; i++) + { + VARIANT Index; + Index.vt = VT_I4; + Index.lVal = i; + if (SUCCEEDED(Processes->Item(Index, &Process))) + { + long PID = 0; + if (SUCCEEDED(Process->get_ProcessID(&PID)) && CurrentProcessId == PID) + { + OutProcess = Process; + return true; + } + + Process.Reset(); + } + } + + return true; +} + +void UVisualStudioToolsBlueprintBreakpointExtension::AttachDebuggerIfNecessary(const TComPtr& Debugger) +{ + TComPtr Processes; + if (FAILED(Debugger->get_DebuggedProcesses(&Processes))) + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to get debugging proccess")); + return; + } + + TComPtr Process; + DWORD CurrentProcessId = GetCurrentProcessId(); + if (!GetProcessById(Processes, CurrentProcessId, Process)) + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to check if UE is already in debug mode")); + return; + } + + // currently debugging this process + if (Process.Get() != nullptr) + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, TEXT("Already debugging UE.")); + return; + } + + Processes.Reset(); + if (FAILED(Debugger->get_LocalProcesses(&Processes))) + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to attach to process")); + return; + } + + Process.Reset(); + if (!GetProcessById(Processes, CurrentProcessId, Process)) + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to get all process")); + return; + } + + if (Process.Get() == nullptr) + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, TEXT("No UE proccess running.")); + return; + } + + if (FAILED(Process->Attach())) + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to attach to process")); + } +} + +bool UVisualStudioToolsBlueprintBreakpointExtension::SetVisualStudioBreakpoint(const UEdGraphNode* Node, const FString& SourceFilePath, const FString& SymbolName, uint32 SourceLineNumber) +{ + TComPtr DTE; + bool bBreakpointAdded = false; + if (!GetRunningVisualStudioDTE(DTE)) + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to access Visual Studio via DTE")); + return bBreakpointAdded; + } + + TComPtr Debugger; + TComPtr Breakpoints; + if (SUCCEEDED(DTE->get_Debugger(&Debugger)) && SUCCEEDED(Debugger->get_Breakpoints(&Breakpoints))) + { + FSmartBSTR BSTREmptyStr; + FSmartBSTR BSTRFilePath(SourceFilePath); + HRESULT Result = Breakpoints->Add( + *BSTREmptyStr, + *BSTRFilePath, + SourceLineNumber, + 1, + *BSTREmptyStr, + EnvDTE::dbgBreakpointConditionType::dbgBreakpointConditionTypeWhenTrue, + *BSTREmptyStr, + *BSTREmptyStr, + 0, + *BSTREmptyStr, + 0, + EnvDTE::dbgHitCountType::dbgHitCountTypeNone, + &Breakpoints); + + if (FAILED(Result)) + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to add breakpoint")); + } + else + { + bBreakpointAdded = true; + AttachDebuggerIfNecessary(Debugger); + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, TEXT("Breakpoint set for %s"), *SymbolName); + } + } + else + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to get debugger or breakpoints")); + } + + return bBreakpointAdded; +} + +void UVisualStudioToolsBlueprintBreakpointExtension::AddVisualStudioBreakpoint(const UEdGraphNode* Node) +{ + FWindowsPlatformMisc::CoInitialize(); + FPlatformStackWalk::InitStackWalking(); + FString SourceFilePath; + FString SymbolName; + uint32 SourceLineNumber; + bool bBreakpointAdded = false; + + if (GetFunctionDefinitionLocation(Node, SourceFilePath, SymbolName, SourceLineNumber)) + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, TEXT("Method defined in %s at line %d"), *SourceFilePath, SourceLineNumber); + bBreakpointAdded = SetVisualStudioBreakpoint(Node, SourceFilePath, SymbolName, SourceLineNumber); + } + else + { + UE_LOG(LogUVisualStudioToolsBlueprintBreakpointExtension, Error, TEXT("Failed to get function definition location")); + } + + ShowOperationResultNotification(bBreakpointAdded, SymbolName); + FWindowsPlatformMisc::CoUninitialize(); +} + +void UVisualStudioToolsBlueprintBreakpointExtension::ShowOperationResultNotification(bool bBreakpointAdded, const FString &SymbolName) +{ + FNotificationInfo Info(bBreakpointAdded ? FText::FromString(FString::Printf(TEXT("Breakpoint added at %s"), *SymbolName)) : FText::FromString("Could not add Breakpoint in Visual Studio")); +#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1 + Info.Image = FAppStyle::GetBrush(TEXT("LevelEditor.RecompileGameCode")); +#else + Info.Image = FEditorStyle::GetBrush(TEXT("LevelEditor.RecompileGameCode")); +#endif + Info.FadeInDuration = 0.1f; + Info.FadeOutDuration = 0.5f; + Info.ExpireDuration = 3.0f; + Info.bUseThrobber = false; + Info.bUseSuccessFailIcons = true; + Info.bUseLargeFont = true; + Info.bFireAndForget = false; + Info.bAllowThrottleWhenFrameRateIsLow = false; + Info.WidthOverride = 400.0f; + TSharedPtr NotificationItem = FSlateNotificationManager::Get().AddNotification(Info); + NotificationItem->SetCompletionState(bBreakpointAdded ? SNotificationItem::CS_Success : SNotificationItem::CS_Fail); + NotificationItem->ExpireAndFadeout(); +} diff --git a/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioToolsBlueprintBreakpointExtension.h b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioToolsBlueprintBreakpointExtension.h new file mode 100644 index 0000000..4e60234 --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioToolsBlueprintBreakpointExtension.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "VisualStudioToolsBlueprintBreakpointExtension.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogUVisualStudioToolsBlueprintBreakpointExtension, Log, All); + +UCLASS() +class UVisualStudioToolsBlueprintBreakpointExtension : public UEditorSubsystem +{ + GENERATED_BODY() + +public: + DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnNodeMenuExtensionHookRequestDelegate, const UEdGraphNode*, const UEdGraph*, TSet&); + + void Initialize(FSubsystemCollectionBase& Collection) override; + void Deinitialize() override; + + FOnNodeMenuExtensionHookRequestDelegate& OnNodeMenuExtensionHookRequest() { return OnNodeMenuExtensionHookRequestDelegate; } + +private: + FOnNodeMenuExtensionHookRequestDelegate OnNodeMenuExtensionHookRequestDelegate; + + TSharedRef HandleOnExtendGraphEditorContextMenu( + const TSharedRef CommandList, + const UEdGraph* Graph, + const UEdGraphNode* Node, + const UEdGraphPin* Pin, + bool bIsConst); + + void AddVisualStudioBlueprintBreakpointMenuOption(FMenuBuilder& MenuBuilder, const UEdGraphNode* node); + + void AddVisualStudioBreakpoint(const UEdGraphNode* Node); + + bool GetFunctionDefinitionLocation(const UEdGraphNode* Node, FString& SourceFilePath, FString& SymbolName, uint32& SourceLineNumber); + + bool SetVisualStudioBreakpoint(const UEdGraphNode* Node, const FString& SourceFilePath, const FString& SymbolName, uint32 SourceLineNumber); + + bool CanAddVisualStudioBreakpoint(const UEdGraphNode* Node, UClass** OutOwnerClass, UFunction** OutFunction); + + void ShowOperationResultNotification(bool bBreakpointAdded, const FString& SymbolName); + + FString GetProjectPath(const FString& ProjectDir); + + bool GetRunningVisualStudioDTE(TComPtr& OutDTE); + + void AttachDebuggerIfNecessary(const TComPtr& Debugger); + + bool GetProcessById(const TComPtr& Processes, DWORD CurrentProcessId, TComPtr& OutProcess); + +#if ENGINE_MAJOR_VERSION < 5 + bool PreloadModule(HANDLE ProcessHandle, HMODULE ModuleHandle, const FString& RemoteStorage); + + bool GetFunctionDefinitionLocation(const FString& FunctionSymbolName, const FString& FunctionModuleName, FString& SourceFilePath, uint32& SourceLineNumber); +#endif +}; diff --git a/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioToolsCommandlet.cpp b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioToolsCommandlet.cpp new file mode 100644 index 0000000..b3d367e --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioToolsCommandlet.cpp @@ -0,0 +1,492 @@ +// Copyright 2022 (c) Microsoft. All rights reserved. + +#include "VisualStudioToolsCommandlet.h" + +#include "Algo/Transform.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "Blueprint/BlueprintSupport.h" +#include "BlueprintAssetHelpers.h" +#include "Engine/BlueprintGeneratedClass.h" +#include "JsonObjectConverter.h" +#include "Misc/Paths.h" +#include "Misc/ScopeExit.h" +#include "Policies/CondensedJsonPrintPolicy.h" +#include "SourceCodeNavigation.h" +#include "UObject/CoreRedirects.h" +#include "UObject/UObjectIterator.h" +#include "VisualStudioTools.h" + +namespace VisualStudioTools +{ +static const FName CategoryFName = TEXT("Category"); +static const FName ModuleNameFName = TEXT("ModuleName"); + +static TArray GetChangedPropertiesList( + UStruct* InStruct, const uint8* DataPtr, const uint8* DefaultDataPtr) +{ + TArray Result; + + const UClass* OwnerClass = Cast(InStruct); + + // Walk only in the properties defined in the current class, the super classes are processed individually + for (TFieldIterator It(OwnerClass, EFieldIteratorFlags::ExcludeSuper); It; ++It) + { + FProperty* Property = *It; + for (int32 Idx = 0; Idx < Property->ArrayDim; Idx++) + { + const uint8* PropertyValue = Property->ContainerPtrToValuePtr(DataPtr, Idx); + const uint8* DefaultPropertyValue = Property->ContainerPtrToValuePtrForDefaults(InStruct, DefaultDataPtr, Idx); + + if (!Property->Identical(PropertyValue, DefaultPropertyValue)) + { + Result.Add(Property); + break; + } + } + } + + return Result; +} + +static bool FindBlueprintNativeParents( + const UClass* BlueprintGeneratedClass, TFunctionRef Callback) +{ + bool bAnyNativeParent = false; + for (UClass* Super = BlueprintGeneratedClass->GetSuperClass(); Super; Super = Super->GetSuperClass()) + { + // Ignore the root `UObject` class and non-native parents. + if (Super->HasAnyClassFlags(CLASS_Native) && Super->GetFName() != NAME_Object) + { + bAnyNativeParent = true; + Callback(Super); + } + } + + return bAnyNativeParent; +} + +struct FPropertyEntry +{ + FProperty* Property; + TArray Blueprints; +}; + +struct FFunctionEntry +{ + UFunction* Function; + TArray Blueprints; +}; + +struct FClassEntry +{ + const UClass* Class; + TArray Blueprints; + TMap Properties; + TMap Functions; +}; + +using ClassMap = TMap; + +struct FAssetIndex +{ + TSet AssetPathCache; + ClassMap Classes; + TArray Blueprints; + + void ProcessBlueprint(const UBlueprintGeneratedClass* BlueprintGeneratedClass) + { + if (BlueprintGeneratedClass == nullptr) + { + return; + } + + int32 BlueprintIndex = Blueprints.Num(); + + bool bHasAnyParent = FindBlueprintNativeParents(BlueprintGeneratedClass, [&](UClass* Parent) + { + FString ParentName = Parent->GetFName().ToString(); + if (!Classes.Contains(ParentName)) + { + Classes.Add(ParentName).Class = Parent; + } + + FClassEntry& ClassEntry = Classes[ParentName]; + + ClassEntry.Blueprints.Add(BlueprintIndex); + + // Retrieve the properties from the parent class that changed in the Blueprint class, by comparing their CDOs. + UObject* GeneratedClassDefault = BlueprintGeneratedClass->ClassDefaultObject; + UObject* SuperClassDefault = Parent->GetDefaultObject(false); + TArray ChangedProperties = GetChangedPropertiesList(Parent, (uint8*)GeneratedClassDefault, (uint8*)SuperClassDefault); + + for (FProperty* Property : ChangedProperties) + { + FString PropertyName = Property->GetFName().ToString(); + if (!ClassEntry.Properties.Contains(PropertyName)) + { + ClassEntry.Properties.Add(PropertyName).Property = Property; + } + + FPropertyEntry& PropEntry = ClassEntry.Properties[PropertyName]; + PropEntry.Blueprints.Add(BlueprintIndex); + } + + // Iterate over the functions originally from the parent class + // and check if they are implemented in the BP class as well. + for (TFieldIterator It(Parent, EFieldIteratorFlags::ExcludeSuper); It; ++It) + { + UFunction* Fn = BlueprintGeneratedClass->FindFunctionByName((*It)->GetFName(), EIncludeSuperFlag::ExcludeSuper); + // If the function not present in the BP class directly, it means it was implemented. Otherwise, ignore. + if (!Fn) + { + continue; + } + + FString FnName = Fn->GetFName().ToString(); + if (!ClassEntry.Functions.Contains(FnName)) + { + ClassEntry.Functions.Add(FnName).Function = Fn; + } + + FFunctionEntry& FuncEntry = ClassEntry.Functions[FnName]; + FuncEntry.Blueprints.Add(BlueprintIndex); + } + }); + + if (bHasAnyParent) + { + check(Blueprints.Add(BlueprintGeneratedClass) == BlueprintIndex); + } + + return; + } +}; + +using JsonWriter = TJsonWriter>; + +static bool ShouldSerializePropertyValue(FProperty* Property) +{ + if (Property->ArrayDim > 1) // Skip properties that are not scalars + { + return false; + } + + if (FEnumProperty* EnumProperty = CastField(Property)) + { + return true; + } + + if (FNumericProperty* NumericProperty = CastField(Property)) + { + UEnum* EnumDef = NumericProperty->GetIntPropertyEnum(); + if (EnumDef != NULL) + { + return true; + } + + if (NumericProperty->IsFloatingPoint()) + { + return true; + } + + if (NumericProperty->IsInteger()) + { + return true; + } + } + + if (FBoolProperty* BoolProperty = CastField(Property)) + { + return true; + } + + if (FStrProperty* StringProperty = CastField(Property)) + { + return true; + } + + return false; +} + +static void SerializeBlueprints(TSharedRef& Json, TArray Items) +{ + Json->WriteArrayStart(); + for (const UClass* Blueprint : Items) + { + Json->WriteObjectStart(); + + Json->WriteValue(TEXT("name"), Blueprint->GetName()); + Json->WriteValue(TEXT("path"), Blueprint->GetPathName()); + Json->WriteObjectEnd(); + } + Json->WriteArrayEnd(); +} + +static void SerializeProperties(TSharedRef& Json, FClassEntry& Entry, TArray& Blueprints) +{ + Json->WriteArrayStart(); + for (auto& Item : Entry.Properties) + { + auto& PropName = Item.Key; + auto& PropEntry = Item.Value; + FProperty* Property = PropEntry.Property; + + Json->WriteObjectStart(); + + Json->WriteValue(TEXT("name"), PropName); + + Json->WriteIdentifierPrefix(TEXT("metadata")); + { + Json->WriteObjectStart(); + if (Property->HasMetaData(CategoryFName)) + { + Json->WriteValue(TEXT("categories"), Property->GetMetaData(CategoryFName)); + } + Json->WriteObjectEnd(); + } + + Json->WriteIdentifierPrefix(TEXT("values")); + { + Json->WriteArrayStart(); + for (auto& BlueprintEntry : PropEntry.Blueprints) + { + Json->WriteObjectStart(); + + Json->WriteValue(TEXT("blueprint"), BlueprintEntry); + + UObject* GeneratedClassDefault = Blueprints[BlueprintEntry]->ClassDefaultObject; + const uint8* PropData = PropEntry.Property->ContainerPtrToValuePtr(GeneratedClassDefault); + + if (ShouldSerializePropertyValue(PropEntry.Property)) + { + TSharedPtr JsonValue = FJsonObjectConverter::UPropertyToJsonValue(Property, PropData); + FJsonSerializer::Serialize(JsonValue.ToSharedRef(), TEXT("value"), Json); + } + + Json->WriteObjectEnd(); + } + Json->WriteArrayEnd(); + } + + Json->WriteObjectEnd(); + } + Json->WriteArrayEnd(); +} + +static void SerializeFunctions(TSharedRef& Json, FClassEntry& Entry) +{ + Json->WriteArrayStart(); + for (auto& Item : Entry.Functions) + { + auto& Name = Item.Key; + auto& FnEntry = Item.Value; + Json->WriteObjectStart(); + Json->WriteValue(TEXT("name"), Name); + Json->WriteValue(TEXT("blueprints"), FnEntry.Blueprints); + Json->WriteObjectEnd(); + } + Json->WriteArrayEnd(); +} + +static void SerializeClasses(TSharedRef& Json, ClassMap& Items, TArray Blueprints) +{ + Json->WriteArrayStart(); + for (auto& Item : Items) + { + auto& ClassName = Item.Key; + auto& Entry = Item.Value; + Json->WriteObjectStart(); + Json->WriteValue(TEXT("name"), FString::Printf(TEXT("%s%s"), Entry.Class->GetPrefixCPP(), *Entry.Class->GetName())); + + Json->WriteValue(TEXT("blueprints"), Entry.Blueprints); + + Json->WriteIdentifierPrefix(TEXT("properties")); + SerializeProperties(Json, Entry, Blueprints); + + Json->WriteIdentifierPrefix(TEXT("functions")); + SerializeFunctions(Json, Entry); + + Json->WriteObjectEnd(); + } + Json->WriteArrayEnd(); +} + +static void SerializeToIndex(FAssetIndex Index, FArchive& IndexFile) +{ + TSharedRef Json = JsonWriter::Create(&IndexFile); + + Json->WriteObjectStart(); + + Json->WriteIdentifierPrefix(TEXT("blueprints")); + SerializeBlueprints(Json, Index.Blueprints); + + Json->WriteIdentifierPrefix(TEXT("classes")); + SerializeClasses(Json, Index.Classes, Index.Blueprints); + + Json->WriteObjectEnd(); + Json->Close(); +} + +static TArray GetModulesByPath(const FString& InDir) +{ + TArray OutResult; + Algo::TransformIf( + FSourceCodeNavigation::GetSourceFileDatabase().GetModuleNames(), + OutResult, + [&](const FString& Module) { + return FPaths::IsUnderDirectory(Module, InDir); + }, + [](const FString& Module) { +#if 0 + // Old version assumes that each module is in a folder with the same name as the module + return FPaths::GetBaseFilename(FPaths::GetPath(*Module)); +#else + // New version assumes that each module is in a file with the name Module.Build.cs + FString TempString = FPaths::GetBaseFilename(*Module); + TempString.RemoveFromEnd(TEXT(".Build")); + return TempString; +#endif + }); + + return OutResult; +} + +static void GetNativeClassesByPath(const FString& InDir, TArray>& OutClasses) +{ + TArray Modules = GetModulesByPath(InDir); + + for (TObjectIterator ClassIt; ClassIt; ++ClassIt) + { + UClass* TestClass = *ClassIt; + if (!TestClass->HasAnyClassFlags(CLASS_Native)) + { + continue; + } + + FAssetData ClassAssetData(TestClass); + FString ModuleName = ClassAssetData.GetTagValueRef(ModuleNameFName); + + if (!ModuleName.IsEmpty() && Modules.Contains(ModuleName)) + { + OutClasses.Add(TestClass); + } + } +} + +static void RunAssetScan( + FAssetIndex& Index, + const TArray>& FilterBaseClasses) +{ + FARFilter Filter; + Filter.bRecursivePaths = true; + Filter.bRecursiveClasses = true; + AssetHelpers::SetBlueprintClassFilter(Filter); + + // Add all base classes to the tag filter for native parent + Algo::Transform(FilterBaseClasses, Filter.TagsAndValues, [](const TWeakObjectPtr& Class) { + return MakeTuple( + FBlueprintTags::NativeParentClassPath, + FObjectPropertyBase::GetExportPath(Class.Get(), nullptr /*Parent*/, nullptr /*ExportRootScope*/, 0 /*PortFlags*/)); + }); + + // Take account of any core redirects for the blueprint classes we want to scan. + for (const auto& BaseClass : FilterBaseClasses) + { + if (BaseClass.IsValid()) + { + TArray PreviousNames; + if (FCoreRedirects::FindPreviousNames(ECoreRedirectFlags::Type_Class, BaseClass->GetPathName(), PreviousNames)) + { + for (const auto& PreviousName : PreviousNames) + { + // FString PreviousString = FObjectPropertyBase::GetExportPath(BaseClass->GetClass()->GetClassPathName(), PreviousName.ToString()); // Alternative way to add /Script/CoreUObject.Class'' wrapper - but not sure it makes sense to use the new class when referencing a previous name + FString PreviousString = "/Script/CoreUObject.Class'" + PreviousName.ToString() + "'"; + Filter.TagsAndValues.Add(FBlueprintTags::NativeParentClassPath, PreviousString); + } + } + } + } + + IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")).Get(); + + TArray TargetAssets; + AssetRegistry.GetAssets(Filter, TargetAssets); + + AssetHelpers::ForEachAsset(TargetAssets, + [&](UBlueprintGeneratedClass* BlueprintGeneratedClass, const FAssetData& /*AssetData*/) + { + Index.ProcessBlueprint(BlueprintGeneratedClass); + }); +} + +} // namespace VS + +static constexpr auto FilterSwitch = TEXT("filter"); +static constexpr auto FullSwitch = TEXT("full"); + +UVisualStudioToolsCommandlet::UVisualStudioToolsCommandlet() + : Super() +{ + HelpDescription = TEXT("Commandlet for generating data used by Blueprint support in Visual Studio."); + + HelpParamNames.Add(FilterSwitch); + HelpParamDescriptions.Add(TEXT("[Optional] Scan only blueprints derived from native classes under the provided path. Defaults to `FPaths::ProjectDir`. Incompatible with `-full`.")); + + HelpParamNames.Add(FullSwitch); + HelpParamDescriptions.Add(TEXT("[Optional] Scan blueprints derived from native classes from ALL modules, include the Engine. This can be _very slow_ for large projects. Incompatible with `-filter`.")); + + HelpUsage = TEXT(" -run=VisualStudioTools -output= [-filter=|-full] [-unattended -noshadercompile -nosound -nullrhi -nocpuprofilertrace -nocrashreports -nosplash]"); +} + +int32 UVisualStudioToolsCommandlet::Run( + TArray& Tokens, + TArray& Switches, + TMap& ParamVals, + FArchive& OutArchive) +{ + using namespace VisualStudioTools; + + FString* Filter = ParamVals.Find(FilterSwitch); + const bool bFullScan = Switches.Contains(FullSwitch); + + if (Filter != nullptr && bFullScan) + { + UE_LOG(LogVisualStudioTools, Error, TEXT("Incompatible scan options.")); + PrintHelp(); + return -1; + } + + TArray> FilterBaseClasses; + if (!bFullScan) + { + if (Filter) + { + FPaths::NormalizeDirectoryName(*Filter); + GetNativeClassesByPath(*Filter, FilterBaseClasses); + } + else + { + GetNativeClassesByPath(FPaths::ProjectDir(), FilterBaseClasses); + } + } + else + { + for (TObjectIterator ClassIt; ClassIt; ++ClassIt) + { + UClass* TestClass = *ClassIt; + if (!TestClass->HasAnyClassFlags(CLASS_Native)) + { + continue; + } + + FilterBaseClasses.Add(TestClass); + } + } + + FAssetIndex Index; + RunAssetScan(Index, FilterBaseClasses); + SerializeToIndex(Index, OutArchive); + UE_LOG(LogVisualStudioTools, Display, TEXT("Found %d blueprints."), Index.Blueprints.Num()); + + return 0; +} diff --git a/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioToolsCommandlet.h b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioToolsCommandlet.h new file mode 100644 index 0000000..05ad18d --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioToolsCommandlet.h @@ -0,0 +1,24 @@ +// Copyright 2022 (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include "CoreMinimal.h" +#include "VisualStudioToolsCommandletBase.h" + +#include "VisualStudioToolsCommandlet.generated.h" + +UCLASS() +class UVisualStudioToolsCommandlet + : public UVisualStudioToolsCommandletBase +{ + GENERATED_BODY() + +public: + UVisualStudioToolsCommandlet(); + int32 Run( + TArray& Tokens, + TArray& Switches, + TMap& ParamVals, + FArchive& OutArchive) override; +}; diff --git a/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioToolsCommandletBase.cpp b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioToolsCommandletBase.cpp new file mode 100644 index 0000000..716101a --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioToolsCommandletBase.cpp @@ -0,0 +1,88 @@ +// Copyright 2022 (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +#include "VisualStudioToolsCommandletBase.h" + +#include "Windows/AllowWindowsPlatformTypes.h" + +#include "HAL/FileManager.h" +#include "Misc/Paths.h" +#include "VisualStudioTools.h" + +#include "Windows/HideWindowsPlatformTypes.h" + +static constexpr auto HelpSwitch = TEXT("help"); +static constexpr auto OutputSwitch = TEXT("output"); + +UVisualStudioToolsCommandletBase::UVisualStudioToolsCommandletBase() +{ + IsClient = false; + IsEditor = true; + IsServer = false; + LogToConsole = false; + ShowErrorCount = false; + + HelpParamNames.Add(OutputSwitch); + HelpParamDescriptions.Add(TEXT("[Required] The file path to write the command output.")); + + HelpParamNames.Add(HelpSwitch); + HelpParamDescriptions.Add(TEXT("[Optional] Print this help message and quit the commandlet immediately.")); +} + +void UVisualStudioToolsCommandletBase::PrintHelp() const +{ + UE_LOG(LogVisualStudioTools, Display, TEXT("%s"), *HelpDescription); + UE_LOG(LogVisualStudioTools, Display, TEXT("Usage: %s"), *HelpUsage); + UE_LOG(LogVisualStudioTools, Display, TEXT("Parameters:")); + for (int32 i = 0; i < HelpParamNames.Num(); ++i) + { + UE_LOG(LogVisualStudioTools, Display, TEXT("\t-%s: %s"), *HelpParamNames[i], *HelpParamDescriptions[i]); + } +} + +int32 UVisualStudioToolsCommandletBase::Main(const FString& Params) +{ + TArray Tokens; + TArray Switches; + TMap ParamVals; + + ParseCommandLine(*Params, Tokens, Switches, ParamVals); + + if (Switches.Contains(HelpSwitch)) + { + PrintHelp(); + return 0; + } + + UE_LOG(LogVisualStudioTools, Display, TEXT("Init VS Tools cmdlet.")); + + if (!FPaths::IsProjectFilePathSet()) + { + UE_LOG(LogVisualStudioTools, Error, TEXT("You must invoke this commandlet with a project file.")); + return -1; + } + + FString FullPath = ParamVals.FindRef(OutputSwitch); + + if (FullPath.IsEmpty() && !FParse::Value(*Params, TEXT("output "), FullPath)) + { + // VS:1678426 - Initial version was using `-output "path-to-file"` (POSIX style). + // However, that does not support paths with spaces, even when surrounded with + // quotes because `FParse::Value` only handles that case when there's no space + // between the parameter name and quoted value. + // For back-compatibility reasons, parse that style by including the space in + // the parameter token like it's usually done for the `=` sign. + UE_LOG(LogVisualStudioTools, Error, TEXT("Missing file output parameter.")); + PrintHelp(); + return -1; + } + + TUniquePtr OutArchive{ IFileManager::Get().CreateFileWriter(*FullPath) }; + if (!OutArchive) + { + UE_LOG(LogVisualStudioTools, Error, TEXT("Failed to create index with path: %s."), *FullPath); + return -1; + } + + return this->Run(Tokens, Switches, ParamVals, *OutArchive); +} diff --git a/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioToolsCommandletBase.h b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioToolsCommandletBase.h new file mode 100644 index 0000000..7c370ef --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioTools/Private/VisualStudioToolsCommandletBase.h @@ -0,0 +1,29 @@ +// Copyright 2022 (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include "Commandlets/Commandlet.h" + +#include "VisualStudioToolsCommandletBase.generated.h" + +UCLASS(Abstract) +class UVisualStudioToolsCommandletBase + : public UCommandlet +{ + GENERATED_BODY() + +public: + int32 Main(const FString& Params) override; + +protected: + UVisualStudioToolsCommandletBase(); + + void PrintHelp() const; + + virtual int32 Run( + TArray& Tokens, + TArray& Switches, + TMap& ParamVals, + FArchive& OutArchive) PURE_VIRTUAL(UVisualStudioToolsCommandletBase::Run, return 0;); +}; \ No newline at end of file diff --git a/Plugins/VisualStudioTools/Source/VisualStudioTools/Public/VisualStudioTools.h b/Plugins/VisualStudioTools/Source/VisualStudioTools/Public/VisualStudioTools.h new file mode 100644 index 0000000..0ffe4e9 --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioTools/Public/VisualStudioTools.h @@ -0,0 +1,7 @@ +// Copyright 2022 (c) Microsoft. All rights reserved. + +#pragma once + +#include "CoreMinimal.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogVisualStudioTools, Log, All); \ No newline at end of file diff --git a/Plugins/VisualStudioTools/Source/VisualStudioTools/VisualStudioTools.Build.cs b/Plugins/VisualStudioTools/Source/VisualStudioTools/VisualStudioTools.Build.cs new file mode 100644 index 0000000..da2b217 --- /dev/null +++ b/Plugins/VisualStudioTools/Source/VisualStudioTools/VisualStudioTools.Build.cs @@ -0,0 +1,77 @@ +// Copyright 2022 (c) Microsoft. All rights reserved. + +using UnrealBuildTool; + +public class VisualStudioTools : ModuleRules +{ + public VisualStudioTools(ReadOnlyTargetRules Target) : base(Target) + { + bool bIsCustomDevBuild = System.Environment.GetEnvironmentVariable("VSTUE_IsCustomDevBuild") == "1"; + if (bIsCustomDevBuild) + { + // Get correct header suggestions in the IDE and validate + // the plugin build without having to affect for the whole target, + // which is expensive in source-builds of the engine. + PCHUsage = ModuleRules.PCHUsageMode.NoPCHs; + bUseUnity = false; + + // When debugging the commandlet code, disable optimizations to get + // proper local variable inspection and less inlined stack frames + OptimizeCode = CodeOptimization.Never; + + // Enable more restrict warnings during compilation in UE5. + // Required by tasks in the compliance pipeline. + if (Target.Version.MajorVersion >= 5) + { + UnsafeTypeCastWarningLevel = WarningLevel.Error; + } + } + else + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + } + + // To support UE5.1+, the code is using the new FTopLevelAssetPath API + // with a detection of support via version numbers. + // If the check is producing a false positive/negative in your version of the engine + // you can change the block below and force the check as enabled/disabled. + if ((Target.Version.MajorVersion == 5 && Target.Version.MinorVersion >= 1) || Target.Version.MajorVersion > 5) + { + PrivateDefinitions.Add("FILTER_ASSETS_BY_CLASS_PATH=1"); + } + else + { + PrivateDefinitions.Add("FILTER_ASSETS_BY_CLASS_PATH=0"); + } + + PublicDependencyModuleNames.AddRange( + new[] + { + "Core", + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "ApplicationCore", + "AssetRegistry", + "CoreUObject", + "Engine", + "Json", + "JsonUtilities", + "Kismet", + "UnrealEd", + "Slate", + "SlateCore", + "ToolMenus", + "EditorSubsystem", + "MainFrame", + "BlueprintGraph", + "VisualStudioDTE", + "EditorStyle", + "Projects" + } + ); + } +} diff --git a/Plugins/VisualStudioTools/VisualStudioTools.uplugin b/Plugins/VisualStudioTools/VisualStudioTools.uplugin new file mode 100644 index 0000000..b9bb932 --- /dev/null +++ b/Plugins/VisualStudioTools/VisualStudioTools.uplugin @@ -0,0 +1,38 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "2.7", + "FriendlyName": "Visual Studio Integration Tools", + "Description": "Enables integration with Visual Studio IDE.", + "Category": "Programming", + "CreatedBy": "Microsoft", + "CreatedByURL": "http://www.microsoft.com", + "DocsURL": "", + "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/362651520df94e4fa65492dbcba44ae2", + "SupportURL": "https://developercommunity.visualstudio.com/", + "EnabledByDefault": true, + "Installed": false, + "bExplicitlyLoaded": true, + "CanContainContent": false, + "SupportedTargetPlatforms": [ + "Win64" + ], + "Modules": [ + { + "Name": "VisualStudioTools", + "Type": "Editor", + "LoadingPhase": "Default", + "PlatformAllowList": [ + "Win64" + ] + }, + { + "Name": "VisualStudioBlueprintDebuggerHelper", + "Type": "Editor", + "LoadingPhase": "None", + "PlatformAllowList": [ + "Win64" + ] + } + ] +} diff --git a/Plugins/VisualStudioTools/azure-pipelines/Support-extra-UBT-args-in-UAT.BuildPlugin.patch b/Plugins/VisualStudioTools/azure-pipelines/Support-extra-UBT-args-in-UAT.BuildPlugin.patch new file mode 100644 index 0000000..9d1a396 --- /dev/null +++ b/Plugins/VisualStudioTools/azure-pipelines/Support-extra-UBT-args-in-UAT.BuildPlugin.patch @@ -0,0 +1,46 @@ +From f7238064c8680f6392793eb664ee2c773daff594 Mon Sep 17 00:00:00 2001 +From: Oleksandr Kozlov +Date: Tue, 1 Apr 2025 15:22:14 +0200 +Subject: [PATCH] Support extra UBT args in UAT.BuildPlugin + +Forwarding extra parameters to UBT to allow customizing the build of a plugin. +Example: runuat.bat buildpluing -plugin=... -ubtargs="-LinkerArguments=\"/profile\"" + +--- + .../Scripts/BuildPluginCommand.Automation.cs | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +diff --git a/Engine/Source/Programs/AutomationTool/Scripts/BuildPluginCommand.Automation.cs b/Engine/Source/Programs/AutomationTool/Scripts/BuildPluginCommand.Automation.cs +index 5a43dc0c4..aaf3f192f 100644 +--- a/Engine/Source/Programs/AutomationTool/Scripts/BuildPluginCommand.Automation.cs ++++ b/Engine/Source/Programs/AutomationTool/Scripts/BuildPluginCommand.Automation.cs +@@ -64,6 +64,9 @@ public sealed class BuildPlugin : BuildCommand + // Option for verifying that all include directive s + bool bStrictIncludes = ParseParam("StrictIncludes"); + ++ // Extra arguments forwarded to UBT ++ string UBTArgs = ParseParamValue("ubtargs"); ++ + // Make sure the packaging directory is valid + DirectoryReference PackageDir = new DirectoryReference(PackageParam); + +@@ -126,6 +129,16 @@ public sealed class BuildPlugin : BuildCommand + AdditionalArgs.Append(" -NoPCH -NoSharedPCH -DisableUnity"); + } + ++ // Pass extra parameters to UBT ++ if (string.IsNullOrEmpty(UBTArgs) == false) ++ { ++ Logger.LogInformation("Building with extra UBT parameters: {UBTArgs}", UBTArgs); ++ string Arg = UBTArgs; ++ Arg = Arg.TrimStart(new char[] { '\"' }); ++ Arg = Arg.TrimEnd(new char[] { '\"' }); ++ AdditionalArgs.Append(' ').Append(Arg); ++ } ++ + // check if any architectures were specified + foreach (UnrealTargetPlatform Platform in UnrealTargetPlatform.GetValidPlatforms()) + { +-- +2.49.0.windows.1 + diff --git a/Plugins/VisualStudioTools/azure-pipelines/TSAOptions.json b/Plugins/VisualStudioTools/azure-pipelines/TSAOptions.json new file mode 100644 index 0000000..54198e1 --- /dev/null +++ b/Plugins/VisualStudioTools/azure-pipelines/TSAOptions.json @@ -0,0 +1,21 @@ +{ + "tsaVersion": "TsaV2", + "codebase": "NewOrUpdate", + "codebaseName": "vc-ue-extensions", + "tsaStamp": "DevDiv", + "tsaEnvironment": "PROD", + "notificationAliases": [ + "hebaggio@microsoft.com", + "cpp-apogee@microsoft.com" + ], + "codebaseAdmins": [ + "REDMOND\\hebaggio", + "REDMOND\\cpp-apogee" + ], + "instanceUrl": "https://devdiv.visualstudio.com", + "projectName": "DevDiv", + "areaPath": "DevDiv\\Cpp Developer Experience\\GameDev Experience\\Unreal Engine integrations", + "iterationPath": "DevDiv", + "alltools": true, + "repositoryName": "vc-ue-extensions" + } \ No newline at end of file diff --git a/Plugins/VisualStudioTools/azure-pipelines/compliance.yml b/Plugins/VisualStudioTools/azure-pipelines/compliance.yml new file mode 100644 index 0000000..82aee67 --- /dev/null +++ b/Plugins/VisualStudioTools/azure-pipelines/compliance.yml @@ -0,0 +1,123 @@ +# IMPORTANT: +# Do not run BinSkim because we do not control producing the binaries. That process is owned by +# Epic. We just provide source code. Since we do not control the build, BinSkim is not needed. + +variables: + Codeql.Enabled: true + Codeql.SourceRoot: '$(Build.SourcesDirectory)' + +trigger: +- main + +pr: + autoCancel: true + branches: + include: + - main + - dev/* + +schedules: + - cron: "0 12 * * Sun" + displayName: Weekly run + branches: + include: + - main + always: true + +resources: + repositories: + - repository: 1ESPipelineTemplates + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release + +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates + parameters: + pool: + name: VSEngSS-MicroBuild2022-1ES + os: windows + customBuildTags: + - ES365AIMigrationTooling + stages: + - stage: stage + jobs: + - job: 'UnrealEngine_VisualStudioTools_Compliance' + timeoutInMinutes: 1440 + steps: + - task: CmdLine@2 + displayName: 'Clone Unreal Engine Repository' + inputs: + script: 'git clone "https://$(token)@github.com/EpicGames/UnrealEngine" --single-branch --branch $(ue_branch) --depth 1 ue' + workingDirectory: '$(Agent.BuildDirectory)' + - task: CmdLine@2 + displayName: 'Apply patch to allow us to pass linker flags to the build' + inputs: + script: 'git apply --ignore-whitespace $(Build.SourcesDirectory)/azure-pipelines/Support-extra-UBT-args-in-UAT.BuildPlugin.patch' + workingDirectory: '$(Agent.BuildDirectory)\ue' + - task: PowerShell@2 + displayName: '[UE] Append /unattended option' + inputs: + targetType: 'inline' + script: + $filePath = "$(Agent.BuildDirectory)/ue/Setup.bat"; + (Get-Content $filePath).Replace("/register", "/register /unattended") | Set-Content $filePath + - task: CmdLine@2 + displayName: '[UE] Run Setup.bat' + inputs: + script: 'ue\Setup.bat --force' + workingDirectory: '$(Agent.BuildDirectory)' + - task: MSBuild@1 + displayName: 'Build Plugin' + timeoutInMinutes: 300 + inputs: + solution: '$(Build.SourcesDirectory)/build.proj' + msbuildArguments: '/p:UnrealEngine=$(Agent.BuildDirectory)\ue /p:OutputPath=$(Build.ArtifactStagingDirectory)\drop /p:VulkanReadyBinaries=true' + createLogFile: true + - task: CopyFiles@2 + displayName: 'Collect binaries to analyze' + inputs: + SourceFolder: '$(Build.ArtifactStagingDirectory)\drop' + Contents: '**\unrealeditor-visualstudiotools.*' + TargetFolder: '$(Build.ArtifactStagingDirectory)\binariesToAnalyze' + CleanTargetFolder: true + OverWrite: true + - task: PoliCheck@2 + inputs: + targetType: 'F' + targetArgument: '$(Build.SourcesDirectory)' + - task: ComponentGovernanceComponentDetection@0 + inputs: + ignoreDirectories: '$(Agent.BuildDirectory)\ue' + displayName: 'Component Detection' + - task: APIScan@2 + displayName: 'Run APIScan' + inputs: + softwareFolder: '$(Build.ArtifactStagingDirectory)/binariesToAnalyze' + softwareName: 'Visual Studio Tools for Unreal Engine' + softwareVersionNum: '2.4' + softwareBuildNum: '$(Build.BuildId)' + toolVersion: 'Latest' + env: + AzureServicesAuthConnectionString: runAs=App;AppId=$(ApiScanClientId) + - task: SDLNativeRules@3 + displayName: 'Run the PREfast SDL Native Rules for MSBuild' + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + inputs: + publishXML: true + userProvideBuildInfo: auto + rulesetName: Recommended + setupCommandlinePicker: 'vs2022' + - task: PublishSecurityAnalysisLogs@3 + displayName: 'Publish security analysis logs' + inputs: + ArtifactName: 'CodeAnalysisLogs' + ArtifactType: 'Container' + AllTools: true + ToolLogsNotFoundAction: 'Standard' + - task: TSAUpload@2 + displayName: 'TSA upload' + inputs: + GdnPublishTsaOnboard: True + GdnPublishTsaConfigFile: '$(Build.SourcesDirectory)/azure-pipelines/TSAOptions.json' diff --git a/Plugins/VisualStudioTools/azure-pipelines/release.yml b/Plugins/VisualStudioTools/azure-pipelines/release.yml new file mode 100644 index 0000000..bf04be1 --- /dev/null +++ b/Plugins/VisualStudioTools/azure-pipelines/release.yml @@ -0,0 +1,117 @@ +# IMPORTANT: +# Do not run BinSkim because we do not control producing the binaries. That process is owned by +# Epic. We just provide source code. Since we do not control the build, BinSkim is not needed. + +# Manual trigger for now +trigger: none +pr: none + +parameters: +- name: SignTypeOverride + displayName: Signing Type (default is real for the main branch and test otherwise) + type: string + default: default + values: + - default + - test + - real + +variables: + # MicroBuild requires TeamName to be set. + - name: TeamName + value: C++ Unreal Engine + - name: TagName + value: $[replace(variables['Build.SourceBranch'], 'refs/tags/', '')] + # If the user didn't override the signing type, then only real-sign on main. + - ${{ if ne(parameters.SignTypeOverride, 'default') }}: + - name: SignType + value: ${{ parameters.SignTypeOverride }} + - ${{ if and(eq(parameters.SignTypeOverride, 'default'), or(eq(variables['Build.SourceBranchName'], 'main'), startsWith(variables['Build.SourceBranch'], 'refs/tags'))) }}: + - name: SignType + value: real + - ${{ if and(eq(parameters.SignTypeOverride, 'default'), not(or(eq(variables['Build.SourceBranchName'], 'main'), startsWith(variables['Build.SourceBranch'], 'refs/tags')))) }}: + - name: SignType + value: test + +resources: + repositories: + - repository: 1ESPipelineTemplates + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release + +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates + parameters: + pool: + name: VSEngSS-MicroBuild2022-1ES + os: windows + customBuildTags: + - ES365AIMigrationTooling + stages: + - stage: stage + jobs: + - job: 'UnrealEngine_VisualStudioTools_Release' + timeoutInMinutes: 1440 + templateContext: + outputParentDirectory: $(Agent.BuildDirectory)/out/ + outputs: + - output: pipelineArtifact + displayName: 'Publish signed plugin files' + targetPath: $(Agent.BuildDirectory)/out + artifactName: SignedPlugin + artifactType: container + sbomEnabled: false + steps: + - powershell: | + Write-Host "##vso[task.setVariable variable=SHA1]$(git rev-parse HEAD)" + displayName: Save tag commit hash + workingDirectory: '$(Build.SourcesDirectory)' + - task: MicroBuildSigningPlugin@4 + displayName: Install MicroBuild Signing + inputs: + signType: $(SignType) + zipSources: true + - task: PowerShell@2 + displayName: "Create zip excluding .git folder" + inputs: + targetType: 'inline' + script: | + $destinationDirectory = "$(Agent.BuildDirectory)/out" + New-Item -Path $destinationDirectory -ItemType Directory + git archive -o "$destinationDirectory/VisualStudioTools.zip" $(SHA1) + - powershell: New-FileCatalog -Path .\VisualStudioTools.zip -CatalogFilePath .\VisualStudioTools.zip.cat -CatalogVersion 2.0 + displayName: Create standalone catalog + workingDirectory: '$(Agent.BuildDirectory)\out' + - task: NuGetToolInstaller@1 + inputs: + versionSpec: 5.7 + - task: NuGetCommand@2 + displayName: 'NuGet Restore MicroBuild Signing Extension' + inputs: + command: 'restore' + restoreSolution: 'Scripts/SignDetached.proj' + feedsToUse: 'config' + restoreDirectory: '$(Build.SourcesDirectory)\Scripts\packages' + - task: MSBuild@1 + displayName: Sign catalogs + inputs: + solution: Scripts/SignDetached.proj + msbuildArguments: /p:SignType=$(SignType) /p:BaseOutputDirectory=$(Agent.BuildDirectory)\out + # === Disabled for now in favor of uploading the artifacts to the pipeline === + # - task: GitHubRelease@1 + # displayName: Pre-release to public repo + # condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags')) + # inputs: + # gitHubConnection: GitHub-VSCodeExtensions + # repositoryName: microsoft/vc-ue-extensions + # action: create + # target: $(SHA1) + # tagSource: gitTag + # tag: $(TagName) + # title: $(TagName) + # isDraft: true + # isPreRelease: true + # assets: | + # $(Agent.BuildDirectory)\out\VisualStudioTools.zip + # $(Agent.BuildDirectory)\out\VisualStudioTools.zip.cat diff --git a/Plugins/VisualStudioTools/build.proj b/Plugins/VisualStudioTools/build.proj new file mode 100644 index 0000000..14e1f70 --- /dev/null +++ b/Plugins/VisualStudioTools/build.proj @@ -0,0 +1,24 @@ + + + $(MSBuildProjectDirectory) + $([System.IO.Path]::Combine($(PluginFolder), `VisualStudioTools.uplugin`)) + $([System.IO.Path]::Combine($(PluginFolder), "bin")) + $(UnrealEngine) + + $([MSBuild]::GetRegistryValue('HKEY_LOCAL_MACHINE\SOFTWARE\EpicGames\Unreal Engine\$(UnrealEngine)', 'InstalledDirectory')) + + $(EnginePath.Trim())\Engine\Build\BatchFiles\RunUAT.bat + -Unversioned + -ubtargs="-LinkerArguments=\"/profile\" " + + + + + + + + + + diff --git a/Source/DungeonCrawlerUE.Target.cs b/Source/DungeonCrawlerUE.Target.cs new file mode 100644 index 0000000..23526f0 --- /dev/null +++ b/Source/DungeonCrawlerUE.Target.cs @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class DungeonCrawlerUETarget : TargetRules +{ + public DungeonCrawlerUETarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Game; + DefaultBuildSettings = BuildSettingsVersion.V6; + IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7; + ExtraModuleNames.Add("DungeonCrawlerUE"); + } +} diff --git a/Source/DungeonCrawlerUE/DungeonCrawlerUE.Build.cs b/Source/DungeonCrawlerUE/DungeonCrawlerUE.Build.cs new file mode 100644 index 0000000..69e57f5 --- /dev/null +++ b/Source/DungeonCrawlerUE/DungeonCrawlerUE.Build.cs @@ -0,0 +1,51 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class DungeonCrawlerUE : ModuleRules +{ + public DungeonCrawlerUE(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[] { + "DungeonCrawlerUE", + "DungeonCrawlerUE/Variant_Platforming", + "DungeonCrawlerUE/Variant_Platforming/Animation", + "DungeonCrawlerUE/Variant_Combat", + "DungeonCrawlerUE/Variant_Combat/AI", + "DungeonCrawlerUE/Variant_Combat/Animation", + "DungeonCrawlerUE/Variant_Combat/Gameplay", + "DungeonCrawlerUE/Variant_Combat/Interfaces", + "DungeonCrawlerUE/Variant_Combat/UI", + "DungeonCrawlerUE/Variant_SideScrolling", + "DungeonCrawlerUE/Variant_SideScrolling/AI", + "DungeonCrawlerUE/Variant_SideScrolling/Gameplay", + "DungeonCrawlerUE/Variant_SideScrolling/Interfaces", + "DungeonCrawlerUE/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/DungeonCrawlerUE/DungeonCrawlerUE.cpp b/Source/DungeonCrawlerUE/DungeonCrawlerUE.cpp new file mode 100644 index 0000000..94c0460 --- /dev/null +++ b/Source/DungeonCrawlerUE/DungeonCrawlerUE.cpp @@ -0,0 +1,8 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "DungeonCrawlerUE.h" +#include "Modules/ModuleManager.h" + +IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, DungeonCrawlerUE, "DungeonCrawlerUE" ); + +DEFINE_LOG_CATEGORY(LogDungeonCrawlerUE) \ No newline at end of file diff --git a/Source/DungeonCrawlerUE/DungeonCrawlerUE.h b/Source/DungeonCrawlerUE/DungeonCrawlerUE.h new file mode 100644 index 0000000..3b9a1b9 --- /dev/null +++ b/Source/DungeonCrawlerUE/DungeonCrawlerUE.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(LogDungeonCrawlerUE, Log, All); \ No newline at end of file diff --git a/Source/DungeonCrawlerUE/DungeonCrawlerUECharacter.cpp b/Source/DungeonCrawlerUE/DungeonCrawlerUECharacter.cpp new file mode 100644 index 0000000..ab11746 --- /dev/null +++ b/Source/DungeonCrawlerUE/DungeonCrawlerUECharacter.cpp @@ -0,0 +1,133 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "DungeonCrawlerUECharacter.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 "DungeonCrawlerUE.h" + +ADungeonCrawlerUECharacter::ADungeonCrawlerUECharacter() +{ + // 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 ADungeonCrawlerUECharacter::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, &ADungeonCrawlerUECharacter::Move); + EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this, &ADungeonCrawlerUECharacter::Look); + + // Looking + EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ADungeonCrawlerUECharacter::Look); + } + else + { + UE_LOG(LogDungeonCrawlerUE, 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 ADungeonCrawlerUECharacter::Move(const FInputActionValue& Value) +{ + // input is a Vector2D + FVector2D MovementVector = Value.Get(); + + // route the input + DoMove(MovementVector.X, MovementVector.Y); +} + +void ADungeonCrawlerUECharacter::Look(const FInputActionValue& Value) +{ + // input is a Vector2D + FVector2D LookAxisVector = Value.Get(); + + // route the input + DoLook(LookAxisVector.X, LookAxisVector.Y); +} + +void ADungeonCrawlerUECharacter::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 ADungeonCrawlerUECharacter::DoLook(float Yaw, float Pitch) +{ + if (GetController() != nullptr) + { + // add yaw and pitch input to controller + AddControllerYawInput(Yaw); + AddControllerPitchInput(Pitch); + } +} + +void ADungeonCrawlerUECharacter::DoJumpStart() +{ + // signal the character to jump + Jump(); +} + +void ADungeonCrawlerUECharacter::DoJumpEnd() +{ + // signal the character to stop jumping + StopJumping(); +} diff --git a/Source/DungeonCrawlerUE/DungeonCrawlerUECharacter.h b/Source/DungeonCrawlerUE/DungeonCrawlerUECharacter.h new file mode 100644 index 0000000..0a66477 --- /dev/null +++ b/Source/DungeonCrawlerUE/DungeonCrawlerUECharacter.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 "DungeonCrawlerUECharacter.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 ADungeonCrawlerUECharacter : 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 */ + ADungeonCrawlerUECharacter(); + +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/DungeonCrawlerUE/DungeonCrawlerUEGameMode.cpp b/Source/DungeonCrawlerUE/DungeonCrawlerUEGameMode.cpp new file mode 100644 index 0000000..b7e5abf --- /dev/null +++ b/Source/DungeonCrawlerUE/DungeonCrawlerUEGameMode.cpp @@ -0,0 +1,8 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "DungeonCrawlerUEGameMode.h" + +ADungeonCrawlerUEGameMode::ADungeonCrawlerUEGameMode() +{ + // stub +} diff --git a/Source/DungeonCrawlerUE/DungeonCrawlerUEGameMode.h b/Source/DungeonCrawlerUE/DungeonCrawlerUEGameMode.h new file mode 100644 index 0000000..0a37cef --- /dev/null +++ b/Source/DungeonCrawlerUE/DungeonCrawlerUEGameMode.h @@ -0,0 +1,24 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/GameModeBase.h" +#include "DungeonCrawlerUEGameMode.generated.h" + +/** + * Simple GameMode for a third person game + */ +UCLASS(abstract) +class ADungeonCrawlerUEGameMode : public AGameModeBase +{ + GENERATED_BODY() + +public: + + /** Constructor */ + ADungeonCrawlerUEGameMode(); +}; + + + diff --git a/Source/DungeonCrawlerUE/DungeonCrawlerUEPlayerController.cpp b/Source/DungeonCrawlerUE/DungeonCrawlerUEPlayerController.cpp new file mode 100644 index 0000000..d21626f --- /dev/null +++ b/Source/DungeonCrawlerUE/DungeonCrawlerUEPlayerController.cpp @@ -0,0 +1,67 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "DungeonCrawlerUEPlayerController.h" +#include "EnhancedInputSubsystems.h" +#include "Engine/LocalPlayer.h" +#include "InputMappingContext.h" +#include "Blueprint/UserWidget.h" +#include "DungeonCrawlerUE.h" +#include "Widgets/Input/SVirtualJoystick.h" + +void ADungeonCrawlerUEPlayerController::BeginPlay() +{ + Super::BeginPlay(); + + // only spawn touch controls on local player controllers + if (ShouldUseTouchControls() && IsLocalPlayerController()) + { + // spawn the mobile controls widget + MobileControlsWidget = CreateWidget(this, MobileControlsWidgetClass); + + if (MobileControlsWidget) + { + // add the controls to the player screen + MobileControlsWidget->AddToPlayerScreen(0); + + } else { + + UE_LOG(LogDungeonCrawlerUE, Error, TEXT("Could not spawn mobile controls widget.")); + + } + + } +} + +void ADungeonCrawlerUEPlayerController::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); + } + } + } + } +} + +bool ADungeonCrawlerUEPlayerController::ShouldUseTouchControls() const +{ + // are we on a mobile platform? Should we force touch? + return SVirtualJoystick::ShouldDisplayTouchInterface() || bForceTouchControls; +} diff --git a/Source/DungeonCrawlerUE/DungeonCrawlerUEPlayerController.h b/Source/DungeonCrawlerUE/DungeonCrawlerUEPlayerController.h new file mode 100644 index 0000000..cc8ccef --- /dev/null +++ b/Source/DungeonCrawlerUE/DungeonCrawlerUEPlayerController.h @@ -0,0 +1,52 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/PlayerController.h" +#include "DungeonCrawlerUEPlayerController.generated.h" + +class UInputMappingContext; +class UUserWidget; + +/** + * Basic PlayerController class for a third person game + * Manages input mappings + */ +UCLASS(abstract) +class ADungeonCrawlerUEPlayerController : 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; + + /** Gameplay initialization */ + virtual void BeginPlay() override; + + /** Input mapping context setup */ + virtual void SetupInputComponent() override; + + /** Returns true if the player should use UMG touch controls */ + bool ShouldUseTouchControls() const; + +}; diff --git a/Source/DungeonCrawlerUE/Variant_Combat/AI/CombatAIController.cpp b/Source/DungeonCrawlerUE/Variant_Combat/AI/CombatAIController.cpp new file mode 100644 index 0000000..0ef8de1 --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_Combat/AI/CombatAIController.cpp @@ -0,0 +1,19 @@ +// 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 = true; + + // ensure we're attached to the possessed character. + // this is necessary for EnvQueries to work correctly + bAttachToPawn = true; +} diff --git a/Source/DungeonCrawlerUE/Variant_Combat/AI/CombatAIController.h b/Source/DungeonCrawlerUE/Variant_Combat/AI/CombatAIController.h new file mode 100644 index 0000000..6da1d69 --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_Combat/AI/CombatAIController.h @@ -0,0 +1,27 @@ +// 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(); +}; diff --git a/Source/DungeonCrawlerUE/Variant_Combat/AI/CombatEnemy.cpp b/Source/DungeonCrawlerUE/Variant_Combat/AI/CombatEnemy.cpp new file mode 100644 index 0000000..393957f --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/AI/CombatEnemy.h b/Source/DungeonCrawlerUE/Variant_Combat/AI/CombatEnemy.h new file mode 100644 index 0000000..1a4a99d --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/AI/CombatEnemySpawner.cpp b/Source/DungeonCrawlerUE/Variant_Combat/AI/CombatEnemySpawner.cpp new file mode 100644 index 0000000..b29a858 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/AI/CombatEnemySpawner.h b/Source/DungeonCrawlerUE/Variant_Combat/AI/CombatEnemySpawner.h new file mode 100644 index 0000000..ca9eae5 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/AI/CombatStateTreeUtility.cpp b/Source/DungeonCrawlerUE/Variant_Combat/AI/CombatStateTreeUtility.cpp new file mode 100644 index 0000000..82dacc1 --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_Combat/AI/CombatStateTreeUtility.cpp @@ -0,0 +1,325 @@ +// 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 +{ + // have we transitioned from another state? + if (Transition.ChangeType == EStateTreeStateChangeType::Changed) + { + // 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 +{ + // have we transitioned from another state? + if (Transition.ChangeType == EStateTreeStateChangeType::Changed) + { + // 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 +{ + // have we transitioned from another state? + if (Transition.ChangeType == EStateTreeStateChangeType::Changed) + { + // 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 +{ + // have we transitioned from another state? + if (Transition.ChangeType == EStateTreeStateChangeType::Changed) + { + // 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 +{ + // have we transitioned from another state? + if (Transition.ChangeType == EStateTreeStateChangeType::Changed) + { + // 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 +{ + // have we transitioned from another state? + if (Transition.ChangeType == EStateTreeStateChangeType::Changed) + { + // 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 +{ + // have we transitioned from another state? + if (Transition.ChangeType == EStateTreeStateChangeType::Changed) + { + // 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 +{ + // have we transitioned to another state? + if (Transition.ChangeType == EStateTreeStateChangeType::Changed) + { + // 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 +{ + // have we transitioned from another state? + if (Transition.ChangeType == EStateTreeStateChangeType::Changed) + { + // 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 +{ + // have we transitioned to another state? + if (Transition.ChangeType == EStateTreeStateChangeType::Changed) + { + // 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 +{ + // have we transitioned from another state? + if (Transition.ChangeType == EStateTreeStateChangeType::Changed) + { + // 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::Tick(FStateTreeExecutionContext& Context, const float DeltaTime) const +{ + // get the instance data + FInstanceDataType& InstanceData = Context.GetInstanceData(*this); + + // get the character possessed by the first local player + InstanceData.TargetPlayerCharacter = Cast(UGameplayStatics::GetPlayerPawn(InstanceData.Character, 0)); + + // do we have a valid target? + if (InstanceData.TargetPlayerCharacter) + { + // update the last known location + InstanceData.TargetPlayerLocation = InstanceData.TargetPlayerCharacter->GetActorLocation(); + } + + // update the distance + InstanceData.DistanceToTarget = FVector::Distance(InstanceData.TargetPlayerLocation, InstanceData.Character->GetActorLocation()); + + return EStateTreeRunStatus::Running; +} + +#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/DungeonCrawlerUE/Variant_Combat/AI/CombatStateTreeUtility.h b/Source/DungeonCrawlerUE/Variant_Combat/AI/CombatStateTreeUtility.h new file mode 100644 index 0000000..283c3e4 --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_Combat/AI/CombatStateTreeUtility.h @@ -0,0 +1,365 @@ +// 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") + ACharacter* Character; + + /** If true, the condition passes if the character is not grounded instead */ + UPROPERTY(EditAnywhere, Category = "Condition") + bool bMustBeOnAir = false; +}; +STATETREE_POD_INSTANCEDATA(FStateTreeCharacterGroundedConditionInstanceData); + +/** + * 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") + ACombatEnemy* 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_POD_INSTANCEDATA(FStateTreeIsInDangerConditionInstanceData); + +/** + * 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() + + /* 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() + + /* 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() + + /* 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() + + /* 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() + + /* 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() + + /* 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) + TObjectPtr TargetPlayerCharacter; + + /** Last known location for the target */ + UPROPERTY(VisibleAnywhere) + FVector TargetPlayerLocation = FVector::ZeroVector; + + /** Distance to the target */ + UPROPERTY(VisibleAnywhere) + float DistanceToTarget = 0.0f; +}; + +/** + * StateTree task to get information about the player character + */ +USTRUCT(meta=(DisplayName="GetPlayerInfo", Category="Combat")) +struct FStateTreeGetPlayerInfoTask : public FStateTreeTaskCommonBase +{ + GENERATED_BODY() + + /* Ensure we're using the correct instance data struct */ + using FInstanceDataType = FStateTreeGetPlayerInfoInstanceData; + virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); } + + /** Runs while the owning state is active */ + virtual EStateTreeRunStatus Tick(FStateTreeExecutionContext& Context, const float DeltaTime) 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/DungeonCrawlerUE/Variant_Combat/AI/EnvQueryContext_Danger.cpp b/Source/DungeonCrawlerUE/Variant_Combat/AI/EnvQueryContext_Danger.cpp new file mode 100644 index 0000000..f356711 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/AI/EnvQueryContext_Danger.h b/Source/DungeonCrawlerUE/Variant_Combat/AI/EnvQueryContext_Danger.h new file mode 100644 index 0000000..3d32c15 --- /dev/null +++ b/Source/DungeonCrawlerUE/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 DUNGEONCRAWLERUE_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/DungeonCrawlerUE/Variant_Combat/AI/EnvQueryContext_Player.cpp b/Source/DungeonCrawlerUE/Variant_Combat/AI/EnvQueryContext_Player.cpp new file mode 100644 index 0000000..46ca669 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/AI/EnvQueryContext_Player.h b/Source/DungeonCrawlerUE/Variant_Combat/AI/EnvQueryContext_Player.h new file mode 100644 index 0000000..dc43895 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/Animation/AnimNotify_CheckChargedAttack.cpp b/Source/DungeonCrawlerUE/Variant_Combat/Animation/AnimNotify_CheckChargedAttack.cpp new file mode 100644 index 0000000..dbc5f12 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/Animation/AnimNotify_CheckChargedAttack.h b/Source/DungeonCrawlerUE/Variant_Combat/Animation/AnimNotify_CheckChargedAttack.h new file mode 100644 index 0000000..98b1722 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/Animation/AnimNotify_CheckCombo.cpp b/Source/DungeonCrawlerUE/Variant_Combat/Animation/AnimNotify_CheckCombo.cpp new file mode 100644 index 0000000..117dbf7 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/Animation/AnimNotify_CheckCombo.h b/Source/DungeonCrawlerUE/Variant_Combat/Animation/AnimNotify_CheckCombo.h new file mode 100644 index 0000000..cce0f6c --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/Animation/AnimNotify_DoAttackTrace.cpp b/Source/DungeonCrawlerUE/Variant_Combat/Animation/AnimNotify_DoAttackTrace.cpp new file mode 100644 index 0000000..13bd38f --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/Animation/AnimNotify_DoAttackTrace.h b/Source/DungeonCrawlerUE/Variant_Combat/Animation/AnimNotify_DoAttackTrace.h new file mode 100644 index 0000000..e9d7a4d --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/CombatCharacter.cpp b/Source/DungeonCrawlerUE/Variant_Combat/CombatCharacter.cpp new file mode 100644 index 0000000..63ebcf9 --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_Combat/CombatCharacter.cpp @@ -0,0 +1,547 @@ +// 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) + { + // 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; + + // if we've done the charge loop at least once, release the charged attack right away + if (bHasLoopedChargedAttack) + { + CheckChargedAttack(); + } +} + +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; + + // 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; + + // jump to either the loop or the attack section depending on whether we're still holding the charge button + if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance()) + { + AnimInstance->Montage_JumpToSection(bIsChargingAttack ? ChargeLoopSection : ChargeAttackSection, 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/DungeonCrawlerUE/Variant_Combat/CombatCharacter.h b/Source/DungeonCrawlerUE/Variant_Combat/CombatCharacter.h new file mode 100644 index 0000000..85e8641 --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_Combat/CombatCharacter.h @@ -0,0 +1,334 @@ +// 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; + + /** 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; + + // ~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/DungeonCrawlerUE/Variant_Combat/CombatGameMode.cpp b/Source/DungeonCrawlerUE/Variant_Combat/CombatGameMode.cpp new file mode 100644 index 0000000..68f052f --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_Combat/CombatGameMode.cpp @@ -0,0 +1,9 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "Variant_Combat/CombatGameMode.h" + +ACombatGameMode::ACombatGameMode() +{ + +} \ No newline at end of file diff --git a/Source/DungeonCrawlerUE/Variant_Combat/CombatGameMode.h b/Source/DungeonCrawlerUE/Variant_Combat/CombatGameMode.h new file mode 100644 index 0000000..e28d67b --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_Combat/CombatGameMode.h @@ -0,0 +1,20 @@ +// 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: + + ACombatGameMode(); +}; diff --git a/Source/DungeonCrawlerUE/Variant_Combat/CombatPlayerController.cpp b/Source/DungeonCrawlerUE/Variant_Combat/CombatPlayerController.cpp new file mode 100644 index 0000000..0cd7d2d --- /dev/null +++ b/Source/DungeonCrawlerUE/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 "DungeonCrawlerUE.h" +#include "Widgets/Input/SVirtualJoystick.h" + +void ACombatPlayerController::BeginPlay() +{ + Super::BeginPlay(); + + // only spawn touch controls on local player controllers + if (ShouldUseTouchControls() && IsLocalPlayerController()) + { + // spawn the mobile controls widget + MobileControlsWidget = CreateWidget(this, MobileControlsWidgetClass); + + if (MobileControlsWidget) + { + // add the controls to the player screen + MobileControlsWidget->AddToPlayerScreen(0); + + } else { + + UE_LOG(LogDungeonCrawlerUE, Error, TEXT("Could not spawn mobile controls widget.")); + + } + + } +} + +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); + } + } + } + } +} + +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/DungeonCrawlerUE/Variant_Combat/CombatPlayerController.h b/Source/DungeonCrawlerUE/Variant_Combat/CombatPlayerController.h new file mode 100644 index 0000000..2e1ac16 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatActivationVolume.cpp b/Source/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatActivationVolume.cpp new file mode 100644 index 0000000..2d8d762 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatActivationVolume.h b/Source/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatActivationVolume.h new file mode 100644 index 0000000..9d63ede --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatCheckpointVolume.cpp b/Source/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatCheckpointVolume.cpp new file mode 100644 index 0000000..78df43d --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatCheckpointVolume.cpp @@ -0,0 +1,47 @@ +// 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) +{ + // ensure we use this only once + if (bCheckpointUsed) + { + return; + } + + // has the player entered this volume? + ACombatCharacter* PlayerCharacter = Cast(OtherActor); + + if (PlayerCharacter) + { + if (ACombatPlayerController* PC = Cast(PlayerCharacter->GetController())) + { + // raise the checkpoint used flag + bCheckpointUsed = true; + + // update the player's respawn checkpoint + PC->SetRespawnTransform(PlayerCharacter->GetActorTransform()); + } + + } +} diff --git a/Source/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatCheckpointVolume.h b/Source/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatCheckpointVolume.h new file mode 100644 index 0000000..d3f326b --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatCheckpointVolume.h @@ -0,0 +1,32 @@ +// 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: + + /** Set to true after use to avoid accidentally resetting the checkpoint */ + bool bCheckpointUsed = false; + + /** 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/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatDamageableBox.cpp b/Source/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatDamageableBox.cpp new file mode 100644 index 0000000..2448ea7 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatDamageableBox.h b/Source/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatDamageableBox.h new file mode 100644 index 0000000..d0cb5c0 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatDummy.cpp b/Source/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatDummy.cpp new file mode 100644 index 0000000..6a38a3f --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatDummy.h b/Source/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatDummy.h new file mode 100644 index 0000000..5359506 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatLavaFloor.cpp b/Source/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatLavaFloor.cpp new file mode 100644 index 0000000..816663b --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatLavaFloor.h b/Source/DungeonCrawlerUE/Variant_Combat/Gameplay/CombatLavaFloor.h new file mode 100644 index 0000000..813e33a --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/Interfaces/CombatActivatable.cpp b/Source/DungeonCrawlerUE/Variant_Combat/Interfaces/CombatActivatable.cpp new file mode 100644 index 0000000..5a9f567 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/Interfaces/CombatActivatable.h b/Source/DungeonCrawlerUE/Variant_Combat/Interfaces/CombatActivatable.h new file mode 100644 index 0000000..b7dafd4 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/Interfaces/CombatAttacker.cpp b/Source/DungeonCrawlerUE/Variant_Combat/Interfaces/CombatAttacker.cpp new file mode 100644 index 0000000..2abd0a2 --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_Combat/Interfaces/CombatAttacker.cpp @@ -0,0 +1,4 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "CombatAttacker.h" diff --git a/Source/DungeonCrawlerUE/Variant_Combat/Interfaces/CombatAttacker.h b/Source/DungeonCrawlerUE/Variant_Combat/Interfaces/CombatAttacker.h new file mode 100644 index 0000000..503a081 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/Interfaces/CombatDamageable.cpp b/Source/DungeonCrawlerUE/Variant_Combat/Interfaces/CombatDamageable.cpp new file mode 100644 index 0000000..5c82792 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/Interfaces/CombatDamageable.h b/Source/DungeonCrawlerUE/Variant_Combat/Interfaces/CombatDamageable.h new file mode 100644 index 0000000..8f2a09c --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Combat/UI/CombatLifeBar.cpp b/Source/DungeonCrawlerUE/Variant_Combat/UI/CombatLifeBar.cpp new file mode 100644 index 0000000..ed9c23e --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_Combat/UI/CombatLifeBar.cpp @@ -0,0 +1,5 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "CombatLifeBar.h" + diff --git a/Source/DungeonCrawlerUE/Variant_Combat/UI/CombatLifeBar.h b/Source/DungeonCrawlerUE/Variant_Combat/UI/CombatLifeBar.h new file mode 100644 index 0000000..9a2fcbe --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Platforming/Animation/AnimNotify_EndDash.cpp b/Source/DungeonCrawlerUE/Variant_Platforming/Animation/AnimNotify_EndDash.cpp new file mode 100644 index 0000000..cdf4041 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Platforming/Animation/AnimNotify_EndDash.h b/Source/DungeonCrawlerUE/Variant_Platforming/Animation/AnimNotify_EndDash.h new file mode 100644 index 0000000..596620f --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Platforming/PlatformingCharacter.cpp b/Source/DungeonCrawlerUE/Variant_Platforming/PlatformingCharacter.cpp new file mode 100644 index 0000000..38939f2 --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_Platforming/PlatformingCharacter.cpp @@ -0,0 +1,367 @@ +// 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) +{ + // 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/DungeonCrawlerUE/Variant_Platforming/PlatformingCharacter.h b/Source/DungeonCrawlerUE/Variant_Platforming/PlatformingCharacter.h new file mode 100644 index 0000000..1ba2a69 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_Platforming/PlatformingGameMode.cpp b/Source/DungeonCrawlerUE/Variant_Platforming/PlatformingGameMode.cpp new file mode 100644 index 0000000..69de2c5 --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_Platforming/PlatformingGameMode.cpp @@ -0,0 +1,9 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "Variant_Platforming/PlatformingGameMode.h" + +APlatformingGameMode::APlatformingGameMode() +{ + // stub +} \ No newline at end of file diff --git a/Source/DungeonCrawlerUE/Variant_Platforming/PlatformingGameMode.h b/Source/DungeonCrawlerUE/Variant_Platforming/PlatformingGameMode.h new file mode 100644 index 0000000..00c48bf --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_Platforming/PlatformingGameMode.h @@ -0,0 +1,21 @@ +// 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(); +}; diff --git a/Source/DungeonCrawlerUE/Variant_Platforming/PlatformingPlayerController.cpp b/Source/DungeonCrawlerUE/Variant_Platforming/PlatformingPlayerController.cpp new file mode 100644 index 0000000..87845c0 --- /dev/null +++ b/Source/DungeonCrawlerUE/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 "DungeonCrawlerUE.h" +#include "Widgets/Input/SVirtualJoystick.h" + +void APlatformingPlayerController::BeginPlay() +{ + Super::BeginPlay(); + + // only spawn touch controls on local player controllers + if (ShouldUseTouchControls() && IsLocalPlayerController()) + { + // spawn the mobile controls widget + MobileControlsWidget = CreateWidget(this, MobileControlsWidgetClass); + + if (MobileControlsWidget) + { + // add the controls to the player screen + MobileControlsWidget->AddToPlayerScreen(0); + + } else { + + UE_LOG(LogDungeonCrawlerUE, Error, TEXT("Could not spawn mobile controls widget.")); + + } + + } +} + +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); + } + } + } + } +} + +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/DungeonCrawlerUE/Variant_Platforming/PlatformingPlayerController.h b/Source/DungeonCrawlerUE/Variant_Platforming/PlatformingPlayerController.h new file mode 100644 index 0000000..64f8420 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/AI/SideScrollingAIController.cpp b/Source/DungeonCrawlerUE/Variant_SideScrolling/AI/SideScrollingAIController.cpp new file mode 100644 index 0000000..e025986 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/AI/SideScrollingAIController.h b/Source/DungeonCrawlerUE/Variant_SideScrolling/AI/SideScrollingAIController.h new file mode 100644 index 0000000..f314e90 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/AI/SideScrollingNPC.cpp b/Source/DungeonCrawlerUE/Variant_SideScrolling/AI/SideScrollingNPC.cpp new file mode 100644 index 0000000..1b5b26c --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/AI/SideScrollingNPC.h b/Source/DungeonCrawlerUE/Variant_SideScrolling/AI/SideScrollingNPC.h new file mode 100644 index 0000000..ee47560 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/AI/SideScrollingStateTreeUtility.cpp b/Source/DungeonCrawlerUE/Variant_SideScrolling/AI/SideScrollingStateTreeUtility.cpp new file mode 100644 index 0000000..489e0ca --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_SideScrolling/AI/SideScrollingStateTreeUtility.cpp @@ -0,0 +1,32 @@ +// 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::Tick(FStateTreeExecutionContext& Context, const float DeltaTime) const +{ + // get the instance data + FInstanceDataType& InstanceData = Context.GetInstanceData(*this); + + // set the player pawn as the target + InstanceData.TargetPlayer = UGameplayStatics::GetPlayerPawn(InstanceData.Controller.Get(), 0); + + // are the NPC and target valid? + if (IsValid(InstanceData.TargetPlayer) && IsValid(InstanceData.NPC)) + { + InstanceData.bValidTarget = FVector::Distance(InstanceData.NPC->GetActorLocation(), InstanceData.TargetPlayer->GetActorLocation()) < InstanceData.RangeMax; + } + + return EStateTreeRunStatus::Running; +} + +#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/DungeonCrawlerUE/Variant_SideScrolling/AI/SideScrollingStateTreeUtility.h b/Source/DungeonCrawlerUE/Variant_SideScrolling/AI/SideScrollingStateTreeUtility.h new file mode 100644 index 0000000..ebed7d1 --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_SideScrolling/AI/SideScrollingStateTreeUtility.h @@ -0,0 +1,59 @@ +// 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() + + /* Ensure we're using the correct instance data struct */ + using FInstanceDataType = FStateTreeGetPlayerInstanceData; + virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); } + + /** Runs while the owning state is active */ + virtual EStateTreeRunStatus Tick(FStateTreeExecutionContext& Context, const float DeltaTime) 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/DungeonCrawlerUE/Variant_SideScrolling/Gameplay/SideScrollingJumpPad.cpp b/Source/DungeonCrawlerUE/Variant_SideScrolling/Gameplay/SideScrollingJumpPad.cpp new file mode 100644 index 0000000..d98c665 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/Gameplay/SideScrollingJumpPad.h b/Source/DungeonCrawlerUE/Variant_SideScrolling/Gameplay/SideScrollingJumpPad.h new file mode 100644 index 0000000..c503b4b --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/Gameplay/SideScrollingMovingPlatform.cpp b/Source/DungeonCrawlerUE/Variant_SideScrolling/Gameplay/SideScrollingMovingPlatform.cpp new file mode 100644 index 0000000..7c5b7a8 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/Gameplay/SideScrollingMovingPlatform.h b/Source/DungeonCrawlerUE/Variant_SideScrolling/Gameplay/SideScrollingMovingPlatform.h new file mode 100644 index 0000000..6b89732 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/Gameplay/SideScrollingPickup.cpp b/Source/DungeonCrawlerUE/Variant_SideScrolling/Gameplay/SideScrollingPickup.cpp new file mode 100644 index 0000000..bbe3753 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/Gameplay/SideScrollingPickup.h b/Source/DungeonCrawlerUE/Variant_SideScrolling/Gameplay/SideScrollingPickup.h new file mode 100644 index 0000000..80e80d0 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/Gameplay/SideScrollingSoftPlatform.cpp b/Source/DungeonCrawlerUE/Variant_SideScrolling/Gameplay/SideScrollingSoftPlatform.cpp new file mode 100644 index 0000000..81f9139 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/Gameplay/SideScrollingSoftPlatform.h b/Source/DungeonCrawlerUE/Variant_SideScrolling/Gameplay/SideScrollingSoftPlatform.h new file mode 100644 index 0000000..04ed012 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/Interfaces/SideScrollingInteractable.cpp b/Source/DungeonCrawlerUE/Variant_SideScrolling/Interfaces/SideScrollingInteractable.cpp new file mode 100644 index 0000000..d44da2b --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/Interfaces/SideScrollingInteractable.h b/Source/DungeonCrawlerUE/Variant_SideScrolling/Interfaces/SideScrollingInteractable.h new file mode 100644 index 0000000..05d43d1 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/SideScrollingCameraManager.cpp b/Source/DungeonCrawlerUE/Variant_SideScrolling/SideScrollingCameraManager.cpp new file mode 100644 index 0000000..9aa30ac --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/SideScrollingCameraManager.h b/Source/DungeonCrawlerUE/Variant_SideScrolling/SideScrollingCameraManager.h new file mode 100644 index 0000000..7286306 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/SideScrollingCharacter.cpp b/Source/DungeonCrawlerUE/Variant_SideScrolling/SideScrollingCharacter.cpp new file mode 100644 index 0000000..b11faa5 --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/SideScrollingCharacter.h b/Source/DungeonCrawlerUE/Variant_SideScrolling/SideScrollingCharacter.h new file mode 100644 index 0000000..9815e8b --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/SideScrollingGameMode.cpp b/Source/DungeonCrawlerUE/Variant_SideScrolling/SideScrollingGameMode.cpp new file mode 100644 index 0000000..1fa5246 --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_SideScrolling/SideScrollingGameMode.cpp @@ -0,0 +1,35 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "SideScrollingGameMode.h" +#include "Kismet/GameplayStatics.h" +#include "Blueprint/UserWidget.h" +#include "SideScrollingUI.h" +#include "SideScrollingPickup.h" + +void ASideScrollingGameMode::BeginPlay() +{ + Super::BeginPlay(); + + // create the game UI + APlayerController* OwningPlayer = UGameplayStatics::GetPlayerController(GetWorld(), 0); + + UserInterface = CreateWidget(OwningPlayer, UserInterfaceClass); + + check(UserInterface); +} + +void ASideScrollingGameMode::ProcessPickup() +{ + // increment the pickups counter + ++PickupsCollected; + + // 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/DungeonCrawlerUE/Variant_SideScrolling/SideScrollingGameMode.h b/Source/DungeonCrawlerUE/Variant_SideScrolling/SideScrollingGameMode.h new file mode 100644 index 0000000..a9d540a --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_SideScrolling/SideScrollingGameMode.h @@ -0,0 +1,44 @@ +// 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; + +public: + + /** Receives an interaction event from another actor */ + virtual void ProcessPickup(); +}; diff --git a/Source/DungeonCrawlerUE/Variant_SideScrolling/SideScrollingPlayerController.cpp b/Source/DungeonCrawlerUE/Variant_SideScrolling/SideScrollingPlayerController.cpp new file mode 100644 index 0000000..f65648f --- /dev/null +++ b/Source/DungeonCrawlerUE/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 "DungeonCrawlerUE.h" +#include "Widgets/Input/SVirtualJoystick.h" + +void ASideScrollingPlayerController::BeginPlay() +{ + Super::BeginPlay(); + + // only spawn touch controls on local player controllers + if (ShouldUseTouchControls() && IsLocalPlayerController()) + { + // spawn the mobile controls widget + MobileControlsWidget = CreateWidget(this, MobileControlsWidgetClass); + + if (MobileControlsWidget) + { + // add the controls to the player screen + MobileControlsWidget->AddToPlayerScreen(0); + + } else { + + UE_LOG(LogDungeonCrawlerUE, Error, TEXT("Could not spawn mobile controls widget.")); + + } + + } +} + +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); + } + } + } + } +} + +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/DungeonCrawlerUE/Variant_SideScrolling/SideScrollingPlayerController.h b/Source/DungeonCrawlerUE/Variant_SideScrolling/SideScrollingPlayerController.h new file mode 100644 index 0000000..115029e --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUE/Variant_SideScrolling/UI/SideScrollingUI.cpp b/Source/DungeonCrawlerUE/Variant_SideScrolling/UI/SideScrollingUI.cpp new file mode 100644 index 0000000..0a4e042 --- /dev/null +++ b/Source/DungeonCrawlerUE/Variant_SideScrolling/UI/SideScrollingUI.cpp @@ -0,0 +1,5 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "SideScrollingUI.h" + diff --git a/Source/DungeonCrawlerUE/Variant_SideScrolling/UI/SideScrollingUI.h b/Source/DungeonCrawlerUE/Variant_SideScrolling/UI/SideScrollingUI.h new file mode 100644 index 0000000..a19cafd --- /dev/null +++ b/Source/DungeonCrawlerUE/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/DungeonCrawlerUEEditor.Target.cs b/Source/DungeonCrawlerUEEditor.Target.cs new file mode 100644 index 0000000..6a1f4d4 --- /dev/null +++ b/Source/DungeonCrawlerUEEditor.Target.cs @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class DungeonCrawlerUEEditorTarget : TargetRules +{ + public DungeonCrawlerUEEditorTarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Editor; + DefaultBuildSettings = BuildSettingsVersion.V6; + IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7; + ExtraModuleNames.Add("DungeonCrawlerUE"); + } +}