├── .clang-format ├── .gitattributes ├── .gitignore ├── 0001-Make-linux-work-with-WorkerSdk.patch ├── README.md ├── default_launch.json ├── generatesnapshot.bat ├── rpg-demo-logo.jpg ├── schema └── improbable │ ├── player │ └── heartbeat.schema │ └── spawner │ └── spawner.schema ├── snapshots └── default.snapshot ├── spatialos.json └── workers └── unreal ├── .gitignore ├── Game ├── Config │ ├── DefaultEditor.ini │ ├── DefaultEditorPerProjectUserSettings.ini │ ├── DefaultEngine.ini │ ├── DefaultGame.ini │ └── DefaultInput.ini ├── Content │ ├── EntityBlueprints │ │ ├── Npc.uasset │ │ ├── Player.uasset │ │ └── Spawner.uasset │ ├── Geometry │ │ └── Meshes │ │ │ ├── 1M_Cube.uasset │ │ │ ├── 1M_Cube_Chamfer.uasset │ │ │ ├── CubeMaterial.uasset │ │ │ └── TemplateFloor.uasset │ ├── Mannequin │ │ ├── Animations │ │ │ ├── ThirdPersonIdle.uasset │ │ │ ├── ThirdPersonJump_End.uasset │ │ │ ├── ThirdPersonJump_Loop.uasset │ │ │ ├── ThirdPersonJump_Start.uasset │ │ │ ├── ThirdPersonRun.uasset │ │ │ ├── ThirdPersonWalk.uasset │ │ │ ├── ThirdPerson_AnimBP.uasset │ │ │ ├── ThirdPerson_IdleRun_2D.uasset │ │ │ └── ThirdPerson_Jump.uasset │ │ └── Character │ │ │ ├── Materials │ │ │ ├── M_UE4Man_Body.uasset │ │ │ ├── M_UE4Man_ChestLogo.uasset │ │ │ └── MaterialLayers │ │ │ │ ├── ML_GlossyBlack_Latex_UE4.uasset │ │ │ │ ├── ML_Plastic_Shiny_Beige.uasset │ │ │ │ ├── ML_Plastic_Shiny_Beige_LOGO.uasset │ │ │ │ ├── ML_SoftMetal_UE4.uasset │ │ │ │ ├── T_ML_Aluminum01.uasset │ │ │ │ ├── T_ML_Aluminum01_N.uasset │ │ │ │ ├── T_ML_Rubber_Blue_01_D.uasset │ │ │ │ └── T_ML_Rubber_Blue_01_N.uasset │ │ │ ├── Mesh │ │ │ ├── SK_Mannequin.uasset │ │ │ ├── SK_Mannequin_PhysicsAsset.uasset │ │ │ └── UE4_Mannequin_Skeleton.uasset │ │ │ └── Textures │ │ │ ├── UE4Man_Logo_N.uasset │ │ │ ├── UE4_LOGO_CARD.uasset │ │ │ ├── UE4_Mannequin_MAT_MASKA.uasset │ │ │ └── UE4_Mannequin__normals.uasset │ └── TopDownCPP │ │ ├── Blueprints │ │ ├── M_Cursor_Decal.uasset │ │ ├── NpcController.uasset │ │ └── RPGDemoGameMode.uasset │ │ ├── Maps │ │ └── TopDownExampleMap.umap │ │ └── TopDownOverview.uasset ├── RpgDemo.uproject └── Source │ ├── RpgDemo.Target.cs │ ├── RpgDemo │ ├── ExportSnapshotCommandlet.cpp │ ├── ExportSnapshotCommandlet.h │ ├── OtherPlayerController.cpp │ ├── OtherPlayerController.h │ ├── RPGDemoGameInstance.cpp │ ├── RPGDemoGameInstance.h │ ├── RpgDemo.Build.cs │ ├── RpgDemo.cpp │ ├── RpgDemo.h │ ├── RpgDemoCharacter.cpp │ ├── RpgDemoCharacter.h │ ├── RpgDemoGameMode.cpp │ ├── RpgDemoGameMode.h │ ├── RpgDemoPlayerController.cpp │ └── RpgDemoPlayerController.h │ ├── RpgDemoEditor.Target.cs │ └── RpgDemoServer.Target.cs ├── spatialos.UnrealClient.worker.json └── spatialos.UnrealWorker.worker.json /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: Google 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: true 9 | AlignOperands: false 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: Empty 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakBeforeMultilineStrings: true 19 | AlwaysBreakTemplateDeclarations: true 20 | BinPackArguments: true 21 | BinPackParameters: true 22 | BreakBeforeBinaryOperators: None 23 | BreakBeforeBraces: Allman 24 | BreakBeforeTernaryOperators: true 25 | BreakConstructorInitializersBeforeComma: true 26 | ColumnLimit: 100 27 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 28 | ConstructorInitializerIndentWidth: 0 29 | ContinuationIndentWidth: 4 30 | Cpp11BracedListStyle: true 31 | DerivePointerAlignment: false 32 | DisableFormat: false 33 | ExperimentalAutoDetectBinPacking: false 34 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 35 | IncludeCategories: 36 | # Unreal requires ".generated.h" files to be the last includes in any header file 37 | - Regex: '.*\.generated\.h' 38 | Priority: 100 39 | - Regex: '.*(PCH).*' 40 | Priority: -1 41 | - Regex: 'Sdk.h' 42 | Priority: -1 43 | - Regex: '".*"' 44 | Priority: 1 45 | - Regex: '^' 48 | Priority: 3 49 | - Regex: '^<.*>' 50 | Priority: 4 51 | IndentCaseLabels: true 52 | IndentWidth: 4 53 | IndentWrappedFunctionNames: false 54 | KeepEmptyLinesAtTheStartOfBlocks: false 55 | MacroBlockBegin: '' 56 | MacroBlockEnd: '' 57 | MaxEmptyLinesToKeep: 1 58 | NamespaceIndentation: None 59 | PenaltyBreakBeforeFirstCallParameter: 10 60 | PenaltyBreakComment: 400 61 | PenaltyBreakFirstLessLess: 200 62 | PenaltyBreakString: 1000 63 | PenaltyExcessCharacter: 1000000 64 | PenaltyReturnTypeOnItsOwnLine: 50 65 | SortIncludes: true 66 | PointerAlignment: Left 67 | SpaceAfterCStyleCast: false 68 | SpaceBeforeAssignmentOperators: true 69 | SpaceBeforeParens: ControlStatements 70 | SpaceInEmptyParentheses: false 71 | SpacesBeforeTrailingComments: 2 72 | SpacesInAngles: false 73 | SpacesInCStyleCastParentheses: false 74 | SpacesInParentheses: false 75 | SpacesInSquareBrackets: false 76 | Standard: Cpp11 77 | TabWidth: 4 78 | UseTab: Never 79 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.cc text 2 | *.cpp text 3 | *.h text 4 | *.json text 5 | *.md text 6 | *.txt text 7 | *.uproject text 8 | *.bat text 9 | *.c text 10 | *.cxx text 11 | *.o -text 12 | *.lib -text 13 | *.a -text 14 | *.dll -text 15 | *.exe -text 16 | *.cab -text 17 | *.cat -text 18 | *.sys -text 19 | *.so -text 20 | *.in text 21 | *.am text 22 | *.m4 text 23 | *.ac text 24 | *.proj text 25 | *.sln text 26 | Makefile text 27 | *.cs text 28 | *.py text 29 | *.ini text 30 | *.schema text 31 | *.uproject text -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | toolbelt.log 2 | build/ 3 | .idea/ 4 | 5 | /.spatialos 6 | 7 | ## Ignore the generated gsim build file 8 | spatialos.gsim.build.json 9 | 10 | ## Ignore schema.iml files 11 | /schema/schema.iml 12 | 13 | ## Ignore ./logs folder 14 | /logs 15 | 16 | ## Ignore spatial.log files 17 | spatial.log 18 | 19 | ## Ignore zip_toolbelt.log files 20 | *zip_toolbelt.log 21 | 22 | ## Ignore spatialOs generated folder 23 | workers/unreal/Game/Source/SpatialOS/ 24 | -------------------------------------------------------------------------------- /0001-Make-linux-work-with-WorkerSdk.patch: -------------------------------------------------------------------------------- 1 | diff --git a/Engine/Source/Programs/UnrealBuildTool/Linux/LinuxToolChain.cs b/Engine/Source/Programs/UnrealBuildTool/Linux/LinuxToolChain.cs 2 | index a994f8b5ce..2fc97bdbda 100644 3 | --- a/Engine/Source/Programs/UnrealBuildTool/Linux/LinuxToolChain.cs 4 | +++ b/Engine/Source/Programs/UnrealBuildTool/Linux/LinuxToolChain.cs 5 | @@ -304,8 +304,11 @@ namespace UnrealBuildTool 6 | 7 | private static bool ShouldUseLibcxx(string Architecture) 8 | { 9 | - // set UE4_LINUX_USE_LIBCXX to either 0 or 1. If unset, defaults to 1. 10 | - string UseLibcxxEnvVarOverride = Environment.GetEnvironmentVariable("UE4_LINUX_USE_LIBCXX"); 11 | + // Improbable: it's not possible to disable libc++ via the UE4_LINUX_USE_LIBCXX variable, so disable it fully. 12 | + return false; 13 | + /* 14 | + // set UE4_LINUX_USE_LIBCXX to either 0 or 1. If unset, defaults to 1. 15 | + string UseLibcxxEnvVarOverride = Environment.GetEnvironmentVariable("UE4_LINUX_USE_LIBCXX"); 16 | if (UseLibcxxEnvVarOverride != null && (UseLibcxxEnvVarOverride == "1")) 17 | { 18 | return true; 19 | @@ -313,6 +316,7 @@ namespace UnrealBuildTool 20 | 21 | // at the moment only x86_64 is supported 22 | return Architecture.StartsWith("x86_64") || Architecture.StartsWith("aarch64"); 23 | + */ 24 | } 25 | 26 | static string GetCLArguments_Global(CPPEnvironment CompileEnvironment) 27 | diff --git a/Engine/Source/Runtime/Core/Private/HAL/MallocBinned.cpp b/Engine/Source/Runtime/Core/Private/HAL/MallocBinned.cpp 28 | index 9c4235ff92..583251530e 100644 29 | --- a/Engine/Source/Runtime/Core/Private/HAL/MallocBinned.cpp 30 | +++ b/Engine/Source/Runtime/Core/Private/HAL/MallocBinned.cpp 31 | @@ -430,7 +430,7 @@ struct FMallocBinned::Private 32 | 33 | UPTRINT BasePtr; 34 | FPoolInfo* Pool = FindPoolInfo(Allocator, (UPTRINT)Ptr, BasePtr); 35 | -#if PLATFORM_IOS || PLATFORM_MAC 36 | +#if PLATFORM_IOS || PLATFORM_MAC || PLATFORM_LINUX 37 | if (Pool == NULL) 38 | { 39 | UE_LOG(LogMemory, Warning, TEXT("Attempting to free a pointer we didn't allocate!")); 40 | @@ -1061,7 +1061,7 @@ bool FMallocBinned::GetAllocationSize(void *Original, SIZE_T &SizeOut) 41 | UPTRINT BasePtr; 42 | FPoolInfo* Pool = Private::FindPoolInfo(*this, (UPTRINT)Original, BasePtr); 43 | 44 | -#if PLATFORM_IOS || PLATFORM_MAC 45 | +#if PLATFORM_IOS || PLATFORM_MAC || PLATFORM_LINUX 46 | if (Pool == NULL) 47 | { 48 | UE_LOG(LogMemory, Warning, TEXT("Attempting to access memory pool info for a pointer we didn't allocate!")); 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RPG demo (SpatialOS) 2 | 3 | ![RPG demo Logo](rpg-demo-logo.jpg) 4 | 5 | ***** 6 | 7 | Copyright Improbable 2017 8 | 9 | * Link to docs: [SpatialOS Unreal Engine 4 integration documentation](https://spatialos.improbable.io/docs/reference/latest/workers/unreal/introduction) 10 | 11 | ***** 12 | 13 | ### Deprecation notice 14 | 15 | As from the release of SpatialOS 12.0.0, we're no longer maintaining the RPG demo. Alternative starter projects and demos can be found [here](https://spatialos.improbable.io/docs/reference/latest/). 16 | 17 | ### Introduction 18 | 19 | This repository contains the SpatialOS project "RPG demo" built with the [Unreal Engine 4](https://www.unrealengine.com/) integration for [SpatialOS](http://www.spatialos.com). 20 | It's an example of a [SpatialOS](http://www.spatialos.com) project that uses [Unreal Engine 4](https://www.unrealengine.com/) as a worker. 21 | 22 | For instructions on how to get started with the RPG demo, and general information on using SpatialOS with Unreal, see 23 | the [SpatialOS Unreal intgration documentation](https://spatialos.improbable.io/docs/reference/latest/workers/unreal/introduction). 24 | 25 | The main documentation for [SpatialOS](http://www.spatialos.com) can be found [here](https://spatialos.improbable.io/docs/reference/latest/). 26 | 27 | #### To use the repository 28 | 29 | For detailed instructions on how to build and run this demo, see the [Setting up the example project](https://spatialos.improbable.io/docs/reference/latest/workers/unreal/setup-example-project) 30 | page in the SpatialOS documentation. -------------------------------------------------------------------------------- /default_launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "small", 3 | "world": { 4 | "dimensions": { 5 | "x_meters": 5000, 6 | "z_meters": 5000 7 | }, 8 | "chunk_edge_length_meters": 100, 9 | "streaming_query_interval": 4, 10 | "snapshots": { 11 | "snapshot_write_period_seconds": 0 12 | }, 13 | "legacy_flags": [ 14 | { 15 | "name": "enable_grpc_receptionist_replacement", 16 | "value": "true" 17 | } 18 | ] 19 | }, 20 | "workers": [ 21 | { 22 | "worker_type": "UnrealWorker", 23 | "flags": [], 24 | "load_balancing": { 25 | "auto_hex_grid": { 26 | "num_workers": 2 27 | } 28 | }, 29 | "permissions": [ 30 | { 31 | "all": {} 32 | } 33 | ] 34 | }, 35 | { 36 | "worker_type": "UnrealClient", 37 | "flags": [], 38 | "load_balancing": { 39 | "singleton_worker": {} 40 | }, 41 | "permissions": [ 42 | { 43 | "all": {} 44 | } 45 | ] 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /generatesnapshot.bat: -------------------------------------------------------------------------------- 1 | "%UNREAL_HOME%/Engine/Binaries/Win64/UE4Editor-Cmd.exe" "%~dp0/workers/unreal/Game/RpgDemo.uproject" -run=ExportSnapshotCommandlet -------------------------------------------------------------------------------- /rpg-demo-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/rpg-demo-logo.jpg -------------------------------------------------------------------------------- /schema/improbable/player/heartbeat.schema: -------------------------------------------------------------------------------- 1 | package improbable.player; 2 | 3 | type HeartbeatRequest 4 | { 5 | } 6 | 7 | type HeartbeatResponse 8 | { 9 | } 10 | 11 | component HeartbeatReceiver 12 | { 13 | id = 21001; 14 | 15 | command HeartbeatResponse heartbeat(HeartbeatRequest); 16 | } 17 | 18 | component HeartbeatSender 19 | { 20 | id = 21002; 21 | } -------------------------------------------------------------------------------- /schema/improbable/spawner/spawner.schema: -------------------------------------------------------------------------------- 1 | package improbable.spawner; 2 | import "improbable/standard_library.schema"; 3 | 4 | type SpawnPlayerRequest 5 | { 6 | Coordinates position = 1; 7 | } 8 | 9 | type SpawnPlayerResponse 10 | { 11 | bool success = 1; 12 | string error_message = 2; 13 | EntityId created_entity_id = 3; 14 | } 15 | 16 | component Spawner 17 | { 18 | id = 21010; 19 | 20 | command SpawnPlayerResponse spawn_player(SpawnPlayerRequest); 21 | } -------------------------------------------------------------------------------- /snapshots/default.snapshot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/snapshots/default.snapshot -------------------------------------------------------------------------------- /spatialos.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "your_project_name_here", 3 | "project_version": "0.0.1", 4 | "sdk_version": "12.0.1", 5 | "dependencies": [ 6 | { 7 | "name": "standard_library", 8 | "version": "12.0.1" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /workers/unreal/.gitignore: -------------------------------------------------------------------------------- 1 | # Unreal Ignores 2 | # -------------- 3 | 4 | Engine 5 | 6 | **/Binaries/ 7 | **/Build 8 | **/Intermediate/ 9 | 10 | ## Generated by the Editor 11 | **/Saved/ 12 | 13 | ## Generated by the engine 14 | *.xcodeproj 15 | *.sln 16 | *.VC.db 17 | *.VC.VC.opendb 18 | .vs 19 | **/DerivedDataCache/ 20 | 21 | # SpatialOS Ignores 22 | # ----------------- 23 | 24 | /.spatialos 25 | 26 | 27 | ## Generated by Spatial Codegen 28 | Game/Source/RpgDemo/Improbable 29 | Game/Source/RpgDemo/SpatialOS 30 | Game/Source/RpgDemo/SpatialOSModule.Build.cs 31 | 32 | ## Generated by us, when we're building out workers 33 | temp_worker_builds/ 34 | -------------------------------------------------------------------------------- /workers/unreal/Game/Config/DefaultEditor.ini: -------------------------------------------------------------------------------- 1 | [UnrealEd.SimpleMap] 2 | SimpleMapName=/Game/TopDown/Maps/TopDownExampleMap 3 | 4 | [EditoronlyBP] 5 | bAllowClassAndBlueprintPinMatching=true 6 | bReplaceBlueprintWithClass= true 7 | bDontLoadBlueprintOutsideEditor= true 8 | bBlueprintIsNotBlueprintType= true -------------------------------------------------------------------------------- /workers/unreal/Game/Config/DefaultEditorPerProjectUserSettings.ini: -------------------------------------------------------------------------------- 1 | [ContentBrowser] 2 | ContentBrowserTab1.SelectedPaths=/Game/TopDown -------------------------------------------------------------------------------- /workers/unreal/Game/Config/DefaultEngine.ini: -------------------------------------------------------------------------------- 1 | [/Script/EngineSettings.GameMapsSettings] 2 | GameDefaultMap=/Game/TopDownCPP/Maps/TopDownExampleMap 3 | EditorStartupMap=/Game/TopDownCPP/Maps/TopDownExampleMap 4 | GlobalDefaultGameMode=/Game/TopDownCPP/Blueprints/RPGDemoGameMode.RPGDemoGameMode_C 5 | GlobalDefaultServerGameMode=/Game/TopDownCPP/Blueprints/RPGDemoGameMode.RPGDemoGameMode_C 6 | ServerDefaultMap=/Game/TopDownCPP/Maps/TopDownExampleMap.TopDownExampleMap 7 | GameInstanceClass=/Script/RpgDemo.RPGDemoGameInstance 8 | 9 | [/Script/Engine.Engine] 10 | +ActiveGameNameRedirects=(OldGameName="TP_TopDown",NewGameName="/Script/RpgDemo") 11 | +ActiveGameNameRedirects=(OldGameName="/Script/TP_TopDown",NewGameName="/Script/RpgDemo") 12 | +ActiveClassRedirects=(OldClassName="TP_TopDownPlayerController",NewClassName="RpgDemoPlayerController") 13 | +ActiveClassRedirects=(OldClassName="TP_TopDownGameMode",NewClassName="RpgDemoGameMode") 14 | +ActiveClassRedirects=(OldClassName="TP_TopDownCharacter",NewClassName="RpgDemoCharacter") 15 | 16 | [/Script/HardwareTargeting.HardwareTargetingSettings] 17 | TargetedHardwareClass=Desktop 18 | AppliedTargetedHardwareClass=Desktop 19 | DefaultGraphicsPerformance=Maximum 20 | AppliedDefaultGraphicsPerformance=Maximum 21 | 22 | [/Script/Engine.PhysicsSettings] 23 | DefaultGravityZ=-980.000000 24 | DefaultTerminalVelocity=4000.000000 25 | DefaultFluidFriction=0.300000 26 | SimulateScratchMemorySize=262144 27 | RagdollAggregateThreshold=4 28 | TriangleMeshTriangleMinAreaThreshold=5.000000 29 | bEnableAsyncScene=False 30 | bEnableShapeSharing=False 31 | bEnablePCM=False 32 | bWarnMissingLocks=True 33 | bEnable2DPhysics=False 34 | LockedAxis=Invalid 35 | DefaultDegreesOfFreedom=Full3D 36 | BounceThresholdVelocity=200.000000 37 | FrictionCombineMode=Average 38 | RestitutionCombineMode=Average 39 | MaxAngularVelocity=3600.000000 40 | MaxDepenetrationVelocity=0.000000 41 | ContactOffsetMultiplier=0.010000 42 | MinContactOffset=0.000100 43 | MaxContactOffset=1.000000 44 | bSimulateSkeletalMeshOnDedicatedServer=True 45 | DefaultShapeComplexity=CTF_UseSimpleAndComplex 46 | bDefaultHasComplexCollision=True 47 | bSuppressFaceRemapTable=False 48 | bSupportUVFromHitResults=False 49 | bDisableActiveActors=False 50 | bDisableCCD=False 51 | MaxPhysicsDeltaTime=0.033333 52 | bSubstepping=False 53 | bSubsteppingAsync=False 54 | MaxSubstepDeltaTime=0.016667 55 | MaxSubsteps=6 56 | SyncSceneSmoothingFactor=0.000000 57 | AsyncSceneSmoothingFactor=0.990000 58 | InitialAverageFrameRate=0.016667 59 | 60 | 61 | -------------------------------------------------------------------------------- /workers/unreal/Game/Config/DefaultGame.ini: -------------------------------------------------------------------------------- 1 | [/Script/EngineSettings.GeneralProjectSettings] 2 | ProjectID=FF2EF42D4C6B59B92E4CDE94217943D2 3 | ProjectName=Top Down Game Template 4 | 5 | [/Script/RpgDemo.RpgDemoCharacter] 6 | FixedCameraPitch=-45.0 7 | FixedCameraDistance=1500.0 8 | 9 | [/Script/UnrealEd.ProjectPackagingSettings] 10 | BlueprintNativizationMethod=Inclusive 11 | 12 | -------------------------------------------------------------------------------- /workers/unreal/Game/Config/DefaultInput.ini: -------------------------------------------------------------------------------- 1 | [/Script/Engine.InputSettings] 2 | DefaultTouchInterface= 3 | 4 | !AxisMappings=Empty 5 | 6 | +AxisMappings=(AxisName="MoveForward", Key=W, Scale=1.f) 7 | +AxisMappings=(AxisName="MoveForward", Key=S, Scale=-1.f) 8 | 9 | +AxisMappings=(AxisName="MoveRight", Key=D, Scale=1.f) 10 | +AxisMappings=(AxisName="MoveRight", Key=A, Scale=-1.f) 11 | 12 | !ActionMappings=Empty 13 | 14 | +ActionMappings=(ActionName="SetDestination", Key=LeftMouseButton) -------------------------------------------------------------------------------- /workers/unreal/Game/Content/EntityBlueprints/Npc.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/EntityBlueprints/Npc.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/EntityBlueprints/Player.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/EntityBlueprints/Player.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/EntityBlueprints/Spawner.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/EntityBlueprints/Spawner.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Geometry/Meshes/1M_Cube.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Geometry/Meshes/1M_Cube.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Geometry/Meshes/1M_Cube_Chamfer.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Geometry/Meshes/1M_Cube_Chamfer.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Geometry/Meshes/CubeMaterial.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Geometry/Meshes/CubeMaterial.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Geometry/Meshes/TemplateFloor.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Geometry/Meshes/TemplateFloor.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Animations/ThirdPersonIdle.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Animations/ThirdPersonIdle.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Animations/ThirdPersonJump_End.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Animations/ThirdPersonJump_End.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Animations/ThirdPersonJump_Loop.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Animations/ThirdPersonJump_Loop.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Animations/ThirdPersonJump_Start.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Animations/ThirdPersonJump_Start.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Animations/ThirdPersonRun.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Animations/ThirdPersonRun.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Animations/ThirdPersonWalk.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Animations/ThirdPersonWalk.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Animations/ThirdPerson_AnimBP.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Animations/ThirdPerson_AnimBP.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Animations/ThirdPerson_IdleRun_2D.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Animations/ThirdPerson_IdleRun_2D.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Animations/ThirdPerson_Jump.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Animations/ThirdPerson_Jump.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Character/Materials/M_UE4Man_Body.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Character/Materials/M_UE4Man_Body.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Character/Materials/M_UE4Man_ChestLogo.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Character/Materials/M_UE4Man_ChestLogo.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Character/Materials/MaterialLayers/ML_GlossyBlack_Latex_UE4.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Character/Materials/MaterialLayers/ML_GlossyBlack_Latex_UE4.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Character/Materials/MaterialLayers/ML_Plastic_Shiny_Beige.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Character/Materials/MaterialLayers/ML_Plastic_Shiny_Beige.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Character/Materials/MaterialLayers/ML_Plastic_Shiny_Beige_LOGO.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Character/Materials/MaterialLayers/ML_Plastic_Shiny_Beige_LOGO.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Character/Materials/MaterialLayers/ML_SoftMetal_UE4.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Character/Materials/MaterialLayers/ML_SoftMetal_UE4.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Character/Materials/MaterialLayers/T_ML_Aluminum01.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Character/Materials/MaterialLayers/T_ML_Aluminum01.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Character/Materials/MaterialLayers/T_ML_Aluminum01_N.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Character/Materials/MaterialLayers/T_ML_Aluminum01_N.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Character/Materials/MaterialLayers/T_ML_Rubber_Blue_01_D.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Character/Materials/MaterialLayers/T_ML_Rubber_Blue_01_D.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Character/Materials/MaterialLayers/T_ML_Rubber_Blue_01_N.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Character/Materials/MaterialLayers/T_ML_Rubber_Blue_01_N.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Character/Mesh/SK_Mannequin.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Character/Mesh/SK_Mannequin.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Character/Mesh/SK_Mannequin_PhysicsAsset.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Character/Mesh/SK_Mannequin_PhysicsAsset.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Character/Mesh/UE4_Mannequin_Skeleton.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Character/Mesh/UE4_Mannequin_Skeleton.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Character/Textures/UE4Man_Logo_N.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Character/Textures/UE4Man_Logo_N.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Character/Textures/UE4_LOGO_CARD.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Character/Textures/UE4_LOGO_CARD.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Character/Textures/UE4_Mannequin_MAT_MASKA.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Character/Textures/UE4_Mannequin_MAT_MASKA.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/Mannequin/Character/Textures/UE4_Mannequin__normals.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/Mannequin/Character/Textures/UE4_Mannequin__normals.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/TopDownCPP/Blueprints/M_Cursor_Decal.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/TopDownCPP/Blueprints/M_Cursor_Decal.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/TopDownCPP/Blueprints/NpcController.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/TopDownCPP/Blueprints/NpcController.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/TopDownCPP/Blueprints/RPGDemoGameMode.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/TopDownCPP/Blueprints/RPGDemoGameMode.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/Content/TopDownCPP/Maps/TopDownExampleMap.umap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/TopDownCPP/Maps/TopDownExampleMap.umap -------------------------------------------------------------------------------- /workers/unreal/Game/Content/TopDownCPP/TopDownOverview.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/RPGDemo/97549e80a3698086e88ffb5960cb8370c513120b/workers/unreal/Game/Content/TopDownCPP/TopDownOverview.uasset -------------------------------------------------------------------------------- /workers/unreal/Game/RpgDemo.uproject: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "EngineAssociation": "{753604FD-40BE-3B1A-054E-17B9E16564A9}", 4 | "Category": "", 5 | "Description": "", 6 | "Modules": [ 7 | { 8 | "Name": "RpgDemo", 9 | "Type": "Runtime", 10 | "LoadingPhase": "Default", 11 | "AdditionalDependencies": [ 12 | "AIModule", 13 | "Engine" 14 | ] 15 | } 16 | ], 17 | "PostBuildSteps": 18 | { 19 | "Win64": [ 20 | "cd /D \"$(ProjectDir)\"", 21 | "spatial invoke unreal package --engineDir=\"$(EngineDir)\" --project=\"$(ProjectFile)\" --platform=\"$(TargetPlatform)\" --target=\"$(TargetType)\" --configuration=\"$(TargetConfiguration)\" -- -allmaps", 22 | "exit /B %ERRORLEVEL%" 23 | ], 24 | "Linux": [ 25 | "cd \"$(ProjectDir)\"", 26 | "spatial invoke unreal package --engineDir=\"$(EngineDir)\" --project=\"$(ProjectFile)\" --platform=\"$(TargetPlatform)\" --target=\"$(TargetType)\" --configuration=\"$(TargetConfiguration)\" -- -allmaps", 27 | "exit /B %ERRORLEVEL%" 28 | ] 29 | } 30 | } -------------------------------------------------------------------------------- /workers/unreal/Game/Source/RpgDemo.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | public class RpgDemoTarget : TargetRules 7 | { 8 | public RpgDemoTarget(TargetInfo Target) : base(Target) 9 | { 10 | Type = TargetType.Game; 11 | ExtraModuleNames.AddRange(new[] { "RpgDemo" }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /workers/unreal/Game/Source/RpgDemo/ExportSnapshotCommandlet.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | 3 | #include "ExportSnapshotCommandlet.h" 4 | 5 | #include "EntityBuilder.h" 6 | #include "RpgDemo.h" 7 | #include "SpatialOSCommon.h" 8 | #include "improbable/collections.h" 9 | #include "improbable/spawner/spawner.h" 10 | #include "improbable/standard_library.h" 11 | #include "improbable/worker.h" 12 | #include 13 | 14 | using namespace improbable; 15 | 16 | const int g_SpawnerEntityId = 1; 17 | const int g_NumberNPCEntitites = 5; 18 | const int g_NPCEntityIdStart = 10; 19 | 20 | UExportSnapshotCommandlet::UExportSnapshotCommandlet() 21 | { 22 | } 23 | 24 | UExportSnapshotCommandlet::~UExportSnapshotCommandlet() 25 | { 26 | } 27 | 28 | int32 UExportSnapshotCommandlet::Main(const FString& Params) 29 | { 30 | FString combinedPath = 31 | FPaths::Combine(*FPaths::GetPath(FPaths::GetProjectFilePath()), TEXT("../../../snapshots")); 32 | UE_LOG(LogTemp, Display, TEXT("Combined path %s"), *combinedPath); 33 | if (FPaths::CollapseRelativeDirectories(combinedPath)) 34 | { 35 | GenerateSnapshot(combinedPath); 36 | } 37 | else 38 | { 39 | UE_LOG(LogTemp, Display, TEXT("Path was invalid - snapshot not generated")); 40 | } 41 | 42 | return 0; 43 | } 44 | 45 | void UExportSnapshotCommandlet::GenerateSnapshot(const FString& savePath) const 46 | { 47 | const FString fullPath = FPaths::Combine(*savePath, TEXT("default.snapshot")); 48 | 49 | worker::SnapshotOutputStream stream(improbable::unreal::Components{}, TCHAR_TO_UTF8(*fullPath)); 50 | stream.WriteEntity(g_SpawnerEntityId, CreateSpawnerEntity()); 51 | for (int npcId = 0; npcId < g_NumberNPCEntitites; ++npcId) 52 | { 53 | worker::Option Result = stream.WriteEntity(g_NPCEntityIdStart + npcId, CreateNPCEntity()); 54 | if (!Result.empty()) 55 | { 56 | std::string ErrorString = Result.value_or(""); 57 | UE_LOG(LogTemp, Display, TEXT("Error: %s"), UTF8_TO_TCHAR(ErrorString.c_str())); 58 | return; 59 | } 60 | } 61 | UE_LOG(LogTemp, Display, TEXT("Snapshot exported to the path %s"), *fullPath); 62 | } 63 | 64 | const std::array verticalCorridors = {{-2.0f, 2.75f, 7.5f, 12.25f, 17.0f}}; 65 | const std::array horizontalCorridors = {{-10.0f, -4.0f, 2.0f, 8.0f, 14.0f}}; 66 | 67 | worker::Entity UExportSnapshotCommandlet::CreateNPCEntity() const 68 | { 69 | const int randomVerticalCorridor = FMath::RandRange(0, verticalCorridors.size()); 70 | const int randomHorizontalCorridor = FMath::RandRange(0, horizontalCorridors.size()); 71 | 72 | const Coordinates initialPosition{verticalCorridors[randomVerticalCorridor], 4.0, 73 | horizontalCorridors[randomHorizontalCorridor]}; 74 | 75 | WorkerAttributeSet unrealWorkerAttributeSet{worker::List{"UnrealWorker"}}; 76 | WorkerAttributeSet unrealClientAttributeSet{worker::List{"UnrealClient"}}; 77 | 78 | WorkerRequirementSet unrealWorkerWritePermission{{unrealWorkerAttributeSet}}; 79 | WorkerRequirementSet unrealClientWritePermission{{unrealClientAttributeSet}}; 80 | WorkerRequirementSet anyWorkerReadPermission{ 81 | {unrealClientAttributeSet, unrealWorkerAttributeSet}}; 82 | 83 | auto snapshotEntity = 84 | improbable::unreal::FEntityBuilder::Begin() 85 | .AddPositionComponent(Position::Data{initialPosition}, unrealWorkerWritePermission) 86 | .AddMetadataComponent(Metadata::Data{"Npc"}) 87 | .SetPersistence(true) 88 | .SetReadAcl(anyWorkerReadPermission) 89 | .Build(); 90 | 91 | return snapshotEntity; 92 | } 93 | 94 | worker::Entity UExportSnapshotCommandlet::CreateSpawnerEntity() const 95 | { 96 | const Coordinates initialPosition{0, 0, 0}; 97 | 98 | WorkerAttributeSet unrealWorkerAttributeSet{worker::List{"UnrealWorker"}}; 99 | WorkerAttributeSet unrealClientAttributeSet{worker::List{"UnrealClient"}}; 100 | 101 | WorkerRequirementSet unrealWorkerWritePermission{{unrealWorkerAttributeSet}}; 102 | WorkerRequirementSet anyWorkerReadPermission{ 103 | {unrealClientAttributeSet, unrealWorkerAttributeSet}}; 104 | 105 | auto snapshotEntity = 106 | improbable::unreal::FEntityBuilder::Begin() 107 | .AddPositionComponent(Position::Data{initialPosition}, unrealWorkerWritePermission) 108 | .AddMetadataComponent(Metadata::Data("Spawner")) 109 | .SetPersistence(true) 110 | .SetReadAcl(anyWorkerReadPermission) 111 | .AddComponent(spawner::Spawner::Data{}, unrealWorkerWritePermission) 112 | .Build(); 113 | 114 | return snapshotEntity; 115 | } -------------------------------------------------------------------------------- /workers/unreal/Game/Source/RpgDemo/ExportSnapshotCommandlet.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | 3 | #pragma once 4 | 5 | #include "Commandlets/Commandlet.h" 6 | #include "improbable/worker.h" 7 | #include "ExportSnapshotCommandlet.generated.h" 8 | 9 | UCLASS() 10 | class RPGDEMO_API UExportSnapshotCommandlet : public UCommandlet 11 | { 12 | GENERATED_BODY() 13 | public: 14 | UExportSnapshotCommandlet(); 15 | ~UExportSnapshotCommandlet(); 16 | 17 | virtual int32 Main(const FString& Params) override; 18 | 19 | private: 20 | void GenerateSnapshot(const FString& savePath) const; 21 | worker::Entity CreateNPCEntity() const; 22 | worker::Entity CreateSpawnerEntity() const; 23 | }; 24 | -------------------------------------------------------------------------------- /workers/unreal/Game/Source/RpgDemo/OtherPlayerController.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | 3 | #include "OtherPlayerController.h" 4 | 5 | #include "PositionComponent.h" 6 | #include "RpgDemo.h" 7 | #include "RpgDemoCharacter.h" 8 | #include "SpatialOSConversionFunctionLibrary.h" 9 | 10 | AOtherPlayerController::AOtherPlayerController() 11 | { 12 | } 13 | 14 | void AOtherPlayerController::Possess(APawn* InPawn) 15 | { 16 | Super::Possess(InPawn); 17 | mOtherPlayer = Cast(InPawn); 18 | 19 | mOtherPlayer->GetPositionComponent()->OnCoordsUpdate.AddDynamic( 20 | this, &AOtherPlayerController::OnPositionUpdate); 21 | } 22 | 23 | void AOtherPlayerController::UnPossess() 24 | { 25 | Super::UnPossess(); 26 | mOtherPlayer->GetPositionComponent()->OnCoordsUpdate.RemoveDynamic( 27 | this, &AOtherPlayerController::OnPositionUpdate); 28 | } 29 | 30 | void AOtherPlayerController::OnPositionUpdate() 31 | { 32 | SetNewMoveDestination(mOtherPlayer->GetPositionComponent()->Coords); 33 | } 34 | 35 | void AOtherPlayerController::SetNewMoveDestination(const FVector& DestLocation) 36 | { 37 | UNavigationSystem* const NavSys = GetWorld()->GetNavigationSystem(); 38 | float const Distance = FVector::Dist(DestLocation, mOtherPlayer->GetActorLocation()); 39 | if (NavSys) 40 | { 41 | NavSys->SimpleMoveToLocation(this, DestLocation); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /workers/unreal/Game/Source/RpgDemo/OtherPlayerController.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | 3 | #pragma once 4 | 5 | #include "AIController.h" 6 | #include "RpgDemoCharacter.h" 7 | #include "OtherPlayerController.generated.h" 8 | 9 | /** 10 | * 11 | */ 12 | UCLASS() 13 | class RPGDEMO_API AOtherPlayerController : public AAIController 14 | { 15 | GENERATED_BODY() 16 | 17 | public: 18 | AOtherPlayerController(); 19 | virtual void Possess(APawn* InPawn) override; 20 | virtual void UnPossess() override; 21 | 22 | protected: 23 | UFUNCTION(BlueprintCallable, Category = "OtherPlayerController") 24 | void OnPositionUpdate(); 25 | 26 | void SetNewMoveDestination(const FVector& DestLocation); 27 | 28 | ARpgDemoCharacter* mOtherPlayer; 29 | }; 30 | -------------------------------------------------------------------------------- /workers/unreal/Game/Source/RpgDemo/RPGDemoGameInstance.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | 3 | #include "RPGDemoGameInstance.h" 4 | 5 | #include "EntityPipeline.h" 6 | #include "EntityRegistry.h" 7 | #include "RpgDemo.h" 8 | #include "SimpleEntitySpawnerBlock.h" 9 | #include "SpatialOS.h" 10 | #include "SpatialOSComponentUpdater.h" 11 | 12 | #define ENTITY_BLUEPRINTS_FOLDER "/Game/EntityBlueprints" 13 | 14 | URPGDemoGameInstance::URPGDemoGameInstance() : SpatialOSInstance(), MetricsReporterHandle(), SpatialOSComponentUpdater() 15 | { 16 | SpatialOSComponentUpdater = CreateDefaultSubobject(TEXT("SpatialOSComponentUpdater")); 17 | } 18 | 19 | URPGDemoGameInstance::~URPGDemoGameInstance() 20 | { 21 | } 22 | 23 | void URPGDemoGameInstance::Init() 24 | { 25 | UGameInstance::Init(); 26 | 27 | SpatialOSInstance = NewObject(this); 28 | 29 | SpatialOSInstance->OnConnectedDelegate.AddDynamic(this, 30 | &URPGDemoGameInstance::OnSpatialOsConnected); 31 | SpatialOSInstance->OnDisconnectedDelegate.AddDynamic( 32 | this, &URPGDemoGameInstance::OnSpatialOsDisconnected); 33 | 34 | EntityRegistry = NewObject(this); 35 | } 36 | 37 | void URPGDemoGameInstance::Shutdown() 38 | { 39 | UGameInstance::Shutdown(); 40 | 41 | SpatialOSInstance->OnConnectedDelegate.RemoveDynamic( 42 | this, &URPGDemoGameInstance::OnSpatialOsConnected); 43 | SpatialOSInstance->OnDisconnectedDelegate.RemoveDynamic( 44 | this, &URPGDemoGameInstance::OnSpatialOsDisconnected); 45 | } 46 | 47 | void URPGDemoGameInstance::ProcessOps(float DeltaTime) 48 | { 49 | if (SpatialOSInstance != nullptr && SpatialOSInstance->GetEntityPipeline() != nullptr) 50 | { 51 | SpatialOSInstance->ProcessOps(); 52 | SpatialOSInstance->GetEntityPipeline()->ProcessOps( 53 | SpatialOSInstance->GetView(), SpatialOSInstance->GetConnection(), GetWorld()); 54 | SpatialOSComponentUpdater->UpdateComponents(EntityRegistry, DeltaTime); 55 | } 56 | } 57 | 58 | USpatialOS* URPGDemoGameInstance::GetSpatialOS() 59 | { 60 | return SpatialOSInstance; 61 | } 62 | 63 | UEntityRegistry* URPGDemoGameInstance::GetEntityRegistry() 64 | { 65 | return EntityRegistry; 66 | } 67 | 68 | void URPGDemoGameInstance::OnSpatialOsConnected() 69 | { 70 | auto EntitySpawnerBlock = NewObject(); 71 | EntitySpawnerBlock->Init(EntityRegistry); 72 | SpatialOSInstance->GetEntityPipeline()->AddBlock(EntitySpawnerBlock); 73 | 74 | TArray BlueprintPaths; 75 | BlueprintPaths.Add(TEXT(ENTITY_BLUEPRINTS_FOLDER)); 76 | 77 | EntityRegistry->RegisterEntityBlueprints(BlueprintPaths); 78 | 79 | constexpr auto ShouldTimerLoop = true; 80 | constexpr auto InitialDelay = 2.0f; 81 | constexpr auto LoopDelay = 2.0f; 82 | 83 | auto MetricsDelegate = FTimerDelegate::CreateLambda([this]() { 84 | auto Connection = SpatialOSInstance->GetConnection().Pin(); 85 | 86 | if (Connection.IsValid()) 87 | { 88 | Connection->SendMetrics(SpatialOSInstance->GetMetrics()); 89 | } 90 | }); 91 | 92 | GetWorld()->GetTimerManager().SetTimer(MetricsReporterHandle, MetricsDelegate, LoopDelay, 93 | ShouldTimerLoop, InitialDelay); 94 | } 95 | 96 | void URPGDemoGameInstance::OnSpatialOsDisconnected() 97 | { 98 | GetWorld()->GetTimerManager().ClearTimer(MetricsReporterHandle); 99 | } 100 | -------------------------------------------------------------------------------- /workers/unreal/Game/Source/RpgDemo/RPGDemoGameInstance.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | 3 | #pragma once 4 | 5 | #include "Engine/GameInstance.h" 6 | #include "SpatialOS.h" 7 | #include "RPGDemoGameInstance.generated.h" 8 | 9 | class UEntityPipeline; 10 | class UEntityRegistry; 11 | class UCallbackDispatcher; 12 | class USpatialOSComponentUpdater; 13 | 14 | /** 15 | * 16 | */ 17 | UCLASS() 18 | class RPGDEMO_API URPGDemoGameInstance : public UGameInstance 19 | { 20 | GENERATED_BODY() 21 | public: 22 | URPGDemoGameInstance(); 23 | ~URPGDemoGameInstance(); 24 | 25 | virtual void Init() override; 26 | virtual void Shutdown() override; 27 | 28 | void ProcessOps(float DeltaTime); 29 | 30 | UFUNCTION(BlueprintCallable, Category = "SpatialOS") 31 | USpatialOS* GetSpatialOS(); 32 | 33 | UFUNCTION(BlueprintCallable, Category = "SpatialOS") 34 | UEntityRegistry* GetEntityRegistry(); 35 | 36 | private: 37 | UPROPERTY() 38 | USpatialOS* SpatialOSInstance; 39 | 40 | UPROPERTY() 41 | UEntityRegistry* EntityRegistry; 42 | 43 | UPROPERTY() 44 | USpatialOSComponentUpdater* SpatialOSComponentUpdater; 45 | 46 | UFUNCTION() 47 | void OnSpatialOsConnected(); 48 | 49 | UFUNCTION() 50 | void OnSpatialOsDisconnected(); 51 | 52 | FTimerHandle MetricsReporterHandle; 53 | }; 54 | -------------------------------------------------------------------------------- /workers/unreal/Game/Source/RpgDemo/RpgDemo.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using UnrealBuildTool; 6 | using System.IO; 7 | 8 | public class RpgDemo : ModuleRules 9 | { 10 | public RpgDemo(ReadOnlyTargetRules Target) : base(Target) 11 | { 12 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 13 | bFasterWithoutUnity = true; 14 | 15 | 16 | PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "SpatialOS" }); 17 | } 18 | } -------------------------------------------------------------------------------- /workers/unreal/Game/Source/RpgDemo/RpgDemo.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | 3 | #include "RpgDemo.h" 4 | 5 | IMPLEMENT_PRIMARY_GAME_MODULE(FDefaultGameModuleImpl, RpgDemo, "RpgDemo"); 6 | 7 | DEFINE_LOG_CATEGORY(LogRpgDemo) 8 | -------------------------------------------------------------------------------- /workers/unreal/Game/Source/RpgDemo/RpgDemo.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | 3 | #ifndef __UNREAL_H__ 4 | #define __UNREAL_H__ 5 | 6 | #include "EngineMinimal.h" 7 | 8 | DECLARE_LOG_CATEGORY_EXTERN(LogRpgDemo, Log, All); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /workers/unreal/Game/Source/RpgDemo/RpgDemoCharacter.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | 3 | #include "RpgDemoCharacter.h" 4 | 5 | #include "EntityRegistry.h" 6 | #include "OtherPlayerController.h" 7 | #include "PositionComponent.h" 8 | #include "RPGDemoGameInstance.h" 9 | #include "RpgDemo.h" 10 | #include "Runtime/CoreUObject/Public/UObject/ConstructorHelpers.h" 11 | #include "Runtime/Engine/Classes/Components/DecalComponent.h" 12 | #include "SpatialOS.h" 13 | #include "SpatialOSConversionFunctionLibrary.h" 14 | #include "improbable/standard_library.h" 15 | #include "improbable/standard_library.h" 16 | 17 | using ::improbable::Position; 18 | using ::improbable::Coordinates; 19 | 20 | ARpgDemoCharacter::ARpgDemoCharacter() 21 | { 22 | // Set size for player capsule 23 | GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f); 24 | 25 | // Don't rotate character to camera direction 26 | bUseControllerRotationPitch = false; 27 | bUseControllerRotationYaw = false; 28 | bUseControllerRotationRoll = false; 29 | 30 | // Configure character movement 31 | GetCharacterMovement()->bOrientRotationToMovement = 32 | true; // Rotate character to moving direction 33 | GetCharacterMovement()->RotationRate = FRotator(0.f, 640.f, 0.f); 34 | GetCharacterMovement()->bConstrainToPlane = true; 35 | GetCharacterMovement()->bSnapToPlaneAtStart = true; 36 | 37 | // Create a camera boom... 38 | CameraBoom = CreateDefaultSubobject(TEXT("CameraBoom")); 39 | CameraBoom->SetupAttachment(RootComponent); 40 | CameraBoom->bAbsoluteRotation = true; // Don't want arm to rotate when character does 41 | CameraBoom->TargetArmLength = 800.f; 42 | CameraBoom->RelativeRotation = FRotator(-60.f, 0.f, 0.f); 43 | CameraBoom->bDoCollisionTest = 44 | false; // Don't want to pull camera in when it collides with level 45 | 46 | // Create a camera... 47 | TopDownCameraComponent = CreateDefaultSubobject(TEXT("TopDownCamera")); 48 | TopDownCameraComponent->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); 49 | TopDownCameraComponent->bUsePawnControlRotation = 50 | false; // Camera does not rotate relative to arm 51 | 52 | // Create a decal in the world to show the cursor's location 53 | CursorToWorld = CreateDefaultSubobject("CursorToWorld"); 54 | CursorToWorld->SetupAttachment(RootComponent); 55 | static ConstructorHelpers::FObjectFinder DecalMaterialAsset( 56 | TEXT("Material'/Game/TopDownCPP/Blueprints/M_Cursor_Decal.M_Cursor_Decal'")); 57 | if (DecalMaterialAsset.Succeeded()) 58 | { 59 | CursorToWorld->SetDecalMaterial(DecalMaterialAsset.Object); 60 | } 61 | CursorToWorld->DecalSize = FVector(16.0f, 32.0f, 32.0f); 62 | CursorToWorld->SetRelativeRotation(FRotator(90.0f, 0.0f, 0.0f).Quaternion()); 63 | 64 | // Deactivate by default 65 | CursorToWorld->SetActive(false); 66 | CursorToWorld->SetHiddenInGame(true); 67 | 68 | // Activate ticking in order to update the cursor every frame. 69 | PrimaryActorTick.bCanEverTick = true; 70 | PrimaryActorTick.bStartWithTickEnabled = true; 71 | 72 | // Create a transform component 73 | PositionComponent = CreateDefaultSubobject(TEXT("PositionComponent")); 74 | 75 | SpawnCollisionHandlingMethod = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; 76 | } 77 | 78 | void ARpgDemoCharacter::Tick(float DeltaSeconds) 79 | { 80 | Super::Tick(DeltaSeconds); 81 | 82 | if (PositionComponent->GetAuthority() == EAuthority::Authoritative || PositionComponent->GetAuthority() == EAuthority::AuthorityLossImminent) 83 | { 84 | UpdateCursorPosition(); 85 | 86 | PositionComponent->Coords = GetActorLocation(); 87 | } 88 | } 89 | 90 | void ARpgDemoCharacter::BeginPlay() 91 | { 92 | Super::BeginPlay(); 93 | 94 | InitialiseAsOtherPlayer(); 95 | PositionComponent->OnAuthorityChange.AddDynamic(this, 96 | &ARpgDemoCharacter::OnPositionAuthorityChange); 97 | PositionComponent->OnComponentReady.AddDynamic(this, 98 | &ARpgDemoCharacter::OnPositionComponentReady); 99 | } 100 | 101 | void ARpgDemoCharacter::OnPositionAuthorityChange(EAuthority newAuthority) 102 | { 103 | Initialise(newAuthority); 104 | } 105 | 106 | void ARpgDemoCharacter::OnPositionComponentReady() 107 | { 108 | SetActorLocation(PositionComponent->Coords); 109 | } 110 | 111 | /** If this is our player, then possess it with the player controller and activate the camera and 112 | *the cursor, 113 | * otherwise, add an OtherPlayerController */ 114 | void ARpgDemoCharacter::Initialise(EAuthority authority) 115 | { 116 | if (authority == EAuthority::Authoritative || authority == EAuthority::AuthorityLossImminent) 117 | { 118 | InitialiseAsOwnPlayer(); 119 | UE_LOG(LogTemp, Warning, 120 | TEXT("ARpgDemoCharacter::Initialise did just call InitialiseAsOwnPlayer" 121 | "controller for actor %s"), 122 | *GetName()) 123 | } 124 | else 125 | { 126 | InitialiseAsOtherPlayer(); 127 | UE_LOG(LogTemp, Warning, 128 | TEXT("ARpgDemoCharacter::Initialise did just call InitialiseAsOtherPlayer" 129 | "controller for actor %s"), 130 | *GetName()) 131 | } 132 | } 133 | 134 | void ARpgDemoCharacter::InitialiseAsOwnPlayer() 135 | { 136 | APlayerController* playerController = GetWorld()->GetFirstPlayerController(); 137 | if (playerController == nullptr) 138 | { 139 | UE_LOG(LogTemp, Warning, 140 | TEXT("ARpgDemoCharacter::InitialiseAsOwnPlayer error, playerController was null")) 141 | return; 142 | } 143 | AController* currentController = GetController(); 144 | if (currentController != playerController) 145 | { 146 | if (currentController != nullptr) 147 | { 148 | currentController->UnPossess(); 149 | } 150 | 151 | if (playerController->GetPawn() != nullptr) 152 | { 153 | playerController->UnPossess(); 154 | } 155 | playerController->Possess(this); 156 | UE_LOG(LogTemp, Warning, 157 | TEXT("ARpgDemoCharacter::InitialiseAsOwnPlayer creating own player " 158 | "controller for actor %s"), 159 | *GetName()) 160 | } 161 | 162 | if (!CursorToWorld->IsActive()) 163 | { 164 | CursorToWorld->SetActive(true); 165 | } 166 | CursorToWorld->SetHiddenInGame(false); 167 | 168 | if (!TopDownCameraComponent->IsActive()) 169 | { 170 | TopDownCameraComponent->Activate(); 171 | } 172 | } 173 | 174 | void ARpgDemoCharacter::InitialiseAsOtherPlayer() 175 | { 176 | AController* currentController = GetController(); 177 | APlayerController* playerController = GetWorld()->GetFirstPlayerController(); 178 | if (currentController == playerController && playerController != nullptr) 179 | { 180 | playerController->UnPossess(); 181 | currentController = GetController(); 182 | } 183 | 184 | if (currentController == nullptr) 185 | { 186 | AOtherPlayerController* otherPlayerController = Cast( 187 | GetWorld()->SpawnActor(AOtherPlayerController::StaticClass())); 188 | 189 | UE_LOG(LogTemp, Warning, 190 | TEXT("ARpgDemoCharacter::InitialiseAsOtherPlayer creating other player " 191 | "controller for actor %s"), 192 | *GetName()) 193 | otherPlayerController->Possess(this); 194 | } 195 | else 196 | { 197 | UE_LOG(LogTemp, Warning, 198 | TEXT("ARpgDemoCharacter::InitialiseAsOtherPlayer controller was not null" 199 | "controller for actor %s"), 200 | *GetName()) 201 | } 202 | 203 | if (CursorToWorld->IsActive()) 204 | { 205 | CursorToWorld->SetActive(false); 206 | } 207 | CursorToWorld->SetHiddenInGame(true); 208 | 209 | if (TopDownCameraComponent->IsActive()) 210 | { 211 | TopDownCameraComponent->Deactivate(); 212 | } 213 | } 214 | 215 | void ARpgDemoCharacter::UpdateCursorPosition() const 216 | { 217 | if (CursorToWorld != nullptr) 218 | { 219 | if (APlayerController* PC = Cast(GetController())) 220 | { 221 | FHitResult TraceHitResult; 222 | PC->GetHitResultUnderCursor(ECC_Visibility, true, TraceHitResult); 223 | FVector CursorFV = TraceHitResult.ImpactNormal; 224 | FRotator CursorR = CursorFV.Rotation(); 225 | CursorToWorld->SetWorldLocation(TraceHitResult.Location); 226 | CursorToWorld->SetWorldRotation(CursorR); 227 | } 228 | } 229 | } 230 | 231 | FEntityId ARpgDemoCharacter::GetEntityId() const 232 | { 233 | auto GameInstance = Cast(GetWorld()->GetGameInstance()); 234 | 235 | if (GameInstance != nullptr) 236 | { 237 | auto EntityRegistry = GameInstance->GetEntityRegistry(); 238 | if (EntityRegistry != nullptr) 239 | { 240 | return EntityRegistry->GetEntityIdFromActor(this); 241 | } 242 | } 243 | 244 | return FEntityId(); 245 | } -------------------------------------------------------------------------------- /workers/unreal/Game/Source/RpgDemo/RpgDemoCharacter.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | #pragma once 3 | #include "EntityId.h" 4 | #include "GameFramework/Character.h" 5 | #include "RpgDemoCharacter.generated.h" 6 | 7 | UCLASS(Blueprintable) 8 | class ARpgDemoCharacter : public ACharacter 9 | { 10 | GENERATED_BODY() 11 | 12 | public: 13 | ARpgDemoCharacter(); 14 | 15 | // Called every frame. 16 | virtual void Tick(float DeltaSeconds) override; 17 | 18 | virtual void BeginPlay() override; 19 | 20 | /** Returns TopDownCameraComponent subobject **/ 21 | FORCEINLINE class UCameraComponent* GetTopDownCameraComponent() const 22 | { 23 | return TopDownCameraComponent; 24 | } 25 | /** Returns CameraBoom subobject **/ 26 | FORCEINLINE class USpringArmComponent* GetCameraBoom() const 27 | { 28 | return CameraBoom; 29 | } 30 | 31 | FORCEINLINE class UPositionComponent* GetPositionComponent() const 32 | { 33 | return PositionComponent; 34 | } 35 | 36 | UFUNCTION(BlueprintPure, Category = "RpgDemoCharacter") 37 | FEntityId GetEntityId() const; 38 | 39 | private: 40 | void UpdateCursorPosition() const; 41 | void Initialise(EAuthority authority); 42 | void InitialiseAsOwnPlayer(); 43 | void InitialiseAsOtherPlayer(); 44 | 45 | /** Top down camera */ 46 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, 47 | meta = (AllowPrivateAccess = "true")) 48 | class UCameraComponent* TopDownCameraComponent; 49 | 50 | /** Camera boom positioning the camera above the character */ 51 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, 52 | meta = (AllowPrivateAccess = "true")) 53 | class USpringArmComponent* CameraBoom; 54 | 55 | /** A decal that projects to the cursor location. */ 56 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, 57 | meta = (AllowPrivateAccess = "true")) 58 | class UDecalComponent* CursorToWorld; 59 | 60 | /** The transform component */ 61 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "RpgDemoCharacter", 62 | meta = (AllowPrivateAccess = "true")) 63 | class UPositionComponent* PositionComponent; 64 | 65 | UFUNCTION(BlueprintCallable, Category = "RpgDemoCharacter") 66 | void OnPositionComponentReady(); 67 | 68 | UFUNCTION(BlueprintCallable, Category = "RpgDemoCharacter") 69 | void OnPositionAuthorityChange(EAuthority newAuthority); 70 | }; 71 | -------------------------------------------------------------------------------- /workers/unreal/Game/Source/RpgDemo/RpgDemoGameMode.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | 3 | #include "RpgDemoGameMode.h" 4 | 5 | #include "Commander.h" 6 | #include "EntityBuilder.h" 7 | #include "RPGDemoGameInstance.h" 8 | #include "RpgDemo.h" 9 | #include "RpgDemoPlayerController.h" 10 | #include "SpatialOSConversionFunctionLibrary.h" 11 | #include "SpatialOSWorkerConfigurationData.h" 12 | #include "WorkerConnection.h" 13 | #include "improbable/player/heartbeat.h" 14 | #include "improbable/spawner/spawner.h" 15 | #include "improbable/standard_library.h" 16 | 17 | #define ENTITY_BLUEPRINTS_FOLDER "/Game/EntityBlueprints" 18 | 19 | using namespace improbable; 20 | using namespace improbable::unreal::core; 21 | 22 | ARpgDemoGameMode::ARpgDemoGameMode() 23 | : entityQueryCallback(-1), WorkerTypeOverride(""), WorkerIdOverride(""), UseExternalIp(false) 24 | { 25 | PrimaryActorTick.bCanEverTick = true; 26 | 27 | // Set the default player controller class 28 | PlayerControllerClass = ARpgDemoPlayerController::StaticClass(); 29 | 30 | // Don't spawn players automatically 31 | bStartPlayersAsSpectators = true; 32 | 33 | // No need for default pawn class 34 | DefaultPawnClass = nullptr; 35 | 36 | UnbindEntityQueryDelegate.BindUObject(this, &ARpgDemoGameMode::UnbindEntityQueryCallback); 37 | } 38 | 39 | FString ARpgDemoGameMode::GetSpatialOsWorkerType() const 40 | { 41 | auto SpatialOS = GetSpatialOS(); 42 | if (SpatialOS != nullptr) 43 | { 44 | return SpatialOS->GetWorkerConfiguration().GetWorkerType(); 45 | } 46 | return ""; 47 | } 48 | 49 | UEntityTemplate* ARpgDemoGameMode::CreatePlayerEntityTemplate(FString clientWorkerId, 50 | const FVector& position) 51 | { 52 | const auto& spatialOsPosition = 53 | USpatialOSConversionFunctionLibrary::UnrealCoordinatesToSpatialOsCoordinates(position); 54 | const Coordinates initialPosition{spatialOsPosition.X, spatialOsPosition.Y, 55 | spatialOsPosition.Z}; 56 | 57 | const WorkerAttributeSet unrealWorkerAttributeSet{worker::List{"UnrealWorker"}}; 58 | 59 | const std::string clientWorkerIdString = TCHAR_TO_UTF8(*clientWorkerId); 60 | const std::string clientAttribute = "workerId:" + clientWorkerIdString; 61 | UE_LOG(LogTemp, Warning, TEXT("Making ourselves authoritative over Player Transform and " 62 | "HeartbeatReceiver with worker ID %s"), 63 | *FString(clientAttribute.c_str())) 64 | 65 | const WorkerAttributeSet ownUnrealClientAttributeSet{ 66 | worker::List{clientAttribute}}; 67 | const WorkerAttributeSet allUnrealClientsAttributeSet{ 68 | worker::List{"UnrealClient"}}; 69 | 70 | const WorkerRequirementSet workerRequirementSet{{unrealWorkerAttributeSet}}; 71 | const WorkerRequirementSet ownClientRequirementSet{{ownUnrealClientAttributeSet}}; 72 | const WorkerRequirementSet globalRequirementSet{ 73 | {allUnrealClientsAttributeSet, unrealWorkerAttributeSet}}; 74 | 75 | auto playerTemplate = 76 | improbable::unreal::FEntityBuilder::Begin() 77 | .AddPositionComponent(Position::Data{initialPosition}, ownClientRequirementSet) 78 | .AddMetadataComponent(Metadata::Data{"Player"}) 79 | .SetPersistence(false) 80 | .SetReadAcl(globalRequirementSet) 81 | .AddComponent(player::HeartbeatSender::Data{}, 82 | workerRequirementSet) 83 | .AddComponent(player::HeartbeatReceiver::Data{}, 84 | ownClientRequirementSet) 85 | .Build(); 86 | 87 | return NewObject(this, UEntityTemplate::StaticClass())->Init(playerTemplate); 88 | } 89 | 90 | void ARpgDemoGameMode::GetSpawnerEntityId(const FGetSpawnerEntityIdResultDelegate& callback, 91 | int timeoutMs) 92 | { 93 | auto SpatialOS = GetSpatialOS(); 94 | if (SpatialOS != nullptr) 95 | { 96 | auto LockedConnection = SpatialOS->GetConnection().Pin(); 97 | 98 | if (LockedConnection.IsValid()) 99 | { 100 | auto LockedDispatcher = SpatialOS->GetView().Pin(); 101 | 102 | if (LockedDispatcher.IsValid()) 103 | { 104 | GetSpawnerEntityIdResultCallback = new FGetSpawnerEntityIdResultDelegate(callback); 105 | const worker::query::EntityQuery& entity_query = { 106 | worker::query::ComponentConstraint{spawner::Spawner::ComponentId}, 107 | worker::query::SnapshotResultType{}}; 108 | 109 | auto requestId = LockedConnection->SendEntityQueryRequest( 110 | entity_query, static_cast(timeoutMs)); 111 | entityQueryCallback = LockedDispatcher->OnEntityQueryResponse([this, requestId]( 112 | const worker::EntityQueryResponseOp& op) { 113 | if (op.RequestId != requestId) 114 | { 115 | return; 116 | } 117 | if (!GetSpawnerEntityIdResultCallback->IsBound()) 118 | { 119 | UE_LOG(LogTemp, Warning, 120 | TEXT("GetSpawnerEntityIdResultCallback is unbound")) 121 | } 122 | if (op.StatusCode != worker::StatusCode::kSuccess) 123 | { 124 | std::string errorMessage = "Could not find spawner entity: " + op.Message; 125 | GetSpawnerEntityIdResultCallback->ExecuteIfBound( 126 | false, FString(errorMessage.c_str()), FEntityId()); 127 | return; 128 | } 129 | if (op.ResultCount == 0) 130 | { 131 | std::string errorMessage = "Query returned 0 spawner entities"; 132 | GetSpawnerEntityIdResultCallback->ExecuteIfBound( 133 | false, FString(errorMessage.c_str()), FEntityId()); 134 | return; 135 | } 136 | GetSpawnerEntityIdResultCallback->ExecuteIfBound( 137 | true, FString(), FEntityId(op.Result.begin()->first)); 138 | GetWorldTimerManager().SetTimerForNextTick(UnbindEntityQueryDelegate); 139 | return; 140 | }); 141 | } 142 | } 143 | } 144 | } 145 | 146 | void ARpgDemoGameMode::StartPlay() 147 | { 148 | AGameModeBase::StartPlay(); 149 | 150 | auto SpatialOS = GetSpatialOS(); 151 | if (SpatialOS != nullptr) 152 | { 153 | SpatialOS->OnConnectedDelegate.AddDynamic(this, &ARpgDemoGameMode::OnSpatialOsConnected); 154 | SpatialOS->OnConnectionFailedDelegate.AddDynamic( 155 | this, &ARpgDemoGameMode::OnSpatialOsFailedToConnect); 156 | SpatialOS->OnDisconnectedDelegate.AddDynamic(this, 157 | &ARpgDemoGameMode::OnSpatialOsDisconnected); 158 | UE_LOG(LogTemp, Display, TEXT("Startplay called to SpatialOS")) 159 | 160 | auto workerConfig = FSOSWorkerConfigurationData(); 161 | 162 | workerConfig.Networking.UseExternalIp = UseExternalIp; 163 | 164 | if (!WorkerTypeOverride.IsEmpty()) 165 | { 166 | workerConfig.SpatialOSApplication.WorkerPlatform = WorkerTypeOverride; 167 | } 168 | 169 | if (!WorkerIdOverride.IsEmpty()) 170 | { 171 | workerConfig.SpatialOSApplication.WorkerId = WorkerIdOverride; 172 | } 173 | 174 | SpatialOS->ApplyConfiguration(workerConfig); 175 | SpatialOS->Connect(); 176 | } 177 | } 178 | 179 | void ARpgDemoGameMode::EndPlay(const EEndPlayReason::Type EndPlayReason) 180 | { 181 | AGameModeBase::EndPlay(EndPlayReason); 182 | 183 | UnbindEntityQueryCallback(); 184 | 185 | auto SpatialOS = GetSpatialOS(); 186 | if (SpatialOS != nullptr) 187 | { 188 | SpatialOS->Disconnect(); 189 | 190 | SpatialOS->OnConnectedDelegate.RemoveDynamic(this, &ARpgDemoGameMode::OnSpatialOsConnected); 191 | SpatialOS->OnConnectionFailedDelegate.RemoveDynamic( 192 | this, &ARpgDemoGameMode::OnSpatialOsFailedToConnect); 193 | SpatialOS->OnDisconnectedDelegate.RemoveDynamic(this, 194 | &ARpgDemoGameMode::OnSpatialOsDisconnected); 195 | } 196 | } 197 | 198 | void ARpgDemoGameMode::Tick(float DeltaTime) 199 | { 200 | AGameModeBase::Tick(DeltaTime); 201 | 202 | auto GameInstance = Cast(GetWorld()->GetGameInstance()); 203 | 204 | if (GameInstance != nullptr) 205 | { 206 | GameInstance->ProcessOps(DeltaTime); 207 | } 208 | } 209 | 210 | bool ARpgDemoGameMode::IsConnectedToSpatialOs() const 211 | { 212 | auto SpatialOS = GetSpatialOS(); 213 | if (SpatialOS != nullptr) 214 | { 215 | return SpatialOS->IsConnected(); 216 | } 217 | return false; 218 | } 219 | 220 | UCommander* ARpgDemoGameMode::SendWorkerCommand() 221 | { 222 | if (Commander == nullptr) 223 | { 224 | auto SpatialOS = GetSpatialOS(); 225 | if (SpatialOS != nullptr) 226 | { 227 | Commander = NewObject(this, UCommander::StaticClass()) 228 | ->Init(nullptr, SpatialOS->GetConnection(), SpatialOS->GetView()); 229 | } 230 | } 231 | return Commander; 232 | } 233 | 234 | void ARpgDemoGameMode::UnbindEntityQueryCallback() 235 | { 236 | if (entityQueryCallback != -1) 237 | { 238 | auto SpatialOS = GetSpatialOS(); 239 | if (SpatialOS != nullptr) 240 | { 241 | auto LockedDispatcher = SpatialOS->GetView().Pin(); 242 | 243 | if (LockedDispatcher.IsValid()) 244 | { 245 | LockedDispatcher->Remove(entityQueryCallback); 246 | entityQueryCallback = -1; 247 | } 248 | } 249 | } 250 | } 251 | 252 | USpatialOS* ARpgDemoGameMode::GetSpatialOS() const 253 | { 254 | auto gameInstance = Cast(GetWorld()->GetGameInstance()); 255 | 256 | if (gameInstance != nullptr) 257 | { 258 | return gameInstance->GetSpatialOS(); 259 | } 260 | 261 | return nullptr; 262 | } -------------------------------------------------------------------------------- /workers/unreal/Game/Source/RpgDemo/RpgDemoGameMode.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | #pragma once 3 | 4 | #include "EntityId.h" 5 | #include "EntityTemplate.h" 6 | #include "SpatialOS.h" 7 | #include "RpgDemoGameMode.generated.h" 8 | 9 | DECLARE_DYNAMIC_DELEGATE_ThreeParams(FGetSpawnerEntityIdResultDelegate, bool, success, FString, errorMessage, FEntityId, spawnerEntityId); 10 | 11 | UCLASS(minimalapi) 12 | class ARpgDemoGameMode : public AGameModeBase 13 | { 14 | GENERATED_BODY() 15 | 16 | public: 17 | ARpgDemoGameMode(); 18 | 19 | UFUNCTION(BlueprintCallable, Category = "RpgDemoGameMode") 20 | FString GetSpatialOsWorkerType() const; 21 | 22 | UFUNCTION(BlueprintImplementableEvent, Category = "RpgDemoGameMode") 23 | void OnSpatialOsConnected(); 24 | 25 | UFUNCTION(BlueprintImplementableEvent, Category = "RpgDemoGameMode") 26 | void OnSpatialOsDisconnected(); 27 | 28 | UFUNCTION(BlueprintImplementableEvent, Category = "RpgDemoGameMode") 29 | void OnSpatialOsFailedToConnect(); 30 | 31 | UFUNCTION(BlueprintPure, Category = "RpgDemoGameMode") 32 | UEntityTemplate* CreatePlayerEntityTemplate(FString clientWorkerId, const FVector& position); 33 | 34 | UFUNCTION(BlueprintCallable, Category = "RpgDemoGameMode") 35 | void GetSpawnerEntityId(const FGetSpawnerEntityIdResultDelegate& callback, int timeoutMs); 36 | 37 | void StartPlay() override; 38 | 39 | void EndPlay(const EEndPlayReason::Type EndPlayReason) override; 40 | 41 | void Tick(float DeltaTime) override; 42 | 43 | UFUNCTION(BlueprintPure, Category = "RpgDemoGameMode") 44 | bool IsConnectedToSpatialOs() const; 45 | 46 | UFUNCTION(BlueprintPure, Category = "RpgDemoGameMode") 47 | UCommander* SendWorkerCommand(); 48 | 49 | UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, NoClear) 50 | FString WorkerTypeOverride; 51 | 52 | UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, NoClear) 53 | FString WorkerIdOverride; 54 | 55 | /* 56 | * Note: The flag UseExternalIp is required to connect to a deployment using 57 | * the terminal command `spatial connect`. It is also required when you launch 58 | * your worker using the SpatialOS launcher. 59 | */ 60 | UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, NoClear) 61 | bool UseExternalIp; 62 | 63 | private: 64 | DECLARE_DELEGATE(FUnbindDelegate); 65 | 66 | USpatialOS* GetSpatialOS() const; 67 | 68 | UPROPERTY() 69 | UCommander* Commander; 70 | 71 | FGetSpawnerEntityIdResultDelegate* GetSpawnerEntityIdResultCallback; 72 | 73 | FUnbindDelegate UnbindEntityQueryDelegate; 74 | void UnbindEntityQueryCallback(); 75 | std::uint64_t entityQueryCallback; 76 | }; -------------------------------------------------------------------------------- /workers/unreal/Game/Source/RpgDemo/RpgDemoPlayerController.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | 3 | #include "RpgDemoPlayerController.h" 4 | 5 | #include "AI/Navigation/NavigationSystem.h" 6 | #include "RpgDemo.h" 7 | 8 | ARpgDemoPlayerController::ARpgDemoPlayerController() 9 | { 10 | bShowMouseCursor = true; 11 | DefaultMouseCursor = EMouseCursor::Crosshairs; 12 | } 13 | 14 | void ARpgDemoPlayerController::PlayerTick(float DeltaTime) 15 | { 16 | Super::PlayerTick(DeltaTime); 17 | 18 | // keep updating the destination every tick while desired 19 | if (bMoveToMouseCursor) 20 | { 21 | MoveToMouseCursor(); 22 | } 23 | } 24 | 25 | void ARpgDemoPlayerController::SetupInputComponent() 26 | { 27 | // set up gameplay key bindings 28 | Super::SetupInputComponent(); 29 | 30 | InputComponent->BindAction("SetDestination", IE_Pressed, this, 31 | &ARpgDemoPlayerController::OnSetDestinationPressed); 32 | InputComponent->BindAction("SetDestination", IE_Released, this, 33 | &ARpgDemoPlayerController::OnSetDestinationReleased); 34 | 35 | // support touch devices 36 | InputComponent->BindTouch(EInputEvent::IE_Pressed, this, 37 | &ARpgDemoPlayerController::MoveToTouchLocation); 38 | InputComponent->BindTouch(EInputEvent::IE_Repeat, this, 39 | &ARpgDemoPlayerController::MoveToTouchLocation); 40 | } 41 | 42 | void ARpgDemoPlayerController::MoveToMouseCursor() 43 | { 44 | // Trace to see what is under the mouse cursor 45 | FHitResult Hit; 46 | GetHitResultUnderCursor(ECC_Visibility, false, Hit); 47 | 48 | if (Hit.bBlockingHit) 49 | { 50 | // We hit something, move there 51 | SetNewMoveDestination(Hit.ImpactPoint); 52 | } 53 | } 54 | 55 | void ARpgDemoPlayerController::MoveToTouchLocation(const ETouchIndex::Type FingerIndex, 56 | const FVector Location) 57 | { 58 | FVector2D ScreenSpaceLocation(Location); 59 | 60 | // Trace to see what is under the touch location 61 | FHitResult HitResult; 62 | GetHitResultAtScreenPosition(ScreenSpaceLocation, CurrentClickTraceChannel, true, HitResult); 63 | if (HitResult.bBlockingHit) 64 | { 65 | // We hit something, move there 66 | SetNewMoveDestination(HitResult.ImpactPoint); 67 | } 68 | } 69 | 70 | void ARpgDemoPlayerController::SetNewMoveDestination(const FVector& DestLocation) 71 | { 72 | APawn* const pawn = GetPawn(); 73 | if (pawn != nullptr) 74 | { 75 | UNavigationSystem* const NavSys = GetWorld()->GetNavigationSystem(); 76 | float const Distance = FVector::Dist(DestLocation, pawn->GetActorLocation()); 77 | 78 | // We need to issue move command only if far enough in order for walk animation to play 79 | // correctly 80 | if (NavSys && (Distance > 120.0f)) 81 | { 82 | NavSys->SimpleMoveToLocation(this, DestLocation); 83 | } 84 | } 85 | } 86 | 87 | void ARpgDemoPlayerController::OnSetDestinationPressed() 88 | { 89 | // set flag to keep updating destination until released 90 | bMoveToMouseCursor = true; 91 | } 92 | 93 | void ARpgDemoPlayerController::OnSetDestinationReleased() 94 | { 95 | // clear flag to indicate we should stop updating the destination 96 | bMoveToMouseCursor = false; 97 | } 98 | -------------------------------------------------------------------------------- /workers/unreal/Game/Source/RpgDemo/RpgDemoPlayerController.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | #pragma once 3 | #include "GameFramework/PlayerController.h" 4 | #include "RpgDemoPlayerController.generated.h" 5 | 6 | UCLASS() 7 | class ARpgDemoPlayerController : public APlayerController 8 | { 9 | GENERATED_BODY() 10 | 11 | public: 12 | ARpgDemoPlayerController(); 13 | 14 | protected: 15 | /** True if the controlled character should navigate to the mouse cursor. */ 16 | uint32 bMoveToMouseCursor : 1; 17 | 18 | // Begin PlayerController interface 19 | virtual void PlayerTick(float DeltaTime) override; 20 | virtual void SetupInputComponent() override; 21 | // End PlayerController interface 22 | 23 | /** Navigate player to the current mouse cursor location. */ 24 | void MoveToMouseCursor(); 25 | 26 | /** Navigate player to the current touch location. */ 27 | void MoveToTouchLocation(const ETouchIndex::Type FingerIndex, const FVector Location); 28 | 29 | /** Navigate player to the given world location. */ 30 | void SetNewMoveDestination(const FVector& DestLocation); 31 | 32 | /** Input handlers for SetDestination action. */ 33 | void OnSetDestinationPressed(); 34 | void OnSetDestinationReleased(); 35 | }; 36 | -------------------------------------------------------------------------------- /workers/unreal/Game/Source/RpgDemoEditor.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | public class RpgDemoEditorTarget : TargetRules 7 | { 8 | public RpgDemoEditorTarget(TargetInfo Target) : base(Target) 9 | { 10 | Type = TargetType.Editor; 11 | ExtraModuleNames.AddRange(new[] { "RpgDemo" }); 12 | } 13 | } -------------------------------------------------------------------------------- /workers/unreal/Game/Source/RpgDemoServer.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Improbable Worlds Ltd, All Rights Reserved 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | public class RpgDemoServerTarget : TargetRules 7 | { 8 | public RpgDemoServerTarget(TargetInfo Target) : base(Target) 9 | { 10 | Type = TargetType.Server; 11 | ExtraModuleNames.AddRange(new[] { "RpgDemo" }); 12 | } 13 | } -------------------------------------------------------------------------------- /workers/unreal/spatialos.UnrealClient.worker.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "tasks_filename": "Game/Intermediate/Improbable/spatialos.unreal.client.build.json", 4 | "generated_build_scripts_type": "unreal" 5 | }, 6 | "bridge": { 7 | "worker_attribute_set": { 8 | "attributes": [ 9 | "UnrealClient" 10 | ] 11 | }, 12 | "entity_interest": { 13 | "range_entity_interest": { 14 | "radius": 2 15 | } 16 | }, 17 | "streaming_query": [], 18 | "component_delivery": { 19 | "default": "RELIABLE_ORDERED", 20 | "checkout_all_initially": true 21 | } 22 | }, 23 | "external": { 24 | "default": { 25 | "run_type": "EXECUTABLE", 26 | "windows": { 27 | "command": "${UNREAL_HOME}\\Engine\\Binaries\\Win64\\UE4Editor.exe", 28 | "arguments": [ 29 | "${IMPROBABLE_WORKER_DIR}/Game/RpgDemo.uproject", 30 | "-game", 31 | "-stdout", 32 | "+workerType", 33 | "UnrealClient" 34 | ] 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /workers/unreal/spatialos.UnrealWorker.worker.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "tasks_filename": "Game/Intermediate/Improbable/spatialos.unreal.worker.build.json", 4 | "generated_build_scripts_type": "unreal" 5 | }, 6 | "bridge": { 7 | "worker_attribute_set": { 8 | "attributes": [ 9 | "UnrealWorker" 10 | ] 11 | }, 12 | "entity_interest": { 13 | "range_entity_interest": { 14 | "radius": 2 15 | } 16 | }, 17 | "streaming_query": [], 18 | "component_delivery": { 19 | "default": "RELIABLE_ORDERED", 20 | "checkout_all_initially": true 21 | } 22 | }, 23 | "managed": { 24 | "windows": { 25 | "artifact_name": "UnrealEditor@Windows.zip", 26 | "command": "StartServer.bat", 27 | "arguments": [ 28 | "-server", 29 | "-stdout", 30 | "-nowrite", 31 | "-unattended", 32 | "-nologtimes", 33 | "-nopause", 34 | "-messaging", 35 | "-SaveToUserDir", 36 | "+workerType", 37 | "UnrealWorker", 38 | "+appName", 39 | "${IMPROBABLE_PROJECT_NAME}", 40 | "+receptionistIp", 41 | "${IMPROBABLE_RECEPTIONIST_HOST}", 42 | "+receptionistPort", 43 | "${IMPROBABLE_RECEPTIONIST_PORT}", 44 | "+workerType", 45 | "${IMPROBABLE_WORKER_NAME}", 46 | "+workerId", 47 | "${IMPROBABLE_WORKER_ID}", 48 | "+linkProtocol", 49 | "RakNet", 50 | "log=${IMPROBABLE_LOG_FILE}" 51 | ] 52 | }, 53 | "linux": { 54 | "artifact_name": "UnrealWorker@Linux.zip", 55 | "command": "StartServer.sh", 56 | "arguments": [ 57 | "${IMPROBABLE_WORKER_ID}", 58 | "RpgDemo", 59 | "+appName", 60 | "${IMPROBABLE_PROJECT_NAME}", 61 | "+receptionistIp", 62 | "${IMPROBABLE_RECEPTIONIST_HOST}", 63 | "+receptionistPort", 64 | "${IMPROBABLE_RECEPTIONIST_PORT}", 65 | "+workerType", 66 | "${IMPROBABLE_WORKER_NAME}", 67 | "+workerId", 68 | "${IMPROBABLE_WORKER_ID}", 69 | "+linkProtocol", 70 | "RakNet" 71 | ] 72 | } 73 | }, 74 | "external": { 75 | "default": { 76 | "run_type": "EXECUTABLE", 77 | "windows": { 78 | "command": "${UNREAL_HOME}\\Engine\\Binaries\\Win64\\UE4Editor.exe", 79 | "arguments": [ 80 | "${IMPROBABLE_WORKER_DIR}/Game/RpgDemo.uproject", 81 | "-server", 82 | "-stdout", 83 | "-nowrite", 84 | "-unattended", 85 | "-nologtimes", 86 | "-nopause", 87 | "-messaging", 88 | "-SaveToUserDir", 89 | "+workerType", 90 | "UnrealWorker", 91 | "log=UnrealWorker.log" 92 | ] 93 | } 94 | } 95 | } 96 | } 97 | --------------------------------------------------------------------------------