diff --git a/BuildProject.bat b/BuildProject.bat index 57b129aa..9a536dcd 100644 --- a/BuildProject.bat +++ b/BuildProject.bat @@ -1,7 +1,35 @@ @echo off + call "%~dp0ProjectPaths.bat" -call %~dp0%PROJECT_PATH%\"Plugins/UnrealGDK/SpatialGDK/Build/Scripts/BuildWorker.bat" %GAME_NAME%Server Linux Development %GAME_NAME%.uproject || goto :error -call %~dp0%PROJECT_PATH%\"Plugins/UnrealGDK/SpatialGDK/Build/Scripts/BuildWorker.bat" %GAME_NAME% Win64 Development %GAME_NAME%.uproject || goto :error + +setlocal EnableDelayedExpansion + +set GDK_DIRECTORY="" + +rem If a project plugin exists. Use this for building. +if exist "%~dp0\%PROJECT_PATH%\Plugins\UnrealGDK" ( + set GDK_DIRECTORY="%~dp0\%PROJECT_PATH%\Plugins\UnrealGDK\" + goto :BuildWorkers +) + +rem If there is no project plugin. Find the engine plugin. +call "%~dp0FindEngine.bat" + +if %UNREAL_ENGINE%=="" ( + echo Error: Could not find the Unreal Engine. Please associate your '.uproject' with an engine version or ensure this game project is nested within an engine build. + pause + exit /b 1 +) + +set GDK_DIRECTORY=%UNREAL_ENGINE%\Engine\Plugins\UnrealGDK + + +:BuildWorkers +echo Building worker with GDK located at %GDK_DIRECTORY% + +call %GDK_DIRECTORY%\SpatialGDK\Build\Scripts\BuildWorker.bat %GAME_NAME%Server Linux Development "%~dp0\%PROJECT_PATH%\%GAME_NAME%.uproject" || goto :error +call %GDK_DIRECTORY%\SpatialGDK\Build\Scripts\BuildWorker.bat %GAME_NAME%SimulatedPlayer Linux Development "%~dp0\%PROJECT_PATH%\%GAME_NAME%.uproject" || goto :error +call %GDK_DIRECTORY%\SpatialGDK\Build\Scripts\BuildWorker.bat %GAME_NAME% Win64 Development "%~dp0\%PROJECT_PATH%\%GAME_NAME%.uproject" || goto :error echo All builds succeeded. pause diff --git a/DeployGame.bat b/DeployGame.bat index c7bf60df..3fc3d7de 100644 --- a/DeployGame.bat +++ b/DeployGame.bat @@ -18,8 +18,6 @@ spatial cloud launch %deploymentname% one_worker_test.json %deploymentname% --sn spatial project deployment tags add %deploymentname% dev_login || goto :error spatial project deployment tags add %deploymentname% status_lobby || goto :error - - echo Deployment succeeded. cd ../ pause diff --git a/FindEngine.bat b/FindEngine.bat new file mode 100644 index 00000000..6bcae7e4 --- /dev/null +++ b/FindEngine.bat @@ -0,0 +1,84 @@ +@echo off + +setlocal EnableDelayedExpansion + +rem Get the Unreal Engine used by this project by querying the registry for the engine association found in the .uproject. +set UNREAL_ENGINE="" +set UPROJECT="" + +rem First find the .uproject +for /f "delims=" %%A in (' powershell -Command "Get-ChildItem %~dp0 -Depth 1 -Filter *.uproject -File | %% {$_.FullName}" ') do set UPROJECT="%%A" + +if %UPROJECT%=="" ( + echo Error: Could not find uproject. Please make sure you have passed in the project directory correctly. + pause + exit /b 1 +) + +echo Using uproject: %UPROJECT% + +rem Get the Engine association from the uproject. +for /f "delims=" %%A in (' powershell -Command "(Get-Content %UPROJECT% | ConvertFrom-Json).EngineAssociation" ') do set ENGINE_ASSOCIATION=%%A + +echo Engine association for uproject is: %ENGINE_ASSOCIATION% + +rem If the engine association is a path then use this. If the path is relative then it will be relative to the uproject, thus we must change directory to the uproject folder. + +rem Grab the project path from the .uproject file. +for %%i in (%UPROJECT%) do ( + rem file drive + file directory + set UNREAL_PROJECT_DIR="%%~di%%~pi" +) + +pushd %UNREAL_PROJECT_DIR% + +if exist "%ENGINE_ASSOCIATION%" ( + cd /d "%ENGINE_ASSOCIATION%" + set UNREAL_ENGINE="!cd!" +) + +popd + +rem Try and use the engine association as a key in the registry to get the path to Unreal. +if %UNREAL_ENGINE%=="" ( + if not "%ENGINE_ASSOCIATION%"=="" ( + rem Query the registry for the path to the Unreal Engine using the engine association. + for /f "usebackq tokens=1,2* skip=2" %%A in (`reg query "HKCU\Software\Epic Games\Unreal Engine\Builds" /v %ENGINE_ASSOCIATION%`) do ( + set UNREAL_ENGINE="%%C" + ) + ) +) + +rem If there was no engine association then we need to climb the directory path of the project to find the Engine. +if %UNREAL_ENGINE%=="" ( + pushd "%~dp0" + + :climb_parent_directory + if exist Engine ( + rem Check for the Build.version file to be sure we have found a correct Engine folder. + if exist "Engine\Build\Build.version" ( + set UNREAL_ENGINE="!cd!" + ) + ) else ( + rem This checks if we are in a root directory. If so we cannot check any higher and so should error out. + if "%cd:~3,1%"=="" ( + echo Error: Could not find Unreal Engine folder. Please set a project association or ensure your game project is within an Unreal Engine folder. + pause + exit /b 1 + ) + cd .. + goto :climb_parent_directory + ) + + popd +) + +if %UNREAL_ENGINE%=="" ( + echo Error: Could not find the Unreal Engine. Please associate your '.uproject' with an engine version or ensure this game project is nested within an engine build. + pause + exit /b 1 +) + +endlocal & set UNREAL_ENGINE=%UNREAL_ENGINE% + +echo Unreal engine found at: %UNREAL_ENGINE% \ No newline at end of file diff --git a/Game/Config/DefaultEngine.ini b/Game/Config/DefaultEngine.ini index 7a5b38dd..c146213c 100644 --- a/Game/Config/DefaultEngine.ini +++ b/Game/Config/DefaultEngine.ini @@ -1,7 +1,7 @@ [/Script/EngineSettings.GameMapsSettings] GameDefaultMap=/Game/Maps/Deployments.Deployments EditorStartupMap=/Game/Maps/FPS-Start_Tiny.FPS-Start_Tiny -GlobalDefaultGameMode=/Game/GameMode/BP_GDKGameMode.BP_GDKGameMode_C +GlobalDefaultGameMode=/Game/GameMode/BP_DeathmatchGameMode.BP_DeathmatchGameMode_C GameInstanceClass=/Script/SpatialGDK.SpatialGameInstance GlobalDefaultServerGameMode=None ServerDefaultMap=/Game/Maps/FPS-Start_Medium.FPS-Start_Medium @@ -57,13 +57,12 @@ DefaultFluidFriction=0.300000 SimulateScratchMemorySize=262144 RagdollAggregateThreshold=4 TriangleMeshTriangleMinAreaThreshold=5.000000 -bEnableAsyncScene=False bEnableShapeSharing=False bEnablePCM=False bEnableStabilization=False bWarnMissingLocks=True bEnable2DPhysics=False -PhysicErrorCorrection=(PingExtrapolation=0.100000,ErrorPerLinearDifference=1.000000,ErrorPerAngularDifference=1.000000,MaxRestoredStateError=1.000000,PositionLerp=0.000000,AngleLerp=0.400000,LinearVelocityCoefficient=100.000000,AngularVelocityCoefficient=10.000000,ErrorAccumulationSeconds=0.500000,ErrorAccumulationDistanceSq=15.000000,ErrorAccumulationSimilarity=100.000000) +PhysicErrorCorrection=(PingExtrapolation=0.100000,PingLimit=100.000000,ErrorPerLinearDifference=1.000000,ErrorPerAngularDifference=1.000000,MaxRestoredStateError=1.000000,MaxLinearHardSnapDistance=400.000000,PositionLerp=0.000000,AngleLerp=0.400000,LinearVelocityCoefficient=100.000000,AngularVelocityCoefficient=10.000000,ErrorAccumulationSeconds=0.500000,ErrorAccumulationDistanceSq=15.000000,ErrorAccumulationSimilarity=100.000000) LockedAxis=Invalid DefaultDegreesOfFreedom=Full3D BounceThresholdVelocity=200.000000 @@ -90,9 +89,10 @@ bSubsteppingAsync=False MaxSubstepDeltaTime=0.016667 MaxSubsteps=6 SyncSceneSmoothingFactor=0.000000 -AsyncSceneSmoothingFactor=0.990000 InitialAverageFrameRate=0.016667 PhysXTreeRebuildRate=10 DefaultBroadphaseSettings=(bUseMBPOnClient=False,bUseMBPOnServer=False,MBPBounds=(Min=(X=0.000000,Y=0.000000,Z=0.000000),Max=(X=0.000000,Y=0.000000,Z=0.000000),IsValid=0),MBPNumSubdivs=2) +[/Script/NavigationSystem.NavigationSystemV1] +bAllowClientSideNavigation=True diff --git a/Game/Config/DefaultInput.ini b/Game/Config/DefaultInput.ini index 67b296d0..688c6550 100644 --- a/Game/Config/DefaultInput.ini +++ b/Game/Config/DefaultInput.ini @@ -70,11 +70,11 @@ FOVScale=0.011110 DoubleClickTime=0.200000 +ActionMappings=(ActionName="Jump",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=SpaceBar) +ActionMappings=(ActionName="Jump",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Gamepad_FaceButton_Bottom) -+ActionMappings=(ActionName="Fire",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=LeftMouseButton) ++ActionMappings=(ActionName="Primary",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=LeftMouseButton) +ActionMappings=(ActionName="Sprint",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=LeftShift) +ActionMappings=(ActionName="ShowScoreboard",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Tab) +ActionMappings=(ActionName="Crouch",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=LeftControl) -+ActionMappings=(ActionName="Aim",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=RightMouseButton) ++ActionMappings=(ActionName="Secondary",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=RightMouseButton) +ActionMappings=(ActionName="1",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=One) +ActionMappings=(ActionName="2",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Two) +ActionMappings=(ActionName="3",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Three) @@ -90,6 +90,10 @@ DoubleClickTime=0.200000 +ActionMappings=(ActionName="ShowMenu",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=BackSpace) +ActionMappings=(ActionName="ShowMenu",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Escape) +ActionMappings=(ActionName="ShowMenu",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=F12) ++ActionMappings=(ActionName="ScrollUp",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MouseScrollUp) ++ActionMappings=(ActionName="ScrollDown",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MouseScrollDown) ++ActionMappings=(ActionName="QuickToggle",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Q) ++ActionMappings=(ActionName="ToggleMode",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=X) +AxisMappings=(AxisName="MoveForward",Scale=1.000000,Key=W) +AxisMappings=(AxisName="MoveForward",Scale=-1.000000,Key=S) +AxisMappings=(AxisName="MoveForward",Scale=1.000000,Key=Up) diff --git a/Game/Config/DefaultSpatialGDKEditorSettings.ini b/Game/Config/DefaultSpatialGDKEditorSettings.ini new file mode 100644 index 00000000..5651a92d --- /dev/null +++ b/Game/Config/DefaultSpatialGDKEditorSettings.ini @@ -0,0 +1,8 @@ +[/Script/SpatialGDKEditor.SpatialGDKEditorSettings] +bDeleteDynamicEntities=True +bGenerateDefaultLaunchConfig=True +bStopSpatialOnExit=False +SpatialOSSnapshotFile=default.snapshot +LaunchConfigDesc=(Template="w2_r0500_e5",World=(Dimensions=(X=2000,Y=2000),ChunkEdgeLengthMeters=5,StreamingQueryIntervalSeconds=4,SnapshotWritePeriodSeconds=0,LegacyFlags=(("bridge_qos_max_timeout", "0"),("bridge_soft_handover_enabled", "false"),("enable_chunk_interest", "false")),LegacyJavaParams=()),ServerWorkers=((WorkerTypeName="UnrealWorker"),(WorkerTypeName="AIWorker"),(WorkerTypeName="CrashBotWorker"))) +bGeneratePlaceholderEntitiesInSnapshot=True + diff --git a/Game/Config/DefaultSpatialGDKSettings.ini b/Game/Config/DefaultSpatialGDKSettings.ini index c448b6a0..556ee16d 100644 --- a/Game/Config/DefaultSpatialGDKSettings.ini +++ b/Game/Config/DefaultSpatialGDKSettings.ini @@ -1,5 +1,34 @@ - - [/Script/SpatialGDK.SpatialGDKSettings] +EntityPoolInitialReservationCount=3000 +EntityPoolRefreshThreshold=1000 +EntityPoolRefreshCount=2000 +HeartbeatIntervalSeconds=2.000000 +HeartbeatTimeoutSeconds=10.000000 +ActorReplicationRateLimit=0 +EntityCreationRateLimit=0 +OpsUpdateRate=1000.000000 +bEnableHandover=True bUsingQBI=True +PositionUpdateFrequency=1.000000 +PositionDistanceThreshold=100.000000 +bEnableMetrics=True +bEnableMetricsDisplay=False +MetricsReportRate=2.000000 +bUseFrameTimeAsLoad=False +bCheckRPCOrder=False +bBatchSpatialPositionUpdates=True +bEnableServerQBI=True +bPackUnreliableRPCs=True +bUseDevelopmentAuthenticationFlow=False +DevelopmentAuthenticationToken= +DevelopmentDeploymentToConnect= +DefaultWorkerType=(WorkerTypeName="UnrealWorker") +bEnableOffloading=True +ActorGroups=(("AI", (OwningWorkerType=(WorkerTypeName="AIWorker"),ActorClasses=(/Game/Characters/Turret/BP_Turret_Base.BP_Turret_Base_C,/Game/Controllers/BP_TurretController.BP_TurretController_C,/Game/Characters/Turret/BP_TurretShield.BP_TurretShield_C,/Game/Blueprints/Weapons/BP_HeavyMachineGun_ForTurret.BP_HeavyMachineGun_ForTurret_C,/Game/Blueprints/Weapons/Grenades/Turret_Rocket_Propelled.Turret_Rocket_Propelled_C,/Game/Blueprints/Weapons/BP_RocketLauncher_Continuous.BP_RocketLauncher_Continuous_C))),("CrashBot", (OwningWorkerType=(WorkerTypeName="CrashBotWorker"),ActorClasses=(/Game/Characters/BP_CrashBot.BP_CrashBot_C,/Game/Controllers/BP_CrashBotController.BP_CrashBotController_C)))) +ServerWorkerTypes=("UnrealWorker","AIWorker","CrashBotWorker") +MaxDynamicallyAttachedSubobjectsPerClass=3 +bPackRPCs=True +DefaultReceptionistHost=127.0.0.1 +MaxNetCullDistanceSquared=900000000.000000 + diff --git a/Game/Content/AI/BB_NPC.uasset b/Game/Content/AI/BB_NPC.uasset new file mode 100644 index 00000000..9bc185f0 Binary files /dev/null and b/Game/Content/AI/BB_NPC.uasset differ diff --git a/Game/Content/AI/BT_Wander.uasset b/Game/Content/AI/BT_Wander.uasset new file mode 100644 index 00000000..e4024756 Binary files /dev/null and b/Game/Content/AI/BT_Wander.uasset differ diff --git a/Game/Content/AI/Services/BTService_RandomLocation.uasset b/Game/Content/AI/Services/BTService_RandomLocation.uasset new file mode 100644 index 00000000..b1f7082d Binary files /dev/null and b/Game/Content/AI/Services/BTService_RandomLocation.uasset differ diff --git a/Game/Content/Base/MasterMaterials/MF_Object_Distance.uasset b/Game/Content/Base/MasterMaterials/MF_Object_Distance.uasset new file mode 100644 index 00000000..2dd01278 Binary files /dev/null and b/Game/Content/Base/MasterMaterials/MF_Object_Distance.uasset differ diff --git a/Game/Content/Base/MasterMaterials/M_Pickup_MASTER.uasset b/Game/Content/Base/MasterMaterials/M_Pickup_MASTER.uasset index a0077d27..54bb4419 100644 Binary files a/Game/Content/Base/MasterMaterials/M_Pickup_MASTER.uasset and b/Game/Content/Base/MasterMaterials/M_Pickup_MASTER.uasset differ diff --git a/Game/Content/Base/MasterMaterials/M_Spatial_Asset_MASTER.uasset b/Game/Content/Base/MasterMaterials/M_Spatial_Asset_MASTER.uasset index e8ac1d57..8e4222a9 100644 Binary files a/Game/Content/Base/MasterMaterials/M_Spatial_Asset_MASTER.uasset and b/Game/Content/Base/MasterMaterials/M_Spatial_Asset_MASTER.uasset differ diff --git a/Game/Content/Base/MasterMaterials/M_Spatial_Greybox_MASTER.uasset b/Game/Content/Base/MasterMaterials/M_Spatial_Greybox_MASTER.uasset index 541741c7..44ee274c 100644 Binary files a/Game/Content/Base/MasterMaterials/M_Spatial_Greybox_MASTER.uasset and b/Game/Content/Base/MasterMaterials/M_Spatial_Greybox_MASTER.uasset differ diff --git a/Game/Content/Base/MasterMaterials/M_SunlightFunction.uasset b/Game/Content/Base/MasterMaterials/M_SunlightFunction.uasset new file mode 100644 index 00000000..9d722338 Binary files /dev/null and b/Game/Content/Base/MasterMaterials/M_SunlightFunction.uasset differ diff --git a/Game/Content/Base/MasterMaterials/M_TurretShield_MASTER.uasset b/Game/Content/Base/MasterMaterials/M_TurretShield_MASTER.uasset new file mode 100644 index 00000000..9e107c02 Binary files /dev/null and b/Game/Content/Base/MasterMaterials/M_TurretShield_MASTER.uasset differ diff --git a/Game/Content/Base/MaterialFunctions/MF_BlendMaskedDetails.uasset b/Game/Content/Base/MaterialFunctions/MF_BlendMaskedDetails.uasset new file mode 100644 index 00000000..4b66bd03 Binary files /dev/null and b/Game/Content/Base/MaterialFunctions/MF_BlendMaskedDetails.uasset differ diff --git a/Game/Content/Base/MaterialFunctions/MF_MaskRGBCMY.uasset b/Game/Content/Base/MaterialFunctions/MF_MaskRGBCMY.uasset new file mode 100644 index 00000000..2cd5d77e Binary files /dev/null and b/Game/Content/Base/MaterialFunctions/MF_MaskRGBCMY.uasset differ diff --git a/Game/Content/Base/MaterialFunctions/MF_Object_Distance.uasset b/Game/Content/Base/MaterialFunctions/MF_Object_Distance.uasset new file mode 100644 index 00000000..2dd01278 Binary files /dev/null and b/Game/Content/Base/MaterialFunctions/MF_Object_Distance.uasset differ diff --git a/Game/Content/Base/MaterialFunctions/MF_OverlaySimple.uasset b/Game/Content/Base/MaterialFunctions/MF_OverlaySimple.uasset new file mode 100644 index 00000000..fcdf38be Binary files /dev/null and b/Game/Content/Base/MaterialFunctions/MF_OverlaySimple.uasset differ diff --git a/Game/Content/Base/MaterialFunctions/MF_OverlaySimpleRGB.uasset b/Game/Content/Base/MaterialFunctions/MF_OverlaySimpleRGB.uasset new file mode 100644 index 00000000..11222b4c Binary files /dev/null and b/Game/Content/Base/MaterialFunctions/MF_OverlaySimpleRGB.uasset differ diff --git a/Game/Content/Base/MaterialFunctions/MF_Panini_Projection.uasset b/Game/Content/Base/MaterialFunctions/MF_Panini_Projection.uasset new file mode 100644 index 00000000..37a06022 Binary files /dev/null and b/Game/Content/Base/MaterialFunctions/MF_Panini_Projection.uasset differ diff --git a/Game/Content/Base/MaterialFunctions/MF_Scorched.uasset b/Game/Content/Base/MaterialFunctions/MF_Scorched.uasset new file mode 100644 index 00000000..a707bf7c Binary files /dev/null and b/Game/Content/Base/MaterialFunctions/MF_Scorched.uasset differ diff --git a/Game/Content/Base/PostMaterials/M_HighlightOccluded.uasset b/Game/Content/Base/PostMaterials/M_HighlightOccluded.uasset new file mode 100644 index 00000000..b4920324 Binary files /dev/null and b/Game/Content/Base/PostMaterials/M_HighlightOccluded.uasset differ diff --git a/Game/Content/Base/Textures/M_Space.uasset b/Game/Content/Base/Textures/M_Space.uasset new file mode 100644 index 00000000..dda2346b Binary files /dev/null and b/Game/Content/Base/Textures/M_Space.uasset differ diff --git a/Game/Content/Base/Textures/T_Grunge_M.uasset b/Game/Content/Base/Textures/T_Grunge_M.uasset new file mode 100644 index 00000000..96ae4b4f Binary files /dev/null and b/Game/Content/Base/Textures/T_Grunge_M.uasset differ diff --git a/Game/Content/Base/Textures/T_Sunlight_Clouds_BC.uasset b/Game/Content/Base/Textures/T_Sunlight_Clouds_BC.uasset new file mode 100644 index 00000000..1dd3335c Binary files /dev/null and b/Game/Content/Base/Textures/T_Sunlight_Clouds_BC.uasset differ diff --git a/Game/Content/Base/Textures/space_skybox_clr.uasset b/Game/Content/Base/Textures/space_skybox_clr.uasset new file mode 100644 index 00000000..3d491077 Binary files /dev/null and b/Game/Content/Base/Textures/space_skybox_clr.uasset differ diff --git a/Game/Content/Blueprints/BP_BlockoutDoor.uasset b/Game/Content/Blueprints/BP_BlockoutDoor.uasset new file mode 100644 index 00000000..fc00a6b1 Binary files /dev/null and b/Game/Content/Blueprints/BP_BlockoutDoor.uasset differ diff --git a/Game/Content/Blueprints/BP_DamageText.uasset b/Game/Content/Blueprints/BP_DamageText.uasset new file mode 100644 index 00000000..9876fe7f Binary files /dev/null and b/Game/Content/Blueprints/BP_DamageText.uasset differ diff --git a/Game/Content/Blueprints/BP_Door.uasset b/Game/Content/Blueprints/BP_Door.uasset new file mode 100644 index 00000000..89ceb24d Binary files /dev/null and b/Game/Content/Blueprints/BP_Door.uasset differ diff --git a/Game/Content/Blueprints/MapBuilder/BP_MapBuilder.uasset b/Game/Content/Blueprints/MapBuilder/BP_MapBuilder.uasset index 1e4db6aa..48d33f4c 100644 Binary files a/Game/Content/Blueprints/MapBuilder/BP_MapBuilder.uasset and b/Game/Content/Blueprints/MapBuilder/BP_MapBuilder.uasset differ diff --git a/Game/Content/Blueprints/PhasedFloorSection.uasset b/Game/Content/Blueprints/PhasedFloorSection.uasset new file mode 100644 index 00000000..a1682287 Binary files /dev/null and b/Game/Content/Blueprints/PhasedFloorSection.uasset differ diff --git a/Game/Content/Blueprints/ShieldPickup.uasset b/Game/Content/Blueprints/ShieldPickup.uasset index 0dbdcfb4..5bebbd83 100644 Binary files a/Game/Content/Blueprints/ShieldPickup.uasset and b/Game/Content/Blueprints/ShieldPickup.uasset differ diff --git a/Game/Content/Blueprints/Weapons/BP_AutomaticRifle.uasset b/Game/Content/Blueprints/Weapons/BP_AutomaticRifle.uasset index f0e5cc35..3df34cc5 100644 Binary files a/Game/Content/Blueprints/Weapons/BP_AutomaticRifle.uasset and b/Game/Content/Blueprints/Weapons/BP_AutomaticRifle.uasset differ diff --git a/Game/Content/Blueprints/Weapons/BP_GrenadeLauncher_Frag.uasset b/Game/Content/Blueprints/Weapons/BP_GrenadeLauncher_Frag.uasset index ae4a6fa1..da3a2a3d 100644 Binary files a/Game/Content/Blueprints/Weapons/BP_GrenadeLauncher_Frag.uasset and b/Game/Content/Blueprints/Weapons/BP_GrenadeLauncher_Frag.uasset differ diff --git a/Game/Content/Blueprints/Weapons/BP_HeavyMachineGun.uasset b/Game/Content/Blueprints/Weapons/BP_HeavyMachineGun.uasset index c4da7f5f..f00d74d1 100644 Binary files a/Game/Content/Blueprints/Weapons/BP_HeavyMachineGun.uasset and b/Game/Content/Blueprints/Weapons/BP_HeavyMachineGun.uasset differ diff --git a/Game/Content/Blueprints/Weapons/BP_HeavyMachineGun_ForTurret.uasset b/Game/Content/Blueprints/Weapons/BP_HeavyMachineGun_ForTurret.uasset new file mode 100644 index 00000000..4eac5765 Binary files /dev/null and b/Game/Content/Blueprints/Weapons/BP_HeavyMachineGun_ForTurret.uasset differ diff --git a/Game/Content/Blueprints/Weapons/BP_InstantWeapon.uasset b/Game/Content/Blueprints/Weapons/BP_InstantWeapon.uasset index 024cd75c..0b5b8c7e 100644 Binary files a/Game/Content/Blueprints/Weapons/BP_InstantWeapon.uasset and b/Game/Content/Blueprints/Weapons/BP_InstantWeapon.uasset differ diff --git a/Game/Content/Blueprints/Weapons/BP_ProjectileWeapon.uasset b/Game/Content/Blueprints/Weapons/BP_ProjectileWeapon.uasset index b1704c9a..dbda8df4 100644 Binary files a/Game/Content/Blueprints/Weapons/BP_ProjectileWeapon.uasset and b/Game/Content/Blueprints/Weapons/BP_ProjectileWeapon.uasset differ diff --git a/Game/Content/Blueprints/Weapons/BP_RailGun.uasset b/Game/Content/Blueprints/Weapons/BP_RailGun.uasset index 8f0165a1..57cf7c50 100644 Binary files a/Game/Content/Blueprints/Weapons/BP_RailGun.uasset and b/Game/Content/Blueprints/Weapons/BP_RailGun.uasset differ diff --git a/Game/Content/Blueprints/Weapons/BP_RocketLauncher.uasset b/Game/Content/Blueprints/Weapons/BP_RocketLauncher.uasset index 15a951a5..d94f4529 100644 Binary files a/Game/Content/Blueprints/Weapons/BP_RocketLauncher.uasset and b/Game/Content/Blueprints/Weapons/BP_RocketLauncher.uasset differ diff --git a/Game/Content/Blueprints/Weapons/BP_RocketLauncher_Continuous.uasset b/Game/Content/Blueprints/Weapons/BP_RocketLauncher_Continuous.uasset new file mode 100644 index 00000000..738867d4 Binary files /dev/null and b/Game/Content/Blueprints/Weapons/BP_RocketLauncher_Continuous.uasset differ diff --git a/Game/Content/Blueprints/Weapons/BP_SniperRifle.uasset b/Game/Content/Blueprints/Weapons/BP_SniperRifle.uasset index 9266a149..698d91c2 100644 Binary files a/Game/Content/Blueprints/Weapons/BP_SniperRifle.uasset and b/Game/Content/Blueprints/Weapons/BP_SniperRifle.uasset differ diff --git a/Game/Content/Blueprints/Weapons/BP_SubMachineGun.uasset b/Game/Content/Blueprints/Weapons/BP_SubMachineGun.uasset index f5cd3c1e..c5c1849a 100644 Binary files a/Game/Content/Blueprints/Weapons/BP_SubMachineGun.uasset and b/Game/Content/Blueprints/Weapons/BP_SubMachineGun.uasset differ diff --git a/Game/Content/Blueprints/Weapons/BP_TripleShot.uasset b/Game/Content/Blueprints/Weapons/BP_TripleShot.uasset index 6891ddbe..a93bf4d3 100644 Binary files a/Game/Content/Blueprints/Weapons/BP_TripleShot.uasset and b/Game/Content/Blueprints/Weapons/BP_TripleShot.uasset differ diff --git a/Game/Content/Blueprints/Weapons/Grenades/BP_Projectile.uasset b/Game/Content/Blueprints/Weapons/Grenades/BP_Projectile.uasset index a84e7be6..23f0e49b 100644 Binary files a/Game/Content/Blueprints/Weapons/Grenades/BP_Projectile.uasset and b/Game/Content/Blueprints/Weapons/Grenades/BP_Projectile.uasset differ diff --git a/Game/Content/Blueprints/Weapons/Grenades/Grenade_Sticky.uasset b/Game/Content/Blueprints/Weapons/Grenades/Grenade_Sticky.uasset index ab8f30bd..3970d92e 100644 Binary files a/Game/Content/Blueprints/Weapons/Grenades/Grenade_Sticky.uasset and b/Game/Content/Blueprints/Weapons/Grenades/Grenade_Sticky.uasset differ diff --git a/Game/Content/Blueprints/Weapons/Grenades/Turret_Rocket_Propelled.uasset b/Game/Content/Blueprints/Weapons/Grenades/Turret_Rocket_Propelled.uasset new file mode 100644 index 00000000..234d8ec0 Binary files /dev/null and b/Game/Content/Blueprints/Weapons/Grenades/Turret_Rocket_Propelled.uasset differ diff --git a/Game/Content/Characters/BP_Base_Character.uasset b/Game/Content/Characters/BP_Base_Character.uasset new file mode 100644 index 00000000..2635b036 Binary files /dev/null and b/Game/Content/Characters/BP_Base_Character.uasset differ diff --git a/Game/Content/Characters/BP_CrashBot.uasset b/Game/Content/Characters/BP_CrashBot.uasset new file mode 100644 index 00000000..8158cb5f Binary files /dev/null and b/Game/Content/Characters/BP_CrashBot.uasset differ diff --git a/Game/Content/Characters/BP_CrashBot_PlacedInWorld.uasset b/Game/Content/Characters/BP_CrashBot_PlacedInWorld.uasset new file mode 100644 index 00000000..6b9877cb Binary files /dev/null and b/Game/Content/Characters/BP_CrashBot_PlacedInWorld.uasset differ diff --git a/Game/Content/Characters/BP_FPS_Character.uasset b/Game/Content/Characters/BP_FPS_Character.uasset index 91622feb..96e952c1 100644 Binary files a/Game/Content/Characters/BP_FPS_Character.uasset and b/Game/Content/Characters/BP_FPS_Character.uasset differ diff --git a/Game/Content/Characters/BP_SimulatedPlayerCharacter.uasset b/Game/Content/Characters/BP_SimulatedPlayerCharacter.uasset new file mode 100644 index 00000000..614c061d Binary files /dev/null and b/Game/Content/Characters/BP_SimulatedPlayerCharacter.uasset differ diff --git a/Game/Content/Characters/Components/BPI_OnHover.uasset b/Game/Content/Characters/Components/BPI_OnHover.uasset new file mode 100644 index 00000000..3c1ba8b0 Binary files /dev/null and b/Game/Content/Characters/Components/BPI_OnHover.uasset differ diff --git a/Game/Content/Characters/Components/BP_FloatingCombatText.uasset b/Game/Content/Characters/Components/BP_FloatingCombatText.uasset new file mode 100644 index 00000000..cc7f7aa3 Binary files /dev/null and b/Game/Content/Characters/Components/BP_FloatingCombatText.uasset differ diff --git a/Game/Content/Characters/Player/Animations/AnimationBlueprints/CH_Robot_AnimBlueprint.uasset b/Game/Content/Characters/Player/Animations/AnimationBlueprints/CH_Robot_AnimBlueprint.uasset index 68ae4a16..f7fab24c 100644 Binary files a/Game/Content/Characters/Player/Animations/AnimationBlueprints/CH_Robot_AnimBlueprint.uasset and b/Game/Content/Characters/Player/Animations/AnimationBlueprints/CH_Robot_AnimBlueprint.uasset differ diff --git a/Game/Content/Characters/Player/Animations/AnimationBlueprints/CH_Robot_FirstPerson_AnimBlueprint.uasset b/Game/Content/Characters/Player/Animations/AnimationBlueprints/CH_Robot_FirstPerson_AnimBlueprint.uasset index 635f5ece..3e1d2030 100644 Binary files a/Game/Content/Characters/Player/Animations/AnimationBlueprints/CH_Robot_FirstPerson_AnimBlueprint.uasset and b/Game/Content/Characters/Player/Animations/AnimationBlueprints/CH_Robot_FirstPerson_AnimBlueprint.uasset differ diff --git a/Game/Content/Characters/Player/Materials/CH_MI_AIRobot.uasset b/Game/Content/Characters/Player/Materials/CH_MI_AIRobot.uasset new file mode 100644 index 00000000..c4f5000b Binary files /dev/null and b/Game/Content/Characters/Player/Materials/CH_MI_AIRobot.uasset differ diff --git a/Game/Content/Characters/Player/Materials/CH_MI_PlayerRobot.uasset b/Game/Content/Characters/Player/Materials/CH_MI_PlayerRobot.uasset index dca2c7d9..62271555 100644 Binary files a/Game/Content/Characters/Player/Materials/CH_MI_PlayerRobot.uasset and b/Game/Content/Characters/Player/Materials/CH_MI_PlayerRobot.uasset differ diff --git a/Game/Content/Characters/Player/Materials/CH_MI_TestRobot.uasset b/Game/Content/Characters/Player/Materials/CH_MI_TestRobot.uasset new file mode 100644 index 00000000..0bffa464 Binary files /dev/null and b/Game/Content/Characters/Player/Materials/CH_MI_TestRobot.uasset differ diff --git a/Game/Content/Characters/Player/Meshes/CH_SM_AI_Faceplate.uasset b/Game/Content/Characters/Player/Meshes/CH_SM_AI_Faceplate.uasset new file mode 100644 index 00000000..d961dc39 Binary files /dev/null and b/Game/Content/Characters/Player/Meshes/CH_SM_AI_Faceplate.uasset differ diff --git a/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_AORM.uasset b/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_AORM.uasset index 5693e64e..b84d7840 100644 Binary files a/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_AORM.uasset and b/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_AORM.uasset differ diff --git a/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_BC.uasset b/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_BC.uasset index ce108aea..4e9b494b 100644 Binary files a/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_BC.uasset and b/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_BC.uasset differ diff --git a/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_L.uasset b/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_L.uasset index 43290c0b..1e285113 100644 Binary files a/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_L.uasset and b/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_L.uasset differ diff --git a/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_M.uasset b/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_M.uasset index becb0a61..70dac401 100644 Binary files a/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_M.uasset and b/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_M.uasset differ diff --git a/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_N.uasset b/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_N.uasset index 3057a715..2c52763a 100644 Binary files a/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_N.uasset and b/Game/Content/Characters/Player/Textures/CH_T_PlayerRobot_N.uasset differ diff --git a/Game/Content/Characters/Player/Textures/CH_T_TestRobot_M.uasset b/Game/Content/Characters/Player/Textures/CH_T_TestRobot_M.uasset new file mode 100644 index 00000000..14332832 Binary files /dev/null and b/Game/Content/Characters/Player/Textures/CH_T_TestRobot_M.uasset differ diff --git a/Game/Content/Characters/SimulatedPlayer/SimulatedPlayerBehaviorTree_BP.uasset b/Game/Content/Characters/SimulatedPlayer/SimulatedPlayerBehaviorTree_BP.uasset new file mode 100644 index 00000000..c12b475d Binary files /dev/null and b/Game/Content/Characters/SimulatedPlayer/SimulatedPlayerBehaviorTree_BP.uasset differ diff --git a/Game/Content/Characters/SimulatedPlayer/SimulatedPlayerBlackboard_BP.uasset b/Game/Content/Characters/SimulatedPlayer/SimulatedPlayerBlackboard_BP.uasset new file mode 100644 index 00000000..de61f3a2 Binary files /dev/null and b/Game/Content/Characters/SimulatedPlayer/SimulatedPlayerBlackboard_BP.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/AnimBP_Turret_Ceiling.uasset b/Game/Content/Characters/Turret/Animations/AnimBP_Turret_Ceiling.uasset new file mode 100644 index 00000000..5ac93b06 Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/AnimBP_Turret_Ceiling.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/AnimBP_Turret_Floor.uasset b/Game/Content/Characters/Turret/Animations/AnimBP_Turret_Floor.uasset new file mode 100644 index 00000000..959a1940 Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/AnimBP_Turret_Floor.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/BS_Turret_Ceiling_Aim.uasset b/Game/Content/Characters/Turret/Animations/BS_Turret_Ceiling_Aim.uasset new file mode 100644 index 00000000..bffc27ea Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/BS_Turret_Ceiling_Aim.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/BS_Turret_Floor_Aim.uasset b/Game/Content/Characters/Turret/Animations/BS_Turret_Floor_Aim.uasset new file mode 100644 index 00000000..d2f58b8b Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/BS_Turret_Floor_Aim.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_0_0.uasset b/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_0_0.uasset new file mode 100644 index 00000000..d6db31d8 Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_0_0.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_0_D60.uasset b/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_0_D60.uasset new file mode 100644 index 00000000..f2d034f6 Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_0_D60.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_0_U60.uasset b/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_0_U60.uasset new file mode 100644 index 00000000..7fdda61f Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_0_U60.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_L180_0.uasset b/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_L180_0.uasset new file mode 100644 index 00000000..2039b761 Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_L180_0.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_L180_D60.uasset b/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_L180_D60.uasset new file mode 100644 index 00000000..e3fe33c6 Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_L180_D60.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_L180_U60.uasset b/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_L180_U60.uasset new file mode 100644 index 00000000..8d4473b5 Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_L180_U60.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_R180_0.uasset b/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_R180_0.uasset new file mode 100644 index 00000000..a5f4d344 Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_R180_0.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_R180_D60.uasset b/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_R180_D60.uasset new file mode 100644 index 00000000..d08e6db6 Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_R180_D60.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_R180_U60.uasset b/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_R180_U60.uasset new file mode 100644 index 00000000..4ea5111b Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/SK_Turret_Ceiling_Aim_R180_U60.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_0_0.uasset b/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_0_0.uasset new file mode 100644 index 00000000..5f32a90b Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_0_0.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_0_D60.uasset b/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_0_D60.uasset new file mode 100644 index 00000000..14850dd2 Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_0_D60.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_0_U60.uasset b/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_0_U60.uasset new file mode 100644 index 00000000..43be8889 Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_0_U60.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_L180_0.uasset b/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_L180_0.uasset new file mode 100644 index 00000000..5a481471 Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_L180_0.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_L180_D60.uasset b/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_L180_D60.uasset new file mode 100644 index 00000000..f2073a8b Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_L180_D60.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_L180_U60.uasset b/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_L180_U60.uasset new file mode 100644 index 00000000..1306edda Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_L180_U60.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_R180_0.uasset b/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_R180_0.uasset new file mode 100644 index 00000000..937462cc Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_R180_0.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_R180_D60.uasset b/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_R180_D60.uasset new file mode 100644 index 00000000..8faa23d9 Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_R180_D60.uasset differ diff --git a/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_R180_U60.uasset b/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_R180_U60.uasset new file mode 100644 index 00000000..99a5f863 Binary files /dev/null and b/Game/Content/Characters/Turret/Animations/SK_Turret_Floor_Aim_R180_U60.uasset differ diff --git a/Game/Content/Characters/Turret/BB_Turret.uasset b/Game/Content/Characters/Turret/BB_Turret.uasset new file mode 100644 index 00000000..ac6dc0f2 Binary files /dev/null and b/Game/Content/Characters/Turret/BB_Turret.uasset differ diff --git a/Game/Content/Characters/Turret/BP_TurretShield.uasset b/Game/Content/Characters/Turret/BP_TurretShield.uasset new file mode 100644 index 00000000..2239d32e Binary files /dev/null and b/Game/Content/Characters/Turret/BP_TurretShield.uasset differ diff --git a/Game/Content/Characters/Turret/BP_Turret_Base.uasset b/Game/Content/Characters/Turret/BP_Turret_Base.uasset new file mode 100644 index 00000000..019afecd Binary files /dev/null and b/Game/Content/Characters/Turret/BP_Turret_Base.uasset differ diff --git a/Game/Content/Characters/Turret/BP_Turret_Ceiling_Rifle.uasset b/Game/Content/Characters/Turret/BP_Turret_Ceiling_Rifle.uasset new file mode 100644 index 00000000..45bc08c7 Binary files /dev/null and b/Game/Content/Characters/Turret/BP_Turret_Ceiling_Rifle.uasset differ diff --git a/Game/Content/Characters/Turret/BP_Turret_Floor.uasset b/Game/Content/Characters/Turret/BP_Turret_Floor.uasset new file mode 100644 index 00000000..278e893c Binary files /dev/null and b/Game/Content/Characters/Turret/BP_Turret_Floor.uasset differ diff --git a/Game/Content/Characters/Turret/BP_Turret_Floor_Rifle.uasset b/Game/Content/Characters/Turret/BP_Turret_Floor_Rifle.uasset new file mode 100644 index 00000000..e2322d1d Binary files /dev/null and b/Game/Content/Characters/Turret/BP_Turret_Floor_Rifle.uasset differ diff --git a/Game/Content/Characters/Turret/BT_Turret.uasset b/Game/Content/Characters/Turret/BT_Turret.uasset new file mode 100644 index 00000000..94aa74a5 Binary files /dev/null and b/Game/Content/Characters/Turret/BT_Turret.uasset differ diff --git a/Game/Content/Characters/Turret/Materials/MI_TurretShield.uasset b/Game/Content/Characters/Turret/Materials/MI_TurretShield.uasset new file mode 100644 index 00000000..086ea9fe Binary files /dev/null and b/Game/Content/Characters/Turret/Materials/MI_TurretShield.uasset differ diff --git a/Game/Content/Characters/Turret/Materials/MI_Turrets.uasset b/Game/Content/Characters/Turret/Materials/MI_Turrets.uasset new file mode 100644 index 00000000..2914f7ec Binary files /dev/null and b/Game/Content/Characters/Turret/Materials/MI_Turrets.uasset differ diff --git a/Game/Content/Characters/Turret/Meshes/SK_Turret_Ceiling_Machinegun.uasset b/Game/Content/Characters/Turret/Meshes/SK_Turret_Ceiling_Machinegun.uasset new file mode 100644 index 00000000..035f548e Binary files /dev/null and b/Game/Content/Characters/Turret/Meshes/SK_Turret_Ceiling_Machinegun.uasset differ diff --git a/Game/Content/Characters/Turret/Meshes/SK_Turret_Ceiling_Machinegun_PhysicsAsset.uasset b/Game/Content/Characters/Turret/Meshes/SK_Turret_Ceiling_Machinegun_PhysicsAsset.uasset new file mode 100644 index 00000000..e7c3ec70 Binary files /dev/null and b/Game/Content/Characters/Turret/Meshes/SK_Turret_Ceiling_Machinegun_PhysicsAsset.uasset differ diff --git a/Game/Content/Characters/Turret/Meshes/SK_Turret_Ceiling_Rocket.uasset b/Game/Content/Characters/Turret/Meshes/SK_Turret_Ceiling_Rocket.uasset new file mode 100644 index 00000000..a41ef8a7 Binary files /dev/null and b/Game/Content/Characters/Turret/Meshes/SK_Turret_Ceiling_Rocket.uasset differ diff --git a/Game/Content/Characters/Turret/Meshes/SK_Turret_Ceiling_Rocket_PhysicsAsset.uasset b/Game/Content/Characters/Turret/Meshes/SK_Turret_Ceiling_Rocket_PhysicsAsset.uasset new file mode 100644 index 00000000..59eb3058 Binary files /dev/null and b/Game/Content/Characters/Turret/Meshes/SK_Turret_Ceiling_Rocket_PhysicsAsset.uasset differ diff --git a/Game/Content/Characters/Turret/Meshes/SK_Turret_Ceiling_Skeleton.uasset b/Game/Content/Characters/Turret/Meshes/SK_Turret_Ceiling_Skeleton.uasset new file mode 100644 index 00000000..4f7d6d1e Binary files /dev/null and b/Game/Content/Characters/Turret/Meshes/SK_Turret_Ceiling_Skeleton.uasset differ diff --git a/Game/Content/Characters/Turret/Meshes/SK_Turret_Floor_Machinegun.uasset b/Game/Content/Characters/Turret/Meshes/SK_Turret_Floor_Machinegun.uasset new file mode 100644 index 00000000..68b607f7 Binary files /dev/null and b/Game/Content/Characters/Turret/Meshes/SK_Turret_Floor_Machinegun.uasset differ diff --git a/Game/Content/Characters/Turret/Meshes/SK_Turret_Floor_Machinegun_PhysicsAsset.uasset b/Game/Content/Characters/Turret/Meshes/SK_Turret_Floor_Machinegun_PhysicsAsset.uasset new file mode 100644 index 00000000..cfc60326 Binary files /dev/null and b/Game/Content/Characters/Turret/Meshes/SK_Turret_Floor_Machinegun_PhysicsAsset.uasset differ diff --git a/Game/Content/Characters/Turret/Meshes/SK_Turret_Floor_Rocket.uasset b/Game/Content/Characters/Turret/Meshes/SK_Turret_Floor_Rocket.uasset new file mode 100644 index 00000000..07a22a70 Binary files /dev/null and b/Game/Content/Characters/Turret/Meshes/SK_Turret_Floor_Rocket.uasset differ diff --git a/Game/Content/Characters/Turret/Meshes/SK_Turret_Floor_Rocket_PhysicsAsset.uasset b/Game/Content/Characters/Turret/Meshes/SK_Turret_Floor_Rocket_PhysicsAsset.uasset new file mode 100644 index 00000000..fe16dacb Binary files /dev/null and b/Game/Content/Characters/Turret/Meshes/SK_Turret_Floor_Rocket_PhysicsAsset.uasset differ diff --git a/Game/Content/Characters/Turret/Meshes/SK_Turret_Floor_Skeleton.uasset b/Game/Content/Characters/Turret/Meshes/SK_Turret_Floor_Skeleton.uasset new file mode 100644 index 00000000..0515e339 Binary files /dev/null and b/Game/Content/Characters/Turret/Meshes/SK_Turret_Floor_Skeleton.uasset differ diff --git a/Game/Content/Characters/Turret/Textures/T_Turrets_AORM.uasset b/Game/Content/Characters/Turret/Textures/T_Turrets_AORM.uasset new file mode 100644 index 00000000..0c30b2cc Binary files /dev/null and b/Game/Content/Characters/Turret/Textures/T_Turrets_AORM.uasset differ diff --git a/Game/Content/Characters/Turret/Textures/T_Turrets_BC.uasset b/Game/Content/Characters/Turret/Textures/T_Turrets_BC.uasset new file mode 100644 index 00000000..f93cab2d Binary files /dev/null and b/Game/Content/Characters/Turret/Textures/T_Turrets_BC.uasset differ diff --git a/Game/Content/Characters/Turret/Textures/T_Turrets_L.uasset b/Game/Content/Characters/Turret/Textures/T_Turrets_L.uasset new file mode 100644 index 00000000..7c397f9d Binary files /dev/null and b/Game/Content/Characters/Turret/Textures/T_Turrets_L.uasset differ diff --git a/Game/Content/Characters/Turret/Textures/T_Turrets_M.uasset b/Game/Content/Characters/Turret/Textures/T_Turrets_M.uasset new file mode 100644 index 00000000..b9bafa83 Binary files /dev/null and b/Game/Content/Characters/Turret/Textures/T_Turrets_M.uasset differ diff --git a/Game/Content/Characters/Turret/Textures/T_Turrets_N.uasset b/Game/Content/Characters/Turret/Textures/T_Turrets_N.uasset new file mode 100644 index 00000000..b08619e9 Binary files /dev/null and b/Game/Content/Characters/Turret/Textures/T_Turrets_N.uasset differ diff --git a/Game/Content/Characters/Turret/TurretTraceProvider.uasset b/Game/Content/Characters/Turret/TurretTraceProvider.uasset new file mode 100644 index 00000000..33ab306f Binary files /dev/null and b/Game/Content/Characters/Turret/TurretTraceProvider.uasset differ diff --git a/Game/Content/Controllers/BP_CrashBotController.uasset b/Game/Content/Controllers/BP_CrashBotController.uasset new file mode 100644 index 00000000..3aef406e Binary files /dev/null and b/Game/Content/Controllers/BP_CrashBotController.uasset differ diff --git a/Game/Content/Controllers/BP_GDK_PlayerController.uasset b/Game/Content/Controllers/BP_GDK_PlayerController.uasset index 09e9bc6a..ef3e8ea4 100644 Binary files a/Game/Content/Controllers/BP_GDK_PlayerController.uasset and b/Game/Content/Controllers/BP_GDK_PlayerController.uasset differ diff --git a/Game/Content/Controllers/BP_SimulatedPlayerAIController.uasset b/Game/Content/Controllers/BP_SimulatedPlayerAIController.uasset new file mode 100644 index 00000000..0e5c57e2 Binary files /dev/null and b/Game/Content/Controllers/BP_SimulatedPlayerAIController.uasset differ diff --git a/Game/Content/Controllers/BP_TurretController.uasset b/Game/Content/Controllers/BP_TurretController.uasset new file mode 100644 index 00000000..d7d0b537 Binary files /dev/null and b/Game/Content/Controllers/BP_TurretController.uasset differ diff --git a/Game/Content/Controllers/Components/MenuMediator.uasset b/Game/Content/Controllers/Components/MenuMediator.uasset new file mode 100644 index 00000000..35fa1439 Binary files /dev/null and b/Game/Content/Controllers/Components/MenuMediator.uasset differ diff --git a/Game/Content/Controllers/Components/Menus.uasset b/Game/Content/Controllers/Components/Menus.uasset new file mode 100644 index 00000000..7284e3a5 Binary files /dev/null and b/Game/Content/Controllers/Components/Menus.uasset differ diff --git a/Game/Content/Environment/Terrain/Materials/MI_GreenFloor.uasset b/Game/Content/Environment/Terrain/Materials/MI_GreenFloor.uasset new file mode 100644 index 00000000..e5bbb7d4 Binary files /dev/null and b/Game/Content/Environment/Terrain/Materials/MI_GreenFloor.uasset differ diff --git a/Game/Content/Environment/Terrain/Materials/MI_OrangeFloor.uasset b/Game/Content/Environment/Terrain/Materials/MI_OrangeFloor.uasset new file mode 100644 index 00000000..d933000c Binary files /dev/null and b/Game/Content/Environment/Terrain/Materials/MI_OrangeFloor.uasset differ diff --git a/Game/Content/Environment/Terrain/Materials/MI_RedFloor.uasset b/Game/Content/Environment/Terrain/Materials/MI_RedFloor.uasset new file mode 100644 index 00000000..51b22566 Binary files /dev/null and b/Game/Content/Environment/Terrain/Materials/MI_RedFloor.uasset differ diff --git a/Game/Content/GameMode/BP_CorridorGameMode.uasset b/Game/Content/GameMode/BP_CorridorGameMode.uasset new file mode 100644 index 00000000..63f5bfae Binary files /dev/null and b/Game/Content/GameMode/BP_CorridorGameMode.uasset differ diff --git a/Game/Content/GameMode/BP_CorridorGameState.uasset b/Game/Content/GameMode/BP_CorridorGameState.uasset new file mode 100644 index 00000000..aa9973b5 Binary files /dev/null and b/Game/Content/GameMode/BP_CorridorGameState.uasset differ diff --git a/Game/Content/GameMode/BP_DeathmatchGameMode.uasset b/Game/Content/GameMode/BP_DeathmatchGameMode.uasset new file mode 100644 index 00000000..e711ea1f Binary files /dev/null and b/Game/Content/GameMode/BP_DeathmatchGameMode.uasset differ diff --git a/Game/Content/GameMode/BP_DeathmatchGameState.uasset b/Game/Content/GameMode/BP_DeathmatchGameState.uasset new file mode 100644 index 00000000..b5ace0f7 Binary files /dev/null and b/Game/Content/GameMode/BP_DeathmatchGameState.uasset differ diff --git a/Game/Content/GameMode/BP_DeploymentGameMode.uasset b/Game/Content/GameMode/BP_DeploymentGameMode.uasset index 483b2e17..9d9b663a 100644 Binary files a/Game/Content/GameMode/BP_DeploymentGameMode.uasset and b/Game/Content/GameMode/BP_DeploymentGameMode.uasset differ diff --git a/Game/Content/GameMode/BP_GDKGameMode.uasset b/Game/Content/GameMode/BP_GDKGameMode.uasset deleted file mode 100644 index 3705506b..00000000 Binary files a/Game/Content/GameMode/BP_GDKGameMode.uasset and /dev/null differ diff --git a/Game/Content/GameMode/BP_GDKGameState.uasset b/Game/Content/GameMode/BP_GDKGameState.uasset deleted file mode 100644 index a321b05b..00000000 Binary files a/Game/Content/GameMode/BP_GDKGameState.uasset and /dev/null differ diff --git a/Game/Content/GameMode/BP_GymGameMode.uasset b/Game/Content/GameMode/BP_GymGameMode.uasset new file mode 100644 index 00000000..620cb808 Binary files /dev/null and b/Game/Content/GameMode/BP_GymGameMode.uasset differ diff --git a/Game/Content/GameMode/BP_GymGameState.uasset b/Game/Content/GameMode/BP_GymGameState.uasset new file mode 100644 index 00000000..e1a63c5b Binary files /dev/null and b/Game/Content/GameMode/BP_GymGameState.uasset differ diff --git a/Game/Content/GameMode/BP_PlayerState.uasset b/Game/Content/GameMode/BP_PlayerState.uasset new file mode 100644 index 00000000..17798b82 Binary files /dev/null and b/Game/Content/GameMode/BP_PlayerState.uasset differ diff --git a/Game/Content/GameMode/Components/PhaseBasedMapComponent.uasset b/Game/Content/GameMode/Components/PhaseBasedMapComponent.uasset new file mode 100644 index 00000000..4429dbbd Binary files /dev/null and b/Game/Content/GameMode/Components/PhaseBasedMapComponent.uasset differ diff --git a/Game/Content/GameMode/Components/PhaseData.uasset b/Game/Content/GameMode/Components/PhaseData.uasset new file mode 100644 index 00000000..1ae6ccad Binary files /dev/null and b/Game/Content/GameMode/Components/PhaseData.uasset differ diff --git a/Game/Content/GameMode/Components/PhaseEntry.uasset b/Game/Content/GameMode/Components/PhaseEntry.uasset new file mode 100644 index 00000000..efff62b8 Binary files /dev/null and b/Game/Content/GameMode/Components/PhaseEntry.uasset differ diff --git a/Game/Content/GameMode/Components/SquadTrackingComponent.uasset b/Game/Content/GameMode/Components/SquadTrackingComponent.uasset new file mode 100644 index 00000000..36b60faf Binary files /dev/null and b/Game/Content/GameMode/Components/SquadTrackingComponent.uasset differ diff --git a/Game/Content/Maps/Corridor_Gym.umap b/Game/Content/Maps/Corridor_Gym.umap new file mode 100644 index 00000000..46f2461c Binary files /dev/null and b/Game/Content/Maps/Corridor_Gym.umap differ diff --git a/Game/Content/Maps/Corridor_Gym_BuiltData.uasset b/Game/Content/Maps/Corridor_Gym_BuiltData.uasset new file mode 100644 index 00000000..ef045370 Binary files /dev/null and b/Game/Content/Maps/Corridor_Gym_BuiltData.uasset differ diff --git a/Game/Content/Maps/CrashBot_Gym.umap b/Game/Content/Maps/CrashBot_Gym.umap new file mode 100644 index 00000000..87bbbd2d Binary files /dev/null and b/Game/Content/Maps/CrashBot_Gym.umap differ diff --git a/Game/Content/Maps/CrashBot_Gym_BuiltData.uasset b/Game/Content/Maps/CrashBot_Gym_BuiltData.uasset new file mode 100644 index 00000000..8ce46341 Binary files /dev/null and b/Game/Content/Maps/CrashBot_Gym_BuiltData.uasset differ diff --git a/Game/Content/Maps/FPS-Start_Medium.umap b/Game/Content/Maps/FPS-Start_Medium.umap index f40657e2..4332f32b 100644 Binary files a/Game/Content/Maps/FPS-Start_Medium.umap and b/Game/Content/Maps/FPS-Start_Medium.umap differ diff --git a/Game/Content/Maps/FPS-Start_Medium_BuiltData.uasset b/Game/Content/Maps/FPS-Start_Medium_BuiltData.uasset index 9c37540f..af33bd09 100644 Binary files a/Game/Content/Maps/FPS-Start_Medium_BuiltData.uasset and b/Game/Content/Maps/FPS-Start_Medium_BuiltData.uasset differ diff --git a/Game/Content/Maps/FPS-Start_Tiny.umap b/Game/Content/Maps/FPS-Start_Tiny.umap index 541e370c..daf48fcc 100644 Binary files a/Game/Content/Maps/FPS-Start_Tiny.umap and b/Game/Content/Maps/FPS-Start_Tiny.umap differ diff --git a/Game/Content/Maps/FPS-Start_Tiny_BuiltData.uasset b/Game/Content/Maps/FPS-Start_Tiny_BuiltData.uasset index 4b612e46..89111fff 100644 Binary files a/Game/Content/Maps/FPS-Start_Tiny_BuiltData.uasset and b/Game/Content/Maps/FPS-Start_Tiny_BuiltData.uasset differ diff --git a/Game/Content/Maps/Offloading_Gym.umap b/Game/Content/Maps/Offloading_Gym.umap new file mode 100644 index 00000000..abc74a4b Binary files /dev/null and b/Game/Content/Maps/Offloading_Gym.umap differ diff --git a/Game/Content/Maps/Offloading_Gym_BuiltData.uasset b/Game/Content/Maps/Offloading_Gym_BuiltData.uasset new file mode 100644 index 00000000..e4a3fb33 Binary files /dev/null and b/Game/Content/Maps/Offloading_Gym_BuiltData.uasset differ diff --git a/Game/Content/UI/Blueprints/BP_GDK_HUD.uasset b/Game/Content/UI/Blueprints/BP_GDK_HUD.uasset index 2979dc56..53b27ec8 100644 Binary files a/Game/Content/UI/Blueprints/BP_GDK_HUD.uasset and b/Game/Content/UI/Blueprints/BP_GDK_HUD.uasset differ diff --git a/Game/Content/UI/Blueprints/BP_HUD.uasset b/Game/Content/UI/Blueprints/BP_HUD.uasset index ca20a37d..5d761732 100644 Binary files a/Game/Content/UI/Blueprints/BP_HUD.uasset and b/Game/Content/UI/Blueprints/BP_HUD.uasset differ diff --git a/Game/Content/UI/Blueprints/BP_HUD_CorridorShooter.uasset b/Game/Content/UI/Blueprints/BP_HUD_CorridorShooter.uasset new file mode 100644 index 00000000..040d0f6c Binary files /dev/null and b/Game/Content/UI/Blueprints/BP_HUD_CorridorShooter.uasset differ diff --git a/Game/Content/UI/Blueprints/BP_HUD_Deathmatch.uasset b/Game/Content/UI/Blueprints/BP_HUD_Deathmatch.uasset index a72b5fd2..abaebb6d 100644 Binary files a/Game/Content/UI/Blueprints/BP_HUD_Deathmatch.uasset and b/Game/Content/UI/Blueprints/BP_HUD_Deathmatch.uasset differ diff --git a/Game/Content/UI/Blueprints/BP_HUD_Gym.uasset b/Game/Content/UI/Blueprints/BP_HUD_Gym.uasset new file mode 100644 index 00000000..41ab5414 Binary files /dev/null and b/Game/Content/UI/Blueprints/BP_HUD_Gym.uasset differ diff --git a/Game/Content/UI/Blueprints/BP_Minimal_HUD.uasset b/Game/Content/UI/Blueprints/BP_Minimal_HUD.uasset new file mode 100644 index 00000000..97dbd12b Binary files /dev/null and b/Game/Content/UI/Blueprints/BP_Minimal_HUD.uasset differ diff --git a/Game/Content/UI/Blueprints/BP_PlayerHUD_CorridorShooter.uasset b/Game/Content/UI/Blueprints/BP_PlayerHUD_CorridorShooter.uasset new file mode 100644 index 00000000..5f4d6679 Binary files /dev/null and b/Game/Content/UI/Blueprints/BP_PlayerHUD_CorridorShooter.uasset differ diff --git a/Game/Content/UI/Blueprints/BP_PlayerHUD_Deathmatch.uasset b/Game/Content/UI/Blueprints/BP_PlayerHUD_Deathmatch.uasset new file mode 100644 index 00000000..d570eeb7 Binary files /dev/null and b/Game/Content/UI/Blueprints/BP_PlayerHUD_Deathmatch.uasset differ diff --git a/Game/Content/UI/Blueprints/BP_RespawnMenu.uasset b/Game/Content/UI/Blueprints/BP_RespawnMenu.uasset index fc402536..20787192 100644 Binary files a/Game/Content/UI/Blueprints/BP_RespawnMenu.uasset and b/Game/Content/UI/Blueprints/BP_RespawnMenu.uasset differ diff --git a/Game/Content/UI/Blueprints/BP_SessionScreen.uasset b/Game/Content/UI/Blueprints/BP_SessionScreen.uasset index 2f8c1825..75201773 100644 Binary files a/Game/Content/UI/Blueprints/BP_SessionScreen.uasset and b/Game/Content/UI/Blueprints/BP_SessionScreen.uasset differ diff --git a/Game/Content/UI/Blueprints/Scores/BP_ScoreTableWidget.uasset b/Game/Content/UI/Blueprints/Scores/BP_ScoreTableWidget.uasset index 2d7e3d3b..0ce975db 100644 Binary files a/Game/Content/UI/Blueprints/Scores/BP_ScoreTableWidget.uasset and b/Game/Content/UI/Blueprints/Scores/BP_ScoreTableWidget.uasset differ diff --git a/Game/Content/UI/Materials/M_CombatFeedback.uasset b/Game/Content/UI/Materials/M_CombatFeedback.uasset new file mode 100644 index 00000000..a2b413e6 Binary files /dev/null and b/Game/Content/UI/Materials/M_CombatFeedback.uasset differ diff --git a/Game/Content/UI/Materials/M_Nameplate.uasset b/Game/Content/UI/Materials/M_Nameplate.uasset new file mode 100644 index 00000000..e481851e Binary files /dev/null and b/Game/Content/UI/Materials/M_Nameplate.uasset differ diff --git a/Game/Content/UI/OnlinePlayers/Blueprints/BP_OnlinePlayerScrollBox.uasset b/Game/Content/UI/OnlinePlayers/Blueprints/BP_OnlinePlayerScrollBox.uasset new file mode 100644 index 00000000..3ae6ece3 Binary files /dev/null and b/Game/Content/UI/OnlinePlayers/Blueprints/BP_OnlinePlayerScrollBox.uasset differ diff --git a/Game/Content/UI/OnlinePlayers/Blueprints/BP_OnlinePlayerWidget.uasset b/Game/Content/UI/OnlinePlayers/Blueprints/BP_OnlinePlayerWidget.uasset new file mode 100644 index 00000000..06cb9f5f Binary files /dev/null and b/Game/Content/UI/OnlinePlayers/Blueprints/BP_OnlinePlayerWidget.uasset differ diff --git a/Game/Content/UI/OnlinePlayers/Blueprints/BP_OnlinePlayersScreen.uasset b/Game/Content/UI/OnlinePlayers/Blueprints/BP_OnlinePlayersScreen.uasset new file mode 100644 index 00000000..478669f3 Binary files /dev/null and b/Game/Content/UI/OnlinePlayers/Blueprints/BP_OnlinePlayersScreen.uasset differ diff --git a/Game/Content/UI/PartyInvites/BP_PartyInviteBox.uasset b/Game/Content/UI/PartyInvites/BP_PartyInviteBox.uasset new file mode 100644 index 00000000..21283f3c Binary files /dev/null and b/Game/Content/UI/PartyInvites/BP_PartyInviteBox.uasset differ diff --git a/Game/Content/UI/PartyInvites/BP_PartyInvitePanel.uasset b/Game/Content/UI/PartyInvites/BP_PartyInvitePanel.uasset new file mode 100644 index 00000000..98563734 Binary files /dev/null and b/Game/Content/UI/PartyInvites/BP_PartyInvitePanel.uasset differ diff --git a/Game/Content/UI/PartyInvites/BP_PartyInviteWidget.uasset b/Game/Content/UI/PartyInvites/BP_PartyInviteWidget.uasset new file mode 100644 index 00000000..e518e690 Binary files /dev/null and b/Game/Content/UI/PartyInvites/BP_PartyInviteWidget.uasset differ diff --git a/Game/Content/UI/Shared/Blueprints/BP_LeaveButton.uasset b/Game/Content/UI/Shared/Blueprints/BP_LeaveButton.uasset new file mode 100644 index 00000000..8f973248 Binary files /dev/null and b/Game/Content/UI/Shared/Blueprints/BP_LeaveButton.uasset differ diff --git a/Game/Content/UI/TeamRanking/BluePrints/BP_PlayerRankingWidget.uasset b/Game/Content/UI/TeamRanking/BluePrints/BP_PlayerRankingWidget.uasset new file mode 100644 index 00000000..0131e6e3 Binary files /dev/null and b/Game/Content/UI/TeamRanking/BluePrints/BP_PlayerRankingWidget.uasset differ diff --git a/Game/Content/UI/TeamRanking/BluePrints/BP_TeamRankingScreen.uasset b/Game/Content/UI/TeamRanking/BluePrints/BP_TeamRankingScreen.uasset new file mode 100644 index 00000000..e0ea147e Binary files /dev/null and b/Game/Content/UI/TeamRanking/BluePrints/BP_TeamRankingScreen.uasset differ diff --git a/Game/Content/UI/Textures/UI_T_Nameplate_BG.uasset b/Game/Content/UI/Textures/UI_T_Nameplate_BG.uasset new file mode 100644 index 00000000..883c8403 Binary files /dev/null and b/Game/Content/UI/Textures/UI_T_Nameplate_BG.uasset differ diff --git a/Game/Content/UI/WinningTeam/Blueprints/BP_WinningTeamScreen.uasset b/Game/Content/UI/WinningTeam/Blueprints/BP_WinningTeamScreen.uasset new file mode 100644 index 00000000..f314be79 Binary files /dev/null and b/Game/Content/UI/WinningTeam/Blueprints/BP_WinningTeamScreen.uasset differ diff --git a/Game/Content/UI/WinningTeam/Blueprints/BP_WinningTeamWidget.uasset b/Game/Content/UI/WinningTeam/Blueprints/BP_WinningTeamWidget.uasset new file mode 100644 index 00000000..7d9302bd Binary files /dev/null and b/Game/Content/UI/WinningTeam/Blueprints/BP_WinningTeamWidget.uasset differ diff --git a/Game/Content/VFX/Meshes/SM_SphereMesh.uasset b/Game/Content/VFX/Meshes/SM_SphereMesh.uasset new file mode 100644 index 00000000..81219e70 Binary files /dev/null and b/Game/Content/VFX/Meshes/SM_SphereMesh.uasset differ diff --git a/Game/Content/VFX/Particles/ElectricalDamaged_P.uasset b/Game/Content/VFX/Particles/ElectricalDamaged_P.uasset new file mode 100644 index 00000000..90031ccf Binary files /dev/null and b/Game/Content/VFX/Particles/ElectricalDamaged_P.uasset differ diff --git a/Game/Content/VFX/Particles/Materials/MI_RocketTrail.uasset b/Game/Content/VFX/Particles/Materials/MI_RocketTrail.uasset index 361a71bb..77a73b87 100644 Binary files a/Game/Content/VFX/Particles/Materials/MI_RocketTrail.uasset and b/Game/Content/VFX/Particles/Materials/MI_RocketTrail.uasset differ diff --git a/Game/Content/Weapons/Turret/Meshes/SM_Turret_Projectile.uasset b/Game/Content/Weapons/Turret/Meshes/SM_Turret_Projectile.uasset new file mode 100644 index 00000000..cd89ecb3 Binary files /dev/null and b/Game/Content/Weapons/Turret/Meshes/SM_Turret_Projectile.uasset differ diff --git a/Game/Content/Weapons/Turret/Meshes/WP_SK_Turret_Machinegun.uasset b/Game/Content/Weapons/Turret/Meshes/WP_SK_Turret_Machinegun.uasset new file mode 100644 index 00000000..42ebf71e Binary files /dev/null and b/Game/Content/Weapons/Turret/Meshes/WP_SK_Turret_Machinegun.uasset differ diff --git a/Game/Content/Weapons/Turret/Meshes/WP_SK_Turret_Machinegun_PhysicsAsset.uasset b/Game/Content/Weapons/Turret/Meshes/WP_SK_Turret_Machinegun_PhysicsAsset.uasset new file mode 100644 index 00000000..4622028e Binary files /dev/null and b/Game/Content/Weapons/Turret/Meshes/WP_SK_Turret_Machinegun_PhysicsAsset.uasset differ diff --git a/Game/Content/Weapons/Turret/Meshes/WP_SK_Turret_Machinegun_Skeleton.uasset b/Game/Content/Weapons/Turret/Meshes/WP_SK_Turret_Machinegun_Skeleton.uasset new file mode 100644 index 00000000..61df4021 Binary files /dev/null and b/Game/Content/Weapons/Turret/Meshes/WP_SK_Turret_Machinegun_Skeleton.uasset differ diff --git a/Game/Content/Weapons/Turret/Meshes/WP_SK_Turret_Rocket.uasset b/Game/Content/Weapons/Turret/Meshes/WP_SK_Turret_Rocket.uasset new file mode 100644 index 00000000..6f970d4e Binary files /dev/null and b/Game/Content/Weapons/Turret/Meshes/WP_SK_Turret_Rocket.uasset differ diff --git a/Game/Content/Weapons/Turret/Meshes/WP_SK_Turret_Rocket_PhysicsAsset.uasset b/Game/Content/Weapons/Turret/Meshes/WP_SK_Turret_Rocket_PhysicsAsset.uasset new file mode 100644 index 00000000..9da860c7 Binary files /dev/null and b/Game/Content/Weapons/Turret/Meshes/WP_SK_Turret_Rocket_PhysicsAsset.uasset differ diff --git a/Game/Content/Weapons/Turret/Meshes/WP_SK_Turret_Rocket_Skeleton.uasset b/Game/Content/Weapons/Turret/Meshes/WP_SK_Turret_Rocket_Skeleton.uasset new file mode 100644 index 00000000..f1188b1f Binary files /dev/null and b/Game/Content/Weapons/Turret/Meshes/WP_SK_Turret_Rocket_Skeleton.uasset differ diff --git a/Game/Source/GDKShooter.Target.cs b/Game/Source/GDKShooter.Target.cs index e3d35404..b8677abd 100644 --- a/Game/Source/GDKShooter.Target.cs +++ b/Game/Source/GDKShooter.Target.cs @@ -9,5 +9,7 @@ public GDKShooterTarget(TargetInfo Target) : base(Target) { Type = TargetType.Game; ExtraModuleNames.Add("GDKShooter"); + // TODO: UNR-1791 for long-term fix + GlobalDefinitions.Add("UE_ALLOW_MAP_OVERRIDE_IN_SHIPPING=1"); } } diff --git a/Game/Source/GDKShooter/GDKShooter.Build.cs b/Game/Source/GDKShooter/GDKShooter.Build.cs index 91ff5c46..92d76fa7 100644 --- a/Game/Source/GDKShooter/GDKShooter.Build.cs +++ b/Game/Source/GDKShooter/GDKShooter.Build.cs @@ -23,7 +23,8 @@ public GDKShooter(ReadOnlyTargetRules Target) : base(Target) "SlateCore", "SpatialGDK", "Json", - "HTTP" + "HTTP", + "AIModule" }); } } diff --git a/Game/Source/GDKShooter/Private/Characters/Components/EquippedComponent.cpp b/Game/Source/GDKShooter/Private/Characters/Components/EquippedComponent.cpp new file mode 100644 index 00000000..602b5db5 --- /dev/null +++ b/Game/Source/GDKShooter/Private/Characters/Components/EquippedComponent.cpp @@ -0,0 +1,292 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "EquippedComponent.h" +#include "UnrealNetwork.h" +#include "GDKLogging.h" +#include "Engine/World.h" +#include "Weapons/Holdable.h" + + +UEquippedComponent::UEquippedComponent() +{ + PrimaryComponentTick.bCanEverTick = false; + bReplicates = true; +} + + +void UEquippedComponent::BeginPlay() +{ + Super::BeginPlay(); + + OnRep_HeldUpdate(); + +} + +void UEquippedComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + if (GetNetMode() == NM_DedicatedServer && GetOwner()->HasAuthority()) + { + for (AHoldable* Holdable : HeldItems) + { + if (Holdable != nullptr && !Holdable->IsPendingKill()) + { + GetWorld()->DestroyActor(Holdable); + } + } + } +} + +void UEquippedComponent::SpawnStarterTemplates(FGDKMetaData MetaData) +{ + if (!bHeldItemsInitialised && GetOwner()->HasAuthority()) + { + HeldItems.SetNum(HoldableCapacity, false); + bHeldItemsInitialised = true; + + for (int i = 0; i < StarterTemplates.Num(); i++) + { + if (StarterTemplates[i] == nullptr) + { + continue; + } + + AHoldable* Starter = GetWorld()->SpawnActor(StarterTemplates[i], GetOwner()->GetActorTransform()); + Starter->SetMetaData(MetaData); + Grant(Starter); + } + + // Default to holding the weapon in slot 0 + if (StarterTemplates.Num() > 0) + { + CurrentHeldIndex = 0; + } + + OnRep_HeldUpdate(); + } +} + +bool UEquippedComponent::Grant(AHoldable* NewHoldable) +{ + if (NewHoldable == nullptr || !HasAnyEmptySlots() || AlreadyHas(NewHoldable)) + { + return false; + } + + int Index = GetNextAvailableSlot(); + NewHoldable->AttachToActor(GetOwner(), FAttachmentTransformRules::SnapToTargetNotIncludingScale); + NewHoldable->SetOwner(GetOwner()); + HeldItems[Index] = NewHoldable; + CurrentHeldIndex = Index; + OnRep_HeldUpdate(); + return true; +} + +bool UEquippedComponent::HasAnyEmptySlots() +{ + for (int i = 0; i < HeldItems.Num(); i++) + { + if (HeldItems[i] == nullptr) + { + return true; + } + } + return false; +} + +int UEquippedComponent::GetNextAvailableSlot() +{ + for (int i = 0; i < HeldItems.Num(); i++) + { + if (HeldItems[i] == nullptr) + { + return i; + } + } + return -1; +} + +bool UEquippedComponent::AlreadyHas(AHoldable* NewHoldable) +{ + for (int i = 0; i < HeldItems.Num(); i++) + { + if (HeldItems[i] != nullptr && HeldItems[i]->IsA(NewHoldable->GetClass())) + { + return true; + } + } + return false; +} + +void UEquippedComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UEquippedComponent, CurrentHeldIndex); + DOREPLIFETIME(UEquippedComponent, HeldItems); + DOREPLIFETIME(UEquippedComponent, bHeldItemsInitialised); +} + +void UEquippedComponent::OnRep_HeldUpdate() +{ + for (int i = 0; i < HeldItems.Num(); i++) + { + if(!HeldItems[i]) + { + continue; + } + + if (i == CurrentHeldIndex) + { + LastCachedIndex = CurrentCachedIndex; + CurrentCachedIndex = CurrentHeldIndex; + LocallyActivate(CurrentlyHeldItem()); + } + else + { + HeldItems[i]->SetIsActive(false); + } + } +} + +AHoldable* UEquippedComponent::CurrentlyHeldItem() const +{ + if (CurrentHeldIndex < 0 || CurrentHeldIndex >= HeldItems.Num()) + return nullptr; + + return HeldItems[CurrentHeldIndex]; +} + +void UEquippedComponent::LocallyActivate(AHoldable* Holdable) +{ + if (LocallyActiveHoldable) + { + LocallyActiveHoldable->SetIsActive(false); + } + + Holdable->SetIsActive(true); + LocallyActiveHoldable = Holdable; + HoldableUpdated.Broadcast(Holdable); +} + +void UEquippedComponent::ToggleMode() +{ + if (CurrentlyHeldItem()) + { + CurrentlyHeldItem()->ToggleMode(); + } +} + +void UEquippedComponent::QuickToggle() +{ + ServerRequestEquip(LastCachedIndex); +} + +bool UEquippedComponent::HasHoldableAtIndex(int32 Index) +{ + return Index >= 0 && Index < HeldItems.Num() && HeldItems[Index]; +} + +void UEquippedComponent::ScrollUp() +{ + for (int i = CurrentHeldIndex + 1; i < HeldItems.Num(); i++) + { + if (HasHoldableAtIndex(i)) + { + ServerRequestEquip(i); + return; + } + } +} + +void UEquippedComponent::ScrollDown() +{ + for (int i = CurrentHeldIndex - 1; i >= 0; i--) + { + if (HasHoldableAtIndex(i)) + { + ServerRequestEquip(i); + return; + } + } +} + +void UEquippedComponent::ServerRequestEquip_Implementation(int32 TargetIndex) +{ + if (HasHoldableAtIndex(TargetIndex)) + { + CurrentHeldIndex = TargetIndex; + } + OnRep_HeldUpdate(); +} + +bool UEquippedComponent::ServerRequestEquip_Validate(int32 TargetIndex) +{ + return true; +} + +void UEquippedComponent::ForceCooldown(float Cooldown) +{ + if (CurrentlyHeldItem()) + { + CurrentlyHeldItem()->ForceCooldown(Cooldown); + } +} + +void UEquippedComponent::BlockUsing(bool bBlock) +{ + bBlockUsing = bBlock; + if (bBlock) + { + StopPrimaryUse(); + StopSecondaryUse(); + } +} + +void UEquippedComponent::StartPrimaryUse() +{ + if (bBlockUsing) + { + return; + } + + if (CurrentlyHeldItem()) + { + if (bIsSprinting) + { + CurrentlyHeldItem()->ForceCooldown(SprintRecoveryTime); + } + + CurrentlyHeldItem()->StartPrimaryUse(); + } +} + +void UEquippedComponent::StopPrimaryUse() +{ + if (CurrentlyHeldItem()) + { + CurrentlyHeldItem()->StopPrimaryUse(); + } +} + +void UEquippedComponent::StartSecondaryUse() +{ + if (bBlockUsing) + { + return; + } + + if (CurrentlyHeldItem()) + { + CurrentlyHeldItem()->StartSecondaryUse(); + } +} + +void UEquippedComponent::StopSecondaryUse() +{ + if (CurrentlyHeldItem()) + { + CurrentlyHeldItem()->StopSecondaryUse(); + } +} diff --git a/Game/Source/GDKShooter/Private/Characters/Components/FirstPersonTraceProvider.cpp b/Game/Source/GDKShooter/Private/Characters/Components/FirstPersonTraceProvider.cpp new file mode 100644 index 00000000..74cd4403 --- /dev/null +++ b/Game/Source/GDKShooter/Private/Characters/Components/FirstPersonTraceProvider.cpp @@ -0,0 +1,31 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "FirstPersonTraceProvider.h" +#include "GDKLogging.h" + +UFirstPersonTraceProvider::UFirstPersonTraceProvider() +{ + PrimaryComponentTick.bCanEverTick = false; +} + +void UFirstPersonTraceProvider::BeginPlay() +{ + Super::BeginPlay(); + FirstPersonCamera = Cast(GetOwner()->GetComponentByClass(UCameraComponent::StaticClass())); + + if (!FirstPersonCamera) + { + UE_LOG(LogGDK, Error, TEXT("FirstPersonTraceProvider Exists without a Camera Component.")); + } +} + +FVector UFirstPersonTraceProvider::GetLineTraceStart_Implementation() const +{ + return FirstPersonCamera->GetComponentLocation(); +} + + +FVector UFirstPersonTraceProvider::GetLineTraceDirection_Implementation() const +{ + return FirstPersonCamera->GetForwardVector(); +} diff --git a/Game/Source/GDKShooter/Private/Components/GDKMovementComponent.cpp b/Game/Source/GDKShooter/Private/Characters/Components/GDKMovementComponent.cpp similarity index 78% rename from Game/Source/GDKShooter/Private/Components/GDKMovementComponent.cpp rename to Game/Source/GDKShooter/Private/Characters/Components/GDKMovementComponent.cpp index 6efc3656..865f583a 100644 --- a/Game/Source/GDKShooter/Private/Components/GDKMovementComponent.cpp +++ b/Game/Source/GDKShooter/Private/Characters/Components/GDKMovementComponent.cpp @@ -2,37 +2,53 @@ #include "Components/GDKMovementComponent.h" -#include "Characters/Core/GDKCharacter.h" #include "GameFramework/Character.h" +#include "GameFramework/Controller.h" +#include "UnrealNetwork.h" +#include "GDKLogging.h" // Use the first custom movement flag slot in the character for sprinting. static const FSavedMove_Character::CompressedFlags FLAG_WantsToSprint = FSavedMove_GDKMovement::FLAG_Custom_0; UGDKMovementComponent::UGDKMovementComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) + , MaxJogSpeed(450) + , bCanSprint(true) + , bWantsToSprint(false) + , bWasSprintingLastFrame(false) + , bIsAiming(false) + , bIsBusy(false) + , bShouldOrientToControlRotation(false) , MaxSprintSpeed(850) , SprintAcceleration(3400) , SprintDirectionTolerance(0.1f) - , SprintCooldown(150) - , MaxJogSpeed(450) , JogAcceleration(1800) - , bCanSprint(true) - , bShouldOrientToControlRotation(false) { MaxWalkSpeed = 250; MaxWalkSpeedCrouched = 125; MaxAcceleration = 1000; + bReplicates = true; + JumpZVelocity = 600.f; + AirControl = 0.2f; } void UGDKMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); - if (IsSprinting()) { - lastTimeSprinting = FDateTime::Now(); + if (IsSprinting() != bWasSprintingLastFrame) { + bWasSprintingLastFrame = IsSprinting(); + SprintingUpdated.Broadcast(IsSprinting()); } } +void UGDKMovementComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UGDKMovementComponent, bIsAiming); +} + void UGDKMovementComponent::UpdateFromCompressedFlags(uint8 Flags) { Super::UpdateFromCompressedFlags(Flags); @@ -75,24 +91,8 @@ bool UGDKMovementComponent::IsSprinting() const && static_cast(bWantsToSprint) && IsMovingForward() && !IsCrouching() - && !IsAiming(); -} - -bool UGDKMovementComponent::HasSprintedRecently() const -{ - return - IsSprinting() - || TimeSinceLastSprint() < SprintCooldown; -} - -double UGDKMovementComponent::TimeSinceLastSprint() const -{ - return (FDateTime::Now() - lastTimeSprinting).GetTotalMilliseconds(); -} - -void UGDKMovementComponent::SetAiming(bool bAiming) -{ - bIsAiming = bAiming; + && !IsAiming() + && !IsBusy(); } bool UGDKMovementComponent::IsAiming() const @@ -143,13 +143,10 @@ bool UGDKMovementComponent::IsMovingForward() const FVector MoveDirection = Velocity.GetSafeNormal(); FVector Forward = PawnOwner->GetActorForwardVector(); - if (AGDKCharacter* Character = Cast(PawnOwner)) + if (AController* PlayerController = PawnOwner->GetController()) { - if (AController* PlayerController = Character->GetController()) - { - // Check move direction against control rotation. - Forward = PlayerController->GetControlRotation().Vector(); - } + // Check move direction against control rotation. + Forward = PlayerController->GetControlRotation().Vector(); } // Ignore the Z axis for comparison. @@ -208,7 +205,6 @@ FSavedMovePtr FNetworkPredictionData_Client_GDKMovement::AllocateNewMove() return FSavedMovePtr(new FSavedMove_GDKMovement()); } - bool UGDKMovementComponent::CanCrouchInCurrentState() const { if (!CanEverCrouch()) @@ -218,3 +214,28 @@ bool UGDKMovementComponent::CanCrouchInCurrentState() const return !IsFalling() && IsMovingOnGround() && UpdatedComponent && !UpdatedComponent->IsSimulatingPhysics(); } + +bool UGDKMovementComponent::ServerSetAiming_Validate(bool NewValue) +{ + return true; +} + +void UGDKMovementComponent::ServerSetAiming_Implementation(bool NewValue) +{ + bIsAiming = NewValue; +} + +void UGDKMovementComponent::SetAiming(bool NewValue) +{ + bIsAiming = NewValue; + if (GetOwnerRole() != ROLE_SimulatedProxy) + { + ServerSetAiming(NewValue); + } + OnAimingUpdated.Broadcast(bIsAiming); +} + +void UGDKMovementComponent::OnRep_IsAiming() +{ + OnAimingUpdated.Broadcast(bIsAiming); +} diff --git a/Game/Source/GDKShooter/Private/Characters/Components/HealthComponent.cpp b/Game/Source/GDKShooter/Private/Characters/Components/HealthComponent.cpp new file mode 100644 index 00000000..e403c76b --- /dev/null +++ b/Game/Source/GDKShooter/Private/Characters/Components/HealthComponent.cpp @@ -0,0 +1,220 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "HealthComponent.h" +#include "Components/ControllerEventsComponent.h" +#include "Components/ScorePublisher.h" +#include "GameFramework/Pawn.h" +#include "TeamComponent.h" +#include "UnrealNetwork.h" + +UHealthComponent::UHealthComponent() +{ + PrimaryComponentTick.bCanEverTick = true; + + bReplicates = true; + + MaxHealth = 100.f; + CurrentHealth = MaxHealth; + MaxArmour = 100.f; + CurrentArmour = 0.f; +} + + +void UHealthComponent::BeginPlay() +{ + Super::BeginPlay(); + + if (GetOwner()->HasAuthority()) + { + CurrentHealth = MaxHealth; + CurrentArmour = 0.f; + } +} + +void UHealthComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UHealthComponent, CurrentHealth); + + DOREPLIFETIME(UHealthComponent, CurrentArmour); +} + +void UHealthComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + if (GetOwner()->GetWorldTimerManager().IsTimerActive(HealthRegenerationHandle)) + { + GetOwner()->GetWorldTimerManager().ClearTimer(HealthRegenerationHandle); + } + + if (GetOwner()->GetWorldTimerManager().IsTimerActive(ArmourRegenerationHandle)) + { + GetOwner()->GetWorldTimerManager().ClearTimer(ArmourRegenerationHandle); + } +} + +void UHealthComponent::TakeDamage(float Damage, const FDamageEvent& DamageEvent, AController* EventInstigator, AActor* DamageCauser) +{ + if (UTeamComponent* Team = Cast(GetOwner()->GetComponentByClass(UTeamComponent::StaticClass()))) + { + if (EventInstigator && !Team->CanDamageActor(EventInstigator->GetPawn())) + { + return; + } + } + + int32 ArmourRemoved = FMath::Min(Damage, CurrentArmour); + CurrentArmour -= ArmourRemoved; + int32 DamageDealt = FMath::Min(Damage - ArmourRemoved, CurrentHealth); + bool bWasDead = CurrentHealth <= 0.f; + CurrentHealth -= DamageDealt; + bool bIsDead = CurrentHealth <= 0.f; + + int32 InstigatorPlayerId = -1; + FGenericTeamId InstigatorTeamId = FGenericTeamId::NoTeam; + if (EventInstigator != nullptr) + { + APlayerState* InstigatorPlayerState = EventInstigator->PlayerState; + if (InstigatorPlayerState != nullptr) + { + InstigatorPlayerId = InstigatorPlayerState->PlayerId; + } + } + if (IGenericTeamAgentInterface* InstgatorTeam = Cast(EventInstigator)) + { + InstigatorTeamId = InstgatorTeam->GetGenericTeamId(); + } + else if (IGenericTeamAgentInterface* CauserTeam = Cast(DamageCauser)) + { + InstigatorTeamId = CauserTeam->GetGenericTeamId(); + } + + FVector Source; + FVector Impact; + + if (DamageEvent.IsOfType(FPointDamageEvent::ClassID)) + { + FPointDamageEvent* const PointDamageEvent = (FPointDamageEvent*)&DamageEvent; + Source = DamageCauser ? DamageCauser->GetActorLocation() : GetOwner()->GetActorLocation() - PointDamageEvent->ShotDirection; + Impact = PointDamageEvent->HitInfo.ImpactPoint; + } + else if (DamageEvent.IsOfType(FRadialDamageEvent::ClassID)) + { + FRadialDamageEvent* const RadialDamageEvent = (FRadialDamageEvent*)&DamageEvent; + Impact = GetOwner()->GetActorLocation() + RadialDamageImpactOffset; + Source = RadialDamageEvent->Origin; + } + else + { + Source = DamageCauser ? DamageCauser->GetActorLocation() : GetOwner()->GetActorLocation(); + Impact = GetOwner()->GetActorLocation(); + } + + MulticastDamageTaken(Damage, Source, Impact, InstigatorPlayerId, InstigatorTeamId); + + if (!bWasDead && bIsDead) + { + AuthoritativeDeath.Broadcast(EventInstigator); + + if (APawn* OwnerAsPawn = Cast(GetOwner())) + { + if (AController* Controller = OwnerAsPawn->GetController()) + { + if (UControllerEventsComponent* ControllerEvents = Cast(Controller->GetComponentByClass(UControllerEventsComponent::StaticClass()))) + { + ControllerEvents->Death(EventInstigator); + } + + if (EventInstigator != nullptr) + { + if (UControllerEventsComponent* ControllerEvents = Cast(EventInstigator->GetComponentByClass(UControllerEventsComponent::StaticClass()))) + { + ControllerEvents->Kill(Controller); + } + } + } + } + } + + + if(!bIsDead) + { + if (GetOwner()->GetWorldTimerManager().IsTimerActive(HealthRegenerationHandle)) + { + GetOwner()->GetWorldTimerManager().ClearTimer(HealthRegenerationHandle); + } + if (GetOwner()->GetWorldTimerManager().IsTimerActive(ArmourRegenerationHandle)) + { + GetOwner()->GetWorldTimerManager().ClearTimer(ArmourRegenerationHandle); + } + if (HealthRegenInterval > 0) + { + GetOwner()->GetWorldTimerManager().SetTimer(HealthRegenerationHandle, this, &UHealthComponent::RegenerateHealth, HealthRegenInterval, true, HealthRegenCooldown); + } + if (ArmourRegenInterval > 0) + { + GetOwner()->GetWorldTimerManager().SetTimer(ArmourRegenerationHandle, this, &UHealthComponent::RegenerateArmour, ArmourRegenInterval, true, ArmourRegenCooldown); + } + } +} + +bool UHealthComponent::GrantHealth(float Value) +{ + if (CurrentHealth < MaxHealth) + { + CurrentHealth = FMath::Min(CurrentHealth + Value, MaxHealth); + + return true; + } + + return false; +} + +bool UHealthComponent::GrantShield(float Value) +{ + if (CurrentArmour < MaxArmour) + { + CurrentArmour = FMath::Min(CurrentArmour + Value, MaxArmour); + + return true; + } + + return false; +} + +void UHealthComponent::RegenerateHealth() +{ + if (CurrentHealth > 0.f) + { + GrantHealth(HealthRegenValue); + } +} + +void UHealthComponent::RegenerateArmour() +{ + if (CurrentArmour > 0.f) + { + GrantHealth(ArmourRegenValue); + } +} + +void UHealthComponent::OnRep_CurrentArmour() +{ + ArmourUpdated.Broadcast(CurrentArmour, MaxArmour); +} + +void UHealthComponent::OnRep_CurrentHealth() +{ + HealthUpdated.Broadcast(CurrentHealth, MaxHealth); + if (CurrentHealth <= 0.f) + { + Death.Broadcast(); + } +} + +void UHealthComponent::MulticastDamageTaken_Implementation(float Value, FVector Source, FVector Impact, int32 PlayerId, FGenericTeamId TeamId) +{ + DamageTaken.Broadcast(Value, Source, Impact, PlayerId, TeamId); +} diff --git a/Game/Source/GDKShooter/Private/Characters/Components/MetaDataComponent.cpp b/Game/Source/GDKShooter/Private/Characters/Components/MetaDataComponent.cpp new file mode 100644 index 00000000..a5933dba --- /dev/null +++ b/Game/Source/GDKShooter/Private/Characters/Components/MetaDataComponent.cpp @@ -0,0 +1,30 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "MetaDataComponent.h" +#include "UnrealNetwork.h" +#include "GDKLogging.h" + +UMetaDataComponent::UMetaDataComponent() +{ + bReplicates = true; +} + +void UMetaDataComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UMetaDataComponent, MetaData); + DOREPLIFETIME(UMetaDataComponent, bMetaDataAvailable); +} + +void UMetaDataComponent::SetMetaData(FGDKMetaData NewMetaData) +{ + MetaData = NewMetaData; + bMetaDataAvailable = true; + MetaDataUpdated.Broadcast(MetaData); +} + +void UMetaDataComponent::OnRep_MetaData() +{ + MetaDataUpdated.Broadcast(MetaData); +} diff --git a/Game/Source/GDKShooter/Private/Characters/Components/ShootingComponent.cpp b/Game/Source/GDKShooter/Private/Characters/Components/ShootingComponent.cpp new file mode 100644 index 00000000..88a8e218 --- /dev/null +++ b/Game/Source/GDKShooter/Private/Characters/Components/ShootingComponent.cpp @@ -0,0 +1,81 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "ShootingComponent.h" +#include "CollisionQueryParams.h" +#include "Engine/World.h" +#include "GameFramework/Actor.h" +#include "GDKLogging.h" + +UShootingComponent::UShootingComponent() +{ + PrimaryComponentTick.bCanEverTick = false; + + MaxRange = 50000.0f; +} + +void UShootingComponent::BeginPlay() +{ + Super::BeginPlay(); + + TArray AllComponents; + GetOwner()->GetComponents(AllComponents); + bool bHasTraceProvider = false; + + for (int i = 0; i < AllComponents.Num(); i++) + { + if (AllComponents[i]->GetClass()->ImplementsInterface(UTraceProvider::StaticClass())) + { + TraceProvider.SetObject(AllComponents[i]); + TraceProvider.SetInterface(Cast(AllComponents[i])); + bHasTraceProvider = true; + } + } + + if (!bHasTraceProvider) + { + UE_LOG(LogGDK, Error, TEXT("Shooting Component Exists without a Trace Providing component, please add an Actor Component that implements ITraceProvider.")); + } +} + + +FInstantHitInfo UShootingComponent::DoLineTrace(FVector Direction, AActor* ActorToIgnore) +{ + FInstantHitInfo OutHitInfo; + + FCollisionQueryParams TraceParams; + TraceParams.bTraceComplex = true; + TraceParams.bReturnPhysicalMaterial = false; + if (ActorToIgnore != nullptr) + { + TraceParams.AddIgnoredActor(ActorToIgnore); + AActor* ActorToIgnoresOwner = ActorToIgnore->GetOwner(); + if (ActorToIgnoresOwner != nullptr) + { + TraceParams.AddIgnoredActor(ActorToIgnoresOwner); + } + } + + FHitResult HitResult(ForceInit); + FVector TraceStart = GetLineTraceStart(); + FVector TraceEnd = TraceStart + Direction * MaxRange; + + bool bDidHit = GetWorld()->LineTraceSingleByChannel( + HitResult, + TraceStart, + TraceEnd, + TraceChannel, + TraceParams); + + if (!bDidHit) + { + OutHitInfo.Location = TraceEnd; + return OutHitInfo; + } + + OutHitInfo.Location = HitResult.ImpactPoint; + OutHitInfo.HitActor = HitResult.GetActor(); + + OutHitInfo.bDidHit = true; + + return OutHitInfo; +} diff --git a/Game/Source/GDKShooter/Private/Characters/Components/TeamComponent.cpp b/Game/Source/GDKShooter/Private/Characters/Components/TeamComponent.cpp new file mode 100644 index 00000000..dbd72765 --- /dev/null +++ b/Game/Source/GDKShooter/Private/Characters/Components/TeamComponent.cpp @@ -0,0 +1,40 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "TeamComponent.h" +#include "UnrealNetwork.h" + +#include "Engine/World.h" + + +UTeamComponent::UTeamComponent() +{ + PrimaryComponentTick.bCanEverTick = false; + bReplicates = true; +} + +void UTeamComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UTeamComponent, TeamId); +} + +bool UTeamComponent::CanDamageActor(AActor* OtherActor) +{ + if (bAllowFriendlyFire) + { + return true; + } + + if (!HasTeam()) + { + return true; + } + + if (UTeamComponent* OtherTeamComponent = Cast(OtherActor->GetComponentByClass(UTeamComponent::StaticClass()))) + { + return !OtherTeamComponent->HasTeam() || OtherTeamComponent->GetTeam() != GetTeam(); + } + + return true; +} diff --git a/Game/Source/GDKShooter/Private/Characters/Core/GDKCharacter.cpp b/Game/Source/GDKShooter/Private/Characters/Core/GDKCharacter.cpp deleted file mode 100644 index 9380bb7e..00000000 --- a/Game/Source/GDKShooter/Private/Characters/Core/GDKCharacter.cpp +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#include "GDKCharacter.h" - -#include "Components/CapsuleComponent.h" -#include "GameFramework/CharacterMovementComponent.h" -#include "SpatialNetDriver.h" -#include "UnrealNetwork.h" -#include "GDKLogging.h" -#include "Game/GDKPlayerState.h" -#include "Controllers/GDKPlayerController.h" -#include "Weapons/InstantWeapon.h" - -// Sets default values -AGDKCharacter::AGDKCharacter(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - // Set size for collision capsule - GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f); - - MaxHealth = 100; - CurrentHealth = MaxHealth; - MaxArmour = 100; - CurrentArmour = 0; - bIsRagdoll = false; -} - -// Called when the game starts or when spawned -void AGDKCharacter::BeginPlay() -{ - Super::BeginPlay(); - - if (HasAuthority()) - { - CurrentHealth = MaxHealth; - CurrentArmour = 0; - } - OnMetaDataUpdated(); - -} - -void AGDKCharacter::Destroyed() -{ - Super::Destroyed(); - - if (GetWorldTimerManager().IsTimerActive(RegenerationHandle)) - { - GetWorldTimerManager().ClearTimer(RegenerationHandle); - } -} - -void AGDKCharacter::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const -{ - Super::GetLifetimeReplicatedProps(OutLifetimeProps); - - DOREPLIFETIME(AGDKCharacter, bIsRagdoll); - - DOREPLIFETIME(AGDKCharacter, CurrentHealth); - - DOREPLIFETIME(AGDKCharacter, CurrentArmour); - - DOREPLIFETIME(AGDKCharacter, MetaData); -} - -void AGDKCharacter::Die(const AGDKCharacter* Killer) -{ - TearOff(); - - if (GetNetMode() == NM_DedicatedServer && HasAuthority()) - { - AGDKPlayerController* PC = Cast(GetController()); - if (PC) - { - PC->KillCharacter(Killer); - } - - bIsRagdoll = true; - OnRep_IsRagdoll(); - } -} - -void AGDKCharacter::StartRagdoll() -{ - // Disable capsule collision and disable movement. - UCapsuleComponent* CapsuleComponent = GetCapsuleComponent(); - if (CapsuleComponent == nullptr) - { - UE_LOG(LogGDK, Error, TEXT("Invalid capsule component on character %s"), *this->GetName()); - return; - } - - CapsuleComponent->SetSimulatePhysics(false); - CapsuleComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision); - GetCharacterMovement()->StopMovementImmediately(); - GetCharacterMovement()->DisableMovement(); - - // Enable mesh collision and physics. - USkeletalMeshComponent* MeshComponent = GetMesh(); - MeshComponent->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly); - MeshComponent->SetCollisionProfileName(FName(TEXT("Ragdoll"))); - MeshComponent->SetSimulatePhysics(true); - - // Gather list of child components of the capsule. - TArray ComponentsToMove; - int NumChildren = CapsuleComponent->GetNumChildrenComponents(); - for (int i = 0; i < NumChildren; ++i) - { - USceneComponent* Component = CapsuleComponent->GetChildComponent(i); - if (Component != nullptr && Component != MeshComponent) - { - ComponentsToMove.Add(Component); - } - } - - SetRootComponent(MeshComponent); - - // Move the capsule's former child components over to the mesh. - for (USceneComponent* Component : ComponentsToMove) - { - Component->AttachToComponent(MeshComponent, FAttachmentTransformRules::SnapToTargetNotIncludingScale); - } - - // Fix up the camera to a "death view". - if (GetNetMode() == NM_Client) - { - ShowRespawnScreen(); - } -} - -void AGDKCharacter::OnRep_CurrentHealth() -{ - if (GetNetMode() != NM_DedicatedServer) - { - AGDKPlayerController* PC = Cast(GetController()); - if (PC) - { - PC->UpdateHealthUI(CurrentHealth, MaxHealth); - } - - OnHealthUpdated(CurrentHealth, MaxHealth); - } -} - -void AGDKCharacter::OnRep_CurrentArmour() -{ - if (GetNetMode() != NM_DedicatedServer) - { - AGDKPlayerController* PC = Cast(GetController()); - if (PC) - { - PC->UpdateArmourUI(CurrentArmour, MaxArmour); - } - - OnArmourUpdated(CurrentArmour, MaxArmour); - } -} - -void AGDKCharacter::OnRep_IsRagdoll() -{ - if (bIsRagdoll) - { - StartRagdoll(); - DeletionDelegate.BindUFunction(this, FName("DeleteSelf")); - GetWorldTimerManager().SetTimer(DeletionTimer, DeletionDelegate, 5.0f, false); - } -} - -void AGDKCharacter::DeleteSelf() -{ - if (this->IsValidLowLevel()) - { - this->Destroy(); - } -} - -bool AGDKCharacter::GrantShield(float Value) -{ - if (CurrentArmour < MaxArmour) - { - CurrentArmour = FMath::Min(static_cast(CurrentArmour + Value), MaxArmour); - - return true; - } - - return false; -} - -void AGDKCharacter::RegenerateHealth() -{ - if (CurrentHealth > 0 && CurrentHealth < MaxHealth) - { - CurrentHealth = FMath::Min(static_cast(CurrentHealth + HealthRegenValue), MaxHealth); - } -} - -float AGDKCharacter::TakeDamage(float Damage, const FDamageEvent& DamageEvent, AController* EventInstigator, AActor* DamageCauser) -{ - - float ActualDamage = Super::TakeDamage(Damage, DamageEvent, EventInstigator, DamageCauser); - - TakeDamageCrossServer(ActualDamage, DamageEvent, EventInstigator, DamageCauser); - - return Damage; -} - -void AGDKCharacter::TakeDamageCrossServer_Implementation(float Damage, const FDamageEvent& DamageEvent, AController* EventInstigator, AActor* DamageCauser) -{ - if (!HasAuthority()) - { - return; - } - - const AWeapon* DamageSourceWeapon = Cast(DamageCauser); - const AGDKCharacter* Killer = Cast(DamageSourceWeapon->GetWeilder()); - - int32 ArmourRemoved = FMath::Min(static_cast(Damage), CurrentArmour); - CurrentArmour -= ArmourRemoved; - int32 DamageDealt = FMath::Min(static_cast(Damage) - ArmourRemoved, CurrentHealth); - CurrentHealth -= DamageDealt; - - MulticastDamageTaken(DamageCauser->GetActorLocation()); - - if (CurrentHealth <= 0) - { - Die(Killer); - } - else { - if (GetWorldTimerManager().IsTimerActive(RegenerationHandle)) - { - GetWorldTimerManager().ClearTimer(RegenerationHandle); - } - if (HealthRegenInterval > 0) - { - GetWorldTimerManager().SetTimer(RegenerationHandle, this, &AGDKCharacter::RegenerateHealth, HealthRegenInterval, true, HealthRegenCooldown); - } - } -} - -void AGDKCharacter::MulticastDamageTaken_Implementation(FVector DamageSource) -{ - if (GetNetMode() == NM_Client) - { - OnDamageTaken(DamageSource); - } -} - -void AGDKCharacter::OnRep_MetaData() -{ - OnMetaDataUpdated(); -} - -void AGDKCharacter::SetMetaData(FGDKMetaData NewMetaData) -{ - if (!HasAuthority()) - { - return; - } - - MetaData = NewMetaData; - OnMetaDataUpdated(); -} - -FString AGDKCharacter::GetPlayerName() const -{ - if (AGDKPlayerState* PS = Cast(PlayerState)) - { - return PS->GetPlayerName(); - } - return FString("UNKNOWN"); -} - -void AGDKCharacter::IgnoreMe(AActor* ToIgnore) -{ - GetCapsuleComponent()->IgnoreActorWhenMoving(ToIgnore, true); -} diff --git a/Game/Source/GDKShooter/Private/Characters/Core/GDKMobileCharacter.cpp b/Game/Source/GDKShooter/Private/Characters/Core/GDKMobileCharacter.cpp deleted file mode 100644 index 92755c53..00000000 --- a/Game/Source/GDKShooter/Private/Characters/Core/GDKMobileCharacter.cpp +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#include "GDKMobileCharacter.h" -#include "GDKLogging.h" -#include "UnrealNetwork.h" - -#include "Components/GDKMovementComponent.h" - -AGDKMobileCharacter::AGDKMobileCharacter(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer.SetDefaultSubobjectClass(ACharacter::CharacterMovementComponentName)) -{ - // set our turn rates for input - BaseTurnRate = 45.f; - BaseLookUpRate = 45.f; - - // Configure character movement - GetCharacterMovement()->JumpZVelocity = 600.f; - GetCharacterMovement()->AirControl = 0.2f; -} - -// Called every frame -void AGDKMobileCharacter::Tick(float DeltaTime) -{ - Super::Tick(DeltaTime); - - bJumpedThisFrame = false; - if (HasAuthority()) - { - Pitch = GetControlRotation().Pitch; - } -} - -// Called to bind functionality to input -void AGDKMobileCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) -{ - Super::SetupPlayerInputComponent(PlayerInputComponent); - - // Set up gameplay key bindings - check(PlayerInputComponent); - PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump); - PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping); - - PlayerInputComponent->BindAxis("MoveForward", this, &AGDKMobileCharacter::MoveForward); - PlayerInputComponent->BindAxis("MoveRight", this, &AGDKMobileCharacter::MoveRight); - - // We have 2 versions of the rotation bindings to handle different kinds of devices differently - // "turn" handles devices that provide an absolute delta, such as a mouse. - // "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick - PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput); - PlayerInputComponent->BindAxis("TurnRate", this, &AGDKMobileCharacter::TurnAtRate); - PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput); - PlayerInputComponent->BindAxis("LookUpRate", this, &AGDKMobileCharacter::LookUpAtRate); - - PlayerInputComponent->BindAction("Sprint", IE_Pressed, this, &AGDKMobileCharacter::StartSprinting); - PlayerInputComponent->BindAction("Sprint", IE_Released, this, &AGDKMobileCharacter::StopSprinting); - - PlayerInputComponent->BindAction("Crouch", IE_Pressed, this, &AGDKMobileCharacter::StartCrouching); - PlayerInputComponent->BindAction("Crouch", IE_Released, this, &AGDKMobileCharacter::StopCrouching); - - /* - PlayerInputComponent->BindAction("Interact", IE_Pressed, this, &AGDKCharacter::Interact); - PlayerInputComponent->BindAction("DebugResetCharacter", IE_Pressed, this, &AGDKCharacter::DebugResetCharacter); - */ -} - -void AGDKMobileCharacter::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const -{ - Super::GetLifetimeReplicatedProps(OutLifetimeProps); - - DOREPLIFETIME(AGDKMobileCharacter, Pitch); -} - -void AGDKMobileCharacter::MoveForward(float Value) -{ - if (Value != 0.0f) - { - // add movement in that direction - AddMovementInput(GetActorForwardVector(), Value); - } -} - -void AGDKMobileCharacter::MoveRight(float Value) -{ - if (Value != 0.0f) - { - // add movement in that direction - AddMovementInput(GetActorRightVector(), Value); - } -} - -void AGDKMobileCharacter::TurnAtRate(float Rate) -{ - // calculate delta for this frame from the rate information - AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds()); -} - -void AGDKMobileCharacter::LookUpAtRate(float Rate) -{ - // calculate delta for this frame from the rate information - AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds()); -} - -void AGDKMobileCharacter::StartSprinting() -{ - if (UGDKMovementComponent* MovementComponent = Cast(GetCharacterMovement())) - { - MovementComponent->SetWantsToSprint(true); - } -} - -void AGDKMobileCharacter::StopSprinting() -{ - if (UGDKMovementComponent* MovementComponent = Cast(GetCharacterMovement())) - { - MovementComponent->SetWantsToSprint(false); - } -} - -void AGDKMobileCharacter::StartCrouching() -{ - Crouch(true); -} - -void AGDKMobileCharacter::StopCrouching() -{ - UnCrouch(true); -} - -bool AGDKMobileCharacter::HasSprintedRecently() -{ - if (UGDKMovementComponent* MovementComponent = Cast(GetCharacterMovement())) - { - return MovementComponent->HasSprintedRecently(); - } - return IsSprinting(); -} - -bool AGDKMobileCharacter::IsSprinting() -{ - UGDKMovementComponent* Movement = Cast(GetCharacterMovement()); - if (Movement == nullptr) - { - return false; - } - - if (Role >= ROLE_AutonomousProxy) - { - // If we're authoritative or the owning client, we know definitively whether we're sprinting. - return Movement->IsSprinting(); - } - - // For all other client types, we need to guess based on speed. - // Add a tolerance factor to the max jog speed and use that as a sprint threshold. - float SquaredSprintThreshold = Movement->MaxWalkSpeed + 10.0f; - SquaredSprintThreshold *= SquaredSprintThreshold; - - // We only care about speed in the X-Y plane. - return GetVelocity().SizeSquared2D() > SquaredSprintThreshold; -} - -void AGDKMobileCharacter::OnJumped_Implementation() -{ - bJumpedThisFrame = true; -} - -bool AGDKMobileCharacter::JumpedThisFrame() -{ - return bJumpedThisFrame; -} - diff --git a/Game/Source/GDKShooter/Private/Characters/Core/GDKShooterCharacter.cpp b/Game/Source/GDKShooter/Private/Characters/Core/GDKShooterCharacter.cpp deleted file mode 100644 index 37d59ee1..00000000 --- a/Game/Source/GDKShooter/Private/Characters/Core/GDKShooterCharacter.cpp +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#include "GDKShooterCharacter.h" - -#include "Weapons/InstantWeapon.h" -#include "Components/GDKMovementComponent.h" -#include "Controllers/GDKPlayerController.h" -#include "Engine/World.h" -#include "GDKLogging.h" -#include "UnrealNetwork.h" - -AGDKShooterCharacter::AGDKShooterCharacter(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - EquippedWeapon = nullptr; -} - -void AGDKShooterCharacter::BeginPlay() -{ - Super::BeginPlay(); - - // Only spawn a new starter weapon if we're authoritative and don't already have one. - if (HasAuthority()) - { - SpawnStarterWeapons(); - } -} - - -void AGDKShooterCharacter::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const -{ - Super::GetLifetimeReplicatedProps(OutLifetimeProps); - - DOREPLIFETIME(AGDKShooterCharacter, EquippedWeapon); - - DOREPLIFETIME(AGDKShooterCharacter, bIsAiming); -} - -// Called to bind functionality to input -void AGDKShooterCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) -{ - Super::SetupPlayerInputComponent(PlayerInputComponent); - - PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AGDKShooterCharacter::StartFire); - PlayerInputComponent->BindAction("Fire", IE_Released, this, &AGDKShooterCharacter::StopFire); - PlayerInputComponent->BindAction("Aim", IE_Pressed, this, &AGDKShooterCharacter::StartAim); - PlayerInputComponent->BindAction("Aim", IE_Released, this, &AGDKShooterCharacter::StopAim); - PlayerInputComponent->BindAction< FWeaponSelection>("1", IE_Pressed, this, &AGDKShooterCharacter::EquipWeaponLocally, 0); - PlayerInputComponent->BindAction< FWeaponSelection>("2", IE_Pressed, this, &AGDKShooterCharacter::EquipWeaponLocally, 1); - PlayerInputComponent->BindAction< FWeaponSelection>("3", IE_Pressed, this, &AGDKShooterCharacter::EquipWeaponLocally, 2); - PlayerInputComponent->BindAction< FWeaponSelection>("4", IE_Pressed, this, &AGDKShooterCharacter::EquipWeaponLocally, 3); - PlayerInputComponent->BindAction< FWeaponSelection>("5", IE_Pressed, this, &AGDKShooterCharacter::EquipWeaponLocally, 4); - PlayerInputComponent->BindAction< FWeaponSelection>("6", IE_Pressed, this, &AGDKShooterCharacter::EquipWeaponLocally, 5); - PlayerInputComponent->BindAction< FWeaponSelection>("7", IE_Pressed, this, &AGDKShooterCharacter::EquipWeaponLocally, 6); - PlayerInputComponent->BindAction< FWeaponSelection>("8", IE_Pressed, this, &AGDKShooterCharacter::EquipWeaponLocally, 7); - PlayerInputComponent->BindAction< FWeaponSelection>("9", IE_Pressed, this, &AGDKShooterCharacter::EquipWeaponLocally, 8); - PlayerInputComponent->BindAction< FWeaponSelection>("0", IE_Pressed, this, &AGDKShooterCharacter::EquipWeaponLocally, 9); - PlayerInputComponent->BindAction< FWeaponSelection>("-", IE_Pressed, this, &AGDKShooterCharacter::EquipWeaponLocally, 10); - PlayerInputComponent->BindAction< FWeaponSelection>("+", IE_Pressed, this, &AGDKShooterCharacter::EquipWeaponLocally, 11); -} - -void AGDKShooterCharacter::AttachWeapon(AWeapon* Weapon) const -{ - Weapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, kRightGunSocketName); - Weapon->EnableShadows(true); - Weapon->SetFirstPerson(false); -} - -void AGDKShooterCharacter::AddAimingListener(FAimingStateChanged Listener) -{ - AimingCallback = Listener; -} - -void AGDKShooterCharacter::AddWeaponListener(FWeaponChanged Listener) -{ - WeaponCallback = Listener; -} - -bool AGDKShooterCharacter::IgnoreActionInput() const -{ - check(GetNetMode() != NM_DedicatedServer); - - if (AGDKPlayerController* PC = Cast(GetController())) - { - return PC->IgnoreActionInput(); - } - return false; -} - -void AGDKShooterCharacter::StartFire() -{ - check(GetNetMode() != NM_DedicatedServer); - bFireHeld = true; - if (IgnoreActionInput()) - { - return; - } - - if (EquippedWeapon != nullptr) - { - // Don't allow sprinting and shooting at the same time. - StopSprinting(); - - EquippedWeapon->StartFire(); - } -} - -void AGDKShooterCharacter::StopFire() -{ - check(GetNetMode() != NM_DedicatedServer); - bFireHeld = false; - - if (EquippedWeapon != nullptr) - { - EquippedWeapon->StopFire(); - } -} - -bool AGDKShooterCharacter::CanFire() -{ - return EquippedWeapon && !HasSprintedRecently(); -} - -void AGDKShooterCharacter::StartAim() -{ - bIsAiming = true; - SetAiming(bIsAiming); - if (UGDKMovementComponent* MovementComponent = Cast(GetCharacterMovement())) - { - MovementComponent->SetAiming(true); - } - OnStartedAiming(); - AimingCallback.ExecuteIfBound(true, EquippedWeapon->AimingRotationSpeed); -} - -void AGDKShooterCharacter::StopAim() -{ - bIsAiming = false; - SetAiming(bIsAiming); - if (UGDKMovementComponent* MovementComponent = Cast(GetCharacterMovement())) - { - MovementComponent->SetAiming(false); - } - OnStoppedAiming(); - AimingCallback.ExecuteIfBound(false, 1); -} - -bool AGDKShooterCharacter::SetAiming_Validate(bool NewValue) -{ - return true; -} - -void AGDKShooterCharacter::SetAiming_Implementation(bool NewValue) -{ - bIsAiming = NewValue; -} - -bool AGDKShooterCharacter::IsAiming() -{ - return bIsAiming; -} - -void AGDKShooterCharacter::SpawnStarterWeapons() -{ - if (!HasAuthority()) - { - return; - } - - for (int i=0; i Weapon, bool bIsActive) -{ - if (!HasAuthority()) - { - return; - } - - FActorSpawnParameters SpawnParams; - SpawnParams.Owner = this; - AWeapon* StartWeapon = GetWorld()->SpawnActor(Weapon, GetActorTransform(), SpawnParams); - StartWeapon->SetOwningCharacter(this); - StartWeapon->SetIsActive(bIsActive); - StartWeapon->AttachToActor(this, FAttachmentTransformRules::SnapToTargetNotIncludingScale); - StartWeapon->SetMetaData(MetaData); - - AvailableWeapons.Add(StartWeapon); - - if (bIsActive) - { - EquippedWeapon = StartWeapon; - } -} - -void AGDKShooterCharacter::EquipWeaponLocally(int32 WeaponId) -{ - StopAim(); - EquipWeapon(WeaponId); -} - -bool AGDKShooterCharacter::EquipWeapon_Validate(int32 WeaponId) -{ - return true; -} - -void AGDKShooterCharacter::OnRep_EquippedWeapon() -{ - if (CachedEquippedWeapon) - { - CachedEquippedWeapon->RemoveShotListener(); - } - if (EquippedWeapon) - { - FShotDelegate ShotCallback; - ShotCallback.BindUObject(this, &AGDKShooterCharacter::OnShot); - EquippedWeapon->AddShotListener(ShotCallback); - } - CachedEquippedWeapon = EquippedWeapon; - WeaponCallback.ExecuteIfBound(EquippedWeapon); - OnEquippedWeaponChanged(); -} - -void AGDKShooterCharacter::AddShotListener(FWeaponShotDelegate Listener) -{ - ShotCallback = Listener; -} - -void AGDKShooterCharacter::OnShot(bool Hit) -{ - ShotCallback.ExecuteIfBound(EquippedWeapon, Hit); -} - -void AGDKShooterCharacter::EquipWeapon_Implementation(int32 WeaponId) -{ - if (AvailableWeapons.Num() <= WeaponId) - { - return; - } - - if (EquippedWeapon == AvailableWeapons[WeaponId]) - { - return; - } - - EquippedWeapon->SetIsActive(false); - EquippedWeapon = AvailableWeapons[WeaponId]; - EquippedWeapon->SetIsActive(true); -} - - -void AGDKShooterCharacter::SetMetaData(FGDKMetaData MetaData) -{ - Super::SetMetaData(MetaData); - - for (AWeapon* Weapon : AvailableWeapons) - { - if (Weapon) - { - Weapon->SetMetaData(MetaData); - } - } -} - - -void AGDKShooterCharacter::Die(const AGDKCharacter* Killer) -{ - Super::Die(Killer); - - if (EquippedWeapon != nullptr) - { - EquippedWeapon->StopFire(); - } - - if (GetNetMode() == NM_DedicatedServer && HasAuthority()) - { - if (EquippedWeapon != nullptr && !EquippedWeapon->IsPendingKill()) - { - GetWorld()->DestroyActor(EquippedWeapon); - } - } -} - -void AGDKShooterCharacter::Destroyed() -{ - Super::Destroyed(); - - if (GetNetMode() == NM_DedicatedServer && HasAuthority()) - { - for (AWeapon* Weapon : AvailableWeapons) - { - if (Weapon != nullptr && !Weapon->IsPendingKill()) - { - GetWorld()->DestroyActor(Weapon); - } - } - } -} diff --git a/Game/Source/GDKShooter/Private/Characters/GDKCharacter.cpp b/Game/Source/GDKShooter/Private/Characters/GDKCharacter.cpp new file mode 100644 index 00000000..13374f07 --- /dev/null +++ b/Game/Source/GDKShooter/Private/Characters/GDKCharacter.cpp @@ -0,0 +1,208 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "GDKCharacter.h" + +#include "Components/CapsuleComponent.h" +#include "GameFramework/CharacterMovementComponent.h" +#include "Components/SkeletalMeshComponent.h" +#include "SpatialNetDriver.h" +#include "UnrealNetwork.h" +#include "GDKLogging.h" +#include "Controllers/GDKPlayerController.h" +#include "Controllers/Components/ControllerEventsComponent.h" +#include "Weapons/Holdable.h" + +AGDKCharacter::AGDKCharacter(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer.SetDefaultSubobjectClass(ACharacter::CharacterMovementComponentName)) +{ + // Set size for collision capsule + GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f); + + HealthComponent = CreateDefaultSubobject(TEXT("Health")); + EquippedComponent = CreateDefaultSubobject(TEXT("Equipment")); + MetaDataComponent = CreateDefaultSubobject(TEXT("MetaData")); + TeamComponent = CreateDefaultSubobject(TEXT("Team")); + GDKMovementComponent = Cast(GetCharacterMovement()); +} + +// Called when the game starts or when spawned +void AGDKCharacter::BeginPlay() +{ + Super::BeginPlay(); + + EquippedComponent->HoldableUpdated.AddDynamic(this, &AGDKCharacter::OnEquippedUpdated); + GDKMovementComponent->SprintingUpdated.AddDynamic(EquippedComponent, &UEquippedComponent::SetIsSprinting); +} + +void AGDKCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) +{ + Super::SetupPlayerInputComponent(PlayerInputComponent); + check(PlayerInputComponent); + + PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump); + PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping); + + PlayerInputComponent->BindAxis("MoveForward", this, &AGDKCharacter::MoveForward); + PlayerInputComponent->BindAxis("MoveRight", this, &AGDKCharacter::MoveRight); + + PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput); + PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput); + + PlayerInputComponent->BindAction("Sprint", IE_Pressed, GDKMovementComponent, &UGDKMovementComponent::SetWantsToSprint, true); + PlayerInputComponent->BindAction("Sprint", IE_Released, GDKMovementComponent, &UGDKMovementComponent::SetWantsToSprint, false); + PlayerInputComponent->BindAction("Crouch", IE_Pressed, this, &AGDKCharacter::Crouch, true); + PlayerInputComponent->BindAction("Crouch", IE_Released, this, &AGDKCharacter::UnCrouch, true); + + PlayerInputComponent->BindAction("Primary", IE_Pressed, EquippedComponent, &UEquippedComponent::StartPrimaryUse); + PlayerInputComponent->BindAction("Primary", IE_Released, EquippedComponent, &UEquippedComponent::StopPrimaryUse); + PlayerInputComponent->BindAction("Secondary", IE_Pressed, EquippedComponent, &UEquippedComponent::StartSecondaryUse); + PlayerInputComponent->BindAction("Secondary", IE_Released, EquippedComponent, &UEquippedComponent::StopSecondaryUse); + + PlayerInputComponent->BindAction< FHoldableSelection>("1", IE_Pressed, EquippedComponent, &UEquippedComponent::ServerRequestEquip, 0); + PlayerInputComponent->BindAction< FHoldableSelection>("2", IE_Pressed, EquippedComponent, &UEquippedComponent::ServerRequestEquip, 1); + PlayerInputComponent->BindAction< FHoldableSelection>("3", IE_Pressed, EquippedComponent, &UEquippedComponent::ServerRequestEquip, 2); + PlayerInputComponent->BindAction< FHoldableSelection>("4", IE_Pressed, EquippedComponent, &UEquippedComponent::ServerRequestEquip, 3); + PlayerInputComponent->BindAction< FHoldableSelection>("5", IE_Pressed, EquippedComponent, &UEquippedComponent::ServerRequestEquip, 4); + PlayerInputComponent->BindAction< FHoldableSelection>("6", IE_Pressed, EquippedComponent, &UEquippedComponent::ServerRequestEquip, 5); + PlayerInputComponent->BindAction< FHoldableSelection>("7", IE_Pressed, EquippedComponent, &UEquippedComponent::ServerRequestEquip, 6); + PlayerInputComponent->BindAction< FHoldableSelection>("8", IE_Pressed, EquippedComponent, &UEquippedComponent::ServerRequestEquip, 7); + PlayerInputComponent->BindAction< FHoldableSelection>("9", IE_Pressed, EquippedComponent, &UEquippedComponent::ServerRequestEquip, 8); + PlayerInputComponent->BindAction< FHoldableSelection>("0", IE_Pressed, EquippedComponent, &UEquippedComponent::ServerRequestEquip, 9); + PlayerInputComponent->BindAction("QuickToggle", IE_Pressed, EquippedComponent, &UEquippedComponent::QuickToggle); + PlayerInputComponent->BindAction("ToggleMode", IE_Pressed, EquippedComponent, &UEquippedComponent::ToggleMode); + PlayerInputComponent->BindAction("ScrollUp", IE_Pressed, EquippedComponent, &UEquippedComponent::ScrollUp); + PlayerInputComponent->BindAction("ScrollDown", IE_Pressed, EquippedComponent, &UEquippedComponent::ScrollDown); +} + +void AGDKCharacter::MoveForward(float Value) +{ + if (Value != 0.0f) + { + AddMovementInput(GetActorForwardVector(), Value); + } +} + +void AGDKCharacter::MoveRight(float Value) +{ + if (Value != 0.0f) + { + AddMovementInput(GetActorRightVector(), Value); + } +} + +void AGDKCharacter::OnEquippedUpdated_Implementation(AHoldable* Holdable) +{ + if (Holdable) + { + Holdable->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, Holdable->GetActiveSocket()); + } +} + +void AGDKCharacter::StartRagdoll_Implementation() +{ + // Disable capsule collision and disable movement. + UCapsuleComponent* Capsule = GetCapsuleComponent(); + if (Capsule == nullptr) + { + UE_LOG(LogGDK, Error, TEXT("Invalid capsule component on character %s"), *this->GetName()); + return; + } + + Capsule->SetSimulatePhysics(false); + Capsule->SetCollisionEnabled(ECollisionEnabled::NoCollision); + GetCharacterMovement()->StopMovementImmediately(); + GetCharacterMovement()->DisableMovement(); + + // Enable mesh collision and physics. + USkeletalMeshComponent* MeshComponent = GetMesh(); + MeshComponent->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly); + MeshComponent->SetCollisionProfileName(FName(TEXT("Ragdoll"))); + MeshComponent->SetSimulatePhysics(true); + + // Gather list of child components of the capsule. + TArray ComponentsToMove; + int NumChildren = Capsule->GetNumChildrenComponents(); + for (int i = 0; i < NumChildren; ++i) + { + USceneComponent* Component = Capsule->GetChildComponent(i); + if (Component != nullptr && Component != MeshComponent) + { + ComponentsToMove.Add(Component); + } + } + + SetRootComponent(MeshComponent); + + // Move the capsule's former child components over to the mesh. + for (USceneComponent* Component : ComponentsToMove) + { + Component->AttachToComponent(MeshComponent, FAttachmentTransformRules::SnapToTargetNotIncludingScale); + } + + if (this->IsValidLowLevel() && RagdollLifetime >= 0.f) + { + DeletionDelegate.BindUFunction(this, FName("DeleteSelf")); + GetWorldTimerManager().SetTimer(DeletionTimer, DeletionDelegate, RagdollLifetime, false); + } +} + + +void AGDKCharacter::DeleteSelf() +{ + if (this->IsValidLowLevel()) + { + this->Destroy(); + } +} + +float AGDKCharacter::TakeDamage(float Damage, const FDamageEvent& DamageEvent, AController* EventInstigator, AActor* DamageCauser) +{ + TakeDamageCrossServer(Damage, DamageEvent, EventInstigator, DamageCauser); + return Damage; +} + +void AGDKCharacter::TakeDamageCrossServer_Implementation(float Damage, const FDamageEvent& DamageEvent, AController* EventInstigator, AActor* DamageCauser) +{ + float ActualDamage = Super::TakeDamage(Damage, DamageEvent, EventInstigator, DamageCauser); + HealthComponent->TakeDamage(ActualDamage, DamageEvent, EventInstigator, DamageCauser); + +} + +FGenericTeamId AGDKCharacter::GetGenericTeamId() const +{ + return TeamComponent->GetTeam(); +} + +bool AGDKCharacter::CanBeSeenFrom(const FVector& ObserverLocation, FVector& OutSeenLocation, int32& NumberOfLoSChecksPerformed, float& OutSightStrength, const AActor* IgnoreActor) const +{ + int32 PositiveHits = 0; + + if (HealthComponent->GetCurrentHealth() <= 0) + { + return 0; + } + + bool bHasSeen = false; + + for (int i = 0; i < LineOfSightSockets.Num(); i++) + { + FVector Target = GetMesh()->GetSocketLocation(LineOfSightSockets[i]); + FHitResult HitResult; + const bool bHit = GetWorld()->LineTraceSingleByChannel(HitResult, ObserverLocation, Target + , LineOfSightCollisionChannel.GetValue() + , FCollisionQueryParams(SCENE_QUERY_STAT(AILineOfSight), true, IgnoreActor)); + + if (bHit == false || (HitResult.Actor.IsValid() && HitResult.Actor->IsOwnedBy(this))) + { + if (!bHasSeen) + { + OutSeenLocation = Target; + bHasSeen = true; + } + PositiveHits++; + } + } + NumberOfLoSChecksPerformed = LineOfSightSockets.Num(); + OutSightStrength = (float)PositiveHits / (float)NumberOfLoSChecksPerformed; + return PositiveHits > 0; +} diff --git a/Game/Source/GDKShooter/Private/Characters/GDKFPShooterCharacter.cpp b/Game/Source/GDKShooter/Private/Characters/GDKFPShooterCharacter.cpp deleted file mode 100644 index 37d36dcb..00000000 --- a/Game/Source/GDKShooter/Private/Characters/GDKFPShooterCharacter.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#include "GDKFPShooterCharacter.h" - -#include "Camera/CameraComponent.h" -#include "Components/CapsuleComponent.h" -#include "GDKLogging.h" - -FName AGDKFPShooterCharacter::FirstPersonMeshComponentName(TEXT("FirstPersonMesh")); - -AGDKFPShooterCharacter::AGDKFPShooterCharacter(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - - // Hide the ThirdPerson Mesh from Owner - GetMesh()->bOwnerNoSee = true; - GetMesh()->bOnlyOwnerSee = false; - - // Create the first person camera - FirstPersonCamera = CreateDefaultSubobject(TEXT("FirstPersonCamera")); - FirstPersonCamera->SetupAttachment(GetCapsuleComponent()); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation - FirstPersonCamera->bUsePawnControlRotation = true; - - // Create the FirstPerson Mesh - FirstPersonMesh = CreateOptionalDefaultSubobject(AGDKFPShooterCharacter::FirstPersonMeshComponentName); - if (FirstPersonMesh) - { - FirstPersonMesh->AlwaysLoadOnClient = true; - FirstPersonMesh->AlwaysLoadOnServer = false; - FirstPersonMesh->bOwnerNoSee = false; - FirstPersonMesh->bOnlyOwnerSee = true; - FirstPersonMesh->MeshComponentUpdateFlag = EMeshComponentUpdateFlag::AlwaysTickPose; - FirstPersonMesh->bCastDynamicShadow = false; - FirstPersonMesh->CastShadow = false; - FirstPersonMesh->bAffectDynamicIndirectLighting = true; - FirstPersonMesh->PrimaryComponentTick.TickGroup = TG_PrePhysics; - FirstPersonMesh->SetupAttachment(FirstPersonCamera); - static FName MeshCollisionProfileName(TEXT("CharacterMesh")); - FirstPersonMesh->SetCollisionProfileName(MeshCollisionProfileName); - FirstPersonMesh->SetGenerateOverlapEvents(false); - FirstPersonMesh->SetCanEverAffectNavigation(false); - } -} - -FVector AGDKFPShooterCharacter::GetLineTraceStart() const -{ - return FirstPersonCamera->GetComponentLocation(); -} - -FVector AGDKFPShooterCharacter::GetLineTraceDirection() const -{ - return FirstPersonCamera->GetForwardVector(); -} - -void AGDKFPShooterCharacter::AttachWeapon(AWeapon* Weapon) const -{ - if (IsLocallyControlled()) - { - Weapon->AttachToComponent(FirstPersonMesh, FAttachmentTransformRules::SnapToTargetNotIncludingScale, kRightGunSocketName); - UE_LOG(LogGDK, Log, TEXT("AGDKFPShooterCharacter:: Attached %s to %s"), *Weapon->GetName(), *Weapon->GetAttachParentSocketName().ToString()); - Weapon->EnableShadows(false); - Weapon->SetFirstPerson(true); - } - else - { - Super::AttachWeapon(Weapon); - } -} diff --git a/Game/Source/GDKShooter/Private/Controllers/Components/ControllerEventsComponent.cpp b/Game/Source/GDKShooter/Private/Controllers/Components/ControllerEventsComponent.cpp new file mode 100644 index 00000000..74c534c4 --- /dev/null +++ b/Game/Source/GDKShooter/Private/Controllers/Components/ControllerEventsComponent.cpp @@ -0,0 +1,49 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "ControllerEventsComponent.h" +#include "GameFramework/Controller.h" +#include "GameFramework/PlayerState.h" + + +UControllerEventsComponent::UControllerEventsComponent() +{ + PrimaryComponentTick.bCanEverTick = false; +} + +void UControllerEventsComponent::Death_Implementation(const AController* Killer) +{ + DeathEvent.Broadcast(Killer); + + if (Killer != nullptr) + { + APlayerState* KillerPlayerState = Killer->PlayerState; + if (KillerPlayerState != nullptr) + { + ClientInformOfDeath(KillerPlayerState->GetPlayerName(), KillerPlayerState->PlayerId); + } + } +} + +void UControllerEventsComponent::Kill_Implementation(const AController* Victim) +{ + KillEvent.Broadcast(Victim); + + if (Victim != nullptr) + { + APlayerState* VictimPlayerState = Victim->PlayerState; + if (VictimPlayerState != nullptr) + { + ClientInformOfKill(VictimPlayerState->GetPlayerName(), VictimPlayerState->PlayerId); + } + } +} + +void UControllerEventsComponent::ClientInformOfKill_Implementation(const FString& VictimName, int32 VictimId) +{ + KillDetailsEvent.Broadcast(VictimName, VictimId); +} + +void UControllerEventsComponent::ClientInformOfDeath_Implementation(const FString& KillerName, int32 KillerId) +{ + DeathDetailsEvent.Broadcast(KillerName, KillerId); +} diff --git a/Game/Source/GDKShooter/Private/Controllers/Components/TeamSettingComponent.cpp b/Game/Source/GDKShooter/Private/Controllers/Components/TeamSettingComponent.cpp new file mode 100644 index 00000000..6fdb62c9 --- /dev/null +++ b/Game/Source/GDKShooter/Private/Controllers/Components/TeamSettingComponent.cpp @@ -0,0 +1,24 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "TeamSettingComponent.h" +#include "GDKLogging.h" +#include "AIController.h" + +UTeamSettingComponent::UTeamSettingComponent() +{ + PrimaryComponentTick.bCanEverTick = false; +} + +void UTeamSettingComponent::BeginPlay() +{ + Super::BeginPlay(); + if (AAIController* OwningController = Cast(GetOwner())) + { + OwningController->SetGenericTeamId(TeamId); + } + else + { + UE_LOG(LogGDK, Error, TEXT("TeamSettingComponent is designed to work with AIControllers, will not work with %s"), *GetOwner()->GetName()); + } +} + diff --git a/Game/Source/GDKShooter/Private/Controllers/GDKPlayerController.cpp b/Game/Source/GDKShooter/Private/Controllers/GDKPlayerController.cpp index 2addc14e..b94a390c 100644 --- a/Game/Source/GDKShooter/Private/Controllers/GDKPlayerController.cpp +++ b/Game/Source/GDKShooter/Private/Controllers/GDKPlayerController.cpp @@ -2,19 +2,25 @@ #include "Controllers/GDKPlayerController.h" -#include "Characters/Core/GDKShooterCharacter.h" -#include "Game/GDKGameState.h" -#include "Game/GDKSessionGameState.h" -#include "Game/GDKPlayerState.h" #include "Blueprint/UserWidget.h" -#include "GDKLogging.h" -#include "UnrealNetwork.h" -#include "TimerManager.h" #include "Camera/CameraComponent.h" +#include "Components/ControllerEventsComponent.h" +#include "Components/EquippedComponent.h" +#include "Components/HealthComponent.h" +#include "Components/MetaDataComponent.h" +#include "Connection/SpatialWorkerConnection.h" +#include "Game/Components/ScorePublisher.h" +#include "Game/Components/SpawnRequestPublisher.h" +#include "Game/Components/PlayerPublisher.h" +#include "GameFramework/Character.h" +#include "GameFramework/GameStateBase.h" +#include "GameFramework/PlayerState.h" #include "GameFramework/SpringArmComponent.h" - #include "SpatialNetDriver.h" -#include "Connection/SpatialWorkerConnection.h" +#include "UnrealNetwork.h" +#include "Weapons/Holdable.h" +#include "Weapons/Projectile.h" +#include "Weapons/Weapon.h" AGDKPlayerController::AGDKPlayerController() @@ -41,11 +47,13 @@ void AGDKPlayerController::BeginPlay() { Super::BeginPlay(); - if (AGDKSessionGameState* GameState = Cast(GetWorld()->GetGameState())) + if (PlayerState) { - GameState->OnTimerUpdated().AddUObject(this, &AGDKPlayerController::TimerUpdated); + if (UPlayerPublisher* PlayerPublisher = Cast(GetWorld()->GetGameState()->GetComponentByClass(UPlayerPublisher::StaticClass()))) + { + PlayerPublisher->PublishPlayer(PlayerState, EPlayerProgress::Connected); + } } - SetUIMode(true, false); } void AGDKPlayerController::Tick(float DeltaTime) @@ -56,65 +64,23 @@ void AGDKPlayerController::Tick(float DeltaTime) } } -void AGDKPlayerController::EndPlay(const EEndPlayReason::Type Reason) -{ - Super::EndPlay(Reason); - - GetWorld()->GetTimerManager().ClearAllTimersForObject(this); -} - -void AGDKPlayerController::UpdateHealthUI(int32 NewHealth, int32 MaxHealth) -{ - HealthChangedEvent.Broadcast(NewHealth, MaxHealth); -} - -void AGDKPlayerController::UpdateArmourUI(int32 NewArmour, int32 MaxArmour) -{ - ArmourChangedEvent.Broadcast(NewArmour, MaxArmour); -} - void AGDKPlayerController::SetPawn(APawn* InPawn) { Super::SetPawn(InPawn); - if (GetNetMode() == NM_Client) + if (GetNetMode() == NM_Client && InPawn) { - AGDKCharacter* Character = Cast(InPawn); - if (Character != nullptr) - { - SetCharacterState(EGDKCharacterState::Alive); - - UpdateHealthUI(Character->GetCurrentHealth(), Character->GetMaxHealth()); - UpdateArmourUI(Character->GetCurrentArmour(), Character->GetMaxArmour()); - - // Make the new pawn's camera this controller's camera. - SetViewTarget(InPawn); - - this->ClientSetRotation(InPawn->GetActorRotation(), true); - } - else - { - SetViewTarget(this); - } - - AGDKShooterCharacter* ShooterCharacter = Cast(InPawn); - if (ShooterCharacter != nullptr) - { - FAimingStateChanged AimingCallback; - AimingCallback.BindUObject(this, &AGDKPlayerController::AimingChanged); - ShooterCharacter->AddAimingListener(AimingCallback); - AimingChanged(ShooterCharacter->IsAiming(), 1); - - FWeaponShotDelegate ShotCallback; - ShotCallback.BindUObject(this, &AGDKPlayerController::OnCharacterShot); - ShooterCharacter->AddShotListener(ShotCallback); - - FWeaponChanged WeaponCallback; - WeaponCallback.BindUObject(this, &AGDKPlayerController::WeaponChanged); - ShooterCharacter->AddWeaponListener(WeaponCallback); - - } + SetViewTarget(InPawn); + // Make the new pawn's camera this controller's camera. + this->ClientSetRotation(InPawn->GetActorRotation(), true); + } + else + { + SetViewTarget(this); } + + PawnEvent.Broadcast(InPawn); + OnNewPawn(InPawn); } void AGDKPlayerController::GetPlayerViewPoint(FVector& out_Location, FRotator& out_Rotation) const @@ -131,334 +97,84 @@ void AGDKPlayerController::GetPlayerViewPoint(FVector& out_Location, FRotator& o } } -void AGDKPlayerController::OnCharacterShot(AWeapon* Weapon, bool Hit) -{ - if (Weapon) - { - OnShot(Weapon, Hit); - } - ShotEvent.Broadcast(Weapon, Hit); -} - -void AGDKPlayerController::AimingChanged(bool bIsAiming, float AimRotationSpeed) -{ - AimingChangedEvent.Broadcast(bIsAiming); - OnAimingChanged(bIsAiming, AimRotationSpeed); -} - -void AGDKPlayerController::WeaponChanged(AWeapon* Weapon) -{ - WeaponNotification.Broadcast(Weapon); -} - -void AGDKPlayerController::KillCharacter(const AGDKCharacter* Killer) -{ - check(GetNetMode() == NM_DedicatedServer); - - if (!HasAuthority()) - { - return; - } - - FString KillerName; - - if (Killer) - { - KillerName = Killer->GetPlayerName(); - InformOfDeath(KillerName, Killer->PlayerId); - - if (Killer->GetController()) - { - if (AGDKPlayerController* KC = Cast(Killer->GetController())) - { - if (AGDKCharacter* PC = Cast(GetPawn())) - { - KC->InformOfKill(PC->GetPlayerName(), PC->PlayerId); - } - } - } - } - - if (AGDKGameState* GM = Cast(GetWorld()->GetGameState())) - { - if (AGDKPlayerState* PS = Cast(PlayerState)) - GM->AddDeath(Killer->PlayerId, PS->PlayerId); - } - - UnPossess(); -} - -void AGDKPlayerController::InformOfKill_Implementation(const FString& VictimName, int32 VictimId) -{ - KillNotification.Broadcast(VictimName, VictimId); -} -void AGDKPlayerController::InformOfDeath_Implementation(const FString& KillerName, int32 KillerId) -{ - KilledNotification.Broadcast(KillerName, KillerId); - SetCharacterState(EGDKCharacterState::Dead); - HealthChangedEvent.Broadcast(0, 100); - ArmourChangedEvent.Broadcast(0, 100); - -} - -void AGDKPlayerController::SetupInputComponent() -{ - Super::SetupInputComponent(); - - InputComponent->BindAction("ShowScoreboard", IE_Pressed, this, &AGDKPlayerController::ShowScoreboard); - InputComponent->BindAction("ShowScoreboard", IE_Released, this, &AGDKPlayerController::HideScoreboard); - InputComponent->BindAction("ShowMenu", IE_Pressed, this, &AGDKPlayerController::ToggleMenu); -} - -void AGDKPlayerController::TimerUpdated(EGDKSessionProgress SessionProgress, int SessionTimer) -{ - if (SessionProgress == EGDKSessionProgress::Results && !bGameFinished) - { - bGameFinished = true; - SetControllerState(EGDKControllerState::Finished); - } - if (SessionProgress == EGDKSessionProgress::Finished) - { - FURL TravelURL; - TravelURL.Map = TEXT("Deployments"); - ClientTravel(TravelURL.ToString(), TRAVEL_Absolute, false /*bSeamless*/); - } -} - -void AGDKPlayerController::SetUIMode(bool bIsUIMode, bool bAllowMovement) +void AGDKPlayerController::SetUIMode(bool bIsUIMode) { bShowMouseCursor = bIsUIMode; ResetIgnoreLookInput(); SetIgnoreLookInput(bIsUIMode); ResetIgnoreMoveInput(); - SetIgnoreMoveInput(bIsUIMode && !bAllowMovement); - SetIgnoreActionInput(bIsUIMode); + SetIgnoreMoveInput(bIsUIMode); + if (bIsUIMode) { SetInputMode(FInputModeGameAndUI()); - - AGDKShooterCharacter* ShooterCharacter = Cast(GetPawn()); - if (ShooterCharacter != nullptr) - { - ShooterCharacter->StopFire(); - } } else { SetInputMode(FInputModeGameOnly()); } -} -void AGDKPlayerController::TryJoinGame(const FString& NewPlayerName, const FGDKMetaData MetaData) -{ - check(GetNetMode() != NM_DedicatedServer); - SetControllerState(EGDKControllerState::PendingCharacter); - bHasRequetsedPlayer = true; - ServerTryJoinGame( - NewPlayerName.IsEmpty() ? TEXT("Unknown") : NewPlayerName, - MetaData); -} - -void AGDKPlayerController::ChooseNewSpawnPoint() -{ - AActor* const NewStartSpot = GetWorld()->GetAuthGameMode()->ChoosePlayerStart(this); - if (NewStartSpot != nullptr) + if (GetPawn()) { - // Set the player controller / camera in this new location - FRotator InitialControllerRot = NewStartSpot->GetActorRotation(); - InitialControllerRot.Roll = 0.f; - SetInitialLocationAndRotation(NewStartSpot->GetActorLocation(), InitialControllerRot); - StartSpot = NewStartSpot; + if (UEquippedComponent* EquippedComponent = Cast(GetPawn()->GetComponentByClass(UEquippedComponent::StaticClass()))) + { + EquippedComponent->BlockUsing(bIsUIMode); + } } } -void AGDKPlayerController::ServerTryJoinGame_Implementation(const FString& NewPlayerName, const FGDKMetaData MetaData) +void AGDKPlayerController::ServerTryJoinGame_Implementation() { - bool bJoinWasSuccessful = true; - // Validate player name - if (NewPlayerName.IsEmpty()) + if (USpawnRequestPublisher* Spawner = Cast(GetWorld()->GetGameState()->GetComponentByClass(USpawnRequestPublisher::StaticClass()))) { - bJoinWasSuccessful = false; - - UE_LOG(LogGDK, Error, TEXT("%s PlayerController: Player attempted to join with empty name."), *this->GetName()); - } - - // Validate PlayerState - if (PlayerState == nullptr - || !PlayerState->IsA(AGDKPlayerState::StaticClass())) - { - bJoinWasSuccessful = false; - - UE_LOG(LogGDK, Error, TEXT("%s PlayerController: Invalid PlayerState pointer (%s)"), *this->GetName(), PlayerState == nullptr ? TEXT("nullptr") : *PlayerState->GetName()); - } - - // Validate the join request - if (bHasBeenGrantedPlayer) - { - bJoinWasSuccessful = false; - - UE_LOG(LogGDK, Error, TEXT("%s PlayerController: Already submitted Join request. Client attempting to join session multiple times."), *this->GetName()); - } - - // Inform Client as to whether or not join was accepted - ClientJoinResults(bJoinWasSuccessful); - - if (bJoinWasSuccessful) - { - bHasBeenGrantedPlayer = true; - - // Set the player-selected values - PlayerState->SetPlayerName(NewPlayerName); - Cast(PlayerState)->SetMetaData(MetaData); - - // Spawn the Pawn - RespawnCharacter(); - - // Add the player to the game's scoreboard. - if (AGDKGameState* GS = GetWorld()->GetGameState()) - { - GS->AddPlayer(PlayerState->PlayerId, NewPlayerName); - } - else - { - UE_LOG(LogGDK, Error, TEXT("%s: failed to add player because GameMode didn't exist"), - *GDKLogging::LogPrefix(this)); - } + Spawner->RequestSpawn(this); + return; } - } -bool AGDKPlayerController::ServerTryJoinGame_Validate(const FString& NewPlayerName, const FGDKMetaData MetaData) +bool AGDKPlayerController::ServerTryJoinGame_Validate() { return true; } -void AGDKPlayerController::ClientJoinResults_Implementation(const bool bJoinSucceeded) +void AGDKPlayerController::ServerRequestName_Implementation(const FString& NewPlayerName) { - if (bJoinSucceeded) - { - bHasBeenGrantedPlayer = true; - SetControllerState(EGDKControllerState::InProgress); - } - else + if (PlayerState) { - bHasRequetsedPlayer = false; - SetControllerState(EGDKControllerState::PreCharacter); + PlayerState->SetPlayerName(NewPlayerName); } - } -void AGDKPlayerController::RequestRespawn() +bool AGDKPlayerController::ServerRequestName_Validate(const FString& NewPlayerName) { - check(GetNetMode() == NM_Client); + return true; +} - if (CurrentCharacterState != EGDKCharacterState::Dead) +void AGDKPlayerController::ServerRequestMetaData_Implementation(const FGDKMetaData NewMetaData) +{ + if (UMetaDataComponent* MetaData = Cast(PlayerState->GetComponentByClass(UMetaDataComponent::StaticClass()))) { - return; + MetaData->SetMetaData(NewMetaData); } - - SetCharacterState(EGDKCharacterState::PendingRespawn); - - RespawnCharacter(); } -bool AGDKPlayerController::RespawnCharacter_Validate() +bool AGDKPlayerController::ServerRequestMetaData_Validate(const FGDKMetaData NewMetaData) { return true; } -void AGDKPlayerController::RespawnCharacter_Implementation() +void AGDKPlayerController::ServerRespawnCharacter_Implementation() { - check(GetNetMode() == NM_DedicatedServer); - - if (bGameFinished) + if (USpawnRequestPublisher* Spawner = Cast(GetWorld()->GetGameState()->GetComponentByClass(USpawnRequestPublisher::StaticClass()))) { + Spawner->RequestSpawn(this); return; } - - if (AGameModeBase* GameMode = GetWorld()->GetAuthGameMode()) - { - APawn* NewPawn = nullptr; - - ChooseNewSpawnPoint(); - - check(StartSpot.IsValid()); - - NewPawn = GameMode->SpawnDefaultPawnFor(this, StartSpot.Get()); - - Possess(NewPawn); - - AGDKCharacter* NewCharacter = Cast(NewPawn); - if (NewCharacter != nullptr) - { - AGDKPlayerState* GDKPlayerState = Cast(PlayerState); - if (GDKPlayerState) - { - NewCharacter->SetMetaData(GDKPlayerState->GetMetaData()); - NewCharacter->PlayerId = GDKPlayerState->PlayerId; - } - else - { - UE_LOG(LogGDK, Error, TEXT("%d: Created a player without a PlayerState"), *this->GetName()); - } - } - } -} - -void AGDKPlayerController::SetUIMode() -{ - bool bInMenu = CurrentControllerState != EGDKControllerState::InProgress || CurrentMenu != EGDKMenu::None || CurrentCharacterState != EGDKCharacterState::Alive; - SetUIMode(bInMenu, !bInMenu); -} - -void AGDKPlayerController::SetControllerState(EGDKControllerState NewState) -{ - CurrentControllerState = NewState; - OnControllerState.Broadcast(CurrentControllerState); - //There is currently no state transition where we should be keeping a menu window open - SetUIMode(); } -void AGDKPlayerController::SetCharacterState(EGDKCharacterState NewState) +bool AGDKPlayerController::ServerRespawnCharacter_Validate() { - CurrentCharacterState = NewState; - OnCharacterState.Broadcast(CurrentCharacterState); - SetUIMode(); -} - -void AGDKPlayerController::SetMenu(EGDKMenu NewMenu) -{ - CurrentMenu = NewMenu; - OnMenuChanged.Broadcast(CurrentMenu); - SetUIMode(); + return true; } -void AGDKPlayerController::ShowScoreboard() -{ - if (CurrentControllerState == EGDKControllerState::InProgress) - { - SetMenu(EGDKMenu::Scores); - } -} -void AGDKPlayerController::HideScoreboard() -{ - if (CurrentMenu == EGDKMenu::Scores) - { - SetMenu(EGDKMenu::None); - } -} -void AGDKPlayerController::ToggleMenu() -{ - if (CurrentControllerState == EGDKControllerState::InProgress) - { - if (CurrentMenu != EGDKMenu::Menu) - { - SetMenu(EGDKMenu::Menu); - } - else - { - SetMenu(EGDKMenu::None); - } - } -} diff --git a/Game/Source/GDKShooter/Private/Deployments/DeploymentsPlayerController.cpp b/Game/Source/GDKShooter/Private/Deployments/DeploymentsPlayerController.cpp index f1944a64..c4b30128 100644 --- a/Game/Source/GDKShooter/Private/Deployments/DeploymentsPlayerController.cpp +++ b/Game/Source/GDKShooter/Private/Deployments/DeploymentsPlayerController.cpp @@ -76,7 +76,7 @@ void ADeploymentsPlayerController::QueryPIT() { Worker_Alpha_PlayerIdentityTokenRequest* PITParams = new Worker_Alpha_PlayerIdentityTokenRequest(); // Replace this string with a dev auth token, see docs for information on how to generate one of these - PITParams->development_authentication_token_id = "REPLACE ME"; + PITParams->development_authentication_token = "REPLACE ME"; PITParams->player_id = "Player Id"; PITParams->display_name = ""; PITParams->metadata = ""; diff --git a/Game/Source/GDKShooter/Private/Game/Components/DeathmatchScoreComponent.cpp b/Game/Source/GDKShooter/Private/Game/Components/DeathmatchScoreComponent.cpp new file mode 100644 index 00000000..6af180f9 --- /dev/null +++ b/Game/Source/GDKShooter/Private/Game/Components/DeathmatchScoreComponent.cpp @@ -0,0 +1,59 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "DeathmatchScoreComponent.h" +#include "UnrealNetwork.h" + +UDeathmatchScoreComponent::UDeathmatchScoreComponent() +{ + PrimaryComponentTick.bCanEverTick = false; + bReplicates = true; +} + +void UDeathmatchScoreComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UDeathmatchScoreComponent, PlayerScoreArray); +} + +void UDeathmatchScoreComponent::RecordNewPlayer(APlayerState* PlayerState) +{ + if (!PlayerScoreMap.Contains(PlayerState->PlayerId)) + { + FPlayerScore NewPlayerScore; + NewPlayerScore.PlayerId = PlayerState->PlayerId; + NewPlayerScore.PlayerName = PlayerState->GetPlayerName(); + NewPlayerScore.Kills = 0; + NewPlayerScore.Deaths = 0; + + int32 Index = PlayerScoreArray.Add(NewPlayerScore); + PlayerScoreMap.Emplace(NewPlayerScore.PlayerId, Index); + } +} + +void UDeathmatchScoreComponent::RecordKill(const int32 Killer, const int32 Victim) +{ + if (Killer != Victim && PlayerScoreMap.Contains(Killer)) + { + ++PlayerScoreArray[PlayerScoreMap[Killer]].Kills; + } + if (PlayerScoreMap.Contains(Victim)) + { + ++PlayerScoreArray[PlayerScoreMap[Victim]].Deaths; + } +} + +void UDeathmatchScoreComponent::OnRep_PlayerScores() +{ + if (GetNetMode() == NM_Client) + { + // Re-sort player scores. + PlayerScoreArray.Sort([](const FPlayerScore& lhs, const FPlayerScore& rhs) + { + // Sort in reverse order. + return lhs.Kills == rhs.Kills ? lhs.Deaths < rhs.Deaths : lhs.Kills > rhs.Kills; + }); + } + + ScoreEvent.Broadcast(PlayerScoreArray); +} diff --git a/Game/Source/GDKShooter/Private/Game/Components/DeathmatchSpawnerComponent.cpp b/Game/Source/GDKShooter/Private/Game/Components/DeathmatchSpawnerComponent.cpp new file mode 100644 index 00000000..50091e18 --- /dev/null +++ b/Game/Source/GDKShooter/Private/Game/Components/DeathmatchSpawnerComponent.cpp @@ -0,0 +1,84 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "DeathmatchSpawnerComponent.h" + +#include "Characters/Components/MetaDataComponent.h" +#include "Components/TeamComponent.h" +#include "Engine/World.h" +#include "Game/Components/PlayerPublisher.h" +#include "GameFramework/GameModeBase.h" +#include "GameFramework/GameStateBase.h" +#include "GDKLogging.h" + +UDeathmatchSpawnerComponent::UDeathmatchSpawnerComponent() +{ + PrimaryComponentTick.bCanEverTick = false; +} + + +void UDeathmatchSpawnerComponent::RequestSpawn(APlayerController* Controller) +{ + // Spawn the Pawn + SpawnCharacter(Controller); + + if (Controller->PlayerState) + { + if (UPlayerPublisher* PlayerPublisher = Cast(GetWorld()->GetGameState()->GetComponentByClass(UPlayerPublisher::StaticClass()))) + { + PlayerPublisher->PublishPlayer(Controller->PlayerState, EPlayerProgress::InGame); + } + } +} + +AActor* UDeathmatchSpawnerComponent::GetSpawnPoint(APlayerController* Controller) +{ + AActor* NewStartSpot = GetWorld()->GetAuthGameMode()->ChoosePlayerStart(Controller); + if (NewStartSpot != nullptr) + { + // Set the player controller / camera in this new location + FRotator InitialControllerRot = NewStartSpot->GetActorRotation(); + InitialControllerRot.Roll = 0.f; + Controller->SetInitialLocationAndRotation(NewStartSpot->GetActorLocation(), InitialControllerRot); + } + return NewStartSpot; +} + +void UDeathmatchSpawnerComponent::SpawnCharacter(APlayerController* Controller) +{ + if (GetNetMode() == NM_Client) + { + UE_LOG(LogGDK, Error, TEXT("Attempting to call spawn player on a client.")); + return; + } + + if (!bSpawningEnabled) + { + UE_LOG(LogGDK, Error, TEXT("Requested a player spawn while spawning was disabled.")); + return; + } + + AActor* SpawnPoint = GetSpawnPoint(Controller); + + if (!SpawnPoint) + { + UE_LOG(LogGDK, Error, TEXT("Unable to find a valid spawn point.")); + return; + } + + if (AGameModeBase* GameMode = GetWorld()->GetAuthGameMode()) + { + APawn* NewPawn = nullptr; + + NewPawn = GameMode->SpawnDefaultPawnFor(Controller, SpawnPoint); + + Controller->Possess(NewPawn); + + if (UMetaDataComponent* StateMetaData = Cast(Controller->PlayerState->GetComponentByClass(UMetaDataComponent::StaticClass()))) + { + if (UMetaDataComponent* MetaData = Cast(NewPawn->GetComponentByClass(UMetaDataComponent::StaticClass()))) + { + MetaData->SetMetaData(StateMetaData->GetMetaData()); + } + } + } +} diff --git a/Game/Source/GDKShooter/Private/Game/Components/LobbyTimerComponent.cpp b/Game/Source/GDKShooter/Private/Game/Components/LobbyTimerComponent.cpp new file mode 100644 index 00000000..f3c2e884 --- /dev/null +++ b/Game/Source/GDKShooter/Private/Game/Components/LobbyTimerComponent.cpp @@ -0,0 +1,30 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "LobbyTimerComponent.h" +#include "GDKLogging.h" +#include "GameFramework/Actor.h" + +void ULobbyTimerComponent::BeginPlay() +{ + bAutoStart = (MinimumPlayersToStartCountdown == 0); + Super::BeginPlay(); +} + +void ULobbyTimerComponent::InformOfPlayerCount(int32 PlayerCount) +{ + if (bHasTimerFinished || !GetOwner()->HasAuthority()) + { + return; + } + + if (!bIsTimerRunning && PlayerCount >= MinimumPlayersToStartCountdown) + { + StartTimer(); + } + else if (bIsTimerRunning && PlayerCount < MinimumPlayersToStartCountdown) + { + StopTimer(); + SetTimer(DefaultTimerDuration); + } +} + diff --git a/Game/Source/GDKShooter/Private/Game/Components/MatchStateComponent.cpp b/Game/Source/GDKShooter/Private/Game/Components/MatchStateComponent.cpp new file mode 100644 index 00000000..f7569aaa --- /dev/null +++ b/Game/Source/GDKShooter/Private/Game/Components/MatchStateComponent.cpp @@ -0,0 +1,29 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "MatchStateComponent.h" +#include "UnrealNetwork.h" +#include "GameFramework/Actor.h" + +UMatchStateComponent::UMatchStateComponent() +{ + PrimaryComponentTick.bCanEverTick = false; + bReplicates = true; +} + +void UMatchStateComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UMatchStateComponent, CurrentState); +} + +void UMatchStateComponent::SetMatchState(EMatchState NewState) +{ + if (!GetOwner()->HasAuthority()) + { + return; + } + + CurrentState = NewState; + OnRep_State(); +} diff --git a/Game/Source/GDKShooter/Private/Game/Components/MatchTimerComponent.cpp b/Game/Source/GDKShooter/Private/Game/Components/MatchTimerComponent.cpp new file mode 100644 index 00000000..7a5879c8 --- /dev/null +++ b/Game/Source/GDKShooter/Private/Game/Components/MatchTimerComponent.cpp @@ -0,0 +1,5 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "MatchTimerComponent.h" + + diff --git a/Game/Source/GDKShooter/Private/Game/Components/PlayerCountingComponent.cpp b/Game/Source/GDKShooter/Private/Game/Components/PlayerCountingComponent.cpp new file mode 100644 index 00000000..b491fc3f --- /dev/null +++ b/Game/Source/GDKShooter/Private/Game/Components/PlayerCountingComponent.cpp @@ -0,0 +1,37 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "PlayerCountingComponent.h" +#include "UnrealNetwork.h" +#include "GDKLogging.h" + + +UPlayerCountingComponent::UPlayerCountingComponent() +{ + PrimaryComponentTick.bCanEverTick = false; + bReplicates = true; +} + +void UPlayerCountingComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UPlayerCountingComponent, ConnectedPlayerCount); +} + +void UPlayerCountingComponent::PlayerEvent(APlayerState* PlayerState, EPlayerProgress Progress) +{ + if (Progress == EPlayerProgress::Connected) + { + ConnectedPlayerCount++; + } + else if (Progress == EPlayerProgress::Disconnected) + { + ConnectedPlayerCount--; + } + PlayerCountEvent.Broadcast(ConnectedPlayerCount); +} + +void UPlayerCountingComponent::OnRep_ConnectedPlayerCount() +{ + PlayerCountEvent.Broadcast(ConnectedPlayerCount); +} diff --git a/Game/Source/GDKShooter/Private/Game/Components/SpatialSessionStateComponent.cpp b/Game/Source/GDKShooter/Private/Game/Components/SpatialSessionStateComponent.cpp new file mode 100644 index 00000000..73e7a631 --- /dev/null +++ b/Game/Source/GDKShooter/Private/Game/Components/SpatialSessionStateComponent.cpp @@ -0,0 +1,40 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpatialSessionStateComponent.h" + +#include "Engine/World.h" +#include "UnrealNetwork.h" +#include "SpatialNetDriver.h" +#include "SpatialStaticComponentView.h" +#include "SpatialWorkerConnection.h" + +#include +#include + +void USpatialSessionStateComponent::SendStateUpdate(EGDKSessionProgress SessionProgressState) +{ + // Only send the state update if we're using Spatial networking and if we have authority over the session entity. + UNetDriver* NetDriver = GetWorld()->GetNetDriver(); + if (NetDriver == nullptr || !NetDriver->IsA()) + { + return; + } + + USpatialNetDriver* SpatialNetDriver = Cast(NetDriver); + bool bAuthoritativeOverSessionEntity = SpatialNetDriver->StaticComponentView->HasAuthority(SessionEntityId, SessionComponentId); + if (!bAuthoritativeOverSessionEntity) + { + return; + } + + // There's an offset of 1 between the corresponding states of session progress and session state. + int SessionState = static_cast(SessionProgressState) + 1; + + Worker_EntityId target_entity_id = SessionEntityId; + Worker_ComponentUpdate component_update = {}; + component_update.component_id = SessionComponentId; + component_update.schema_type = Schema_CreateComponentUpdate(SessionComponentId); + Schema_Object* fields_object = Schema_GetComponentUpdateFields(component_update.schema_type); + Schema_AddInt32(fields_object, 1, SessionState); + SpatialNetDriver->Connection->SendComponentUpdate(target_entity_id, &component_update); +} diff --git a/Game/Source/GDKShooter/Private/Game/Components/SpawnRequestPublisher.cpp b/Game/Source/GDKShooter/Private/Game/Components/SpawnRequestPublisher.cpp new file mode 100644 index 00000000..b5aacc99 --- /dev/null +++ b/Game/Source/GDKShooter/Private/Game/Components/SpawnRequestPublisher.cpp @@ -0,0 +1,10 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "SpawnRequestPublisher.h" + + +USpawnRequestPublisher::USpawnRequestPublisher() +{ + PrimaryComponentTick.bCanEverTick = false; +} + diff --git a/Game/Source/GDKShooter/Private/Game/Components/TeamSpawnerComponent.cpp b/Game/Source/GDKShooter/Private/Game/Components/TeamSpawnerComponent.cpp new file mode 100644 index 00000000..3c8a5b32 --- /dev/null +++ b/Game/Source/GDKShooter/Private/Game/Components/TeamSpawnerComponent.cpp @@ -0,0 +1,136 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "TeamSpawnerComponent.h" + +#include "Components/MetaDataComponent.h" +#include "Components/TeamComponent.h" +#include "EngineUtils.h" +#include "Components/PlayerPublisher.h" +#include "GameFramework/GameModeBase.h" +#include "GameFramework/GameStateBase.h" +#include "GameFramework/PlayerController.h" +#include "GameFramework/PlayerState.h" +#include "GDKLogging.h" +#include "Runtime/AIModule/Classes/GenericTeamAgentInterface.h" + +UTeamSpawnerComponent::UTeamSpawnerComponent() +{ + PrimaryComponentTick.bCanEverTick = false; +} + +void UTeamSpawnerComponent::BeginPlay() +{ + Super::BeginPlay(); + + //Assign PlayerStart's to TeamId's + int32 TeamId = CurrentTeamPointer; + for (TActorIterator It(GetWorld()); It; ++It) + { + APlayerStart* PlayerStart = *It; + if (PlayerStart->IsA()) + { + PlayerStartPIE = Cast(PlayerStart); + } + else + { + TeamStartPoints.Add(TeamId, PlayerStart); + TeamAssignments.Add(TeamId, 0); + TeamId++; + } + } +} + +int32 UTeamSpawnerComponent::GetAvailableTeamId() +{ + int32 AssignedTeam = CurrentTeamPointer; + + if (!TeamAssignments.Contains(CurrentTeamPointer)) + { + TeamAssignments[CurrentTeamPointer] = 0; + } + + TeamAssignments[CurrentTeamPointer]++; + + if (TeamAssignments[CurrentTeamPointer] == TeamCapacity) + { + CurrentTeamPointer++; + } + + return AssignedTeam; +} + +void UTeamSpawnerComponent::RequestSpawn(APlayerController* Controller) +{ + if (CurrentTeamPointer >= TeamAssignments.Num() && !PlayerStartPIE) + { + // We should be limiting the player count before we run out of teams + return; + } + + int32 TeamId = 255; + + if (SpawnedPlayers.Contains(Controller)) + { + TeamId = SpawnedPlayers[Controller]; + // This is a respawn, we either grant another player character, or a spectator pawn + if (!bAllowRespawning) + { + return; + } + } + else + { + TeamId = GetAvailableTeamId(); + SpawnedPlayers.Add(Controller, TeamId); + } + + APlayerStart* PlayerStart = PlayerStartPIE ? PlayerStartPIE : TeamStartPoints[TeamId]; + + if (AGameModeBase* GameMode = GetWorld()->GetAuthGameMode()) + { + APawn* NewPawn = nullptr; + + NewPawn = GameMode->SpawnDefaultPawnFor(Controller, PlayerStart); + + Controller->Possess(NewPawn); + + if (!NewPawn) + { + UE_LOG(LogGDK, Error, TEXT("Null Pawn Returned from SpawnDefaultPawn")); + return; + } + + if (UMetaDataComponent* MetaDataComponent = Cast(NewPawn->GetComponentByClass(UMetaDataComponent::StaticClass()))) + { + FGDKMetaData MetaData; + MetaData.Customization = TeamId; + MetaDataComponent->SetMetaData(MetaData); + } + if (UTeamComponent* TeamComponent = Cast(NewPawn->GetComponentByClass(UTeamComponent::StaticClass()))) + { + TeamComponent->SetTeam(FGenericTeamId(TeamId)); + } + else + { + UE_LOG(LogGDK, Error, TEXT("TeamComponent Required on Character")); + } + + if (Controller->PlayerState != nullptr) + { + if (UTeamComponent* TeamComponent = Cast(Controller->PlayerState->GetComponentByClass(UTeamComponent::StaticClass()))) + { + TeamComponent->SetTeam(FGenericTeamId(TeamId)); + } + else + { + UE_LOG(LogGDK, Error, TEXT("TeamComponent Required on PlayerState")); + } + + if (UPlayerPublisher* PlayerPublisher = Cast(GetWorld()->GetGameState()->GetComponentByClass(UPlayerPublisher::StaticClass()))) + { + PlayerPublisher->PublishPlayer(Controller->PlayerState, EPlayerProgress::InGame); + } + } + } + +} diff --git a/Game/Source/GDKShooter/Private/Game/Components/TimerComponent.cpp b/Game/Source/GDKShooter/Private/Game/Components/TimerComponent.cpp new file mode 100644 index 00000000..26b45f81 --- /dev/null +++ b/Game/Source/GDKShooter/Private/Game/Components/TimerComponent.cpp @@ -0,0 +1,85 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "TimerComponent.h" +#include "Engine/World.h" +#include "GDKLogging.h" +#include "UnrealNetwork.h" + +UTimerComponent::UTimerComponent() +{ + PrimaryComponentTick.bCanEverTick = false; + bReplicates = true; +} + +void UTimerComponent::BeginPlay() +{ + Super::BeginPlay(); + TimeLeft = DefaultTimerDuration; + if (bAutoStart) + { + StartTimer(); + } +} + +void UTimerComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UTimerComponent, bIsTimerRunning); + DOREPLIFETIME(UTimerComponent, TimeLeft); + DOREPLIFETIME(UTimerComponent, bHasTimerFinished); +} + +void UTimerComponent::StartTimer() +{ + TimeLeft = DefaultTimerDuration; + ResumeTimer(); +} + +void UTimerComponent::ResumeTimer() +{ + if (bIsTimerRunning) + { + return; + } + + bIsTimerRunning = true; + GetWorld()->GetTimerManager().SetTimer(TimerHandle, this, &UTimerComponent::DecrementTimer, 1.0f, true, 1.0f); +} + +void UTimerComponent::SetTimer(int32 NewValue) +{ + TimeLeft = NewValue; + bHasTimerFinished = false; +} + +void UTimerComponent::StopTimer() +{ + bIsTimerRunning = false; + GetWorld()->GetTimerManager().ClearTimer(TimerHandle); +} + +void UTimerComponent::OnRep_Timer() +{ + OnTimer.Broadcast(TimeLeft); +} + +void UTimerComponent::OnRep_TimerFinished() +{ + if (bHasTimerFinished) + { + OnTimerFinished.Broadcast(); + } +} + +void UTimerComponent::DecrementTimer() +{ + TimeLeft--; + + if (TimeLeft <= 0) + { + bHasTimerFinished = true; + StopTimer(); + OnTimerFinished.Broadcast(); + } +} diff --git a/Game/Source/GDKShooter/Private/Game/GDKGameState.cpp b/Game/Source/GDKShooter/Private/Game/GDKGameState.cpp deleted file mode 100644 index 9e38fb3a..00000000 --- a/Game/Source/GDKShooter/Private/Game/GDKGameState.cpp +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#include "GDKGameState.h" - -#include "GDKLogging.h" -#include "UnrealNetwork.h" - -AGDKGameState::AGDKGameState() -{ - PrimaryActorTick.bCanEverTick = false; -} - -void AGDKGameState::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const -{ - Super::GetLifetimeReplicatedProps(OutLifetimeProps); - - DOREPLIFETIME(AGDKGameState, PlayerScoreArray); - DOREPLIFETIME(AGDKGameState, ConnectedPlayers); -} - -void AGDKGameState::AddPlayerState(APlayerState* PlayerState) -{ - Super::AddPlayerState(PlayerState); - - if (GetNetMode() != NM_Client) - { - ConnectedPlayers = PlayerArray.Num(); - } -} - -void AGDKGameState::RemovePlayerState(APlayerState* PlayerState) -{ - Super::RemovePlayerState(PlayerState); - - if (GetNetMode() != NM_Client) - { - ConnectedPlayers = PlayerArray.Num(); - } -} - -void AGDKGameState::AddPlayer(const int32 Player, const FString& PlayerName) -{ - // Start game ticking once the first player joins - // Could later make this wait for a certain number of players - - if(PlayerScoreMap.Contains(Player)) - { - // Player already added - return; - } - - AddPlayerInternal(Player, PlayerName); -} - -void AGDKGameState::AddDeath(const int32 Killer, const int32 Victim) -{ - if (Killer != Victim && PlayerScoreMap.Contains(Killer)) - { - ++PlayerScoreArray[PlayerScoreMap[Killer]].Kills; - } - if (PlayerScoreMap.Contains(Victim)) - { - ++PlayerScoreArray[PlayerScoreMap[Victim]].Deaths; - } - - - //MulticastNotifyKill(Killer, PlayerScores[Killer]->PlayerName, Victim, PlayerScores[Victim]->PlayerName); -} - -void AGDKGameState::OnRep_PlayerScores() -{ - if (GetNetMode() == NM_Client) - { - // Re-sort player scores. - PlayerScoreArray.Sort([](const FPlayerScore& lhs, const FPlayerScore& rhs) - { - // Sort in reverse order. - return lhs.Kills == rhs.Kills ? lhs.Deaths < rhs.Deaths : lhs.Kills > rhs.Kills; - }); - } - - ScoreEvent.Broadcast(PlayerScoreArray); -} - -void AGDKGameState::OnRep_ConnectedPlayers() -{ - PlayerCountEvent.Broadcast(ConnectedPlayers); -} - -void AGDKGameState::AddPlayerInternal(const int32 Player, const FString& PlayerName) -{ - FPlayerScore NewPlayerScore; - NewPlayerScore.PlayerId = Player; - NewPlayerScore.PlayerName = PlayerName; - NewPlayerScore.Kills = 0; - NewPlayerScore.Deaths = 0; - - int32 Index = PlayerScoreArray.Add(NewPlayerScore); - PlayerScoreMap.Emplace(Player, Index); -} - -void AGDKGameState::MulticastNotifyKill_Implementation(int32 KillerId, const FString& KillerName, int32 VictimId, const FString& VictimName) -{ - if (GetNetMode() == NM_Client) - { - //Not currently implemented - //KillNotificationCallback.ExecuteIfBound(KillerId, KillerName, VictimId, VictimName); - } -} - - - diff --git a/Game/Source/GDKShooter/Private/Game/GDKPlayerState.cpp b/Game/Source/GDKShooter/Private/Game/GDKPlayerState.cpp deleted file mode 100644 index 1bb3fed5..00000000 --- a/Game/Source/GDKShooter/Private/Game/GDKPlayerState.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#include "Game/GDKPlayerState.h" - -#include "Net/UnrealNetwork.h" -#include "SpatialNetDriver.h" - - -void AGDKPlayerState::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const -{ - Super::GetLifetimeReplicatedProps(OutLifetimeProps); - - DOREPLIFETIME(AGDKPlayerState, MetaData); -} diff --git a/Game/Source/GDKShooter/Private/Game/GDKSessionGameState.cpp b/Game/Source/GDKShooter/Private/Game/GDKSessionGameState.cpp deleted file mode 100644 index 0ca88a0f..00000000 --- a/Game/Source/GDKShooter/Private/Game/GDKSessionGameState.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#include "GDKSessionGameState.h" - -#include "TimerManager.h" -#include "UnrealNetwork.h" -#include "SpatialNetDriver.h" -#include "SpatialWorkerConnection.h" -#include "c_worker.h" -#include "c_schema.h" - - -void AGDKSessionGameState::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const -{ - Super::GetLifetimeReplicatedProps(OutLifetimeProps); - - DOREPLIFETIME(AGDKSessionGameState, SessionProgress); - DOREPLIFETIME(AGDKSessionGameState, SessionTimer); -} - -void AGDKSessionGameState::AddPlayerState(APlayerState* PlayerState) -{ - Super::AddPlayerState(PlayerState); - if (GetNetMode() != NM_Client) - { - if (ConnectedPlayers == 1) - { - BeginTimer(); - } - } -} - -void AGDKSessionGameState::RemovePlayerState(APlayerState* PlayerState) -{ - Super::RemovePlayerState(PlayerState); - if (GetNetMode() != NM_Client) - { - if (ConnectedPlayers == 0 && SessionProgress == EGDKSessionProgress::Lobby) - { - GetWorld()->GetTimerManager().ClearTimer(TickTimer); - } - } -} - -void AGDKSessionGameState::OnRep_SessionProgress() -{ - TimerEvent.Broadcast(SessionProgress, SessionTimer); -} - -void AGDKSessionGameState::OnRep_SessionTimer() -{ - TimerEvent.Broadcast(SessionProgress, SessionTimer); -} - -void AGDKSessionGameState::BeginTimer() -{ - if (!GetWorldTimerManager().IsTimerActive(TickTimer)) - { - SessionProgress = EGDKSessionProgress::Lobby; - SessionTimer = LobbySessionLength; - GetWorldTimerManager().SetTimer(TickTimer, this, &AGDKSessionGameState::TickGameTimer, 1.0f, true, 1.0f); - } -} - -void AGDKSessionGameState::TickGameTimer() -{ - if (GetNetMode() != NM_Client) - { - SessionTimer--; - - if (SessionProgress == EGDKSessionProgress::Lobby && SessionTimer <= 0) - { - UE_LOG(LogGDK, Log, TEXT("Advance GameState to Running")); - SessionProgress = EGDKSessionProgress::Running; - SendStateUpdate(2); - SessionTimer = GameSessionLength; - } - if (SessionProgress == EGDKSessionProgress::Running && SessionTimer <= 0) - { - UE_LOG(LogGDK, Log, TEXT("Advance GameState to Results")); - SessionProgress = EGDKSessionProgress::Results; - SendStateUpdate(3); - SessionTimer = ResultsSessionLength; - } - if (SessionProgress == EGDKSessionProgress::Results && SessionTimer <= 0) - { - UE_LOG(LogGDK, Log, TEXT("Advance GameState to Finished")); - SessionProgress = EGDKSessionProgress::Finished; - SendStateUpdate(4); - } - } -} - -void AGDKSessionGameState::SendStateUpdate(int NewState) -{ - if (!GetWorld()->GetNetDriver() || !GetWorld()->GetNetDriver()->IsA()) - { - return; - } - - Worker_EntityId target_entity_id = 39; - Worker_ComponentUpdate component_update = {}; - component_update.component_id = 1000; - component_update.schema_type = Schema_CreateComponentUpdate(1000); - Schema_Object* fields_object = Schema_GetComponentUpdateFields(component_update.schema_type); - Schema_AddInt32(fields_object, 1, NewState); - Cast(GetWorld()->GetNetDriver())->Connection->SendComponentUpdate(target_entity_id, &component_update); -} - - diff --git a/Game/Source/GDKShooter/Private/GameFramework/CrossServerPawn.cpp b/Game/Source/GDKShooter/Private/GameFramework/CrossServerPawn.cpp new file mode 100644 index 00000000..a36bc130 --- /dev/null +++ b/Game/Source/GDKShooter/Private/GameFramework/CrossServerPawn.cpp @@ -0,0 +1,15 @@ + // Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "CrossServerPawn.h" + +float ACrossServerPawn::TakeDamage(float Damage, const FDamageEvent& DamageEvent, AController* EventInstigator, AActor* DamageCauser) +{ + TakeDamageCrossServer(Damage, DamageEvent, nullptr, DamageCauser); + return Damage; +} + +void ACrossServerPawn::TakeDamageCrossServer_Implementation(float Damage, const FDamageEvent& DamageEvent, AController* EventInstigator, AActor* DamageCauser) +{ + float ActualDamage = Super::TakeDamage(Damage, DamageEvent, EventInstigator, DamageCauser); + IncomingDamage.Broadcast(ActualDamage, DamageEvent, EventInstigator, DamageCauser); +} diff --git a/Game/Source/GDKShooter/Private/UI/GDKWidget.cpp b/Game/Source/GDKShooter/Private/UI/GDKWidget.cpp index 65a9bf16..ee279e09 100644 --- a/Game/Source/GDKShooter/Private/UI/GDKWidget.cpp +++ b/Game/Source/GDKShooter/Private/UI/GDKWidget.cpp @@ -1,13 +1,16 @@ // Copyright (c) Improbable Worlds Ltd, All Rights Reserved #include "GDKWidget.h" -#include "Game/GDKGameState.h" -#include "Game/GDKSessionGameState.h" -#include "Game/GDKPlayerState.h" -#include "Controllers/GDKPlayerController.h" -#include "GDKLogging.h" +#include "Components/ControllerEventsComponent.h" +#include "Components/GDKMovementComponent.h" +#include "Components/HealthComponent.h" +#include "Game/Components/LobbyTimerComponent.h" +#include "Game/Components/MatchTimerComponent.h" +#include "Game/Components/PlayerCountingComponent.h" +#include "GameFramework/GameStateBase.h" +#include "Weapons/InstantWeapon.h" -// Registr listeners on AGDKPlayerController and AGDKGameState +// Register listeners on AGDKPlayerController and AGDKGameState void UGDKWidget::NativeConstruct() { Super::NativeConstruct(); @@ -21,42 +24,89 @@ void UGDKWidget::NativeConstruct() bListenersAdded = true; } - PlayerController = GetOwningPlayer(); + RegisterListeners(); + + APlayerController* PlayerController = GetOwningPlayer(); if (AGDKPlayerController* GDKPC = Cast(PlayerController)) { - GDKPC->OnHealthUpdated().AddUObject(this, &UGDKWidget::OnHealthUpdated); - GDKPC->OnArmourUpdated().AddUObject(this, &UGDKWidget::OnArmourUpdated); - GDKPC->OnKillNotification().AddUObject(this, &UGDKWidget::OnKill); - GDKPC->OnKilledNotification().AddUObject(this, &UGDKWidget::OnDeath); - GDKPC->OnAimingUpdated().AddUObject(this, &UGDKWidget::OnAimingUpdated); - GDKPC->OnWeaponChanged().AddUObject(this, &UGDKWidget::OnWeaponChanged); - GDKPC->OnShot().AddUObject(this, &UGDKWidget::OnShot); + GDKPlayerController = GDKPC; + GDKPlayerController->OnPawn().AddDynamic(this, &UGDKWidget::OnPawn); + OnPawn(GDKPlayerController->GetPawn()); + } + + if (UControllerEventsComponent* ControllerEvents = Cast(PlayerController->GetComponentByClass(UControllerEventsComponent::StaticClass()))) + { + ControllerEvents->KillDetailsEvent.AddDynamic(this, &UGDKWidget::OnKill); + ControllerEvents->DeathDetailsEvent.AddDynamic(this, &UGDKWidget::OnDeath); } - if (AGDKGameState* GS = GetWorld()->GetGameState()) + if (UDeathmatchScoreComponent* Deathmatch = Cast(GetWorld()->GetGameState()->GetComponentByClass(UDeathmatchScoreComponent::StaticClass()))) { - GS->OnScoreUpdated().AddUObject(this, &UGDKWidget::OnPlayerScoresUpdated); - OnPlayerScoresUpdated(GS->PlayerScores()); - GS->OnPlayerCountUpdated().AddUObject(this, &UGDKWidget::OnPlayerCountUpdated); - OnPlayerCountUpdated(GS->ConnectedPlayers); + Deathmatch->ScoreEvent.AddDynamic(this, &UGDKWidget::OnPlayerScoresUpdated); + OnPlayerScoresUpdated(Deathmatch->PlayerScores()); } - if (AGDKSessionGameState* SGS = GetWorld()->GetGameState()) + + if (UPlayerCountingComponent* PlayerCounter = Cast(GetWorld()->GetGameState()->GetComponentByClass(UPlayerCountingComponent::StaticClass()))) + { + PlayerCounter->PlayerCountEvent.AddDynamic(this, &UGDKWidget::OnPlayerCountUpdated); + OnPlayerCountUpdated(PlayerCounter->PlayerCount()); + } + + if (UMatchStateComponent* MatchState = Cast(GetWorld()->GetGameState()->GetComponentByClass(UMatchStateComponent::StaticClass()))) + { + MatchState->MatchEvent.AddDynamic(this, &UGDKWidget::OnStateUpdated); + OnStateUpdated(MatchState->GetCurrentState()); + } + + if (UMatchTimerComponent* MatchTimer = Cast(GetWorld()->GetGameState()->GetComponentByClass(UMatchTimerComponent::StaticClass()))) + { + MatchTimer->OnTimer.AddDynamic(this, &UGDKWidget::OnMatchTimerUpdated); + OnMatchTimerUpdated(MatchTimer->GetTimer()); + } + + if (ULobbyTimerComponent* LobbyTimer = Cast(GetWorld()->GetGameState()->GetComponentByClass(ULobbyTimerComponent::StaticClass()))) + { + LobbyTimer->OnTimer.AddDynamic(this, &UGDKWidget::OnLobbyTimerUpdated); + OnLobbyTimerUpdated(LobbyTimer->GetTimer()); + } +} + +void UGDKWidget::OnPawn(APawn* InPawn) +{ + if (!InPawn) + { + return; + } + + if (UGDKMovementComponent* Movement = Cast< UGDKMovementComponent>(InPawn->GetComponentByClass(UGDKMovementComponent::StaticClass()))) + { + Movement->OnAimingUpdated.AddUniqueDynamic(this, &UGDKWidget::OnAimingUpdated); + } + + if (UHealthComponent* Health = Cast< UHealthComponent>(InPawn->GetComponentByClass(UHealthComponent::StaticClass()))) + { + Health->HealthUpdated.AddUniqueDynamic(this, &UGDKWidget::OnHealthUpdated); + Health->ArmourUpdated.AddUniqueDynamic(this, &UGDKWidget::OnArmourUpdated); + OnHealthUpdated(Health->GetCurrentHealth(), Health->GetMaxHealth()); + OnArmourUpdated(Health->GetCurrentArmour(), Health->GetMaxArmour()); + } + + if (UShootingComponent* Shooting = Cast< UShootingComponent>(InPawn->GetComponentByClass(UShootingComponent::StaticClass()))) { - SGS->OnTimerUpdated().AddUObject(this, &UGDKWidget::OnTimerUpdated); - OnTimerUpdated(SGS->SessionProgress, SGS->SessionTimer); + Shooting->ShotEvent.AddUniqueDynamic(this, &UGDKWidget::OnShot); } } // Function to call to ClientTravel to the TargetMap void UGDKWidget::LeaveGame(const FString& TargetMap) { - check(PlayerController); + check(GetOwningPlayer()); FURL TravelURL; TravelURL.Map = *TargetMap; - PlayerController->ClientTravel(TravelURL.ToString(), TRAVEL_Absolute, false /*bSeamless*/); + GetOwningPlayer()->ClientTravel(TravelURL.ToString(), TRAVEL_Absolute, false /*bSeamless*/); } diff --git a/Game/Source/GDKShooter/Private/Weapons/Holdable.cpp b/Game/Source/GDKShooter/Private/Weapons/Holdable.cpp new file mode 100644 index 00000000..8976a7d3 --- /dev/null +++ b/Game/Source/GDKShooter/Private/Weapons/Holdable.cpp @@ -0,0 +1,86 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#include "Holdable.h" +#include "GDKLogging.h" +#include "Components/EquippedComponent.h" +#include "Components/SkeletalMeshComponent.h" +#include "UnrealNetwork.h" + + +AHoldable::AHoldable() +{ + // Default to not ticking + PrimaryActorTick.bCanEverTick = false; + + bReplicates = true; + bReplicateMovement = true; + + LocationComponent = CreateDefaultSubobject(TEXT("RootComponent")); + SetRootComponent(LocationComponent); + + Mesh = CreateDefaultSubobject(TEXT("WeaponMesh")); + Mesh->SetupAttachment(RootComponent); +} + +void AHoldable::BeginPlay() +{ + Super::BeginPlay(); + OnMetaDataUpdated(); +} + +void AHoldable::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(AHoldable, MetaData); + DOREPLIFETIME(AHoldable, CurrentMode); +} + +void AHoldable::StartPrimaryUse_Implementation() { IsPrimaryUsing = true; } +void AHoldable::StopPrimaryUse_Implementation() { IsPrimaryUsing = false; } +void AHoldable::StartSecondaryUse_Implementation() { IsSecondaryUsing = true; } +void AHoldable::StopSecondaryUse_Implementation() { IsSecondaryUsing = false; } + +void AHoldable::OnRep_MetaData() +{ + if (GetNetMode() == NM_DedicatedServer) + { + return; + } + + OnMetaDataUpdated(); +} + +void AHoldable::SetMetaData(FGDKMetaData NewMetaData) +{ + if (HasAuthority()) + { + MetaData = NewMetaData; + } + OnMetaDataUpdated(); +} + +void AHoldable::SetIsActive_Implementation(bool bNewIsActive) +{ + //TODO Find logic for sheathing inactive weapons + bIsActive = bNewIsActive; + this->SetActorHiddenInGame(!bNewIsActive); + StopPrimaryUse(); + StopSecondaryUse(); +} + +FVector AHoldable::EffectSpawnPoint() +{ + return Mesh->GetSocketLocation(EffectSocketName); +} + +void AHoldable::SetFirstPerson_Implementation(bool bNewFirstPerson) +{ + bIsFirstPerson = bNewFirstPerson; + Mesh->CastShadow = !bNewFirstPerson; +} + +void AHoldable::ToggleMode_Implementation() +{ + +} diff --git a/Game/Source/GDKShooter/Private/Weapons/InstantWeapon.cpp b/Game/Source/GDKShooter/Private/Weapons/InstantWeapon.cpp index b421291a..fa654f7f 100644 --- a/Game/Source/GDKShooter/Private/Weapons/InstantWeapon.cpp +++ b/Game/Source/GDKShooter/Private/Weapons/InstantWeapon.cpp @@ -2,9 +2,9 @@ #include "InstantWeapon.h" -#include "Characters/Core/GDKShooterCharacter.h" #include "Engine/World.h" #include "Kismet/GameplayStatics.h" +#include "GameFramework/DamageType.h" #include "GDKLogging.h" #include "UnrealNetwork.h" @@ -14,7 +14,7 @@ AInstantWeapon::AInstantWeapon() BurstInterval = 0.5f; BurstCount = 1; ShotInterval = 0.2f; - LastBurstTime = 0.0f; + NextBurstTime = 0.0f; BurstShotsRemaining = 0; ShotBaseDamage = 10.0f; HitValidationTolerance = 50.0f; @@ -22,135 +22,46 @@ AInstantWeapon::AInstantWeapon() ShotVisualizationDelayTolerance = FTimespan::FromMilliseconds(3000.0f); } -void AInstantWeapon::ActuallyStartFiring() +void AInstantWeapon::StartPrimaryUse_Implementation() { - SetWeaponState(EWeaponState::Firing); - - // Initialize the burst. - LastBurstTime = UGameplayStatics::GetRealTimeSeconds(GetWorld()); - BurstShotsRemaining = BurstCount; -} - -bool AInstantWeapon::ReadyToStartFiring() -{ - float Now = UGameplayStatics::GetRealTimeSeconds(GetWorld()); - if (BurstCount > 0) - { - return GetWeaponState() == EWeaponState::Idle && Now > LastBurstTime + BurstInterval; - } - else - { - return GetWeaponState() == EWeaponState::Idle && ReadyToFire(); - } -} - -bool AInstantWeapon::ReadyToFire() -{ - float Now = UGameplayStatics::GetRealTimeSeconds(GetWorld()); - return Now > LastShotTime + ShotInterval; -} - -void AInstantWeapon::StartFire() -{ - check(GetNetMode() == NM_Client); - - if (!bIsActive) - { - return; - } - - float Now = UGameplayStatics::GetRealTimeSeconds(GetWorld()); - if (ReadyToFire()) - { - ActuallyStartFiring(); - } - else - { - ShotBuffered = true; - ShotBufferedUntil = Now + ShotInputLeniancy; - } -} - -void AInstantWeapon::Tick(float DeltaTime) -{ - Super::Tick(DeltaTime); - - if (GetNetMode() != NM_Client) - { - return; - } - - if (GetWeaponState() == EWeaponState::Firing) + if (IsBurstFire()) { - if (ReadyToFire()) + if (!IsPrimaryUsing && NextBurstTime < UGameplayStatics::GetRealTimeSeconds(GetWorld())) { - DoFire(); - } - } - else if(ShotBuffered) - { - float Now = UGameplayStatics::GetRealTimeSeconds(GetWorld()); - if (ShotBufferedUntil > Now) - { - if (ReadyToFire()) - { - ShotBuffered = false; - ActuallyStartFiring(); - DoFire(); - } - } - else - { - ShotBuffered = false; + BurstShotsRemaining = BurstCount; + NextBurstTime = UGameplayStatics::GetRealTimeSeconds(GetWorld()) + BurstInterval; } } + + Super::StartPrimaryUse_Implementation(); } -void AInstantWeapon::StopFire() +void AInstantWeapon::StopPrimaryUse_Implementation() { - if (GetNetMode() != NM_Client) - { - return; - } - - ShotBuffered = false; // Can't force stop a burst. - if (GetWeaponState() == EWeaponState::Firing && !IsBurstFire()) + if (!IsBurstFire() || bAllowContinuousBurstFire) { - StopFiring(); + Super::StopPrimaryUse_Implementation(); } } -void AInstantWeapon::BeginPlay() -{ - Super::BeginPlay(); - - GetWorld()->DebugDrawTraceTag = kTraceTag; -} - -void AInstantWeapon::EndPlay(const EEndPlayReason::Type Reason) -{ - Super::EndPlay(Reason); -} - -void AInstantWeapon::DoFire() +void AInstantWeapon::DoFire_Implementation() { - check(GetNetMode() == NM_Client); - - if (!GetOwningCharacter()->CanFire()) + if (!bIsActive) { - // Don't attempt to fire if the character can't. + IsPrimaryUsing = false; + ConsumeBufferedShot(); return; } - LastShotTime = UGameplayStatics::GetRealTimeSeconds(GetWorld()); - - FInstantHitInfo HitInfo; - if (DoLineTrace(HitInfo)) + NextShotTime = UGameplayStatics::GetRealTimeSeconds(GetWorld()) + ShotInterval; + + FInstantHitInfo HitInfo = DoLineTrace(); + if (HitInfo.bDidHit) { ServerDidHit(HitInfo); SpawnFX(HitInfo, true); // Spawn the hit fx locally - AnnounceShot(HitInfo.HitActor->bCanBeDamaged); + AnnounceShot(HitInfo.HitActor ? HitInfo.HitActor->bCanBeDamaged : false); } else { @@ -165,7 +76,20 @@ void AInstantWeapon::DoFire() if (BurstShotsRemaining <= 0) { FinishedBurst(); - StopFiring(); + if (bAllowContinuousBurstFire) + { + BurstShotsRemaining = BurstCount; + // We will force a cooldown for the full burst interval, regardless of the time already consumed in the previous burst, for simplicity. + ForceCooldown(BurstInterval); + } + else + { + if (GetMovementComponent()) + { + GetMovementComponent()->SetIsBusy(false); + } + IsPrimaryUsing = false; + } } } @@ -173,21 +97,28 @@ void AInstantWeapon::DoFire() FVector AInstantWeapon::GetLineTraceDirection() { - float SpreadToUse = GetOwningCharacter()->IsAiming() ? SpreadAt100mWhenAiming : SpreadAt100m; - if (GetOwningCharacter()->bIsCrouched) + FVector Direction = Super::GetLineTraceDirection(); + + float SpreadToUse = SpreadAt100m; + if (GetMovementComponent()) { - SpreadToUse *= SpreadCrouchModifier; + if (GetMovementComponent()->IsAiming()) + { + SpreadToUse = SpreadAt100mWhenAiming; + } + if (GetMovementComponent()->IsCrouching()) + { + SpreadToUse *= SpreadCrouchModifier; + } } if (SpreadToUse > 0) { auto Spread = FMath::RandPointInCircle(SpreadToUse); - return GetOwningCharacter()->GetLineTraceDirection().Rotation().RotateVector(FVector(10000, Spread.X, Spread.Y)); - } - else - { - return GetOwningCharacter()->GetLineTraceDirection(); + Direction = Direction.Rotation().RotateVector(FVector(10000, Spread.X, Spread.Y)); } + + return Direction; } void AInstantWeapon::NotifyClientsOfHit(const FInstantHitInfo& HitInfo, bool bImpact) @@ -245,7 +176,10 @@ void AInstantWeapon::DealDamage(const FInstantHitInfo& HitInfo) DmgEvent.DamageTypeClass = DamageTypeClass; DmgEvent.HitInfo.ImpactPoint = HitInfo.Location; - HitInfo.HitActor->TakeDamage(ShotBaseDamage, DmgEvent, GetOwningCharacter()->GetController(), this); + if (APawn* Pawn = Cast(GetOwner())) + { + HitInfo.HitActor->TakeDamage(ShotBaseDamage, DmgEvent, Pawn->GetController(), this); + } } bool AInstantWeapon::ServerDidHit_Validate(const FInstantHitInfo& HitInfo) @@ -255,11 +189,6 @@ bool AInstantWeapon::ServerDidHit_Validate(const FInstantHitInfo& HitInfo) void AInstantWeapon::ServerDidHit_Implementation(const FInstantHitInfo& HitInfo) { - if (!GetOwningCharacter()->CanFire()) - { - UE_LOG(LogGDK, Verbose, TEXT("%s server: rejected shot because character is unable to fire"), *this->GetName()); - return; - } bool bDoNotifyHit = false; @@ -293,28 +222,24 @@ bool AInstantWeapon::ServerDidMiss_Validate(const FInstantHitInfo& HitInfo) void AInstantWeapon::ServerDidMiss_Implementation(const FInstantHitInfo& HitInfo) { - if (!GetOwningCharacter()->CanFire()) - { - UE_LOG(LogGDK, Verbose, TEXT("%s server: rejected shot because character is unable to fire"), *this->GetName()); - return; - } - NotifyClientsOfHit(HitInfo, false); } -void AInstantWeapon::StopFiring() -{ - check(GetNetMode() == NM_Client); - ShotBuffered = false; - SetWeaponState(EWeaponState::Idle); -} - void AInstantWeapon::MulticastNotifyHit_Implementation(FInstantHitInfo HitInfo, bool bImpact) { // Make sure we're a client, and we're not the client that owns this gun (they will have already played the effect locally). + APawn* Pawn = Cast(GetOwner()); + if (GetNetMode() != NM_DedicatedServer && - (GetOwningCharacter() == nullptr || !GetOwningCharacter()->IsLocallyControlled())) + (Pawn == nullptr || !Pawn->IsLocallyControlled())) { SpawnFX(HitInfo, bImpact); } } + +void AInstantWeapon::SetIsActive(bool bNewActive) +{ + Super::SetIsActive(bNewActive); + + ConsumeBufferedShot(); +} diff --git a/Game/Source/GDKShooter/Private/Weapons/Projectile.cpp b/Game/Source/GDKShooter/Private/Weapons/Projectile.cpp index c8cd1ad6..998652d2 100644 --- a/Game/Source/GDKShooter/Private/Weapons/Projectile.cpp +++ b/Game/Source/GDKShooter/Private/Weapons/Projectile.cpp @@ -3,6 +3,8 @@ #include "Projectile.h" #include "Kismet/GameplayStatics.h" #include "Components/StaticMeshComponent.h" +#include "Engine/World.h" +#include "GameFramework/Pawn.h" #include "GDKLogging.h" #include "UnrealNetwork.h" @@ -46,14 +48,18 @@ void AProjectile::PostInitializeComponents() CollisionComp->MoveIgnoreActors.Add(Instigator); MovementComp->OnProjectileStop.AddDynamic(this, &AProjectile::OnStop); MovementComp->OnProjectileBounce.AddDynamic(this, &AProjectile::OnBounce); + CollisionComp->OnComponentBeginOverlap.AddDynamic(this, &AProjectile::BeginOverlap); BeginTime = UGameplayStatics::GetRealTimeSeconds(GetWorld()); } -void AProjectile::SetPlayer(AGDKCharacter* Character, AWeapon* Weapon) +void AProjectile::SetPlayer(AWeapon* Weapon) { + AActor* Character = Weapon->GetOwner(); CollisionComp->MoveIgnoreActors.Add(Character); - Character->IgnoreMe(this); - InstigatingController = Character->GetController(); + if (auto Pawn = Cast(Character)) + { + InstigatingController = Pawn->GetController(); + } InstigatingWeapon = Weapon; } @@ -80,7 +86,7 @@ void AProjectile::OnRep_MetaData() void AProjectile::Tick(float DeltaTime) { - if (GetNetMode() == NM_Client) + if (!HasAuthority()) { return; } @@ -95,7 +101,7 @@ void AProjectile::Tick(float DeltaTime) void AProjectile::OnStop(const FHitResult& ImpactResult) { - if (GetNetMode() == NM_Client) + if (!HasAuthority()) { return; } @@ -108,7 +114,7 @@ void AProjectile::OnStop(const FHitResult& ImpactResult) void AProjectile::OnBounce(const FHitResult& ImpactResult, const FVector& ImpactVelocity) { - if (GetNetMode() == NM_Client) + if (!HasAuthority()) { return; } @@ -120,6 +126,23 @@ void AProjectile::OnBounce(const FHitResult& ImpactResult, const FVector& Impact } } +void AProjectile::BeginOverlap(UPrimitiveComponent* OverlappedComponent, + AActor* OtherActor, + UPrimitiveComponent* OtherComp, + int32 OtherBodyIndex, + bool bFromSweep, + const FHitResult &SweepResult) +{ + if (APawn* OtherPawn = Cast(OtherActor)) + { + OverlapPawn(OtherPawn); + } +} + +void AProjectile::OverlapPawn_Implementation(APawn* OtherPawn) +{ + Explode(); +} void AProjectile::OnRep_Exploded() { @@ -133,7 +156,7 @@ void AProjectile::ExplosionVisuals_Implementation() void AProjectile::Explode() { - if (GetNetMode() == NM_Client) + if (!HasAuthority()) { return; } @@ -144,6 +167,6 @@ void AProjectile::Explode() SetLifeSpan(2.0f); if (ExplosionDamage > 0 && ExplosionRadius > 0) { - UGameplayStatics::ApplyRadialDamageWithFalloff(this, ExplosionDamage, ExplosionMinimumDamage, this->GetActorLocation(), ExplosionInnerRadius, ExplosionRadius, ExplosionFalloff, DamageTypeClass, TArray{this}, InstigatingWeapon, InstigatingController); + UGameplayStatics::ApplyRadialDamageWithFalloff(this, ExplosionDamage, ExplosionMinimumDamage, this->GetActorLocation(), ExplosionInnerRadius, ExplosionRadius, ExplosionFalloff, DamageTypeClass, TArray{this}, this, InstigatingController); } } diff --git a/Game/Source/GDKShooter/Private/Weapons/ProjectileWeapon.cpp b/Game/Source/GDKShooter/Private/Weapons/ProjectileWeapon.cpp index b72ba635..4ad2bee0 100644 --- a/Game/Source/GDKShooter/Private/Weapons/ProjectileWeapon.cpp +++ b/Game/Source/GDKShooter/Private/Weapons/ProjectileWeapon.cpp @@ -5,47 +5,55 @@ #include "Kismet/GameplayStatics.h" #include "Engine/World.h" #include "Weapons/Projectile.h" -#include "Characters/Core/GDKShooterCharacter.h" #include "GDKLogging.h" #include "Components/SkeletalMeshComponent.h" AProjectileWeapon::AProjectileWeapon() { ShotCooldown = 1; - LastShotTime = 0; } -void AProjectileWeapon::StartFire() +void AProjectileWeapon::DoFire_Implementation() { - check(GetNetMode() == NM_Client); + if (!GetShootingComponent()) + { + UE_LOG(LogGDK, Verbose, TEXT("Weapon %s does not have a shooting component"), *this->GetName()); + return; + } float Now = UGameplayStatics::GetRealTimeSeconds(GetWorld()); - if (GetWeaponState() == EWeaponState::Idle && Now > LastShotTime + ShotCooldown) - { - LastShotTime = Now; + NextShotTime = Now + ShotCooldown; - FVector Direction = GetOwningCharacter()->GetLineTraceDirection(); + FVector Direction = GetShootingComponent()->GetLineTraceDirection(); - FVector Barrel = Mesh->GetSocketLocation(BarrelSocket); + FVector Barrel = Mesh->GetSocketLocation(BarrelSocket); + + FInstantHitInfo HitInfo = DoLineTrace(); + if (HitInfo.bDidHit) + { + Direction = HitInfo.Location - Barrel; + Direction.Normalize(); + } + else + { + Direction = GetShootingComponent()->GetLineTraceDirection(); + Direction.Normalize(); + } + AnnounceShot(false); + OnShot(); + FireProjectile(Barrel, Direction); - FInstantHitInfo HitInfo; - if (DoLineTrace(HitInfo)) - { - Direction = HitInfo.Location - Barrel; - Direction.Normalize(); - } - else + if (!bAllowContinuousFire) + { + IsPrimaryUsing = false; + if (GetMovementComponent()) { - Direction = GetOwningCharacter()->GetLineTraceDirection(); - Direction.Normalize(); + GetMovementComponent()->SetIsBusy(false); } - - AnnounceShot(false); - OnShot(); - FireProjectile(Barrel, Direction); } } + bool AProjectileWeapon::FireProjectile_Validate(FVector Origin, FVector_NetQuantizeNormal Direction) { return true; @@ -58,8 +66,21 @@ void AProjectileWeapon::FireProjectile_Implementation(FVector Origin, FVector_Ne AProjectile* Projectile = Cast(UGameplayStatics::BeginDeferredActorSpawnFromClass(this, ProjectileClass, SpawnTransformMatrix)); if (Projectile) { - Projectile->SetPlayer(GetOwningCharacter(), this); + Projectile->SetPlayer(this); Projectile->MetaData = MetaData; UGameplayStatics::FinishSpawningActor(Projectile, SpawnTransformMatrix); } } + +void AProjectileWeapon::ConsumeBufferedShot() +{ + Super::ConsumeBufferedShot(); + if (!bAllowContinuousFire) + { + IsPrimaryUsing = false; + if (GetMovementComponent()) + { + GetMovementComponent()->SetIsBusy(false); + } + } +} diff --git a/Game/Source/GDKShooter/Private/Weapons/Weapon.cpp b/Game/Source/GDKShooter/Private/Weapons/Weapon.cpp index 2869615c..24a6a5ee 100644 --- a/Game/Source/GDKShooter/Private/Weapons/Weapon.cpp +++ b/Game/Source/GDKShooter/Private/Weapons/Weapon.cpp @@ -2,205 +2,207 @@ #include "Weapon.h" -#include "Characters/Core/GDKShooterCharacter.h" #include "Engine/World.h" #include "Components/SkeletalMeshComponent.h" #include "CollisionQueryParams.h" +#include "Kismet/GameplayStatics.h" #include "GDKLogging.h" #include "UnrealNetwork.h" AWeapon::AWeapon() - : CurrentState(EWeaponState::Idle) - , OwningCharacter(nullptr) { PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.TickGroup = TG_PrePhysics; - - bReplicates = true; - bReplicateMovement = true; - bDrawDebugLineTrace = false; - MaxRange = 50000.0f; + BufferShotThreshold = 0.2f; +} - LocationComponent = CreateDefaultSubobject(TEXT("RootComponent")); - SetRootComponent(LocationComponent); +void AWeapon::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); - Mesh = CreateDefaultSubobject(TEXT("WeaponMesh")); - Mesh->SetupAttachment(RootComponent); } -void AWeapon::BeginPlay() +FVector AWeapon::GetLineTraceDirection() { - Super::BeginPlay(); - - if (!bHasAttached) + if (!GetShootingComponent()) { - TryToAttach(); + return FVector::ZeroVector; } - OnMetaDataUpdated(); + return GetShootingComponent()->GetLineTraceDirection(); } -void AWeapon::Tick(float DeltaSeconds) +void AWeapon::AnnounceShot(bool bHit) { - Super::Tick(DeltaSeconds); - - if (!bHasAttached) + if (GetShootingComponent()) { - TryToAttach(); + GetShootingComponent()->FireShot(this, bHit); } } -void AWeapon::StopFire() {} - -class AGDKShooterCharacter* AWeapon::GetOwningCharacter() const +void AWeapon::DoFire_Implementation() { - return OwningCharacter; -} -void AWeapon::SetOwningCharacter(AGDKShooterCharacter* NewCharacter) -{ - OwningCharacter = NewCharacter; } -void AWeapon::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +void AWeapon::StartPrimaryUse_Implementation() { - Super::GetLifetimeReplicatedProps(OutLifetimeProps); + Super::StartPrimaryUse_Implementation(); - DOREPLIFETIME(AWeapon, OwningCharacter); - DOREPLIFETIME(AWeapon, MetaData); - DOREPLIFETIME(AWeapon, bIsActive); -} + bHasBufferedShot = true; + BufferedShotUntil = UGameplayStatics::GetRealTimeSeconds(GetWorld()) + BufferShotThreshold; -FVector AWeapon::GetLineTraceDirection() -{ - return GetOwningCharacter()->GetLineTraceDirection(); + if (GetMovementComponent()) + { + GetMovementComponent()->SetIsBusy(true); + } } -EWeaponState AWeapon::GetWeaponState() const +void AWeapon::StopPrimaryUse_Implementation() { - return CurrentState; -} + Super::StopPrimaryUse_Implementation(); -void AWeapon::SetWeaponState(EWeaponState NewState) -{ - CurrentState = NewState; + if (!HasBufferedShot()) + { + if (GetMovementComponent()) + { + GetMovementComponent()->SetIsBusy(false); + } + } } -void AWeapon::OnRep_MetaData() +void AWeapon::StartSecondaryUse_Implementation() { - if (GetNetMode() == NM_DedicatedServer) + Super::StartSecondaryUse_Implementation(); + + if (GetMovementComponent()) { - return; + GetMovementComponent()->SetAiming(true); + GetMovementComponent()->SetAimingRotationModifier(AimingRotationSpeed); } - - OnMetaDataUpdated(); } -void AWeapon::SetMetaData(FGDKMetaData NewMetaData) +void AWeapon::StopSecondaryUse_Implementation() { - if (HasAuthority()) + Super::StopSecondaryUse_Implementation(); + + if (GetMovementComponent()) { - MetaData = NewMetaData; + GetMovementComponent()->SetAiming(false); } - OnMetaDataUpdated(); } -void AWeapon::EnableShadows(bool bShadows) +void AWeapon::ForceCooldown(float Cooldown) { - Mesh->CastShadow = bShadows; -} + Super::ForceCooldown(Cooldown); + NextShotTime = FMath::Max(NextShotTime, UGameplayStatics::GetRealTimeSeconds(GetWorld()) + Cooldown); -void AWeapon::SetFirstPerson(bool bFirstPerson) -{ - this->bFirstPerson = bFirstPerson; } -bool AWeapon::IsFirstPerson() +bool AWeapon::ReadyToFire() { - return bFirstPerson; + float Now = UGameplayStatics::GetRealTimeSeconds(GetWorld()); + return Now > NextShotTime; } -void AWeapon::TryToAttach() +bool AWeapon::BufferedShotStillValid() { - if (GetRootComponent()->GetAttachParent() - && GetRootComponent()->GetAttachParent()->GetOwner()) - { - AGDKShooterCharacter* ShooterCharacter = Cast(GetRootComponent()->GetAttachParent()->GetOwner()); - if (ShooterCharacter) { - ShooterCharacter->AttachWeapon(this); - bHasAttached = true; - } - } - this->SetActorHiddenInGame(!bIsActive); + return UGameplayStatics::GetRealTimeSeconds(GetWorld()) < BufferedShotUntil; } -void AWeapon::OnRep_IsActive() +bool AWeapon::HasBufferedShot() { - StopFire(); - this->SetActorHiddenInGame(!bIsActive); + return bHasBufferedShot; } -void AWeapon::AddShotListener(FShotDelegate Listener) +void AWeapon::ConsumeBufferedShot() { - ShotCallback = Listener; + BufferedShotUntil = 0; + bHasBufferedShot = false; } -void AWeapon::RemoveShotListener() + +void AWeapon::Tick(float DeltaTime) { - ShotCallback.Unbind(); + Super::Tick(DeltaTime); + + if ((IsPrimaryUsing || HasBufferedShot()) && ReadyToFire()) + { + ConsumeBufferedShot(); + DoFire(); + + if (!IsPrimaryUsing) + { + if (GetMovementComponent()) + { + GetMovementComponent()->SetIsBusy(false); + } + } + } + + if (HasBufferedShot() && !BufferedShotStillValid()) + { + ConsumeBufferedShot(); + if (!IsPrimaryUsing) + { + if (GetMovementComponent()) + { + GetMovementComponent()->SetIsBusy(false); + } + } + } } -void AWeapon::AnnounceShot(bool bHit) +void AWeapon::SetIsActive(bool bNewIsActive) { - ShotCallback.ExecuteIfBound(bHit); + Super::SetIsActive(bNewIsActive); + + ConsumeBufferedShot(); } -FVector AWeapon::BulletSpawnPoint() +void AWeapon::RefreshComponentCache() { - return Mesh->GetSocketLocation(BarrelSocketName); + CachedOwner = GetOwner(); + if (CachedOwner == nullptr) + { + CachedMovementComponent = nullptr; + CachedShootingComponent = nullptr; + } + else + { + CachedMovementComponent = Cast(CachedOwner->GetComponentByClass(UGDKMovementComponent::StaticClass())); + CachedShootingComponent = Cast(CachedOwner->GetComponentByClass(UShootingComponent::StaticClass())); + } } -bool AWeapon::DoLineTrace(FInstantHitInfo& OutHitInfo) +UGDKMovementComponent* AWeapon::GetMovementComponent() { - AGDKShooterCharacter* Character = GetOwningCharacter(); - if (Character == nullptr) + if (GetOwner() != CachedOwner) { - UE_LOG(LogGDK, Verbose, TEXT("Weapon %s does not have an owning character"), *this->GetName()); - return false; + RefreshComponentCache(); } + return CachedMovementComponent; +} - FCollisionQueryParams TraceParams; - TraceParams.bTraceComplex = true; - TraceParams.bTraceAsyncScene = true; - TraceParams.bReturnPhysicalMaterial = false; - TraceParams.AddIgnoredActor(this); - TraceParams.AddIgnoredActor(Character); - - if (bDrawDebugLineTrace) +UShootingComponent* AWeapon::GetShootingComponent() +{ + if (GetOwner() != CachedOwner) { - TraceParams.TraceTag = kTraceTag; + RefreshComponentCache(); } + return CachedShootingComponent; +} - FHitResult HitResult(ForceInit); - FVector TraceStart = Character->GetLineTraceStart(); - FVector TraceEnd = TraceStart + GetLineTraceDirection() * MaxRange; - - bool bDidHit = GetWorld()->LineTraceSingleByChannel( - HitResult, - TraceStart, - TraceEnd, - TraceChannel, - TraceParams); - - if (!bDidHit) +FInstantHitInfo AWeapon::DoLineTrace() +{ + if (GetShootingComponent() == nullptr) { - OutHitInfo.Location = TraceEnd; - return false; - } + UE_LOG(LogGDK, Error, TEXT("%s requires a UShootingComponent on its Owner"), *this->GetName()); - OutHitInfo.Location = HitResult.ImpactPoint; - OutHitInfo.HitActor = HitResult.GetActor(); + FInstantHitInfo HitInfo; + return HitInfo; + } - return true; + return GetShootingComponent()->DoLineTrace(GetLineTraceDirection(), this); } diff --git a/Game/Source/GDKShooter/Public/Characters/Components/EquippedComponent.h b/Game/Source/GDKShooter/Public/Characters/Components/EquippedComponent.h new file mode 100644 index 00000000..c0f88b0b --- /dev/null +++ b/Game/Source/GDKShooter/Public/Characters/Components/EquippedComponent.h @@ -0,0 +1,133 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "MetaDataComponent.h" +#include "EquippedComponent.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FHoldableUpdated, AHoldable*, NewHoldable); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FBusyUpdated, bool, bIsBusy); + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API UEquippedComponent : public UActorComponent +{ + GENERATED_BODY() + +// Unreal Logic +public: + UEquippedComponent(); + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + +protected: + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + +// Starter Templates +public: + UFUNCTION(BlueprintCallable) + virtual void SpawnStarterTemplates(FGDKMetaData NewMetaData); + +protected: + + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly) + int32 HoldableCapacity = 4; + + // Holdables available to the player on spawn + UPROPERTY(EditDefaultsOnly, Category = "Holdables") + TArray> StarterTemplates; + +// Held Items +public: + UFUNCTION(BlueprintPure) + AHoldable* CurrentlyHeldItem() const; + + UFUNCTION(Server, Reliable, WithValidation) + void ServerRequestEquip(int32 Index); + + UFUNCTION(BlueprintCallable) + void QuickToggle(); + + UFUNCTION(BlueprintCallable) + void ToggleMode(); + + UFUNCTION(BlueprintCallable) + void ScrollUp(); + + UFUNCTION(BlueprintCallable) + void ScrollDown(); + + bool HasHoldableAtIndex(int32 Index); + + UPROPERTY(BlueprintAssignable) + FHoldableUpdated HoldableUpdated; + + UFUNCTION(BlueprintCallable) + bool Grant(AHoldable* NewHoldable); + +protected: + UFUNCTION() + void OnRep_HeldUpdate(); + + UFUNCTION() + virtual void LocallyActivate(AHoldable* Holdable); + + UPROPERTY() + AHoldable* LocallyActiveHoldable; + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, ReplicatedUsing = OnRep_HeldUpdate) + int CurrentHeldIndex; + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, ReplicatedUsing = OnRep_HeldUpdate) + TArray HeldItems; + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Replicated) + bool bHeldItemsInitialised; + + UFUNCTION() + bool HasAnyEmptySlots(); + + UFUNCTION() + int GetNextAvailableSlot(); + + UFUNCTION() + bool AlreadyHas(AHoldable* NewHoldable); + +// Using Logic +public: + UFUNCTION() + void SetIsSprinting(bool bNewSprinting) { bIsSprinting = bNewSprinting; } + + UFUNCTION() + void BlockUsing(bool bBlock); + + UFUNCTION() + void StartPrimaryUse(); + UFUNCTION() + void StopPrimaryUse(); + UFUNCTION() + void StartSecondaryUse(); + UFUNCTION() + void StopSecondaryUse(); + + UFUNCTION() + void ForceCooldown(float Cooldown); + + UPROPERTY(EditDefaultsOnly) + float SprintRecoveryTime = 0.2f; + +protected: + + // Is using holdables blocked by e.g. Menus being open + bool bBlockUsing; + // Are we currently busy using an item, used to e.g. block sprinting + bool bIsBusy; + // Do we think we are sprinting + // Should we apply the sprinting cooldown when going to use a holdable + bool bIsSprinting; + + int32 LastCachedIndex = -1; + int32 CurrentCachedIndex = -1; + +}; diff --git a/Game/Source/GDKShooter/Public/Characters/Components/FirstPersonTraceProvider.h b/Game/Source/GDKShooter/Public/Characters/Components/FirstPersonTraceProvider.h new file mode 100644 index 00000000..b17651c0 --- /dev/null +++ b/Game/Source/GDKShooter/Public/Characters/Components/FirstPersonTraceProvider.h @@ -0,0 +1,35 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "Weapons/ITraceProvider.h" +#include "Camera/CameraComponent.h" + +#include "FirstPersonTraceProvider.generated.h" + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API UFirstPersonTraceProvider : public UActorComponent, public ITraceProvider +{ + GENERATED_BODY() + +public: + UFirstPersonTraceProvider(); + + virtual void BeginPlay(); + + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Trace Provider") + FVector GetLineTraceStart() const; + virtual FVector GetLineTraceStart_Implementation() const; + + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Trace Provider") + FVector GetLineTraceDirection() const; + virtual FVector GetLineTraceDirection_Implementation() const; + +protected: + + UPROPERTY() + UCameraComponent* FirstPersonCamera; + +}; diff --git a/Game/Source/GDKShooter/Public/Components/GDKMovementComponent.h b/Game/Source/GDKShooter/Public/Characters/Components/GDKMovementComponent.h similarity index 73% rename from Game/Source/GDKShooter/Public/Components/GDKMovementComponent.h rename to Game/Source/GDKShooter/Public/Characters/Components/GDKMovementComponent.h index 326238df..bdb9e256 100644 --- a/Game/Source/GDKShooter/Public/Components/GDKMovementComponent.h +++ b/Game/Source/GDKShooter/Public/Characters/Components/GDKMovementComponent.h @@ -6,6 +6,9 @@ #include "GameFramework/CharacterMovementComponent.h" #include "GDKMovementComponent.generated.h" +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAimingUpdated, bool, bIsAiming); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSprintingUpdated, bool, bIsSprinting); + UCLASS() class GDKSHOOTER_API UGDKMovementComponent : public UCharacterMovementComponent { @@ -15,6 +18,8 @@ class GDKSHOOTER_API UGDKMovementComponent : public UCharacterMovementComponent // Called every frame virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + friend class FSavedMove_GDKMovement; UGDKMovementComponent(const FObjectInitializer& ObjectInitializer); @@ -30,22 +35,31 @@ class GDKSHOOTER_API UGDKMovementComponent : public UCharacterMovementComponent UFUNCTION(BlueprintPure, Category = "Sprint") bool IsSprinting() const; - // Returns true if the character is actually sprinting. + // Set whether the character is allowed to sprint UFUNCTION(BlueprintCallable, Category = "Sprint") void SetSprintEnabled(bool bSprintEnabled); - - // Returns true if the character is actually sprinting. - UFUNCTION(BlueprintPure, Category = "Sprint") - bool HasSprintedRecently() const; // Set if the character should be aiming - UFUNCTION(BlueprintCallable, Category = "Aiming") - void SetAiming(bool bAiming); + UFUNCTION(BlueprintCallable, Server, Reliable, WithValidation) + void ServerSetAiming(bool NewValue); + UFUNCTION(BlueprintCallable) + void SetAiming(bool NewValue); // Returns true if the character is aiming. UFUNCTION(BlueprintPure, Category = "Aiming") bool IsAiming() const; + UPROPERTY(BlueprintAssignable, Category = "Aiming") + FAimingUpdated OnAimingUpdated; + + UPROPERTY(BlueprintAssignable, Category = "Sprint") + FSprintingUpdated SprintingUpdated; + + UFUNCTION(BlueprintCallable, Category = "Aiming") + void SetAimingRotationModifier(float NewAimingRotationModifier) { AimingRotationModifier = NewAimingRotationModifier; } + UFUNCTION(BlueprintPure, Category = "Aiming") + float GetAimingRotationModifier() { return AimingRotationModifier; } + // Returns the max speed of the character, modified if sprinting. virtual float GetMaxSpeed() const override; @@ -53,11 +67,9 @@ class GDKSHOOTER_API UGDKMovementComponent : public UCharacterMovementComponent virtual float GetMaxAcceleration() const override; // True if movement direction is within SprintDirectionTolerance of the look direction. - bool IsMovingForward() const; - - // Time in miliseconds since IsSprinting was last true - double TimeSinceLastSprint() const; - + UFUNCTION(BlueprintPure) + bool IsMovingForward() const; + // Multiply max speed by this factor when sprinting. UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Character Movement: Walking") float MaxJogSpeed; @@ -65,6 +77,17 @@ class GDKSHOOTER_API UGDKMovementComponent : public UCharacterMovementComponent /** Overriding this method to stop crouching when falling */ virtual bool CanCrouchInCurrentState() const override; + // Set if the character is busy + UFUNCTION(BlueprintCallable) + void SetIsBusy(bool bBusy) { bIsBusy = bBusy; } + + // Set if the character is busy + UFUNCTION(BlueprintPure) + bool IsBusy() const { return bIsBusy; } + + UFUNCTION(BlueprintCallable) + void SetGravityScale(float NewScale) { GravityScale = NewScale; } + private: // Override whether sprint is allowed. @@ -77,11 +100,17 @@ class GDKSHOOTER_API UGDKMovementComponent : public UCharacterMovementComponent // Set to true on each frame that IsSprinting is true, for detecting when sprinting stops uint8 bWasSprintingLastFrame : 1; - // Last time, in miliseconds, when sprinting was true - FDateTime lastTimeSprinting; - // If true, the player is aiming, should therefore move slower, and should not be allowed to sprint. - uint8 bIsAiming : 1; + UPROPERTY(VisibleAnywhere, ReplicatedUsing = OnRep_IsAiming) + bool bIsAiming; + + UFUNCTION() + void OnRep_IsAiming(); + + float AimingRotationModifier = 1; + + // If true, the player is using something, e.g. Shooting, so should not sprint + uint8 bIsBusy : 1; // If true, the player will attempt to rotate all the way to the control rotation. Used to correct for // over-rotation while standing still (e.g. trying have an aim offset of > 90 degrees). @@ -100,11 +129,6 @@ class GDKSHOOTER_API UGDKMovementComponent : public UCharacterMovementComponent UPROPERTY(EditAnywhere, Category = "Character Movement (General Settings)") float SprintDirectionTolerance; - // Time in miliseconds after sprinting until character has recovered from the sprint. - // For example to return to a non-sprinting animation before shooting. - UPROPERTY(EditAnywhere, Category = "Character Movement (General Settings)") - float SprintCooldown; - // Multiply acceleration by this factor when aiming. UPROPERTY(EditAnywhere, Category = "Character Movement (General Settings)") float JogAcceleration; diff --git a/Game/Source/GDKShooter/Public/Characters/Components/HealthComponent.h b/Game/Source/GDKShooter/Public/Characters/Components/HealthComponent.h new file mode 100644 index 00000000..43e8b7df --- /dev/null +++ b/Game/Source/GDKShooter/Public/Characters/Components/HealthComponent.h @@ -0,0 +1,136 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "GameFramework/Controller.h" +#include "GameFramework/Actor.h" +#include "Runtime/AIModule/Classes/GenericTeamAgentInterface.h" +#include "TimerManager.h" +#include "HealthComponent.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FFloatValue, float, Current, float, Max); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FDamageTakenEvent, float, Value, FVector, Source, FVector, Impact, int32, PlayerId, FGenericTeamId, TeamId); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDeathCauserEvent, const AController*, Instigator); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDeathEvent); + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API UHealthComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UHealthComponent(); + + virtual void BeginPlay() override; + + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + UFUNCTION(BlueprintCallable) + virtual void TakeDamage(float Damage, const FDamageEvent& DamageEvent, AController* EventInstigator, AActor* DamageCauser); + + UFUNCTION(BlueprintCallable) + bool GrantShield(float Value); + + UFUNCTION(BlueprintCallable) + bool GrantHealth(float Value); + + UFUNCTION(BlueprintPure) + FORCEINLINE float GetCurrentHealth() const + { + return CurrentHealth; + } + + UFUNCTION(BlueprintPure) + FORCEINLINE float GetMaxHealth() const + { + return MaxHealth; + } + + UFUNCTION(BlueprintPure) + FORCEINLINE float GetCurrentArmour() const + { + return CurrentArmour; + } + + UFUNCTION(BlueprintPure) + FORCEINLINE float GetMaxArmour() const + { + return MaxArmour; + } + + UPROPERTY(BlueprintAssignable) + FFloatValue HealthUpdated; + UPROPERTY(BlueprintAssignable) + FFloatValue ArmourUpdated; + UPROPERTY(BlueprintAssignable) + FDamageTakenEvent DamageTaken; + UPROPERTY(BlueprintAssignable) + FDeathCauserEvent AuthoritativeDeath; + UPROPERTY(BlueprintAssignable) + FDeathEvent Death; + +protected: + + // Notifies all clients that a the character has been hit and from what direction. + UFUNCTION(NetMulticast, Unreliable) + void MulticastDamageTaken(float Value, FVector Source, FVector Impact, int32 PlayerId, FGenericTeamId TeamId); + + UFUNCTION() + void OnRep_CurrentHealth(); + + UFUNCTION() + void OnRep_CurrentArmour(); + + // Max health this character can have. + UPROPERTY(EditDefaultsOnly, Category = "Health", meta = (ClampMin = "1")) + float MaxHealth; + + // Current health of the character, can be at most MaxHealth. + UPROPERTY(VisibleAnywhere, ReplicatedUsing = OnRep_CurrentHealth, Category = "Health") + float CurrentHealth; + + // Max armour this character can have. + UPROPERTY(EditDefaultsOnly, Category = "Health", meta = (ClampMin = "1")) + float MaxArmour; + + // Current armour of the character, can be at most MaxArmour. + UPROPERTY(VisibleAnywhere, ReplicatedUsing = OnRep_CurrentArmour, Category = "Health") + float CurrentArmour; + + FTimerHandle HealthRegenerationHandle; + + UFUNCTION() + void RegenerateHealth(); + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + float HealthRegenValue; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + float HealthRegenCooldown; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + float HealthRegenInterval; + + FTimerHandle ArmourRegenerationHandle; + + UFUNCTION() + void RegenerateArmour(); + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + float ArmourRegenValue; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + float ArmourRegenCooldown; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + float ArmourRegenInterval; + + // When hit by radial damage, we assume the impact point is the owner's actor location plus this value + UPROPERTY(EditAnywhere, BlueprintReadOnly) + FVector RadialDamageImpactOffset; + +}; diff --git a/Game/Source/GDKShooter/Public/Characters/Components/MetaDataComponent.h b/Game/Source/GDKShooter/Public/Characters/Components/MetaDataComponent.h new file mode 100644 index 00000000..27c39d78 --- /dev/null +++ b/Game/Source/GDKShooter/Public/Characters/Components/MetaDataComponent.h @@ -0,0 +1,57 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "MetaDataComponent.generated.h" + + +// Meta Data sctruct holding information about a player, also passed to a player's inventory items +USTRUCT(BlueprintType) +struct FGDKMetaData { + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite) + int32 Customization; +}; + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMetaDataUpdated, FGDKMetaData, MetaData); + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API UMetaDataComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UMetaDataComponent(); + + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + FORCEINLINE FGDKMetaData GetMetaData() const + { + return MetaData; + } + + FORCEINLINE bool IsMetaDataAvailable() const + { + return bMetaDataAvailable; + } + + UFUNCTION(BlueprintCallable) + void SetMetaData(FGDKMetaData NewMetaData); + + UPROPERTY(BlueprintAssignable) + FMetaDataUpdated MetaDataUpdated; + +protected: + UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_MetaData) + FGDKMetaData MetaData; + + UPROPERTY(BlueprintReadOnly, BlueprintReadOnly, Replicated) + bool bMetaDataAvailable = false; + + UFUNCTION() + void OnRep_MetaData(); +}; diff --git a/Game/Source/GDKShooter/Public/Characters/Components/ShootingComponent.h b/Game/Source/GDKShooter/Public/Characters/Components/ShootingComponent.h new file mode 100644 index 00000000..f9b0d600 --- /dev/null +++ b/Game/Source/GDKShooter/Public/Characters/Components/ShootingComponent.h @@ -0,0 +1,81 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "Weapons/ITraceProvider.h" +#include "ShootingComponent.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FShotEvent, AWeapon*, Weapon, bool, Hit); + +USTRUCT(BlueprintType) +struct FInstantHitInfo +{ + GENERATED_USTRUCT_BODY() + + // Location of the hit in world space. + UPROPERTY(BlueprintReadOnly) + FVector Location; + + // Actor that was hit, or nullptr if nothing was hit. + UPROPERTY(BlueprintReadOnly) + AActor* HitActor; + + UPROPERTY(BlueprintReadOnly) + bool bDidHit; + + FInstantHitInfo() : + Location(FVector{ 0,0,0 }), + HitActor(nullptr), + bDidHit(false) + {} +}; + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API UShootingComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + // Sets default values for this component's properties + UShootingComponent(); + + void BeginPlay(); + + UPROPERTY(BlueprintAssignable) + FShotEvent ShotEvent; + + void FireShot(AWeapon* Weapon, bool bHit) { ShotEvent.Broadcast(Weapon, bHit); } + + UFUNCTION(BlueprintPure) + FVector GetLineTraceStart() + { + UObject* TraceProviderObject = TraceProvider.GetObject(); + return ITraceProvider::Execute_GetLineTraceStart(TraceProvider.GetObject()); + } + + UFUNCTION(BlueprintPure) + FVector GetLineTraceDirection() + { + UObject* TraceProviderObject = TraceProvider.GetObject(); + return ITraceProvider::Execute_GetLineTraceDirection(TraceProvider.GetObject()); + } + + UFUNCTION(BlueprintPure) + FInstantHitInfo DoLineTrace(FVector Direction, AActor* ActorToIgnore = nullptr); + +protected: + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Shooting") + TScriptInterface TraceProvider; + + // Maximum range of the weapon's hitscan. + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Shooting") + float MaxRange; + + // Channel to use for raytrace on shot + UPROPERTY(EditAnywhere, Category = "Shooting") + TEnumAsByte TraceChannel = ECC_WorldStatic; + +}; diff --git a/Game/Source/GDKShooter/Public/Characters/Components/TeamComponent.h b/Game/Source/GDKShooter/Public/Characters/Components/TeamComponent.h new file mode 100644 index 00000000..7f84630e --- /dev/null +++ b/Game/Source/GDKShooter/Public/Characters/Components/TeamComponent.h @@ -0,0 +1,49 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "Runtime/AIModule/Classes/GenericTeamAgentInterface.h" +#include "TeamComponent.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTeamChangedEvent, FGenericTeamId, Team); + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API UTeamComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UTeamComponent(); + + UPROPERTY(BlueprintAssignable) + FTeamChangedEvent TeamChanged; + + UFUNCTION(BlueprintPure) + virtual bool CanDamageActor(AActor* OtherActor); + + UFUNCTION(BlueprintCallable) + void SetTeam(FGenericTeamId NewTeamId) { TeamId = NewTeamId; } + + UFUNCTION(BlueprintPure) + FGenericTeamId GetTeam() { return TeamId; } + + //Negative or Zero as a Team Id is not considered a valid team + UFUNCTION(BlueprintPure) + bool HasTeam() { return TeamId != FGenericTeamId::NoTeam; } + + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly) + bool bAllowFriendlyFire; + +protected: + + UPROPERTY(ReplicatedUsing = OnRep_TeamId, EditDefaultsOnly) + FGenericTeamId TeamId = FGenericTeamId::NoTeam; + + UFUNCTION() + void OnRep_TeamId() { TeamChanged.Broadcast(TeamId); }; + + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + +}; diff --git a/Game/Source/GDKShooter/Public/Characters/Core/GDKCharacter.h b/Game/Source/GDKShooter/Public/Characters/Core/GDKCharacter.h deleted file mode 100644 index 70668cea..00000000 --- a/Game/Source/GDKShooter/Public/Characters/Core/GDKCharacter.h +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "CoreMinimal.h" -#include "Game/GDKMetaData.h" -#include "Materials/MaterialInstance.h" -#include "GameFramework/Character.h" -#include "GDKCharacter.generated.h" - -USTRUCT(BlueprintType) -struct FGDKCharacterMaterials -{ - GENERATED_BODY() -public: - UPROPERTY(BlueprintReadWrite, EditAnywhere) - UMaterialInstance* CharacterMaterial; -}; - -UCLASS() -class GDKSHOOTER_API AGDKCharacter : public ACharacter -{ - GENERATED_BODY() - -public: - // Sets default values for this character's properties - AGDKCharacter(const FObjectInitializer& ObjectInitializer); - - virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; - - virtual void SetMetaData(FGDKMetaData MetaData); - - UFUNCTION(BlueprintCallable) - void IgnoreMe(AActor* ToIgnore); - -protected: - // Called when the game starts or when spawned - virtual void BeginPlay() override; - - virtual void Destroyed() override; - -protected: - - // Max health this character can have. - UPROPERTY(EditDefaultsOnly, Category = "Health", meta = (ClampMin = "1")) - int32 MaxHealth; - - // Current health of the character, can be at most MaxHealth. - UPROPERTY(VisibleAnywhere, ReplicatedUsing = OnRep_CurrentHealth, Category = "Health") - int32 CurrentHealth; - - // Max armour this character can have. - UPROPERTY(EditDefaultsOnly, Category = "Health", meta = (ClampMin = "1")) - int32 MaxArmour; - - // Current armour of the character, can be at most MaxArmour. - UPROPERTY(VisibleAnywhere, ReplicatedUsing = OnRep_CurrentArmour, Category = "Health") - int32 CurrentArmour; - - // If true, the character is currently ragdoll-ing. - UPROPERTY(ReplicatedUsing = OnRep_IsRagdoll) - bool bIsRagdoll; - - // If true, the character is currently ragdoll-ing. - UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_MetaData) - FGDKMetaData MetaData; - - // [server] Tells this player that it's time to die. - // @param Killer The player who killed me. Can be null if it wasn't a player who dealt the damage that killed me. - virtual void Die(const class AGDKCharacter* Killer); - - UFUNCTION() - virtual void OnRep_MetaData(); - - UFUNCTION(BlueprintImplementableEvent) - void OnMetaDataUpdated(); - - FTimerHandle RegenerationHandle; - - UFUNCTION() - void RegenerateHealth(); - - UPROPERTY(EditAnywhere, BlueprintReadWrite) - float HealthRegenValue; - - UPROPERTY(EditAnywhere, BlueprintReadWrite) - float HealthRegenCooldown; - - UPROPERTY(EditAnywhere, BlueprintReadWrite) - float HealthRegenInterval; - - -private: - - // [client + server] Puts the player in ragdoll mode. - void StartRagdoll(); - - // [owning client + server] Updates the aim variables to be sync-ed out to clients, or updates the values locally - // if we're executing on the owning client. - // Will only update the angles if they differ from the current stored value by more than AngleUpdateThreshold. - void UpdateAimRotation(float AngleUpdateThreshold); - - // Notifies all clients that a the character has been hit and from what direction. - UFUNCTION(NetMulticast, Unreliable) - void MulticastDamageTaken(FVector DamageSource); - - UFUNCTION() - void OnRep_CurrentHealth(); - - UFUNCTION() - void OnRep_CurrentArmour(); - - UFUNCTION() - void OnRep_IsRagdoll(); - - UFUNCTION() - void DeleteSelf(); - - FTimerHandle DeletionTimer; - FTimerDelegate DeletionDelegate; - -public: - - UFUNCTION(BlueprintCallable) - bool GrantShield(float Value); - - float TakeDamage(float Damage, const struct FDamageEvent& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override; - - UFUNCTION(CrossServer, Reliable) - void TakeDamageCrossServer(float Damage, const struct FDamageEvent& DamageEvent, AController* EventInstigator, AActor* DamageCauser); - - UPROPERTY(Replicated) - int32 PlayerId; - - FORCEINLINE float GetCurrentHealth() const - { - return CurrentHealth; - } - - FORCEINLINE float GetMaxHealth() const - { - return MaxHealth; - } - - FORCEINLINE float GetCurrentArmour() const - { - return CurrentArmour; - } - - FORCEINLINE float GetMaxArmour() const - { - return MaxArmour; - } - - // Returns the player's name, as specified on login. - FString GetPlayerName() const; - - FORCEINLINE FGDKMetaData GetMetaData() const - { - return MetaData; - } - - // Called on clients when the player health changes - UFUNCTION(BlueprintImplementableEvent, Category = "Health") - void OnHealthUpdated(float NewHealth, float MaximumHealth); - - // Called on clients when the player armour changes - UFUNCTION(BlueprintImplementableEvent, Category = "Health") - void OnArmourUpdated(float NewArmour, float MaximumHealth); - - // Called on clients when the player health changes - UFUNCTION(BlueprintImplementableEvent, Category = "Health") - void OnDamageTaken(FVector DamageSource); - - UFUNCTION(BlueprintImplementableEvent, Category = "Health") - void ShowRespawnScreen(); - -}; diff --git a/Game/Source/GDKShooter/Public/Characters/Core/GDKMobileCharacter.h b/Game/Source/GDKShooter/Public/Characters/Core/GDKMobileCharacter.h deleted file mode 100644 index 40753e6c..00000000 --- a/Game/Source/GDKShooter/Public/Characters/Core/GDKMobileCharacter.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "CoreMinimal.h" -#include "GDKCharacter.h" -#include "GDKMobileCharacter.generated.h" - -/** - * - */ -UCLASS() -class GDKSHOOTER_API AGDKMobileCharacter : public AGDKCharacter -{ - GENERATED_BODY() - -public: - AGDKMobileCharacter(const FObjectInitializer& ObjectInitializer); - - virtual void Tick(float DeltaTime) override; - - // Called to bind functionality to input - virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; - - virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; -public: - - /** Base turn rate, in deg/sec. Other scaling may affect final turn rate. */ - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = Camera) - float BaseTurnRate; - - /** Base look up/down rate, in deg/sec. Other scaling may affect final rate. */ - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = Camera) - float BaseLookUpRate; - - // [server + client] Returns true if the character is currently sprinting. - UFUNCTION(BlueprintPure, Category = "Movement") - bool IsSprinting(); - - // [server + client] Returns true if the character is currently sprinting. - UFUNCTION(BlueprintPure, Category = "Movement") - bool JumpedThisFrame(); - - // [server + client] Returns true if the character is currently sprinting. - UFUNCTION(BlueprintPure, Category = "Movement") - bool HasSprintedRecently(); - - virtual void OnJumped_Implementation() override; - - UPROPERTY(Replicated, VisibleAnywhere, BlueprintReadOnly, Category = Camera) - float Pitch; - -protected: - - /** Handles moving forward/backward */ - virtual void MoveForward(float Val); - - /** Handles stafing movement, left and right */ - virtual void MoveRight(float Val); - - /** - * Called via input to turn at a given rate. - * @param Rate This is a normalized rate, i.e. 1.0 means 100% of desired turn rate - */ - virtual void TurnAtRate(float Rate); - - /** - * Called via input to turn look up/down at a given rate. - * @param Rate This is a normalized rate, i.e. 1.0 means 100% of desired turn rate - */ - virtual void LookUpAtRate(float Rate); - - void StartSprinting(); - - void StopSprinting(); - - void StartCrouching(); - - void StopCrouching(); - -private: - bool bJumpedThisFrame; -}; diff --git a/Game/Source/GDKShooter/Public/Characters/Core/GDKShooterCharacter.h b/Game/Source/GDKShooter/Public/Characters/Core/GDKShooterCharacter.h deleted file mode 100644 index 826fa237..00000000 --- a/Game/Source/GDKShooter/Public/Characters/Core/GDKShooterCharacter.h +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "CoreMinimal.h" -#include "GDKMobileCharacter.h" -#include "Weapons/Weapon.h" -#include "GDKShooterCharacter.generated.h" - - -DECLARE_DELEGATE_TwoParams(FAimingStateChanged, bool, float); -DECLARE_DELEGATE_OneParam(FWeaponSelection, int32); -DECLARE_DELEGATE_OneParam(FWeaponChanged, AWeapon*); -DECLARE_DELEGATE_TwoParams(FWeaponShotDelegate, AWeapon*, bool); -UCLASS() -class GDKSHOOTER_API AGDKShooterCharacter : public AGDKMobileCharacter -{ - GENERATED_BODY() - -public: - AGDKShooterCharacter(const FObjectInitializer& ObjectInitializer); - - virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; - - // Called to bind functionality to input - virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; - - // [server + client] Returns true if the character is able to shoot at the given moment. - bool CanFire(); - - // Returns a position from which to start a line trace. - // Use this so your line trace doesn't collide with the player character. - virtual FVector GetLineTraceStart() const PURE_VIRTUAL(AGDKShooterCharacter::GetLineTraceStart, return FVector::ZeroVector;); - - // Returns the direction in which to perform a line trace so it lines up with the center of the crosshair. - virtual FVector GetLineTraceDirection() const PURE_VIRTUAL(AGDKShooterCharacter::GetLineTraceDirection, return FVector::ZeroVector;); - - virtual void AttachWeapon(AWeapon* weapon) const; - - // [server + client] Returns true if the character is currently sprinting. - UFUNCTION(BlueprintPure, Category = "Aiming") - bool IsAiming(); - - // Called on clients when the player health changes - UFUNCTION(BlueprintImplementableEvent, Category = "Aiming") - void OnStartedAiming(); - - // Called on clients when the player health changes - UFUNCTION(BlueprintImplementableEvent, Category = "Aiming") - void OnStoppedAiming(); - - UFUNCTION(Server, Reliable, WithValidation) - void EquipWeapon(int32 weapon); - UFUNCTION(BlueprintCallable) - void EquipWeaponLocally(int32 weapon); - - void StartFire(); - - void StopFire(); - - void AddAimingListener(FAimingStateChanged Listener); - void AddWeaponListener(FWeaponChanged Listener); - void AddShotListener(FWeaponShotDelegate Listener); - - virtual void SetMetaData(FGDKMetaData MetaData) override; - - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicatedusing=OnRep_EquippedWeapon) - class AWeapon* EquippedWeapon; - -protected: - - UPROPERTY(EditDefaultsOnly) - FName kRightGunSocketName = FName(TEXT("Gun_Transform")); - - UPROPERTY(Transient) - class AWeapon* CachedEquippedWeapon; - - // Called when the game starts or when spawned - virtual void BeginPlay() override; - virtual void Destroyed() override; - - // [client] If true, the character should ignore all action inputs. - bool IgnoreActionInput() const; - - void StartAim(); - - void StopAim(); - - // Debug RPC to tell the server to aim or not - UFUNCTION(Server, Reliable, WithValidation) - void SetAiming(bool NewValue); - - virtual void Die(const class AGDKCharacter* Killer) override; - - UFUNCTION() - virtual void OnRep_EquippedWeapon(); - - UFUNCTION(BlueprintImplementableEvent) - void OnEquippedWeaponChanged(); - - virtual void OnShot(bool Hit); - -private: - - UPROPERTY(VisibleAnywhere, Replicated) - TArray AvailableWeapons; - - UPROPERTY(VisibleAnywhere, Replicated) - bool bIsAiming; - - bool bFireHeld; - - FAimingStateChanged AimingCallback; - FWeaponChanged WeaponCallback; - FWeaponShotDelegate ShotCallback; - - // Weapons available to the player, and whether they are available on spawn - UPROPERTY(EditDefaultsOnly, Category = "Weapons") - TArray> StarterWeaponTemplates; - - // [server] Spawns the starter weapons and attaches them to the character. - void SpawnStarterWeapons(); - - // [server] Spawns a starter weapon and attaches it to the character. - void SpawnStarterWeapon(TSubclassOf Weapon, bool bIsActive); -}; diff --git a/Game/Source/GDKShooter/Public/Characters/GDKCharacter.h b/Game/Source/GDKShooter/Public/Characters/GDKCharacter.h new file mode 100644 index 00000000..d5896057 --- /dev/null +++ b/Game/Source/GDKShooter/Public/Characters/GDKCharacter.h @@ -0,0 +1,95 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Materials/MaterialInstance.h" +#include "GameFramework/Character.h" +#include "Components/HealthComponent.h" +#include "Components/EquippedComponent.h" +#include "Components/MetaDataComponent.h" +#include "Components/GDKMovementComponent.h" +#include "Components/TeamComponent.h" +#include "Weapons/Holdable.h" +#include "TimerManager.h" +#include "Runtime/AIModule/Classes/GenericTeamAgentInterface.h" +#include "Runtime/AIModule/Classes/Perception/AISightTargetInterface.h" +#include "GDKCharacter.generated.h" + +DECLARE_DELEGATE_OneParam(FBoolean, bool); +DECLARE_DELEGATE_OneParam(FHoldableSelection, int32); + +UCLASS() +class GDKSHOOTER_API AGDKCharacter : public ACharacter, public IGenericTeamAgentInterface, public IAISightTargetInterface +{ + GENERATED_BODY() + +public: + AGDKCharacter(const FObjectInitializer& ObjectInitializer); + + virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; + +protected: + virtual void BeginPlay() override; + + UPROPERTY(Category = Character, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + UHealthComponent* HealthComponent; + + UPROPERTY(Category = Character, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + UGDKMovementComponent* GDKMovementComponent; + + UPROPERTY(Category = Character, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + UEquippedComponent* EquippedComponent; + + UPROPERTY(Category = Character, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + UMetaDataComponent* MetaDataComponent; + + UPROPERTY(Category = Character, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + UTeamComponent* TeamComponent; + + UFUNCTION(BlueprintPure) + float GetRemotePitch() { + return RemoteViewPitch; + } + + /** Handles moving forward/backward */ + virtual void MoveForward(float Val); + + /** Handles stafing movement, left and right */ + virtual void MoveRight(float Val); + + UFUNCTION(BlueprintNativeEvent) + void OnEquippedUpdated(AHoldable* NewHoldable); + + virtual FGenericTeamId GetGenericTeamId() const override; + + virtual bool CanBeSeenFrom(const FVector& ObserverLocation, FVector& OutSeenLocation, int32& NumberOfLoSChecksPerformed, float& OutSightStrength, const AActor* IgnoreActor = NULL) const override; + + UPROPERTY(EditDefaultsOnly) + TArray LineOfSightSockets; + + UPROPERTY(EditDefaultsOnly) + TEnumAsByte LineOfSightCollisionChannel; + + UPROPERTY(EditAnywhere) + float RagdollLifetime = 5.0f; + + // [client + server] Puts the player in ragdoll mode. + UFUNCTION(BlueprintNativeEvent, BlueprintCallable) + void StartRagdoll(); + +private: + + UFUNCTION() + void DeleteSelf(); + + FTimerHandle DeletionTimer; + FTimerDelegate DeletionDelegate; + +public: + + float TakeDamage(float Damage, const struct FDamageEvent& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override; + + UFUNCTION(CrossServer, Reliable) + void TakeDamageCrossServer(float Damage, const struct FDamageEvent& DamageEvent, AController* EventInstigator, AActor* DamageCauser); +}; diff --git a/Game/Source/GDKShooter/Public/Characters/GDKFPShooterCharacter.h b/Game/Source/GDKShooter/Public/Characters/GDKFPShooterCharacter.h deleted file mode 100644 index e193f3cf..00000000 --- a/Game/Source/GDKShooter/Public/Characters/GDKFPShooterCharacter.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "CoreMinimal.h" -#include "Core/GDKShooterCharacter.h" -#include "GDKFPShooterCharacter.generated.h" - -/** - * - */ -UCLASS() -class GDKSHOOTER_API AGDKFPShooterCharacter : public AGDKShooterCharacter -{ - GENERATED_BODY() - -public: - AGDKFPShooterCharacter(const FObjectInitializer& ObjectInitializer); - - // Returns a position from which to start a line trace. - // Use this so your line trace doesn't collide with the player character. - virtual FVector GetLineTraceStart() const override; - - // Returns the direction in which to perform a line trace so it lines up with the center of the crosshair. - virtual FVector GetLineTraceDirection() const override; - - virtual void AttachWeapon(AWeapon* weapon) const override; - -protected: - - /** Name of the FirstPersonMeshComponent. Use this name if you want to prevent creation of the component (with ObjectInitializer.DoNotCreateDefaultSubobject). */ - static FName FirstPersonMeshComponentName; - - /** First person camera */ - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true")) - class UCameraComponent* FirstPersonCamera; - - /** The first person skeletal mesh associated with this Character. */ - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Character, meta = (AllowPrivateAccess = "true")) - USkeletalMeshComponent* FirstPersonMesh; - -}; diff --git a/Game/Source/GDKShooter/Public/Controllers/Components/ControllerEventsComponent.h b/Game/Source/GDKShooter/Public/Controllers/Components/ControllerEventsComponent.h new file mode 100644 index 00000000..ea7fe381 --- /dev/null +++ b/Game/Source/GDKShooter/Public/Controllers/Components/ControllerEventsComponent.h @@ -0,0 +1,40 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "GameFramework/Actor.h" +#include "ControllerEventsComponent.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FControllerEvent, const AController*, Controller); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FKillDetailsEvent, const FString&, VictimName, int32, VictimId); + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API UControllerEventsComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UControllerEventsComponent(); + + UFUNCTION(CrossServer, Reliable) + void Death(const AController* Killer); + UFUNCTION(CrossServer, Reliable) + void Kill(const AController* Victim); + + UPROPERTY(BlueprintAssignable) + FControllerEvent DeathEvent; + UPROPERTY(BlueprintAssignable) + FControllerEvent KillEvent; + + UFUNCTION(Client, Reliable) + void ClientInformOfKill(const FString& VictimName, int32 VictimId); + UFUNCTION(Client, Reliable) + void ClientInformOfDeath(const FString& KillerName, int32 KillerId); + + UPROPERTY(BlueprintAssignable) + FKillDetailsEvent KillDetailsEvent; + UPROPERTY(BlueprintAssignable) + FKillDetailsEvent DeathDetailsEvent; +}; diff --git a/Game/Source/GDKShooter/Public/Controllers/Components/TeamSettingComponent.h b/Game/Source/GDKShooter/Public/Controllers/Components/TeamSettingComponent.h new file mode 100644 index 00000000..450dae88 --- /dev/null +++ b/Game/Source/GDKShooter/Public/Controllers/Components/TeamSettingComponent.h @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "Runtime/AIModule/Classes/GenericTeamAgentInterface.h" +#include "TeamSettingComponent.generated.h" + + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API UTeamSettingComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UTeamSettingComponent(); + +protected: + virtual void BeginPlay() override; + + UPROPERTY(EditAnywhere) + FGenericTeamId TeamId = FGenericTeamId::NoTeam; + +}; diff --git a/Game/Source/GDKShooter/Public/Controllers/GDKPlayerController.h b/Game/Source/GDKShooter/Public/Controllers/GDKPlayerController.h index f47fb9d1..4c404433 100644 --- a/Game/Source/GDKShooter/Public/Controllers/GDKPlayerController.h +++ b/Game/Source/GDKShooter/Public/Controllers/GDKPlayerController.h @@ -4,45 +4,12 @@ #include "CoreMinimal.h" #include "GameFramework/PlayerController.h" -#include "Game/GDKMetaData.h" -#include "Game/GDKPlayerScore.h" -#include "Game/GDKSessionProgress.h" +#include "Characters/Components/MetaDataComponent.h" +#include "Game/Components/DeathmatchScoreComponent.h" +#include "Game/Components/MatchStateComponent.h" #include "GDKPlayerController.generated.h" -UENUM(BlueprintType) -enum class EGDKMenu : uint8 -{ - None UMETA(DisplayName = "None"), - Menu UMETA(DisplayName = "Menu"), - Scores UMETA(DisplayName = "Scores"), -}; - -UENUM(BlueprintType) -enum class EGDKControllerState : uint8 -{ - PreCharacter UMETA(DisplayName = "PreCharacter"), - PendingCharacter UMETA(DisplayName = "PendingCharacter"), - InProgress UMETA(DisplayName = "InProgress"), - Finished UMETA(DisplayName = "Finished"), -}; - -UENUM(BlueprintType) -enum class EGDKCharacterState : uint8 -{ - Alive UMETA(DisplayName = "Alive"), - Dead UMETA(DisplayName = "Dead"), - PendingRespawn UMETA(DisplayName = "PendingRespawn"), -}; - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMenuEvent, EGDKMenu, CurrentMenu); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FControllerStateEvent, EGDKControllerState, CurrentState); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FCharacterStateEvent, EGDKCharacterState, CurrentState); - -DECLARE_EVENT_TwoParams(AGDKPlayerController, FValueChangedEvent, int32, int32); -DECLARE_EVENT_OneParam(AGDKPlayerController, FBooleanChangedEvent, bool); -DECLARE_EVENT_TwoParams(AGDKPlayerController, FShotEvent, AWeapon*, bool); -DECLARE_EVENT_TwoParams(AGDKPlayerController, FKillNotificationEvent, const FString&, int32); -DECLARE_EVENT_OneParam(AGDKPlayerController, FWeaponEvent, AWeapon*); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FPawnEvent, APawn*, InPawn); UCLASS(SpatialType) class GDKSHOOTER_API AGDKPlayerController : public APlayerController @@ -54,101 +21,30 @@ class GDKSHOOTER_API AGDKPlayerController : public APlayerController virtual void BeginPlay() override; virtual void Tick(float DeltaTime) override; - virtual void EndPlay(const EEndPlayReason::Type Reason) override; - - UPROPERTY(BlueprintAssignable) - FMenuEvent OnMenuChanged; - UPROPERTY(BlueprintAssignable) - FControllerStateEvent OnControllerState; - UPROPERTY(BlueprintAssignable) - FCharacterStateEvent OnCharacterState; - - - FValueChangedEvent& OnHealthUpdated() { return HealthChangedEvent; } - FValueChangedEvent& OnArmourUpdated() { return ArmourChangedEvent; } - - FBooleanChangedEvent& OnAimingUpdated() { return AimingChangedEvent; } - - FShotEvent& OnShot() { return ShotEvent; } - - FKillNotificationEvent& OnKillNotification() { return KillNotification; } - FKillNotificationEvent& OnKilledNotification() { return KilledNotification; } - - void UpdateHealthUI(int32 NewHealth, int32 MaxHealth); - void UpdateArmourUI(int32 NewArmour, int32 MaxArmour); - FWeaponEvent& OnWeaponChanged() { return WeaponNotification; } + FPawnEvent& OnPawn() { return PawnEvent; } // Overrides AController::SetPawn, which should be called on the client and server whenever the controller // possesses (or unpossesses) a pawn. virtual void SetPawn(APawn* InPawn) override; - - // [server] Tells the controller that it's time for the player to die, and sets up conditions for respawn. - // @param Killer The player who killed me. Can be null if it wasn't a player who dealt the damage that killed me. - void KillCharacter(const class AGDKCharacter* Killer); - + // [client] Sets whether the cursor is in "UI mode", meaning it is visible and can be moved around the screen, // instead of locked, invisible, and used for aiming.v - void SetUIMode(); - void SetUIMode(bool bIsUIMode, bool bAllowMovement = false); + UFUNCTION(BlueprintCallable) + void SetUIMode(bool bIsUIMode); // [client] Sets whether we should ignore action input. For this to work properly, the character - // must check the result of IgnoreActionInput before applying any action inputs. - void SetIgnoreActionInput(bool bIgnoreInput) { bIgnoreActionInput = bIgnoreInput; } - // [client] If true, action input should be ignored. This should be called from the character, or any other object // which handles user input. bool IgnoreActionInput() const { return bIgnoreActionInput; } - // [client] Sets the player-choice data (name, team, etc) and requests to spawn player pawn and join play. - UFUNCTION(BlueprintCallable) - void TryJoinGame(const FString& NewPlayerName, const FGDKMetaData MetaData); - - UFUNCTION(BlueprintCallable) - void RequestRespawn(); - - void UpdatePlayerScores(const TArray& PlayerScores); - - UFUNCTION(Client, unreliable) - void InformOfKill(const FString& VictimName, int32 VictimId); - UFUNCTION(Client, unreliable) - void InformOfDeath(const FString& KillerName, int32 KillerId); - protected: - virtual void SetupInputComponent() override; - - bool bHasRequetsedPlayer; - bool bHasBeenGrantedPlayer; - - UFUNCTION(BlueprintImplementableEvent) - void OnAimingChanged(bool bIsAiming, float AimRotationSpeed); - - virtual void OnCharacterShot(AWeapon* Weapon, bool Hit); + UPROPERTY(BlueprintAssignable) + FPawnEvent PawnEvent; UFUNCTION(BlueprintImplementableEvent) - void OnShot(AWeapon* Weapon, bool Hit); - - virtual void ChooseNewSpawnPoint(); - - FValueChangedEvent HealthChangedEvent; - FValueChangedEvent ArmourChangedEvent; - - FBooleanChangedEvent AimingChangedEvent; - - FShotEvent ShotEvent; - - FKillNotificationEvent KillNotification; - FKillNotificationEvent KilledNotification; - - FWeaponEvent WeaponNotification; - - UPROPERTY(BlueprintReadOnly) - EGDKMenu CurrentMenu = EGDKMenu::None; - UPROPERTY(BlueprintReadOnly) - EGDKCharacterState CurrentCharacterState = EGDKCharacterState::Alive; - UPROPERTY(BlueprintReadOnly) - EGDKControllerState CurrentControllerState = EGDKControllerState::PreCharacter; - + void OnNewPawn(APawn* InPawn); + /** Death camera */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true")) class UCameraComponent* DeathCamera; @@ -162,23 +58,19 @@ class GDKSHOOTER_API AGDKPlayerController : public APlayerController float LatestPawnYaw; private: - // Sets the player-choice data (name, team, etc) and requests to spawn player pawn and join play - UFUNCTION(Server, Reliable, WithValidation) - void ServerTryJoinGame(const FString& NewPlayerName, const FGDKMetaData MetaData); - - // [client] Informs the invoking client whether the join request suceeded or failed - UFUNCTION(Client, Reliable) - void ClientJoinResults(const bool bJoinSucceeded); + // Requests to spawn player pawn and join play + UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable) + void ServerTryJoinGame(); - // [server] Causes the character to respawn. - UFUNCTION(Server, Reliable, WithValidation) - void RespawnCharacter(); - - void AimingChanged(bool bIsAiming, float AimRotationSpeed); + UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable) + void ServerRequestName(const FString& NewPlayerName); - void WeaponChanged(AWeapon* Weapon); + UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable) + void ServerRequestMetaData(const FGDKMetaData NewMetaData); - bool bGameFinished; + // [server] Causes the character to respawn. + UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable) + void ServerRespawnCharacter(); // Gets a default player name based upon the worker's ID. // Generates a GUID if we're not running on a SpatialOS worker. @@ -192,18 +84,7 @@ class GDKSHOOTER_API AGDKPlayerController : public APlayerController UPROPERTY(EditDefaultsOnly, Category = "Respawn") float DeleteCharacterDelay; - UFUNCTION() - void TimerUpdated(EGDKSessionProgress SessionProgress, int SessionTimer); - FTimerHandle RespawnTimerHandle; - void SetControllerState(EGDKControllerState NewState); - void SetCharacterState(EGDKCharacterState NewState); - void SetMenu(EGDKMenu NewMenu); - - void ShowScoreboard();; - void HideScoreboard(); - void ToggleMenu(); - }; diff --git a/Game/Source/GDKShooter/Public/Game/Components/DeathmatchScoreComponent.h b/Game/Source/GDKShooter/Public/Game/Components/DeathmatchScoreComponent.h new file mode 100644 index 00000000..5ec8b7c4 --- /dev/null +++ b/Game/Source/GDKShooter/Public/Game/Components/DeathmatchScoreComponent.h @@ -0,0 +1,64 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "GameFramework/PlayerState.h" +#include "DeathmatchScoreComponent.generated.h" + +// Information about a players performance during a match +USTRUCT(BlueprintType) +struct FPlayerScore { + GENERATED_BODY() + + UPROPERTY(BlueprintReadOnly) + int32 PlayerId; + + UPROPERTY(BlueprintReadOnly) + FString PlayerName; + + UPROPERTY(BlueprintReadOnly) + int32 Kills; + + UPROPERTY(BlueprintReadOnly) + int32 Deaths; +}; + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FScoreChangeEvent, const TArray&, LatestScores); + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API UDeathmatchScoreComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UDeathmatchScoreComponent(); + + UFUNCTION(BlueprintCallable) + void RecordKill(int32 KillerId, int32 VictimId); + + UFUNCTION(BlueprintCallable) + void RecordNewPlayer(APlayerState* PlayerState); + + UFUNCTION(BlueprintPure) + TArray& PlayerScores() { return PlayerScoreArray; } + + UPROPERTY(BlueprintAssignable) + FScoreChangeEvent ScoreEvent; + +protected: + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + UFUNCTION() + void OnRep_PlayerScores(); + + UPROPERTY(ReplicatedUsing = OnRep_PlayerScores) + TArray PlayerScoreArray; + + // A map from player name to score, to make it easier to update scores + UPROPERTY() + TMap PlayerScoreMap; + +}; diff --git a/Game/Source/GDKShooter/Public/Game/Components/DeathmatchSpawnerComponent.h b/Game/Source/GDKShooter/Public/Game/Components/DeathmatchSpawnerComponent.h new file mode 100644 index 00000000..cf203b60 --- /dev/null +++ b/Game/Source/GDKShooter/Public/Game/Components/DeathmatchSpawnerComponent.h @@ -0,0 +1,35 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "GameFramework/PlayerController.h" +#include "GameFramework/PlayerState.h" +#include "DeathmatchSpawnerComponent.generated.h" + + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API UDeathmatchSpawnerComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UDeathmatchSpawnerComponent(); + + UFUNCTION(BlueprintCallable) + void RequestSpawn(APlayerController* Controller); + + UFUNCTION(BlueprintCallable) + void EnableSpawning() { bSpawningEnabled = true; } + + UFUNCTION(BlueprintCallable) + void DisableSpawning() { bSpawningEnabled = false; } + +protected: + AActor* GetSpawnPoint(APlayerController* Controller); + + void SpawnCharacter(APlayerController* Controller); + + bool bSpawningEnabled; +}; diff --git a/Game/Source/GDKShooter/Public/Game/Components/LobbyTimerComponent.h b/Game/Source/GDKShooter/Public/Game/Components/LobbyTimerComponent.h new file mode 100644 index 00000000..d0afd5e2 --- /dev/null +++ b/Game/Source/GDKShooter/Public/Game/Components/LobbyTimerComponent.h @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "TimerComponent.h" +#include "LobbyTimerComponent.generated.h" + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API ULobbyTimerComponent : public UTimerComponent +{ + GENERATED_BODY() + +public: + + UFUNCTION(BlueprintCallable) + void InformOfPlayerCount(int32 PlayerCount); + UPROPERTY(EditDefaultsOnly) + int32 MinimumPlayersToStartCountdown = 1; + +protected: + virtual void BeginPlay() override; + + +}; diff --git a/Game/Source/GDKShooter/Public/Game/Components/MatchStateComponent.h b/Game/Source/GDKShooter/Public/Game/Components/MatchStateComponent.h new file mode 100644 index 00000000..00459664 --- /dev/null +++ b/Game/Source/GDKShooter/Public/Game/Components/MatchStateComponent.h @@ -0,0 +1,44 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "MatchStateComponent.generated.h" + +UENUM(BlueprintType) +enum class EMatchState : uint8 +{ + PreGame UMETA(DisplayName = "PreGame"), + InGame UMETA(DisplayName = "InGame"), + PostGame UMETA(DisplayName = "PostGame") +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMatchEvent, EMatchState, CurrentState); + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API UMatchStateComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UMatchStateComponent(); + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + UPROPERTY(BlueprintAssignable) + FMatchEvent MatchEvent; + + UFUNCTION(BlueprintCallable) + void SetMatchState(EMatchState NewState); + + UFUNCTION(BlueprintPure) + EMatchState GetCurrentState() { return CurrentState; } + +protected: + + UPROPERTY(ReplicatedUsing=OnRep_State) + EMatchState CurrentState; + + UFUNCTION() + void OnRep_State() { MatchEvent.Broadcast(CurrentState); } +}; diff --git a/Game/Source/GDKShooter/Public/Game/Components/MatchTimerComponent.h b/Game/Source/GDKShooter/Public/Game/Components/MatchTimerComponent.h new file mode 100644 index 00000000..008f77a0 --- /dev/null +++ b/Game/Source/GDKShooter/Public/Game/Components/MatchTimerComponent.h @@ -0,0 +1,15 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "TimerComponent.h" +#include "MatchTimerComponent.generated.h" + + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API UMatchTimerComponent : public UTimerComponent +{ + GENERATED_BODY() + +}; diff --git a/Game/Source/GDKShooter/Public/Game/Components/PlayerCountingComponent.h b/Game/Source/GDKShooter/Public/Game/Components/PlayerCountingComponent.h new file mode 100644 index 00000000..1a5657ea --- /dev/null +++ b/Game/Source/GDKShooter/Public/Game/Components/PlayerCountingComponent.h @@ -0,0 +1,36 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "PlayerPublisher.h" +#include "PlayerCountingComponent.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FPlayerCountEvent, int32, PlayerCount); + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API UPlayerCountingComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UPlayerCountingComponent(); + + UFUNCTION(BlueprintPure) + int32 PlayerCount() { return ConnectedPlayerCount; } + + UPROPERTY(BlueprintAssignable) + FPlayerCountEvent PlayerCountEvent; + + UFUNCTION(BlueprintCallable) + void PlayerEvent(APlayerState* PlayerState, EPlayerProgress Progress); + +protected: + UPROPERTY(ReplicatedUsing = OnRep_ConnectedPlayerCount) + int32 ConnectedPlayerCount; + + UFUNCTION() + void OnRep_ConnectedPlayerCount(); + +}; diff --git a/Game/Source/GDKShooter/Public/Game/Components/PlayerPublisher.h b/Game/Source/GDKShooter/Public/Game/Components/PlayerPublisher.h new file mode 100644 index 00000000..638c6d6b --- /dev/null +++ b/Game/Source/GDKShooter/Public/Game/Components/PlayerPublisher.h @@ -0,0 +1,32 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "GameFramework/PlayerState.h" +#include "PlayerPublisher.generated.h" + +UENUM(BlueprintType) +enum class EPlayerProgress : uint8 +{ + Connected UMETA(DisplayName = "Connected"), + InGame UMETA(DisplayName = "InGame"), + Finished UMETA(DisplayName = "Finished"), + Disconnected UMETA(DisplayName = "Disconnected"), +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FPlayerEvent, APlayerState*, PlayerState, EPlayerProgress, Progress); + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API UPlayerPublisher : public UActorComponent +{ + GENERATED_BODY() + +public: + void PublishPlayer(APlayerState* PlayerState, EPlayerProgress Progress) { PlayerEvent.Broadcast(PlayerState, Progress); } + + UPROPERTY(BlueprintAssignable) + FPlayerEvent PlayerEvent; + +}; diff --git a/Game/Source/GDKShooter/Public/Game/Components/ScorePublisher.h b/Game/Source/GDKShooter/Public/Game/Components/ScorePublisher.h new file mode 100644 index 00000000..065cd2bd --- /dev/null +++ b/Game/Source/GDKShooter/Public/Game/Components/ScorePublisher.h @@ -0,0 +1,30 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "GameFramework/PlayerState.h" +#include "ScorePublisher.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FKillEvent, int32, KillerId, int32, VictimId); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDeathOfState, APlayerState*, VictimState); + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API UScorePublisher : public UActorComponent +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable) + void PublishKill(int32 KillerId, APlayerState* VictimState) { + KillEvent.Broadcast(KillerId, VictimState->PlayerId); + DeathEvent.Broadcast(VictimState); + } + + UPROPERTY(BlueprintAssignable) + FKillEvent KillEvent; + UPROPERTY(BlueprintAssignable) + FDeathOfState DeathEvent; + +}; diff --git a/Game/Source/GDKShooter/Public/Game/Components/SpatialSessionStateComponent.h b/Game/Source/GDKShooter/Public/Game/Components/SpatialSessionStateComponent.h new file mode 100644 index 00000000..65872fa8 --- /dev/null +++ b/Game/Source/GDKShooter/Public/Game/Components/SpatialSessionStateComponent.h @@ -0,0 +1,36 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" + +#include + +#include "SpatialSessionStateComponent.generated.h" + +// States of a session, to match the schema of the deployment manager +UENUM(BlueprintType) +enum class EGDKSessionProgress : uint8 +{ + Lobby UMETA(DisplayName = "Lobby"), + Running UMETA(DisplayName = "Running"), + Results UMETA(DisplayName = "Results"), + Finished UMETA(DisplayName = "Finished"), +}; + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API USpatialSessionStateComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + + UFUNCTION(BlueprintCallable) + void SendStateUpdate(EGDKSessionProgress SessionProgressState); + +protected: + Worker_EntityId SessionEntityId = 39; + Worker_ComponentId SessionComponentId = 1000; + +}; diff --git a/Game/Source/GDKShooter/Public/Game/Components/SpawnRequestPublisher.h b/Game/Source/GDKShooter/Public/Game/Components/SpawnRequestPublisher.h new file mode 100644 index 00000000..af7aa4a9 --- /dev/null +++ b/Game/Source/GDKShooter/Public/Game/Components/SpawnRequestPublisher.h @@ -0,0 +1,27 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "SpawnRequestPublisher.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSpawnRequest, APlayerController*, Controller); + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API USpawnRequestPublisher : public UActorComponent +{ + GENERATED_BODY() + +public: + USpawnRequestPublisher(); + + UPROPERTY(BlueprintAssignable) + FSpawnRequest OnSpawnRequest; + + void RequestSpawn(APlayerController* Controller) { OnSpawnRequest.Broadcast(Controller); } + + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly) + bool bAutoConnect; + +}; diff --git a/Game/Source/GDKShooter/Public/Game/Components/TeamSpawnerComponent.h b/Game/Source/GDKShooter/Public/Game/Components/TeamSpawnerComponent.h new file mode 100644 index 00000000..ac0c8792 --- /dev/null +++ b/Game/Source/GDKShooter/Public/Game/Components/TeamSpawnerComponent.h @@ -0,0 +1,40 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "GameFramework/PlayerStart.h" +#include "Engine/PlayerStartPIE.h" +#include "TeamSpawnerComponent.generated.h" + + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API UTeamSpawnerComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UTeamSpawnerComponent(); + + UFUNCTION(BlueprintCallable) + void RequestSpawn(APlayerController* Controller); + + UPROPERTY(EditDefaultsOnly) + int32 TeamCapacity = 3; + + UPROPERTY(VisibleAnywhere, BlueprintReadWrite) + bool bAllowRespawning = true; + +protected: + void BeginPlay() override; + + APlayerStartPIE* PlayerStartPIE; + TMap TeamStartPoints; + TMap TeamAssignments; + TMap SpawnedPlayers; + + int32 CurrentTeamPointer = 0; + int32 StartingTeamId = 1; + int32 GetAvailableTeamId(); +}; diff --git a/Game/Source/GDKShooter/Public/Game/Components/TimerComponent.h b/Game/Source/GDKShooter/Public/Game/Components/TimerComponent.h new file mode 100644 index 00000000..b5aabc75 --- /dev/null +++ b/Game/Source/GDKShooter/Public/Game/Components/TimerComponent.h @@ -0,0 +1,63 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "TimerManager.h" +#include "TimerComponent.generated.h" + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTimerEvent, int, CurrentTimer); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FTimerFinishedEvent); + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class GDKSHOOTER_API UTimerComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UTimerComponent(); + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + UFUNCTION(BlueprintCallable) + void StartTimer(); + UFUNCTION(BlueprintCallable) + void ResumeTimer(); + UFUNCTION(BlueprintCallable) + void SetTimer(int32 NewValue); + UFUNCTION(BlueprintCallable) + void StopTimer(); + + UPROPERTY(BlueprintAssignable) + FTimerEvent OnTimer; + UPROPERTY(BlueprintAssignable) + FTimerFinishedEvent OnTimerFinished; + + UFUNCTION(BlueprintPure) + int32 GetTimer() { return TimeLeft; } + +protected: + void BeginPlay(); + + UPROPERTY(EditDefaultsOnly) + bool bAutoStart = false; + UPROPERTY(EditDefaultsOnly) + int32 DefaultTimerDuration = 300; + + UPROPERTY(Replicated, BlueprintReadOnly) + bool bIsTimerRunning = false; + UPROPERTY(ReplicatedUsing = OnRep_Timer, BlueprintReadOnly) + int32 TimeLeft; + UPROPERTY(ReplicatedUsing = OnRep_TimerFinished, BlueprintReadOnly) + bool bHasTimerFinished = false; + + UFUNCTION() + void DecrementTimer(); + UFUNCTION() + void OnRep_Timer(); + UFUNCTION() + void OnRep_TimerFinished(); + + FTimerHandle TimerHandle; +}; diff --git a/Game/Source/GDKShooter/Public/Game/GDKGameState.h b/Game/Source/GDKShooter/Public/Game/GDKGameState.h deleted file mode 100644 index 274a9634..00000000 --- a/Game/Source/GDKShooter/Public/Game/GDKGameState.h +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "CoreMinimal.h" -#include "GameFramework/Actor.h" -#include "GameFramework/GameStateBase.h" -#include "Game/GDKPlayerScore.h" -#include "GDKGameState.generated.h" - -// Events for listening to game state updates -DECLARE_EVENT_OneParam(AGDKGameState, FScoreChangeEvent, const TArray&); -DECLARE_EVENT_OneParam(AGDKGameState, FPlayerCountEvent, int); - -UCLASS(SpatialType = Singleton) -class GDKSHOOTER_API AGDKGameState : public AGameStateBase -{ - GENERATED_BODY() - -public: - AGDKGameState(); - - // Adds a player to the game's score data with empty stats. - void AddPlayer(const int32 Actor, const FString& Player); - - // Adds a death (and a corresponding kill, if necessary) to the game's score data. - void AddDeath(const int32 Killer, const int32 Victim); - - FScoreChangeEvent& OnScoreUpdated() { return ScoreEvent; } - - FPlayerCountEvent& OnPlayerCountUpdated() { return PlayerCountEvent; } - - UPROPERTY(ReplicatedUsing = OnRep_ConnectedPlayers) - int ConnectedPlayers; - - virtual void AddPlayerState(APlayerState* PlayerState) override; - virtual void RemovePlayerState(APlayerState* PlayerState) override; - - TArray& PlayerScores() { return PlayerScoreArray; } - -protected: - - virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; - -private: - - UFUNCTION() - void OnRep_ConnectedPlayers(); - - UFUNCTION() - void OnRep_PlayerScores(); - - UFUNCTION(NetMulticast, Unreliable) - void MulticastNotifyKill(int32 KillerId, const FString& KillerName, int32 VictimId, const FString& VictimName); - - // Retrieves the FTeamScore value for a given team, or nullptr if the team is invalid (or the scores haven't been initialized yet). - FPlayerScore* GetScoreForPlayer(int32 Player); - - // Actually adds the player. - void AddPlayerInternal(const int32 Player, const FString& PlayerName); - - // List of teams' scores, including top player lists. - UPROPERTY(ReplicatedUsing = OnRep_PlayerScores) - TArray PlayerScoreArray; - - // A map from player name to score, to make it easier to refer to players. - UPROPERTY() - TMap PlayerScoreMap; - - FScoreChangeEvent ScoreEvent; - FPlayerCountEvent PlayerCountEvent; -}; diff --git a/Game/Source/GDKShooter/Public/Game/GDKMetaData.h b/Game/Source/GDKShooter/Public/Game/GDKMetaData.h deleted file mode 100644 index 427827c2..00000000 --- a/Game/Source/GDKShooter/Public/Game/GDKMetaData.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "CoreUObject.h" - -#include "GDKMetaData.generated.h" - -// Meta Data sctruct holding information about a player, also passed to a player's inventory items -USTRUCT(BlueprintType) -struct FGDKMetaData { - GENERATED_BODY() - - UPROPERTY(BlueprintReadWrite) - int32 Customization; -}; diff --git a/Game/Source/GDKShooter/Public/Game/GDKPlayerScore.h b/Game/Source/GDKShooter/Public/Game/GDKPlayerScore.h deleted file mode 100644 index 56da8e64..00000000 --- a/Game/Source/GDKShooter/Public/Game/GDKPlayerScore.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "CoreMinimal.h" - -#include "GDKPlayerScore.generated.h" - -// Information about a players performane during a match -USTRUCT(BlueprintType) -struct FPlayerScore { - GENERATED_BODY() - - UPROPERTY(BlueprintReadOnly) - int32 PlayerId; - - UPROPERTY(BlueprintReadOnly) - FString PlayerName; - - UPROPERTY(BlueprintReadOnly) - int32 Kills; - - UPROPERTY(BlueprintReadOnly) - int32 Deaths; -}; diff --git a/Game/Source/GDKShooter/Public/Game/GDKPlayerState.h b/Game/Source/GDKShooter/Public/Game/GDKPlayerState.h deleted file mode 100644 index da939152..00000000 --- a/Game/Source/GDKShooter/Public/Game/GDKPlayerState.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "CoreMinimal.h" -#include "GameFramework/PlayerState.h" -#include "GDKMetaData.h" -#include "GDKPlayerState.generated.h" - -UCLASS(SpatialType) -class GDKSHOOTER_API AGDKPlayerState : public APlayerState -{ - GENERATED_BODY() - -protected: - UPROPERTY(Transient, Replicated) - FGDKMetaData MetaData; - -public: - void SetMetaData(const FGDKMetaData& NewMetaData) { MetaData = NewMetaData; } - const FGDKMetaData GetMetaData() const { return MetaData; } -}; diff --git a/Game/Source/GDKShooter/Public/Game/GDKSessionGameState.h b/Game/Source/GDKShooter/Public/Game/GDKSessionGameState.h deleted file mode 100644 index ff8f80e2..00000000 --- a/Game/Source/GDKShooter/Public/Game/GDKSessionGameState.h +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "CoreMinimal.h" -#include "GDKLogging.h" -#include "Game/GDKGameState.h" -#include "GDKSessionProgress.h" -#include "GDKSessionGameState.generated.h" - -DECLARE_EVENT_TwoParams(AGDKGameState, FSessionTimerEvent, EGDKSessionProgress, int); - -UCLASS(SpatialType = Singleton) -class GDKSHOOTER_API AGDKSessionGameState : public AGDKGameState -{ - GENERATED_BODY() - -public: - - virtual void AddPlayerState(APlayerState* PlayerState) override; - virtual void RemovePlayerState(APlayerState* PlayerState) override; - - FSessionTimerEvent& OnTimerUpdated() { return TimerEvent; } - - UPROPERTY(ReplicatedUsing = OnRep_SessionProgress) - EGDKSessionProgress SessionProgress; - - UPROPERTY(ReplicatedUsing = OnRep_SessionTimer) - int SessionTimer; - -protected: - - virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; - -private: - - UPROPERTY(EditAnywhere, Category = "Timers") - int LobbySessionLength = 60; - - UPROPERTY(EditAnywhere, Category = "Timers") - int GameSessionLength = 300; - - UPROPERTY(EditAnywhere, Category = "Timers") - int ResultsSessionLength = 60; - - FTimerHandle TickTimer; - - UFUNCTION() - void OnRep_SessionProgress(); - - UFUNCTION() - void OnRep_SessionTimer(); - - // Called once per second to advance game timer - void TickGameTimer(); - - // Send a component update to the session manager entity to be picked up by the deployment manager - void SendStateUpdate(int NewState); - - // Begin progressing through the different stages of game session, if not already started - void BeginTimer(); - - FSessionTimerEvent TimerEvent; -}; diff --git a/Game/Source/GDKShooter/Public/Game/GDKSessionProgress.h b/Game/Source/GDKShooter/Public/Game/GDKSessionProgress.h deleted file mode 100644 index 05bc5451..00000000 --- a/Game/Source/GDKShooter/Public/Game/GDKSessionProgress.h +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Improbable Worlds Ltd, All Rights Reserved - -#pragma once - -#include "CoreUObject.h" - -// States of a session, to match the schema of the deployment manager -UENUM(BlueprintType) -enum class EGDKSessionProgress : uint8 -{ - Lobby UMETA(DisplayName = "Lobby"), - Running UMETA(DisplayName = "Running"), - Results UMETA(DisplayName = "Results"), - Finished UMETA(DisplayName = "Finished"), -}; diff --git a/Game/Source/GDKShooter/Public/GameFramework/CrossServerPawn.h b/Game/Source/GDKShooter/Public/GameFramework/CrossServerPawn.h new file mode 100644 index 00000000..bba568b4 --- /dev/null +++ b/Game/Source/GDKShooter/Public/GameFramework/CrossServerPawn.h @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Pawn.h" +#include "CrossServerPawn.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FIncomingDamageEvent, float, Damage, const struct FDamageEvent&, DamageEvent, AController*, EventInstigator, AActor*, DamageCauser); + +UCLASS() +class GDKSHOOTER_API ACrossServerPawn : public APawn +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FIncomingDamageEvent IncomingDamage; + + float TakeDamage(float Damage, const struct FDamageEvent& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override; + + UFUNCTION(CrossServer, Reliable) + void TakeDamageCrossServer(float Damage, const struct FDamageEvent& DamageEvent, AController* EventInstigator, AActor* DamageCauser); + +}; diff --git a/Game/Source/GDKShooter/Public/Phases/IPhaseActivated.h b/Game/Source/GDKShooter/Public/Phases/IPhaseActivated.h new file mode 100644 index 00000000..1b3fe266 --- /dev/null +++ b/Game/Source/GDKShooter/Public/Phases/IPhaseActivated.h @@ -0,0 +1,23 @@ +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" + +#include "IPhaseActivated.generated.h" + +UINTERFACE(BlueprintType) +class GDKSHOOTER_API UPhaseActivated : public UInterface +{ + GENERATED_BODY() +}; + +class GDKSHOOTER_API IPhaseActivated +{ + GENERATED_BODY() +public: + + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Phases") + void SnapToPhase(int32 Phase); + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Phases") + void ProgressToPhase(int32 Phase); +}; diff --git a/Game/Source/GDKShooter/Public/Phases/PhasedPainCausingVolume.h b/Game/Source/GDKShooter/Public/Phases/PhasedPainCausingVolume.h new file mode 100644 index 00000000..be4c6a54 --- /dev/null +++ b/Game/Source/GDKShooter/Public/Phases/PhasedPainCausingVolume.h @@ -0,0 +1,64 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/PostProcessVolume.h" +#include "GameFramework/PainCausingVolume.h" +#include "IPhaseActivated.h" +#include "PhasedPainCausingVolume.generated.h" + +/** + * + */ +UCLASS() +class GDKSHOOTER_API APhasedPainCausingVolume : public APainCausingVolume, public IPhaseActivated +{ + GENERATED_BODY() + +public: + APhasedPainCausingVolume() + { + bPainCausing = false; + } + + void BeginPlay() override + { + if (PostProcessVolume) + { + PostProcessVolume->bEnabled = false; + PostProcessVolume->SetActorLocation(GetActorLocation()); + PostProcessVolume->SetActorScale3D(GetActorScale3D() - FVector(1, 1, 0)); + } + } + + UPROPERTY(EditInstanceOnly, BlueprintReadOnly) + int ActivatesInPhase = 255; + + UPROPERTY(EditInstanceOnly, BlueprintReadOnly) + APostProcessVolume* PostProcessVolume; + + void Activate() + { + bPainCausing = true; + if (PostProcessVolume) + { + PostProcessVolume->bEnabled = true; + } + } + + void SnapToPhase_Implementation(int32 Phase) + { + if (Phase >= ActivatesInPhase) + { + Activate(); + } + } + void ProgressToPhase_Implementation(int32 Phase) + { + if (Phase >= ActivatesInPhase) + { + Activate(); + } + } +}; diff --git a/Game/Source/GDKShooter/Public/UI/GDKWidget.h b/Game/Source/GDKShooter/Public/UI/GDKWidget.h index 974c39a6..fea56662 100644 --- a/Game/Source/GDKShooter/Public/UI/GDKWidget.h +++ b/Game/Source/GDKShooter/Public/UI/GDKWidget.h @@ -4,9 +4,10 @@ #include "CoreMinimal.h" #include "Weapons/Weapon.h" -#include "Game/GDKSessionProgress.h" -#include "Game/GDKPlayerScore.h" +#include "Game/Components/DeathmatchScoreComponent.h" +#include "Game/Components/MatchStateComponent.h" #include "Blueprint/UserWidget.h" +#include "Controllers/GDKPlayerController.h" #include "GDKWidget.generated.h" /** @@ -23,19 +24,25 @@ class GDKSHOOTER_API UGDKWidget : public UUserWidget protected: + UFUNCTION(BlueprintImplementableEvent) + void RegisterListeners(); + UPROPERTY(BlueprintReadOnly) - APlayerController* PlayerController; + AGDKPlayerController* GDKPlayerController; UPROPERTY(BlueprintReadOnly) bool bListenersAdded; + UFUNCTION() + void OnPawn(APawn* InPawn); + // Called whenever the current health value is updated UFUNCTION(BlueprintImplementableEvent, Category = "GDK") - void OnHealthUpdated(int32 CurrentHealth, int32 MaxHealth); + void OnHealthUpdated(float CurrentHealth, float MaxHealth); // Called whenever the current armour value is updated UFUNCTION(BlueprintImplementableEvent, Category = "GDK") - void OnArmourUpdated(int32 CurrentArmour, int32 MaxArmour); + void OnArmourUpdated(float CurrentArmour, float MaxArmour); // Called whenever the local player dies UFUNCTION(BlueprintImplementableEvent, Category = "GDK") @@ -57,9 +64,17 @@ class GDKSHOOTER_API UGDKWidget : public UUserWidget UFUNCTION(BlueprintImplementableEvent, Category = "GDK") void OnPlayerScoresUpdated(const TArray& Scores); - // Called each time the game state timer ticks or changes session state + // Called each time the game state changes + UFUNCTION(BlueprintImplementableEvent, Category = "GDK") + void OnStateUpdated(EMatchState MatchState); + + // Called each time the lobby timer changes + UFUNCTION(BlueprintImplementableEvent, Category = "GDK") + void OnLobbyTimerUpdated(int SecondsRemaining); + + // Called each time the match timer changes UFUNCTION(BlueprintImplementableEvent, Category = "GDK") - void OnTimerUpdated(EGDKSessionProgress SessionProgress, int SecondsRemaining); + void OnMatchTimerUpdated(int SecondsRemaining); // Called each time a shot is fired by the local player UFUNCTION(BlueprintImplementableEvent, Category = "GDK") diff --git a/Game/Source/GDKShooter/Public/Weapons/Holdable.h b/Game/Source/GDKShooter/Public/Weapons/Holdable.h new file mode 100644 index 00000000..eb367d16 --- /dev/null +++ b/Game/Source/GDKShooter/Public/Weapons/Holdable.h @@ -0,0 +1,98 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "Components/EquippedComponent.h" +#include "Characters/Components/MetaDataComponent.h" +#include "Holdable.generated.h" + +UCLASS() +class GDKSHOOTER_API AHoldable : public AActor +{ + GENERATED_BODY() + +public: + AHoldable(); + + virtual void BeginPlay(); + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + +public: + + UFUNCTION(BlueprintCallable, BlueprintNativeEvent) + void StartPrimaryUse(); + UFUNCTION(BlueprintCallable, BlueprintNativeEvent) + void StopPrimaryUse(); + UFUNCTION(BlueprintCallable, BlueprintNativeEvent) + void StartSecondaryUse(); + UFUNCTION(BlueprintCallable, BlueprintNativeEvent) + void StopSecondaryUse(); + + UFUNCTION(BlueprintCallable, BlueprintNativeEvent) + void ToggleMode(); + + UFUNCTION(BlueprintNativeEvent) + void SetFirstPerson(bool bNewFirstPerson); + + UFUNCTION(BlueprintImplementableEvent) + void OnMetaDataUpdated(); + + void SetMetaData(FGDKMetaData MetaData); + + UFUNCTION(BlueprintNativeEvent) + void SetIsActive(bool bNewActive); + + UFUNCTION(BlueprintPure) + FVector EffectSpawnPoint(); + + UFUNCTION(BlueprintPure) + FName GetActiveSocket() { return ActiveSocket; } + + UFUNCTION(BlueprintCallable) + virtual void ForceCooldown(float Cooldown) {} + +protected: + UPROPERTY(Transient, BlueprintReadOnly) + bool IsPrimaryUsing; + UPROPERTY(Transient, BlueprintReadOnly) + bool IsSecondaryUsing; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + int32 NumberOfModes; + + UPROPERTY(Replicated, BlueprintReadWrite) + int32 CurrentMode = 0; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + FName ActiveSocket = "Gun_Transform"; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + FName EffectSocketName = FName(TEXT("WP_Barrel")); + + USceneComponent* LocationComponent; + + // Visible mesh. + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapons", meta = (AllowPrivateAccess = "true")) + class USkeletalMeshComponent* Mesh; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_MetaData) + FGDKMetaData MetaData; + + UFUNCTION() + void OnRep_MetaData(); + + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly) + bool bVisibleWhenInactive; + UPROPERTY(BlueprintReadOnly, EditDefaultsOnly) + bool bCanBeVisibleWhenInactive; + + UPROPERTY(BlueprintReadOnly) + bool bIsFirstPerson; + + UPROPERTY(BlueprintReadOnly) + bool bIsActive; + + virtual void OnRep_AttachmentReplication() override {} +}; diff --git a/Game/Source/GDKShooter/Public/Weapons/ITraceProvider.h b/Game/Source/GDKShooter/Public/Weapons/ITraceProvider.h new file mode 100644 index 00000000..7652179c --- /dev/null +++ b/Game/Source/GDKShooter/Public/Weapons/ITraceProvider.h @@ -0,0 +1,23 @@ +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" + +#include "ITraceProvider.generated.h" + +UINTERFACE(BlueprintType) +class GDKSHOOTER_API UTraceProvider : public UInterface +{ + GENERATED_BODY() +}; + +class GDKSHOOTER_API ITraceProvider +{ + GENERATED_BODY() +public: + + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Trace Provider") + FVector GetLineTraceStart() const; + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Trace Provider") + FVector GetLineTraceDirection() const; +}; diff --git a/Game/Source/GDKShooter/Public/Weapons/InstantWeapon.h b/Game/Source/GDKShooter/Public/Weapons/InstantWeapon.h index 2393fa74..85bf9eaf 100644 --- a/Game/Source/GDKShooter/Public/Weapons/InstantWeapon.h +++ b/Game/Source/GDKShooter/Public/Weapons/InstantWeapon.h @@ -20,21 +20,16 @@ class GDKSHOOTER_API AInstantWeapon : public AWeapon public: AInstantWeapon(); - virtual void StartFire() override; - - virtual void StopFire() override; + virtual void StartPrimaryUse_Implementation() override; + virtual void StopPrimaryUse_Implementation() override; // RPC for telling the server that we fired and hit something. UFUNCTION(Server, Reliable, WithValidation) void ServerDidHit(const FInstantHitInfo& HitInfo); - bool ServerDidHit_Validate(const FInstantHitInfo& HitInfo); - void ServerDidHit_Implementation(const FInstantHitInfo& HitInfo); // RPC for telling the server that we fired and did not hit anything. UFUNCTION(Server, Unreliable, WithValidation) void ServerDidMiss(const FInstantHitInfo& HitInfo); - bool ServerDidMiss_Validate(const FInstantHitInfo& HitInfo); - void ServerDidMiss_Implementation(const FInstantHitInfo& HitInfo); UFUNCTION(BlueprintImplementableEvent, Category = "Weapons") void OnRenderShot(const FVector Location, bool bImpact); @@ -42,13 +37,12 @@ class GDKSHOOTER_API AInstantWeapon : public AWeapon UFUNCTION(BlueprintImplementableEvent) void FinishedBurst(); + virtual void SetIsActive(bool bNewActive) override; + protected: - virtual void BeginPlay() override; - virtual void EndPlay(const EEndPlayReason::Type Reason) override; - virtual void Tick(float DeltaTime) override; // [client] Runs a line trace and triggers the server RPC for hits. - virtual void DoFire(); + virtual void DoFire_Implementation() override; virtual FVector GetLineTraceDirection() override; @@ -69,15 +63,6 @@ class GDKSHOOTER_API AInstantWeapon : public AWeapon // [client] Clears the NextShotTimer if it's running. void ClearTimerIfRunning(); - // [client] Actually stops the weapon firing. - void StopFiring(); - - void ActuallyStartFiring(); - - bool ReadyToStartFiring(); - - bool ReadyToFire(); - // Notifies all clients that a shot hit something. Used for visualizing shots on the client. UFUNCTION(NetMulticast, Unreliable) void MulticastNotifyHit(FInstantHitInfo HitInfo, bool bImpact); @@ -103,28 +88,18 @@ class GDKSHOOTER_API AInstantWeapon : public AWeapon UPROPERTY(EditAnywhere, Category = "Weapons") float ShotInterval; + UPROPERTY(EditAnywhere, Category = "Weapons") + bool bAllowContinuousBurstFire = false; + // Number of shots in a single burst. // 0 = full-auto // 1 = single-shot // >1 = burst fire UPROPERTY(EditAnywhere, Category = "Weapons") int32 BurstCount; - - // Amount of time to buffer a shot for if fired just before off cooldown - UPROPERTY(EditAnywhere, Category = "Weapons") - float ShotInputLeniancy; - - // Time for which we have a shot buffered - float ShotBufferedUntil; - - // If we have a shot buffered - bool ShotBuffered; - - // Time (in seconds since start of level) since the last burst fire. Used for limiting fire rate. - float LastBurstTime; - - // Time (in seconds since start of level) since the last shot fired. Used for limiting fire rate. - float LastShotTime; + + // Time of the next allowed burst, based on last burst start + burst interval. + float NextBurstTime; // Number of shots remaining in the current burst. int32 BurstShotsRemaining; diff --git a/Game/Source/GDKShooter/Public/Weapons/Projectile.h b/Game/Source/GDKShooter/Public/Weapons/Projectile.h index 74ee0937..ee5dceea 100644 --- a/Game/Source/GDKShooter/Public/Weapons/Projectile.h +++ b/Game/Source/GDKShooter/Public/Weapons/Projectile.h @@ -6,7 +6,6 @@ #include "GameFramework/Actor.h" #include "GameFramework/ProjectileMovementComponent.h" #include "Components/SphereComponent.h" -#include "Characters/Core/GDKCharacter.h" #include "Weapons/Weapon.h" #include "Projectile.generated.h" @@ -24,7 +23,7 @@ class GDKSHOOTER_API AProjectile : public AActor virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; - void SetPlayer(AGDKCharacter* Character, AWeapon* Weapon); + void SetPlayer(AWeapon* Weapon); UPROPERTY(VisibleAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_MetaData) FGDKMetaData MetaData; @@ -32,6 +31,9 @@ class GDKSHOOTER_API AProjectile : public AActor UFUNCTION(BlueprintImplementableEvent) void OnMetaDataUpdated(); + UPROPERTY(Handover) + AWeapon* InstigatingWeapon; + protected: virtual void PostNetReceiveVelocity(const FVector& NewVelocity) override; @@ -41,6 +43,17 @@ class GDKSHOOTER_API AProjectile : public AActor UFUNCTION() virtual void OnBounce(const FHitResult& ImpactResult, const FVector& ImpactVelocity); + UFUNCTION() + void BeginOverlap(UPrimitiveComponent* OverlappedComponent, + AActor* OtherActor, + UPrimitiveComponent* OtherComp, + int32 OtherBodyIndex, + bool bFromSweep, + const FHitResult &SweepResult); + + UFUNCTION(BlueprintNativeEvent) + void OverlapPawn(APawn* Pawn); + // If doesn't explode on stop, will explode on timer // Later we can add proximity mines etc. UPROPERTY(EditDefaultsOnly, Category = Projectile) @@ -75,9 +88,8 @@ class GDKSHOOTER_API AProjectile : public AActor UFUNCTION(BlueprintNativeEvent) void ExplosionVisuals(); - AController* InstigatingController; - - AWeapon* InstigatingWeapon; + UPROPERTY(Handover) + AController* InstigatingController; UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = Projectile) float ExplosionDamage = 50; @@ -98,9 +110,9 @@ class GDKSHOOTER_API AProjectile : public AActor UPROPERTY(EditAnywhere, Category = Projectile) TSubclassOf DamageTypeClass; - UPROPERTY(BlueprintReadOnly, Category = Projectile) + UPROPERTY(Category = Projectile, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) UProjectileMovementComponent* MovementComp; - UPROPERTY(BlueprintReadOnly, Category = Projectile) + UPROPERTY(Category = Projectile, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) USphereComponent* CollisionComp; }; diff --git a/Game/Source/GDKShooter/Public/Weapons/ProjectileWeapon.h b/Game/Source/GDKShooter/Public/Weapons/ProjectileWeapon.h index faf198a9..c241292d 100644 --- a/Game/Source/GDKShooter/Public/Weapons/ProjectileWeapon.h +++ b/Game/Source/GDKShooter/Public/Weapons/ProjectileWeapon.h @@ -16,11 +16,11 @@ class GDKSHOOTER_API AProjectileWeapon : public AWeapon public: AProjectileWeapon(); - - virtual void StartFire() override; protected: + virtual void DoFire_Implementation() override; + UFUNCTION(BlueprintImplementableEvent, Category = "Weapons") void OnShot(); @@ -28,18 +28,19 @@ class GDKSHOOTER_API AProjectileWeapon : public AWeapon UPROPERTY(EditAnywhere, Category = "Weapons") float ShotCooldown; - // Projectile class to fire. UPROPERTY(EditAnywhere, Category = "Weapons") + bool bAllowContinuousFire = false; + + // Projectile class to fire. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapons") TSubclassOf ProjectileClass; // Socket name of where to spawn projectiles UPROPERTY(EditAnywhere, Category = "Weapons") FName BarrelSocket = FName(TEXT("WP_Barrel")); - // Time (in seconds since start of level) of the last shot. Used for limiting fire rate. - float LastShotTime; - UFUNCTION(reliable, server, WithValidation) void FireProjectile(FVector Origin, FVector_NetQuantizeNormal Direction); - + + virtual void ConsumeBufferedShot() override; }; diff --git a/Game/Source/GDKShooter/Public/Weapons/Weapon.h b/Game/Source/GDKShooter/Public/Weapons/Weapon.h index e454e5f8..4417d4cf 100644 --- a/Game/Source/GDKShooter/Public/Weapons/Weapon.h +++ b/Game/Source/GDKShooter/Public/Weapons/Weapon.h @@ -3,80 +3,29 @@ #pragma once #include "CoreMinimal.h" -#include "Game/GDKMetaData.h" +#include "Camera/CameraShake.h" +#include "Components/GDKMovementComponent.h" +#include "Components/ShootingComponent.h" #include "GameFramework/Actor.h" +#include "Holdable.h" #include "Materials/Material.h" +#include "TimerManager.h" #include "Weapon.generated.h" -USTRUCT() -struct FInstantHitInfo -{ - GENERATED_USTRUCT_BODY() - - // Location of the hit in world space. - UPROPERTY() - FVector Location; - - // Actor that was hit, or nullptr if nothing was hit. - UPROPERTY() - AActor* HitActor; - - FInstantHitInfo() : - Location(FVector{ 0,0,0 }), - HitActor(nullptr) - {} -}; - -enum class EWeaponState : uint8 -{ - Idle, - Firing -}; - -// Tag for weapon line trace visualization. -const FName kTraceTag("GDKTrace"); - -DECLARE_DELEGATE_OneParam(FShotDelegate, bool); UCLASS(Abstract) -class GDKSHOOTER_API AWeapon : public AActor +class GDKSHOOTER_API AWeapon : public AHoldable { GENERATED_BODY() public: AWeapon(); - virtual void Tick(float DeltaSeconds) override; + virtual void Tick(float DeltaTime) override; - // [client] Starts firing the weapon. - virtual void StartFire() PURE_VIRTUAL(AWeapon::StartFire,); + virtual void StartSecondaryUse_Implementation() override; + virtual void StopSecondaryUse_Implementation() override; - // [client] Stops firing the weapon. - virtual void StopFire(); - - // [client] Fire the weapon if not on cooldown. - virtual void TryShooting() PURE_VIRTUAL(AWeapon::TryShooting, ); - - class AGDKShooterCharacter* GetOwningCharacter() const; - void SetOwningCharacter(class AGDKShooterCharacter* NewCharacter); - - const AActor* GetWeilder() const { return GetAttachmentReplication().AttachParent; } - - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_MetaData) - FGDKMetaData MetaData; - - UFUNCTION(BlueprintImplementableEvent) - void OnMetaDataUpdated(); - - void SetMetaData(FGDKMetaData MetaData); - - void EnableShadows(bool bShadows); - - void SetIsActive(bool bNewValue) { bIsActive = bNewValue; } - - UFUNCTION(BlueprintCallable, Category = "Weapons") - bool IsFirstPerson(); - - void SetFirstPerson(bool bFirstPerson); + virtual void SetIsActive(bool bNewActive); UPROPERTY(EditAnywhere, BlueprintReadOnly) float AimingFoV = 55; @@ -89,8 +38,8 @@ class GDKSHOOTER_API AWeapon : public AActor UPROPERTY(EditAnywhere, BlueprintReadOnly) FVector2D ReticleSize = FVector2D(16, 16); - void AddShotListener(FShotDelegate Listener); - void RemoveShotListener(); + + virtual void ForceCooldown(float Cooldown) override; //Recoil applied as input to the CharacterController rotations UPROPERTY(EditAnywhere, BlueprintReadOnly) @@ -117,72 +66,50 @@ class GDKSHOOTER_API AWeapon : public AActor UPROPERTY(EditAnywhere, BlueprintReadOnly) TSubclassOf RecoilShake; - UFUNCTION(BlueprintCallable) - FVector BulletSpawnPoint(); + virtual void StartPrimaryUse_Implementation() override; + virtual void StopPrimaryUse_Implementation() override; protected: - // Called when the game starts or when spawned - virtual void BeginPlay() override; - virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; - - EWeaponState GetWeaponState() const; - void SetWeaponState(EWeaponState NewState); - - UFUNCTION() - virtual void OnRep_IsActive(); + + UFUNCTION(BlueprintNativeEvent) + void DoFire(); + + virtual bool ReadyToFire(); virtual void AnnounceShot(bool bHit); - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) - FName BarrelSocketName = FName(TEXT("WP_Barrel")); - - // Weapon mesh. - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapons", meta = (AllowPrivateAccess = "true")) - class USkeletalMeshComponent* Mesh; - virtual FVector GetLineTraceDirection(); - // [client] Performs a line trace and populates OutHitInfo based on the results. - // Returns true if it hits anything, false otherwise. - bool DoLineTrace(FInstantHitInfo& OutHitInfo); - - UPROPERTY(ReplicatedUsing = OnRep_IsActive) - bool bIsActive; - - // Maximum range of the weapon's hitscan. - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Weapons") - float MaxRange; + UFUNCTION(BlueprintPure) + UGDKMovementComponent* GetMovementComponent(); + UFUNCTION(BlueprintPure) + UShootingComponent* GetShootingComponent(); + + UFUNCTION(BlueprintPure) + FInstantHitInfo DoLineTrace(); + + // Time that we are next able to shoot + float NextShotTime; + // Buffered shots are for when e.g. people double click just slightly faster than the RoF + // or a single shot that needs to wait for the sprint cooldown + float BufferedShotUntil; + float BufferShotThreshold; + bool bHasBufferedShot; + bool HasBufferedShot(); + bool BufferedShotStillValid(); + virtual void ConsumeBufferedShot(); private: - bool bHasAttached; - - void TryToAttach(); - - EWeaponState CurrentState; - - bool bFirstPerson; - - FShotDelegate ShotCallback; - - // Set up a root component so this actor can have a position in the world. - class USceneComponent* LocationComponent; - - // Character that currently owns this weapon. - UPROPERTY(Replicated) - class AGDKShooterCharacter* OwningCharacter; - + UPROPERTY() + AActor* CachedOwner; + UPROPERTY() + UGDKMovementComponent* CachedMovementComponent; + UPROPERTY() + UShootingComponent* CachedShootingComponent; UFUNCTION() - void OnRep_MetaData(); - - // Channel to use for raytrace on shot - UPROPERTY(EditAnywhere, Category = "Weapons") - TEnumAsByte TraceChannel = ECC_WorldStatic; - - // If true, draws debug line traces for hitscan shots. - UPROPERTY(EditAnywhere, Category = "Weapons") - bool bDrawDebugLineTrace; + void RefreshComponentCache(); }; diff --git a/LaunchClient.bat b/LaunchClient.bat index 9b82b3b6..aa0b8bd2 100644 --- a/LaunchClient.bat +++ b/LaunchClient.bat @@ -1,3 +1,4 @@ @echo off +call "%~dp0FindEngine.bat" call "%~dp0ProjectPaths.bat" -"%UNREAL_HOME%\Engine\Binaries\Win64\UE4Editor.exe" "%~dp0%PROJECT_PATH%\%GAME_NAME%.uproject" 127.0.0.1 -game -log -workerType UnrealClient -stdout -nowrite -unattended -nologtimes -nopause -noin -messaging -NoVerifyGC -windowed -ResX=800 -ResY=600 +"%UNREAL_ENGINE:"=%\Engine\Binaries\Win64\UE4Editor.exe" "%~dp0%PROJECT_PATH%\%GAME_NAME%.uproject" 127.0.0.1 -game -log -workerType UnrealClient -stdout -nowrite -unattended -nologtimes -nopause -noin -messaging -NoVerifyGC -windowed -ResX=800 -ResY=600 diff --git a/LaunchServer.bat b/LaunchServer.bat index 702cdb6c..415541ac 100644 --- a/LaunchServer.bat +++ b/LaunchServer.bat @@ -1,3 +1,4 @@ @echo off +call "%~dp0FindEngine.bat" call "%~dp0ProjectPaths.bat" -"%UNREAL_HOME%\Engine\Binaries\Win64\UE4Editor.exe" "%~dp0%PROJECT_PATH%\%GAME_NAME%.uproject" FPS-Start_Medium -server -log -workerType UnrealWorker -stdout -nowrite -unattended -nologtimes -nopause -noin -messaging -SaveToUserDir -NoVerifyGC -windowed -resX=400 -resY=300 +"%UNREAL_ENGINE:"=%\Engine\Binaries\Win64\UE4Editor.exe" "%~dp0%PROJECT_PATH%\%GAME_NAME%.uproject" -server -log -workerType UnrealWorker -stdout -nowrite -unattended -nologtimes -nopause -noin -messaging -SaveToUserDir -NoVerifyGC -windowed -resX=400 -resY=300 diff --git a/spatial/default_launch.json b/spatial/default_launch.json index 159c9ee6..84be46d1 100644 --- a/spatial/default_launch.json +++ b/spatial/default_launch.json @@ -1,5 +1,5 @@ { - "template": "small", + "template": "w4_r1000_e10", "world": { "dimensions": { "x_meters": 2000, @@ -8,10 +8,6 @@ "chunk_edge_length_meters": 50, "streaming_query_interval": 4, "legacy_flags": [ - { - "name": "streaming_query_diff", - "value": "true" - }, { "name": "bridge_qos_max_timeout", "value": "0" @@ -21,8 +17,8 @@ "value": "false" }, { - "name": "qos_max_unacked_pings_rate", - "value": "10" + "name": "enable_chunk_interest", + "value": "false" } ], "snapshots": { diff --git a/spatial/one_worker_test.json b/spatial/one_worker_test.json index f88c5036..d6522819 100644 --- a/spatial/one_worker_test.json +++ b/spatial/one_worker_test.json @@ -1,5 +1,5 @@ { - "template": "small", + "template": "w4_r1000_e10", "world": { "chunkEdgeLengthMeters": 50, "snapshots": { diff --git a/spatial/spatialos.json b/spatial/spatialos.json index 950563d7..5ebacfa2 100644 --- a/spatial/spatialos.json +++ b/spatial/spatialos.json @@ -1,11 +1,11 @@ { "name": "your_project_name_here", "project_version": "0.0.1", - "sdk_version": "13.7.1", + "sdk_version": "13.8.1", "dependencies": [ { "name": "standard_library", - "version": "13.7.1" + "version": "13.8.1" } ] } diff --git a/spatial/workers/improbable/spatialos.SimulatedPlayerCoordinator.worker.json b/spatial/workers/improbable/spatialos.SimulatedPlayerCoordinator.worker.json new file mode 100644 index 00000000..277201c4 --- /dev/null +++ b/spatial/workers/improbable/spatialos.SimulatedPlayerCoordinator.worker.json @@ -0,0 +1,65 @@ +{ + "build": { + "tasks": [ + { + "name": "codegen", + "description": "required by spatial worker build build-config.", + "steps": [{"name": "No-op", "command": "echo", "arguments": ["No-op."]}] + }, + { + "name": "build", + "description": "required by spatial worker build build-config.", + "steps": [{"name": "No-op", "command": "echo", "arguments": ["No-op."]}] + }, + { + "name": "clean", + "description": "required by spatial worker build build-config.", + "steps": [{"name": "No-op", "command": "echo", "arguments": ["No-op."]}] + } + ] + }, + "bridge": { + "worker_attribute_set": { + "attributes": [ + "SimulatedPlayerCoordinator" + ] + }, + "entity_interest": { + "range_entity_interest": { + "radius": 50 + } + }, + "component_delivery": { + "default": "RELIABLE_ORDERED", + "checkout_all_initially": true + } + }, + "managed": { + "linux": { + "artifact_name": "UnrealSimulatedPlayer@Linux.zip", + "command": "StartCoordinator.sh", + "arguments": [ + "receptionist", + "${IMPROBABLE_RECEPTIONIST_HOST}", + "${IMPROBABLE_RECEPTIONIST_PORT}", + "${IMPROBABLE_WORKER_ID}", + "coordinator_start_delay_millis=10000", + + "+workerType", + "UnrealClient", + "+loginToken", + "", + "+playerIdentityToken", + "", + "+workerId", + "", + "+useExternalIpForBridge", + "true", + "-abslog=${IMPROBABLE_LOG_FILE}", + "-NoVerifyGC", + "-nullRHI", + "-simulatedplayer" + ] + } + } +} diff --git a/spatial/workers/unreal/spatialos.AIWorker.worker.json b/spatial/workers/unreal/spatialos.AIWorker.worker.json new file mode 100644 index 00000000..b6a4c824 --- /dev/null +++ b/spatial/workers/unreal/spatialos.AIWorker.worker.json @@ -0,0 +1,103 @@ +{ + "build": { + "tasks": [ + { + "name": "codegen", + "description": "required by spatial worker build build-config.", + "steps": [{"name": "No-op", "command": "echo", "arguments": ["No-op."]}] + }, + { + "name": "build", + "description": "required by spatial worker build build-config.", + "steps": [{"name": "No-op", "command": "echo", "arguments": ["No-op."]}] + }, + { + "name": "clean", + "description": "required by spatial worker build build-config.", + "steps": [{"name": "No-op", "command": "echo", "arguments": ["No-op."]}] + } + ] + }, + "bridge": { + "worker_attribute_set": { + "attributes": [ + "AIWorker" + ] + }, + "entity_interest": { + "range_entity_interest": { + "radius": 50 + } + }, + "streaming_query": [ + { + "global_component_streaming_query": { + "component_name": "unreal.SingletonManager" + } + }, + { + "global_component_streaming_query": { + "component_name": "unreal.Singleton" + } + } + ], + "component_delivery": { + "default": "RELIABLE_ORDERED", + "checkout_all_initially": true + } + }, + "managed": { + "windows": { + "artifact_name": "UnrealEditor@Windows.zip", + "command": "StartEditor.bat", + "arguments": [ + "ThirdPersonExampleMap", + "-server", + "-stdout", + "-nowrite", + "-unattended", + "-nologtimes", + "-nopause", + "-messaging", + "-SaveToUserDir", + "+appName", + "${IMPROBABLE_PROJECT_NAME}", + "+receptionistHost", + "${IMPROBABLE_RECEPTIONIST_HOST}", + "+receptionistPort", + "${IMPROBABLE_RECEPTIONIST_PORT}", + "+workerType", + "${IMPROBABLE_WORKER_NAME}", + "+workerId", + "${IMPROBABLE_WORKER_ID}", + "+linkProtocol", + "Tcp", + "-abslog=${IMPROBABLE_LOG_FILE}", + "-NoVerifyGC" + ] + }, + "linux": { + "artifact_name": "UnrealWorker@Linux.zip", + "command": "StartWorker.sh", + "arguments": [ + "${IMPROBABLE_WORKER_ID}", + "${IMPROBABLE_LOG_FILE}", + "ThirdPersonExampleMap", + "+appName", + "${IMPROBABLE_PROJECT_NAME}", + "+receptionistHost", + "${IMPROBABLE_RECEPTIONIST_HOST}", + "+receptionistPort", + "${IMPROBABLE_RECEPTIONIST_PORT}", + "+workerType", + "${IMPROBABLE_WORKER_NAME}", + "+workerId", + "${IMPROBABLE_WORKER_ID}", + "+linkProtocol", + "Tcp", + "-NoVerifyGC", + "-OverrideSpatialNetworking" + ] + } + } +} diff --git a/spatial/workers/unreal/spatialos.CrashBotWorker.worker.json b/spatial/workers/unreal/spatialos.CrashBotWorker.worker.json new file mode 100644 index 00000000..179cb622 --- /dev/null +++ b/spatial/workers/unreal/spatialos.CrashBotWorker.worker.json @@ -0,0 +1,103 @@ +{ + "build": { + "tasks": [ + { + "name": "codegen", + "description": "required by spatial worker build build-config.", + "steps": [{"name": "No-op", "command": "echo", "arguments": ["No-op."]}] + }, + { + "name": "build", + "description": "required by spatial worker build build-config.", + "steps": [{"name": "No-op", "command": "echo", "arguments": ["No-op."]}] + }, + { + "name": "clean", + "description": "required by spatial worker build build-config.", + "steps": [{"name": "No-op", "command": "echo", "arguments": ["No-op."]}] + } + ] + }, + "bridge": { + "worker_attribute_set": { + "attributes": [ + "CrashBotWorker" + ] + }, + "entity_interest": { + "range_entity_interest": { + "radius": 50 + } + }, + "streaming_query": [ + { + "global_component_streaming_query": { + "component_name": "unreal.SingletonManager" + } + }, + { + "global_component_streaming_query": { + "component_name": "unreal.Singleton" + } + } + ], + "component_delivery": { + "default": "RELIABLE_ORDERED", + "checkout_all_initially": true + } + }, + "managed": { + "windows": { + "artifact_name": "UnrealEditor@Windows.zip", + "command": "StartEditor.bat", + "arguments": [ + "ThirdPersonExampleMap", + "-server", + "-stdout", + "-nowrite", + "-unattended", + "-nologtimes", + "-nopause", + "-messaging", + "-SaveToUserDir", + "+appName", + "${IMPROBABLE_PROJECT_NAME}", + "+receptionistHost", + "${IMPROBABLE_RECEPTIONIST_HOST}", + "+receptionistPort", + "${IMPROBABLE_RECEPTIONIST_PORT}", + "+workerType", + "${IMPROBABLE_WORKER_NAME}", + "+workerId", + "${IMPROBABLE_WORKER_ID}", + "+linkProtocol", + "Tcp", + "-abslog=${IMPROBABLE_LOG_FILE}", + "-NoVerifyGC" + ] + }, + "linux": { + "artifact_name": "UnrealWorker@Linux.zip", + "command": "StartWorker.sh", + "arguments": [ + "${IMPROBABLE_WORKER_ID}", + "${IMPROBABLE_LOG_FILE}", + "ThirdPersonExampleMap", + "+appName", + "${IMPROBABLE_PROJECT_NAME}", + "+receptionistHost", + "${IMPROBABLE_RECEPTIONIST_HOST}", + "+receptionistPort", + "${IMPROBABLE_RECEPTIONIST_PORT}", + "+workerType", + "${IMPROBABLE_WORKER_NAME}", + "+workerId", + "${IMPROBABLE_WORKER_ID}", + "+linkProtocol", + "Tcp", + "-NoVerifyGC", + "-OverrideSpatialNetworking" + ] + } + } +} diff --git a/spatial/workers/unreal/spatialos.UnrealWorker.worker.json b/spatial/workers/unreal/spatialos.UnrealWorker.worker.json index f8896aa2..c887d063 100644 --- a/spatial/workers/unreal/spatialos.UnrealWorker.worker.json +++ b/spatial/workers/unreal/spatialos.UnrealWorker.worker.json @@ -97,7 +97,8 @@ "${IMPROBABLE_WORKER_ID}", "+linkProtocol", "Tcp", - "-NoVerifyGC" + "-NoVerifyGC", + "-OverrideSpatialNetworking" ] } }