From 20ef7cfbaafd03dff91428fb06e1a6d3de808cd6 Mon Sep 17 00:00:00 2001 From: Jason Larabie Date: Mon, 22 Sep 2025 13:16:33 -0700 Subject: [PATCH] Add Unreal Blackholio (#3260) # Description of Changes Closes: https://github.com/clockworklabs/SpacetimeDBPrivate/issues/1925 This adds Unreal Blackholio to the demo/Blackholio folder matching the Unity version using server-rust as the default. Includes: - Leaderboard - Circle Material - wavy border - Split/Sucide/Input Lock - Parallax Background - Camera movement based on total mass # API and ABI breaking changes No changes # Expected complexity level and risk 1 - Only adds the demo has no impact elsewise # Testing I've tested this backwards and forwards against the server-rust version and played alongside the Unity client. - [ ] Run it locally for review --------- Co-authored-by: John Detter <4099508+jdetter@users.noreply.github.com> --- demo/Blackholio/.gitignore | 31 + .../client-unreal/Config/DefaultEditor.ini | 0 .../client-unreal/Config/DefaultEngine.ini | 95 ++ .../client-unreal/Config/DefaultGame.ini | 6 + .../client-unreal/Config/DefaultInput.ini | 84 ++ .../Content/BP_BlackholioGameMode.uasset | Bin 0 -> 21368 bytes .../client-unreal/Content/BP_Circle.uasset | Bin 0 -> 48560 bytes .../client-unreal/Content/BP_Food.uasset | Bin 0 -> 25220 bytes .../Content/BP_GameManager.uasset | Bin 0 -> 24403 bytes .../Content/BP_PlayerController.uasset | Bin 0 -> 22069 bytes .../Content/BP_PlayerPawn.uasset | Bin 0 -> 24282 bytes .../client-unreal/Content/Blackholio.umap | Bin 0 -> 30877 bytes .../client-unreal/Content/Circle.uasset | Bin 0 -> 15221 bytes .../Content/Circle_Sprite.uasset | Bin 0 -> 10071 bytes .../Gameplay/BP_ParallaxBackground.uasset | Bin 0 -> 25398 bytes .../Content/Gameplay/StarBackground.uasset | Bin 0 -> 98336 bytes .../Gameplay/StarBackground_Sprite.uasset | Bin 0 -> 14346 bytes .../Content/Gameplay/WBP_Leaderboard.uasset | Bin 0 -> 29170 bytes .../Gameplay/WBP_LeaderboardRow.uasset | Bin 0 -> 32477 bytes .../Content/Gameplay/WBP_Respawn.uasset | Bin 0 -> 29927 bytes .../Gameplay/WBP_UsernameChooser.uasset | Bin 0 -> 40116 bytes .../Content/Input/IA_InputLock.uasset | Bin 0 -> 1151 bytes .../Content/Input/IA_Split.uasset | Bin 0 -> 1131 bytes .../Content/Input/IA_Suicide.uasset | Bin 0 -> 1141 bytes .../Content/Input/IMC_Main.uasset | Bin 0 -> 3494 bytes .../Content/MFI_WavyOutline_Inst.uasset | Bin 0 -> 6283 bytes .../Content/MF_WavyOutline.uasset | Bin 0 -> 26798 bytes .../client-unreal/Content/MI_Circle.uasset | Bin 0 -> 6696 bytes .../client-unreal/Content/MI_Food.uasset | Bin 0 -> 8431 bytes .../client-unreal/Content/M_Circle.uasset | Bin 0 -> 37207 bytes .../Content/WBP_Nameplate.uasset | Bin 0 -> 43242 bytes .../Source/client_unreal.Target.cs | 15 + .../Private/BlackholioGameMode.cpp | 2 + .../Private/BlackholioPlayerController.cpp | 196 +++ .../Source/client_unreal/Private/Circle.cpp | 53 + .../Source/client_unreal/Private/Entity.cpp | 110 ++ .../Source/client_unreal/Private/Food.cpp | 26 + .../client_unreal/Private/GameManager.cpp | 341 +++++ .../Private/Gameplay/LeaderboardRowWidget.cpp | 15 + .../Private/Gameplay/LeaderboardWidget.cpp | 134 ++ .../Private/Gameplay/ParallaxBackground.cpp | 31 + .../Private/Gameplay/RespawnWidget.cpp | 23 + .../Gameplay/UsernameChooserWidget.cpp | 56 + .../ModuleBindings/SpacetimeDBClient.g.cpp | 927 ++++++++++++ .../Tables/CircleDecayTimerTable.g.cpp | 47 + .../Tables/CircleRecombineTimerTable.g.cpp | 47 + .../ModuleBindings/Tables/CircleTable.g.cpp | 60 + .../ModuleBindings/Tables/ConfigTable.g.cpp | 47 + .../Tables/ConsumeEntityTimerTable.g.cpp | 47 + .../ModuleBindings/Tables/EntityTable.g.cpp | 47 + .../ModuleBindings/Tables/FoodTable.g.cpp | 47 + .../Tables/MoveAllPlayersTimerTable.g.cpp | 47 + .../ModuleBindings/Tables/PlayerTable.g.cpp | 52 + .../Tables/SpawnFoodTimerTable.g.cpp | 47 + .../client_unreal/Private/PlayerPawn.cpp | 216 +++ .../client_unreal/Public/BlackholioGameMode.h | 15 + .../Public/BlackholioPlayerController.h | 78 + .../Source/client_unreal/Public/Circle.h | 38 + .../Source/client_unreal/Public/DbVector2.h | 29 + .../Source/client_unreal/Public/Entity.h | 53 + .../Source/client_unreal/Public/Food.h | 20 + .../Source/client_unreal/Public/GameManager.h | 126 ++ .../Public/Gameplay/LeaderboardRowWidget.h | 23 + .../Public/Gameplay/LeaderboardWidget.h | 52 + .../Public/Gameplay/ParallaxBackground.h | 23 + .../Public/Gameplay/RespawnWidget.h | 22 + .../Public/Gameplay/UsernameChooserWidget.h | 32 + .../Public/ModuleBindings/ReducerBase.g.h | 18 + .../ModuleBindings/Reducers/CircleDecay.g.h | 54 + .../Reducers/CircleRecombine.g.h | 54 + .../ModuleBindings/Reducers/Connect.g.h | 43 + .../ModuleBindings/Reducers/ConsumeEntity.g.h | 54 + .../ModuleBindings/Reducers/Disconnect.g.h | 43 + .../ModuleBindings/Reducers/EnterGame.g.h | 53 + .../Reducers/MoveAllPlayers.g.h | 54 + .../ModuleBindings/Reducers/PlayerSplit.g.h | 43 + .../ModuleBindings/Reducers/Respawn.g.h | 43 + .../ModuleBindings/Reducers/SpawnFood.g.h | 54 + .../ModuleBindings/Reducers/Suicide.g.h | 43 + .../Reducers/UpdatePlayerInput.g.h | 54 + .../ModuleBindings/SpacetimeDBClient.g.h | 1264 +++++++++++++++++ .../Tables/CircleDecayTimerTable.g.h | 104 ++ .../Tables/CircleRecombineTimerTable.g.h | 104 ++ .../ModuleBindings/Tables/CircleTable.g.h | 141 ++ .../ModuleBindings/Tables/ConfigTable.g.h | 104 ++ .../Tables/ConsumeEntityTimerTable.g.h | 104 ++ .../ModuleBindings/Tables/EntityTable.g.h | 104 ++ .../ModuleBindings/Tables/FoodTable.g.h | 104 ++ .../Tables/MoveAllPlayersTimerTable.g.h | 104 ++ .../ModuleBindings/Tables/PlayerTable.g.h | 144 ++ .../Tables/SpawnFoodTimerTable.g.h | 104 ++ .../Types/CircleDecayTimerType.g.h | 50 + .../Types/CircleRecombineTimerType.g.h | 54 + .../ModuleBindings/Types/CircleType.g.h | 63 + .../ModuleBindings/Types/ConfigType.g.h | 49 + .../Types/ConsumeEntityTimerType.g.h | 58 + .../ModuleBindings/Types/DbVector2Type.g.h | 49 + .../ModuleBindings/Types/EntityType.g.h | 54 + .../Public/ModuleBindings/Types/FoodType.g.h | 45 + .../Types/MoveAllPlayersTimerType.g.h | 50 + .../ModuleBindings/Types/PlayerType.g.h | 54 + .../Types/SpawnFoodTimerType.g.h | 50 + .../Source/client_unreal/Public/PlayerPawn.h | 61 + .../client_unreal/client_unreal.Build.cs | 37 + .../Source/client_unreal/client_unreal.cpp | 6 + .../Source/client_unreal/client_unreal.h | 6 + .../Source/client_unrealEditor.Target.cs | 15 + .../client-unreal/client_unreal.uproject | 28 + demo/Blackholio/server-csharp/generate.bat | 2 + demo/Blackholio/server-csharp/generate.sh | 1 + demo/Blackholio/server-csharp/publish.bat | 1 + demo/Blackholio/server-rust/generate.bat | 1 + demo/Blackholio/server-rust/generate.sh | 1 + 113 files changed, 7086 insertions(+) create mode 100644 demo/Blackholio/client-unreal/Config/DefaultEditor.ini create mode 100644 demo/Blackholio/client-unreal/Config/DefaultEngine.ini create mode 100644 demo/Blackholio/client-unreal/Config/DefaultGame.ini create mode 100644 demo/Blackholio/client-unreal/Config/DefaultInput.ini create mode 100644 demo/Blackholio/client-unreal/Content/BP_BlackholioGameMode.uasset create mode 100644 demo/Blackholio/client-unreal/Content/BP_Circle.uasset create mode 100644 demo/Blackholio/client-unreal/Content/BP_Food.uasset create mode 100644 demo/Blackholio/client-unreal/Content/BP_GameManager.uasset create mode 100644 demo/Blackholio/client-unreal/Content/BP_PlayerController.uasset create mode 100644 demo/Blackholio/client-unreal/Content/BP_PlayerPawn.uasset create mode 100644 demo/Blackholio/client-unreal/Content/Blackholio.umap create mode 100644 demo/Blackholio/client-unreal/Content/Circle.uasset create mode 100644 demo/Blackholio/client-unreal/Content/Circle_Sprite.uasset create mode 100644 demo/Blackholio/client-unreal/Content/Gameplay/BP_ParallaxBackground.uasset create mode 100644 demo/Blackholio/client-unreal/Content/Gameplay/StarBackground.uasset create mode 100644 demo/Blackholio/client-unreal/Content/Gameplay/StarBackground_Sprite.uasset create mode 100644 demo/Blackholio/client-unreal/Content/Gameplay/WBP_Leaderboard.uasset create mode 100644 demo/Blackholio/client-unreal/Content/Gameplay/WBP_LeaderboardRow.uasset create mode 100644 demo/Blackholio/client-unreal/Content/Gameplay/WBP_Respawn.uasset create mode 100644 demo/Blackholio/client-unreal/Content/Gameplay/WBP_UsernameChooser.uasset create mode 100644 demo/Blackholio/client-unreal/Content/Input/IA_InputLock.uasset create mode 100644 demo/Blackholio/client-unreal/Content/Input/IA_Split.uasset create mode 100644 demo/Blackholio/client-unreal/Content/Input/IA_Suicide.uasset create mode 100644 demo/Blackholio/client-unreal/Content/Input/IMC_Main.uasset create mode 100644 demo/Blackholio/client-unreal/Content/MFI_WavyOutline_Inst.uasset create mode 100644 demo/Blackholio/client-unreal/Content/MF_WavyOutline.uasset create mode 100644 demo/Blackholio/client-unreal/Content/MI_Circle.uasset create mode 100644 demo/Blackholio/client-unreal/Content/MI_Food.uasset create mode 100644 demo/Blackholio/client-unreal/Content/M_Circle.uasset create mode 100644 demo/Blackholio/client-unreal/Content/WBP_Nameplate.uasset create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal.Target.cs create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/BlackholioGameMode.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/BlackholioPlayerController.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/Circle.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/Entity.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/Food.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/GameManager.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/LeaderboardRowWidget.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/LeaderboardWidget.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/ParallaxBackground.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/RespawnWidget.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/UsernameChooserWidget.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/SpacetimeDBClient.g.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/CircleDecayTimerTable.g.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/CircleRecombineTimerTable.g.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/CircleTable.g.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/ConfigTable.g.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/ConsumeEntityTimerTable.g.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/EntityTable.g.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/FoodTable.g.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/MoveAllPlayersTimerTable.g.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/PlayerTable.g.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/SpawnFoodTimerTable.g.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Private/PlayerPawn.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/BlackholioGameMode.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/BlackholioPlayerController.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/Circle.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/DbVector2.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/Entity.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/Food.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/GameManager.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/LeaderboardRowWidget.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/LeaderboardWidget.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/ParallaxBackground.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/RespawnWidget.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/UsernameChooserWidget.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/ReducerBase.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/CircleDecay.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/CircleRecombine.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Connect.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/ConsumeEntity.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Disconnect.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/EnterGame.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/MoveAllPlayers.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/PlayerSplit.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Respawn.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/SpawnFood.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Suicide.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/UpdatePlayerInput.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/SpacetimeDBClient.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/CircleDecayTimerTable.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/CircleRecombineTimerTable.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/CircleTable.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/ConfigTable.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/ConsumeEntityTimerTable.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/EntityTable.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/FoodTable.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/MoveAllPlayersTimerTable.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/PlayerTable.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/SpawnFoodTimerTable.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleDecayTimerType.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleRecombineTimerType.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleType.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/ConfigType.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/ConsumeEntityTimerType.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/DbVector2Type.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/EntityType.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/FoodType.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/MoveAllPlayersTimerType.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/PlayerType.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/SpawnFoodTimerType.g.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/PlayerPawn.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/client_unreal.Build.cs create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/client_unreal.cpp create mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/client_unreal.h create mode 100644 demo/Blackholio/client-unreal/Source/client_unrealEditor.Target.cs create mode 100644 demo/Blackholio/client-unreal/client_unreal.uproject create mode 100644 demo/Blackholio/server-csharp/generate.bat create mode 100644 demo/Blackholio/server-csharp/publish.bat diff --git a/demo/Blackholio/.gitignore b/demo/Blackholio/.gitignore index ffa6ae370..104ec592e 100644 --- a/demo/Blackholio/.gitignore +++ b/demo/Blackholio/.gitignore @@ -36,3 +36,34 @@ Temporary Items *.icloud # End of https://www.toptal.com/developers/gitignore/api/macos + +# Unreal plugin build artifacts +Binaries/ +Intermediate/ +DerivedDataCache/ +Saved/ +.vs/ +.idea/ +*.VC.db +*.VC.opendb +*.sln +*.opensdf +*.sdf +*.suo +*.user +*.userprefs +*.log +*.tlog +*.ipch +*.obj +*.pch +*.pdb +*.idb +*.aps +*.ncb +*.cache +*.sbr +*.db +*.xcodeproj +xcuserdata/ +/sdks/unreal/tests/TestClient/TestResults \ No newline at end of file diff --git a/demo/Blackholio/client-unreal/Config/DefaultEditor.ini b/demo/Blackholio/client-unreal/Config/DefaultEditor.ini new file mode 100644 index 000000000..e69de29bb diff --git a/demo/Blackholio/client-unreal/Config/DefaultEngine.ini b/demo/Blackholio/client-unreal/Config/DefaultEngine.ini new file mode 100644 index 000000000..edd23f942 --- /dev/null +++ b/demo/Blackholio/client-unreal/Config/DefaultEngine.ini @@ -0,0 +1,95 @@ + + +[/Script/EngineSettings.GameMapsSettings] +GameDefaultMap=/Game/Blackholio.Blackholio +EditorStartupMap=/Game/Blackholio.Blackholio + +[/Script/Engine.RendererSettings] +r.AllowStaticLighting=False + +r.GenerateMeshDistanceFields=True + +r.DynamicGlobalIlluminationMethod=1 + +r.ReflectionMethod=1 + +r.SkinCache.CompileShaders=True + +r.RayTracing=True + +r.RayTracing.RayTracingProxies.ProjectEnabled=True + +r.Shadow.Virtual.Enable=1 + +r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True + +r.DefaultFeature.LocalExposure.HighlightContrastScale=0.8 + +r.DefaultFeature.LocalExposure.ShadowContrastScale=0.8 + +[/Script/WindowsTargetPlatform.WindowsTargetSettings] +DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 +DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 +-D3D12TargetedShaderFormats=PCD3D_SM5 ++D3D12TargetedShaderFormats=PCD3D_SM6 +-D3D11TargetedShaderFormats=PCD3D_SM5 ++D3D11TargetedShaderFormats=PCD3D_SM5 +Compiler=Default +AudioSampleRate=48000 +AudioCallbackBufferFrameSize=1024 +AudioNumBuffersToEnqueue=1 +AudioMaxChannels=0 +AudioNumSourceWorkers=4 +SpatializationPlugin= +SourceDataOverridePlugin= +ReverbPlugin= +OcclusionPlugin= +CompressionOverrides=(bOverrideCompressionTimes=False,DurationThreshold=5.000000,MaxNumRandomBranches=0,SoundCueQualityIndex=0) +CacheSizeKB=65536 +MaxChunkSizeOverrideKB=0 +bResampleForDevice=False +MaxSampleRate=48000.000000 +HighSampleRate=32000.000000 +MedSampleRate=24000.000000 +LowSampleRate=12000.000000 +MinSampleRate=8000.000000 +CompressionQualityModifier=1.000000 +AutoStreamingThreshold=0.000000 +SoundCueCookQualityIndex=-1 + +[/Script/LinuxTargetPlatform.LinuxTargetSettings] +-TargetedRHIs=SF_VULKAN_SM5 ++TargetedRHIs=SF_VULKAN_SM6 + +[/Script/HardwareTargeting.HardwareTargetingSettings] +TargetedHardwareClass=Desktop +AppliedTargetedHardwareClass=Desktop +DefaultGraphicsPerformance=Maximum +AppliedDefaultGraphicsPerformance=Maximum + +[/Script/WorldPartitionEditor.WorldPartitionEditorSettings] +CommandletClass=Class'/Script/UnrealEd.WorldPartitionConvertCommandlet' + +[/Script/Engine.UserInterfaceSettings] +bAuthorizeAutomaticWidgetVariableCreation=False +FontDPIPreset=Standard +FontDPI=72 + +[/Script/Engine.Engine] ++ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/client_unreal") ++ActiveGameNameRedirects=(OldGameName="/Script/TP_Blank",NewGameName="/Script/client_unreal") + +[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] +bEnablePlugin=True +bAllowNetworkConnection=True +SecurityToken=44F0FBC34B5D09A59F02A0AA6395FF14 +bIncludeInShipping=False +bAllowExternalStartInShipping=False +bCompileAFSProject=False +bUseCompression=False +bLogFiles=False +bReportStats=False +ConnectionType=USBOnly +bUseManualIPAddress=False +ManualIPAddress= + diff --git a/demo/Blackholio/client-unreal/Config/DefaultGame.ini b/demo/Blackholio/client-unreal/Config/DefaultGame.ini new file mode 100644 index 000000000..2e8bc0d95 --- /dev/null +++ b/demo/Blackholio/client-unreal/Config/DefaultGame.ini @@ -0,0 +1,6 @@ + +[/Script/CommonUI.CommonUISettings] +CommonButtonAcceptKeyHandling=TriggerClick + +[/Script/EngineSettings.GeneralProjectSettings] +ProjectID=1CB09ED64584D03E3ED8AA86458B2D83 diff --git a/demo/Blackholio/client-unreal/Config/DefaultInput.ini b/demo/Blackholio/client-unreal/Config/DefaultInput.ini new file mode 100644 index 000000000..a919105de --- /dev/null +++ b/demo/Blackholio/client-unreal/Config/DefaultInput.ini @@ -0,0 +1,84 @@ +[/Script/Engine.InputSettings] +-AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) +-AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) +-AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) ++AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseWheelAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_Special_Left_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_Special_Left_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) +bAltEnterTogglesFullscreen=True +bF11TogglesFullscreen=True +bUseMouseForTouch=False +bEnableMouseSmoothing=True +bEnableFOVScaling=True +bCaptureMouseOnLaunch=True +bEnableLegacyInputScales=True +bEnableMotionControls=True +bFilterInputByPlatformUser=False +bShouldFlushPressedKeysOnViewportFocusLost=True +bAlwaysShowTouchInterface=False +bShowConsoleOnFourFingerTap=True +bEnableGestureRecognizer=False +bUseAutocorrect=False +DefaultViewportMouseCaptureMode=CapturePermanently_IncludingInitialMouseDown +DefaultViewportMouseLockMode=LockOnCapture +FOVScale=0.011110 +DoubleClickTime=0.200000 +DefaultPlayerInputClass=/Script/EnhancedInput.EnhancedPlayerInput +DefaultInputComponentClass=/Script/EnhancedInput.EnhancedInputComponent +DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.DefaultVirtualJoysticks +-ConsoleKeys=Tilde ++ConsoleKeys=Tilde + diff --git a/demo/Blackholio/client-unreal/Content/BP_BlackholioGameMode.uasset b/demo/Blackholio/client-unreal/Content/BP_BlackholioGameMode.uasset new file mode 100644 index 0000000000000000000000000000000000000000..0a1dfd117c37c8423d2a157d07a95369f0adeead GIT binary patch literal 21368 zcmeHP33yx8mA)bggb-+;VJ}1u*+aa@d)Q)0mX|o*mn0AzS$eV+%X*IVWXAzADNX4{ zr-in3T3TqCk0~YT5SBnXElavghjal-p)E{-PH8B#OiN){nn33K_ucc}lP$>#PW$!a z>-8ty_s%`{o^#K3&waYj`#!YnrZ)~BKD=?75VMXE;w`FCW_0Z@IdsK0l224t-FDht z$KHI$qMOz}>?Is(yKvv-l?UseKlV>M#GY-Nmo1u3u=Wj4t*dYFoU&!zmc5^NaqsZ) z1iR*pb2jd13GaI5@zZX9@&_-S_z1y1_{3Gwoi!~7{?OKV`;vR_dti)U^PlMa(gQ7v zZVMb&@YzSNz9)D-!Jha0V)uzHADegIwnui|()e}-=w4zRdSttQx9{1x*L^1GKIim> z1oK{g<1Yeh?q6`*u2Xi5ogRL+ieN7f?0)`5?`K~-;i-~aYQ7aHTtcvc(99RsHk~&( ze0KGY>n>k(+Q|g_0DX$;f+zNce3b!Dd5x#6wyM-aC}u&Y|2|cS_f!4+DJE>!S$6&K zi9+DsMW8=X{U&|po-M?(Ywx^%RqsRI-6#EUzxYy1H`*~4n3*AtCH&W-w;qE5moM+C zEnnWQ#k62jTTxzEA=`!cvroSMS+W!1)%BP)>p9bRe;i`# zYZ=iJiD+00q~akt@j0)5`clj-vq=}1?78fWY^}XMQE=B+J_HT+kuXxhSad7tVrW>8 zlLW=ik5oJ*85uJ)i;)A(D;|QDe5AI1JuxQU*!J1)1B(I8Witlt@F%5e3lk&9?p-HRlCNtL)9>36D|Ilg*6~Z4)68B?L0$V?Kz>6?Y z8;%-U*sq6Du*!ybL>J%copTVJnoXv=v<8g<@spaHw}WeI-s~df&wD0SiSJT&(XV%6qTTvhD|+QGjnYxg1;P0y4fvP|8PY1g3tL{|1yAH_2dJ} zYwV|2(r1{6WL)F&h{3z6!$xOiJOgUDJ0mpjA(<6N$jf8Z-oqr1<7LH^v~O zX7XL}+O+SV2q7uk;?P<1^xZE>aLO1?83>)yAS&xIw z2;R(?0p|alj7^XhT79_*9_|l zP#4)}KVoAmDK06_I`5WJ#3@>TppNm=S#$SB0Xpm)(yn&iu98aI0$rG_~3T`|p)4BKhI(x5MY8Urr%KDivS=;0qW+9*kucH@~M9Jen{XX5x0%lQQ9obx@w^*Xk z7u*m98p&dRvfajnwjFaG*SaF>IE9%6U^B8t4Kz6;jHx=viYM)a@zfA2Y=)T)39-rN zgIvO+GY9B2Av7u^#({}Aq9nA7eFFAxsKFeHj2VfPMtd3a2K`X11_%aa2Yrf`Et_?` zdzsrAE!;_&&7be4(BmGaZIb3TbT=&nqj7hLv%{o27!SL{x?3l#gqwnKYPe7iwJNR~ znj0&JJC)SJ?r7ZYqflUImS?*oDtQHdk#K_*#l{(ML4ok?q=WEE!r?sBoS@}MI(^2J0sKdSN} zj+5!iG^Q!tquqvEqn+iLI|8RV-yMy(4NApebHu!6Zb+Qm!&+sscI1o~Ta4#TS=3M; zlmhxp`Z#8tL>1yvRsRvsyVUi?szybRd1Tq3>Q@Nwa&_&*gN2~vwr8I_pG9K&@n%(SBSAZzv~pvxvg1Aq&4KCw@+cd z=Aegp^(pvLs+XzjJKEI6BvmeY^Htwh9rWPih=Dx67ZlDfj{?2lsImU+pojS(mNLB^ z3g@AAbumemYku1^^aZ_R>2n-?m?mn#P&?stdqf3e%WRAvG|22O15M&UKe~o4Yk+_S6Bn58` zm4^E6NY7BBt*dCLG7wn3IU4mCmF4AVGDg?0=_wjmy>6_1bfhFzQyTV{Y;7y17GGVR zx1@idwt6tpT-h1eoaouITyNewN=>zm+EDdCU1wkM;O5n%9Ru})Yu7hdm0`S^Hu`vh zR5Q}<2^i};s(oF7YF}SnU{laDFw$2SGvQq&BkSwBYU+Sl+t{|YxG}k|r#TU*RP{p60ROT5oZarT31~E*nyFu(?8mZbf-P8s%BRfP4J!>-{@f%Xt)1&yYv~CPZTMpHmq#g+ zKrGmw6j;qmJXK|tRaIp&R0pD7X>k`(9z30#4}bU3HWqcY{5E0uKHj=NLtWL0O+u%o zw2{7jVn~e8va?wvB{U_PDQN2S86aqqddyb6+URbW@BmE_ZbTT9wjQJUPL&7z{))eD z5mH=DgiJfiv|308aiZpuzy5-{4Plwv^EydUZz9PUvR_Jcim-^%2yq(8CC*V8^(4PO zl4DfVO5Dw)fj+U-8hxV(h*9dLlay^-TMwCAXQN7UR!8%d#+t{H%w9i)y;%4Q(-M78G(xkE#)p+Zp^+m zrFS=D9tUG5@#4av+cp7S&CkXH%u0A0X@}2uL$V_ zmg%F<0A1^%P1&xCB9=)dQya^PYn#TAMIxr*msAb1PnM2NKU3Q*#oNiMwk7GjVyQZH z==4RK##HCQ{IGS^lg>Ah?YSrgGNm}Ng`;rpIm}o61ENBBM7b!Tx{UtHg_ojACEW$c zLaXV|OBUdfI9{=mV3lNf9{MhkAKTBTI#=e~84GQ#)|@p*3~|!iNNI8@ zVZBdbPIXQkG3v<ItEz~kZe`cQ))yV1!XocyM z@CacQ(TY+_`a&LBKqL3lUjg|VRvl1-cc334=a3w=f85H6vEW5pX)KpuFJo!E+ck)7 z3o#7&3D!e9o^kf%Y%OrK^V*tQw(L!luexU{ElhP*%nxT2tt9zPYK^y7R6FiWhIf|Y zi^t6z57^R*Wv0xTW-&!PUD1w^?(96oCC)xd^W`kHjb;gZvU7YJ@&QGQ_Z7m$k&I{6 zN*0$Qo5Xj4%$N~32c%^n|Kp5*s@mao5IK22jXpw_oubF%uyE|%T(ti*BWHGua}|b( zM-M7od^JzCOqENveN%#dC4~X<(ZS-OM(_84@z=bZHkHrFpO%8p$ra z#5-aT_|FFCoQvDvZDe8jfi671pFTs1uX;{(2E4cBl@+-KGyy&2YFjaZKCcDYpG2`O+dGcTPXjiRBuySt6p2R2Hkd2y zu6Vt6(F0dAcI1a$sIWQS*qLs=PbhdTb9!<&Neb~YKOQ)@&V~(NaL$nP{oHF4=QHU~ z5XNg1*$w20HKka|BKNo)RXh|O$|+9N(JZ~RhcBm?QX%68qKBViOqtxt`>4$)BT6;h zSIPapk8oXtVM;wWJ=F(Ir8()VT!i=MA1bycyx5ctD5t9rE?cDS$kDpbKtx;_wK{iTPB%aK?e+r>9dj! zI&Gk}U_N=6d50E{yq-@!IA6AvsrzzuUm@==6;9kr)mEi&tJQspq5}$`VbWp>B^C|D z0B`_2rh}(|4CC=jC@wIJX?QFe9*YK^5t?`&i-yOdfoF^+FNj3IOP>v%C~`Z~^H4l8 zVWk$mQj1=xq8AZC%EXeCiD0J4moR#qYv3VBQJD3pLy2>mqV<%ybn#t?SEUCO3 zuV?AmUSDQO^hmtk9*5^-`>E6GSx%FCJ$y2o7Rpr|Nsm3)bD2^^o&<>*B0=jeM-;Y? zax1QtOTEE9uwyg(=Es?!-xemkMu+p7e4JtY36k_9D~qi+ev4|AnWs97#iH7a?C6i1TbuPCKvCBJ1O+?mgSPv{99 zbgCYRN+_V|M8siL3B+*AaYJQNWFVAD)O2J%eF`cCsvXy0lgs@8;5hDl1#xz2Rxse; z!v)+)R{8v{v(r3<#RGFEbda3@Xi!i-xr$pULLs(4$j8o)m?e<>J7Wp5{_C<-2-#6_ z+L2|Ah}|d%AZZ0hQc;|mD>gytoHS=omB=>#=x#TwapI2zq%Pun5hB2Qg`8!8~j z#p*-=c@EvDT9_Z)%JJW!5%7L0%7jJ$okP)?L$gf4e4B>3NHu2jWf@XnD|`$YHOd6V z6`An-6?fOo(dVB0%r6>mh|a#@>TBm-fApA!GAykeJ%)D_hwm6%a$EC*pDz1da?CX? zjbY?1C{DY@8)qocGGxdlfQt%sH$g3Ll3L1DW3{SL$ywDZ0MkYij51ywbIvp2^s9zh zK!1U{9?uU}8ZMH`LUo7pdV|nyz8_aO%by7LyY)!KJ&bn>>1Eu$R5TX0UZbUd*IV`Ah0=tqs#evyZ ze>W4%`RYRY9G0k|GR^^QSsPTxOw|$1LGA`Hic`CP1+NMyPI*FEz3M`RI3dG&lp6lw za;8N(t#S^@W#;s*&bu#t;Ph3WyWxlP=iD5>gT)MC%9W04gqkN zzxRK5?7DABwgUf1 zO1_l8RJihCGQoqBF|KGgWQV_dmFl Ir2zc@2h5heN&o-= literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/BP_Circle.uasset b/demo/Blackholio/client-unreal/Content/BP_Circle.uasset new file mode 100644 index 0000000000000000000000000000000000000000..bc3cb9285348d65b749b30c1ea8bbc310fc888f3 GIT binary patch literal 48560 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 Y&^0@n#utlU&z;fl$={Am(*WTAKUseW-T(jq literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/BP_Food.uasset b/demo/Blackholio/client-unreal/Content/BP_Food.uasset new file mode 100644 index 0000000000000000000000000000000000000000..8e9e591f87597b6bde36246925e10d924cbe7aea GIT binary patch literal 25220 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 Bmt_C| literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/BP_GameManager.uasset b/demo/Blackholio/client-unreal/Content/BP_GameManager.uasset new file mode 100644 index 0000000000000000000000000000000000000000..cbf4de166e118ccc2c86195853479418cc0be9c6 GIT binary patch literal 24403 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 literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/BP_PlayerController.uasset b/demo/Blackholio/client-unreal/Content/BP_PlayerController.uasset new file mode 100644 index 0000000000000000000000000000000000000000..5b509f27f8978356d2790817f7ffe23f751df960 GIT binary patch literal 22069 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 literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/BP_PlayerPawn.uasset b/demo/Blackholio/client-unreal/Content/BP_PlayerPawn.uasset new file mode 100644 index 0000000000000000000000000000000000000000..358b9a7641ab824e1b25f3b76cbeec2cfbb8cb02 GIT binary patch literal 24282 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 JfV(W{{|e~!MZ*99 literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/Blackholio.umap b/demo/Blackholio/client-unreal/Content/Blackholio.umap new file mode 100644 index 0000000000000000000000000000000000000000..bb7bd7420b8f5b3b8c0af5f80a9272107ea00c1b GIT binary patch literal 30877 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 literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/Circle.uasset b/demo/Blackholio/client-unreal/Content/Circle.uasset new file mode 100644 index 0000000000000000000000000000000000000000..607587dfef8f8736e51dde647dd2f667c05cf590 GIT binary patch literal 15221 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 BNeKV| literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/Circle_Sprite.uasset b/demo/Blackholio/client-unreal/Content/Circle_Sprite.uasset new file mode 100644 index 0000000000000000000000000000000000000000..53f4da29f2a3f1958d4d4b26ccd1f9fbb4ae53a1 GIT binary patch literal 10071 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 E01Hb9NdN!< literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/Gameplay/BP_ParallaxBackground.uasset b/demo/Blackholio/client-unreal/Content/Gameplay/BP_ParallaxBackground.uasset new file mode 100644 index 0000000000000000000000000000000000000000..1aba9dc832c0310f6737d7a1fd1af95efd63551f GIT binary patch literal 25398 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î!!n)8C2o@7}?4}bTKEEVY7*Z++z^yFi9YoU#Rz!8c5 z#;+Sa42zJhL(}Dt+dDJ!lSOCO2A?aGj=`b*f2>S`nvND}-`dIQFtdvFP3=$kr4>9s zO`gGfy#HBls>IgAHt*tBdQq`;l(rNX7YzzCBssCq1`|)q+2t0pgU)#lnkaD?3z2v) zx78(-h6$~`IDhgJ&cNe~oJ=pARk*=wk8pQMAJhMm@24!!ZZ39zKO?PGb7IbUXK{M3 z0*B5Sd`;%vc}`DqFD(*p2fKjWMvYS54`unZ;xj}0OEJ1nuE(@iP0bfnGnPuqtj}p6 z%wpx1Mdvn*erpv-cD%gZYq{PE%j^r-bSTKDYK~v-}{u@=5q?R19=9 zc*>^CDp`zqpWN`|)_sA@&Xt@m``c8qru8}(8-jzgx?X06+{2l9_l;q(d2QCVrZFq( z*`i4sWIZ?qvphc>@U|9u=8_taT6YkcZPR|>duWmfrc#VatQU!Qq>eRwX6PvipKVO< zbg*dqz~^c<`=QZxdd*4T*%{OKIXNUZ-3a_9jq9e@d)q-NIt{MTMP~S7LlWWOvk8Re zI6ERY66S)Y%9s6RJE9(+&y0`*=eBg6d8W5DW%v~m(GI$g&(UW+qrApg%r~}~+(@Nn zqmHRm^GxB-qwbbnt6QY)>V(!NeF4AI1xrUIvX4lNTeP(M=sSJkbkff$Z^nk(MUl#^ zC?_EfOze5qiY9=*uh8n~|{Kp{j_m4Ii{u6$}X+wCp|UMQO6^^}liY z#r-GYc>>blKft^kmaO|KC5zn)0WL z%I%BZ%eZsl!kUWvMeS1s%GNfSw!L;rvNY_s7MRO#amN7z|2bWw&zcVtza73KbE(<> zZh5p4k1e^SgLr51+FPbu58J2ZwIjTdRNWw-%i9%FJ@wm%aPKc;he4OnYnTp!VJX`%&qdE2)xOM=Q!q z4*gupe+TOF*{HR`^x9Qb&n2<7Geob1JKfBZTv?>W+mX9c`15-Tp9WTZW{OTcG27=T zw#8M<=Jhsy(=W?JggSYg? z@i>5=5blcLVVkfpg!cwu2*mQU37W$Wj{h|y@J|_khk+xoW8O-J65#)UEiPjLO3jJ| zd{K#E&QQ|R)Bnr=039N@o}+Jv@bTqw>?Ll&Asqb>c5rNHB#Wc(i(tvbs2G;LtuC7t z8W$4+2&;Y=D<)3gHX4vjfa#;+^h2YgSh0z^OJk!Flk`bU4odzhaH1C<8=<7bQ1Sy- zoW$Umc(Of)GfZX}8W@@xnV~$?RCjw5GAArFINa1M#5Bw})Fdo4G(0>s%*4pdWO}%v zVTeI^uqmPk{2PU6puwzY1fh}zl^ClRU9UKxB4Q(QWMNBV$3`Hw`LB4%i zi3mdGUCZxvjY9WNEmz!`zMPz{w($jK#t&K z`v5-^42=;4BE|w7fNjRIq9GmwwXv77FcK=~i~+i{0S*rPBMT{jo2R{PR4|9c?F>jY zfD|SKqT(@e#KIxK&)>&^>Er3|6*MP~9UVsM9fTVufL=h6#|tBc1UQ??iv8JV1pfKC zGkt>`*>N%BCB~SK6T8I)hk3>$fSN<9-Gp=y>g>USSPj6;tl$_3K0#a{IGV#EtM`gq z&SHCp`z?=chB6~ozCKLbKZJxr zN3)~YiA#f{C%Q~F3C8r7Rw1Mim7Tc$cZ$FiA!D6LA{rB7f#)jpz5;H?U6u2ZVGudcUOqG_Su9Aco|1>*h^Mq{wIr z2L(C%26@=J*$0i~!CzDtn0B>VPbZQo}3oKn1RD`fZkAJc~nc5<-s2s&x~5Cha-bY1?S zH|e#5poTZcPS(oj>SZ*|f`a@#yn~!U1jve^>%-UsLlYNOT|4A-Xr*NNwVOiAyl=*I zl_PFJLH530Zb5FgZb8`4I6VsdplhOBeY$AC-MA^2`mJOQqXlB*k?9HwB3FqZWVXk9 zIc2@35oCZ#j`QjST6F!hwjg=fL`TOhhw$Sc#uFPFFFrBq6VJbDB3fX4Sm7)- zD>jrh5quvnI~Uapm(th+JESx0_3%?vvifS(rFHeGUOwX*LK%BHV!>D_mWah+Y%B>2 z!W=Mji1&NJR~)t&W083rNU_7BAte-Bf-NU$mtbyCG6X|;AwV6B#X@c*lt3xISUky> z1)QU>7)Z6pY%yJ+LmcBEB?QuA;Y%M2gTFD5rVkX+WT_>;s|?!}Op%v5*3b2~htts{bhWf4Xdty8Vyu zqdvw21wbnK$6Gw9cu1xGm3rv?%iJ-8=>Q`9r;CUm{W;P|NNx^|7dsr)Q%81L4}rUr4ywVb;C z*p^S9T)EI@>0gou8K{qo8(bRLq5M20g6bd72?e(U_>76hD>yNdY}=m>uZ>g2v7_MZ zRdDpg$p0k%#aGgw$mHmcYs_DiQ19=PC$s|XAtt;SPqrs)4%ks)ENId%!woxI*j%BJ z{?EwJXN>x%=>N3{|4JQXG$FzGu~TDFEDr2Q6IoC_(iC#e5P#Q>pMv;xg#LbFn*YP( zA=Pn=H=z`Q4l}XB{v-rY))3H6`>5dPxCm%8EAbz)gE>*5a11AIY2vR9o@g>+FguD9 z1qZ(-N|Kclt4ZC5>@ipJ-z&==cSQedrO5`2*ZEBJs|I=bcCh&ihR7r1p%##0m zvi!LyApQHVXy|`Klz&t7{{sE|XY1d;C)YpB<9}7KWXXS1N&j1t{iQHj{r_JR|6e!_ z|0~<^6V|`1*#E{lHSTEruQdOA(nL7*f5togUpfExr5b=h#A=m z)I4SM1sD3+#l%GlU>qL(36TF4?1}LgJbQ|(2%*4 z_RrzvKpD_aJREJV9%45IVj9Xd#rr0|Ms*y%4wobY?Jc$KeUamNyY-tRQbE+`xwy z_~F2Z#|^p)_2Ze4_5hx{pl!ZTW+t$`bX=!PpgoTB1KYaLrsi~=P6%-V>w{G3SHf`F3 zJ$m$rA}J}!eevQ2W$M(aBzsrzuB|9_6Am zA0Hozn3x!uj`+QQ|DJ+)I5|0y{Lh>@Lw?)X*lN&g z!iz$71`U!8Asan)30zqaicKP@nrZ~D#zPGB2@us)lL(rICV}gT6Er;<(V|DR@IWq& z$j*RQX9CkhQrw5h%F1$IzI;hpx^yYo@{EiOQbMTJ6Ov&t7^DQ` z0s>q+J3DS_YAPwz-Me>l4<0juD=+R6597PKKcB z;ZnHvK%WqS>kAQ{%8;rp3sV0hji!t%f%p~C*nrTrF`%6IhIh?LgsTF`9A0J7P-(*$ zV1%74Fq8tROKF4DAx~=J8(1jGjSaKhLMR7~648UP!iWrLo2~)jT1>Q9K^+8vse{Vn zhheaIrtX!VyMDEG+IO7;ClZH!N-7>6H}c89+o}EA_ukW|Ug>cymW6)|s$TN+_o1a! z22nOua|>;O!~<(aFsAn#FHtXi^MaPjl0w@}mAnf6ftbv!UZWPx>qq3GFIp&B_%Al- znw+rQZEwt$W${xy-oALc#a1hp>5^7ps!_K;Nbz{w=}TKhE!!mvT2tOuvaY?DQ%KK< z3AwaYE&to}x7hn+taoVbS@lD?&bxWejaP8@vypgGZr-Vyo$o!Ci52V;y}R+e)TZf& zms!tIR~>k|MMEbx(T(@GxrSRykPZ8}eTJ-*rB8A}>(?Q_{Fh#<+z+hSz3qjg@%2?F z`Y^1o_S$u0hek2&6`i|&80wr-e*W#U|6u80sPEKU@;zF!cBGcRZEy*#wF$O=Q+vTF z>nQc~krQ@VZ-eWsZ@E-BdJLpREMc$zbkpH=XXGRee+4<>9MA3mRsRas?nfHnW zRaT4~16&!Vp%PV!LS&$qfo@&GRfM2vlMkwn96?pJLOP=$4hz!@3vsrK_z zIS9tJdLIm;dj0<9wq~G_ff+4N5Uy%z)%(yV&}!kTU#z-sRYJAsBv&=_+t>)VcO>h7&mcVur&8? zXDqYa?^t>_?t4JY_2H%I*BRbX8A=&tfiJGND!x{fpS9uC0=2>gdwQpdUH;6sy*l@i z%!TYt=kJ&q1gv^dA6nQ_fBE4t2lsq0_XY1>%C4)kU)pWZXK^@R8Ef90pWYNuSg)hi zQFukKu7g_oJ!ZH{H?Tg$Tw28|CMU$(IMD3jwU_i4-2&yOL&KLY3cQ|QMkpWB9Yvk%v$ z&g-#!>UJpKyZl(UQGJVeXx-{n0l3+nb?-lZ7@FtfKNxT^ey`(-imDUg3!2Jj@xFM) zYHctJEL$6r9^=iptnN|Qr+YOeMj?@UdB;(y+>Q-8lAoK51@kvI!(^7nrBz_?z~nL- zVX`XY(!|)%YVi;)xE!QQx#Dz`Lx54A0#+IWO_<1&8Ou^2?6g7Iq(LJ@KmiyS(IP@n z6+t7^kUoIsp;S?VsR|Yc>Y^zL03!{8DvfIak%Ay0qKIoj#p-HcYG{N9Huj|qDYy=< z0BeQ_m8h!ITAh1yyJ=7%zDYCIb2fqof99b@(7n z3G$FdEQIdq0|7%DDu4{BO=Af(1#N;UB1VX4gGQ==#iGsvwZxB?xjfjUnQHCUbHjd@ zs!^0{{*^t(?|Q|J2S-&h^B-om`<&eAc>=4d4vMSqP5%CLbl1J#?~l};yIA&p@uz}= znAc1ZPX{TBRK_hO2ICPHRXq8-S*7}&nb(eqZ|`ss)f0*eGiYM(U01kZz2>}wv1bh4 zZ(pP_z0S}1%M>%uT-yG^WlEXJci&~zZJkR!A+Y4g*1A|F7lnd8y)K_k(W7NAJd8^t z1S0iQ8Y+X*10O~x`w|?3?_07*7HHi|@qSjRFe!Gyu@ggj2Tu8$7Q4SNjJtZ)P&K4d zTTMRp%E)}txz&8if!5cr*|{imGrfNlP0d-98oD?0U|Z45Ed{4l?AjvlEq*n#Y0u1y zlI!%}9F4P!sq&L3Se2OY=GCGkIn${k?dI=1M~Gg}JHov+!NclHRdx&9OuU>wm&m!Q zxR_=%TW_<=O~*U=(f%{mFL@Gt-79jXbAeV62M%Z~(&}f3eKC3}mn6P#_O^F{OUm}# z4|a4eVQ-C{@3X`G$MpGb#)CQn?^qvSb|jf;e~*mNq6&w9{B}HhLAlmwi^bA}o4WJ; ze*bE9B;nr2_`9K9zdsT2JuXo#CfxOEO_sxvP1|$q_l7a_Rabvhwomc!cH6f0xX83G zJH@SzoK%$V;#AERfA!-`+K(aIZTe4_##Y4~Ot&q_ju&3gqoXg^%wsEKF`_x@Jj-_t z@y)YW{mZn$K5uFOeQ9US?)8>;KKc1+9Vt6<@p$4zYC!FeJ*GciEmo`I+`g_`rtPUd zP108*R7x7ZS|Kp#-7MLj`^ms%s+07R2OV<^SMJ`lR_kr(6vGW|aR!#tSIEuoxI3_t z&*8V8=02fM+iM-tWvBR`5O;WFs3j2?(2%p= zaH@mO;sE=^Yo@!^E7iXXP3r}ltprn%fy<@2f;HEo5G_<>x?#qkWPX@s@Kl6Jg1iQO zFa-L%kVch*RDCcH==qZhm?=sSG*nUqZkY8{1*Y7!&xE@u$ ztq}}|##k!3=70gAsZIh@F^Le-1j8avBib6ls89$ASzM_F{0)7WsL~iAqJXRwm1uz$ zfcGOIOJjgvn1O4+OotYUFd$nR-t~D15nWdX540H9pn^q!;^3a(BDj((7zs__r~t-L zhv}+_7*hzE$OHvY!-B>HUze)m3SE~4=Sdn@2gb0X(opx1zbcE?JjhWOAgJJs0Tsby z1OT}nE{m(92E)4w*eSIMLaHK?5)F(GQwUlNxzJHHW-A7=K#nym#n~BP;6$L16c|XI zu^iy75A5`ywMw|$SRPGR3W|bT0lByc_nu__|?cr?sK~oCv0a|7ZU=ZSwhd@UKU+EAI>;>;s(JXVeknK3Pey}q>kM0eGf+uNL?;h( zrM3FHaU}-po33ARc+vdo#S0Cdi^}`2KCHE`l<=3Za?9AMqW}I{&Ee*gvEEFVlc#-O zgiTlQtkU_st7JEI&AZ^I#K+tqqwCAh$h6jb^`0+0HQQinthE z`M1tJNjx>u%4x-NAn|z;o!Y^IRPeJX7Tdw5tp1_R@=T?*}AN*~quX@_Z ziW`e}){Q=4d=)=Ozj%Ra z@hAG0hhg+t%Dv9Jk~7N`O82S;KHK+f*~ex3y!^5re49Qat7jlG`%JeIP4Faza?Fc1 zgZIpL>OhasjFaCUUiS01TlW3Hvx~0+75A35m37PPvgtMJriWkO(!(yUTw2xOzPMpy z?viUaK3?qFov?_t#J+Xl*deaj`>W5!394IdUEkwpWUl zpG#j@Bi&smnM*rjpy2J5?sww3xJl`zi`Jo=Qok!)suf>9%$4!|T6wpX0#i5xSHTr! zU{!_vnY0i=mBNvRYvB602B?rSD3CHJoBmktKsU;zs-r~_R{)LGLHV$9N=XnRQZOMw z<#DNjJ{eFmC`|{gm4;Lm;GhWp8=BDCkf#LyK@KRRD)7}2B7iTMCkYdSv>z%keUv8h zq`^|4HbQH0847q8z*u)GL6ZSZmIWf1-^jb9>St##>8MFIk zQYb>3R3S|c%`KqG&HxJdpC-*i(7+MM&VXw5VArCK2&I85WWsc!qikFamqXd$-c*1) zfSeaOI~86qQ!udw1*p@Zla&Lj8A&bw(yT>mk%gzSv5IhhzOl^R@`Ohj)RX= z`xWnAuAl-`vmiR4 z`5loW6o2#C+``s+gI89w8Q09MyTHM-Mxra#Bulb4&$&NOQ=IUZ?&zp^e5GG zT*5=bLQ4TmCA{b=&)gMZ`AD*1&L?lyq|x79sohVKe3 zq|4WdRtj6?NTxLwF5~2 zWHY2mgZ|4;CYaJx@Ym5Qs4hvg08g~?!$tvN4Kxv$Njj+R@hm8Ywhu75pl6zsVFgD7 z3SdB>VtL;2t=@*cnsMtdSlf*5vCte|lh3ViJ$bXEty8;d$^A7)@2*LFJRHq6S;`$@ z4cCd?Wu*HYGJ3I};U~B#3Y*t4b;|F{b7$VHaX9}S+u*bDN^|+e8#^WIrZ~}#_gZSk z{Ps}MVxyv4j~-#RcuL`_z)<~b20TUK%FZqp^J{i*evrMXXnlZP}*Vyc}m;R_&TfJ^&!)|bK zW>_X4aPJTf%$!V_wtVjuZ||%4^QPkU!CvvZwmNC`Z#xpVu1rCA%cR|hpXAJC$sMW; zzN#SMydz|#poIU|&qvqpsB3Q*@9E^{HYnk@hu19kh8k}!?yWZPCcU11Mmea%IrplBJ?T)@=3Z}|D#`R$vuL(EcF<`0n;7tVALx2xh zdTl1yGvrsuNWr+Fgn)?zO|XpM7Q&dKO`CFdhUVBC@)>s$`7$XbBy5z1q=4S{@88d@s;VM!ZiIEupFf{cSXjvQ_xGnvAif9>M|qKvkragZ zqH0kZDu)IcRg0vInuW@td{hUz&>&CX->Aum_Wu3**t~i3Ff_o&j~^$2*#id-P&_?7 zDM3L&WH|%}cXf4uEkL7= zOM|6HcrUm@I9O^|ppvx!%5f&JXCa4I7j!@kN+Zt(hMNia4Vf-uK`Dp|D@#N{;t2nZ8p0$X`UN!%nv$o6(JMmJJ8&Ofo9p!$xn|iVC`2hFZ1@40S>Z7jj z`>IFIFWX^NCBo0JzrHe7!OPe0pazzYIge_2pWRXwwR3yF+_NPyk&O`xzy7|W^4r5g z`)lsPPr2;P_ZOr!HSp%mxuH3@!1aB&P}Z>Ys3<$OI!PxZnbWIvzs*`Fn_zCl~9+^?BU<)Fizd}b@$n!{!$HF z&)u9GxFxp4aCKMdKK=cshZLX8ui#F;9<<@k?lU(APezBnUT8N@w(HyF<_oS{N1Jfb z*!J$x9ir#nrw7wNC?=I(tGwvi7t8{!P->@1d{nwK;Xo zDPIGv(qne7NHS-ZZQmz)#^JrPQ?i@0#tZDJ59OlG{Gx)3Cd>wUweyW7XAS+fGG@H3 znObuvrnubmO=4o81oPmA-2p@OqP(j{?i&X$&MeD#BWGG174cYW`s_TbeesL#1w5Hr zSAICOP~YTid(D|`Ga78?rM}oe?2_MR{eAZeJVNncG22tWFy>^E>#US!?W?OQ3oH-W zYrlxAvekGnkUwj6(&}=aRc-A|vz-=4mYY{-&fmW&tzqN2xo_CqmTdQH_C>PaD)oLJ zp7gqLYfR+IJ^lHWy;WOe?`Q>GC&4srToI|8oCdKGlR(yc;=>9Yos8-#E24#|IKR4L zpwALu!|aR}rqulGw1GZQUWoSK40UpCd4fwZv(vztNA8u3Ft{$`c{);(2#TN~!y|*r zXcG1N3t`isMgv!iDh0Mlj*Mu?X%Z1rT&b|}5YZk>1P4heNYoWLBWyq${2XavNMdGh z0>4WRKt)Xe1r?MC5xPFKTL_?SeW({b(X_N8Jdu)@?3FH9KVog|YN9%e%Y9cfVq8<< zuIXyLZu?u`k=lCBZz<>6M+*A6CH++wPWMvz1yx0S8D2~Fxkypr=O?L&IT{ad_?pcK z6E?Y25p!&p|I!Dm4*MSFCa*d-KQ-=Rw57ARqqbI$93So3@~>rTKCh=L-LiWpCU~~k zX_cDzecB^eMWuy%Uq`;&yI@D7+adfRe!Z|vY0uR2JziV5#HtY!Auu=n_A-$%{ulP(57|AJR$ZY=APQ=~mR z^SzA!ILEH=mBf3o{b%IutwN3NFM6jcsO(Yjdh?OGWqW#tC#@UG-dQ^PU{cJskeG*i zWffy~1-^PPcgyf8?7g$&v)a8=6MfpSwmD8yk8FCzCf?&F!}%u1f3v?E<*>u<@gxb8 zf>#AD>zco=Q0+dt!0~>;zOYYq_QB4Une804sB6WN5-Jv(wj7xHW|>;9{`mpya+bW7 zwiC;F`jU$=rg{skEK_bbAC7*t)T_q(^s%odln+l11Zn%YI6T+}`hjo|I(V0=08i7j2(K3a?#fT5$AucXXK@m^H3zbL9ByvE|a)_J|R34Q>&IodFP+iC=L5>Y_ zXi#0*+1aE+GvVBzrCC~9nq1mZ#5ExyfehGMSy_>MQG3u*{rdH5%8eU0FcgJFyr533 zp`ihO)fYsTMgQC!au3jwkLr~pT){omf*lMUm!3?B$QuxwTrrr=AS@jefnb1a!Hbmr zB@tJW2dTq&$-!P;1>SYWS3wk+R#67MLQlw-LQ9_!tel_%s?$J0&=Yh{30H={2|2=_!GTeM|EfpQgX6t2UxU`ToS z_bxrSzJD*~Ib^Bi3Esus%#tU=fs{FqP7U?fA6m&>`Q!fcni=2conH`i=n`hVf7Z1{ zqpqcbQfF3TYDM1_nVJ?K*yio`HI5Xvp81j%k+LmhT@Lf(SuL5U~fXO=IPs=gdgIl!4CWTtuNBfI179Qz|uU(ReO ziRjK#63-DxkE?HfTF2b0XSPG{B&zz6^Va)|S2Wbzo3pT0VV>QwzKDm;g3rQ3i(fkI zoR9l@N_StQcljxm4_n`9Q7AZZ{;sCXtW~!}`q$c*mnPK28hTBho@QL}M5$)Emu$7j zGRmZ5RfUg=EO*wbTB`CIM|o*G*2GwsvncP@yRZHsqnCF6M6-6w9p;U4CBF|>u07b| z>BE@6GePHTr_}L`4KXfodNm^;2%wPECB?XxTg1WWfdZ@IfE|#!V*!;tq zZvgb@jjD)(!jm8!_8thk)5ReQjngMm&`tsxtPY+oFkrwdn#O9lE{*VH;D8%|i6id8 ze8BzpcCFf&(P?e#{9Bm&&M3HWRedRaDGQE_a7X*i zPmOY)AK|vGtp670Z=LRXQDL(Wz0p~QM`BSOMigt;gf(8Zx!-8K@Iq;EU5JfyLgc#_ zdFMa8)9l)DRZ(fV=He|v9!*-y-{H!=?&s>ocF%S#)H9o%chX4g`~3{+(8}ANdVCHu z<{scx{eAPX#Fht#l(W|GJc*QFS9H_y!^Y~nX00t&gYY{w%O36xx8lI7%q>Ryp7)g< znEox_@Z+jiWf~RVo-O#?N&haj)1**;^+)sD0}nsy>d@q^X!f-&(Q)n!7MD>#v6vEk z!QA%3eMhOu!Jb+M>?zn5&W4)k+m>(IcQ;kl-Oj7i(r}wR`Q7T=^Tk`Eew?@#+>@oL z+t7MF;Go5Hu^i(WPlH_CT~E46(yQF&O1q3K(cAd_O;i$Fv&pt){k|(-rMAb*&MW5( zrgb`|N%k;i@YJo|6B4`dK(&6Jtd=l$PIzIP)}ZTt`bbl9+4jm2n zTon2u+pXiK~Sf{quAm_x_yc?-|LiR6wdGMK?MW)e7^@CDo zv#}{`el53{$)ZB{YYv&$R8CP2-mbz8S8E9&FT~zuM{ATzA`MJ#pQ+?k+zT%s3z_e$1kZ zu5PYkT>eaS(^S`w6~~g_3$A__JlWY=t1jpEg@sDH?^|0ohSg|sw!j1h-KRnj2zJ1r z$1r!*5oir_LY<#*MSuoUdE}0c5s+&&I%EML90|7R(3D+)5{^+IY@Qz+;`!jmAPQ;l zC}6o^Pyuyz)e-@6DjmM1V8K_!wfP8F*w42B4XkYvfM07O3K~!#--MFGDs)*(lt^sF+0I;FTA(sL?XN0V%*JF9mcv)nIpl343 z1J}TH1;~j@(;}%vQM5o0%0nwjh((`lJOg|jTnUU4gx+b$m4b6bG>tz9k)WVkyueDB zY%xyfN3}Bq2|K7#4uq-B2MJ*C^iX03Y;K|RbXaF~RS719$_Dy?haBt`V4b9bjUX3x za@~Y$36a>{D#8QU?(NQcDfz1HeW`J3MUjV7+3Ze-Tkh(Q*M<)tF6q6u;csk?z5(R zESxvfRVz32Y1d=bY05%t?|d&HdIsW1|`-Qca4tMQcI_P(8uF1wrRU11N80{JCx;p!dZO&A~_i=mSU!{s~*|;~I z#o8Ty6pP)tI6#^IbINdFenIFe`2u6NsEjiYf9Tix9PPifC@S@y5WjMcmFTs5Ig5pC z>QCvfDHDBhLuTdmAmtWyp~&V6qcU%&(98D1lTP_tUE3G3KaqMtZAw2Ud02It+(6?^ z7v5Dm%Z`0MM>CNZe;=`BF`M@Ex!#Rn*^a>WFvZ8o)1}jo*2KELP)g7{QEB2GRnoKR zy~LIu7dAvZd9|===aRFN3nzIk9}sVfVqZI2wj z?9-tB{+-yS@ZrE-*?`Wv&s#6nWlaj)Am69q$vToA&^bls?TVcqm%oQpU9NJPPaoQM zbyQ^+PwnKigL>LersrwL+s$sgO?$;1EYj6B9i6G=o!5S1Ma{P$$Ex{_Sq+!@tICw6 zwrsv=E%e22n%%JOajy6j(K)|eTR3_-z-q0XMQf$ozSP0>TLvW-2dv(9^^H^SHpMO< z^O@@vW_R#eDg|3ATHn=L#lGE@u-)Xxj^l^7B*-wh{jte*yClA_v5PrMjI4Xxq83T@ zAHGqm|MkGFu1A9Is!WMdGdtaqijPTYChPsTcMA60I_=eRNNRE|&#n@SsiQ>+Q9RZD z;vwJL9d|DGk*UT4IB47B}@M7YjxP|yGCv6++~^z z9dFMS>alM(8%AJfH#TmsyL@UO){*_J>yuisxkBEa^0`+I zw3)8YsGNN6OM1HWndHZcVeNq(4}!19KYto3w%94+eTeD3{3Wefn~!%UXq$!%$n8@7 z-ofcVq;>%&H+V6=u$+@oahT}10!B1bi28%z55ZAIs!RYF0TC)4O;KD9rY20=w&t-+ zPl!vR3B?Eaw&~9l3u-EAPf`}ZM30qFoFpk|Nkdy!*9^clbf`*t zu!umtGRQ>(Zg3P3DDqv$3?pz*$Y=y%$s;nzh3yYAr3e=W=w%G=hfBU z`o8xKJ*|Jd=GoBm5s&Xf7v5EPns`~`YoqPu=UVfNY`}5;Z9J5PSvMEIxzAQw)v$l2 z+=dW6<(Cg1JDh*IVMW9)Jxj)ncOSAQbI$DcyZ0mi)#uI9e)IjK-lWVh+HG>o{NB1G zjZB-WthlzRTcS13>?n{wr0jTN+LpBW@zFpkApS(iOp-LR~%fN9C*6VH0iWQ_vm!Nt5bGuOzdhoc{3%-p&|PM z?i=EJrmuF@rQ)V}lce;-&ch3~>Yg!#GnI2#`*?Wl9 z^{M`(?aSt@TusI4PnSNk`nGLS)0I9Zqu);BXi#I>y~(Q`HU<*x#9*PyJ_bF*$adap zdZi?VT2m?eW}e-)3_1QKl^l@;bdlN7YlG&MN1m>??b`5r4n?6lL_;qK|GuW|>2x(U z*9Qvuv$xfMx=XBXmvWmU!T0!k&bfy@yN@>N_U@g{iRN8yr;y@F9h&*({1T^mRng84 z-C_ZEeslE8>MH$Gv0>_}i&5`Wwr;&a@g7XKbfugwZ`|E}$6(LL(8jegn|C(aoqTcd z;m+?`FN^Nn_PZs;Q~k>7hi%wX$15L#b#7l;zQaJ??Oo31Tg>t>efIWiIniIAy*-(B zdE+!)-zj#1Q$v@xH5CY+vPo003f8|BQhDCs!-JRa8y)R#?}*w%pR;UFZCu2x#QWY662f^MzBn+2^ZJ~$)N&ufyorcg< zWJ1X(4<8)jg=BP`-&NWb***w7XMkxzuTp%3#MBlv%~im5$RiRONg@bmCiDjSpd45> z8o0PLMs`L9vNbJjEexoH3a7jf+(sh2KqDdv)>RSL1XjQXHpAN}Vg!>uAM6b}l7jGH z@Ii696v6aDew+wcCo04%Xlh_dz|>S%pgCp>rr42)`{Lf17~NIfQ~C zBn`bIR1YC^=t6-qgq|UE4+XqXa0{V!5Sc`P8Ty<%cP<(CLO*Zl>FL4H&lwOSR1W>Rgo47Q=!Rdnpmw}_ z_l|-BaR?nmAx~5Up^PX%hAKosIYfhy#M?>{jUE8s!VpgGo+^z%%tLup(VYSCkWCUs-(y~cB^zrHRNFhSLh6g)d_-PTx zefvP4c$t&G%Wt#=dBG1Daz>u%SHjJiul_%>-UKe@@B1I07|hgQLTTT(Y2WRNRBB2I zg`!AyvXe^DCQ+o4R%x*$+1HXJMN%Y`h-66=LMhe%d0xGJe&5IA-{Zl|+}Czr_nvd^ zS)S+IfK`vJfBaeE-)B~Cd_uP3@fQ8gGkveJ%W_X|AIsV~QD_q@Su&>W=xfXe0}-c~ z4_hD8!T4h#5#=x?q_e4GF4V!Z5OO?#(Med!Nx%$6c8#V18I6QE7^7Wb23dzOhpf}4 ztR;lnmaHRv@4-=*=pvX5p+XMn#)q5`7UN58clI_ z;Se6kCbewpU*x1d@jfvHhATTj8VXh%w zr5vZy(o%{fP+*++4moQ~{GD={a5;noaUr?%Nl=N2iS^sr*|B!--p%?KCZI?@J%|Ja zuIi^eE#hzrIS@A!9^zkwpB_q*iv%F$Z;{}jPz4FNf6;s$9i4uHiqk@%y6X^PEl3GER2HoPC-UorlkoYgLg%MtQQ0p*#?#n1$tm{EC%ZI ziHYg+df?W+T{oRYhaL;%4f66T3m!HzlqGreux{DdrA7&3 zCpw0_ONh?QH13kRMKL~tz{9VyO-s5Gl3)PC9)T7FLFRQwSiR^FRUT-$C zt-QI#!v4kq)0~dCBistZyJwuWwlQHzZjO6Der(kF=F(Mnq^=%Qls{QKEd17@Bf`h!H%Uq%izn?Pf>B`&p=(N(%d*d3v%1qhL?)a&w{%Yuxnm_!TxzP&) zRjxl;IaGx8ICEmN!=i;hb#6vHob$wVkyd}?7KbG!%xz##n&y~f)aMk{?|Rss?JpW6di_(C zx%775425W8ty?=!uMJ2W$NSHga8$6;d=jKs#?^ErG}l4u0JLR(6Wgo$da zBtg+ivN{?le*# z;YuT4BHc+7{(r1pJjF4fQXopG31A_816<73!qTc469w5R&DH`>X~54$a#oG45A6s{ zfe-=3L&!lXMGvsW8CC=)R-hxemkzaZ(8_r9sH;*~wjmrDusmg)QOE?J4De7EoguWm z24j!Qp!4XgkOC^Q;80DriZn2YJT6O`bf>}$v!Kwz-7E*Y{-6C$NI!h?-t@_XwOK;~ zecyk`3rr5I3v_IJ{W@>wufEpWH*A#zd&xN;T=V9#SFU53Z@Q^IYTTXg-j;0#Rpu@@ zKk9Xyl*#OjtB%S~&F-anS*2ZkIr_}V6Am%nFP@3s>s511X_6^Qy5HLW#k=3RPA2Eg zhzBW!`<4|J?`hoPHB95HL)!J#Vws-~eEai$`r+&M6VzU@`opmH*lT}JhiuYah(SDjysxZUqL&3|;lOv5BW5i!dy537Y5jm&1+bz?t zSGXdmKe%2-<^2ffv_j`)sR#DNF(ll;|3dq zNg=hl_udeFxiQYW-Cc!QSC$kc?P;(#f45b>yJfwI99O0=BY%A0`<{{7;}S5ZEyG}cdPCFo^i zxpWbLaqX>QTuASyXaL#DQsM=qHr7_QzBrOr7*v3QkQ&hqSSUhhd(0{1AlDMdD#Ey| z{@*EVB}SD55Rsz|!X`5UAea1Ha3Vqj1RUTYI?eP`mf=TTCi?_qLe~i)7J$TM!~#!A z5--T-3Q=r;QqtHBM8;z?MED_j(BQJu@1|PJ6vXbF(cdSU_p7k4x7~NUR9VlzX?F zQ}-aJuQP83JBBpZXQm57B9g6Q0utVd%+`6Tv+07T@l@>#-Pzx!9^EP`7V&D*ZDB3H zpXY7b;2#>6biP52svmm zndv0yWXLjuaL~aV5EW7IfU=fUnT!acKBPFZ^%S7PQJ93!g}_B4Vy}#}Qs#1PDa5Lw zPE#I*`If7|DUg%48Wh=N8bE^|)j-O$tfN5((FI7T7|NxEkBf0-YSVADle%vphVPhS zI!_PxH-MU_FNbMH_PaWzE4II*U+A_xTOKlzA{S&?sPY)Bp9c7YBr!QXnPxmji)m9O z+9+U=rbSv5Y5=ef95}$LtE(fejufNX+FFqpMy95wvgqDqW|0Ng+1V*FWy%zm zmzNidV3&joA#0Ax6VPS!M?lz&j0^_O3793Ty1JV6_wQf2&GhNhNySrBQ)3w#8nSRF z(*DR4B%77&PcjG5d1@x}lr&Pppg>z?P9v?Aj*(VN*b`C~=~DVf3Mln~Fefx7bUkUi z^hv`;SQ1A^$A0PvX|_~~fnJj^E2OXzRP^G-iy}QeJq!$#-j}KX&>xwbq~y|b2%|#J z@$>UzQ3U|%K55|8SL)Ay`TR8A7<<;SW5-y~fmzf&(pRCV|1)|q?+}U=d?mK5Eg_!u zIC4OQ!WQJISG2csC{=~hd0|S}f(~>bJMohP`3JL<+_D^4A5|6N5O+vfVe8`#8XzY$ z68sL0`MHuHWiW<-K4I;Up+R<}q5*gAzpwBVYYC;;>d4~O?{6kh3`fooLAdsJ!;!gb zND@~HxdJKl4Ud$Wua2(ZQ3O4eJ(!=1pX&d9CbqZ6giHn(;N~(yl-K zf{NJz8Lx{YCLXh#?lThGe z+rpMz{R{dVKL)Ms?RjvHqid+OSaOVXxrovRHm8QQExbYghlXYR?49q89{d?0xpkQ1 z`?+IY>om3e5r#}3W?OH*INQAK#M!$x>m^>b@BgIJXOi}EvgumCjdQit4S$XmKhqwi z-pY#pU_7kW`%<8yRPO7@#%+FCSt&N}5Ii;gXqnpt6eZ(E~b`jcZ>i_X-2F3?(dboKCa3i^g; zUs!LcvGZJ+RA6@A%x>tftZTO%_I(c-I=3)t!TonWzWQ<`ut6_tuU#eDLps$?~ra zkHyZ;e`qwJ>e2BL5vTUn+?k~9ROMMc=84ghbG_Q<11;3gns1u_byt*w=l6~;4K^`L zy!%ehoxb$b3ZwaBdxWkd8lIWYf z+Ih;5Grv@tXIL+@*T1Lh_~MnBczf&2@=@8(EGFhWaMiCEQW<?H{$fWE|^=cDQ!A`Ipjt!>eWmNh`fuYBt3z zjo@EfG2bl1apgJBT(8<~2j|$YNDh4AJywvlBxKq)tN6>@=>D)%>c*q1drHr|s$Z*H zy1{bl=w-y!w34sIz4K6z7rpYvo3q zi|y+!Hwxk`b;%xOk#O6)^28*IPi{4nn!dUxnxD;?@;giCcRi425p%7BZH+9~;3+X{#5FCy< z1Nm%a1kQ~DbO);#n4C`0Rfm2}%{&<>Pn1@uJQ{)prZ3bU%9Ir|m-Lq2OIaz;b{)BAS@#G?#bP%?0wSyT=F zbD9kKb!S||-tJ)jsOi$Q>{XzcU)$HH)6L**^(*E;fs8C{^)RN!n-P!IIUH^6N_~E-tl|7#GV8Q&)I$ z>r$Of`$hBD+hlk2U61X1?CGJ{KcZwslhf<_Q9%p7q#LY#yYX65?@0f1mE-KHz28Np zXP?pK8BTC3-q7G^wmqy=|GD&of~;HPR8LgC+@YQS#`f~Q!xe#LT1jow4}6@#`q-lS zP@((O&*d8@*9f+%&f4{(FuP+--WQF^?O)!r2Zb0lkBHryGx%X%LUo(@n<+Zm&aGAz zaI^1P)gLj>uI$;r?&>Skw4mkLL2*UT%D%t)_W1ANh>oW8Wt~+%OIkNd)%K2jaj{Z3 zr`qaeRJv}S?gpg^5&JfztT&6uE!7|Uh`}IwZuE%5c{Rr(XcyhwTUtah)K6S&QD7VepukJ08*gVSe&i#c8{*Jve z*?CUD{fjSl^wyS$w=M~I-|5pgy`!MyZ&mQCKBH$vnd#Y^JJQTQU0t`LqVnQKgG*Dm z_M_FpYi*Z?`05$P(5e=!4dEGh78hFZ!K`tXnW@N(fwWX-#0d`6K!_<_G$6l zr%)-k>SHV`??>LsVM%_Gb5lp%?r|6@=nZk`AO8DWw&N0|l-p-69GLl5(x|5Nd};Ey zcR%5@HAx?B+eJ`bXz1^V5s#OBSqj8)2@Zep$zA=)%v# z<5X2AtP^uR8S&gxfnRmCUDD)PDBw9oOsHa&Am_ zgUtTD+`Ptp*D{Y@bEp;Y3aXS2Kl#3-+t+#L;`)>9I}Q)E8=}W-kTrQL<6dxR-0Iz~ z)ic!Qj_mj~vPJ8_!Szp^hSg*Uj{2L057rAZcw`g5*gtW>SJ7x2rFl1v)og@Q_HR-; zTyxGRta@{A*vRjb>+>dk^3nf1dX3hV($zZ_q=rA^XG;X`vB-EARM0YRZp4t%k2%X{ ztQ@4FGIY(B0GH~m?mz3w`m0C(nL2yn-r;K;YLA%hcVBw>V7f5VVAl8bjjDV70xnlM zhb7dHaM=A|+$qWZ;nv$uTON=*D|0b?TcPuT)b?i65%=sn%?u-EwpiHR5Rc2u{PDc< zGyhQ1wE4OxU;9c*J$I0L8KyGHM`$$Oa#XUN-xt|Gt0KBZ9gZq|Q=b@n)Gx$#WmaPz zgqn;z1O+?|P^PN@YebxfEr2e~=LpzRl%#+dJzEy;ck&97AjWx7qo@rB2E-fVcu=GY z2yD&&onWO1WI0m=0rDTE3K%S>EJulhWGkLB?nS3-BLFXjl!FqU4iMl>MgtX4L17bR z$X4=3(;1LosGq{o@f2rklmK$YoCmR|#6uD&TMhT8e!=8n+{N-ExKau)VBKPQ$v-iM zTSl^7l}^Hq$sq#g3|e&H?p6$OV?kt->=a89#T3;T?GZ4N3J!2^ zq)EaZ#YVjZyfPlhRs|M41YYB5FiQf>7AiroRLRkL9s1{B z?~Lr8us|hCD{Y~j+B&sJ-H&&(B9oS`wqy6V?@A9o(_Q%McXwmwOWzLn!dtzKooo8u z2i$n5EmHn={;}{7UaU2TAIBQLaMtdyF44xzlMg@QKi@L@tW%KnpZtv#S+iV^7fPA> zL|=+lxAMCkyGC`6Y>H;uEL+d%f|#2PoN2$SeqSv1^1krGeecS%=N|}V%nm#F?^RW_ z*&g=w&Af-R6u%u_d`)X!z>8Wl zW~sNdy+WMFi=%fwO#Xgy(!?Y7#V_BF7d8HSH9bsxt(T<#Qt`@+dH02xHirc}H_mAM zwDhw4-sg1!#a%8Q*Rn+eZ)&G$#E#1|2!AUZ_s7n__j{YQ_fEw(j}uZfjCyM)bw9|+ zJ-i}Sb-8AQ_n6Bg^_T54d+u=XeAiQtO>(Jm8@8s+Iy+vceTUm`rKC;CwtJ?utR8za zZ*rl9#+)7D65lSpwoD3~eYJGDdss`v+n-G<7V}(dUkwh}U;5S9;n{Elb=T{W;=aok z90<0ll5G5KsnrnStEKJf+?K}fNOoSaW{xB%P}0j)zDbPSb;gS;aq zBb#AjwD4iP3pn!NU_E8V03+-(8?{In$0rPXDri7QD9mV!8`EtNCubuaTMJkqw5Y)q zg)H+31`EZi?a!+63}s(ms)D zNGy#Q7DU*9`&U?1#@3?4=#zHQNn$4+h1&T8ek#0~z6YNhm4AYc2$>^z79-eVB1oOn z;l98tA&{9u6~Xdzm2f>qi-Z@USAi*DFzxhGx=K(M{~g0cy6peTlsINYwuj{bacQ^z zbZD&F-K32sEwd8(`~KSR`jH-d`et9xI?v9=qMg0|p4ST(cNV2~e*OH_$C5K_#1)o= zL~yXtZo}I#EYbJN)h7CfOkU*nt-52$y5z|lqXNtg=Fi(+o~@p6ic{~q?`gY~OxDh{ zV;_1Wj~&VIwI2T<|E{;(bf3dDLBo7HgEx9hz5VcRQT{t0e{1PR<8yW<6Q&jIaBTP2 zug|HxxFx}JXp5fMi~AGSq~~WP=@no7$-X>o&0p2%o9lze2Mlf=^r%VUh;-PiX5OQE zjd;y{4PH;{WSd)L;v*CS)@Mf$_9J19Tdb?Eq`o7GCSn>9?G{tlV4 zEI>HJ*6*x>cc^w2U&5*~$`uRKs%weh(%KVnD7-8&23^ch=aCpSM_P?B>;Q!`$p zzW8BtMYeQoUsLg&=D`7~{PjmRR(;PKCNug;N%IvSms zz4H>WA{Rl?0hKIaLgM>ZE+-30rs`Fvtvj#RQLW(SaU#TD{OR($`-XBGAikB@w1n~5 zGMbd=LW+IGz;CR;2o1Eq18zf+xJ}45Ac>E46e~-AGfHzPdi<~5R$Y<_KgjX0{t6Mc z7sB}gIu~Mn`tJy_gY6K?=;C8Z5o*!m1_yD}2qaaZGKn780nrYy9~or`L+}(3oh3-I zjyUibwy?7f;(*LRp%OrhIS30Qn*ovi3NW`$Gz%{ytx9GI6%azsBCXmw6=)@#4Ijh~ z-s6$YrGXfUHtO@_;^x})3XFe*Amnn;GEmZt&ir=-Oo@~Zwwe;7cd)f!km+e*Sh!fT zVEXB(0#!q0_yz>j!1_x$xojah0ev&fyh7LSW;tEoR5dHT=F5rS{tu4k47&L~aCu!{ zzyFV;AKN?KCw6xB-27|EQXbWiaOd8LX)LAN%UF)}Pd|;YyLj14vNCIKZOdY}Cq3sM zo_qB2i<;`aj9(|-5?383x9P~*PsTL|EX5tOoLsz4i>_UCA*ts{*NSg# z6HFzNUFDQUnGHR`zjr>)LGxZ)|4-*Xzoq6kS8_!hlb<#yuex%rTxC_l>8G!9FYUBQ zD1SDgK9!xhBl>yYo{LXTcq&?+92S>!$D=KFcDw!lQav+UN$53yj+VI#~ty z2~jx&^$6<%C=01{Y$aj{_#^~u6xpMCe<)IAE7W4kQ;ZFLhmM4TA5;`e8P>eKJ|!gp z8Nt#8&56_;`};jaZ#wIT?%`3}c6#>o1dLhJdU@^ji<4hYu1w1dywQD^GiZq8i^uLW zW(M=8v-X{f-Fe6ZHA36NRMOwn1*>T9_%OoTf;YYR&mnCY_qRELBa*XPeN5YT*f!Q0 zd4xO68yx77+H`h9cg#vp@s|895m8AOgM$@^?f#TnH>XUFvYesysCm8a)M*?e3l}Z# z*{@9hE(zH(W%&pB&Fzy;L@zRSkdO&{v#{aRtf1(xQR0DTEkt^QX72mGHmGUq_Ce1T zR@RCxiT$#5OS^xW-#wp}Ey*IiCttY_);{#k$V$Y*Ykl)0Z9~iY$?1mH?U8nAVu!q4 z&WU1VfzaS%D4`8gpa2LFwu~|c7c&E24I>ADE`pb*{T-J2Obv|g7_LwUBTWSjQKoF* zy9QKtCgj8itb>$hphy7fpy($FCWNDK1z&Zb90LF#(2CKZCBc|2l-~u}!PH))YvuF? zCS3vEA63L(^~R$O^9;`nb2mlMC@t0aN154d12uXc&Y3`@rN>Yu3UjbzAW()pBYoH^ zV^L37!);5e!&s&T*(RpHgMK!4_V)G}&$;ckuC~8D=d z|3XFI8zC*T7Y6LI6?8n`l5Oo?cm8S~d*;^e4bL^Nxr;n@dAR(=)DIrLyKip)p7`uk zm-KSar5-cnWZZnt#@DlyHTgLS!Ow+vCJ(P4wszsIyIRIpmkkSSJ-OIJGt9s--t+V<-yacgwWjr7o%QF^?VH{k3*wr-n3e56^|Z8-}>xzVU)87kxi; z|JL3YD$~oZE%nQLdRM90(9leyKq2;nAmsOwno+mptjFg{-u*UJ|FP+nm07M{4OSC6 zZ+~+f$8G(yr_^frRpmjl+m211y)o_5(JhUffX(r5@9BuzybN(X_^zF&b^4-;c*XnU z=87v#f2=aFy0h@`pe@sfpR!s!Z->!c%PZaJA|3=Pk9Mc!K(V;CQ=vhQxrQQDP@n3$7DOfZ3X6fBOTgUcu zId6;h_-@tqJng7&G2xyLC>2T1K(+~mlsrT<26|#Fu7T=TLJx+rR1mL{gQNhkASMbF zMV1<au^NKU3?`LA%H-eFg*Zh{q>>)B$Badg-_0 zL>wkXe4ga-%VlnH^!>@9MoO)jgPMMpp3nTjx_>jYQbVxFUbypB8+ml=Fv>U8B<4?16D>|`nJj4to2iBq2a=$xF4g4YqRuvPvNsXud5 zvu-Eu+MiHgAn4dXP7prMm|eSJc*U%Q^tdk1jdq)s$yD38`<=T|QdpsoFyYxX%Yu)J zb%&fk)=Kv(m?nMxmFeR23kfTyr!NPa^1qIm)oiz!;J2AK=w z$bb%pb^*X!i0fijnIo7r7*mYqMW1}gNqs`4&{d?L(UqWPsv7aX-zp4Au?5iKsK_4W zf+On!v?5EL}{@VsAd4}2=4`lq&ghh zs4uAiGNs^|0v!R(f8-&%I3@~s8)-5#ah?Ii_}B&|Yi^om%Bf|uQ>ML;;9>ZS zzoJvE(|#QJqp3JrJYdzgr>)1dKiK;fPV)*nudlKr;lb?5ch=2+_qK3GN7nfq{bTOx zBkD)|mnsvT$^RLZ!*94wfn_96s_K5F~ z;#(10+x*L;)c;FLw9;vI^*mP>4R^=*S&3dwbB`HCZRimD zKBl;*;_u-YuFb8ijh{2-NTeKj{(^0O>+_yNE2hbpO1}y}DgSx_kf*M5u2<{D4PEDB z9LJLvGj37wKRo@|q3+~mUbhamNl27d-*Vt(2DzAuCi*UK(GU*KhzoaGW%(p;;#lX^ zPyCD*f0I9B{7OMtUO4O!|8vnf_Uj85I>wyd`e~MFM0w<)jy~xiKdCgGZ|h}^O>gtfe2OZ(Y<(W%;4Y3E^y1Avs$GeS2VO1%#c4I5Jahk?Ly@%>lVUm5G>YFDg)5Acg@~ zi-E`07I#1aa0YLy2VIC*15YFS3su5+AX>J<2=ouK8|Dy|u9l+1@WkSgd;;C_6rc}d zAA%ROCJ>j=0r0U3Bi0c_qav6bpms>pjfp~!^ocoyRJDrO8H|dmuT>T3I*d1t4g>-n z`mgk@F5U!q4Gv&9Oz}%qKy*;U2KPshn~IhZn1_asr%ZYGjH*pm97Y$w6DbU<9#sRd zLSa4d9hMg!Nd_%EjL!yyq`w(&Lp7UZD1rvL!JMVcwQYYlaDWG8o25xys-Lm$PS})S z%USkn7wn`b8JPt2zwT`5o~QPANKkFR|G2*A`TC7lwq7WD*}Ao}|MSbaUeclySRyax zZklFqDH6Pg6&vMS5>}+=-Jz_*Yc$w!$vSGNtsu;X!xR2z*6%x9qE5GAwA(s0@1-i9uhY~af>I}*{h z{m*ZSC1rnhFC1~f{K+%T1K)h4GFD8kuARO6k9_o0g`qx!0->GUk zZzmZk#JipQuN=*Im}+gjQdL_bXL!G8{Evgr1GhBZl4=?McXq|@^7>U#={9?h zEV*ma^=9=9qq0|z$Cvuw%(D#&n!B_I`&+=TW=ts=I1el8XlnFgp>ARa6f zY;`G6E^NWXdY@l^L3*7NiuCSA%LX=uAZ!VJQAoTu)tx*#t66iZjTn3Cs zVPR>=3rd!x-lE_Ik0Qy;d2q*rOL2-V*6I>?PVpr44(w0`-OrX_^bjLLLOJQMb--Xu zeOH*A%n^)?zAbvpBwhfFFVAofg~&;K#YD}=qY;@0s@D1=FAxua#{%O)K7q+wQJW>P7L9JD76H@at26FxG73*A?y&9cq6hf6Qt6C^c1=qp^FISP6#AQvmxjZeFsj8fx6Q@D6fV-31QUG&>%wFoB&LCG&yS8+%gQWJUnD7QP77#m@!c? zQ9{g*3RE2w>1LEG}MaVTkvVwwwSTvTDsF0VJhrL#4j?>fA8B(CSLUe>k zgzir@jy7-J%%Wrhq6fMm;mv5|X~yU{J)cILW`uBcbezbGk`f62Mceui%}|oV(W6Hx zsevIi!o59v_KbD&=1qc!5%3L%7!sqtW3rgr&}H;`^vd)}=TY7RodZH(h=M4W+Gz5L z<|v_qh~Xd9jmlCI;SuzXUX5Obt|el?0!NwB2szMbJ7> zpf_L+XlXMTXrw%B1|^~rWsaNt`!Nn{B77OrkSw4Xq0+#2L_B5}~Ogqy(9TDyv>iz%PSXv|i zrjP(=I@ZDsNo57c-K~1IqUL9|Syq36g!`-CFW2^5tUKA#Q+e}w{*|ZCUhK>;$+%_X zF48rVHEGjnshc8E=gQ_NKHj0P86kbREi5Wu%Q0l~t+R>iP1pWDC0gF$Ce^byF>K8( zVQJy4PtKdp4>#1+`y6y&^vA!i7IvkTtSnG9n>6dx(j(Kv_w}9HxOVc5!-H4YMLaJ( z5j)gTA*xmTmGjFlb21LD2slux`e6E*M)jwQRX5JO{=!%);m)(5r<$AkZbm-q{4^u; z+KHiqMg7afC$#*^yW5`<`K!}K2oFl9 z5R(vglq4nnkvt$7LlV-?&W>aWu{-e?}<)JZ%kz8LHp|rXV;|gIyo&gbhlF3w0)QE9wR&iy&6A7%)JUIQVWWV+e&9 zZF~+@Wmi$h5aJW@eo|=#m|Z|dZAqs`?G>gD87ixwSUR%?S5;`@aaev01o#KyAdQxx z3M9Q!IAIW>F-2yvXt@zX))s1<_R|tW%L-*@Q7$GeD})%PrG-Q)EhhwWrIGnp+LZvd zl$VKJwpf%(M%U3C&}fi=rGcmAjOK}?6#W?(7%*!z2}hKJWnoV&5t4i)wP-Zylj_3K zeMsujXcF+3?nRSF_n_Y(OI0GD70gyhh|(}s;ALSp0F+O=+mvArs6YX#R=0F{*|ivj6eOj4dI3%Rq@OQIqAN25YCME~gRsOR)Xbk08ofZAv|A&REYUo;amiZlbXR?xkOE@_>h9p&hc?n^XEJIZ0!Xi(`E^hviz-7(b7VatIQ477kx z*Ji9Ol;5c$155#w3ImM%Xl#v%cr`U-eSlA&?jz2YA-YlrUoc&IY2e{;7PFpYJ;UnB77Us-zKJHFxA>6HHcO&&>^!B z(})nK(P$8-5iQai6WtM?6D<;r(Hj%}5u+2G5j_*F{-Z$}6{2gRS)w0|3=6bM15c0p zN9y!=>IRV>4LEg!2Agg}tU-SyW@r-VHgqkO0Y*0%@dTu{3|kvZC(=UNTV+vb1iVM3 zL$HAxo1%zN>=}8dL#ZGzM-?%$m7VB2<|L@8Cq(shnB)y9Wlx_zWl`EH zR9)tB0`e1?QxEAlaUF#E?c28>J$l62uwes>;QaI+BvI%RN?jmT4hiYB3NSUfsh>nG z^p3=fU}f0&uglPd||r z4GvZ1AKWc2Gumr0OL_3)j9FqnwTe%cg}b*&O9Sz%y6(lIZhGhaSME30+gH)BG)B4c?I@@3vtw`YAKFY~ z6)AteymYPE5~UZ9?Q+jlD<&=5R`FJd&d`KWO-T==yO9wv7Wj3${pMFTs|*WTsJpklc)Lk`SPhI zr#4iXiJv)iqVT#@X7uEg1 z4P<)4+4T_~269bPkxAg6$={`nY7!+NI`TRZgoy|cJ5J#Kv{?)2#fsEAjxyQdq~FQl z2pIr!LiCTeT%t%bA@x-9bD<_s4CTL8j)ork1e`j;BS3Lb83JajHY}#dOTl;0CVAvF zvDT6U0N-s96lD|A8#VknRNvuuFCasMmTRZ@LeO2C_J*jq>T~{$#4r{Ye!J3=Hh2jXX)IA(M^n zPvR2aSrDfzGR5e5v{3yMeVAljjF>9v52Rx-v07C2jDa1@lls5E*wS+uPM2e=AfS&5 zw`^Tn1t9C8Y)}*n=zM~&3GqRyxDtJmnxexM;v_vxD(pKf)l%{xCA1K27*I?5J4iBp zCVPiy0b2&~NG)Z^7X4FUPz$t?kidzS=>UidRvU|iItfQw1Zak60ianRb&*DtR74t4 z8V!1L63sNKNatb{9kP$p!! z&7ujVrJ64TEn1Giy?nA$^+C1rjSoS^6b3S2D_~Cx3d{(0;ia{~7^5*F^vPBvf*U9U z&XzX7QM|G=bVt?pRvba1=rRluowIX*CY|NYnuJ_kUm4N8ES)1DCC+9 zcCz-iitSsn@9SFe^$r=Q)>-$y&%Uraa@HHxcFF8jo!__SH|0y}g^pGKzVT7{iOpHQ zpO-9I$L*cs<@dLw;izTa(_@-uvr@$ql0_@M!hfFh-`Lo!?pt{|gf9!#)8htLYn$9O z!SxzN!~GneH=&ES+PpjEyk_PTiy5a=eI6f}ZDh8-)nG?_{K~C^CSNnLag^=Nk81VQ zyCZ5aqvhS36sM0f8{?4Ol+btY*yInsZC=M+=hqy5nlr+t;=swoVqVMa>qWjl4qnZf zKS}w5_{2Ncwj6R^dsjqmTNw9-*a10ZS(VY3D@XhJnd|p`j>t$+;PL*Lv&|Kpj?Z>=9H1ylDaz9@j>|;iaM8=NiKc!X2#al*XF%%YF(FM ze>4%11q|GCeX~vA?&ouWF{~)ujGkp0D$67WOXwm7G`P z>-lw7f8l9oPLle}Se5AqySd*EIUiwZI92*jOdGu5c}u>U>aGJxxgFmdKDZw{=^ErQ z-{6an#<{YM;iJV}oKzdHd|O+-K)T|&<;u6)XFurvwn^5*c!u5-iO2Rs?uV%A=MVle z$@+10*~!4z$FGJiZa;V+u6VgI|H0RN2{LOl9!?PL_1x%IB*F{t`nlxZ_>7P^wRN4t z{&@dbv&(o?`O_Y_QVfA_gj(N*Nbsr&wAA*!v_y%Njr+B$B|ut=-fB^K(U z^H)!|;HUA@r7_l%Q@lba*8DL)v%>0;^Wt0kEX6tsWmtT(NKC?W zl^5G*oR_zC=uyj@w6;KDgsecSQf=`2ed~q8ug%l=7*=WS@yA4 zqSF5w{V^-M|EZ^V+8m9n;H}eMr)>=U)$Toi$h?Se{cg{y^xj;XZ_kaZ2`Rm`KRv}W z@!kF2npH|!pSb4}1MEI6lah@-+u856c(b+purXJ@KDN9++QwyQN#RJ@?OOM{Y7|u0 z&dGc+u_+;Xgj&3gR!xnJ)e!x4<^HWI!p3mZ;XAV5uTV4J`O-FZ%FG!KC1r9ijvks~ zFkdXlZOk$6T@6hZxtWf;LYJKhe7-<6ZJ2+h-~&*SQXZt^^?y5+70yR0xTQ?;A8!MWVX(~#90EJ3<2GuP{J~NrJ@;0IS#;R&B`l}D4wP8$ zwdgfS2uvH4zj;y5@4U--?XO>VreCa`x1#@5+vkU)LZ6(P=yqLIO@z<#+RWk(jgoD< z-YMofWV}W|ioD_zkIdz+byDTL^FwDp4|`Re*x?ydCnaz)l@#2Qs47v9J^uHX;iAQ1 z7ql;a-FauR*^KA~UO7i>s%H!0RW9B9uGjF5eR}Jg(|?YB*Gvm7o7lLgutlY1<>bX` zHc@6;fpf~Wv)GYdyI)SxEbyd9eoB|`dXkc~T(tIH@TR?IB5xdP4!J3@Z1 z_z-$#;+^9=tet*k#68zfW!wD!1$2 zv;B=eTXaZSZGY~faY4CUO4fB{jYpHGoXKA`+ac2{Iq)e2LA0hwyK`G7BMpQ8XcZDfOo1G8P9|ssI z`i|bh+eR7Ll=6a(8knMt9$G+d6u2WCh)|)b!UWfX)+vhzC=Mp1r@_-SMHjfV4r#G9 zO`y`_y@`B*`D56a>Y5-CM@aq?+6-AEDvBjf_F5+`0f#eAPYm}^vb0mNQ`J0R?4tI2 zPK%R`-OrX4L(~%0?Iv7G9py2^?O9KI$Ir7p3;TUH1r??D^f$J)`seQoW^u+F4Ph-C z)R@TIFf%xZB~tj#toxVli3iR5OeIXWXr!`Z0^|6{|&yTLGwVd-| zN2qDrox$JV2kebnuWaq#cxTn$y{XGW?q4dCzO}a`p}419p(Rw*v92VgMlr7CgxI&Q zgPSV2-@ndlsWtW3c5Kqn2V+W?)@s_uhY#Al{h?XWqc3xhdK?-WA8yn2)F!&*;nRCF zszw;iKk(?T=afBv^}aW*d-vh*upalTTfV6M2~c@`c2V2<71!ie*!_w0n^QOAThhmQ zaZ6X7()#jcx|^C`^yVp#MGxdGYvasp3~PE*@wUwRYH(bkZL8_MceC~!SeEpf_h-|K zJ${OHs;T_bg1Y3*C>z;pDt4>XDj+h_H)dY`(66!buXfKKT3UMADO!8wkRhvFq64){ zOATj7ACQmzY8EqZhi_!Xeyguj#c~4U6l*e*>+DX4%PW+Z`6cX=yJa=*(%qSLN^KV6 zv#vHQnCX6etm1fskUR1}gQGvVtgKx*QuK==HKyD1g0uclrU>1lIqjbQ(BY-Jk z5g_0L6Q$^mBeDfP5(AMJ4W#y$mK2D#BC&Sg|O}O3jE1R0RhO zsKV9?CfBRr$fWNz;##4j&;SYJK~XLA3Az?fIwryh{ElNXZ2c$>>MfB+h9f9Ji>e_P z(u6IARAktze254&jy^;Y;kUJUFv9t;nm8B^Tc#D|*#WHKK*YpEjc0Zc5^+CPU9>07 zGPtw9v8YC9=+e3NrFmV!@`;sK)_r*8GULI~)*+ma8{$8H9YvV}eiti;0hL@VUkk1cYL+kwK06sn#i0cj zGKsGSgGg5-QH1Ow{9|k=6^tG1O+Fc(v?tF%)#HiWikJvgsZhr_lQh>TVH^+iD?c|s zcNj*CC(Qs8pz$gUG*E?j{G#*-Dz2hnjKI?W`3}ECObmpd$a%6rLm3|cMQO+pOg1># z)ns#%CHbG*o3Sv-y$HV{8J}b|k||IAVDkTlqhs_g2z}|PfKX&X$SQDLAn>UF9282& zro)pq4()JKXwoOdHPdc3u)(E>8dD~O+`?o6lPj2<3-mNHk*Po@8U6Ise?CyEx6;_y z$W-5?Vx8o@rcNR{^?!O2*9L8ob_A9Al?#F!EM=db_gqyIOh%+!jW><+ zze^})0JH~rfXI(QmlCFc{2t^F`M;hZ@^4U3hNg%7Aw(qq*CPa?V7yo~Z8W{~>i@h% zL^Kq>z^iJjaic*05sXhx(|{>qPE3L+;!!~dz;~E>YqTc=O{c8YzeXjahc%8v!$pRt8LsLJhW*Ik@saH}TNbG zksbAwNRu4rRIHI^_n+^F#)PJh-km;a`p5%DpTs!i=_jTk=Am~dXb$}m1JRI!t62d7 z0W4w%La)$_(G-#QjsA$SND|Qa5)g#iDY`|^peg*n+c=VEjvPvKZwji>vuO?q+d-^C z*ZgBtaxl>|X-FC#grH-+*+=#45`$c>0!o471x%p3b`#*dp3GH)y$61Htx z?;ac1V!z69Qb2lNm7~DX;77uYc(qZ3^7HyuU-{L)=4NAe`_5Yb&ir5PzWKd3dR_U> zZ1Lbk4m^+ zmq&N+7rXRJMDCZ`zEu4K7tNLxXm5TXX*%)K!$9T3Bc2qrMxHbZEp+#~wXHIKBR8Vh zcSomp^{3^lUA^-r-0!%}j$gbb*ePi5?i1sdOzNI@)H$hod;g5va+#*<)L&}nT0_f@ z`&k~^w^Q)UXVZz>v%7rUCnt+Fxq1(m6^vcyI7#e88GjHzELw2q_hEN^QJebm+H3QZ zE!N&zVk0Sk|A@!6-0q`tPh5(U-dg86ztkCSdhPCQ&-aaUmP}tg_S4toyJ4!rjcT>=nuv5 zn4OGON!~KyIzn$iFjO!yfHo9FE(%A95>ACUhXB*GuRZW@I0F(K;AVITbx4;aKjwdj zfng@VWR#GOqR$2h%fsyfrCgh~17g6VKqgT9SC*tclabB-uc#+;UsT=VsB!f01OZOK zR=;dnNEZZ^c&b#t0!1@XqJlvI0xQJRs^H;~Ptl0?k{a0Uo2*X*nA|irh}g5WpciA6 z$CacQ+F}t#gkaz(hFy;^rsO%bRp+731>J^J4LpO%7J>nQb{^^9sJKj#PKZ=nH4><# zOd!>8K+_vWv1xYG@#yXdnc!&x=hUocc8Rf-iLt2`S$03OD+Mj~)6{A%rA_itn<;w5 zzpeMxjmtr=Uv*#J)z`l!zo_VHd|~n_@gaN6?T3a8cHoSUKF$t)&k}YeSXGa@YPmQ> zFud4UM=OT?baQ*lS;QIs^e@4UzF+1E7(Zwm@*vK$VEDar<4+hQkmVDLVRFBe^1&n0>v zUuC_!OfLKN$OWyTbLKzJaGSdFn(ND)8nc-0)^*QjX|V2VZk}h;_xbRmf`YS#M(QFO zC%oQ89Z#?=Y*MiaQ{G*bHm6oR%W8VpPG0s6tC~Bimstfqu`k4?v)qiNVp#op%jeb% ze{f%*RT!z?n>NMdrb)?P%?L@Bu+ez>!a-NdtxVK;3pBLfi;M2mf7SSFLGgR@eK&*F zHsl{1F>48T?|j>H5##GBQskW`ZkCz6WJ~$}wTA*O+P%La)Sf%uV|N|*+h~Jnu7k`Q zzaG{QnOsW&&Ga!bal*ojCC|7!=I=i1_R0I2 zYv$J)vmf29E3Y1&X}QPuY-~!=()6(g`D?8mrtRS>tgUu?s*>-PRXTf#VPsWP!RLe3 zB7%Se=Uq}$R!vJ==(VH5d6R)9C*^7Jc$KaddYMT!s+x1`TOQBo?BrE#i&*|wbCV=% z(2$h0bm>&t6&JI6ACEhhJwf%*4eQwn{lT_-Lvn+a*;DQtA9$m6>aB@GTZWFpwL1sp zInB9M%Ts19laTY6yYt6=>wtA4&9~KZ_UPmrcolSC>xiG9+#EbwJ$c)&6VH=Mr@oxK zCVcALt&gonav!g?*x~YFvZ#()T{O?yN3u5}TIrq5v_<-R1~08TvnPCc>P07(UH+-< z5BaWkwxho}e<{@*aZ>c`tn16G8`aO*cxNi5t*RVxxU9+N>s_llc}bN)^LGrhp3^q& z`}VQR-WTsW-}t(DCs)Qt$8f=LWs}90S4YWM9c&a*HAy~qj86MIq`nPeO2boeOLfvj zh(94a*g)FS{w#BJV70b?1lLho!+(cILQW8$fv;7BJX8v2uun`07kJZDqj;z-!x6yZ zWk3vC5G|-S$&-g)7WsC3DqDtF3R?|Nkb%MvfE0&zpytUq{qGPwvCQ@lP$+?PfO%b2 z0ar0?^$V~zN1DW#6;hdS75sB*`MFGr2U3{{3&>OBk(8sNUvwB9MSU;w^P&a}PlreQ zTSNch@R5MR$IVobX+e7y0v=(7lMqzqBhH5k1IlQG$hLyxjBX`;oHqymy>9-=^s&v0~M>EkkxSsr_o7FyiO9P`_!_ zk>?J^W;p$jll5Hn(sYN(y0EW5MHZFr+3{8W)aso_m2DSG8ol+FUNr4%;G45^w~5JA zT!=b!!Ai(JI3=Xm^pTR!v|TbD6DQ{V*%QxSa7p%LUjNLs_9Iy%xsH+jW|w8mqf|y6 z|7dn&m{7{;{>m%`l|~VHdPvP2@-!<8<5ynSspR1t1++I7YCqL{$Z$ z7}*NY?rov`kZpkc3XqtF1lJ1c5o7kEN0e#kL`Oldl-qD{a?A3Q1TZY!lzNv^+Ir1gGxMN4j%Hkg7 z1vlN2ib@{HKGx7t2(fxT;*Wbk^onHnr>Pfq-CKUS>+Xjtj4EH1C%>0H{K8& zu<@4*xbM;&S9sh{VTbJSjTTC4j<|K4nByn+lHKYZ{H~rCRB>Oi>e5xtqG#T(b+4o+ zmMU0s?vEY&fMXe5dROuC|Hsywz}47w@8c&OoN|y%&GUSkGnGP;kW@0yG7m}SA)!Gk zq(_EKDXB;@4~0+~gvcyY<|K(ih5pxd_w;_>_xJhxJU!=}eeZkPd+oK?aILkbya-B} zao=V2tPZ~?tZi5FCPco%V7&TWpX`v)=jZ8u2$-$D;`Pc{r=8ZrZv?964E$u8Ial;k zH*i#^m*Be0AO=>3;iA&00hE_HzGAF&C9qTygl_~<35kKERQiP87M}_rzrpBLkHCfZ=f2p}{YRtf^Q1+$mqr&49zNZDmC@_bly z#vjiAdF#rO#X~w4gD2Prnj|Bd-`Pdn7kzyl$=g~n_3idFU4@v?mJb- z3UaiUJk?SCVDfsSY0e#;#~T7Sjr+Jdr1o#s&1d2V7X~)kJ4Z_0i*@RUjMMG4Cb{NC z&-`8O79Uo5`_HJ0$9s<|P;7a%%b5#Dz5KgPnN~7U@?+fHbB7jx^w}|R>g(m_-|Mb* zn6YhBTvwZeos?q(g1>K4_OtUW(|EKb(4${TiBo&?b2rp#ye5`Ro6^mHtAma?LJd+@YTkpy&aN@;u`nY?(Y5R@uooA z$42XNFbmiu$q?L`Ois`rP#z>or)l{!H;5)Id`4nbun=0|$#i5m3dKZVIM@@Tm`Oy+ zFb^557)(o|BDhe24B`fAfNbg&FixZ}Srs@N^6UtCwk;-LdiB9igaBrBfpr)F)P!dd zLXjElC~Wk|r2}Fhpcr99s6tGL87(Nxpaaka`CD_VIvxs-3vG!v!CC6fmMUe1d}B}% z#0MnR7VALy(BgMIk0w~53|*CI$>rXQHdvR1mq7`FrZuOlK)idazo0-wL??F%d$z9M z@HsaO40dl*_Vx5UWV(29-q69d=6|!2b<$QpJae|;<>VKQ%lnqTto+ma<+%k9?v2x%QSWBJp4Q2(vqCo6&IlY}U;jd>p)zz+_KiVl-50zG+ZpkC*oxfp?3}1c9-+@$ z%;@2np?V+92O-{kyx_|>!f#n*~^ z){a)#^nJNF$8`Tq`{4eeiS`2-tu)rL#I5m zpL3wkvqiI0uN?Tk&G??u)hdlQ%7)A9&McBW?cDdN`p-4en=3o*Gq^RSy=LOo@>wy7 zxo0kZJvZ#@7YDV>`(E|akBoAecr!SzW_j8EuE%#cO^C?~T>WTSmq$mm_o;QtkpVl2p0Nur_w;$@HlfZtYPOC<#*%`W&Q!5^*!o(*&_ zt^tFBKHGUcZo7=gMmux%AQa9WQUiBrSxeA9NSiq+8TesY^#QG9q2`2xO3ZOgPY0HO zst-63_6%Ht>Ds{Bc`GqgY37@0$}EptsiPNGn&4knqh*8kg{lgyZW%Lre`twYlOI4v<_@8*!n|?tKS&Vs+H?f|3Ny5UyNEQoN=GEb zTnzIwY2o9v_Ge?`$@s>`L;K=OTBJ8-oIBI7Nj&*Wb^bAvIWwj#zqaQ50KcJTzAYDI9rSMJSpCTBz~liz z%9cl3%yBC$uipIAU)|YwzRswHu64JDIPPgxlBc-i;|zoB>x0e)RD=~Y9*Ul^{{Hxr z5jW@Ue%*b5L)oP*mroCz>aXW^YQyO>Es`uklfsT2T(m58_=DYhe-%1p)#-m(In`&x zah3TCE#{a{3%|FfV1}R6Oi#8T%f+MJ*SbgR5+~~fDK9xJ+`mKBPpeYr)>HLIE!3^Q zHQZs3bxEsHI|lh16ju&PUc>tv2J-&4w{Fh6ai8~hs=1uIbZUD$w~l8{hjlhs5N42m zOn&LIUx$yV?djNeZ0puz>pSkgeBSk0i2Ux@{Qs479cTzXh`i9sEx-l=%({ zFK_DL!gx5!VyqLSBJd$2?0+&AN5NAVNoDvyW5kfH^8Yv&-jneoxH)WfMlNO(S|QZ{ zaU_bA!ZH#JLqc!9a{l6T%G0yl!*WT@-~G35jGc0%a^l$+xA%SjcJbS(pA+^~{H*kO zAd*TC_4}}0SEN)RS>iA1tm51@Q~4iJNYc5%>Yn|d?L4?nV{O=pk?YK~ygRR3pX&W( z!_n^VUga*;?69`<)ajoFiwktUhurMEGq%UyPTjkjJhMoBXSaD#XV=PvHx)B;Z}mOu z_i0ckt&G~*YxAS?GiLkrj0t!;%)#7e&C^@$-nQth-_K*TlHxkuONGx?7+hbY>iz21 zX|?etsd`&R%BgI;vuNkFhp_>}hAwq_9Z;I$v}(7@!ifHcqjPQjUTJqfVe+hScu}wD zMZ=@ioN6-MReR>A`2Dlr(hF6~)%sw)3 z*tW9~-wZ7)J4gRsxuz&|2%KYj*|g`KQt4X@|M*I()ccHvYV#GcI8>=UEOtx z%HbZ)@|{VnTJR@<6!dA_pv_qe;uBB^{B58IcX)^b%sAZ??ib;c>1V7o6>>;A)aB@e zg^3~~O%rx*G-?y>0J1^xDHv-B%%_S$!+}Mt|w_zf*ajVoR0_Wxz6HSGDY=$(!H?`BYj`n|n zvskvMc;=#w%P0XxYbDkMl>%=flx|1yPpOgw%NcGhDiknJOO-(N&`g?w>V^x*a5^99 zurq;EkxoH65$SxSQ;*I?Iuq$HrUQ@v=se_ioTCy3hymH%lD} z(RL8jDUUb(2K6YJpetvBg(QJ!!5?lz?uiyCoT?%X@`KUj2W=DN^g})ei?*BZm>MJ~USZanJz_rm9U5IS z(dbTPr=Z7+^VYe3tNG+Jt=I9}LsKSxi5jjkFK&hZ7uA+-`#MwI!p0>sKkx3V`0!s=a0tvch9#A6~VzF(w!IC35hm?$AE)klWv~5*T zNDG*7FiBViw5Y-aXhSX*HQcH=^m;YLhnWhKToNTwjs(8igd zGGmC2+6cAR)B5)}HdPe~s0@3mu@lY&G(gD(J7#ngG;fPy(Bb1pvx1Oa~k{$0eHK{IF0L}V|~0YXj!hENhV z!1zF*DFCiuoG{mhv9Yn7mX?-?&iEB8RtW2sYn$PJ8#itwRD)OwJ`Aw~#8wc0LHq=t zfXkPd3i|X_RaJ$25$wkrJ?JZf@1GCOxNdf3&=5=gFp`{Zf39S9=i~EoyezRfNZ`?U z?%X+1GfY8P#fpj|R?$T^k>;vEd{9eEOU5t?NCe)B|A;O?h$Fv(XNZs;gab+pg{`eE z(Jh2eFi8kMDIVlwBK38#<&!}O+qR`WHuV-lR!Bx2T7t^jDuhGVM{-VKQOls88;T91 z!@*HCNr+Gckn5HyW}q9mgS%=0bPX4y@UuIRAa3lS3h}`bf{h+%7NmoWeE-+rLR!+! zNOOWwlHa03oJcP)F2a)v+P1(sro}m)GEg&JkGiS~E=m9ZO4ZPyfebWykoYuhngYz_ zt4T_e5?XEwbJ9_A?|Z7Ter-)-V@i2)Z2i`eFH+8yzFa%G;oP6hb003OemOVkXVobC zDSHPEy#7^R6eBjY4iF6<@XD=)pGr>!m)`2vA6fpe*W3O1m`9Ms^n$tD@2{`DGRR%* z6@7c_H^-f=FMC>cYByrgoR#kf6&Y2ny4`rxIkd>Uo7K{j4t1Mkt+>$^NAvnrQ+Ll zzTGEH3-M5n|9Sm@bQ$afx@I%Dg_$vrM+&ORRfIBQ0I zk7Y_F{vm!M&sUN{%FYs12Za!T^^mHi6YO(tvb_>>h>$u~3Y|2XzJbIn+_kGJKnRFf zLCh=2p$Lf|bd?})B6qsBSid(|p^I!UuBJ8t9RL~=YkGq4l+>bJ)F+@X7^74p${kf4 z$;Mo0UA9yOIe}EEA6nT%KW%cugyW|wojg3O1tuv-Eyi*RyP6Mxo@w2IxP)V{WYG>~ zVXq)E0g1^)ivAn$Ly%T;ZUXWYF3D!F&u4@J5`D*2%>}u6^JXr5ay7#57}ZA+qPdP5 zqxrbKbNlBXw{&jv6a)D8-*_YzyZ!ISXuc);kY^Ex#Pv^JMP|ZJq+ua?*X1~Ei_U`s(&U>AI&8U$WWyM->spGgfd15w}{L>$Sm!RU0u zO2Q1s^EVt~I0Sb901FvuC|zv?#)ONh3Mp)n z{oM_5&D3cu#KvwoL05pe>-ADlf<8k#TMK#2iRiJ2T$OJ^uFJRhZ%`QJvF1&KG8lIT z&J!ntpB!|4=YTUpoj^Obae^3`PT&^-Up&r90$UQ&;WG@C5wC(w1#}c)6ih=>RdG>Z zuYzi+rYi_T?i8jj<2^W1czYHgf($N};q^@gQA@oaxE$E0I*!7j$=zQ-R>|oJ*@7qt zQV8T0!j~grjDjGy5rPM~z3?AFhJ0`m9cP%^3!tB~5#q{cC&6*H2|QzW?03y)a-X9( z`tKQ(MtKqLrR>c!n|D!Ofe%44)dJ^+#823WV11V$!7x);U@6PGD7dK`gCrnJAf88J zVvVBJ90+Gf2hcQ;bP_{gA;T|77))`l^4~2IrQsM;>=#Tr!uT5&IuyQB_2qT^akfg6 zve}YF3+1wfn)X~Rd~}W#ACZqv;%FAC3BDwe5dBC1CPVOv8Rb6`3;#HF#LF^U6N!d@ zd~He{+|IXb*&?h+#tc&$ShQ$SHK85CL+%c+j?fFyiGtI} z9Cil8Nzw2q0Y=BYI~FeHSF`F(56pYhx3&02Ox|{NDf4IJ2JgBVVpGtkpVR- zi;Mb#5IC7qz;ZB$^5YXWKMJ#0*Kg5~IHUar5rf|t`+!k84lOSqrgNm~VRq(=(m#J5 z97$VQSDF2D?W!{wwJqdT#(#GlEOMV~(obR?A!>K&hV#YvwQXBDZL2wE@1<+BuUgq} za#V1&!kVr@3mbY|^K|I?ptfi2rJ`{q;pMqoPi2mAoiV$1=*TaN#w~fWiE z!;iX`f2N(^dQ)qN<>H|In~S=Lwu~+-`(qxo`GDItt&G-+Wl8#ZMQ1%HTK_s+)jx3l zOY8cWpbQ>#dMGO%&oq&Tc3{`eZBt1^tpaI(;_bQD2$Sx{OH|xtXoZE)~_~Qw?2_~ zUvPWY<$%I2U5eIU4%)I{h?eTs6K(!HY5d_hwy)=)lQY{CZn#u1O+R4n%*gsrQE}(( zW-qytS?@7pw!!Y|Ejg}-hZy)h(0{hA-GkFnA4EPoLj(62J@4d`6)SUc8SAZBe@Z#G zyl2U@@t3Zr|M5+`y)|g_#qru<&*lcs?mD^c$?AO_dX1E;xMWb=Pk-`vk0RG2l3Nu)#KuB(oQI*gSawPdC|5Wvm2M3xb zqoZVC9`2xTV&b#g@ze%bC1wVAO%t3CQP~o1 zD%i%|@ghQ2Mo6xxn~ALso%3;5R*2W)7~qp@6#8@1!4N`Ja2E2YHSN5tuq4*QGDV7@ z9NRPn_B_L5f+8DYn4oy$d5k?_zVtYJ#O z{{Jji9uQj|p_BCXo==RyBA=2SbM6}C&Yq>WCsk4N#YmGz6dE*pLI=cu>^pH|Miq;kU~ zV4CH^?g1))G>`Xjd~?&odb8r`=z>jey!NS!1KoZtwR(M9@BVqK-)rSN4obf67EnL- zuvH?y=0 z>>e*{?=~p?@ANYp9o+AnKHVza&TCZA{ywhBUL94|{;8QZVnXle^E=C`wsvf{RI{C( zUZ1P|yA|%Vo267A5?(V*{NVc4&G)58r#%eJOD<1~4op}+?}F8_M~)NME(~6HZm?_c z@YIOejZeQ7csbQKzOu7q$Dh$NhSOM?|Q>csIh(5+bD|i64J;+4>CrEUR z(FZG2jTR9C3dB0NpQT_i1l$QmtEvhJQELCw;D`t09) zEk?g*r#2nNohBGQkOmv2n2+L*~<|i`WW04hFgZJER0Y3;bGF>!yPB@^=yv zB^%tYiV)3dV=%PBc#G$z_{KktKO3Iwc`7gL9Q6HA!{p^zzw0NiO>_V7yuPZc0aD4h zex2m}u64E^B%V2DO236WMB^tM>9gg=F`q{J>ix$xn0iQL~SOAVEM*E@aRJHfMc>c!ES z8(vlCh;9Dt|9;0faOstwYa{ZOtB-dN`|Dj7Rj4~B!a*-jdveLS^TG1(`ufK242+tw zYweANDe9)La`Y-EtDkX>buQBx5OsT;W7^Wt6zevh=1>0Yu*=BrSLd)jBinyFzH!ou zu#{GDwiUBCEZ*BtYFYDMA>+`L5ets58o1zy^8S3ITMzg3P~6)0SerPd37_LWyj`_h z?p@h~?j~EbPY)jY;g4gk=j5_if3#0lr=OAYdpUc4cK!;7@OvM}eW<$QJJjiDfYdT$ zg-N@(NA_=veRIV($^w3`^UgYbcFqRJngpLkb`R8*6Ll>X1@ufioB!@oprOfnQ zyG=JL@M3(xn|B&5E?q2{)YwY@p~7t4!pU*oe^cGZ>jQ+1Z3_!SqAECXWmH9qx+u7? z6*LH2IIA%i!7oN<5b{%C<-veRb3b%4A*VF3Yi{P;zJ!dvrU-ae15J0t(gG-haVc!# zk_F+?+lnYixMM@OJ>np8al7MY(08Kn!8JxFGuZ^S09uhyo^K2p3vnfMT9|?b(A~x4 zg4om~O789k-150n2*QN^UscfrMnw;7%#b@}(9}#|w}c&>{wp_JpZHOnK|B)CPTb~Y z*hqnha>1DJ5F(@{!cuCB&g=qKz+IXJa;f_2(=Sm(bdr!~3k4k9aa530csdg!=@G>u z#C+0R3pq!~yUaIK6UyX3q-MrZFeRX~=o-mLqs<;S(`u{W-qa00Xw4P;8yGZXA#u|p zuf=x^rQly{Wrv--#ruSj$zf}z^z&@2U(>g-+PEvdw(Eae|ebw9ewRkTX#P({dJ|XPD^BK`fks-6mj;%hN^o53&oM13MY+Y z?isv{j5~5#$5~U`s=Ow8z`Z>N(KhYJX2~39rKxBQcietU?O6H!;Y(`!)|c+v^<`qb zT&pO@#B|Z5TIFYvH^$~(&GQKFk#SGu+|TlHZ+G9g&8^j=ugUAwA0?g|*Khs$ zwBUAWRjq5!gctTm{;}qoQQ?4U1qH)Ct{n~bcy@i7uWt0C>;1od((fo9wQku)@peVb zc`yIAi*;6Z)3Y2g|M^*I_~qNrrY+TY`mv{)Lx))-WQ)c=SfO+_daJpL^_u0G3$FWD zRX=zVFl=|_&?i2Jl~ycuon_zu%R`r&(+Z7h?k}z>x6=OPY#4gvmf}m3dvR_jLDedl zToYwbBX%7YMrWo?p$*dwF%!}u3Own7CItOhiw;~>eVl@BX6{&qnmP!bEfO0+vm^LU z1(M@Iu^9~S+6oF112?#j#Z1plZ<}^Vlm>3ZcFF&=KSQ}qoX_A_OR)v6kU}ky=6Jzv zEwn|4BQFO)Tw=?N6wti}QG-|rZfc-U>|yy>Qai0q1NAcY=U&!h99TL;`yx!O&EeEA5D}FyAimSNl+~{}p_tRZb(+@iI-x^c! z=k9^T(yy+r?=M|UnpytK*y7?z_x-Ka7ku||kl&uNpw0UMZw|TKSkPo#>G+VE>1l65p1I~G#b7UJ1vL(w zme5_@;(X9P3y7dNlvqhs!3|mp61P(Wk}_)?59xsJx%*J)0u8~tC2Si}MPQjI8$m^q z92&on${UJ%9^KsOiBLfeVwY&0$Gm25=T1V(t$Um_;c6fgigd z2b!R(F;jrhM@iThSVjMonLJ#2U!M$9m;(n~U{zu={fK+zhZ}nS}2@ zt;jHtA05&QO9lEZvNHlN1#64of@e_(BO#CrE%BSg3Qz&tq+vC+nsx0|UU*%$WsYmteC5`toJ@ghw^ocPvjj%DS+1hehRd)TcjH;{`SuaYRmY=Kr zo7EU}|3clKB|36utzt~>7EH6Q(b+1JtUD_w`bRYNQ-sE$b-53^)#>~6A6OV(utjoe zO`jiixAX5*ukSU)Z19R@k=w1*R;JILpLOTg>f;-J)cUVbT(Rr8X7#-@Ra17pd~s_2 zg5!1Bkw;HNj5ghJHLQ!8vg(~ZdL>$iUVKbe+kEHcoc&Sh<1d80^WCs`pY6L`^Y*n< zGml(dRo$XmD=sVhNb1_zI(0vX)I2)%JgHlRlEA9XP$)XzlbdH%xAZKfXHX zZ2P}`>TYX0bZPB2IBan7z$4#W^)p7!_3L^ksD0$P28YW|EkAlZUKo_w-b^(Iot6!T&KTxFfCHJt@TM` z*+{ioJDz0jh?g|5>}wk<2DsLxodJTN?eTw3+= zn2)cmx-3;oSf<@y>*mlR(>w2Hn%o$_{rrx)_xqwZ-rc@n^FHs&(3!QO+;!IzeA`F% zzdV2H%#7b2>bqwAJoiq$_h-xaeaAblka+sn-hO!8_vL9T^>L~5@}gcH*%wkWMA`dl z^)>C=b$$M>=`v_bL3p9p4Ik^Z-EN#-m%BG&-_&g)-@@)&cZ6t+awxd%lvJV6Cn!2# zW&Odtq9E;I{=Z#AedSfs=T4aWec_)6s{N)W>)0JKJAJllYG40@Q};)lxM3FZFk;NP zDz7^eR4(VHRGYSsU*)#Yu5akm#ITFL4GX_*dE#nX-`?JEkX5Z}JvcFPu7O>}!*-FsTm zJ?+C2f3$g7zWKXhr#Dw-kDTr9S92`eLB-_e#^Alf505E*{URuDSx)YgF)h=a+Zb2$ z_@bybF(JrGwRnQnf5jNyoYmB$1L_Tb_q18y;?6XUtgSHvP#w1XV;jtF%Pfbyrw-) z>(_G!-yOOk@v;HM(;swiKQ*#X$}ZPg_rnjwYGi-R-8r-M%HEX;lSW0g_4I zREq#lsSLk)V`f<)%R>k`9XqhslPeA}#k3w)Q=-7fuOveR41LVb%Xrt0F)n(k!1s)@ztTFutWU4TDA=*JoS0AGu zU?Y=4Rd&N^QZ1Y|1_5etDg6E7lk6fy!sRFeul@oZAo??cLN+<-Ry zmc0`bt2Z2R7EOy|ya?qybTrqpDCcAL>rJ(HK4zx}SD_MnrhgR`5Wok6r28MW6e&^NJobCNw$qv6->A{Gs&)y;A&Yf*WsUZdhp3 zb18?fl4^!$my{%;&CHK5m-y>5NR5 z2-imgv(8&+Ot$#C<3+;uV*RNjVth9mYetl9&F#Fs#`?z7M`x@fHA?>3pfa|{j-X<3 z{-f_cU;6rbMc)}0+AICBm$A3nA-UXM_I^Kue-~$3TfSGi)jfBC>$rFB4&wt>jSqSu zbG~8j-ACv8mwDQo$3}L19vTqa{lF62YoEV&dN5?$h0NUYF{KCo2~1X$8@1veugCK= zyM>Roa~^m4^_sY?TVkzGZt0ejIM>A{yMIPjr*(CP+f1x8)?W4dVl%%yVEgPtSzS(a zn{chGTG_0>lGSJT?)7c0ym>&!v?-%@D_QvJr}nD;X?3;YP29C?_s8a2GzQOJ;i`4s zy>fQJsv(gj8y6|q#eH}>P^V?twbCo^~SY$Iem7Cbv`Y21-Q zEO&z(Ej5z@Ee9hkxhDsQwWY{1VcI4qMFu|!cVO6mA-26gxyu5Pr;D_+@ncH;BQOOMtWwaXsVZ+XzSx1&djCeJ-&lI5f_uPnFyYVAea zciuU1bysl2DzSU~)G3X?{ zUA?JyBxq00>lO|BkE=ajKD5H`Yp2M`>8aCqI!&9p@0EX5hhvL&IBZberso}K?sRhf z^+u&{?+jCB+}@#6`M%?TS%v4zO`dIe)NilByg&8*6J~@d|&Pw zq5op}9L@RhEff`%;{C_EU;cP?#QtjE*9EJCuIt@Y-8c2w@3vh`Mh^?UXR>$X)h*B6 zoX;1`8t}X4Z1K4m3wuwi(AJH!+s4bAuS(F{rg~!c&aKC^J^gbv=gB)|865JMjN!!o z#~n@zYY7!TB7XPsCnkZRC`twboZlb{3x+AuK{5Yn!?J)nMFSspHrgGlDl|}~8H*Fv z3rsZkKTy*DdmJ}~%C{HI5KGXBu?Nr*2~y&LD16Y_rzKXyqGa?Bm0XG)YC4>G1DH{j z%~arY`blahiho>jO7NS*;~CRj?04?u?1 zA_<=Ve;Yx3f@$Sy!eyth&pQ34j!hW6Ew^T$o`?D8h(5bV8brkAc^e$+?hq$8uEKpy z_MgR#59c+m8NKxTgG;63FD=U&(L&t6-@@Q_0}4JTMd~W1it;~>(LaAiQg%Nvu{5c^ z#U3;Fg#3ebinTMIbPAgv`Ff%GvA%5{Cj0x^efPTMDAnuw>Cph`v>D=bXSMW}?MjZM zHvXMvMc(x@A%7eMyv4+zCXU2MV3jQmbbGt>^H|h_{{Hi@2^f6VWu1vk@K@irP}jK z<)Zk@7e)D+QTpfdO!k%xU-EQBo$t1}rlHHbb(Phh>~yPtP2Vq=s(KarXF3G)P2ywurTMyxqrGgs{6h79-Xw-;@MmUwQjvG_f+}mP;l$mpnI}o zFKP~CYA<|PT=2b5rQx`4n%XgeU5qQ;PNW4LJQhCaxABe;i_gVcx$|sthxUFqEZN$m za_Y$)Z*Dujx;Q*x!q8V2Cr9@B>wJ9T%$GaQi&}isUQ^WLYG0K%mX_ZmViw61zqj7j z`@34(Y|BfGsoMFsIIoYomq9`*qiI8;~j^)eOyvBXy>^n#i}31 z=Dhy2KV;RJ0FR!oJ&j$S4b=LTw{?z9+R}u-dlKFD9ryqHduiIxR$b1vZIu_ZePhD5 z$(<)`YW3YV?PlTG`he6^uOH>NYq>|q{K<^lk#)Jt6BOod^Q})RO&xwe*W^rB{V|&G zW#?0LZx1~KYX$~B!aeAJ*O1c4LvOx@HI8s2fQ|viEtX-i!JQyhH4tQBB_&q7VSuR+ zN->e;A1P;!p1)vGrgE{jtij9TM2&u|&Etr==AlL#uh_%mT zjWBRhe3#&Wi*SIwa1~Eup;-WkVN1)0<3vjWkBAaP0q_A8=H-{-E>&+3^MGv%$_ew{ z&_#gCK2jBA@WCE%5X+Z)7_=E-OV?rKHWxgeTOz}|A|V7J4bf4!BkVfz3%E_#3Bd~l z77@uz+!MiT!~_va!~!O)Sx4X^p+gM~4FWih@IOL`2p1+k>iYHT0v?J5Q2x85Nz0Zk z1ppl}PhWtu0%A!H=x3q!;K`FGg`!M^ixHkgI1=$uK$MAyIwZ)tbLY+?K*|Vx7Lba3 zE@W2|ZQZ(6fJuLDDw`spteXLML@g4rNHip`Nt7DvHxTJam=v!;xEgCVpdWEnd<_#| z`}lb5PJADsS?tE-Y#g3qV6gb892TP4I8kq@k`jXqZw@%LxrM-`p-793Fw@A1 z)|7b?RAUBb7O|v&docf@;NOA+8nOaVe@J!5z%E%gaDGywAm#l(Elp`6TPjGHq8g}G zW)WcpSE(^*UK4wePE(r{VvbZO`KbnYoeHwW!y-x58Caf+P#L3%TaRy*PGX zN&muoanW#%kv93Rq+Q|xF@7Q@cEq~EaPW;u(R>{iLqZ-$0cF^nCXygr&IZx`SS><@ z682^I5r>87Mjq!#6J-czy%2Cf(&va1`N$C`Xs{Wc%$eae2t`J)1Sg1>;H0qn85dS_ zaU`w`E-Jp%zlD)_RlWzC*_Hoz2QH?66ISvDoNBJA=A@Op3(<>Q?qJYJNhnc6C<}a| zQ99sZ>WDtZS0~ICY}&MNqd;GAk((L5(f!Hd$dKD5#@HeN`)Z0$#Qdq63tNX9QHHXn z?-C)x%L-S(a^wdN8DqyWgd;m6jbFelk*hKzt%!zlR#uk)Zi>m}(x$P=*j-A=j@Ofc zz91^PXt)D%0?MF6jlKd6i1bK1EV3Y@?VI4-Go*Cgy4csVPA4_aD)+E4-G3pr)3cZN zOGhn@{cd@`)G2F!eZ#l|{}@FmZ2B>H?x5+tMvGi*zxcF_%!v58eyr17Th$|P+ZQP< z9etpN8D>eYLn@U&)VwdgasI zFGq!+f97)CD0<9(!+T@frnmj!lJ_xf-KXx=ziecE#=!eK)^7lbxw26!cD zxbMFsKcwpBR6Taw<*rsn* zytQ0<=3%$hTDK0TkBywMcSy9(1D9v6`H?+~rhhb4e=>LZyX^Jrm&%S9HhdmXS?at= zI`C8C;rrvqhb(XNzW;t7jU=xXWeuAnJ%O(DIELQ#$d=ak#O6_)+rP$qgx27DAZ0vv6_pfl-WlB8x`C|P3>CHGc{cd z88N~faB$gp@&cwH8OyE;IrwVEul->_gk=&J5<tedrAW)dk&Y zV5`rD;i6dejKeTOJ?}rfrPj^M;5; z2gFuLwcc_Y3F%>icScy?fqwmmW@P-0|I_zxd}Dddy}vEyeHitk^xU6w4HLgqemV1f za(ed5nYA{CvFV3a7$`~$f)fwCS|ifU+-_lRH*Cmavw_Zz4@7g8ttc;aH2A#fp0oDS zR_=rM`}p6S)Z6>KcEZ`ztGn*!{`_!1C-AP)*zpb_pE5jtUoh+(UETHG`$6UayhUFg zw&wA`+{YW8UGE)uJE~y(=-o?gKOM=L@u9`-uWhZ0{Wfono4LH9c$=f^U8|TA?JwHu zp1YPlLvL8b;_FIbDUl+NC6-PF;i1R#2Zu-B4>2?-HoZK{>a*fNxvz1} z`!?N9p6zD$VDi+4#qJ}trv;{XY}&E9%{#aF%s$h5`%bY-oqZ=(W7NtvqfZSRI59Qr z^V@5x?P>C(qDLFqc8xv}V|C5KF>!6fqAziYntsJCE58E@IqH3D^{+Ub!pJ4LU)~7{j!pP&j!xlR<+r;yI*&OvVmcXhYYi8 znZCUz1yB{}Qfknr5a$jm0c=`~%4kKPKV&-G6&fjI1im5~;eiL>hrcqKMj7G37KzIhVW~8aY@PNg@cnP64B09q8;11CfWh)p?MTaL^ zD2%%6LJ}dD6B|JycNePzL4Y<$Cam%&v_HnRj1ZfN?K3+GS*Xx%%KR)6Q#u}DAeM3i zMd?0?8B5|azoO8@WU~@Y_!CfE#=Yu8MjFijR+b=if_YefP(o8T>WhZ+qjMAPti|bs z4uN5)hB0K-+9%jC8hV>1$2K^4jcTZRneA9x{PgnWw9@a5j%OO`&J{(D=wP4}u?`ZD zYPx8?$p7)>{x(OWdj*+x``xW$>@W4P!OJEL{;e@z{O)4Z=gX069j@x7Rg_nozUB{Qm9!`F3T?KRh0`bI3^w}#~ooM~5J{ZBWWte~<%K6&rD z*MAXt`jnlWdEi*&(bH>Y`U-yHXPbK%X^nyg*^#&;H*eQDdeXw}A1zVa7TC2@oP zdH*>wLq9-m=Y=m>Dvll|rD~fq6;G5TXUK`>RUSV&vfaV%J8taqY`w4{!!&Pqzlz#x zv%2jXP++@Y;FaxLx178kvigBW&9LzwmaMV6s(Jfi%MCx;M49BS@?N}VeUNF+xy0Pm zXI&1L^nLKt{P_oyexJ3p2TxeId#p-OZ19rbtxN;+qL13@T~_zUTo7l%bUUyr2ssI4 zusI-al3)h_AvAx33{}MZcFRz>6aW!nyfqp}@1|O>UStD>}kG_qs zWIrFA$@Vi_p3&pb@&%2xRex6fTod%|VvA1Q^wg4e?U=u-K;*S9SfqG(!GQki&+mL! zOT7>j{P;=b{TE3GSA`8MykfYwL#(58M&tRLnmWweaDMdOEl;8 z8gKh%k#bk{@;b}v&+V=KlFN6GliU6Kb&;Rxeb1FQjryF7tKYcY!MWyg*bytQ`$uQF zN#EzZY}Av!+dL?tMf{6_pO+1I{^Uao#gPk?pGwZ{-#PGBZO*IP!?J#KSu$efgF%Jo zLtQs0J0+O^-C-ZFHGlR1t*u9Gew=N4<9+9Y;X5;qh0L23wP?c3Z@&6YFU|M;ktTU0 zP4pQjx9#_;3l=8z3UyD!eR{ZlIyKxr;>C%POD!YS(r3-G)_(mnd$viSn_}YYdr!7J zUwE|llgT2{{tmz8aKaqxA*0JoL>W@rEWTDMX4Nkuoks81vPFD4F4ko8HPA04qF z$me$C_=S_Q#aBk9be*$nj{L7#?%C}JUb(n8MPo_TGsAW{PkM{FKV50Q&V1WSP^0;5sZLWFz6^4{H*J5);V2M$nlkA_0?_k%EN}L7J%} z+{gqBwIMt&_&3j5xF{kbLI|#+TEptf{G@Kd3v z;QeuZ5kefPcrjoLx|mP|fMMYuK70_$7*l)VV^ZazabVuOd4jS+{f4#=p33-ZYF5yF zMR-KP#=(e7>ah$sM9DxoWaSrLg@ywtKSD$}m7yn3o(Qi)eHz!3OGro%)G8{oRE!vq zP8E!OskQN+0%FXlYf%a2b8&c^^<~7KiTwQhM0^Jpv_5m@j0kb@3`vWPjg@0H@MaSU zM?oJX1HOvlt`c)89n~8If>}3ZwFN}p7-4e37KI#7LbEpP147O;wkj%w zH2oAKL8?U47?c4jfu9@c7#w5~x_oNT|!j zpOD~HNw#=4>g@pNgDg>7pfxP%7b0kv^a_@=8WunEvRrj66gQOOt&jE#nXV|Y)n zNeN{jbX!LgmlWjBqCml6G?ib-b|I81`^)x>({SB9gqJmBFaj`q-7Eoug!Bfd(Fl+ z7`BiOM=I0}&dSOXp+2Pubv>wmb4jrx0ZE*6LiHUrE`&rQw1+X&pLC4J6v73E4I3sR zZS(O+C`h6pR3ad4aY>O5Y3rdb&&T3F+JIO%og+qt9)BXV(csXlSFc8{a@u7WKFKA( zTQ(aSh71`Z7#VO(boT67!M?^kDLj`Z5!!Tk4!K0L#ew09;62E+Ct>gw&8SM4VWdeF zCSCYWZi}E|p*wEZYD9Vu6S`%EfE9*#>4z``3*3<*X7n72 zwINk8>#w>LDbU0!NS6ABv<*OumLb+3f;H5EZ`WcwnL7wvMXUu_eU^2mB zlq}E&)d~|!bCJ0@6mt`7ex2bfhXs?7ZDOH-$VQ>OFX@Vp1e3AA3~0XM(DK!|!h(Z? zNt?n7KS zK_MFqx5V5^p@^dnnq^%ZtG-Pu!Ne3)4xTMYHW+^G@G+oa6(h9bS}KvEB1|n7D<=@A znW`#?7?0iJNrG!kSX&6tK7aa_BO6EKy+g$w4qhWiuQ~Z=a%s@o^z5o1zi(wsZaCBU zz)`7{-2Q<|i_M+h_EHor7ERZgd~QJ2&z4C?WQjX1Zs#XvY`k3B&#SL)?|oCscFx)4 ze$*zfc9PpOjmYIbGrGAXPgeH-F8?ge*Vk)!9kiiR-P^TUCN~3*J|5b5+`T~B*J|tb z>1~onIbW;RS=(*Z5Ba%~_chu-eJ_$d8dLJIza2FC)y<4FZ8{` zmoU}96LXX6N)1iI9GCR({0<2I=24M-SrcG9IqNE z(YI_^HKX98+L%YWQ{LxzYN$odaQV@`ThR3CJA+4s7ERx7Yt?6J!O&%mgLWSayy?8U z#y;Wcyum8j_jkzeI`aC}@0=w&Z&}|wIO6VQjeF6P-k-JY?!Kw*&R6F9>}T}tAKasC zjjX&&pVc$osUOJ=`NPUtZA9ZhJKsQ829;H-j_szxpG=aP;&!ggzK7;1sSiAZ7&!jiz(>R~Sn-j0LGNPZ#DuJ%D&&#`(cNLc}#Y1k( z%}jy}guICignXUt%@Q)12-h*C4{pu$U2!Ys_Ram3dw~I%Cf2wHhD>Z?hGFBUKL!Jh zh(pdu!;8t9zw2T&Wx|}9<6~RX>b6pdk}0^M8``AEq#(7J*d)u#EfI1tahIV?L+-+V z6b|qdlqtY}$ZIGT@IAPvkmr!Y@qJ24N`y0---&0AA3t6MNr=e}gm>e)lmqyVbbe3{ zXlA6%;s?c>!Gi~LSm?vxd5}5dvye75uFU^bc1Gcp>Z3Sxp2*BUKI49zkR= zsH%W9#8MwyT+=t)Lj!V!ir7MsGjz~L1wx9Io5jnU=mp*=u@Le}5}S#9s3I-UlIS66 z$y&rQL&`68Gb|g>6?4QQ6I4|Nh670`#p*J&_f`}e z^uXX@ty5Goz{BaHpXOZrw!)&qHm$|aen^?aeR%`7f};thp`n*_oIfY;UN#4zJ0&-U z@9TD`&ZFh1hOt?&}=#zYVQ)(us+<;xBRx-4bek_r&sZ zUm=fu-{^GlQ1tZYi3=aRSK2Xs*Xx~nmd1w@6VJE%IK1v)Ve1jaK1ZJFna|kpC1YsD z(46=Brw-gV4sX4xRnOqn7rVx`&2RB7FXHGN=b7J7e#-bG(E>gVWqtBydhz6ce0ZEMQE}iERkz>WXRwT?egkuo~E`fY1_S5>-~#-y`7JJ8$NYTVqsp2 z?z9b?qF(g29QayVvm$)WkaceZ)#t=c-d;a(rq?)&xKaCkPPB`Bo zTWOC$V%HvNgGN>K58Cm)^}(BB%VehoWmSpZv0K(%1bH&N+E}R8L9tjLTvrHEHj^?J zD(z&97lR)|0@ntAM>NE_C@DrfUNbtlZrLzsrLE4wAnZVfFG)cFixdi$`yL_>gTH zxAR5N>c>Zarl+}Sn1>o%-I1$y#YvU_()qBnlWx+8uES;RK9szl)@euTzEh(g z9p711r0?>NRlG6z3cOQQA8fFI!L*pNQ^^i)SqurSv&`rTtXp)H6*wN-pyLp`X2!If zVg~1<@|94f1TYBt#sqDM$`p-cS{QvbwkaT3oxedc$PtL(00Z>a#~<{n&R{Q~6!>k0 z@{aH`Fn%^VitS=8Oo@ywpdpz)$P3Jg_FGhr62q9>6mKh0>yD?!HB_X`TB(Y{Q#c9& z)&v!h#*LXiK!C`UaBBA-_*xeHCs-8UssG?k|HOY@`zf&VpV80vAMYf0yr0H8N748fJNs^WdG|>Nhn^cRCtlwBE+fKv(yvWDkBDO@|FNDBHa7EG zA4|s%awjUSlivP)YwBNicYZUQ^3$7{< zuyyRq0e#Dc6b?W7V||gm+eV8_UBjQot5*7K*`VHKu#^1{wS^+NYqCe;+Ac0LI}`<4 z8Sj5*-P5l_dprApZx2dT&0ZOQs$z$Fqzw@XfA(G+ez`+W8}Fy~FB7tN_AWKS z3IaGt*o2hCnmLHFpd6!$Qsk#@ z6RhxmfBXaXz)e=7Ad+#rkR7OV2vuLVZvTfT6qn?%a{uxuGrJ^wWL2OsQ4s70H3oBIQhzvic+KXUu;ebM?TmrtGh+p#UG2k(_r)_E5Fq6diFcn{5+S- z`g;m*=*;&&k=i*`G`y?H#K{R6($9{;s`m>|hxY!qXhMkmNaKFX?GwJ2^vbKqC>ZuI z+HPEug^j1$wI^Y5TjLLZjB9<{b9DbtPP?l$UsURqtSw4;y2&Qc?fACN9*^HI^Pc7{ zCmJ#8z{E=@JIchLH@bj{9=%d?_XFXtVSh4tUf$$4EGQ0}5_T~ll*KB=MHc_n(xyp7bjCu&^iw!PiH#;WCbmLL0dGvN z1#SK0@^m@qQtT6(F+&&Po~T3M36$VPhKPp{b1Bo$n)4HVg@@{fk~ccJo=`G~zLhn)Yh^KV~O)}=Oni2aD`-xhcHyJPc)gs=Mx?rqvJ zVp(m+hb=qip9q^W=djm5+FwKSyaKKE4k$U1^BW0g%OjU${pk2QO5x|9w!=G|eEisX z@`RX5HOrjEn=@C0tonM-c9P1B0n3tYYuDv{d*=H8RrMwCRCV9`7gsKBhEkd5oB1Lo zDp3--iYQYP%|t5EBn>iTh-e~0gA5f(l$1(ks+3eTNi=AZH#Fk^tkc{3|NeYFKKI;n z&OO84Yp=cbTF-jcDL>a^H+J^l&?vk4e*RjSlp#)cD?VP1Zho|Fr+&2Og77sz<`pRo z*}rAyCNuA&PadxI9<@@nx@aelKVrt4s^PCZySqb%&8VLKeT$gTvqe=!6Q&I>+$wP^ zN>y#2L!RH#5ocq*iYh+n;Wr(KIb5^%S$5X*1Cl?2UoDtprk{DYv@M}4Xvn%LHx8xC z&VT=@TH|fP&J$X0;-^+C|8t(zyDukHcbVe%F|FG-AC4(s8CF09T_if55ZA#dhm$$T%P0aGbcR*@n~ir0vKhG7J3G7;qR#qCi^i)>+;KblEmCdLT+U3QmP zjPBJbzGGbTczVjOH)VF79LVf>aPC3z7`NqnwU_%jwCFrLF67>NCVa5+*NdNJ9_uc5 zWq4m2*p_(b-11(IkWIS3rp&JG`_&y~v)6R8++)o8Wt>*m+_Y}K&)qt4*7S#Q`%WcA zZrG`oyZ52-;`no#d2<}f@3>Uxwy*C`__1WDzQ2`=YGU);yBg|p3MaaZq}2;bT>CV~ z78G~i?pG0Pv-_ZZGks35$e3;7QX^M3L`W>#95GF5?Xa`_tYnjS9ltX&jKuXjcXBi; z?y)^SNDlq+D5v)5TF-IZMxDCbZ}eQYPQEjpZT7J`PR?ec;ts=k{0lyBb{vS%8)>S& zRLxxD>7)y(277l4vdr>b9m;c@-S&uXbCi1|8&Q=n_3P09&VMt zH0*5HH1h~}p6sucKbGlW&-BI{ft>qT$dBd{a3s2K$|Ei#$CrePCNd(#h>xqH}-S?xehU5>Ds|1&z1Gc z_dgl>ZLVVB)^_7Btu8?|FQ0_+Z))Xz{3_Mx>lj|cchepycZkZ@Trb!$#=~6cNWhrG z(#!TmP1W~#S+dcLFLC>lx=YYX*5dB!fNLg+&S@bM8_nxRXK=^Nz0f%F!7BTjWxd(! zRxXs0o1!z9J*?%|k9+C9x^wtZW7Z1Q4(W|@jA)o1$Lgqz(7330ck!?fcB6VO{YqV- z;=VGf&&9*tMs?oIpJOyQwtMT!CvBG7+-RqGBRuGXW7yG?!_C_?PiUvz9b!=_amS{m zqf2OUvEBImtY@JQPTiYmeN$nzlW@P8tUE_s9g9I4KLQ4yObk6nG;WD(5-R~DoGFTH zD^(*Vj$k6@H!L>@VS=H~P^Caa098V#b9A|wn3PA&V*NRntXDb{fo%+VOk1#18>%q; z8LkvqE~?1l1xq?x5ymftXSDE7i$p+7DMuiV+=rt9#{$<5&elYob3*|pT0#sX1j$gV z$dl;;zbBpFdINfZrbAUi|8b}yhas?D0vw`j9)TX=Q|z@F+=@D;XRKSe%s!4bqCixI zKwTS#5-|N_-^gg-S9lQuu3{C&@Q-01O7@bfK@J_0`37&5fNB-+i0Byik%v*GLeNzZ zX=Aq<;`$uoOac~x$-g5+D`jSrmK%-pXc3LC2)A;<8N-lYpf#`vB+vs{XQWIi>(Gb^ zylD}Dfz%*Zmn=Bue3%GK_8N#XaQ?q%z+TWBBhFHTvJ;(u)YdY(8RUXXdus(&T4tRC z`@W5BasGH>Y-5^hUGlyc$@N;bjjS^*-j#ttpDG6rZ@XA>(Bs$V7lD=L4FN|vJJuYp z5Srp+qwE2E6#}WN4(|QRtwgzu>09^ zr*7Yg_=E?3V;zV4S}a=5tJ@*6Qe&u8O6v23;rA!Ej(Yfb+r%K9CHE#(?MpY!EwD3v zdhS-J?=iKQ(WVD$Y_~icvG>uX=FU<#)h=I`;x#rcw<5cg-W(TRk{_vY$AQ-~dgKHH zlLP)QPbRE$AMx#zqPtT{&*}xSw_m7V8B*mkd|_R<^&79K+P3J$iYL!ICroj5tBFjz z=Voiuwd;P?k|j+KZ0@*-?VX=8-tXW_Z=;U*;a?A{aEwFFmTiuoUzibjb`|$~l;3yD z_M@L2pPioE_N8jdVdKP~dlQ*=CYrps;JY!_D)v^t5Jo9X;IA zb8d}Cj-hT)`r`80*QYiA*y%Mx>Whu!?BGKyKd*jyd@H}Npm4CLb=H`iO(uqxvThz; z>7UxXFBRL=q`B=TmUwC1Q^xz~fbIW%gPReC{c`-BeT`$TU z-s~#*u5+wn?yC6B_i`0H#l&2$Dozh~>6GhN^5&XwG|Y%gl;TpdCtDnXj8Yqcde^zwwc5m zpYF8}#A1%NR>oQuOmA6MJ-fvIL&xL*3)?T9{~)`4Lfi_mwxafzR%=ZoKN-(&k1Z?6 zHs-uL{56By=I8C-<~Ke~c`v2Xk_CdHMf@%l4TmF^iGc87SRlEG36YTHLf|^`p@4dZ z1LAWg1qADQ(ifzHD7%yr90~fVhzg!SanOim4iQNJgJ>t+KqeavbEq2>RD{zTXhDdLCe~u7an+zpq$$+mNiUHSBg*ikE6rK#0il9-|q&mI$ z4&)yEMzo%UNe9ecdo&d!q17qqpqLXtj_~IwobLD~DH{AU(*Uk*EGaGryp_i&hAx%z zY+$Z3u1hMUi&|k!)o>=Ek`^_iK~q4q1r1g9jH=WpeP38WbpUCmVDuml6qeLv;J^S| z1z-mexW6G+?;xfWQt$9jX}~dvpW+S*)O=lU>2$mals<6=?V}%f=-HxY!2Qi27IYbd zS%ql}s7_FH!PPHk1g{FYCW*xk$Iv-?TUoDVsBnpUTkwUOUWdu;r zr3dF(w^n@a9}F3Idj7`HlnFtxGO?nQY}8GBGFj5U&!4$)dc~UCTC2Ad9#@E3Hs}^^ zEc0T^5~l%i=gAL>Ym-uLjS5<+IK1Q0vb_(Im6zuy9UFCaRaoqqw_0NZhuo8%7t?Zb z&O-%>OT*_MlsjN1)*iNNLsRbgEgL(G+zNJl=LpO1K6~NdgqGSB7Y);T1f$~1hkCvr zbGIomdcy{(N1D$z{EE3XRCUs6^Fw97zJ#nxaj`gg+RT!z{BY-5_iSnD%8`YoAxrnv z9t)H%9IH?oFi;_FX?GDYM+Vy&GG`-B|HB zLM_eQE=#R=#JNrR`G-b?T3qB$FMMgW!CCpE^kZnbfUK(yEo>p zXSWb5Z0-^6d7Cz=86W&Co3xcOq_2EZzEdFnvVHBo@aL0j&ZlHlU2gw!GQNCg)#`nA zjfpw2yq15Y?jFC~y*omf6Md~fBCsQIOX=9AJsn~3E6j#9?ep&VeAPWkMwU~2F|&vj0IrWJ8NXT>bPAMisg^R;~0<#^3+H;wy`HZM=U z{IN7qbkB-YrDsYirGeyyYX&ZqI2DMx)}LjSDx^pTzEap*7z7mJ{hvLpHP9h&kt2|+ z%J_Jd;A6!q2=PTh19E79VL>@z{6+~^gF77>h@dq(e~vN602u@fH;FP-0A(Ljs2Ayr z_C(>R!(qG@%qbXG7>pbqC0f!Uxcdo$PMh|mub{UuAh0y$Lg)opE*el8r!0&z0gh6h zBvj6S$M97xk*Epasw8(L6k_2?TwY59qdGB~ZG<6ABb^EE;`d~;>2Z;*!O#&H0p(6~ zTAITUmw;tTi#~cyoRLV@o~jIky~nG9fq@H`HdhyHypdcPV4(HL zp$h6uG=P>mE<{zO@D;^wsMsJ1aa*NE8VRwG)V15193`3Eyv&)Cg$v3`Kb7_`8VCyi z)nzvSxpTqlzNZh5Xk4?)U$R(q+IC?f*Q+5c%b#T{PAHx_R^UH>6Fc+P3@>TbO`$3( zo1(t&wc^av|7yE@_p1fd-yNCzCE>GAu5elAt%766IyJ|6z0@_oWj#BG&n>zBNcixq z?S9V5$F3wS`DFJgBc~;Hw$s*T=RGdQ!*$$o5TSyY^-AS7auQ&-K?Uv;l9HaNb(k3BTk!tcTmyVtX8_kZfO7;P}^$%b18 z&&&4xlC8BGyJN!8xOp6JlW4DLiQk)V_%2h7JN{zNh$rDnu~}y2nm^ow*D6SLFH4p3 zunb`L+5A+P)U)PNN0``Yi|ZQ`MFZ#Zdn5GUmRVau2uP|8z-P!ut_D$t;w#Vze@KP^ zVZvS{{t{@M+-F3Vs-OYsA+}Q{!3=hFy#{bH9E8sp^st65Mu{-$buvZzXr3~LR8S}t z0t2rqWDVr2e|CC6=6S%YfsB-v95M_%QiDn?(If^Tr-TurSo%Llz%_v*kV0~H=-}c= zvZL4-#lqMccnOkW)azuP5oO)D+Kg48LDP)FA?(+MYgnF3(KDLni6cZYnoS5UaIjLA zW#KUal1>@!WU@+(7>^+pXk(7k^hY;OoPwU)yfITrQY3ZjdA0hP^ z*67sOy?0D{b)qZ$TeLg*@$h+*Wkw^X(qqm+z_Kwrr}oVzBVD(e26?)>0<+<4oVJyd9qG ze>`$g%j*f9$HpX>yy!LI=epgn-SWNcO{~!TwAQY*(u(V{F1$Fnaj#t6Z@mehcP~1< z%Rf21-sYWow(_;=U#|wooxHg9!N-b(q&BrFD^=X*=T}a0>$C72DzPo)Mu*BLo@|-T z_-!fiFMCuU@Wr0>jM{MQ%X|L)0B&^KlKNX#j$*cThBKC$%+d){YFX(%LOH&5(WKk{ zeihL=cZ=@qjZLLUEeyvva9kJ>j||%YqDQMzD0rrdm;oiFW7v?R4+K-Nd_ee3OKDo* zLg}*+f2c>)fdWzpFheByaP}dz#ML7zQzjY!%a_VMQ2|SGUh8T=p^}b;>7|qy4Q))d zRFH8(Os0$*P`Szw5(vXd2WwzMO{|;38j?Ed%&F<{b_}mJOo?uwqf=1S#2qk<1_?_opUs zp=1p+=`xu36m5+$>-^u-kU)it!m%kP&N9R>5h}=P?uf9O%F56TZb#6f>)L{@c{#Vg zD*hVZR#DgRr0-|P5Up`G`*-i-?69zAi3)GhT6W`Y@3`hzxx?dZ3`fsBZ4zfa_ifSU zeMOgTe<^Gz51T#x#K=B1{)%HiR>d{ReLN?-WU_7N#21@y`#H1h)Yl$c+EejmocFc$ z=PY7GbMmv7U#oooa*p(Y((3jVUp^-rE1czS2|F-6q4Ma=nX!|rEZ!X2f4Bd%*^y9f z@sB!*ZriqsuBh2#Zh!QUTVP;Vz{_(3=efDH#>t=C?b|EN_l3=QJGC-q`RS=Se9pLt z^)cS#dOLcSX>DTJA(o>BeL*P*S~_ULzuT}oki*S3Hbd8;2L|Nd|r%z`BRfG$@c<~}@;lhP1iUJXs zm;UIJ3Q18pxaZHGvx6?KFs$wZ{85rpCF?I(E@CnIZQz%V9%(w3}rY_D2oug zxDpGPDyG;RJr%)?2xf+>&>OJ`HAGKI=K~PT0MF?y2`NP9V&rM@&}di=s?gF1?Tk83 zdsv2OKXOuuh({!%rQ&256(G=vfW1i#ibO(z#w-?nru@&dtly7!7Wzo-X&M~- zrFT*+$o)=fU-gkCW(|R#N&^=@c99#N?B=QFpe=QE0!vE~wJ{S7RNvWMEp`cLkQh)n z+bmUlyR)a{dYE2F@v*^WbKcnW2Np#f%uqd<#jJ}tph@{k~IuIsCVsr!ka%ZAX_Hhn zwn=Cji!8yA;o-HZP7g)jWh5AOb!~J6p)tsZ3SsX0fX<+8@LaKZI?#p?bkecJGejOh zGFCw)URdz=yEpc;HDM$O#L)C;XNm&y{_H`YQCtwU9@!dbZ6j;O5a(kFt2!JO3RF9v zh-IbG?@*-pAro@ZW`apXs!K5(WfYdg?T9`BvVkhyxXu3@!?;w&b#&=CI+q89O`#V( zjeoas1!ORB02}J+f>9y76$6je%JT5=UPPA7Nb4r zR`f?fbqc=IC5gELDJ;x3Q1J{W3_S+@hN9mPJxsiXC&k2NfMKA3<|ZeSKI~Q^ z$ApGp*nIGvq3e*58TALbE(1m#whY8tKMq2rKtlx{8ZGRk5K?gnyhdRd8z0VI*X@)+Q1M&5#mY_X83Lx-Ts%~gW86I`lQcEWT;Ou!QHK(QfHK3VBMJFWe^ic?u0U`C>TLR?>@aFU>R`Is>eZ`J_Ky$) zloCf`Pv8XN*rD7E0?(qW2ayQI6d9xTrnaS_Kz%@24Fs%(crYv?G%wMEQ9cpT9X?Ms;qkJI}6{2;akxBU% zGy&1HM~M{lk|eRXmC#4vGC+dz--!K-sit%Zx-Gax{!6{1*QduOTs5vU2(dsJJq|6( zNNgxgllip9!VG<{%~fY>Sz=)>GlISe@QoRz>gosr$&7$DJchAKG~CP{Oha%Ua!|rB zf~z|oGR0OG6_-)t>Z*yu%)_FJU>AC}y!j0EJxVR<>85c``#^Iv%XJ2a# zE{__RWXcfR$=_`_0pZwWyp@#ALfb@1W2h_6k|#A+&BC}6`m5^520$ad1>Tz4gGMg> zQ8!R~P`gr>(M0_hKtkDL*v|sl3zN)3ZAzj^lQ^Y}Vbn9=YLZXFtq{2kbv<;wB9I`k zs2Fk~Evor#ugGAykmTJ^MR;*{2{!6^vW;OQG2muxY!{w{9@OPwfX=5=red&-7f(zi zy-hS1LXwQZMEZ)JlJpcIWe62RdYJSt2{a8H`X`~KUZ!(MhtW2jOA3RYoUTJpTvAd( z`imhSqJ3nxI5{~{(h}W*UX}!lUY;&RGD?@B@neD)?fNhlJ%ia*fj%KR@ZnGzI#wrx zAK^M9?j>y3*JH6mV#QX&r`l?~BvaTJlw52qH`rQTAI3nqfV&}$()EGbW-Ts8pZF0< zcCcm8p5(O$-~Ate={6P(PErS?r|AJ{M7X%P&@=u8!qYqa3x=oN)KcO|52cxmM1oq4 z-kQ=uX;vfo@b~v;T7%9-*5H5GC2B<)d(_v|ihpwpNU;zOimpj`DbiEa19ZLr%`GEU zK)pau3z+$Td1aI$Mw*O8SazXwA*OU7d;$WP=!6aC0VRQ?e#UkI^IAhav^3Qr-X-!9-2z9hRe)|VAhHYY3Aq$a*Bte8hm?;(uHZo=!Kt4ygU~CP2 z{G-UM@kI0xqO*GFRm3-K^(@5eY5hr`j_3nCDvz{zE@^XFCS{DWd8tVuoJC320B9^H+AMb!pxu)lRKTb1t|Bmv{3pL&FJ)yBm*>|{MDJ%*NTdYbQ!uD zW@i>@Z_+6ApjA~>l$%D{gL<46A2gX!%9^{oI}vd)lLp-gT7x*-Xm0yU>_qi1>Ee{a zO*R1u%uobi5V+xD-sBKKoQ4wO(-hri2)>!`lui_pA&OgI<@gYTfVjVEBa$pu`Q_HubBnMF}-tnaA1$i5*JL$yF@ zo~16LKA`udE~LIe2T)&7cadaJ8BppM8Y}eqFUF9`lR-6ahU$(wgqDHyb2^9eXKB2V zilVVa*}ilR^$785k;)Qtbf7Kh^kFH6-q35CNkfhS1(__P9pe(XUT8jj>@gSxS11|}n;MMO z#G8R8Mu%u38VMJAB9-l>)jqy+vET+9T{P-6;|Lcr!3CTOfh{6FFoubTIGR9)hCPP3 z0sdvMkB-Su;d^pkHZ)+gib_+z9L{QPjxd_a${dzbI5;>kD7xrj#nbRx%L1Otzxd$) z^h5raH4;}az3}xOCqjf zn|y^m_eD_DvvoaZr!UoUFu3WS^j4zc$YZI8X>xNuvc%@9=Zq{Us_1>`F=^-dDc&;< zYvm;^b#pq?$^)EwwBl^gF#zGU&dI(c0 zG@^j5OJX~mDZZrzJ3&cS+*XURIn=QDgQTP1;Gbf$HYpTAKPT=#v_2Ud23S&|(4G;z zR4^`3lh;UG0M}gvR%0sIiOqck)t9}fA*M%xpU3WalUWyXpg z&C#Q=N8uj@9o&?*g^-1d{t;P~9XSyB%f`l5Z`_#h{k(>qOB)X#STa~WIMC3!J^#$$ zg1*+!!LOfNOP_Ro4ecs*yXlt2jwreR+fJ;Whb z|3IeOl)=)cw)w$oCo(6@>$>`K%1J}VItOuGowmsnc&C&luO*2ZIQbt_J`>g^Y%V+M z#rf!4hF5=APJQxSBO!=?TV6Ue=KY;p_cIf`%VWP!zA2ox{MN>N{(Nqx!7J@6RqtGP zCq9=+Y1KSeHhc5VWaHG*q*{?v9{)HM#w{PX(D3!v!9x>;(soZY{Py_E&oRo@&(|cm zt_ZT#@!sM*$eWiNJI6AAG;zE5_a%k;*r>s`Dpi*{;wyB}(rUbEoGb;AJ7 z>%C{%OqX*y2J;$+Y+Lzdi|?4wB3aj1I&%kJ{)d^1uzd({~nYSCru54aahty49Ed)jKTs+0x2v&nQe-G7~l-}J^)RA z1`9xqXdbbL_%LB?FtIqeLMYuFLaI*=!iIW#-dz?Is$d);1O&xAKxe?Iqyjz)ML0`% z)$_YPSmIo`n@4*>=V;@e4DFBxoPU%Rg!zIg$T*r@7)F9BI3r-=fn)`TxCS_xxWM%U zO;{bMbK&VmjOY(O4V72_3>k7R(;SD)r9|M?;10(#WArN9!g9ey)lCrgr%%jXRI!nOso8JVN6ouxGgJGA<}yvMu|F@4 zNq2Nyo23^l8-Q%R8>7n+~7l@?icAS96<3{JpNp zj&i;su0Dkd8VfZpPn9liTI}~lvcFG7EaG@#Zn@r=_g1A1jZUW1R~c_t5;?atHZ7$s z%+Tx^FL+PFq2<)CSnEoLvd1Z6a!&PTx4;@-I)yZG+x#wc@nkTxm7rXA3IX%ne-8ehB!lZ>e zth^Q%jJ;YIUjHp(N^P6{lZe#=)loG<&+ zVTg3=>Is!%!%jOc?a|#lB7Sz7PWFttlG%|n9H(6psJA#Q37(Q?;d>yMHNE%syucy5 z^+Rqu_c}i@>`PlDcOk4^t$$+FMfV#CE4DxVCS3G>_`t-*1!)T>E3U68%(@>kWt+(z zp7^gJ^}0Fw7wpT!C)j7J?MZiXUMTfRGLC1W=J~?zm%VcDFa7gX9~9PB?XKuL!u#!# zBjq*elTCV}!?nvjdg18J~l&UAD%!h~FHM6+NAKP6L^*=A^{$JF$0XLFV(oia!6_ zPXk&?1Nweeeh6{VU8{ZE$<t~X(C2s8YIzlVrfPk2@VFj% zC);PydcOj_)8Qhs7y1}3i@hAQ#zR7La<^sL{346TUX#z5oZEgXtnaANxi8i_5yr_0 zS>rx2fDuGRU0eop5hgkrBYPSQAcdP=I3EO55ow!Sxwf*i}rx`tsW>$JVuN+eHCMS(3eNy>!Rlk{;>A9A& ziDw2rt;%nc*dZc(v{=)ymaTu)gN1bK=D@+1i%xmItyPGe7&#bYx+Ti#K}xgo#18FP z^-;EARU(@OdU@xRy~lgaT>WLT=_%!+*$>uSxY^%tf4R$&+q}5#v#(WjzVg=zV>Wu- z9MgRN_RJx|`$y!J2M+v>EBY|0?cy5CUbUDN&aydr&WD#or%#LN5UvWUu?^DG3yGS) zYHWdc&}p1;WQwe~!gy1K zX3hQXlDFeMstsp1NN%|xQED@5^VSc88@pE9mOL6NrV-#=eKGYlNn2;VO@rm8S+4Mkg>!7i?HZI zF%l!F2BM#XeG*4H7^hR$7=Rd*Pk=@WsnEjNo-$c*60@|>GJwN3T6O>VK-FEf?WGa<-FMTz)tu|TUU_t>R??|09;f1k_AMA$r+xmw z@tyc|2*-k)c<9kI+-vkrX2Rc>lE>#dcn)#~C5A{KT>x|MEMt9d%mdo z-(Cz%0Yw=xf+-IcIrkhs5pIxggi9)zQ3iULwBZ#Q(nPA(E?aNQeIKFYY`=YWXZ@TxLTv)fXz3rKv>BfgOP1z+X zFLaWNKWASb7dczLV`f^@Yj?f*H$@V>gr|BXsEIC^;{JJGn*Xy17nAcfb2}fodS(Xa zG){0+jC^@QEVyE5io@n3hpN+;Ki|64SuWE+mtVTzi!J|-`h~B7|I7_f2=sI9{I$z; zY0`0xTgu}lVrzbROdga9W)^p-0^*65R~29?)n=<;U551{GT}iR1c_i0o{=SlO+i^x3w4rJdalr)Cx*}I7xP+qNvZu|Fe=_gcQQ4pKqMq2;%$WRxr@5fx z&g4T4$SS2)Kdp%3>#8!<7LhqX<&#m)UKJ6`hS#6?6`<-^GF%SJrN zkUnd5A^yw4$#EaJ&a>YikX=}y8J6X{l7G`dC$->V(yGd=vlABftmAc@NR~1^w#6dl zNLJ(I#SIT$%{&(!U_IUAL4#A@lv4+5m$fg-y`p_(=uqdk39{v7)vu0w4eQ-J<(0tr zXtC+FlB11|1FzP6lTOr=%+ZocbyolE*!IcaxTc~dC?v^Ich0jRZ~JH8P8E?C8I@rz zdbag^$dFjAc>4`k)K7aUrIp;Y)3JH~L}!kdUBz76qh&T_`Hg$LrRq3&H(r=d+A?C^ z;b9LurL0TE4#qzdbcbbVI13i+t6G&CC7m^N=2^L8DYqZG)SvHe^i)}L|I)Cd9y7G| z-S14DvBcG{J@>8ehn(FDON|$w8T~ueQ0-BDNAb07534)MrhMw0KX&@_d27QO}J!`{mqhZX7)i4B>dUKdhG(cf zxE*-!=&nc|_x`j^JNpWin$Dz8`!Vd*1gGM?s)L~#T^*7626`z>dE~M4uBWg0zB`Z|qKnC54e(mN>7Ya7LVo_+G|yHeZUL<(N1`I z5Ns2@3a(gqlIpj^i_a9iA&)*V6awObVK^+IZ17@S#W<)+Hu0_CC{OGmA zdi4S>)i0z{sJJVWlSFR=DuO?2WdxW7Okjpy7o-kEip3!w9?>P&-S)<8V#!QRb#>aa z!ZBQX_t;LyZ6^l@`zOs9{ORVG=5}$jz(KOX{P)9;{huoRJA?RX&hy@uz9_sL)ECev zZ^O#+87ebm2W#}q?dHN)4-2tkSfRRSM&{;AJ5F7(#qj!Ki`!-8BgCtu_leY%D~(iB zH_G{O(z`v!>DZO48rKr)DrUJ|9Czk>*z4TkMXBK*CvV!@tt}=0*8Ik$WJUYTYCB_Z zHM#B4$sZ+uo~`zpoF?m=Ti?_>d1Jv^8-8`;L&@`nv$AFlysrPOr90OqKlsQ`sfXKa zub%o@)BbZ*=*`HHb0eK+6s=BNJB=Is-F?NX>ps?XLd#TE$4MuR+&hpL^=O}8(4URezjo6qoWP&niZ#|C9c}0_lUMsR;OE~ z?O2)X`FXQi#WExHV_}`K3!ixS9i4TnYuWfc`}EZx&iJ}D`n|Qn^;5CcYcncY#=Yrl zdnHYx21nRl`EmDEQoe-!^V%)HBXTpmLk|`lUBpK@<&9&z7S!hriD{7i_{M5rr`inL z+oC>O1K&lfZXJ32%-fOAUW)7%i+|xfYFp0GT_2Avvfg=m^=#)tO^-F6yNq+6jY)fy zYyDv4FzFqE&waA%kO?<)b#bedtn5gwvZ+&?^GA$4BO02(diH4B1HEgCdxEFv-|I_d zbsF7P(&?Nn;q7=oNnn=V|8?n`_g-Vi{ zF;hjV__42-o!Z4cupng4`uq0LvIgnfe$BBQe_*kKU_N)e>ydG3S0oOl@A($?RHNB- z{+q~MQ)kxfXZLlJ>ee-?wJKxUE*zhA@pFl)v*nGv{TFXmzVySyR7)`k2WirOZ>5S)* z*ZXCZisRnL@^iaPuDicg`{Z%=zNh1m1F=FI>LSi}7O+w~E$}d+O0LTm+UPg!Y7U>z zZRoHX=iK)w`L~Hfu*$^raVgxC6t9U>9$QS3u6xw0{dMb&)Q__hTg1torJxWHJgYRV zJx5vd`Q2R??#-9!5E0lL{@8GM%C|dQ&EDXYO#^pD1W8Jc^VIKm>P)FHYEw^r5TNfl zYLBd!-h=i1ff%~F9IaRu>|JFyRy92BNIIqz0DB`NXc3N9dNb)Ndl!^k=0M9P#&I-Z zWXpSuV2P_MAeo5b0Rld7+P82GIYm!c^ZFCTW%!Qx+SNYC1zJ%ausN_b^3toJz_nvp zO}L^G)Q=IfUyc{eR)pmP=CR6LSmQ)rjv8u(1qO^hqrP2f|C#ofx_0AnVABCfJGeFgg~Ll zGexEe)j|LQ!om;=EFd6YFeWBOh)`o7I0tPmLy8FG1;w)|98Wyw1aSjIh9P~Vm^u(Z z#B5FBZGx786`cx+5qyr2PK1skTn0tG0ik1o&X;9nWkmohBF`n-lqsZ_%#VjKmmPfh8@6268@X;9fpR6ENV4+X$ssExPz_$a1V>HHiQYHmZs;W z*CPN3!AuBENEkpusu0YCxPuApfqw>)f_0~&3Pe^N!U7Rw%m5XJ=aRA@$S6?ZC~9DC zN1aoZ#5inW$)T(i4NMKMIS!)|_y}8>%ouDDBLK3KlnJK+fU%q!Ig?~Yz^#ciIB=)3 z#4EEPpWq;Fs8>aKnsfoa>6(hy3-lACA=$>0a5Gy4j(+8F6kD{{#Z9rmHb#Px0>_}W z20B7dAdc}5=mA)=$;PX~$WW;~UQNq$E(H#Z3{bo-^o7N2c3q`ju7ePZ{;oynANhY; C<-|Y$ literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/Gameplay/StarBackground_Sprite.uasset b/demo/Blackholio/client-unreal/Content/Gameplay/StarBackground_Sprite.uasset new file mode 100644 index 0000000000000000000000000000000000000000..723c5d81ec570bada93d272d896b8cfdd2b163d7 GIT binary patch literal 14346 zcmeHOd0f-Sw*RpxNUmNRp{ z=X=hKXFl@W*R9cLmKYI)nIS>+peLkD{=O_tS)=BSTYC)S!~06#`@nmWU^+pJMrDu9 zI98(hiPOt=C;zvX>46$6l=sWZdu;dcvfox+v)udWmX|Z+eH7g@Lmss7PS+TlvUM(& zyVxjii_PREn+1};-ENw2P50sZ8Hhr)##`%Nv zH)e0%@xI#xXwMaWydy-*rT9;}T%`0CDnuC}BJl^w8EGq1C5wgW88U?wRU?RK^to?9 z5a=JV-jpC5(eusFnST5*-*CTKp?zzD$@#5I??2=FxxA zLTX@@!cfLdoicm$`-PGt76}6%vY?hwoFPkBc!#ECND~D~%cNoj@jPnUTnn8termET zRZ46L`Z&r`Cp%V@F3sSE5sSpCb-GQ(rYJLI;!J3f$b4)6W{_uaa&m+;Z8`Cufm>5v zhu;|)B4unw8frnIB%+Sql|joPq7S5!cxkFcnh_>ah=~70b?+Ka=L^$TW{9O>vgOj$ zOj%kg@elr=569^jCP>#PR-#@F+kaXFLQe`!OI2jZl2$6D32F01nVCdlZ`e0up(H9Z zLYgYg5GkY*qWyfl26rz{6Mqmamxn1+MayMk;%8C!cG!nW3#7>+g>04dZCR>BmYPhQ zxM*w!t%RgWlu@af3Q?+9N(8kWzKpg7&J;=&E7OTFr}LC#wKO?1S4-ogIcTS$YHRp% z>GDkCpGotto0A!_X_+zw%ml?k7Q-0 zB5irBY*m^fHcghQAndQ+$pArzNtcRN$`y?0jz-yk3or0PLl?iDmLZqWE3ox#>z`rh zd}a`#DWcR=sho&O;bOhfmeA8x8+C6>gD3pO$W%lG*%`hzZ!L+og z-8354NJDDcX;d>7nkAK)sT2J3SC3v}vhkp|ZM7?%jIv@8po)^EF==9vT(%bN^t4oH zJ~JJyH({+%W`m4kWoxAJ%vfngVrqs|Bu`A0DTtlM%U3~Hq{=eeirW@g23^HVGZliR zOEaYk!vB731xzYFO@Z4dLbk8LK#McdGKd`6H#cB2W07VOZ%y$od$vESHJ@$jOO~!0fFe7DdMrk0UI(qW2IQT0epxr`eDv*p_Y*d%I%CLAS3c8 z0@g$y`4~U}GQ*Gf?@G&*9r5!t`Cg68QEG(U^79 z=ao%p{C<<0VCzHW0F#QBeKT>Mq$V&Cr+H~?(KmsuRmAXBp(l7RtgJ9 zNgd5bJrLB0GH~*zzfe!Go71T8uVlF41xS4ILG&X-2oi?mNj~u3!S(u3y$Gs&xO(9_ z@6umiRu7~N>1FjOrlnVpem9D2f*3&KqlKf}9?I5GRWQMTxx(CFkqoGYfe4J0XgNT+ zZd-%vw^9RA>+}b8BB_d?)>~kopcKm?pTqry{^&km)ZZqmt>N0h4WO5&IiWG%(ELVN zMu$g*69%YV11Qt{OoS4znVOoJn!IK<#%!$lYh$hKtgS38tek8ovFz9`Q$0Og+})>f zgWjCx6X@gaJ}YK+U?@K_Dst*fLE?h&grJB>J~U!rK6b2?g_WbVwIhGJ`*i+aPMT`M z$`r`*!&E13T z={IBMo3m!m2@T_iM?^+N3lkFGdVArbcf=Cu(&QA`vP{LwRjb!1*M7A5<1L?Lf0~nD zu(NR2=S9W4OUn)(Dz7+PdF1$4Crr>x+`~BH-HRo&V>MvYty8gqDKiz1)`TL!_ zE%)v}c-Z==t-YhO>(B1zJ-sk5m?xdcnGKqk6`Gf!v9Xb{3CzpDa5efESs9OVJQ3?;315j;;}je;!&WuD#4!GB9e6!}IREfyVyIu4DJj z+nqGTz9?2d%7cR z@grx{yvlPW3?Q2v7Bherk4jmA#jPlHYx(HSKIfL?7G2FR+go<6q=l~`0t(bUtWXVM z-=-nH!ih9;8%O7N`yv3^esuZd`AZJ$wT=g?QylTdi zUVUEo*6xTE3p&I|a-a6(RCm@kxuF@k_f_=7-LKUUfkJg}`9+uYF&iQe76#V%%s}(a zTZ2b@@UUnX$9PHIH)j5M(X015^mhs_p%FyId_E9U@?^JQ4zK57NlcFXt8meF<<@S& ziq;_8y2!6{Hx`^&ArPLDB&?X{P~h-%;*)KiPiI2iqQ6OU*Sl=9jeRdcF4>)F&&AEt#NIbPdR zXPg}++_-zmvNOz9i;4~POENvPBZIZtey4_*^sCl?4QasPuHv`$~ zFiP``2JE55N)JPx6S)=3FPewgnto<*Q+d;KnWXBG1Zn2g(J`^-L`d=WmhCOw+fq}~ zh4p)3)%9BS!-^kG$|G9NqW#u{bdlL_j{NyCdj`VL7Fe=)i%qUo!6fShNg;@e+ViCt zG|o_fW}e!Sh$^NtzLu^}bDkN25v%WAH= zj*vs0P+i5c^0O+iMuT{4*Cv?oEU!m`6tC2RM6M&zH#$sdM8wc@4ykRw6W~@UiCf0u ztX}{*b5`$|*xxSbYl*K>m-4##v)5+f0q`k}tEV-NkwD44nAe`2wc5l@3W?sfDlb|^ zH4tag|6#5AH+7=>JJ7p6@R`T}cPO0?vgHI@ZQ+VTR&84~sDHE_U;+MIlUc$>SUP`~ z&1SCEBtzC+Gqila;^k{Hie{#{#a3(?ZoYMU*{RNoo<>ze7{aNK<;su#)#PSUS77pd zzBk@ZeGi4MyS&|EB1;tAEQ1n8*1NZ{#qHvOI|Gfk)(TJG&=3zw*C!nfjK| zo%O2g_3G9#tgi!|?4e3iTKLsA_6`~=?(D^|G_GU(#@3uKzYZwqI*2!0$-Rg@7=nb- zUB2cUBERuW_th#+rKgQFH`sL=TS#s$+5>w)Yx^D| zsPwnHU(B|T`9edCDZ5PJRsF;Qx5;x`pLiI}_3q*z+s5uGF_BcA2#E`GE3Qv_bsRpuNUXE#5W z!}YpwK_Rbj;7jo(Tb=hQ8v{H3bFA}3J;^kz6A;rlP3`Pm-d$GMOwTvn_hNRm8D#TK6+1XT7^m$+7~s@SUZoz zS~HBbziLSqo;$*-WKSKecA!{N{yzi0A1pn6w3hIws@TS{`)V9Kp0L$&6`0ZksQ+FB z43wM$C><{LI@ZHAaB8VE(y?-$(VJ|`EYAa6RGo}^fDkTloVpirtk-APvYJ(Zta=*j z<|6p^!yKaFlYto3lbkF*X<*kse8RqsJLe4F_a3ifMG`~4OXXdsqDk8+8Y4J-K+7vg z8;7-J&K}ylQsBj1ByN|S17;Cq+on2CZ=t5K;X+{L?Wu)q|k6#2r@?K%U zQtgc66MQb(Z1R!3_wnR`%G>KA4(6zS3!yrO)w-r^ygQI&aULyIk;8-c9`kB~jA=gX zau~rmO*yc&C^>aIWaVJ>G>4+|o_X=1R;8*taQCFwdg5 z2wPUPRf`=8NMk3IZgDi<9KPY>fXCx!ka{9}0U4&qVae_r9ix@>xPT7VgFmph^iYFf zE#+0{k&6=AjbvTxNeK=mMHI1yknOq*tgocLp) z0)9#Z)vzv6Z*}K+C5IyOT$0S23a>QA-`|5_l6RqK;_q9WjS-tKv>OhlNQa2wHWI{5 zD2J#**CArL`MIamPhHR#5Lgfpq-_Z<6 ztLrAGCt>dq(x`fnJ^)CX_5r}xK^{zb-b|93m(TNBA5*krJ!MqE60xH{`my-0fmFTl z_OKdTdx(d*%Y%3Am-JFdam^O|sV~7N0ZveWoGk4AkSK*#mo(|;`pRBr(m%Qs61}gp zI`TmFS>*WnheyRN(0aMLCcQ@A?Q=wKA4S6%5-LU{G+LL00*}7saG_*hB=TO8DQ-&Z z`(vc<(`J(F$jD7gDRg{aC)%m`k5Q9zyCa&iuJ><=z5n^Zq6O{cqn?w#tAB*cheDe1 zW>bDmCEBdcTB^c?4_?)Khw#BVDt@8sI#b>|MZ#hJLrC~Tb)qYxq3bCKWZ<%$m2jP; zru)r{DCtM|^Zoy5Q#H&07vC$Q8|WFY(0|6p*4!b3w?tT1UOssCq~~6@^#m4_jxUWC zAUo7vnZxUA5lV7+ZlU+4_zOf&g|t7r0sM9xXdpdE!5|wqndXB*`O}&^-VU5|Z64Ox zD9;sjTE|)V45(-*=D~NbmC*JK*>wvFmF%(m`1C-`u9QoN6$~`p#=a{VF*q{oU~Z@b z@@SFA&c(X*!^kvvqU(a$Iz6ikS={rE;IoG-f!1MM!EW;k)b)Xmh6^dA->s)KZLy=* zk(6eri!*SQ|M&ePLB?7CAl=_nl?0VBJ`M-m&d4Nd(%(ifW2lt*3SO2o)0 zGJp}?rWBlP@&}z|IXQfj*Nn_jQfW@gIZhr2NR3*K{FisCaKIvh1iJ4ei@>ERLcx-y z6Ak%Snl|o0?nn4?eeb6(ZXcib0)PfCw!RIJ%E0kO=H*B2 zkvI)>hH}Umo%!u!#1JUNaI#5i7hgi*vVJFRyzZE)5=RT0TX!P^+cTVjZCZ-KyP`*E z*iUmDy_lcY`b52JFLD@yS~_FULoLP@Gm3;!i^--y2kY(O`#4&L24NJU7xr^>Xvc9& zqX})QQ@q0AM*uu&C~gji+trlah;C_SU+K)YY1IScy{#amOQSp5=D76t9bP@e2dLkO)@up&Ym zLExbXLKTi6SlY)V+dX!;@P+Rcm6P(+CT+EU5Rp#$n2-w$BS$)}O2C`~2rHu>q8XWg4Lb&~s5P}|TzdYR9p-Z(~ zw86Kb{P>z7k8x#LA+|Hm7n`m1xSO+&rfyn>YT}B*#gw*tFsxC}o2*C9u<56Iu|E{_ zAD$y)1W_ma>)+ev7H(|)tljZJH#TPyI_htd(pmXQw48A@wQ<1A6KnsH@t;3945)W@ zAcT{LA7BWK7E@z*vGv71#R!7X34*@A6M4FbYa;@%pQK+Avnm*$#)}Roulm5K$(He6 zC{F|U*SwOn?~A>CBG;}P3M2@GNV0@EKovRwIEc1VUL76NXATgqG-?kJ262FwZYD5= z2XoIo-;sIZx6%JW!F$Js=DX+TqPU}5;4+#Bn-a({V9}0`OmeLDN1xS~-%?B0H?rA1a7GlK=9`{2hfmhU4!%s&^0C z1x3zoG9Bq~9TMqK75aFeWl_f`x*<7d>iFb^j*IsE0LN23XICgKQS=yDYUDmFeuR8P z2r4dFD`C8k-8QF-IVgtTK8iU*k?Ab-c*LQ}b1xKJf0ZycI(+KX2J!LUXI;`c@f(>! zAIv=ir%#b~eL|yn%dw}G5AGq1sy@7A7sq_bg>THbW9$pvDSMAXEpw^xv9eJ&I5%)7 z3mrhg$s09Sy}%$osL}}xHj*B{x|cfpa})r~gj=#KvES0c?td?}>^g8|G52k}wB z9&7F=Pn@x&(_3pIz~{do#su;c;SdD*Fvn`R;%u5hanWI9RrgiIpdjWEb(5wb6XDeK zj7e2AbCmMg?dxq`@2T5(97IK3Uwo86?{@0g^;2~08oe}EfsRZZQMApKimZLNPV6wJ zvY{4QE&whu8svu zB={PqNUnFsEE5$G1l*trR-ju}5$FnYI=Tgn?()I4z2Ppf!8Klwg@Ifjzgg3LaPnHD z`-@Esz1{}bs9t#Wce?(CEdp+ok>gB!i?AVk^wD-le`VvV;5<|gLShn(A5sAMz=du) z-{}iZkaZW{gzJ-#=!-v)r~9%yE+u=>eR&^Lgb!R;g{1pZAen{Bp$?s3I&ej5*hF7n z(rt)K)ucP4UwmOh!Bj0*x;A>E>pVE|L#Qvq6ix{a9kH-l6Xr8wAu51v1PQi{Uh<%R zv2}8>2kaoBP5Uky2FGMeAitK+~O3njJeQ8_h3im>k1Y&Dx|>; zB|#>-Nsp6m&#?C)4SoUl47XMx!LmRaGFec6Nc50EEg|Xl;7DfS9_ZzQEZoB+s*r8~ ze!73=@L^W{^zeV&33#6s7y1NOnMGC9o$OpH55^83I)TV>>jbesh|Js{G?Oa*UpGMC z`=3EI1P^9DRCjvX@|T7O@BnEl`M`g1>%Hh|9tE5De`?U>RM#7L^nJ`BZ-PkPpsN4H zS#J*_-`gGpS9l@mHZYsa!ZN3CTS8U?^yo_!js!e&sB);Q`*IFsz{iiO;6yz^ADpT0 zgI}We85H9saG@#&QO}V^G1A(EK|_>i(bw(OxSi*DB?=S zPKWN-=OsaKA0N_RY(TqmH0}kk*_A&sO+z*Hd7+DrZ-h;!(RDg6>sq##`IJdb=5o;sHawNxE}LoNO?6=1Yi1} z((ec1d`2;;_J@{}^+xW$8n;9LLGROw09j|y|LXhv&qEug`=4tBShAz*QO{MkXT=#` MsABt2zoGvB0L00;MF0Q* literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/Gameplay/WBP_Leaderboard.uasset b/demo/Blackholio/client-unreal/Content/Gameplay/WBP_Leaderboard.uasset new file mode 100644 index 0000000000000000000000000000000000000000..e4339e5cee5003177183a7c12842034d6a0d6311 GIT binary patch literal 29170 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 literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/Gameplay/WBP_LeaderboardRow.uasset b/demo/Blackholio/client-unreal/Content/Gameplay/WBP_LeaderboardRow.uasset new file mode 100644 index 0000000000000000000000000000000000000000..d5e76729a5d47f9ed2554c03c6bb1e6905b3e8d8 GIT binary patch literal 32477 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 literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/Gameplay/WBP_Respawn.uasset b/demo/Blackholio/client-unreal/Content/Gameplay/WBP_Respawn.uasset new file mode 100644 index 0000000000000000000000000000000000000000..dc369617213eeb1e4fb09e951c5612aa90affea0 GIT binary patch literal 29927 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 W9UauHR{i0u=}~)rJ2F|of⁡FbU=W literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/Gameplay/WBP_UsernameChooser.uasset b/demo/Blackholio/client-unreal/Content/Gameplay/WBP_UsernameChooser.uasset new file mode 100644 index 0000000000000000000000000000000000000000..b3e68ddb50a29de97636aeadb1e85bf7bf7088f1 GIT binary patch literal 40116 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 literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/Input/IA_InputLock.uasset b/demo/Blackholio/client-unreal/Content/Input/IA_InputLock.uasset new file mode 100644 index 0000000000000000000000000000000000000000..e0295001c8bca73c40b83e36ff7327f46b22f073 GIT binary patch literal 1151 zcmX@utTpfZ|Ns9Jm>C$jm>3v-0%;KV9GCltwIa3IG1V#FM)gs6P#)(iI~E28Mxfl= zj15m-I!*b)eamo><8C)S4WO7L5bL`q=BDa<<`tBd=zBWGgBd>g$=N_D1_n+bE(7TX z0xzINia^@U!r8#Y$<@Tjz{1?kz{1(W(8$=(!obzh+{M_@z`_l#7^Lq!P!kX_)Po2h zu!p)7M1u^3@tuKu5Do_7``%yMmMLaBuCGuzw4!tOACNg93~~%B0~?V2C37j05Rj*> z9qMYT9h91rnpm8wYpQ1kmSK7+Q%rnN^7;nfZBsz`!gnNK8&;5Gtwcz#G1b zTW^_*08P;kPAydPs&P7E@AlLscR>OBa2N*`Ex&wWr2XzQY1BL=6pdc(hQQ}MwIU+y_3uKocFv*mq zLSoStNheZD&;!e=6EstufeUCrSYl3TYDi^4Dg!9Uo$~X686yvzj$u&&6h;LHfknym fZ%Z5WtdCp-xl0CBnxX8TMIg(m{h9_)agcfdf@kUc literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/Input/IA_Split.uasset b/demo/Blackholio/client-unreal/Content/Input/IA_Split.uasset new file mode 100644 index 0000000000000000000000000000000000000000..4f60d6c6867b533fea0421976af34cb8c73b8f6e GIT binary patch literal 1131 zcmX@utTpfZ|Ns9Jm>C$jm>3v-0%;K7kkdD_?hTg-G}gPQ*EPq&k7-S^77GIdBT(*b z#)c;^ou+)@zGb+`akrbE22e~Ci1pnQb5r#_^9o8!^gSKpg9~ypOMt=*44gn*1kwov zc0fxMfwZNgk)?~VvzxJ#k+G4viJ7H|fs32Dp{t3Jftit!xg%ULNZ(<+~5IIT97^cVW{I&!Z(aBN2i$Q%#`Ifj*i4aokIxs*u=$kWyi zbv4xvO3g`4EKb!m)iVRjGcde&n1n+fs0j?buXa5HF>*lm0z(HG00Z?uD8xWcwnma; z1@is!^HLeUFdyZW1#$usld}`kQ+@K26LT`F5=%1k^ZbATSzM5qoXQ|nQrUqsY?t~T zza#=QK|eUTD6^nM-#Nc1HPk;TD>b=<;fJTLofwjwYhFfTUUF&*IC>bat~>pP4JhlJ zlUQ8LP*Pqo5fm)&NZXZsb2AS}02G&w$si{&2uURHZ~(=i&SfZe2RjTLrl4>I0YxAt z7_q>x0s~M$fWSXsK(RxZC?rfB$WjKcAzT1t26}*@gs3&pH&ReXfE1%hDggyxaflL6ddLv~N=P6({D4WMEEN)S zwn#dWVqXs|t4`2Nbp|e=0bz+brKus61*r_6pmxg72WEskaJq#>0ZC$jm>3v-0%;JK!F(c8^x*P@_I(UOFKzgHq_!{=ny@f1FaqV? zW^8!!(rL;U?pub79Cy3vX#mBqW%$B;lvftW2}n%NPE1er$xlwq$*f8&$;{950|sSrL1J<$gHTCj2cFP9 zFW_z|0yINEIJqdZphVv}zbG}-KPf9UxrE_|r>>nClALQ^Mq*xaY6>`t7_P26{e}%F z>ztEVT+C2XUNMmy$U};_)iW-*@_>XuG3uBMau$P-L;?>7Pz>s1hGO>)Q6Lu_uAs05 z0YxAt7`edk0s~NBfWSXsV6j7(C?rfB$WjPv_=Jj<@4PYcO z{Gl-#B8C7a#AyhLnL#C)f9Qh5L`_5%Lj)laj1i+*A`<}__55z%t#9swCQRcmdGFnO z?(d#+?z!il+YWAByyuThCbMQHQPB*di~I#PZ4H|9%SX??GPL=blHdE^ZY^={@4SKN zIv)Eq(*MKR#_c1;$E)|cKk_*qX4!50IGeN{;q=E6Dbwk1_H;FCdW^+H5AkDQCgV~8 zQ5k=iEUWfZyWFlSSFPLUc6mL%niZ}UjkPtkKDW>7_Tg;+iTy$*J&KJKw+8)~Z4(vl0oS8+lc_kxy-;7f3RaC6@F0FV#2qyCc#MXNy~!KVH4H7?X2cW1Fw^AkKcmA= zW7-rs;g2nqpWMkpWhtv+rARkxiG&^t)9ZVdyD-ZeOZDVtwIAEvumHlgsFoH6xZ2vN z;AsJ2nmnyhM6_N#ZX_w3TDZGDFKS(#KNb;&Zdy8$J|o^^t;jgodN($QEztXqb#CW) zqTRv?MVkYO0&KqJagt6Brq&ff5D)3=biq!PeHZD*BEZir;)UM|pG3a2#AAX+W*(k< z6L(tIt`|Ww$8n!H@bkSaZqtI@T3D=#2eqjFtcEBpkjo#csX$t0N@Hol6D@Z0@rUBBfgtb~PGUj~BT z9w@d1mi_&29Pvtyvbo^PlUL?|gK}bJN$I>bbKr-RS?S=3$4s%&r1Q0ppTPp;m=!bY zNQbECrPfiTl(o&2Y}y8{tY+9FAg`iyPnJu_833gd2L-^1RG5hOpfzacDgJz`af$Wa zmj~}6^X|`scY%2iOu$qAEwsk)L7@Wd{!PR~4gy!>;m812<7HW6dF*yZ$??VJ0LYqi z))j@~IvG&x@c{qSm6c}S!bv3}nUR literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/MFI_WavyOutline_Inst.uasset b/demo/Blackholio/client-unreal/Content/MFI_WavyOutline_Inst.uasset new file mode 100644 index 0000000000000000000000000000000000000000..e14858f6ad87eca9ae36da516a90235c931c702d GIT binary patch literal 6283 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 literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/MF_WavyOutline.uasset b/demo/Blackholio/client-unreal/Content/MF_WavyOutline.uasset new file mode 100644 index 0000000000000000000000000000000000000000..288c4e7c7540996e1d63eb9d184f472dfe054310 GIT binary patch literal 26798 zcmeHQ30RC<+&?vKQc{XUS}bj%RSRiarp?r}qHIkyF>RBXiOP+!lQqR9$|$Y4M7fr0 z36Z5MB-=%nqAZE*()a#n=4GUruLjTa-S4^QdFDOa|NNKV`Jd%I?}_q-#@TP%+uK74 z2q{V<^d5^LylHGzHIO1!IEImG4EyE|oaZp&M&<~FWU;!fE8eMn@=92ybmO;tqa*gV z=2+R7@~Ji-Js!%vScJ;v&NLn-gO$y{nnK^0wDbR~IW=3oBc5GaHhPg%fF%1s1v4+M3&0nVFlBta;Qxtc|4> zC?nJxi=}(=ijjrZ*BEHa^%#=;0;$VHzMGc!4sqNDfJ% zR(hVa8kRFL2_jjW_|sx&R2I$Ddc-JRJwnaIbt3AqM!ay+aP%!m+`!}|&)}EgPpp&L zp$B}hg1JI<>R5hcKsb{g&mQT*VA6spVNtYjHhSl2I#xxfjubZ?@LNJ$?1}X=aze*r zUF&*5ipiuV`7#;tG$uO2W2DGCYPF8_!5!$0x8+#Ic>T z`(R}*jMy+t!wAnftTZqwp2k9VD&5Y4eRvh-1YhbL#+VJ4=helr*v=>;E@A92EbB^( zr^Q9k;@EBpap7z_BaW09&!n+fSjvSO7eSApvN6Ur#w^nw7Nr}Y9e>KPVe04|&(ui3PKaB2*diT2t% zUhn%|<#-HC2Gatm&Yo}{I^dh8EYxQy%MLKci}`|BXv9|LM{sO+#MIq*3`}xQphqAV z-w!ij&ckqHAN&|82T5>1^jqq(Mc7op&-q|85ehkH!}(GI4vHS6r5pxSJ>#&^AQ(V- zS4MdOs!?<{hBS)k5ygd36hjS9h@&#;*bvK0KVk~oNL1hgn z;Y0BZAh`wkxCDAqe5ScGsS$K+AUlzRP*U(C8-UD*flXnpgbHQoc>YB9g}2kKeN75j zF(aH!orR6@1f$JL&=HKtNEVHa>ddp2@UjW)SZX5Dd^oZd&(h;ii`I=tkQKP)NY02z z=rw+p*ZZ5+S%(69+@A=y2L25l;7bjUrcS4k8JN$~lc_Myfu0pl4W}VBcG5B!Fun{H zoi_tqvn(`$g7~im$#1|d{SkewwI-VM z_*Zu`Lt8$9SFqSgF*Kx4beaP=vB5hzZYrCgMG)qmmKI-dx-;C?3sitrH4d8@QN)!i z&+vjkI_Bt`L(Z^anHkAnGe+Wl$`TYz>)6>WGmDWyYWdwS- zquvtc7m-5cryte_cwAzrn5FF5iD@vQ;QD_ZG)@r_C}>6!L8tTDcvw{U6Azj*_w1Pg zoszjQu%(C^$41NkRQ*f0QxY6X{u~pa@81tLFW>8f(+T|X=Bjy94LT*no8k>zs4+1p z*4(>S=PG(*u0@ZJNkVG~y{%y8#@l~|f`=^{5_mAWa~1&!VQeN9+hL5_cZ4pC7p_8w zHS~VNPTr=W!$yu7c!9y2z`ohvb!eOFzquD|slM-dXSSg<3=hHZLoD}Y;9S@?4E8Sk z0MK@6UkK#>{5I$Xw$BO>r0oQ;a3o9@*hRXaF4B$fBHe^8(oO6lU2qraLb^ydsf%>h zU8I}b2_1Aa1f0i9tl#|jMM${cI?}aZbkjPa6BMCPJb}e92|^I&$1g$O(D@1q|2H~Z z7hyU~opzij_Nsxv_kfPK)(9HFzF#=te4tD?EzC=p9@66kgu*nqJXnB)T*ndOuK_L(;EJHXgsl1N;#~$@-?d#ZoCvASMJlH}B_n(^Z zfW_*BF%(XB1<$q){sLVL63++T2c1!I1meV;n?AFSDc5%gcM04%!0kIM@Z@ajw+jwdef#Moae z562UiH-Px_!kA#o+c1U2M(wQIQPeD^&RyZwg zJbV=J>P#NIk$}J4g5v}%oblkt_)}Pha9W(aYXV-K$%EHQVRaMZNekpr1T-1=2JZ*t;ro})ry zK+q4!6K5Yj{&4#~P=8z=Zr{5O{Rew|9M5F}JUzpIT;3u9FZkMkbA$a7yemKz_{#_E z2jNl#KgOSero60hTAaVU5j5^h9&R5l?>r5c#E*q#2&cOu?~Rasz-@|vH5@116r?L6 z1&K}~kcZ0@C$Cn}_O*~az!M*8aH4ZdK(lqaAkj&L%YrI#@~#Qmg73pCIUM7GJjnOO zeo%(zJJ!QNgK#~_6wX6`fR78e2flb7<}WyB#q%(=!r3C^ftMsP$b@+q=h=!G52t02 z2VGuadB6~+RpB!5Q|L=510H<@Y0wwWowWpcsDp18IDe=cAe0B2g!yO)>Y!aeK^lvA zZQy_v>;$_(AD{)yK^ATY$OoSNu^;dS9v}~}2YaBuz&_9eum`vsy7Y|o8-CxoDKjT`dtQD)VNvnkeWhjl|2%N;(20|$Do&rNJbUi)m71%y*RJ2V zdGG#%hxHAQ9yd0%ynNOA`pw&S?>~TDU?&~~J@u_!s+e8UGBN}id9X`LdNvrYDkH0B zE~nsh6ho^rKen_-w8Fq9OmbDW&bru?IxW((xrP zY6~6fYd-fn_JBNgP)$vrH}0RzW)Lj4PdjAnxpTy>)6@UlVBSmP!pZ9cqdOr(E%?S@qW29V7B1_VM#$FhXqp+=a&ZIo`2ij4iDr zC}o#QX={uqZ}colF+`NZK84qx*$(uNV}zh}nQg|mS7#;6oyR^vjGuBq`p^pHzO@(R zOd8HPt~x{E=FPlb|CBxF@aM2KQS(yX>9i%^Eq#$;OqfExJZNm7rp}|6yXuQFW_sNV zTfaYz7W+BB*39YeiE)mbcHHwe)b)Sfs;(5I>_2j~D+`8>z4 z556+&z|H+GuUgSaRK1M#wT5xZCBML;wy>rS85U*e`|?B=AQR-$bj6o2P5$F_}iLQnNM zyE$R{gRgvr^U zwPn^bV$qG+N`acXuWC8!{#WQHI78i^-p#T;7(PzxQTpZVv)S$;sNoaoUQJE)Ka)6r zM%lHad@%8vba=V$BmEdzWHLA!{^4rqGhvQ zJ#%~Y%sYo;kh8f#C3#GFGqG2}&08t?j}1soXKv=)ad;AWT1qMS%-;`&rO(o`((^S_ z5AB6C=oD&D`uzC;dCrDeQ8a7CnjK`<$5)NFT%CD&yEBWZwr`%wBO+JVq0;K`fc=KO z72~&;KUX%iIv!uptF+uldxrF7wt5SB+99L+oPkCp-CXAC?Y@M?7C{fuiIpbDrMGjXQw{S8!AuMLnh`7XEj+fZU!pL0fpLY~lYjgP7p$L| zHBM+&7;@U}@%6Vlk5bR>9@jErowb8$mV9%aZ2CX5H5%$w%B=J<0`}`fjnat<9SYM& z2}sLoh2s;<>I1vaani3s`J6P$NaYo}zH@u~AO;kr~obgTr?j&T^fSsC}Nvs+y*wxdZec9Y%8KU_0^FvsD|o+*V5qpn}qUPGywTdHfDYM8Te zNzB-cn6c|k?T6mgFM3YoDvyr1Art-BBI?5QhQf-mmQ@kKGtJzTy|_h>URW!o7@rvA zP_#|W)T8wHvD=@D9?y-NGb>+q*=ggT!u0w6rpU|aer^CoT_Ld`HMD?0mS0GjTBd3B z`Uz2^ro~77cw(M9YI%+MafKX?wt35It}mfDe;?ybRo|xJYF=vSx%=SvLB-514+kw-JIHln@}&dGHNU@D zG3UuLcaMiOo%zMDoehe1xy1#HEmQWUw_~&DyLMzu3AG*_kiN?;Kf56F!rp%(cj^_F zWE-CdP^Bq*OhPIlidTEQ4!^F9PEz)m?arEU=r3E^}VUbVWYx5?`w+w7ECSS@7b+LGM+CDizmZE!3Coby1<0P)UqLI-IDY{d` zx-AR0RLgHlTr3^^d#Doa)%#jn^gC{Bi){7VX>Bs&N#;ERZp>tFUGuWgI67cO#@N>E zBFZnR{1}N!uMRI#nr9=C)qAZ~5N8C|{(cA0^_{L*-6P9i&d>_=P`0B~k>$1&G;n*)nCnN8%W-*`nrJ1ZYgHFzMn7In zcr`9FlFlNkRE?l-%BwQdt}SjIl00*~rLX77OhlyD9H*b_VJpoUXpyL4;56SOcgEY- z+lO(+G@mM%b%dRA=A}k@NXV<@);sn(9$RxhwJKlVp6$IiDE#_D%JrieG1I)zRjZ8a zx7c1OMbY}p@5dzUm|5n#vhM1}n;ZMh{%yx|7A4?`Z$Z_{4}n~r-Iq726%|Pp2G(#| zh-w6Xf**k*H(w2V{u8QN449c7_xw(~9?=yFkN2JhgbV9<0<)j))*XPpuk6umk4&Kn8s}y<}pNb7CYT0k3bjj`gQBKfeYp-GNR|UIqd+I*gZS^8|KZ%&1A3*LQJ?NB{ zujafSpN!P6)Ku%r*KSd^wYCj?O?b(bTUgjI=6LpK%gT&hh!;X>>? zKTpXasxW-qfwxvt(TUoPV=mp?5t)^3C>?I}%=18Yey(;S^q#%t*}_*9b}Cimp8dC1 z*;qw#)HCZpg(vO5IUs0E<*i%td)mIyG^%}*i`k695%44(^dymmV8Cq#7`rG8P zmu7~VCo9g@EX?>+`U!JAAGwVx>fCBVd7j*+Oux${y=Je)<~y(<}czZoG2c97S~TTA5yWgx{n-WX1f^$(i@|t0W&Rj*|DQ(VEZxa1#M4H z4I8l|F!ie6)V%TBo_fWuj@ILq{ZHRD%a?h4*RY5XG~-sihHezvs1&7!CWr`hl9Jf+~f#%p3;5w)h$3^uLK-;;k$gQ>T& zCn?zLzmOg8lhG9F zUUtMNFxJj`sQCw9>F3izY*a#4r=qoMPZD<7geUuA`_59SzNcGr!;=F`ePygVY{dPD zs%PIGQ;fBz9P#GL6r8EKV${;sgi^2_j56a8eg39owXqp*Kj!Ch<(F?!ip%S#MmRZl zz4p+)($(2cm#XBFC=;x;)zST@caxTX$XtB1rO0^H$J3kvfOlGJK|uIp?A@;P+Q-tS z=vMx_9W&*ge~stc#p8HySvu`E;6$R+duDK^(cx-IU+l~!Rq)9i+I2#rixuOyX8E_A zM&LJL@M|o*lNJyehTV-}uxNn{GChn*WhU{Cjohd)EE?|+1;3oiyVAqIw+d%5=GMI9 z6}*;zk>{IgICXKQv3OT^;dUqlIGGWMal!6=O=mFa*i|+NaNhG>O&~3ijV!PlL!SUL z#U+q5G0;#SH{|P;9{u6E`rdR__?Otoh{q47#7z!jVK>=$Cqk~YNGjYy1N#LReVt>l zJ5b(?2paFG$eB#?ah*nqr_M~E@puWSaF(u&SSog}4o;%HU8e;kFeBkU92`0Yv1m*_ zq7g3Cc;2lztdw^yg%&_c5a3`7f(Ji>b0=PV$U+)^co>2u6aYI2cKoQ*C!B;q=rU6{ zHiICJDRck?BYwnt@&xH(mjf&Xh2K@e2PrE-rLa0+3j|^NAP)z33MbX6tJ$xsT(`!& zFlmlWQH9e*2=@Gl_vDF&if?r8Hk{ya5WeBUFZ%NAHk@7QBav`&F-SzhSuF{i%FPuQ zizY@mZD?In%48p2@(bYv%b`nuDx5#$oomH7BhL@lKb*;~&*~ zE4l4nA7f|aO?vnX;e>GsL7e3{I>Jk7AAU35yo-A{6b-?C3b09oAdaa>IL)xHV_dOa*>pF?FK&(TUNizG7{5#sx@Ck+T*Mj@$1XZ`nt~-Qe z7Ql90nc;sBqZlr7=FEl9d z+eWMI^Hcgjj0L^LS)eP)I5-%%Uq1EleV@|W-3R+|hzGlFfq}sJN8y72ixnX4Ehrc& zh=mt0Sg0TfFJ6%6y$jYE{l!{6r;o?Z4V3tDn?bIIP=f^?S1g7`KnMZS!c2HH*fj}W z5a|I(9DW>0@N;|rAkWU`AM$)7F3tI~X_#32@QD>)uLSC%-rr1$B7t=g(03cyscuuq zt47Cp6y7i_QJhs_(rsXM1(HPqTaDd%6*)P)z&fhi6Li7u<0%HDYwl~-GKbrb@_hL# zfh9`{?8FaiwL=DMA(v8yHN0xv(B5re|5uN#!&DOqtVA9gt=OQoyT#ah&%P2Z`jVnY zKLJ>5;P8TnAcle2M<^{kP-yZ`K{>uYN?FS@Vsudmb5 zd|4vWi(g>78j)a2w9Od4<4@;{u^i8|c&=ymtlwvf1^XK`@V|KZ?jQ{A_2~&v!qzNa z0Dy(ThrdIF0Oi8q=ir6vkidd)Be8RHbVY^Anj~wNmFv#y>aiT&3Wv!1qVB(8yF=OB`%G?lxwFDs_La zf0yN&dY z`$DGFQ=NZvTXD0Yp~thp6WyNv@tfGL0@XW<@R_LF+HKh6K~9*mY1|?Imkk@=PUOn}B*pxNiQ4(v{;m<37j&_P4->0@s)^ghFl=J*eob4QmwZq5mH);tol4EyZ zlZ|49toxb;Cb#J~vnETd-RREL`cdr`C~5^Ym{+>4^4?gI@TuNjV(mKkT%2^Xi8TBkc`8 zZ&U6ju;t&oDHr>Vi;F?t?M*qe51JFIx7B&Q6(QGj&8d)IvME0bz=J~&A%TVOHs$}C zuHmWw|7~llz_SXw`X-1wizJ>*e|T#P7uF!apH7Mj5^MM0*7m=x?e_;_aFECw6!6mW z-`4iOt!+2Xng83`N@Smgh?{xJTi8COqbq!_hee;m<~5zl!0N-*Dmc`0|7UTL@bG zi1)Bgs6sXJq{_g>Y-vKcY2jWU?!s{X)y(pNN7Wk>woBIFXi1H1=c*dMTE-T_EQ4 z%c+}iSZ77oEypiQ3!fX?^KIijc{3MOs$suA*bioS{DzG%A#ag{&%y544}$Q(8^SN* zjS)eT7Xf#L%b}+x)aMsDrLkiAO>eH_{nrstJB~-ffAF}2SbUUrTKP?HfWi=j^)Tlb zeK%IKpd9a0QBcGK5lp%;;$gJICCgv^Z;Ih2;V~u>m1F<&(C=^ZI3q@%x&&hu?`^1q TFzb%3pUk;qMrQmhkN$rE*(4O> literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/MI_Circle.uasset b/demo/Blackholio/client-unreal/Content/MI_Circle.uasset new file mode 100644 index 0000000000000000000000000000000000000000..10cc504a6271dc70985c1c3d93ce11c55d927b35 GIT binary patch literal 6696 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 Nsk^PQa*hGz{|j8iOf3Ka literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/MI_Food.uasset b/demo/Blackholio/client-unreal/Content/MI_Food.uasset new file mode 100644 index 0000000000000000000000000000000000000000..c283ed43f8f1960ae9f9a5d47491d9b0f92a51b0 GIT binary patch literal 8431 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 literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/M_Circle.uasset b/demo/Blackholio/client-unreal/Content/M_Circle.uasset new file mode 100644 index 0000000000000000000000000000000000000000..0b451340324c3b5f63710e5c27f841c8dc134338 GIT binary patch literal 37207 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 WLLAC6qMsSN%U;dXr5?2d`u_*C*r@#g literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Content/WBP_Nameplate.uasset b/demo/Blackholio/client-unreal/Content/WBP_Nameplate.uasset new file mode 100644 index 0000000000000000000000000000000000000000..1bfdff8b96105c2828cea11cee20e4ea29a41c26 GIT binary patch literal 43242 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 literal 0 HcmV?d00001 diff --git a/demo/Blackholio/client-unreal/Source/client_unreal.Target.cs b/demo/Blackholio/client-unreal/Source/client_unreal.Target.cs new file mode 100644 index 000000000..497148bd7 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal.Target.cs @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class client_unrealTarget : TargetRules +{ + public client_unrealTarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Game; + DefaultBuildSettings = BuildSettingsVersion.V5; + IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_6; + ExtraModuleNames.Add("client_unreal"); + } +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/BlackholioGameMode.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/BlackholioGameMode.cpp new file mode 100644 index 000000000..14892c345 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/BlackholioGameMode.cpp @@ -0,0 +1,2 @@ +#include "BlackholioGameMode.h" + diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/BlackholioPlayerController.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/BlackholioPlayerController.cpp new file mode 100644 index 000000000..f2dadaba1 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/BlackholioPlayerController.cpp @@ -0,0 +1,196 @@ +#include "BlackholioPlayerController.h" + +#include "DbVector2.h" +#include "EnhancedInputComponent.h" +#include "EnhancedInputSubsystems.h" +#include "GameManager.h" +#include "PlayerPawn.h" +#include "Blueprint/UserWidget.h" +#include "Gameplay/LeaderboardWidget.h" +#include "Gameplay/RespawnWidget.h" +#include "Gameplay/UsernameChooserWidget.h" + +ABlackholioPlayerController::ABlackholioPlayerController() +{ + bShowMouseCursor = true; + bEnableClickEvents = true; + bEnableMouseOverEvents = true; + PrimaryActorTick.bCanEverTick = true; +} + +void ABlackholioPlayerController::ShowDeathScreen() +{ + if (!IsLocalController() || !RespawnClass) return; + + if (!RespawnWidget) + { + RespawnWidget = CreateWidget(this, RespawnClass); + RespawnWidget->AddToViewport(100); + } + else if (!RespawnWidget->IsInViewport()) + { + RespawnWidget->AddToViewport(100); + } + + RespawnWidget->SetVisibility(ESlateVisibility::Visible); + FInputModeUIOnly InputMode; + InputMode.SetWidgetToFocus(RespawnWidget->TakeWidget()); + InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock); + SetInputMode(InputMode); + bShowMouseCursor = true; + //RespawnWidget->SetKeyboardFocus(); +} + +void ABlackholioPlayerController::BeginPlay() +{ + Super::BeginPlay(); + + if (!LeaderboardWidget && LeaderboardClass) + { + LeaderboardWidget = CreateWidget(this, LeaderboardClass); + LeaderboardWidget->AddToViewport(100); + LeaderboardWidget->SetVisibility(ESlateVisibility::Visible); + } + SetInputMode(FInputModeGameOnly()); +} + +void ABlackholioPlayerController::Tick(float DeltaSeconds) +{ + Super::Tick(DeltaSeconds); + + if (!AGameManager::Instance || !AGameManager::Instance->IsConnected()) + { + return; + } + + const float Now = GetWorld() ? GetWorld()->GetTimeSeconds() : 0.f; + if ((Now - LastMovementSendTimestamp) >= SendUpdatesFrequency) + { + LastMovementSendTimestamp = Now; + FVector2D LatestDesiredDirection = ComputeDesiredDirection(); + if ((LatestDesiredDirection.X != 0 && LatestDesiredDirection.Y != 0)) + { + const FVector2D ConvertDirection = FVector2D(LatestDesiredDirection.X, LatestDesiredDirection.Y*-1); + AGameManager::Instance->Conn->Reducers->UpdatePlayerInput(ToDbVector(ConvertDirection)); + } + } + + if (!AGameManager::Instance->bSubscriptionsApplied) return; + if (AGameManager::Instance->PlayerNameAtStart.IsEmpty() && !bShowedUsernameChooser) + { + bShowedUsernameChooser = true; + if (IsLocalController() && UsernameChooserClass) + { + this->UsernameChooserWidget = CreateWidget(this, UsernameChooserClass); + if (this->UsernameChooserWidget) + { + this->UsernameChooserWidget->AddToViewport(100); + SetInputMode(FInputModeUIOnly()); // should focus the textbox + bShowMouseCursor = true; + } + } + //this->UsernameChooserWidget->Hide(); + } +} + +void ABlackholioPlayerController::OnPossess(APawn* InPawn) +{ + Super::OnPossess(InPawn); + LocalPlayer = Cast(InPawn); + EnsureMappingContext(); +} + +void ABlackholioPlayerController::SetupInputComponent() +{ + Super::SetupInputComponent(); + if (UEnhancedInputComponent* EIC = Cast(InputComponent)) + { + if (SplitAction) + { + EIC->BindAction(SplitAction, ETriggerEvent::Triggered, this, &ABlackholioPlayerController::OnSplitTriggered); + } + if (SuicideAction) + { + EIC->BindAction(SuicideAction, ETriggerEvent::Triggered, this, &ABlackholioPlayerController::OnSuicideTriggered); + } + if (ToggleInputLockAction) + { + EIC->BindAction(ToggleInputLockAction, ETriggerEvent::Triggered, this, &ABlackholioPlayerController::OnToggleInputLockTriggered); + } + } +} + +FVector2D ABlackholioPlayerController::ComputeDesiredDirection() const +{ + int32 SizeX = 0, SizeY = 0; + GetViewportSize(SizeX, SizeY); + if (SizeX <= 0 || SizeY <= 0) + { + return FVector2D::ZeroVector; + } + + const FVector2D ViewportCenter(static_cast(SizeX) * 0.5f, static_cast(SizeY) * 0.5f); + + FVector2D MousePos = ViewportCenter; + if (!LockInputPosition.IsSet()) + { + float MouseX = 0.f, MouseY = 0.f; + if (!GetMousePosition(MouseX, MouseY)) + { + return FVector2D::ZeroVector; + } + MousePos = FVector2D(MouseX, MouseY); + } + else + { + MousePos = LockInputPosition.GetValue(); + } + + if (MousePos.X < 0.f || MousePos.X >= SizeX || MousePos.Y < 0.f || MousePos.Y >= SizeY) + { + return FVector2D::ZeroVector; + } + + const float Denominator = FMath::Max(1.f, static_cast(SizeY) / 3.f); + const FVector2D DesiredDir = (MousePos - ViewportCenter) / Denominator; + return DesiredDir; +} + +void ABlackholioPlayerController::EnsureMappingContext() const +{ + if (!PlayerMappingContext) return; + + if (ULocalPlayer* LP = GetLocalPlayer()) + { + if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem(LP)) + { + Subsystem->AddMappingContext(PlayerMappingContext, 0); + } + } +} + +void ABlackholioPlayerController::OnSplitTriggered(const FInputActionValue& Value) +{ + LocalPlayer->Split(); +} + +void ABlackholioPlayerController::OnSuicideTriggered(const FInputActionValue& Value) +{ + LocalPlayer->Suicide(); +} + +void ABlackholioPlayerController::OnToggleInputLockTriggered(const FInputActionValue& Value) +{ + if (LockInputPosition.IsSet()) + { + LockInputPosition.Reset(); + } + else + { + float X, Y; + if (GetMousePosition(X, Y)) + { + LockInputPosition = FVector2D(X, Y); + } + } +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/Circle.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Circle.cpp new file mode 100644 index 000000000..5d0fe7a4c --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Circle.cpp @@ -0,0 +1,53 @@ +#include "Circle.h" +#include "PlayerPawn.h" +#include "ModuleBindings/Types/CircleType.g.h" + +ACircle::ACircle() +{ + ColorPalette = { + // Yellow + FLinearColor::FromSRGBColor(FColor(175, 159, 49, 255)), + FLinearColor::FromSRGBColor(FColor(175, 116, 49, 255)), + + // Purple + FLinearColor::FromSRGBColor(FColor(112, 47, 252, 255)), + FLinearColor::FromSRGBColor(FColor(51, 91, 252, 255)), + + // Red + FLinearColor::FromSRGBColor(FColor(176, 54, 54, 255)), + FLinearColor::FromSRGBColor(FColor(176, 109, 54, 255)), + FLinearColor::FromSRGBColor(FColor(141, 43, 99, 255)), + + // Blue + FLinearColor::FromSRGBColor(FColor(2, 188, 250, 255)), + FLinearColor::FromSRGBColor(FColor(7, 50, 251, 255)), + FLinearColor::FromSRGBColor(FColor(2, 28, 146, 255)), + }; +} + +void ACircle::Spawn(const FCircleType& Circle, APlayerPawn* InOwner) +{ + Super::Spawn(Circle.EntityId); + + const int32 Index = ColorPalette.Num() ? static_cast(InOwner->PlayerId % ColorPalette.Num()) : 0; + const FLinearColor Color = ColorPalette.IsValidIndex(Index) ? ColorPalette[Index] : FLinearColor::Green; + SetColor(Color); + + this->Owner = InOwner; + SetUsername(InOwner->GetUsername()); +} + +void ACircle::OnDelete(const FEventContext& Context) +{ + Super::OnDelete(Context); + Owner->OnCircleDeleted(this); +} + +void ACircle::SetUsername(const FString& InUsername) +{ + if (Username.Equals(InUsername, ESearchCase::CaseSensitive)) + return; + + Username = InUsername; + OnUsernameChanged.Broadcast(Username); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/Entity.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Entity.cpp new file mode 100644 index 000000000..cfa53f53e --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Entity.cpp @@ -0,0 +1,110 @@ +#include "Entity.h" +#include "DbVector2.h" +#include "GameManager.h" +#include "PaperSpriteComponent.h" +#include "ModuleBindings/Tables/EntityTable.g.h" + +AEntity::AEntity() +{ + PrimaryActorTick.bCanEverTick = true; + LerpTime = 0.f; +} + +void AEntity::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + if (bIsDespawning) + { + ConsumeDespawn(DeltaTime); + + return; + } + // Interpolate the position and scale + LerpTime = FMath::Min(LerpTime + DeltaTime, LerpDuration); + const float Alpha = (LerpDuration > 0.f) ? (LerpTime / LerpDuration) : 1.f; + SetActorLocation(FMath::Lerp(LerpStartPosition, LerpTargetPosition, Alpha)); + + const float NewScale = FMath::FInterpTo(GetActorScale3D().X, TargetScale, DeltaTime, 8.f); + SetActorScale3D(FVector(NewScale)); +} + +void AEntity::ConsumeDespawn(float DeltaTime) +{ + if (!bIsDespawning || !ConsumingEntity) + return; + + DespawnElapsed = FMath::Min(DespawnElapsed + DeltaTime, DespawnTime); + const float T = (DespawnTime > 0.f) ? (DespawnElapsed / DespawnTime) : 1.f; + + const FVector CurrentTargetPos = ConsumingEntity->GetActorLocation(); + SetActorLocation(FMath::Lerp(ConsumeStartPosition, CurrentTargetPos, T)); + SetActorScale3D(FMath::Lerp(ConsumeStartScale, FVector::ZeroVector, T)); + + if (DespawnElapsed >= DespawnTime) + { + bIsDespawning = false; + ConsumingEntity = nullptr; + Destroy(); + } +} + +void AEntity::Spawn(uint32 InEntityId) +{ + EntityId = InEntityId; + + const FEntityType EntityRow = AGameManager::Instance->Conn->Db->Entity->EntityId->Find(InEntityId); + + LerpStartPosition = LerpTargetPosition = ToFVector(EntityRow.Position); + SetActorLocation(LerpStartPosition); + TargetScale = MassToDiameter(EntityRow.Mass); + SetActorScale3D(FVector::OneVector); + LerpTime = 0.f; +} + +void AEntity::OnEntityUpdated(const FEntityType& NewVal) +{ + LerpStartPosition = GetActorLocation(); + LerpTargetPosition = ToFVector(NewVal.Position); + TargetScale = MassToDiameter(NewVal.Mass); + LerpTime = 0.f; +} + +void AEntity::OnDelete(const FEventContext& Context) +{ + if (ConsumeDelete(Context)) + return; + + Destroy(); +} + +bool AEntity::ConsumeDelete(const FEventContext& Context) +{ + if (!Context.Event.IsReducer()) + return false; + + const FReducer Reducer = Context.Event.GetAsReducer(); + + if (!Reducer.IsConsumeEntity()) + return false; + + const FConsumeEntityArgs Args = Reducer.GetAsConsumeEntity(); + const uint32 ConsumerId = Args.Request.ConsumerEntityId; + ConsumingEntity = AGameManager::Instance->GetEntity(ConsumerId); + if (!ConsumingEntity) + return false; + + bIsDespawning = true; + DespawnElapsed = 0.f; + ConsumeStartPosition = GetActorLocation(); + ConsumeStartScale = GetActorScale3D(); + return true; +} + +void AEntity::SetColor(const FLinearColor& Color) const +{ + if (UPaperSpriteComponent* SpriteComponent = FindComponentByClass()) + { + SpriteComponent->SetSpriteColor(Color); + } +} \ No newline at end of file diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/Food.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Food.cpp new file mode 100644 index 000000000..41c0d51f6 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Food.cpp @@ -0,0 +1,26 @@ +#include "Food.h" +#include "ModuleBindings/Types/FoodType.g.h" + +AFood::AFood() +{ + ColorPalette = { + // Greenish + FLinearColor::FromSRGBColor(FColor(119, 252, 173, 255)), + FLinearColor::FromSRGBColor(FColor(76, 250, 146, 255)), + FLinearColor::FromSRGBColor(FColor(35, 246, 120, 255)), + + // Aqua / Teal + FLinearColor::FromSRGBColor(FColor(119, 251, 201, 255)), + FLinearColor::FromSRGBColor(FColor(76, 249, 184, 255)), + FLinearColor::FromSRGBColor(FColor(35, 245, 165, 255)), + }; +} + +void AFood::Spawn(const FFoodType& FoodEntity) +{ + Super::Spawn(FoodEntity.EntityId); + + const int32 Index = ColorPalette.Num() ? static_cast(EntityId % ColorPalette.Num()) : 0; + const FLinearColor Color = ColorPalette.IsValidIndex(Index) ? ColorPalette[Index] : FLinearColor::Green; + SetColor(Color); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/GameManager.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/GameManager.cpp new file mode 100644 index 000000000..2e0ff7626 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/GameManager.cpp @@ -0,0 +1,341 @@ +#include "GameManager.h" +#include "Circle.h" +#include "Entity.h" +#include "Food.h" +#include "PlayerPawn.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Connection/Credentials.h" +#include "ModuleBindings/Tables/CircleTable.g.h" +#include "ModuleBindings/Tables/ConfigTable.g.h" +#include "ModuleBindings/Tables/EntityTable.g.h" +#include "ModuleBindings/Tables/FoodTable.g.h" +#include "ModuleBindings/Tables/PlayerTable.g.h" + +AGameManager* AGameManager::Instance = nullptr; + +AGameManager::AGameManager() +{ + PrimaryActorTick.bCanEverTick = true; + PrimaryActorTick.bStartWithTickEnabled = true; + + BorderISM = CreateDefaultSubobject(TEXT("BorderISM")); + SetRootComponent(BorderISM); + + if (CubeMesh != nullptr) + return; + + static ConstructorHelpers::FObjectFinder CubeAsset(TEXT("/Engine/BasicShapes/Cube.Cube")); + if (CubeAsset.Succeeded()) + { + CubeMesh = CubeAsset.Object; + } +} + +AEntity* AGameManager::GetEntity(uint32 EntityId) const +{ + if (const TWeakObjectPtr* WeakEntity = EntityMap.Find(EntityId)) + { + if (!WeakEntity->IsValid()) + { + return nullptr; + } + if (AEntity* Entity = WeakEntity->Get()) + { + return Entity; + } + } + + return nullptr; +} + +void AGameManager::BeginPlay() +{ + Super::BeginPlay(); + Instance = this; + + FOnConnectDelegate ConnectDelegate; + BIND_DELEGATE_SAFE(ConnectDelegate, this, AGameManager, HandleConnect); + FOnDisconnectDelegate DisconnectDelegate; + BIND_DELEGATE_SAFE(DisconnectDelegate, this, AGameManager, HandleDisconnect); + FOnConnectErrorDelegate ConnectErrorDelegate; + BIND_DELEGATE_SAFE(ConnectErrorDelegate, this, AGameManager, HandleConnect); + + UCredentials::Init(FString::Printf(TEXT("%s-%s"), *TokenFilePath, *ServerUri)); + FString Token = UCredentials::LoadToken(); + + UDbConnectionBuilder* Builder = UDbConnection::Builder() + ->WithUri(ServerUri) + ->WithModuleName(ModuleName) + ->OnConnect(ConnectDelegate) + ->OnDisconnect(DisconnectDelegate) + ->OnConnectError(ConnectErrorDelegate); + + if (!Token.IsEmpty()) + { + Builder->WithToken(Token); + } + + Conn = Builder->Build(); +} + +void AGameManager::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Disconnect(); + if (Instance == this) + { + Instance = nullptr; + } + Super::EndPlay(EndPlayReason); +} + +void AGameManager::Tick(float DeltaTime) +{ + if (IsConnected()) + { + Conn->FrameTick(); + } +} + +void AGameManager::HandleConnect(UDbConnection* InConn, FSpacetimeDBIdentity Identity, const FString& Token) +{ + UE_LOG(LogTemp, Log, TEXT("Connected.")); + UCredentials::SaveToken(Token); + LocalIdentity = Identity; + + Conn->Db->Circle->OnInsert.AddDynamic(this, &AGameManager::OnCircleInsert); + Conn->Db->Entity->OnUpdate.AddDynamic(this, &AGameManager::OnEntityUpdate); + Conn->Db->Entity->OnDelete.AddDynamic(this, &AGameManager::OnEntityDelete); + Conn->Db->Food->OnInsert.AddDynamic(this, &AGameManager::OnFoodInsert); + Conn->Db->Player->OnInsert.AddDynamic(this, &AGameManager::OnPlayerInsert); + Conn->Db->Player->OnDelete.AddDynamic(this, &AGameManager::OnPlayerDelete); + + FOnSubscriptionApplied AppliedDelegate; + BIND_DELEGATE_SAFE(AppliedDelegate, this, AGameManager, HandleSubscriptionApplied); + Conn->SubscriptionBuilder() + ->OnApplied(AppliedDelegate) + ->SubscribeToAllTables(); +} + +void AGameManager::HandleConnectError(const FString& Error) +{ + UE_LOG(LogTemp, Log, TEXT("Connection error %s"), *Error); +} + +void AGameManager::HandleDisconnect(UDbConnection* InConn, const FString& Error) +{ + UE_LOG(LogTemp, Log, TEXT("Disconnected.")); + if (!Error.IsEmpty()) + { + UE_LOG(LogTemp, Log, TEXT("Disconnect error %s"), *Error); + } +} + +void AGameManager::HandleSubscriptionApplied(FSubscriptionEventContext& Context) +{ + UE_LOG(LogTemp, Log, TEXT("Subscription applied!")); + this->bSubscriptionsApplied = true; + + // Once we have the initial subscription sync'd to the client cache + // Get the world size from the config table and set up the arena + uint64 WorldSize = Conn->Db->Config->Id->Find(0).WorldSize; + SetupArena(WorldSize); + + FPlayerType Player = Context.Db->Player->Identity->Find(LocalIdentity); + if (!Player.Name.IsEmpty()) + { + this->PlayerNameAtStart = Player.Name; + if (Context.Db->Circle->PlayerId->Filter(Player.PlayerId).Num() == 0) + { + Context.Reducers->EnterGame(Player.Name); + } + } +} + +void AGameManager::SetupArena(uint64 WorldSizeMeters) +{ + if (!BorderISM || !CubeMesh) return; + + BorderISM->ClearInstances(); + BorderISM->SetStaticMesh(CubeMesh); + if (BorderMaterial) + { + BorderISM->SetMaterial(0, BorderMaterial); + } + + // Convert from meters (uint64) → centimeters (double for precision) + const double worldSizeCmDouble = static_cast(WorldSizeMeters) * 100.0; + + // Clamp to avoid float overflow in transforms + const double clampedWorldSizeCmDouble = FMath::Clamp( + worldSizeCmDouble, + 0.0, + FLT_MAX * 0.25 // safe margin + ); + + // Convert to float for actual Unreal math + const float worldSizeCm = static_cast(clampedWorldSizeCmDouble); + + const float borderThicknessCm = BorderThickness; // already cm + + // Create four borders + CreateBorderCube( + FVector2f(worldSizeCm * 0.5f, worldSizeCm + borderThicknessCm * 0.5f), // North + FVector2f(worldSizeCm + borderThicknessCm * 2.0f, borderThicknessCm) + ); + + CreateBorderCube( + FVector2f(worldSizeCm * 0.5f, -borderThicknessCm * 0.5f), // South + FVector2f(worldSizeCm + borderThicknessCm * 2.0f, borderThicknessCm) + ); + + CreateBorderCube( + FVector2f(worldSizeCm + borderThicknessCm * 0.5f, worldSizeCm * 0.5f), // East + FVector2f(borderThicknessCm, worldSizeCm + borderThicknessCm * 2.0f) + ); + + CreateBorderCube( + FVector2f(-borderThicknessCm * 0.5f, worldSizeCm * 0.5f), // West + FVector2f(borderThicknessCm, worldSizeCm + borderThicknessCm * 2.0f) + ); +} + +void AGameManager::CreateBorderCube(const FVector2f Position, const FVector2f Size) const +{ + // Scale from the 100cm default cube to desired size (in cm) + const FVector Scale(Size.X / 100.0f, BorderHeight / 100.0f, Size.Y / 100.0f); + + // Place so the bottom sits on Z=0 (cube is centered) + const FVector Location(Position.X, BorderHeight * 0.5f, Position.Y); + + const FTransform Transform(FRotator::ZeroRotator, Location, Scale); + BorderISM->AddInstance(Transform); +} + +void AGameManager::OnCircleInsert(const FEventContext& Context, const FCircleType& NewRow) +{ + if (EntityMap.Contains(NewRow.EntityId)) return; + SpawnCircle(NewRow); +} + +void AGameManager::OnEntityUpdate(const FEventContext& Context, const FEntityType& OldRow, const FEntityType& NewRow) +{ + if (TWeakObjectPtr* WeakEntity = EntityMap.Find(NewRow.EntityId)) + { + if (!WeakEntity->IsValid()) + { + return; + } + if (AEntity* Entity = WeakEntity->Get()) + { + Entity->OnEntityUpdated(NewRow); + } + } +} + +void AGameManager::OnEntityDelete(const FEventContext& Context, const FEntityType& RemovedRow) +{ + TWeakObjectPtr EntityPtr; + const bool bHadEntry = EntityMap.RemoveAndCopyValue(RemovedRow.EntityId, EntityPtr); + const bool bIsValid =EntityPtr.IsValid(); + if (!bHadEntry || !bIsValid) + { + return; + } + + if (AEntity* Entity = EntityPtr.Get()) + { + Entity->OnDelete(Context); + } +} + +void AGameManager::OnFoodInsert(const FEventContext& Context, const FFoodType& NewRow) +{ + if (EntityMap.Contains(NewRow.EntityId)) return; + SpawnFood(NewRow); +} + +void AGameManager::OnPlayerInsert(const FEventContext& Context, const FPlayerType& NewRow) +{ + SpawnOrGetPlayer(NewRow); +} + +void AGameManager::OnPlayerDelete(const FEventContext& Context, const FPlayerType& RemovedRow) +{ + TWeakObjectPtr PlayerPtr; + const bool bHadEntry = PlayerMap.RemoveAndCopyValue(RemovedRow.PlayerId, PlayerPtr); + + if (!bHadEntry || !PlayerPtr.IsValid()) + { + return; + } + + if (APlayerPawn* Player = PlayerPtr.Get()) + { + Player->Destroy(); + } +} + +APlayerPawn* AGameManager::SpawnOrGetPlayer(const FPlayerType& PlayerRow) +{ + TWeakObjectPtr WeakPlayer = PlayerMap.FindRef(PlayerRow.PlayerId); + if (WeakPlayer.IsValid()) + { + return WeakPlayer.Get(); + } + + if (!PlayerClass) + { + UE_LOG(LogTemp, Error, TEXT("GameManager - PlayerClass not set.")); + return nullptr; + } + FActorSpawnParameters Params; + Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + APlayerPawn* Player = GetWorld()->SpawnActor(PlayerClass, FVector::ZeroVector, FRotator::ZeroRotator, Params); + if (Player) + { + Player->Initialize(PlayerRow); + PlayerMap.Add(PlayerRow.PlayerId, Player); + } + return Player; +} + +ACircle* AGameManager::SpawnCircle(const FCircleType& CircleRow) +{ + if (!CircleClass) + { + UE_LOG(LogTemp, Error, TEXT("GameManager - CircleClass not set.")); + return nullptr; + } + // Need player row for username + const FPlayerType PlayerRow = Conn->Db->Player->PlayerId->Find(CircleRow.PlayerId); + APlayerPawn* OwningPlayer = SpawnOrGetPlayer(PlayerRow); + + FActorSpawnParameters Params; + auto* Circle = GetWorld()->SpawnActor(CircleClass, FVector::ZeroVector, FRotator::ZeroRotator, Params); + if (Circle) + { + Circle->Spawn(CircleRow, OwningPlayer); + EntityMap.Add(CircleRow.EntityId, Circle); + if (OwningPlayer) + OwningPlayer->OnCircleSpawned(Circle); + } + return Circle; +} + +AFood* AGameManager::SpawnFood(const FFoodType& FoodEntity) +{ + if (!FoodClass) + { + UE_LOG(LogTemp, Error, TEXT("GameManager - FoodClass not set.")); + return nullptr; + } + + FActorSpawnParameters Params; + AFood* Food = GetWorld()->SpawnActor(FoodClass, FVector::ZeroVector, FRotator::ZeroRotator, Params); + if (Food) + { + Food->Spawn(FoodEntity); + EntityMap.Add(FoodEntity.EntityId, Food); + } + return Food; +} \ No newline at end of file diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/LeaderboardRowWidget.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/LeaderboardRowWidget.cpp new file mode 100644 index 000000000..f2873553c --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/LeaderboardRowWidget.cpp @@ -0,0 +1,15 @@ +#include "Gameplay/LeaderboardRowWidget.h" + +#include "Components/TextBlock.h" + +void ULeaderboardRowWidget::SetData(const FString& Username, int32 Mass) +{ + if (UsernameText) + { + UsernameText->SetText(FText::FromString(Username)); + } + if (MassText) + { + MassText->SetText(FText::AsNumber(Mass)); + } +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/LeaderboardWidget.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/LeaderboardWidget.cpp new file mode 100644 index 000000000..0be83e45a --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/LeaderboardWidget.cpp @@ -0,0 +1,134 @@ +#include "Gameplay/LeaderboardWidget.h" + +#include "GameManager.h" +#include "PlayerPawn.h" +#include "Components/VerticalBox.h" +#include "Gameplay/LeaderboardRowWidget.h" + +void ULeaderboardWidget::NativeConstruct() +{ + Super::NativeConstruct(); + BuildRowPool(); + + if (UpdatePeriod > 0.f) + { + GetWorld()->GetTimerManager().SetTimer( + UpdateTimer, this, &ULeaderboardWidget::UpdateLeaderboard, UpdatePeriod, true, 0.0f); + } + + UpdateLeaderboard(); +} + +void ULeaderboardWidget::NativeDestruct() +{ + if (UWorld* World = GetWorld()) + { + World->GetTimerManager().ClearTimer(UpdateTimer); + } + Super::NativeDestruct(); +} + +void ULeaderboardWidget::BuildRowPool() +{ + if (!Root || !RowClass) { return; } + + Rows.Reset(); + + for (int32 i = 0; i < MaxRowCount; ++i) + { + ULeaderboardRowWidget* Row = CreateWidget(this, RowClass); + if (!Row) { continue; } + + Root->AddChild(Row); + Row->SetVisibility(ESlateVisibility::Collapsed); + Rows.Add(Row); + } +} + +void ULeaderboardWidget::CollectPlayers(TArray& Out) const +{ + Out.Reset(); + + const AGameManager* GM = AGameManager::Instance; + if (!GM) return; + + TMap> PlayerMap = GM->GetPlayerMap(); + if (PlayerMap.Num() == 0) return; + + // 2) Build entries: mass > 0 only + for (const TPair>& Pair : PlayerMap) + { + APlayerPawn* Pawn = Pair.Value.Get(); + if (!Pawn) continue; + + const int32 Mass = static_cast(Pawn->TotalMass()); + if (Mass == 0) continue; + + FLeaderboardEntry E; + E.Username = Pawn->GetUsername(); + E.Mass = Mass; + E.Pawn = Pawn; + Out.Add(MoveTemp(E)); + } + + // 3) Sort by mass desc (stable by Username as a tiebreaker for consistent ordering) + Out.Sort([](const FLeaderboardEntry& A, const FLeaderboardEntry& B) + { + if (A.Mass != B.Mass) return A.Mass > B.Mass; + return A.Username < B.Username; + }); + + // 4) Keep top 10 + if (Out.Num() > 10) + { + Out.SetNum(10, /*bAllowShrinking*/ false); + } + + // 5) Append local player if not already present and has Mass > 0 + APlayerController* PC = GetOwningPlayer(); + if (PC) + { + if (APlayerPawn* LocalPawn = Cast(PC->GetPawn())) + { + const bool AlreadyIn = Out.ContainsByPredicate( + [LocalPawn](const FLeaderboardEntry& E){ return E.Pawn.Get() == LocalPawn; }); + + const int32 LocalMass = static_cast(LocalPawn->TotalMass()); + if (!AlreadyIn && LocalMass > 0) + { + FLeaderboardEntry Local; + Local.Username = LocalPawn->GetUsername(); + Local.Mass = LocalMass; + Local.Pawn = LocalPawn; + Out.Add(MoveTemp(Local)); + } + } + } +} + +void ULeaderboardWidget::UpdateLeaderboard() +{ + if (Rows.Num() == 0) return; + + TArray Players; + CollectPlayers(Players); + + int32 i = 0; + for (; i < Players.Num() && i < Rows.Num(); ++i) + { + if (ULeaderboardRowWidget* Row = Rows[i]) + { + Row->SetData(Players[i].Username, Players[i].Mass); + Row->SetVisibility(ESlateVisibility::SelfHitTestInvisible); + } + } + + // Hide the rest + for (; i < Rows.Num(); ++i) + { + if (ULeaderboardRowWidget* Row = Rows[i]) + { + Row->SetVisibility(ESlateVisibility::Collapsed); + } + } +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/ParallaxBackground.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/ParallaxBackground.cpp new file mode 100644 index 000000000..cdd6a75f9 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/ParallaxBackground.cpp @@ -0,0 +1,31 @@ +#include "Gameplay/ParallaxBackground.h" + +#include "GameManager.h" +#include "Kismet/GameplayStatics.h" +#include "ModuleBindings/Tables/ConfigTable.g.h" + +AParallaxBackground::AParallaxBackground() +{ + PrimaryActorTick.bCanEverTick = true; +} + +void AParallaxBackground::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + APlayerCameraManager* PCM = UGameplayStatics::GetPlayerCameraManager(this, 0); + if (!PCM) return; + + const FVector Cam = PCM->GetCameraLocation(); + + uint64 WorldSize = AGameManager::Instance->Conn->Db->Config->Id->Find(0).WorldSize; + float WorldCenter = (WorldSize); + const FVector NewLoc( + (Cam.X+WorldCenter) * Multiplier, // plane X + FixedY, // depth constant + (Cam.Z+WorldCenter) * Multiplier // plane Z + ); + + SetActorLocation(NewLoc); +} + diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/RespawnWidget.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/RespawnWidget.cpp new file mode 100644 index 000000000..287c7fc5e --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/RespawnWidget.cpp @@ -0,0 +1,23 @@ +#include "Gameplay/RespawnWidget.h" +#include "Components/Button.h" +#include "GameManager.h" + +void URespawnWidget::NativeConstruct() +{ + Super::NativeConstruct(); + RespawnButton->OnClicked.AddDynamic(this, &URespawnWidget::OnRespawnPressed); +} + +void URespawnWidget::OnRespawnPressed() +{ + AGameManager* Manager = AGameManager::Instance; + UE_LOG(LogTemp, Warning, TEXT("Respawn calling reducer")); + Manager->Conn->Reducers->Respawn(); + UE_LOG(LogTemp, Warning, TEXT("Respawn reducer called")); + SetVisibility(ESlateVisibility::Collapsed); + if (APlayerController* PC = GetOwningPlayer()) + { + PC->SetInputMode(FInputModeGameOnly()); + //PC->bShowMouseCursor = false; + } +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/UsernameChooserWidget.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/UsernameChooserWidget.cpp new file mode 100644 index 000000000..12f6a1f03 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/UsernameChooserWidget.cpp @@ -0,0 +1,56 @@ +#include "Gameplay/UsernameChooserWidget.h" +#include "Components/Button.h" +#include "Components/EditableTextBox.h" +#include "ModuleBindings/Tables/PlayerTable.g.h" +#include "GameManager.h" + +void UUsernameChooserWidget::Hide() +{ + SetVisibility(ESlateVisibility::Collapsed); + if (APlayerController* PC = GetOwningPlayer()) + { + PC->SetInputMode(FInputModeGameOnly()); + } +} + +void UUsernameChooserWidget::NativeConstruct() +{ + Super::NativeConstruct(); + + PlayButton->OnClicked.AddDynamic(this, &UUsernameChooserWidget::OnPlayPressed); + AGameManager* Manager = AGameManager::Instance; + Manager->Conn->Db->Player->OnInsert.AddDynamic(this, &UUsernameChooserWidget::HandlePlayerInserted); +} + +void UUsernameChooserWidget::NativeDestruct() +{ + Super::NativeDestruct(); + AGameManager* Manager = AGameManager::Instance; + Manager->Conn->Db->Player->OnInsert.RemoveDynamic(this, &UUsernameChooserWidget::HandlePlayerInserted); +} + +void UUsernameChooserWidget::OnPlayPressed() +{ + FString Name = UsernameInputField ? UsernameInputField->GetText().ToString().TrimStartAndEnd() : TEXT(""); + if (Name.IsEmpty()) + { + Name = TEXT(""); + } + AGameManager* Manager = AGameManager::Instance; + Manager->Conn->Reducers->EnterGame(Name); + + SetVisibility(ESlateVisibility::Collapsed); + if (APlayerController* PC = GetOwningPlayer()) + { + PC->SetInputMode(FInputModeGameOnly()); + } +} + +void UUsernameChooserWidget::HandlePlayerInserted(const FEventContext& Context, const FPlayerType& NewPlayer) +{ + AGameManager* Manager = AGameManager::Instance; + if (UsernameInputField && NewPlayer.Identity == Manager->LocalIdentity) + { + UsernameInputField->SetText(FText::FromString(NewPlayer.Name)); + } +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/SpacetimeDBClient.g.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/SpacetimeDBClient.g.cpp new file mode 100644 index 000000000..85039015e --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/SpacetimeDBClient.g.cpp @@ -0,0 +1,927 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#include "ModuleBindings/SpacetimeDBClient.g.h" +#include "DBCache/WithBsatn.h" +#include "BSATN/UEBSATNHelpers.h" +#include "ModuleBindings/Tables/EntityTable.g.h" +#include "ModuleBindings/Tables/MoveAllPlayersTimerTable.g.h" +#include "ModuleBindings/Tables/CircleDecayTimerTable.g.h" +#include "ModuleBindings/Tables/EntityTable.g.h" +#include "ModuleBindings/Tables/FoodTable.g.h" +#include "ModuleBindings/Tables/CircleRecombineTimerTable.g.h" +#include "ModuleBindings/Tables/ConfigTable.g.h" +#include "ModuleBindings/Tables/PlayerTable.g.h" +#include "ModuleBindings/Tables/CircleTable.g.h" +#include "ModuleBindings/Tables/PlayerTable.g.h" +#include "ModuleBindings/Tables/ConsumeEntityTimerTable.g.h" +#include "ModuleBindings/Tables/SpawnFoodTimerTable.g.h" +#include "ModuleBindings/Tables/CircleTable.g.h" + +static FReducer DecodeReducer(const FReducerEvent& Event) +{ + const FString& ReducerName = Event.ReducerCall.ReducerName; + + if (ReducerName == TEXT("circle_decay")) + { + FCircleDecayArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); + return FReducer::CircleDecay(Args); + } + + if (ReducerName == TEXT("circle_recombine")) + { + FCircleRecombineArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); + return FReducer::CircleRecombine(Args); + } + + if (ReducerName == TEXT("connect")) + { + FConnectArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); + return FReducer::Connect(Args); + } + + if (ReducerName == TEXT("consume_entity")) + { + FConsumeEntityArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); + return FReducer::ConsumeEntity(Args); + } + + if (ReducerName == TEXT("disconnect")) + { + FDisconnectArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); + return FReducer::Disconnect(Args); + } + + if (ReducerName == TEXT("enter_game")) + { + FEnterGameArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); + return FReducer::EnterGame(Args); + } + + if (ReducerName == TEXT("move_all_players")) + { + FMoveAllPlayersArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); + return FReducer::MoveAllPlayers(Args); + } + + if (ReducerName == TEXT("player_split")) + { + FPlayerSplitArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); + return FReducer::PlayerSplit(Args); + } + + if (ReducerName == TEXT("respawn")) + { + FRespawnArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); + return FReducer::Respawn(Args); + } + + if (ReducerName == TEXT("spawn_food")) + { + FSpawnFoodArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); + return FReducer::SpawnFood(Args); + } + + if (ReducerName == TEXT("suicide")) + { + FSuicideArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); + return FReducer::Suicide(Args); + } + + if (ReducerName == TEXT("update_player_input")) + { + FUpdatePlayerInputArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); + return FReducer::UpdatePlayerInput(Args); + } + + return FReducer(); +} + +UDbConnection::UDbConnection(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) +{ + SetReducerFlags = ObjectInitializer.CreateDefaultSubobject(this, TEXT("SetReducerFlags")); + + Db = ObjectInitializer.CreateDefaultSubobject(this, TEXT("RemoteTables")); + Db->Initialize(); + + Reducers = ObjectInitializer.CreateDefaultSubobject(this, TEXT("RemoteReducers")); + Reducers->SetCallReducerFlags = SetReducerFlags; + Reducers->Conn = this; + + RegisterTable(TEXT("logged_out_entity"), Db->LoggedOutEntity); + RegisterTable(TEXT("move_all_players_timer"), Db->MoveAllPlayersTimer); + RegisterTable(TEXT("circle_decay_timer"), Db->CircleDecayTimer); + RegisterTable(TEXT("entity"), Db->Entity); + RegisterTable(TEXT("food"), Db->Food); + RegisterTable(TEXT("circle_recombine_timer"), Db->CircleRecombineTimer); + RegisterTable(TEXT("config"), Db->Config); + RegisterTable(TEXT("logged_out_player"), Db->LoggedOutPlayer); + RegisterTable(TEXT("logged_out_circle"), Db->LoggedOutCircle); + RegisterTable(TEXT("player"), Db->Player); + RegisterTable(TEXT("consume_entity_timer"), Db->ConsumeEntityTimer); + RegisterTable(TEXT("spawn_food_timer"), Db->SpawnFoodTimer); + RegisterTable(TEXT("circle"), Db->Circle); +} + +FContextBase::FContextBase(UDbConnection* InConn) +{ + Db = InConn->Db; + Reducers = InConn->Reducers; + SetReducerFlags = InConn->SetReducerFlags; + Conn = InConn; +} +bool FContextBase::IsActive() const +{ + return Conn->IsActive(); +} +void FContextBase::Disconnect() +{ + Conn->Disconnect(); +} +USubscriptionBuilder* FContextBase::SubscriptionBuilder() +{ + return Conn->SubscriptionBuilder(); +} +bool FContextBase::TryGetIdentity(FSpacetimeDBIdentity& OutIdentity) const +{ + return Conn->TryGetIdentity(OutIdentity); +} +FSpacetimeDBConnectionId FContextBase::GetConnectionId() const +{ + return Conn->GetConnectionId(); +} + +void URemoteTables::Initialize() +{ + + /** Creating tables */ + LoggedOutEntity = NewObject(this); + MoveAllPlayersTimer = NewObject(this); + CircleDecayTimer = NewObject(this); + Entity = NewObject(this); + Food = NewObject(this); + CircleRecombineTimer = NewObject(this); + Config = NewObject(this); + LoggedOutPlayer = NewObject(this); + LoggedOutCircle = NewObject(this); + Player = NewObject(this); + ConsumeEntityTimer = NewObject(this); + SpawnFoodTimer = NewObject(this); + Circle = NewObject(this); + /**/ + + /** Initialization */ + LoggedOutEntity->PostInitialize(); + MoveAllPlayersTimer->PostInitialize(); + CircleDecayTimer->PostInitialize(); + Entity->PostInitialize(); + Food->PostInitialize(); + CircleRecombineTimer->PostInitialize(); + Config->PostInitialize(); + LoggedOutPlayer->PostInitialize(); + LoggedOutCircle->PostInitialize(); + Player->PostInitialize(); + ConsumeEntityTimer->PostInitialize(); + SpawnFoodTimer->PostInitialize(); + Circle->PostInitialize(); + /**/ +} + +void USetReducerFlags::CircleDecay(ECallReducerFlags Flag) +{ + FlagMap.Add("CircleDecay", Flag); +} +void USetReducerFlags::CircleRecombine(ECallReducerFlags Flag) +{ + FlagMap.Add("CircleRecombine", Flag); +} +void USetReducerFlags::Connect(ECallReducerFlags Flag) +{ + FlagMap.Add("Connect", Flag); +} +void USetReducerFlags::ConsumeEntity(ECallReducerFlags Flag) +{ + FlagMap.Add("ConsumeEntity", Flag); +} +void USetReducerFlags::Disconnect(ECallReducerFlags Flag) +{ + FlagMap.Add("Disconnect", Flag); +} +void USetReducerFlags::EnterGame(ECallReducerFlags Flag) +{ + FlagMap.Add("EnterGame", Flag); +} +void USetReducerFlags::MoveAllPlayers(ECallReducerFlags Flag) +{ + FlagMap.Add("MoveAllPlayers", Flag); +} +void USetReducerFlags::PlayerSplit(ECallReducerFlags Flag) +{ + FlagMap.Add("PlayerSplit", Flag); +} +void USetReducerFlags::Respawn(ECallReducerFlags Flag) +{ + FlagMap.Add("Respawn", Flag); +} +void USetReducerFlags::SpawnFood(ECallReducerFlags Flag) +{ + FlagMap.Add("SpawnFood", Flag); +} +void USetReducerFlags::Suicide(ECallReducerFlags Flag) +{ + FlagMap.Add("Suicide", Flag); +} +void USetReducerFlags::UpdatePlayerInput(ECallReducerFlags Flag) +{ + FlagMap.Add("UpdatePlayerInput", Flag); +} + +void URemoteReducers::CircleDecay(const FCircleDecayTimerType& Timer) +{ + if (!Conn) + { + UE_LOG(LogTemp, Error, TEXT("SpacetimeDB connection is null")); + return; + } + + Conn->CallReducerTyped(TEXT("circle_decay"), FCircleDecayArgs(Timer), SetCallReducerFlags); +} + +bool URemoteReducers::InvokeCircleDecay(const FReducerEventContext& Context, const UCircleDecayReducer* Args) +{ + if (!OnCircleDecay.IsBound()) + { + // Handle unhandled reducer error + if (InternalOnUnhandledReducerError.IsBound()) + { + // TODO: Check Context.Event.Status for Failed/OutOfEnergy cases + // For now, just broadcast any error + InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for CircleDecay")); + } + return false; + } + + OnCircleDecay.Broadcast(Context, Args->Timer); + return true; +} + +void URemoteReducers::CircleRecombine(const FCircleRecombineTimerType& Timer) +{ + if (!Conn) + { + UE_LOG(LogTemp, Error, TEXT("SpacetimeDB connection is null")); + return; + } + + Conn->CallReducerTyped(TEXT("circle_recombine"), FCircleRecombineArgs(Timer), SetCallReducerFlags); +} + +bool URemoteReducers::InvokeCircleRecombine(const FReducerEventContext& Context, const UCircleRecombineReducer* Args) +{ + if (!OnCircleRecombine.IsBound()) + { + // Handle unhandled reducer error + if (InternalOnUnhandledReducerError.IsBound()) + { + // TODO: Check Context.Event.Status for Failed/OutOfEnergy cases + // For now, just broadcast any error + InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for CircleRecombine")); + } + return false; + } + + OnCircleRecombine.Broadcast(Context, Args->Timer); + return true; +} + +void URemoteReducers::Connect() +{ + if (!Conn) + { + UE_LOG(LogTemp, Error, TEXT("SpacetimeDB connection is null")); + return; + } + + Conn->CallReducerTyped(TEXT("connect"), FConnectArgs(), SetCallReducerFlags); +} + +bool URemoteReducers::InvokeConnect(const FReducerEventContext& Context, const UConnectReducer* Args) +{ + if (!OnConnect.IsBound()) + { + // Handle unhandled reducer error + if (InternalOnUnhandledReducerError.IsBound()) + { + // TODO: Check Context.Event.Status for Failed/OutOfEnergy cases + // For now, just broadcast any error + InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for Connect")); + } + return false; + } + + OnConnect.Broadcast(Context); + return true; +} + +void URemoteReducers::ConsumeEntity(const FConsumeEntityTimerType& Request) +{ + if (!Conn) + { + UE_LOG(LogTemp, Error, TEXT("SpacetimeDB connection is null")); + return; + } + + Conn->CallReducerTyped(TEXT("consume_entity"), FConsumeEntityArgs(Request), SetCallReducerFlags); +} + +bool URemoteReducers::InvokeConsumeEntity(const FReducerEventContext& Context, const UConsumeEntityReducer* Args) +{ + if (!OnConsumeEntity.IsBound()) + { + // Handle unhandled reducer error + if (InternalOnUnhandledReducerError.IsBound()) + { + // TODO: Check Context.Event.Status for Failed/OutOfEnergy cases + // For now, just broadcast any error + InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for ConsumeEntity")); + } + return false; + } + + OnConsumeEntity.Broadcast(Context, Args->Request); + return true; +} + +void URemoteReducers::Disconnect() +{ + if (!Conn) + { + UE_LOG(LogTemp, Error, TEXT("SpacetimeDB connection is null")); + return; + } + + Conn->CallReducerTyped(TEXT("disconnect"), FDisconnectArgs(), SetCallReducerFlags); +} + +bool URemoteReducers::InvokeDisconnect(const FReducerEventContext& Context, const UDisconnectReducer* Args) +{ + if (!OnDisconnect.IsBound()) + { + // Handle unhandled reducer error + if (InternalOnUnhandledReducerError.IsBound()) + { + // TODO: Check Context.Event.Status for Failed/OutOfEnergy cases + // For now, just broadcast any error + InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for Disconnect")); + } + return false; + } + + OnDisconnect.Broadcast(Context); + return true; +} + +void URemoteReducers::EnterGame(const FString& Name) +{ + if (!Conn) + { + UE_LOG(LogTemp, Error, TEXT("SpacetimeDB connection is null")); + return; + } + + Conn->CallReducerTyped(TEXT("enter_game"), FEnterGameArgs(Name), SetCallReducerFlags); +} + +bool URemoteReducers::InvokeEnterGame(const FReducerEventContext& Context, const UEnterGameReducer* Args) +{ + if (!OnEnterGame.IsBound()) + { + // Handle unhandled reducer error + if (InternalOnUnhandledReducerError.IsBound()) + { + // TODO: Check Context.Event.Status for Failed/OutOfEnergy cases + // For now, just broadcast any error + InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for EnterGame")); + } + return false; + } + + OnEnterGame.Broadcast(Context, Args->Name); + return true; +} + +void URemoteReducers::MoveAllPlayers(const FMoveAllPlayersTimerType& Timer) +{ + if (!Conn) + { + UE_LOG(LogTemp, Error, TEXT("SpacetimeDB connection is null")); + return; + } + + Conn->CallReducerTyped(TEXT("move_all_players"), FMoveAllPlayersArgs(Timer), SetCallReducerFlags); +} + +bool URemoteReducers::InvokeMoveAllPlayers(const FReducerEventContext& Context, const UMoveAllPlayersReducer* Args) +{ + if (!OnMoveAllPlayers.IsBound()) + { + // Handle unhandled reducer error + if (InternalOnUnhandledReducerError.IsBound()) + { + // TODO: Check Context.Event.Status for Failed/OutOfEnergy cases + // For now, just broadcast any error + InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for MoveAllPlayers")); + } + return false; + } + + OnMoveAllPlayers.Broadcast(Context, Args->Timer); + return true; +} + +void URemoteReducers::PlayerSplit() +{ + if (!Conn) + { + UE_LOG(LogTemp, Error, TEXT("SpacetimeDB connection is null")); + return; + } + + Conn->CallReducerTyped(TEXT("player_split"), FPlayerSplitArgs(), SetCallReducerFlags); +} + +bool URemoteReducers::InvokePlayerSplit(const FReducerEventContext& Context, const UPlayerSplitReducer* Args) +{ + if (!OnPlayerSplit.IsBound()) + { + // Handle unhandled reducer error + if (InternalOnUnhandledReducerError.IsBound()) + { + // TODO: Check Context.Event.Status for Failed/OutOfEnergy cases + // For now, just broadcast any error + InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for PlayerSplit")); + } + return false; + } + + OnPlayerSplit.Broadcast(Context); + return true; +} + +void URemoteReducers::Respawn() +{ + if (!Conn) + { + UE_LOG(LogTemp, Error, TEXT("SpacetimeDB connection is null")); + return; + } + + Conn->CallReducerTyped(TEXT("respawn"), FRespawnArgs(), SetCallReducerFlags); +} + +bool URemoteReducers::InvokeRespawn(const FReducerEventContext& Context, const URespawnReducer* Args) +{ + if (!OnRespawn.IsBound()) + { + // Handle unhandled reducer error + if (InternalOnUnhandledReducerError.IsBound()) + { + // TODO: Check Context.Event.Status for Failed/OutOfEnergy cases + // For now, just broadcast any error + InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for Respawn")); + } + return false; + } + + OnRespawn.Broadcast(Context); + return true; +} + +void URemoteReducers::SpawnFood(const FSpawnFoodTimerType& Timer) +{ + if (!Conn) + { + UE_LOG(LogTemp, Error, TEXT("SpacetimeDB connection is null")); + return; + } + + Conn->CallReducerTyped(TEXT("spawn_food"), FSpawnFoodArgs(Timer), SetCallReducerFlags); +} + +bool URemoteReducers::InvokeSpawnFood(const FReducerEventContext& Context, const USpawnFoodReducer* Args) +{ + if (!OnSpawnFood.IsBound()) + { + // Handle unhandled reducer error + if (InternalOnUnhandledReducerError.IsBound()) + { + // TODO: Check Context.Event.Status for Failed/OutOfEnergy cases + // For now, just broadcast any error + InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for SpawnFood")); + } + return false; + } + + OnSpawnFood.Broadcast(Context, Args->Timer); + return true; +} + +void URemoteReducers::Suicide() +{ + if (!Conn) + { + UE_LOG(LogTemp, Error, TEXT("SpacetimeDB connection is null")); + return; + } + + Conn->CallReducerTyped(TEXT("suicide"), FSuicideArgs(), SetCallReducerFlags); +} + +bool URemoteReducers::InvokeSuicide(const FReducerEventContext& Context, const USuicideReducer* Args) +{ + if (!OnSuicide.IsBound()) + { + // Handle unhandled reducer error + if (InternalOnUnhandledReducerError.IsBound()) + { + // TODO: Check Context.Event.Status for Failed/OutOfEnergy cases + // For now, just broadcast any error + InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for Suicide")); + } + return false; + } + + OnSuicide.Broadcast(Context); + return true; +} + +void URemoteReducers::UpdatePlayerInput(const FDbVector2Type& Direction) +{ + if (!Conn) + { + UE_LOG(LogTemp, Error, TEXT("SpacetimeDB connection is null")); + return; + } + + Conn->CallReducerTyped(TEXT("update_player_input"), FUpdatePlayerInputArgs(Direction), SetCallReducerFlags); +} + +bool URemoteReducers::InvokeUpdatePlayerInput(const FReducerEventContext& Context, const UUpdatePlayerInputReducer* Args) +{ + if (!OnUpdatePlayerInput.IsBound()) + { + // Handle unhandled reducer error + if (InternalOnUnhandledReducerError.IsBound()) + { + // TODO: Check Context.Event.Status for Failed/OutOfEnergy cases + // For now, just broadcast any error + InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for UpdatePlayerInput")); + } + return false; + } + + OnUpdatePlayerInput.Broadcast(Context, Args->Direction); + return true; +} + +void UDbConnection::PostInitProperties() +{ + Super::PostInitProperties(); + + // Connect OnUnhandledReducerError to Reducers.InternalOnUnhandledReducerError + if (Reducers) + { + Reducers->InternalOnUnhandledReducerError.AddDynamic(this, &UDbConnection::OnUnhandledReducerErrorHandler); + } +} + +UFUNCTION() +void UDbConnection::OnUnhandledReducerErrorHandler(const FReducerEventContext& Context, const FString& Error) +{ + if (OnUnhandledReducerError.IsBound()) + { + OnUnhandledReducerError.Broadcast(Context, Error); + } +} + +void UDbConnection::ReducerEvent(const FReducerEvent& Event) +{ + if (!Reducers) { return; } + + FReducer DecodedReducer = DecodeReducer(Event); + + FClientUnrealReducerEvent ReducerEvent; + ReducerEvent.CallerConnectionId = Event.CallerConnectionId; + ReducerEvent.CallerIdentity = Event.CallerIdentity; + ReducerEvent.EnergyConsumed = Event.EnergyConsumed; + ReducerEvent.Status = Event.Status; + ReducerEvent.Timestamp = Event.Timestamp; + ReducerEvent.Reducer = DecodedReducer; + + FReducerEventContext Context(this, ReducerEvent); + + // Use hardcoded string matching for reducer dispatching + const FString& ReducerName = Event.ReducerCall.ReducerName; + + if (ReducerName == TEXT("circle_decay")) + { + FCircleDecayArgs Args = ReducerEvent.Reducer.GetAsCircleDecay(); + UCircleDecayReducer* Reducer = NewObject(); + Reducer->Timer = Args.Timer; + Reducers->InvokeCircleDecay(Context, Reducer); + return; + } + if (ReducerName == TEXT("circle_recombine")) + { + FCircleRecombineArgs Args = ReducerEvent.Reducer.GetAsCircleRecombine(); + UCircleRecombineReducer* Reducer = NewObject(); + Reducer->Timer = Args.Timer; + Reducers->InvokeCircleRecombine(Context, Reducer); + return; + } + if (ReducerName == TEXT("connect")) + { + FConnectArgs Args = ReducerEvent.Reducer.GetAsConnect(); + UConnectReducer* Reducer = NewObject(); + Reducers->InvokeConnect(Context, Reducer); + return; + } + if (ReducerName == TEXT("consume_entity")) + { + FConsumeEntityArgs Args = ReducerEvent.Reducer.GetAsConsumeEntity(); + UConsumeEntityReducer* Reducer = NewObject(); + Reducer->Request = Args.Request; + Reducers->InvokeConsumeEntity(Context, Reducer); + return; + } + if (ReducerName == TEXT("disconnect")) + { + FDisconnectArgs Args = ReducerEvent.Reducer.GetAsDisconnect(); + UDisconnectReducer* Reducer = NewObject(); + Reducers->InvokeDisconnect(Context, Reducer); + return; + } + if (ReducerName == TEXT("enter_game")) + { + FEnterGameArgs Args = ReducerEvent.Reducer.GetAsEnterGame(); + UEnterGameReducer* Reducer = NewObject(); + Reducer->Name = Args.Name; + Reducers->InvokeEnterGame(Context, Reducer); + return; + } + if (ReducerName == TEXT("move_all_players")) + { + FMoveAllPlayersArgs Args = ReducerEvent.Reducer.GetAsMoveAllPlayers(); + UMoveAllPlayersReducer* Reducer = NewObject(); + Reducer->Timer = Args.Timer; + Reducers->InvokeMoveAllPlayers(Context, Reducer); + return; + } + if (ReducerName == TEXT("player_split")) + { + FPlayerSplitArgs Args = ReducerEvent.Reducer.GetAsPlayerSplit(); + UPlayerSplitReducer* Reducer = NewObject(); + Reducers->InvokePlayerSplit(Context, Reducer); + return; + } + if (ReducerName == TEXT("respawn")) + { + FRespawnArgs Args = ReducerEvent.Reducer.GetAsRespawn(); + URespawnReducer* Reducer = NewObject(); + Reducers->InvokeRespawn(Context, Reducer); + return; + } + if (ReducerName == TEXT("spawn_food")) + { + FSpawnFoodArgs Args = ReducerEvent.Reducer.GetAsSpawnFood(); + USpawnFoodReducer* Reducer = NewObject(); + Reducer->Timer = Args.Timer; + Reducers->InvokeSpawnFood(Context, Reducer); + return; + } + if (ReducerName == TEXT("suicide")) + { + FSuicideArgs Args = ReducerEvent.Reducer.GetAsSuicide(); + USuicideReducer* Reducer = NewObject(); + Reducers->InvokeSuicide(Context, Reducer); + return; + } + if (ReducerName == TEXT("update_player_input")) + { + FUpdatePlayerInputArgs Args = ReducerEvent.Reducer.GetAsUpdatePlayerInput(); + UUpdatePlayerInputReducer* Reducer = NewObject(); + Reducer->Direction = Args.Direction; + Reducers->InvokeUpdatePlayerInput(Context, Reducer); + return; + } + + UE_LOG(LogTemp, Warning, TEXT("Unknown reducer: %s"), *ReducerName); +} + +void UDbConnection::ReducerEventFailed(const FReducerEvent& Event, const FString ErrorMessage) +{ + if (!Reducers) { return; } + + FClientUnrealReducerEvent ReducerEvent; + ReducerEvent.CallerConnectionId = Event.CallerConnectionId; + ReducerEvent.CallerIdentity = Event.CallerIdentity; + ReducerEvent.EnergyConsumed = Event.EnergyConsumed; + ReducerEvent.Status = Event.Status; + ReducerEvent.Timestamp = Event.Timestamp; + + FReducerEventContext Context(this, ReducerEvent); + + if (Reducers->InternalOnUnhandledReducerError.IsBound()) + { + Reducers->InternalOnUnhandledReducerError.Broadcast(Context, ErrorMessage); + } +} + +UDbConnectionBuilder* UDbConnection::Builder() +{ + return NewObject(); +} +// Added for creating subscriptions +USubscriptionBuilder* UDbConnection::SubscriptionBuilder() +{ + USubscriptionBuilder* Builder = NewObject(this); + Builder->Conn = this; + return Builder; +} +USubscriptionBuilder* USubscriptionBuilder::OnApplied(FOnSubscriptionApplied Callback) +{ + OnAppliedDelegateInternal = Callback; + return this; +} +USubscriptionBuilder* USubscriptionBuilder::OnError(FOnSubscriptionError Callback) +{ + OnErrorDelegateInternal = Callback; + return this; +} +USubscriptionHandle* USubscriptionBuilder::Subscribe(const TArray& SQL) +{ + USubscriptionHandle* Handle = NewObject(); + + // Store user callbacks on the handle + Handle->Conn = Conn; + Handle->OnAppliedDelegate = OnAppliedDelegateInternal; + Handle->OnErrorDelegate = OnErrorDelegateInternal; + + // Bind forwarding functions that will convert base contexts + FSubscriptionEventDelegate BaseApplied; + BaseApplied.BindUFunction(Handle, TEXT("ForwardOnApplied")); + OnAppliedBase(BaseApplied); + + FSubscriptionErrorDelegate BaseError; + BaseError.BindUFunction(Handle, TEXT("ForwardOnError")); + OnErrorBase(BaseError); + + SubscribeBase(SQL, Handle); + if (Conn) + { + Conn->StartSubscription(Handle); + } + return Handle; +} +USubscriptionHandle* USubscriptionBuilder::SubscribeToAllTables() +{ + return Subscribe({ "SELECT * FROM * " }); +} + +USubscriptionHandle::USubscriptionHandle(UDbConnection* InConn) +{ + Conn = InConn; +} + +void USubscriptionHandle::ForwardOnApplied(const FSubscriptionEventContextBase& BaseCtx) +{ + if (OnAppliedDelegate.IsBound()) + { + FSubscriptionEventContext Ctx(Conn); + OnAppliedDelegate.Execute(Ctx); + } +} + +void USubscriptionHandle::ForwardOnError(const FErrorContextBase& BaseCtx) +{ + if (OnErrorDelegate.IsBound()) + { + FErrorContext Ctx(Conn, BaseCtx.Error); + OnErrorDelegate.Execute(Ctx); + } +} + + +// Cast from parent to child class +UDbConnectionBuilder* UDbConnectionBuilder::WithUri(const FString& InUri) +{ + return Cast(WithUriBase(InUri)); +} +UDbConnectionBuilder* UDbConnectionBuilder::WithModuleName(const FString& InName) +{ + return Cast(WithModuleNameBase(InName)); +} +UDbConnectionBuilder* UDbConnectionBuilder::WithToken(const FString& InToken) +{ + return Cast(WithTokenBase(InToken)); +} +UDbConnectionBuilder* UDbConnectionBuilder::WithCompression(const ESpacetimeDBCompression& InCompression) +{ + return Cast(WithCompressionBase(InCompression)); +} +UDbConnectionBuilder* UDbConnectionBuilder::OnConnect(FOnConnectDelegate Callback) +{ + OnConnectDelegateInternal = Callback; + return this; +} +UDbConnectionBuilder* UDbConnectionBuilder::OnConnectError(FOnConnectErrorDelegate Callback) +{ + return Cast(OnConnectErrorBase(Callback)); +} +UDbConnectionBuilder* UDbConnectionBuilder::OnDisconnect(FOnDisconnectDelegate Callback) +{ + OnDisconnectDelegateInternal = Callback; + return this; +} +UDbConnection* UDbConnectionBuilder::Build() +{ + UDbConnection* Connection = NewObject(); + + // Store delegates on the connection for later use + Connection->OnConnectDelegate = OnConnectDelegateInternal; + Connection->OnDisconnectDelegate = OnDisconnectDelegateInternal; + + // Wrap delegates so the base builder can bind them + FOnConnectBaseDelegate BaseConnect; + BaseConnect.BindUFunction(Connection, TEXT("ForwardOnConnect")); + Connection->SetOnConnectDelegate(BaseConnect); + OnConnectBase(BaseConnect); + + FOnDisconnectBaseDelegate BaseDisconnect; + BaseDisconnect.BindUFunction(Connection, TEXT("ForwardOnDisconnect")); + Connection->SetOnDisconnectDelegate(BaseDisconnect); + OnDisconnectBase(BaseDisconnect); + + return Cast(BuildConnection(Connection)); +} +void UDbConnection::ForwardOnConnect(UDbConnectionBase* BaseConnection, FSpacetimeDBIdentity InIdentity, const FString& InToken) +{ + if (OnConnectDelegate.IsBound()) + { + OnConnectDelegate.Execute(this, Identity, Token); + } +} +void UDbConnection::ForwardOnDisconnect(UDbConnectionBase* BaseConnection, const FString& Error) +{ + if (OnDisconnectDelegate.IsBound()) + { + OnDisconnectDelegate.Execute(this, Error); + } +} + + +void UDbConnection::DbUpdate(const FDatabaseUpdateType& Update, const FSpacetimeDBEvent& Event) +{ + FClientUnrealEvent BaseEvent; + BaseEvent.Tag = Event.Tag; + + switch (Event.Tag) + { + case ESpacetimeDBEventTag::Reducer: + { + FReducerEvent ReducerEvent = Event.GetAsReducer(); + FReducer Reducer = DecodeReducer(ReducerEvent); + BaseEvent = FClientUnrealEvent::Reducer(Reducer); + break; + } + + case ESpacetimeDBEventTag::SubscribeApplied: + BaseEvent = FClientUnrealEvent::SubscribeApplied(Event.GetAsSubscribeApplied()); + break; + + case ESpacetimeDBEventTag::UnsubscribeApplied: + BaseEvent = FClientUnrealEvent::UnsubscribeApplied(Event.GetAsUnsubscribeApplied()); + break; + + case ESpacetimeDBEventTag::Disconnected: + BaseEvent = FClientUnrealEvent::Disconnected(Event.GetAsDisconnected()); + break; + + case ESpacetimeDBEventTag::SubscribeError: + BaseEvent = FClientUnrealEvent::SubscribeError(Event.GetAsSubscribeError()); + break; + + case ESpacetimeDBEventTag::UnknownTransaction: + BaseEvent = FClientUnrealEvent::UnknownTransaction(Event.GetAsUnknownTransaction()); + break; + + default: + break; + } + + FEventContext Context(this, BaseEvent); + // Populate typed reducer args for convenience in table handlers + + ApplyRegisteredTableUpdates(Update, &Context); +} + diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/CircleDecayTimerTable.g.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/CircleDecayTimerTable.g.cpp new file mode 100644 index 000000000..3f843a102 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/CircleDecayTimerTable.g.cpp @@ -0,0 +1,47 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#include "ModuleBindings/Tables/CircleDecayTimerTable.g.h" +#include "DBCache/UniqueIndex.h" +#include "DBCache/BTreeUniqueIndex.h" +#include "DBCache/ClientCache.h" +#include "DBCache/TableCache.h" + +void UCircleDecayTimerTable::PostInitialize() +{ + /** Client cache init and setting up indexes*/ + Data = MakeShared>(); + + TSharedPtr> CircleDecayTimerTable = Data->GetOrAdd(TableName); + CircleDecayTimerTable->AddUniqueConstraint("scheduled_id", [](const FCircleDecayTimerType& Row) -> const uint64& { + return Row.ScheduledId; }); + + ScheduledId = NewObject(this); + ScheduledId->SetCache(CircleDecayTimerTable); + + /***/ +} + +FTableAppliedDiff UCircleDecayTimerTable::Update(TArray> InsertsRef, TArray> DeletesRef) +{ + FTableAppliedDiff Diff = BaseUpdate(InsertsRef, DeletesRef, Data, TableName); + + Diff.DeriveUpdatesByPrimaryKey( + [](const FCircleDecayTimerType& Row) + { + return Row.ScheduledId; + } + ); + + return Diff; +} + +int32 UCircleDecayTimerTable::Count() const +{ + return GetRowCountFromTable(Data, TableName); +} + +TArray UCircleDecayTimerTable::Iter() const +{ + return GetAllRowsFromTable(Data, TableName); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/CircleRecombineTimerTable.g.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/CircleRecombineTimerTable.g.cpp new file mode 100644 index 000000000..c79d81391 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/CircleRecombineTimerTable.g.cpp @@ -0,0 +1,47 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#include "ModuleBindings/Tables/CircleRecombineTimerTable.g.h" +#include "DBCache/UniqueIndex.h" +#include "DBCache/BTreeUniqueIndex.h" +#include "DBCache/ClientCache.h" +#include "DBCache/TableCache.h" + +void UCircleRecombineTimerTable::PostInitialize() +{ + /** Client cache init and setting up indexes*/ + Data = MakeShared>(); + + TSharedPtr> CircleRecombineTimerTable = Data->GetOrAdd(TableName); + CircleRecombineTimerTable->AddUniqueConstraint("scheduled_id", [](const FCircleRecombineTimerType& Row) -> const uint64& { + return Row.ScheduledId; }); + + ScheduledId = NewObject(this); + ScheduledId->SetCache(CircleRecombineTimerTable); + + /***/ +} + +FTableAppliedDiff UCircleRecombineTimerTable::Update(TArray> InsertsRef, TArray> DeletesRef) +{ + FTableAppliedDiff Diff = BaseUpdate(InsertsRef, DeletesRef, Data, TableName); + + Diff.DeriveUpdatesByPrimaryKey( + [](const FCircleRecombineTimerType& Row) + { + return Row.ScheduledId; + } + ); + + return Diff; +} + +int32 UCircleRecombineTimerTable::Count() const +{ + return GetRowCountFromTable(Data, TableName); +} + +TArray UCircleRecombineTimerTable::Iter() const +{ + return GetAllRowsFromTable(Data, TableName); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/CircleTable.g.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/CircleTable.g.cpp new file mode 100644 index 000000000..58f27292a --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/CircleTable.g.cpp @@ -0,0 +1,60 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#include "ModuleBindings/Tables/CircleTable.g.h" +#include "DBCache/UniqueIndex.h" +#include "DBCache/BTreeUniqueIndex.h" +#include "DBCache/ClientCache.h" +#include "DBCache/TableCache.h" + +void UCircleTable::PostInitialize() +{ + /** Client cache init and setting up indexes*/ + Data = MakeShared>(); + + TSharedPtr> CircleTable = Data->GetOrAdd(TableName); + CircleTable->AddUniqueConstraint("entity_id", [](const FCircleType& Row) -> const uint32& { + return Row.EntityId; }); + + EntityId = NewObject(this); + EntityId->SetCache(CircleTable); + + // Register a new multi-key B-Tree index named "player_id" on the CircleTable. + CircleTable->AddMultiKeyBTreeIndex>( + TEXT("player_id"), + [](const FCircleType& Row) + { + // This tuple is stored in the B-Tree index for fast composite key lookups. + return MakeTuple(Row.PlayerId); + } + ); + + PlayerId = NewObject(this); + PlayerId->SetCache(CircleTable); + + /***/ +} + +FTableAppliedDiff UCircleTable::Update(TArray> InsertsRef, TArray> DeletesRef) +{ + FTableAppliedDiff Diff = BaseUpdate(InsertsRef, DeletesRef, Data, TableName); + + Diff.DeriveUpdatesByPrimaryKey( + [](const FCircleType& Row) + { + return Row.EntityId; + } + ); + + return Diff; +} + +int32 UCircleTable::Count() const +{ + return GetRowCountFromTable(Data, TableName); +} + +TArray UCircleTable::Iter() const +{ + return GetAllRowsFromTable(Data, TableName); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/ConfigTable.g.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/ConfigTable.g.cpp new file mode 100644 index 000000000..95b589d1e --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/ConfigTable.g.cpp @@ -0,0 +1,47 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#include "ModuleBindings/Tables/ConfigTable.g.h" +#include "DBCache/UniqueIndex.h" +#include "DBCache/BTreeUniqueIndex.h" +#include "DBCache/ClientCache.h" +#include "DBCache/TableCache.h" + +void UConfigTable::PostInitialize() +{ + /** Client cache init and setting up indexes*/ + Data = MakeShared>(); + + TSharedPtr> ConfigTable = Data->GetOrAdd(TableName); + ConfigTable->AddUniqueConstraint("id", [](const FConfigType& Row) -> const uint32& { + return Row.Id; }); + + Id = NewObject(this); + Id->SetCache(ConfigTable); + + /***/ +} + +FTableAppliedDiff UConfigTable::Update(TArray> InsertsRef, TArray> DeletesRef) +{ + FTableAppliedDiff Diff = BaseUpdate(InsertsRef, DeletesRef, Data, TableName); + + Diff.DeriveUpdatesByPrimaryKey( + [](const FConfigType& Row) + { + return Row.Id; + } + ); + + return Diff; +} + +int32 UConfigTable::Count() const +{ + return GetRowCountFromTable(Data, TableName); +} + +TArray UConfigTable::Iter() const +{ + return GetAllRowsFromTable(Data, TableName); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/ConsumeEntityTimerTable.g.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/ConsumeEntityTimerTable.g.cpp new file mode 100644 index 000000000..398c24bdb --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/ConsumeEntityTimerTable.g.cpp @@ -0,0 +1,47 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#include "ModuleBindings/Tables/ConsumeEntityTimerTable.g.h" +#include "DBCache/UniqueIndex.h" +#include "DBCache/BTreeUniqueIndex.h" +#include "DBCache/ClientCache.h" +#include "DBCache/TableCache.h" + +void UConsumeEntityTimerTable::PostInitialize() +{ + /** Client cache init and setting up indexes*/ + Data = MakeShared>(); + + TSharedPtr> ConsumeEntityTimerTable = Data->GetOrAdd(TableName); + ConsumeEntityTimerTable->AddUniqueConstraint("scheduled_id", [](const FConsumeEntityTimerType& Row) -> const uint64& { + return Row.ScheduledId; }); + + ScheduledId = NewObject(this); + ScheduledId->SetCache(ConsumeEntityTimerTable); + + /***/ +} + +FTableAppliedDiff UConsumeEntityTimerTable::Update(TArray> InsertsRef, TArray> DeletesRef) +{ + FTableAppliedDiff Diff = BaseUpdate(InsertsRef, DeletesRef, Data, TableName); + + Diff.DeriveUpdatesByPrimaryKey( + [](const FConsumeEntityTimerType& Row) + { + return Row.ScheduledId; + } + ); + + return Diff; +} + +int32 UConsumeEntityTimerTable::Count() const +{ + return GetRowCountFromTable(Data, TableName); +} + +TArray UConsumeEntityTimerTable::Iter() const +{ + return GetAllRowsFromTable(Data, TableName); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/EntityTable.g.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/EntityTable.g.cpp new file mode 100644 index 000000000..ca6f02875 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/EntityTable.g.cpp @@ -0,0 +1,47 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#include "ModuleBindings/Tables/EntityTable.g.h" +#include "DBCache/UniqueIndex.h" +#include "DBCache/BTreeUniqueIndex.h" +#include "DBCache/ClientCache.h" +#include "DBCache/TableCache.h" + +void UEntityTable::PostInitialize() +{ + /** Client cache init and setting up indexes*/ + Data = MakeShared>(); + + TSharedPtr> EntityTable = Data->GetOrAdd(TableName); + EntityTable->AddUniqueConstraint("entity_id", [](const FEntityType& Row) -> const uint32& { + return Row.EntityId; }); + + EntityId = NewObject(this); + EntityId->SetCache(EntityTable); + + /***/ +} + +FTableAppliedDiff UEntityTable::Update(TArray> InsertsRef, TArray> DeletesRef) +{ + FTableAppliedDiff Diff = BaseUpdate(InsertsRef, DeletesRef, Data, TableName); + + Diff.DeriveUpdatesByPrimaryKey( + [](const FEntityType& Row) + { + return Row.EntityId; + } + ); + + return Diff; +} + +int32 UEntityTable::Count() const +{ + return GetRowCountFromTable(Data, TableName); +} + +TArray UEntityTable::Iter() const +{ + return GetAllRowsFromTable(Data, TableName); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/FoodTable.g.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/FoodTable.g.cpp new file mode 100644 index 000000000..6d439a84e --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/FoodTable.g.cpp @@ -0,0 +1,47 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#include "ModuleBindings/Tables/FoodTable.g.h" +#include "DBCache/UniqueIndex.h" +#include "DBCache/BTreeUniqueIndex.h" +#include "DBCache/ClientCache.h" +#include "DBCache/TableCache.h" + +void UFoodTable::PostInitialize() +{ + /** Client cache init and setting up indexes*/ + Data = MakeShared>(); + + TSharedPtr> FoodTable = Data->GetOrAdd(TableName); + FoodTable->AddUniqueConstraint("entity_id", [](const FFoodType& Row) -> const uint32& { + return Row.EntityId; }); + + EntityId = NewObject(this); + EntityId->SetCache(FoodTable); + + /***/ +} + +FTableAppliedDiff UFoodTable::Update(TArray> InsertsRef, TArray> DeletesRef) +{ + FTableAppliedDiff Diff = BaseUpdate(InsertsRef, DeletesRef, Data, TableName); + + Diff.DeriveUpdatesByPrimaryKey( + [](const FFoodType& Row) + { + return Row.EntityId; + } + ); + + return Diff; +} + +int32 UFoodTable::Count() const +{ + return GetRowCountFromTable(Data, TableName); +} + +TArray UFoodTable::Iter() const +{ + return GetAllRowsFromTable(Data, TableName); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/MoveAllPlayersTimerTable.g.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/MoveAllPlayersTimerTable.g.cpp new file mode 100644 index 000000000..ed8b6a847 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/MoveAllPlayersTimerTable.g.cpp @@ -0,0 +1,47 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#include "ModuleBindings/Tables/MoveAllPlayersTimerTable.g.h" +#include "DBCache/UniqueIndex.h" +#include "DBCache/BTreeUniqueIndex.h" +#include "DBCache/ClientCache.h" +#include "DBCache/TableCache.h" + +void UMoveAllPlayersTimerTable::PostInitialize() +{ + /** Client cache init and setting up indexes*/ + Data = MakeShared>(); + + TSharedPtr> MoveAllPlayersTimerTable = Data->GetOrAdd(TableName); + MoveAllPlayersTimerTable->AddUniqueConstraint("scheduled_id", [](const FMoveAllPlayersTimerType& Row) -> const uint64& { + return Row.ScheduledId; }); + + ScheduledId = NewObject(this); + ScheduledId->SetCache(MoveAllPlayersTimerTable); + + /***/ +} + +FTableAppliedDiff UMoveAllPlayersTimerTable::Update(TArray> InsertsRef, TArray> DeletesRef) +{ + FTableAppliedDiff Diff = BaseUpdate(InsertsRef, DeletesRef, Data, TableName); + + Diff.DeriveUpdatesByPrimaryKey( + [](const FMoveAllPlayersTimerType& Row) + { + return Row.ScheduledId; + } + ); + + return Diff; +} + +int32 UMoveAllPlayersTimerTable::Count() const +{ + return GetRowCountFromTable(Data, TableName); +} + +TArray UMoveAllPlayersTimerTable::Iter() const +{ + return GetAllRowsFromTable(Data, TableName); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/PlayerTable.g.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/PlayerTable.g.cpp new file mode 100644 index 000000000..2a2c29896 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/PlayerTable.g.cpp @@ -0,0 +1,52 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#include "ModuleBindings/Tables/PlayerTable.g.h" +#include "DBCache/UniqueIndex.h" +#include "DBCache/BTreeUniqueIndex.h" +#include "DBCache/ClientCache.h" +#include "DBCache/TableCache.h" + +void UPlayerTable::PostInitialize() +{ + /** Client cache init and setting up indexes*/ + Data = MakeShared>(); + + TSharedPtr> PlayerTable = Data->GetOrAdd(TableName); + PlayerTable->AddUniqueConstraint("identity", [](const FPlayerType& Row) -> const FSpacetimeDBIdentity& { + return Row.Identity; }); + PlayerTable->AddUniqueConstraint("player_id", [](const FPlayerType& Row) -> const uint32& { + return Row.PlayerId; }); + + Identity = NewObject(this); + Identity->SetCache(PlayerTable); + + PlayerId = NewObject(this); + PlayerId->SetCache(PlayerTable); + + /***/ +} + +FTableAppliedDiff UPlayerTable::Update(TArray> InsertsRef, TArray> DeletesRef) +{ + FTableAppliedDiff Diff = BaseUpdate(InsertsRef, DeletesRef, Data, TableName); + + Diff.DeriveUpdatesByPrimaryKey( + [](const FPlayerType& Row) + { + return Row.Identity; + } + ); + + return Diff; +} + +int32 UPlayerTable::Count() const +{ + return GetRowCountFromTable(Data, TableName); +} + +TArray UPlayerTable::Iter() const +{ + return GetAllRowsFromTable(Data, TableName); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/SpawnFoodTimerTable.g.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/SpawnFoodTimerTable.g.cpp new file mode 100644 index 000000000..f7a36cff7 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/SpawnFoodTimerTable.g.cpp @@ -0,0 +1,47 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#include "ModuleBindings/Tables/SpawnFoodTimerTable.g.h" +#include "DBCache/UniqueIndex.h" +#include "DBCache/BTreeUniqueIndex.h" +#include "DBCache/ClientCache.h" +#include "DBCache/TableCache.h" + +void USpawnFoodTimerTable::PostInitialize() +{ + /** Client cache init and setting up indexes*/ + Data = MakeShared>(); + + TSharedPtr> SpawnFoodTimerTable = Data->GetOrAdd(TableName); + SpawnFoodTimerTable->AddUniqueConstraint("scheduled_id", [](const FSpawnFoodTimerType& Row) -> const uint64& { + return Row.ScheduledId; }); + + ScheduledId = NewObject(this); + ScheduledId->SetCache(SpawnFoodTimerTable); + + /***/ +} + +FTableAppliedDiff USpawnFoodTimerTable::Update(TArray> InsertsRef, TArray> DeletesRef) +{ + FTableAppliedDiff Diff = BaseUpdate(InsertsRef, DeletesRef, Data, TableName); + + Diff.DeriveUpdatesByPrimaryKey( + [](const FSpawnFoodTimerType& Row) + { + return Row.ScheduledId; + } + ); + + return Diff; +} + +int32 USpawnFoodTimerTable::Count() const +{ + return GetRowCountFromTable(Data, TableName); +} + +TArray USpawnFoodTimerTable::Iter() const +{ + return GetAllRowsFromTable(Data, TableName); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/PlayerPawn.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/PlayerPawn.cpp new file mode 100644 index 000000000..4326e026d --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/PlayerPawn.cpp @@ -0,0 +1,216 @@ +#include "PlayerPawn.h" + +#include "BlackholioPlayerController.h" +#include "Circle.h" +#include "GameManager.h" +#include "Camera/CameraComponent.h" +#include "GameFramework/SpringArmComponent.h" +#include "Kismet/GameplayStatics.h" +#include "ModuleBindings/Tables/EntityTable.g.h" +#include "ModuleBindings/Tables/PlayerTable.g.h" +#include "ModuleBindings/Types/EntityType.g.h" +#include "ModuleBindings/Types/PlayerType.g.h" + +APlayerPawn::APlayerPawn() +{ + PrimaryActorTick.bCanEverTick = true; + USceneComponent* DefaultRoot = CreateDefaultSubobject(TEXT("Root")); + RootComponent = DefaultRoot; + + SpringArm = CreateDefaultSubobject(TEXT("SpringArm")); + SpringArm->SetupAttachment(RootComponent); + SpringArm->SetRelativeRotation(FRotator(0.f, -90.f, 0.f)); + SpringArm->TargetArmLength = 15000.f; + SpringArm->bUsePawnControlRotation = false; + SpringArm->bDoCollisionTest = false; + + Camera = CreateDefaultSubobject(TEXT("Camera")); + Camera->SetupAttachment(SpringArm); + Camera->SetProjectionMode(ECameraProjectionMode::Perspective); + Camera->FieldOfView = 90.f; // top-down 90° FOV +} + +void APlayerPawn::Initialize(FPlayerType Player) +{ + PlayerId = Player.PlayerId; + + if (Player.Identity == AGameManager::Instance->LocalIdentity) + { + bIsLocalPlayer = true; + if (APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0)) + { + PC->Possess(this); + } + } +} + +FString APlayerPawn::GetUsername() const +{ + FPlayerType Player = AGameManager::Instance->Conn->Db->Player->PlayerId->Find(PlayerId); + return Player.Name; +} + +void APlayerPawn::OnCircleSpawned(ACircle* Circle) +{ + if (ensure(Circle)) + { + OwnedCircles.AddUnique(Circle); + } +} + +void APlayerPawn::OnCircleDeleted(ACircle* Circle) +{ + if (Circle) + { + for (int32 i = OwnedCircles.Num() - 1; i >= 0; --i) + { + if (!OwnedCircles[i].IsValid() || OwnedCircles[i].Get() == Circle) + { + OwnedCircles.RemoveAt(i); + } + } + } + else + { + for (int32 i = OwnedCircles.Num() - 1; i >= 0; --i) + { + if (!OwnedCircles[i].IsValid()) + { + OwnedCircles.RemoveAt(i); + } + } + } + + if (OwnedCircles.Num() == 0 && bIsLocalPlayer) + { + if (ABlackholioPlayerController* PlayerController = Cast(UGameplayStatics::GetPlayerController(GetWorld(), 0))) + { + PlayerController->ShowDeathScreen(); + } + } +} + +void APlayerPawn::Split() +{ + AGameManager::Instance->Conn->Reducers->PlayerSplit(); +} + +void APlayerPawn::Suicide() +{ + AGameManager::Instance->Conn->Reducers->Suicide(); +} + +uint32 APlayerPawn::TotalMass() const +{ + uint32 Total = 0; + for (int32 Index = 0; Index < OwnedCircles.Num(); ++Index) + { + const TWeakObjectPtr& Weak = OwnedCircles[Index]; + if (!Weak.IsValid()) continue; + + const ACircle* Circle = Weak.Get(); + const uint32 Id = Circle->EntityId; + + const FEntityType Entity = AGameManager::Instance->Conn->Db->Entity->EntityId->Find(Id); + Total += Entity.Mass; + } + return Total; +} + +FVector APlayerPawn::CenterOfMass() const +{ + if (OwnedCircles.Num() == 0) + { + return FVector::ZeroVector; + } + + FVector WeightedPosition = FVector::ZeroVector; // Σ (pos * mass) + double TotalMass = 0.0; // Σ mass + + const int32 Count = OwnedCircles.Num(); + + for (int32 Index = 0; Index < Count; ++Index) + { + const TWeakObjectPtr& Weak = OwnedCircles[Index]; + if (!Weak.IsValid()) continue; + + const ACircle* Circle = Weak.Get(); + const uint32 Id = Circle->EntityId; + + const FEntityType Entity = AGameManager::Instance->Conn->Db->Entity->EntityId->Find(Id); + const double Mass = Entity.Mass; + + const FVector Loc = Circle->GetActorLocation(); + + if (Mass <= 0.0) continue; + + WeightedPosition += (Loc * Mass); + TotalMass += Mass; + } + + const FVector ActorLoc = GetActorLocation(); + + FVector Result = FVector::ZeroVector; + if (TotalMass > 0.0) + { + const FVector CalculatedCenter = WeightedPosition / TotalMass; + // Keep Z at the player's Z, per your original intent + Result = FVector(CalculatedCenter.X, ActorLoc.Y, CalculatedCenter.Z); + } + + return Result; +} + +void APlayerPawn::Destroyed() +{ + Super::Destroyed(); + for (TWeakObjectPtr& CirclePtr : OwnedCircles) + { + if (ACircle* Circle = CirclePtr.Get()) + { + Circle->Destroy(); + } + } + OwnedCircles.Empty(); +} + +void APlayerPawn::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + if (!bIsLocalPlayer || OwnedCircles.Num() == 0) + return; + + const FVector ArenaCenter(0.f, 1.f, 0.f); + FVector Target = ArenaCenter; + if (AGameManager::Instance->IsConnected()) + { + const FVector CoM = CenterOfMass(); + if (!CoM.ContainsNaN()) + { + Target = { CoM.X, 1.f, CoM.Z }; + } + } + const FVector NewLoc = FMath::VInterpTo(GetActorLocation(), Target, DeltaTime, 120.f); + SetActorLocation(NewLoc); + + + const float FOVDeg = 90.f; // vertical FOV + const float HalfAngleRad = FMath::DegreesToRadians(FOVDeg * 0.5f); + const float TanHalf = FMath::Tan(HalfAngleRad); // = 1.0 at 90° + + const float sizeUnity = + BaseSize + + FMath::Min(MaxMassBonus, TotalMass() / MassToSizeDivisor) + + FMath::Min(FMath::Max(OwnedCircles.Num() - 1, 0), 1) * SplitBonus; + + // If you want ~15000 cm at BaseSize (e.g., 50), use scale = 3.f: + const float Scale = 3.f; + + // Distance that matches Unity size at 90° FOV: + const float targetArmCm = (Scale * sizeUnity * 100.f) / TanHalf; // TanHalf==1 at 90° + + SpringArm->TargetArmLength = FMath::FInterpTo( + SpringArm->TargetArmLength, targetArmCm, DeltaTime, /*ZoomSpeed*/2.f); +} + diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/BlackholioGameMode.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/BlackholioGameMode.h new file mode 100644 index 000000000..38a61e127 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/BlackholioGameMode.h @@ -0,0 +1,15 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/GameModeBase.h" +#include "BlackholioGameMode.generated.h" + +/** + * + */ +UCLASS() +class CLIENT_UNREAL_API ABlackholioGameMode : public AGameModeBase +{ + GENERATED_BODY() + +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/BlackholioPlayerController.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/BlackholioPlayerController.h new file mode 100644 index 000000000..3e54aaebf --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/BlackholioPlayerController.h @@ -0,0 +1,78 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/PlayerController.h" +#include "InputActionValue.h" +#include "BlackholioPlayerController.generated.h" + +class APlayerPawn; +class UInputAction; +class UInputMappingContext; +class URespawnWidget; +class UUsernameChooserWidget; +class ULeaderboardWidget; + +UCLASS() +class CLIENT_UNREAL_API ABlackholioPlayerController : public APlayerController +{ + GENERATED_BODY() + +public: + ABlackholioPlayerController(); + + UFUNCTION(BlueprintCallable, Category = "BH|Functions") + void ShowDeathScreen(); + +protected: + virtual void BeginPlay() override; + virtual void Tick(float DeltaSeconds) override; + virtual void OnPossess(APawn* InPawn) override; + virtual void SetupInputComponent() override; + FVector2D ComputeDesiredDirection() const; + + UPROPERTY(EditDefaultsOnly, Category="BH|Config") + TSubclassOf UsernameChooserClass; + + UPROPERTY(EditDefaultsOnly, Category="BH|Config") + TSubclassOf RespawnClass; + + UPROPERTY(EditDefaultsOnly, Category="BH|Config") + TSubclassOf LeaderboardClass; +private: + UFUNCTION() + void EnsureMappingContext() const; + + UPROPERTY() + TObjectPtr LocalPlayer; + + UPROPERTY() + float SendUpdatesFrequency = 0.0333f; + float LastMovementSendTimestamp = 0.f; + bool bShowedUsernameChooser = false; + + TOptional LockInputPosition; + + UPROPERTY(EditDefaultsOnly, Category="BH|Input") + TObjectPtr PlayerMappingContext = nullptr; + + UPROPERTY(EditDefaultsOnly, Category="BH|Input") + TObjectPtr SplitAction = nullptr; + + UPROPERTY(EditDefaultsOnly, Category="BH|Input") + TObjectPtr SuicideAction = nullptr; + + UPROPERTY(EditDefaultsOnly, Category="BH|Input") + TObjectPtr ToggleInputLockAction = nullptr; + + UPROPERTY() + TObjectPtr RespawnWidget = nullptr; + UPROPERTY() + TObjectPtr UsernameChooserWidget = nullptr; + UPROPERTY() + TObjectPtr LeaderboardWidget = nullptr; + + // Input handlers (Enhanced Input) + void OnSplitTriggered(const FInputActionValue& Value); + void OnSuicideTriggered(const FInputActionValue& Value); + void OnToggleInputLockTriggered(const FInputActionValue& Value); +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/Circle.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Circle.h new file mode 100644 index 000000000..0042f6f03 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Circle.h @@ -0,0 +1,38 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Entity.h" +#include "Circle.generated.h" + +struct FCircleType; +class APlayerPawn; + +UCLASS() +class CLIENT_UNREAL_API ACircle : public AEntity +{ + GENERATED_BODY() + +public: + ACircle(); + + uint32 OwnerPlayerId = 0; + UPROPERTY(BlueprintReadOnly, Category="BH|Circle") + FString Username; + + void Spawn(const FCircleType& Circle, APlayerPawn* InOwner); + virtual void OnDelete(const FEventContext& Context) override; + + UFUNCTION(BlueprintCallable, Category="BH|Circle") + void SetUsername(const FString& InUsername); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnUsernameChanged, const FString&, NewUsername); + UPROPERTY(BlueprintAssignable, Category="BH|Circle") + FOnUsernameChanged OnUsernameChanged; + +protected: + UPROPERTY(EditDefaultsOnly, Category="BH|Circle") + TArray ColorPalette; + +private: + TWeakObjectPtr Owner; +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/DbVector2.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/DbVector2.h new file mode 100644 index 000000000..482c3546d --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/DbVector2.h @@ -0,0 +1,29 @@ +#pragma once + +#include "ModuleBindings/Types/DbVector2Type.g.h" + +FORCEINLINE FDbVector2Type ToDbVector(const FVector2D& Vec) +{ + FDbVector2Type Out; + Out.X = Vec.X; + Out.Y = Vec.Y; + return Out; +} + +FORCEINLINE FDbVector2Type ToDbVector(const FVector& Vec) +{ + FDbVector2Type Out; + Out.X = Vec.X; + Out.Y = Vec.Y; + return Out; +} + +FORCEINLINE FVector2D ToFVector2D(const FDbVector2Type& Vec) +{ + return FVector2D(Vec.X * 100.f, Vec.Y * 100.f); +} + +FORCEINLINE FVector ToFVector(const FDbVector2Type& Vec, float Z = 0.f) +{ + return FVector(Vec.X * 100.f, Z, Vec.Y * 100.f); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/Entity.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Entity.h new file mode 100644 index 000000000..4fe24883e --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Entity.h @@ -0,0 +1,53 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "Entity.generated.h" + +struct FEventContext; +struct FEntityType; + +UCLASS() +class CLIENT_UNREAL_API AEntity : public AActor +{ + GENERATED_BODY() + +public: + AEntity(); + +protected: + UPROPERTY(EditDefaultsOnly, Category="BH|Entity") + float LerpTime = 0.f; + UPROPERTY(EditDefaultsOnly, Category="BH|Entity") + float LerpDuration = 0.10f; + UPROPERTY(EditDefaultsOnly, Category="BH|Entity") + float DespawnTime = 0.2f; + + FVector LerpStartPosition = FVector::ZeroVector; + FVector LerpTargetPosition = FVector::ZeroVector; + float TargetScale = 1.f; + +public: + uint32 EntityId = 0; + virtual void Tick(float DeltaTime) override; + void ConsumeDespawn(float DeltaTime); + + void Spawn(uint32 InEntityId); + virtual void OnEntityUpdated(const FEntityType& NewVal); + virtual void OnDelete(const FEventContext& Context); + bool ConsumeDelete(const FEventContext& Context); + + void SetColor(const FLinearColor& Color) const; + + static float MassToRadius(uint32 Mass) { return FMath::Sqrt(static_cast(Mass)); } + static float MassToDiameter(uint32 Mass) { return MassToRadius(Mass) * 2.f; } + +private: + UPROPERTY() + TObjectPtr ConsumingEntity = nullptr; + bool bIsDespawning = false; + float DespawnElapsed = 0.f; + FVector ConsumeStartPosition = FVector::ZeroVector; + FVector ConsumeStartScale = FVector::ZeroVector; + +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/Food.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Food.h new file mode 100644 index 000000000..80efd423e --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Food.h @@ -0,0 +1,20 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Entity.h" +#include "Food.generated.h" + +struct FFoodType; + +UCLASS() +class CLIENT_UNREAL_API AFood : public AEntity +{ + GENERATED_BODY() + +public: + AFood(); + void Spawn(const FFoodType& FoodEntity); +protected: + UPROPERTY(EditDefaultsOnly, Category="BH|Food") + TArray ColorPalette; +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/GameManager.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/GameManager.h new file mode 100644 index 000000000..bdb0347f9 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/GameManager.h @@ -0,0 +1,126 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "ModuleBindings/SpacetimeDBClient.g.h" +#include "GameManager.generated.h" + +class UDbConnection; +class AEntity; +class ACircle; +class AFood; +class APlayerPawn; + +UCLASS() +class CLIENT_UNREAL_API AGameManager : public AActor +{ + GENERATED_BODY() + +public: + AGameManager(); + static AGameManager* Instance; + + UPROPERTY(EditAnywhere, Category="BH|Connection") + FString ServerUri = TEXT("127.0.0.1:3000"); + UPROPERTY(EditAnywhere, Category="BH|Connection") + FString ModuleName = TEXT("blackholio-unreal"); + UPROPERTY(EditAnywhere, Category="BH|Connection") + FString TokenFilePath = TEXT(".spacetime_blackholio"); + + UPROPERTY(EditAnywhere, Category="BH|Classes") + TSubclassOf CircleClass; + UPROPERTY(EditAnywhere, Category="BH|Classes") + TSubclassOf FoodClass; + UPROPERTY(EditAnywhere, Category="BH|Classes") + TSubclassOf PlayerClass; + + UPROPERTY(BlueprintReadOnly, Category="BH|Connection") + FSpacetimeDBIdentity LocalIdentity; + UPROPERTY(BlueprintReadOnly, Category="BH|Connection") + UDbConnection* Conn = nullptr; + UPROPERTY() + FString PlayerNameAtStart = TEXT(""); + UPROPERTY() + bool bSubscriptionsApplied = false; + + UFUNCTION(BlueprintPure, Category="BH|Connection") + bool IsConnected() const + { + return Conn != nullptr && Conn->IsActive(); + } + + UFUNCTION(BlueprintCallable, Category="BH|Connection") + void Disconnect() + { + if (Conn != nullptr) + { + Conn->Disconnect(); + Conn = nullptr; + } + } + + UFUNCTION() + AEntity* GetEntity(uint32 EntityId) const; + + UFUNCTION() + TMap> GetPlayerMap() const { return PlayerMap; }; + +protected: + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + +public: + virtual void Tick(float DeltaTime) override; + +private: + UFUNCTION() + void HandleConnect(UDbConnection* InConn, FSpacetimeDBIdentity Identity, const FString& Token); + UFUNCTION() + void HandleConnectError(const FString& Error); + UFUNCTION() + void HandleDisconnect(UDbConnection* InConn, const FString& Error); + UFUNCTION() + void HandleSubscriptionApplied(FSubscriptionEventContext& Context); + + /* Border */ + UFUNCTION() + void SetupArena(uint64 WorldSizeMeters); + UFUNCTION() + void CreateBorderCube(const FVector2f Position, const FVector2f Size) const; + + UPROPERTY(VisibleAnywhere, Category="Arena") + UInstancedStaticMeshComponent* BorderISM; + UPROPERTY(EditDefaultsOnly, Category="Arena", meta=(ClampMin="1.0")) + float BorderThickness = 50.0f; + UPROPERTY(EditDefaultsOnly, Category="Arena", meta=(ClampMin="1.0")) + float BorderHeight = 100.0f; + UPROPERTY(EditDefaultsOnly, Category="Arena") + UMaterialInterface* BorderMaterial = nullptr; + UPROPERTY(EditDefaultsOnly, Category="Arena") + UStaticMesh* CubeMesh = nullptr; // defaults as /Engine/BasicShapes/Cube.Cube + /* Border */ + + /* Data Bindings */ + UPROPERTY() + TMap> EntityMap; + UPROPERTY() + TMap> PlayerMap; + + APlayerPawn* SpawnOrGetPlayer(const FPlayerType& PlayerRow); + ACircle* SpawnCircle(const FCircleType& CircleRow); + AFood* SpawnFood(const FFoodType& Food); + + UFUNCTION() + void OnCircleInsert(const FEventContext& Context, const FCircleType& NewRow); + UFUNCTION() + void OnEntityUpdate(const FEventContext& Context, const FEntityType& OldRow, const FEntityType& NewRow); + UFUNCTION() + void OnEntityDelete(const FEventContext& Context, const FEntityType& RemovedRow); + UFUNCTION() + void OnFoodInsert(const FEventContext& Context, const FFoodType& NewFood); + UFUNCTION() + void OnPlayerInsert(const FEventContext& Context, const FPlayerType& NewRow); + UFUNCTION() + void OnPlayerDelete(const FEventContext& Context, const FPlayerType& RemovedRow); + /* Data Bindings */ +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/LeaderboardRowWidget.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/LeaderboardRowWidget.h new file mode 100644 index 000000000..ff73bacbf --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/LeaderboardRowWidget.h @@ -0,0 +1,23 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "LeaderboardRowWidget.generated.h" + +class UTextBlock; + +UCLASS() +class CLIENT_UNREAL_API ULeaderboardRowWidget : public UUserWidget +{ + GENERATED_BODY() +public: + UFUNCTION(BlueprintCallable) + void SetData(const FString& Username, int32 Mass); + +protected: + UPROPERTY(meta=(BindWidget)) + TObjectPtr UsernameText = nullptr; + + UPROPERTY(meta=(BindWidget)) + TObjectPtr MassText = nullptr; +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/LeaderboardWidget.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/LeaderboardWidget.h new file mode 100644 index 000000000..b34857fbe --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/LeaderboardWidget.h @@ -0,0 +1,52 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "LeaderboardWidget.generated.h" + +class UVerticalBox; +class ULeaderboardRowWidget; +class APlayerPawn; + +USTRUCT() +struct FLeaderboardEntry +{ + GENERATED_BODY() + + UPROPERTY() FString Username; + UPROPERTY() int32 Mass = 0; + UPROPERTY() TWeakObjectPtr Pawn; +}; + +UCLASS() +class CLIENT_UNREAL_API ULeaderboardWidget : public UUserWidget +{ + GENERATED_BODY() + +public: + virtual void NativeConstruct() override; + virtual void NativeDestruct() override; + +protected: + UPROPERTY(meta=(BindWidget)) + UVerticalBox* Root = nullptr; + + UPROPERTY(EditDefaultsOnly, Category="BH|Leaderboard") + TSubclassOf RowClass; + + UPROPERTY(EditDefaultsOnly, Category="BH|Leaderboard") + int32 MaxRowCount = 10; + + UPROPERTY(EditDefaultsOnly, Category="BH|Leaderboard") + float UpdatePeriod = 0.25f; + +private: + UPROPERTY(Transient) + TArray Rows; + + FTimerHandle UpdateTimer; + + void BuildRowPool(); + void CollectPlayers(TArray& Out) const; + void UpdateLeaderboard(); +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/ParallaxBackground.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/ParallaxBackground.h new file mode 100644 index 000000000..f6712db51 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/ParallaxBackground.h @@ -0,0 +1,23 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "ParallaxBackground.generated.h" + +class UPaperSpriteComponent; + +UCLASS() +class CLIENT_UNREAL_API AParallaxBackground : public AActor +{ + GENERATED_BODY() + +public: + AParallaxBackground(); + + virtual void Tick(float DeltaTime) override; + + UPROPERTY(EditAnywhere, Category="BH|Parallax") + float Multiplier = -0.02f; + UPROPERTY(EditAnywhere, Category="BH|Parallax") + float FixedY; +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/RespawnWidget.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/RespawnWidget.h new file mode 100644 index 000000000..2a7ad78f0 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/RespawnWidget.h @@ -0,0 +1,22 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "RespawnWidget.generated.h" + +class UButton; + +UCLASS() +class CLIENT_UNREAL_API URespawnWidget : public UUserWidget +{ + GENERATED_BODY() +protected: + UPROPERTY(meta=(BindWidget)) + TObjectPtr RespawnButton; + + virtual void NativeConstruct() override; + +private: + UFUNCTION() + void OnRespawnPressed(); +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/UsernameChooserWidget.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/UsernameChooserWidget.h new file mode 100644 index 000000000..ea4f44f4c --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Gameplay/UsernameChooserWidget.h @@ -0,0 +1,32 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "UsernameChooserWidget.generated.h" + +class UEditableTextBox; +class UButton; + +UCLASS() +class CLIENT_UNREAL_API UUsernameChooserWidget : public UUserWidget +{ + GENERATED_BODY() + +public: + UFUNCTION() + void Hide(); +protected: + UPROPERTY(meta=(BindWidget)) + TObjectPtr UsernameInputField; + UPROPERTY(meta=(BindWidget)) + TObjectPtr PlayButton; + + virtual void NativeConstruct() override; + virtual void NativeDestruct() override; + +private: + UFUNCTION() + void OnPlayPressed(); + UFUNCTION() + void HandlePlayerInserted(const FEventContext& Context, const FPlayerType& NewPlayer); +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/ReducerBase.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/ReducerBase.g.h new file mode 100644 index 000000000..a6af9d836 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/ReducerBase.g.h @@ -0,0 +1,18 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "ReducerBase.g.generated.h" + +// Abstract Reducer base class +UCLASS(Abstract, BlueprintType) +class CLIENT_UNREAL_API UReducerBase : public UObject +{ + GENERATED_BODY() + +public: + virtual ~UReducerBase() = default; +}; + diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/CircleDecay.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/CircleDecay.g.h new file mode 100644 index 000000000..d2f2106c5 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/CircleDecay.g.h @@ -0,0 +1,54 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "ModuleBindings/ReducerBase.g.h" +#include "ModuleBindings/Types/CircleDecayTimerType.g.h" +#include "CircleDecay.g.generated.h" + +// Reducer arguments struct for CircleDecay +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FCircleDecayArgs +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category="SpacetimeDB") + FCircleDecayTimerType Timer; + + FCircleDecayArgs() = default; + + FCircleDecayArgs(const FCircleDecayTimerType& InTimer) + : Timer(InTimer) + {} + + + FORCEINLINE bool operator==(const FCircleDecayArgs& Other) const + { + return Timer == Other.Timer; + } + FORCEINLINE bool operator!=(const FCircleDecayArgs& Other) const + { + return !(*this == Other); + } +}; + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_STRUCT(FCircleDecayArgs, Timer); +} + +// Reducer class for internal dispatching +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UCircleDecayReducer : public UReducerBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + FCircleDecayTimerType Timer; + +}; + + diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/CircleRecombine.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/CircleRecombine.g.h new file mode 100644 index 000000000..411925d33 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/CircleRecombine.g.h @@ -0,0 +1,54 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "ModuleBindings/ReducerBase.g.h" +#include "ModuleBindings/Types/CircleRecombineTimerType.g.h" +#include "CircleRecombine.g.generated.h" + +// Reducer arguments struct for CircleRecombine +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FCircleRecombineArgs +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category="SpacetimeDB") + FCircleRecombineTimerType Timer; + + FCircleRecombineArgs() = default; + + FCircleRecombineArgs(const FCircleRecombineTimerType& InTimer) + : Timer(InTimer) + {} + + + FORCEINLINE bool operator==(const FCircleRecombineArgs& Other) const + { + return Timer == Other.Timer; + } + FORCEINLINE bool operator!=(const FCircleRecombineArgs& Other) const + { + return !(*this == Other); + } +}; + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_STRUCT(FCircleRecombineArgs, Timer); +} + +// Reducer class for internal dispatching +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UCircleRecombineReducer : public UReducerBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + FCircleRecombineTimerType Timer; + +}; + + diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Connect.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Connect.g.h new file mode 100644 index 000000000..3f4aa4d29 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Connect.g.h @@ -0,0 +1,43 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "ModuleBindings/ReducerBase.g.h" +#include "Connect.g.generated.h" + +// Reducer arguments struct for Connect +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FConnectArgs +{ + GENERATED_BODY() + + FConnectArgs() = default; + + + FORCEINLINE bool operator==(const FConnectArgs& Other) const + { + return true; + } + FORCEINLINE bool operator!=(const FConnectArgs& Other) const + { + return !(*this == Other); + } +}; + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_STRUCT_EMPTY(FConnectArgs); +} + +// Reducer class for internal dispatching +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UConnectReducer : public UReducerBase +{ + GENERATED_BODY() + +public: +}; + + diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/ConsumeEntity.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/ConsumeEntity.g.h new file mode 100644 index 000000000..c51fb1909 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/ConsumeEntity.g.h @@ -0,0 +1,54 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "ModuleBindings/ReducerBase.g.h" +#include "ModuleBindings/Types/ConsumeEntityTimerType.g.h" +#include "ConsumeEntity.g.generated.h" + +// Reducer arguments struct for ConsumeEntity +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FConsumeEntityArgs +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category="SpacetimeDB") + FConsumeEntityTimerType Request; + + FConsumeEntityArgs() = default; + + FConsumeEntityArgs(const FConsumeEntityTimerType& InRequest) + : Request(InRequest) + {} + + + FORCEINLINE bool operator==(const FConsumeEntityArgs& Other) const + { + return Request == Other.Request; + } + FORCEINLINE bool operator!=(const FConsumeEntityArgs& Other) const + { + return !(*this == Other); + } +}; + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_STRUCT(FConsumeEntityArgs, Request); +} + +// Reducer class for internal dispatching +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UConsumeEntityReducer : public UReducerBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + FConsumeEntityTimerType Request; + +}; + + diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Disconnect.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Disconnect.g.h new file mode 100644 index 000000000..1149cdb40 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Disconnect.g.h @@ -0,0 +1,43 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "ModuleBindings/ReducerBase.g.h" +#include "Disconnect.g.generated.h" + +// Reducer arguments struct for Disconnect +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FDisconnectArgs +{ + GENERATED_BODY() + + FDisconnectArgs() = default; + + + FORCEINLINE bool operator==(const FDisconnectArgs& Other) const + { + return true; + } + FORCEINLINE bool operator!=(const FDisconnectArgs& Other) const + { + return !(*this == Other); + } +}; + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_STRUCT_EMPTY(FDisconnectArgs); +} + +// Reducer class for internal dispatching +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UDisconnectReducer : public UReducerBase +{ + GENERATED_BODY() + +public: +}; + + diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/EnterGame.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/EnterGame.g.h new file mode 100644 index 000000000..c595900b1 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/EnterGame.g.h @@ -0,0 +1,53 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "ModuleBindings/ReducerBase.g.h" +#include "EnterGame.g.generated.h" + +// Reducer arguments struct for EnterGame +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FEnterGameArgs +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category="SpacetimeDB") + FString Name; + + FEnterGameArgs() = default; + + FEnterGameArgs(const FString& InName) + : Name(InName) + {} + + + FORCEINLINE bool operator==(const FEnterGameArgs& Other) const + { + return Name == Other.Name; + } + FORCEINLINE bool operator!=(const FEnterGameArgs& Other) const + { + return !(*this == Other); + } +}; + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_STRUCT(FEnterGameArgs, Name); +} + +// Reducer class for internal dispatching +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UEnterGameReducer : public UReducerBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + FString Name; + +}; + + diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/MoveAllPlayers.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/MoveAllPlayers.g.h new file mode 100644 index 000000000..421c680f6 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/MoveAllPlayers.g.h @@ -0,0 +1,54 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "ModuleBindings/ReducerBase.g.h" +#include "ModuleBindings/Types/MoveAllPlayersTimerType.g.h" +#include "MoveAllPlayers.g.generated.h" + +// Reducer arguments struct for MoveAllPlayers +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FMoveAllPlayersArgs +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category="SpacetimeDB") + FMoveAllPlayersTimerType Timer; + + FMoveAllPlayersArgs() = default; + + FMoveAllPlayersArgs(const FMoveAllPlayersTimerType& InTimer) + : Timer(InTimer) + {} + + + FORCEINLINE bool operator==(const FMoveAllPlayersArgs& Other) const + { + return Timer == Other.Timer; + } + FORCEINLINE bool operator!=(const FMoveAllPlayersArgs& Other) const + { + return !(*this == Other); + } +}; + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_STRUCT(FMoveAllPlayersArgs, Timer); +} + +// Reducer class for internal dispatching +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UMoveAllPlayersReducer : public UReducerBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + FMoveAllPlayersTimerType Timer; + +}; + + diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/PlayerSplit.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/PlayerSplit.g.h new file mode 100644 index 000000000..e58443536 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/PlayerSplit.g.h @@ -0,0 +1,43 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "ModuleBindings/ReducerBase.g.h" +#include "PlayerSplit.g.generated.h" + +// Reducer arguments struct for PlayerSplit +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FPlayerSplitArgs +{ + GENERATED_BODY() + + FPlayerSplitArgs() = default; + + + FORCEINLINE bool operator==(const FPlayerSplitArgs& Other) const + { + return true; + } + FORCEINLINE bool operator!=(const FPlayerSplitArgs& Other) const + { + return !(*this == Other); + } +}; + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_STRUCT_EMPTY(FPlayerSplitArgs); +} + +// Reducer class for internal dispatching +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UPlayerSplitReducer : public UReducerBase +{ + GENERATED_BODY() + +public: +}; + + diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Respawn.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Respawn.g.h new file mode 100644 index 000000000..7d5b5c28e --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Respawn.g.h @@ -0,0 +1,43 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "ModuleBindings/ReducerBase.g.h" +#include "Respawn.g.generated.h" + +// Reducer arguments struct for Respawn +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FRespawnArgs +{ + GENERATED_BODY() + + FRespawnArgs() = default; + + + FORCEINLINE bool operator==(const FRespawnArgs& Other) const + { + return true; + } + FORCEINLINE bool operator!=(const FRespawnArgs& Other) const + { + return !(*this == Other); + } +}; + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_STRUCT_EMPTY(FRespawnArgs); +} + +// Reducer class for internal dispatching +UCLASS(BlueprintType) +class CLIENT_UNREAL_API URespawnReducer : public UReducerBase +{ + GENERATED_BODY() + +public: +}; + + diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/SpawnFood.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/SpawnFood.g.h new file mode 100644 index 000000000..f3aaaa6c7 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/SpawnFood.g.h @@ -0,0 +1,54 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "ModuleBindings/ReducerBase.g.h" +#include "ModuleBindings/Types/SpawnFoodTimerType.g.h" +#include "SpawnFood.g.generated.h" + +// Reducer arguments struct for SpawnFood +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FSpawnFoodArgs +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category="SpacetimeDB") + FSpawnFoodTimerType Timer; + + FSpawnFoodArgs() = default; + + FSpawnFoodArgs(const FSpawnFoodTimerType& InTimer) + : Timer(InTimer) + {} + + + FORCEINLINE bool operator==(const FSpawnFoodArgs& Other) const + { + return Timer == Other.Timer; + } + FORCEINLINE bool operator!=(const FSpawnFoodArgs& Other) const + { + return !(*this == Other); + } +}; + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_STRUCT(FSpawnFoodArgs, Timer); +} + +// Reducer class for internal dispatching +UCLASS(BlueprintType) +class CLIENT_UNREAL_API USpawnFoodReducer : public UReducerBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + FSpawnFoodTimerType Timer; + +}; + + diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Suicide.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Suicide.g.h new file mode 100644 index 000000000..953c33bb6 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Suicide.g.h @@ -0,0 +1,43 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "ModuleBindings/ReducerBase.g.h" +#include "Suicide.g.generated.h" + +// Reducer arguments struct for Suicide +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FSuicideArgs +{ + GENERATED_BODY() + + FSuicideArgs() = default; + + + FORCEINLINE bool operator==(const FSuicideArgs& Other) const + { + return true; + } + FORCEINLINE bool operator!=(const FSuicideArgs& Other) const + { + return !(*this == Other); + } +}; + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_STRUCT_EMPTY(FSuicideArgs); +} + +// Reducer class for internal dispatching +UCLASS(BlueprintType) +class CLIENT_UNREAL_API USuicideReducer : public UReducerBase +{ + GENERATED_BODY() + +public: +}; + + diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/UpdatePlayerInput.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/UpdatePlayerInput.g.h new file mode 100644 index 000000000..39860c0dc --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/UpdatePlayerInput.g.h @@ -0,0 +1,54 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "ModuleBindings/ReducerBase.g.h" +#include "ModuleBindings/Types/DbVector2Type.g.h" +#include "UpdatePlayerInput.g.generated.h" + +// Reducer arguments struct for UpdatePlayerInput +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FUpdatePlayerInputArgs +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category="SpacetimeDB") + FDbVector2Type Direction; + + FUpdatePlayerInputArgs() = default; + + FUpdatePlayerInputArgs(const FDbVector2Type& InDirection) + : Direction(InDirection) + {} + + + FORCEINLINE bool operator==(const FUpdatePlayerInputArgs& Other) const + { + return Direction == Other.Direction; + } + FORCEINLINE bool operator!=(const FUpdatePlayerInputArgs& Other) const + { + return !(*this == Other); + } +}; + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_STRUCT(FUpdatePlayerInputArgs, Direction); +} + +// Reducer class for internal dispatching +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UUpdatePlayerInputReducer : public UReducerBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + FDbVector2Type Direction; + +}; + + diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/SpacetimeDBClient.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/SpacetimeDBClient.g.h new file mode 100644 index 000000000..44531b309 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/SpacetimeDBClient.g.h @@ -0,0 +1,1264 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.4.0 (commit dc59211c1453848981aeb2efce2249c9a07947b2). + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "Connection/Callback.h" +#include "Connection/DbConnectionBase.h" +#include "Connection/DbConnectionBuilder.h" +#include "Connection/SetReducerFlags.h" +#include "Connection/Subscription.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "ModuleBindings/ReducerBase.g.h" +#include "ModuleBindings/Reducers/CircleDecay.g.h" +#include "ModuleBindings/Reducers/CircleRecombine.g.h" +#include "ModuleBindings/Reducers/Connect.g.h" +#include "ModuleBindings/Reducers/ConsumeEntity.g.h" +#include "ModuleBindings/Reducers/Disconnect.g.h" +#include "ModuleBindings/Reducers/EnterGame.g.h" +#include "ModuleBindings/Reducers/MoveAllPlayers.g.h" +#include "ModuleBindings/Reducers/PlayerSplit.g.h" +#include "ModuleBindings/Reducers/Respawn.g.h" +#include "ModuleBindings/Reducers/SpawnFood.g.h" +#include "ModuleBindings/Reducers/Suicide.g.h" +#include "ModuleBindings/Reducers/UpdatePlayerInput.g.h" +#include "ModuleBindings/Types/CircleDecayTimerType.g.h" +#include "ModuleBindings/Types/CircleRecombineTimerType.g.h" +#include "ModuleBindings/Types/ConsumeEntityTimerType.g.h" +#include "ModuleBindings/Types/DbVector2Type.g.h" +#include "ModuleBindings/Types/MoveAllPlayersTimerType.g.h" +#include "ModuleBindings/Types/SpawnFoodTimerType.g.h" +#include "Types/Builtins.h" +#include "SpacetimeDBClient.g.generated.h" + +// Forward declarations +class UDbConnection; +class URemoteTables; +class URemoteReducers; +class USubscriptionBuilder; +class USubscriptionHandle; + +/** Forward declaration for tables */ +class UCircleTable; +class UCircleDecayTimerTable; +class UCircleRecombineTimerTable; +class UConfigTable; +class UConsumeEntityTimerTable; +class UEntityTable; +class UFoodTable; +class UCircleTable; +class UEntityTable; +class UPlayerTable; +class UMoveAllPlayersTimerTable; +class UPlayerTable; +class USpawnFoodTimerTable; +/***/ + +// Delegates using the generated connection type. These wrap the base +// delegates defined in the SDK so that projects can work directly with +// UDbConnection without manual casting in user code. +DECLARE_DYNAMIC_DELEGATE_ThreeParams( + FOnConnectDelegate, + UDbConnection*, Connection, + FSpacetimeDBIdentity, Identity, + const FString&, Token); + +DECLARE_DYNAMIC_DELEGATE_TwoParams( + FOnDisconnectDelegate, + UDbConnection*, Connection, + const FString&, Error); + + +// Context classes for event handling + +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FContextBase +{ + GENERATED_BODY() + + FContextBase() = default; + FContextBase(UDbConnection* InConn); + + UPROPERTY(BlueprintReadOnly, Category = "SpacetimeDB") + URemoteTables* Db; + + UPROPERTY(BlueprintReadOnly, Category = "SpacetimeDB") + URemoteReducers* Reducers; + + UPROPERTY(BlueprintReadOnly, Category = "SpacetimeDB") + USetReducerFlags* SetReducerFlags; + + bool IsActive() const; + void Disconnect(); + bool TryGetIdentity(FSpacetimeDBIdentity& OutIdentity) const; + FSpacetimeDBConnectionId GetConnectionId() const; + USubscriptionBuilder* SubscriptionBuilder(); + +protected: + UPROPERTY() + UDbConnection* Conn; + +}; + +UENUM(BlueprintType, Category = "SpacetimeDB") +enum class EReducerTag : uint8 +{ + CircleDecay, + CircleRecombine, + Connect, + ConsumeEntity, + Disconnect, + EnterGame, + MoveAllPlayers, + PlayerSplit, + Respawn, + SpawnFood, + Suicide, + UpdatePlayerInput +}; + +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FReducer +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly, Category = "SpacetimeDB") + EReducerTag Tag; + + TVariant Data; + + // Optional metadata + UPROPERTY(BlueprintReadOnly, Category = "SpacetimeDB") + FString ReducerName; + uint32 ReducerId = 0; + uint32 RequestId = 0; + + static FReducer CircleDecay(const FCircleDecayArgs& Value) + { + FReducer Out; + Out.Tag = EReducerTag::CircleDecay; + Out.Data.Set(Value); + Out.ReducerName = TEXT("circle_decay"); + return Out; + } + + FORCEINLINE bool IsCircleDecay() const { return Tag == EReducerTag::CircleDecay; } + FORCEINLINE FCircleDecayArgs GetAsCircleDecay() const + { + ensureMsgf(IsCircleDecay(), TEXT("Reducer does not hold CircleDecay!")); + return Data.Get(); + } + + static FReducer CircleRecombine(const FCircleRecombineArgs& Value) + { + FReducer Out; + Out.Tag = EReducerTag::CircleRecombine; + Out.Data.Set(Value); + Out.ReducerName = TEXT("circle_recombine"); + return Out; + } + + FORCEINLINE bool IsCircleRecombine() const { return Tag == EReducerTag::CircleRecombine; } + FORCEINLINE FCircleRecombineArgs GetAsCircleRecombine() const + { + ensureMsgf(IsCircleRecombine(), TEXT("Reducer does not hold CircleRecombine!")); + return Data.Get(); + } + + static FReducer Connect(const FConnectArgs& Value) + { + FReducer Out; + Out.Tag = EReducerTag::Connect; + Out.Data.Set(Value); + Out.ReducerName = TEXT("connect"); + return Out; + } + + FORCEINLINE bool IsConnect() const { return Tag == EReducerTag::Connect; } + FORCEINLINE FConnectArgs GetAsConnect() const + { + ensureMsgf(IsConnect(), TEXT("Reducer does not hold Connect!")); + return Data.Get(); + } + + static FReducer ConsumeEntity(const FConsumeEntityArgs& Value) + { + FReducer Out; + Out.Tag = EReducerTag::ConsumeEntity; + Out.Data.Set(Value); + Out.ReducerName = TEXT("consume_entity"); + return Out; + } + + FORCEINLINE bool IsConsumeEntity() const { return Tag == EReducerTag::ConsumeEntity; } + FORCEINLINE FConsumeEntityArgs GetAsConsumeEntity() const + { + ensureMsgf(IsConsumeEntity(), TEXT("Reducer does not hold ConsumeEntity!")); + return Data.Get(); + } + + static FReducer Disconnect(const FDisconnectArgs& Value) + { + FReducer Out; + Out.Tag = EReducerTag::Disconnect; + Out.Data.Set(Value); + Out.ReducerName = TEXT("disconnect"); + return Out; + } + + FORCEINLINE bool IsDisconnect() const { return Tag == EReducerTag::Disconnect; } + FORCEINLINE FDisconnectArgs GetAsDisconnect() const + { + ensureMsgf(IsDisconnect(), TEXT("Reducer does not hold Disconnect!")); + return Data.Get(); + } + + static FReducer EnterGame(const FEnterGameArgs& Value) + { + FReducer Out; + Out.Tag = EReducerTag::EnterGame; + Out.Data.Set(Value); + Out.ReducerName = TEXT("enter_game"); + return Out; + } + + FORCEINLINE bool IsEnterGame() const { return Tag == EReducerTag::EnterGame; } + FORCEINLINE FEnterGameArgs GetAsEnterGame() const + { + ensureMsgf(IsEnterGame(), TEXT("Reducer does not hold EnterGame!")); + return Data.Get(); + } + + static FReducer MoveAllPlayers(const FMoveAllPlayersArgs& Value) + { + FReducer Out; + Out.Tag = EReducerTag::MoveAllPlayers; + Out.Data.Set(Value); + Out.ReducerName = TEXT("move_all_players"); + return Out; + } + + FORCEINLINE bool IsMoveAllPlayers() const { return Tag == EReducerTag::MoveAllPlayers; } + FORCEINLINE FMoveAllPlayersArgs GetAsMoveAllPlayers() const + { + ensureMsgf(IsMoveAllPlayers(), TEXT("Reducer does not hold MoveAllPlayers!")); + return Data.Get(); + } + + static FReducer PlayerSplit(const FPlayerSplitArgs& Value) + { + FReducer Out; + Out.Tag = EReducerTag::PlayerSplit; + Out.Data.Set(Value); + Out.ReducerName = TEXT("player_split"); + return Out; + } + + FORCEINLINE bool IsPlayerSplit() const { return Tag == EReducerTag::PlayerSplit; } + FORCEINLINE FPlayerSplitArgs GetAsPlayerSplit() const + { + ensureMsgf(IsPlayerSplit(), TEXT("Reducer does not hold PlayerSplit!")); + return Data.Get(); + } + + static FReducer Respawn(const FRespawnArgs& Value) + { + FReducer Out; + Out.Tag = EReducerTag::Respawn; + Out.Data.Set(Value); + Out.ReducerName = TEXT("respawn"); + return Out; + } + + FORCEINLINE bool IsRespawn() const { return Tag == EReducerTag::Respawn; } + FORCEINLINE FRespawnArgs GetAsRespawn() const + { + ensureMsgf(IsRespawn(), TEXT("Reducer does not hold Respawn!")); + return Data.Get(); + } + + static FReducer SpawnFood(const FSpawnFoodArgs& Value) + { + FReducer Out; + Out.Tag = EReducerTag::SpawnFood; + Out.Data.Set(Value); + Out.ReducerName = TEXT("spawn_food"); + return Out; + } + + FORCEINLINE bool IsSpawnFood() const { return Tag == EReducerTag::SpawnFood; } + FORCEINLINE FSpawnFoodArgs GetAsSpawnFood() const + { + ensureMsgf(IsSpawnFood(), TEXT("Reducer does not hold SpawnFood!")); + return Data.Get(); + } + + static FReducer Suicide(const FSuicideArgs& Value) + { + FReducer Out; + Out.Tag = EReducerTag::Suicide; + Out.Data.Set(Value); + Out.ReducerName = TEXT("suicide"); + return Out; + } + + FORCEINLINE bool IsSuicide() const { return Tag == EReducerTag::Suicide; } + FORCEINLINE FSuicideArgs GetAsSuicide() const + { + ensureMsgf(IsSuicide(), TEXT("Reducer does not hold Suicide!")); + return Data.Get(); + } + + static FReducer UpdatePlayerInput(const FUpdatePlayerInputArgs& Value) + { + FReducer Out; + Out.Tag = EReducerTag::UpdatePlayerInput; + Out.Data.Set(Value); + Out.ReducerName = TEXT("update_player_input"); + return Out; + } + + FORCEINLINE bool IsUpdatePlayerInput() const { return Tag == EReducerTag::UpdatePlayerInput; } + FORCEINLINE FUpdatePlayerInputArgs GetAsUpdatePlayerInput() const + { + ensureMsgf(IsUpdatePlayerInput(), TEXT("Reducer does not hold UpdatePlayerInput!")); + return Data.Get(); + } + + FORCEINLINE bool operator==(const FReducer& Other) const + { + if (Tag != Other.Tag || ReducerId != Other.ReducerId || RequestId != Other.RequestId || ReducerName != Other.ReducerName) return false; + switch (Tag) + { + case EReducerTag::CircleDecay: + return GetAsCircleDecay() == Other.GetAsCircleDecay(); + case EReducerTag::CircleRecombine: + return GetAsCircleRecombine() == Other.GetAsCircleRecombine(); + case EReducerTag::Connect: + return GetAsConnect() == Other.GetAsConnect(); + case EReducerTag::ConsumeEntity: + return GetAsConsumeEntity() == Other.GetAsConsumeEntity(); + case EReducerTag::Disconnect: + return GetAsDisconnect() == Other.GetAsDisconnect(); + case EReducerTag::EnterGame: + return GetAsEnterGame() == Other.GetAsEnterGame(); + case EReducerTag::MoveAllPlayers: + return GetAsMoveAllPlayers() == Other.GetAsMoveAllPlayers(); + case EReducerTag::PlayerSplit: + return GetAsPlayerSplit() == Other.GetAsPlayerSplit(); + case EReducerTag::Respawn: + return GetAsRespawn() == Other.GetAsRespawn(); + case EReducerTag::SpawnFood: + return GetAsSpawnFood() == Other.GetAsSpawnFood(); + case EReducerTag::Suicide: + return GetAsSuicide() == Other.GetAsSuicide(); + case EReducerTag::UpdatePlayerInput: + return GetAsUpdatePlayerInput() == Other.GetAsUpdatePlayerInput(); + default: return false; + } + } + FORCEINLINE bool operator!=(const FReducer& Other) const { return !(*this == Other); } +}; + +UCLASS() +class CLIENT_UNREAL_API UReducerBpLib : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +private: + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") + static FReducer CircleDecay(const FCircleDecayArgs& Value) { + return FReducer::CircleDecay(Value); + } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static bool IsCircleDecay(const FReducer& Reducer) { return Reducer.IsCircleDecay(); } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static FCircleDecayArgs GetAsCircleDecay(const FReducer& Reducer) { + return Reducer.GetAsCircleDecay(); + } + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") + static FReducer CircleRecombine(const FCircleRecombineArgs& Value) { + return FReducer::CircleRecombine(Value); + } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static bool IsCircleRecombine(const FReducer& Reducer) { return Reducer.IsCircleRecombine(); } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static FCircleRecombineArgs GetAsCircleRecombine(const FReducer& Reducer) { + return Reducer.GetAsCircleRecombine(); + } + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") + static FReducer Connect(const FConnectArgs& Value) { + return FReducer::Connect(Value); + } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static bool IsConnect(const FReducer& Reducer) { return Reducer.IsConnect(); } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static FConnectArgs GetAsConnect(const FReducer& Reducer) { + return Reducer.GetAsConnect(); + } + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") + static FReducer ConsumeEntity(const FConsumeEntityArgs& Value) { + return FReducer::ConsumeEntity(Value); + } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static bool IsConsumeEntity(const FReducer& Reducer) { return Reducer.IsConsumeEntity(); } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static FConsumeEntityArgs GetAsConsumeEntity(const FReducer& Reducer) { + return Reducer.GetAsConsumeEntity(); + } + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") + static FReducer Disconnect(const FDisconnectArgs& Value) { + return FReducer::Disconnect(Value); + } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static bool IsDisconnect(const FReducer& Reducer) { return Reducer.IsDisconnect(); } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static FDisconnectArgs GetAsDisconnect(const FReducer& Reducer) { + return Reducer.GetAsDisconnect(); + } + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") + static FReducer EnterGame(const FEnterGameArgs& Value) { + return FReducer::EnterGame(Value); + } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static bool IsEnterGame(const FReducer& Reducer) { return Reducer.IsEnterGame(); } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static FEnterGameArgs GetAsEnterGame(const FReducer& Reducer) { + return Reducer.GetAsEnterGame(); + } + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") + static FReducer MoveAllPlayers(const FMoveAllPlayersArgs& Value) { + return FReducer::MoveAllPlayers(Value); + } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static bool IsMoveAllPlayers(const FReducer& Reducer) { return Reducer.IsMoveAllPlayers(); } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static FMoveAllPlayersArgs GetAsMoveAllPlayers(const FReducer& Reducer) { + return Reducer.GetAsMoveAllPlayers(); + } + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") + static FReducer PlayerSplit(const FPlayerSplitArgs& Value) { + return FReducer::PlayerSplit(Value); + } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static bool IsPlayerSplit(const FReducer& Reducer) { return Reducer.IsPlayerSplit(); } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static FPlayerSplitArgs GetAsPlayerSplit(const FReducer& Reducer) { + return Reducer.GetAsPlayerSplit(); + } + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") + static FReducer Respawn(const FRespawnArgs& Value) { + return FReducer::Respawn(Value); + } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static bool IsRespawn(const FReducer& Reducer) { return Reducer.IsRespawn(); } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static FRespawnArgs GetAsRespawn(const FReducer& Reducer) { + return Reducer.GetAsRespawn(); + } + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") + static FReducer SpawnFood(const FSpawnFoodArgs& Value) { + return FReducer::SpawnFood(Value); + } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static bool IsSpawnFood(const FReducer& Reducer) { return Reducer.IsSpawnFood(); } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static FSpawnFoodArgs GetAsSpawnFood(const FReducer& Reducer) { + return Reducer.GetAsSpawnFood(); + } + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") + static FReducer Suicide(const FSuicideArgs& Value) { + return FReducer::Suicide(Value); + } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static bool IsSuicide(const FReducer& Reducer) { return Reducer.IsSuicide(); } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static FSuicideArgs GetAsSuicide(const FReducer& Reducer) { + return Reducer.GetAsSuicide(); + } + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") + static FReducer UpdatePlayerInput(const FUpdatePlayerInputArgs& Value) { + return FReducer::UpdatePlayerInput(Value); + } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static bool IsUpdatePlayerInput(const FReducer& Reducer) { return Reducer.IsUpdatePlayerInput(); } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") + static FUpdatePlayerInputArgs GetAsUpdatePlayerInput(const FReducer& Reducer) { + return Reducer.GetAsUpdatePlayerInput(); + } +}; + +/** Metadata describing a reducer run. */ +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FClientUnrealReducerEvent +{ + GENERATED_BODY() + + /** Timestamp for when the reducer executed */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="SpacetimeDB") + FSpacetimeDBTimestamp Timestamp; + + /** Result status of the reducer */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="SpacetimeDB") + FSpacetimeDBStatus Status; + + /** Identity that initiated the call */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="SpacetimeDB") + FSpacetimeDBIdentity CallerIdentity; + + /** Connection ID for the caller */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="SpacetimeDB") + FSpacetimeDBConnectionId CallerConnectionId; + + /** Energy consumed while executing */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="SpacetimeDB") + FEnergyQuantaType EnergyConsumed; + + /** Detailed call information */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="SpacetimeDB") + FReducer Reducer; + + FORCEINLINE bool operator==(const FClientUnrealReducerEvent& Other) const + { + return Status == Other.Status && Timestamp == Other.Timestamp && CallerIdentity == Other.CallerIdentity && + CallerConnectionId == Other.CallerConnectionId && EnergyConsumed == Other.EnergyConsumed && + Reducer == Other.Reducer; + } + + FORCEINLINE bool operator!=(const FClientUnrealReducerEvent& Other) const + { + return !(*this == Other); + } +}; + +/** Represents event with variant message data. */ +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FClientUnrealEvent +{ + GENERATED_BODY() + + /** Tagged union holding reducer call, unit events, or error string */ + TVariant MessageData; + + /** Type tag indicating what this event represents */ + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + ESpacetimeDBEventTag Tag = ESpacetimeDBEventTag::UnknownTransaction; + + /** === Static factory methods ===*/ + static FClientUnrealEvent Reducer(const FReducer& Value) + { + FClientUnrealEvent Obj; + Obj.Tag = ESpacetimeDBEventTag::Reducer; + Obj.MessageData.Set(Value); + return Obj; + } + + static FClientUnrealEvent SubscribeApplied(const FSpacetimeDBUnit& Value) + { + FClientUnrealEvent Obj; + Obj.Tag = ESpacetimeDBEventTag::SubscribeApplied; + Obj.MessageData.Set(Value); + return Obj; + } + + static FClientUnrealEvent UnsubscribeApplied(const FSpacetimeDBUnit& Value) + { + FClientUnrealEvent Obj; + Obj.Tag = ESpacetimeDBEventTag::UnsubscribeApplied; + Obj.MessageData.Set(Value); + return Obj; + } + + static FClientUnrealEvent Disconnected(const FSpacetimeDBUnit& Value) + { + FClientUnrealEvent Obj; + Obj.Tag = ESpacetimeDBEventTag::Disconnected; + Obj.MessageData.Set(Value); + return Obj; + } + + static FClientUnrealEvent SubscribeError(const FString& Value) + { + FClientUnrealEvent Obj; + Obj.Tag = ESpacetimeDBEventTag::SubscribeError; + Obj.MessageData.Set(Value); + return Obj; + } + + static FClientUnrealEvent UnknownTransaction(const FSpacetimeDBUnit& Value) + { + FClientUnrealEvent Obj; + Obj.Tag = ESpacetimeDBEventTag::UnknownTransaction; + Obj.MessageData.Set(Value); + return Obj; + } + + FORCEINLINE bool IsReducer() const { return Tag == ESpacetimeDBEventTag::Reducer; } + FORCEINLINE FReducer GetAsReducer() const + { + ensureMsgf(IsReducer(), TEXT("MessageData does not hold Reducer!")); + return MessageData.Get(); + } + + FORCEINLINE bool IsSubscribeApplied() const { return Tag == ESpacetimeDBEventTag::SubscribeApplied; } + FORCEINLINE FSpacetimeDBUnit GetAsSubscribeApplied() const + { + ensureMsgf(IsSubscribeApplied(), TEXT("MessageData does not hold SubscribeApplied!")); + return MessageData.Get(); + } + + FORCEINLINE bool IsUnsubscribeApplied() const { return Tag == ESpacetimeDBEventTag::UnsubscribeApplied; } + FORCEINLINE FSpacetimeDBUnit GetAsUnsubscribeApplied() const + { + ensureMsgf(IsUnsubscribeApplied(), TEXT("MessageData does not hold UnsubscribeApplied!")); + return MessageData.Get(); + } + + FORCEINLINE bool IsDisconnected() const { return Tag == ESpacetimeDBEventTag::Disconnected; } + FORCEINLINE FSpacetimeDBUnit GetAsDisconnected() const + { + ensureMsgf(IsDisconnected(), TEXT("MessageData does not hold Disconnected!")); + return MessageData.Get(); + } + + FORCEINLINE bool IsSubscribeError() const { return Tag == ESpacetimeDBEventTag::SubscribeError; } + FORCEINLINE FString GetAsSubscribeError() const + { + ensureMsgf(IsSubscribeError(), TEXT("MessageData does not hold SubscribeError!")); + return MessageData.Get(); + } + + FORCEINLINE bool IsUnknownTransaction() const { return Tag == ESpacetimeDBEventTag::UnknownTransaction; } + FORCEINLINE FSpacetimeDBUnit GetAsUnknownTransaction() const + { + ensureMsgf(IsUnknownTransaction(), TEXT("MessageData does not hold UnknownTransaction!")); + return MessageData.Get(); + } + + FORCEINLINE bool operator==(const FClientUnrealEvent& Other) const + { + if (Tag != Other.Tag) return false; + switch (Tag) + { + case ESpacetimeDBEventTag::Reducer: return GetAsReducer() == Other.GetAsReducer(); + case ESpacetimeDBEventTag::SubscribeApplied: return GetAsSubscribeApplied() == Other.GetAsSubscribeApplied(); + case ESpacetimeDBEventTag::UnsubscribeApplied: return GetAsUnsubscribeApplied() == Other.GetAsUnsubscribeApplied(); + case ESpacetimeDBEventTag::Disconnected: return GetAsDisconnected() == Other.GetAsDisconnected(); + case ESpacetimeDBEventTag::SubscribeError: return GetAsSubscribeError() == Other.GetAsSubscribeError(); + case ESpacetimeDBEventTag::UnknownTransaction: return GetAsUnknownTransaction() == Other.GetAsUnknownTransaction(); + default: return false; + } + } + + FORCEINLINE bool operator!=(const FClientUnrealEvent& Other) const + { + return !(*this == Other); + } +}; + +UCLASS() +class CLIENT_UNREAL_API UClientUnrealEventBpLib : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +private: + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|ClientUnrealEvent") + static FClientUnrealEvent Reducer(const FReducer& InValue) + { + return FClientUnrealEvent::Reducer(InValue); + } + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|ClientUnrealEvent") + static FClientUnrealEvent SubscribeApplied(const FSpacetimeDBUnit& InValue) + { + return FClientUnrealEvent::SubscribeApplied(InValue); + } + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|ClientUnrealEvent") + static FClientUnrealEvent UnsubscribeApplied(const FSpacetimeDBUnit& InValue) + { + return FClientUnrealEvent::UnsubscribeApplied(InValue); + } + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|ClientUnrealEvent") + static FClientUnrealEvent Disconnected(const FSpacetimeDBUnit& InValue) + { + return FClientUnrealEvent::Disconnected(InValue); + } + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|ClientUnrealEvent") + static FClientUnrealEvent SubscribeError(const FString& InValue) + { + return FClientUnrealEvent::SubscribeError(InValue); + } + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|ClientUnrealEvent") + static FClientUnrealEvent UnknownTransaction(const FSpacetimeDBUnit& InValue) + { + return FClientUnrealEvent::UnknownTransaction(InValue); + } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|ClientUnrealEvent") + static bool IsReducer(const FClientUnrealEvent& Event) { return Event.IsReducer(); } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|ClientUnrealEvent") + static bool IsSubscribeApplied(const FClientUnrealEvent& Event) { return Event.IsSubscribeApplied(); } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|ClientUnrealEvent") + static bool IsUnsubscribeApplied(const FClientUnrealEvent& Event) { return Event.IsUnsubscribeApplied(); } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|ClientUnrealEvent") + static bool IsDisconnected(const FClientUnrealEvent& Event) { return Event.IsDisconnected(); } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|ClientUnrealEvent") + static bool IsSubscribeError(const FClientUnrealEvent& Event) { return Event.IsSubscribeError(); } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|ClientUnrealEvent") + static bool IsUnknownTransaction(const FClientUnrealEvent& Event) { return Event.IsUnknownTransaction(); } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|ClientUnrealEvent") + static FReducer GetAsReducer(const FClientUnrealEvent& Event) + { + return Event.GetAsReducer(); + } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|ClientUnrealEvent") + static FSpacetimeDBUnit GetAsSubscribeApplied(const FClientUnrealEvent& Event) + { + return Event.GetAsSubscribeApplied(); + } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|ClientUnrealEvent") + static FSpacetimeDBUnit GetAsUnsubscribeApplied(const FClientUnrealEvent& Event) + { + return Event.GetAsUnsubscribeApplied(); + } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|ClientUnrealEvent") + static FSpacetimeDBUnit GetAsDisconnected(const FClientUnrealEvent& Event) + { + return Event.GetAsDisconnected(); + } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|ClientUnrealEvent") + static FString GetAsSubscribeError(const FClientUnrealEvent& Event) + { + return Event.GetAsSubscribeError(); + } + + UFUNCTION(BlueprintPure, Category = "SpacetimeDB|ClientUnrealEvent") + static FSpacetimeDBUnit GetAsUnknownTransaction(const FClientUnrealEvent& Event) + { + return Event.GetAsUnknownTransaction(); + } + +}; + + +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FEventContext : public FContextBase +{ + GENERATED_BODY() + + FEventContext() = default; + FEventContext(UDbConnection* InConn, const FClientUnrealEvent& InEvent) : FContextBase(InConn), Event(InEvent) {} + + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + FClientUnrealEvent Event; +}; + +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FReducerEventContext : public FContextBase +{ + GENERATED_BODY() + + FReducerEventContext() = default; + FReducerEventContext(UDbConnection* InConn, FClientUnrealReducerEvent InEvent) : FContextBase(InConn), Event(InEvent) {} + + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + FClientUnrealReducerEvent Event; +}; + +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FErrorContext : public FContextBase +{ + GENERATED_BODY() + + FErrorContext() = default; + FErrorContext(UDbConnection* InConn, const FString& InError) : FContextBase(InConn), Error(InError) {} + + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + FString Error; + +}; + +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FSubscriptionEventContext : public FContextBase +{ + GENERATED_BODY() + + FSubscriptionEventContext() = default; + FSubscriptionEventContext(UDbConnection* InConn) : FContextBase(InConn) {} + +}; + +DECLARE_DYNAMIC_DELEGATE_OneParam( + FOnSubscriptionApplied, + FSubscriptionEventContext, Context); + +DECLARE_DYNAMIC_DELEGATE_OneParam( + FOnSubscriptionError, + FErrorContext, Context); + +UCLASS(BlueprintType) +class CLIENT_UNREAL_API USetReducerFlags : public USetReducerFlagsBase +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + void CircleDecay(ECallReducerFlags Flag); + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + void CircleRecombine(ECallReducerFlags Flag); + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + void Connect(ECallReducerFlags Flag); + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + void ConsumeEntity(ECallReducerFlags Flag); + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + void Disconnect(ECallReducerFlags Flag); + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + void EnterGame(ECallReducerFlags Flag); + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + void MoveAllPlayers(ECallReducerFlags Flag); + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + void PlayerSplit(ECallReducerFlags Flag); + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + void Respawn(ECallReducerFlags Flag); + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + void SpawnFood(ECallReducerFlags Flag); + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + void Suicide(ECallReducerFlags Flag); + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + void UpdatePlayerInput(ECallReducerFlags Flag); + +}; + +// RemoteTables class +UCLASS(BlueprintType) +class CLIENT_UNREAL_API URemoteTables : public UObject +{ + GENERATED_BODY() + +public: + void Initialize(); + + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + UEntityTable* LoggedOutEntity; + + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + UMoveAllPlayersTimerTable* MoveAllPlayersTimer; + + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + UCircleDecayTimerTable* CircleDecayTimer; + + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + UEntityTable* Entity; + + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + UFoodTable* Food; + + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + UCircleRecombineTimerTable* CircleRecombineTimer; + + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + UConfigTable* Config; + + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + UPlayerTable* LoggedOutPlayer; + + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + UCircleTable* LoggedOutCircle; + + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + UPlayerTable* Player; + + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + UConsumeEntityTimerTable* ConsumeEntityTimer; + + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + USpawnFoodTimerTable* SpawnFoodTimer; + + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + UCircleTable* Circle; + +}; + +// RemoteReducers class +UCLASS(BlueprintType) +class CLIENT_UNREAL_API URemoteReducers : public UObject +{ + GENERATED_BODY() + +public: + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FCircleDecayHandler, + const FReducerEventContext&, Context, + const FCircleDecayTimerType&, Timer + ); + UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") + FCircleDecayHandler OnCircleDecay; + + UFUNCTION(BlueprintCallable, Category="SpacetimeDB") + void CircleDecay(const FCircleDecayTimerType& Timer); + + bool InvokeCircleDecay(const FReducerEventContext& Context, const UCircleDecayReducer* Args); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FCircleRecombineHandler, + const FReducerEventContext&, Context, + const FCircleRecombineTimerType&, Timer + ); + UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") + FCircleRecombineHandler OnCircleRecombine; + + UFUNCTION(BlueprintCallable, Category="SpacetimeDB") + void CircleRecombine(const FCircleRecombineTimerType& Timer); + + bool InvokeCircleRecombine(const FReducerEventContext& Context, const UCircleRecombineReducer* Args); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam( + FConnectHandler, + const FReducerEventContext&, Context + ); + UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") + FConnectHandler OnConnect; + + UFUNCTION(BlueprintCallable, Category="SpacetimeDB") + void Connect(); + + bool InvokeConnect(const FReducerEventContext& Context, const UConnectReducer* Args); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FConsumeEntityHandler, + const FReducerEventContext&, Context, + const FConsumeEntityTimerType&, Request + ); + UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") + FConsumeEntityHandler OnConsumeEntity; + + UFUNCTION(BlueprintCallable, Category="SpacetimeDB") + void ConsumeEntity(const FConsumeEntityTimerType& Request); + + bool InvokeConsumeEntity(const FReducerEventContext& Context, const UConsumeEntityReducer* Args); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam( + FDisconnectHandler, + const FReducerEventContext&, Context + ); + UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") + FDisconnectHandler OnDisconnect; + + UFUNCTION(BlueprintCallable, Category="SpacetimeDB") + void Disconnect(); + + bool InvokeDisconnect(const FReducerEventContext& Context, const UDisconnectReducer* Args); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FEnterGameHandler, + const FReducerEventContext&, Context, + const FString&, Name + ); + UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") + FEnterGameHandler OnEnterGame; + + UFUNCTION(BlueprintCallable, Category="SpacetimeDB") + void EnterGame(const FString& Name); + + bool InvokeEnterGame(const FReducerEventContext& Context, const UEnterGameReducer* Args); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FMoveAllPlayersHandler, + const FReducerEventContext&, Context, + const FMoveAllPlayersTimerType&, Timer + ); + UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") + FMoveAllPlayersHandler OnMoveAllPlayers; + + UFUNCTION(BlueprintCallable, Category="SpacetimeDB") + void MoveAllPlayers(const FMoveAllPlayersTimerType& Timer); + + bool InvokeMoveAllPlayers(const FReducerEventContext& Context, const UMoveAllPlayersReducer* Args); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam( + FPlayerSplitHandler, + const FReducerEventContext&, Context + ); + UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") + FPlayerSplitHandler OnPlayerSplit; + + UFUNCTION(BlueprintCallable, Category="SpacetimeDB") + void PlayerSplit(); + + bool InvokePlayerSplit(const FReducerEventContext& Context, const UPlayerSplitReducer* Args); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam( + FRespawnHandler, + const FReducerEventContext&, Context + ); + UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") + FRespawnHandler OnRespawn; + + UFUNCTION(BlueprintCallable, Category="SpacetimeDB") + void Respawn(); + + bool InvokeRespawn(const FReducerEventContext& Context, const URespawnReducer* Args); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FSpawnFoodHandler, + const FReducerEventContext&, Context, + const FSpawnFoodTimerType&, Timer + ); + UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") + FSpawnFoodHandler OnSpawnFood; + + UFUNCTION(BlueprintCallable, Category="SpacetimeDB") + void SpawnFood(const FSpawnFoodTimerType& Timer); + + bool InvokeSpawnFood(const FReducerEventContext& Context, const USpawnFoodReducer* Args); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam( + FSuicideHandler, + const FReducerEventContext&, Context + ); + UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") + FSuicideHandler OnSuicide; + + UFUNCTION(BlueprintCallable, Category="SpacetimeDB") + void Suicide(); + + bool InvokeSuicide(const FReducerEventContext& Context, const USuicideReducer* Args); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FUpdatePlayerInputHandler, + const FReducerEventContext&, Context, + const FDbVector2Type&, Direction + ); + UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") + FUpdatePlayerInputHandler OnUpdatePlayerInput; + + UFUNCTION(BlueprintCallable, Category="SpacetimeDB") + void UpdatePlayerInput(const FDbVector2Type& Direction); + + bool InvokeUpdatePlayerInput(const FReducerEventContext& Context, const UUpdatePlayerInputReducer* Args); + + // Internal error handling + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FInternalOnUnhandledReducerError, const FReducerEventContext&, Context, const FString&, Error); + FInternalOnUnhandledReducerError InternalOnUnhandledReducerError; + +private: + + friend UDbConnection; + + UPROPERTY() + class UDbConnection* Conn; + + UPROPERTY() + USetReducerFlags* SetCallReducerFlags; +}; + +// SubscriptionBuilder class +UCLASS(BlueprintType) +class CLIENT_UNREAL_API USubscriptionBuilder : public USubscriptionBuilderBase +{ + GENERATED_BODY() + +public: + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + USubscriptionBuilder* OnApplied(FOnSubscriptionApplied Callback); + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + USubscriptionBuilder* OnError(FOnSubscriptionError Callback); + + UFUNCTION(BlueprintCallable, Category="SpacetimeDB") + USubscriptionHandle* Subscribe(const TArray& SQL); + + /** Convenience for subscribing to all rows from all tables */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + USubscriptionHandle* SubscribeToAllTables(); + + + friend class UDbConnection; + friend class UDbConnectionBase; + +protected: + UPROPERTY() + class UDbConnection* Conn; + + // Delegates stored so Subscribe() can bind forwarding callbacks + FOnSubscriptionApplied OnAppliedDelegateInternal; + FOnSubscriptionError OnErrorDelegateInternal; +}; + +// SubscriptionHandle class +UCLASS(BlueprintType) +class CLIENT_UNREAL_API USubscriptionHandle : public USubscriptionHandleBase +{ + GENERATED_BODY() + +public: + + USubscriptionHandle() {}; + + explicit USubscriptionHandle(UDbConnection* InConn); + + friend class USubscriptionBuilder; + +private: + UPROPERTY() + class UDbConnection* Conn; + + // Delegates that expose subscription events with connection aware contexts + FOnSubscriptionApplied OnAppliedDelegate; + FOnSubscriptionError OnErrorDelegate; + + UFUNCTION() + void ForwardOnApplied(const FSubscriptionEventContextBase& BaseCtx); + + UFUNCTION() + void ForwardOnError(const FErrorContextBase& BaseCtx); +}; + +/* + @Note: Child class of UDbConnectionBuilderBase. +*/ +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UDbConnectionBuilder : public UDbConnectionBuilderBase +{ + GENERATED_BODY() +public: + + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + UDbConnectionBuilder* WithUri(const FString& InUri); + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + UDbConnectionBuilder* WithModuleName(const FString& InName); + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + UDbConnectionBuilder* WithToken(const FString& InToken); + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + UDbConnectionBuilder* WithCompression(const ESpacetimeDBCompression& InCompression); + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + UDbConnectionBuilder* OnConnect(FOnConnectDelegate Callback); + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + UDbConnectionBuilder* OnConnectError(FOnConnectErrorDelegate Callback); + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + UDbConnectionBuilder* OnDisconnect(FOnDisconnectDelegate Callback); + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + UDbConnection* Build(); + +private: + + // Stored delegates which will be forwarded when the connection events occur. + FOnConnectDelegate OnConnectDelegateInternal; + FOnDisconnectDelegate OnDisconnectDelegateInternal; +}; + +// Main DbConnection class +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UDbConnection : public UDbConnectionBase +{ + GENERATED_BODY() + +public: + explicit UDbConnection(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + URemoteTables* Db; + + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + URemoteReducers* Reducers; + + UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") + USetReducerFlags* SetReducerFlags; + + // Delegates that allow users to bind with the concrete connection type. + FOnConnectDelegate OnConnectDelegate; + FOnDisconnectDelegate OnDisconnectDelegate; + + UFUNCTION(BlueprintCallable, Category="SpacetimeDB") + USubscriptionBuilder* SubscriptionBuilder(); + + /** Static entry point for constructing a connection. */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB", DisplayName = "SpacetimeDB Builder") + static UDbConnectionBuilder* Builder(); + + // Error handling + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnUnhandledReducerError, const FReducerEventContext&, Context, const FString&, Error); + UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") + FOnUnhandledReducerError OnUnhandledReducerError; + + +protected: + + // Hook up error handling to reducers + virtual void PostInitProperties() override; + + UFUNCTION() + void ForwardOnConnect(UDbConnectionBase* BaseConnection, FSpacetimeDBIdentity InIdentity, const FString& InToken); + UFUNCTION() + void ForwardOnDisconnect(UDbConnectionBase* BaseConnection, const FString& Error); + + UFUNCTION() + void OnUnhandledReducerErrorHandler(const FReducerEventContext& Context, const FString& Error); + + // Override the DbConnectionBase methods to handle updates and events + virtual void DbUpdate(const FDatabaseUpdateType& Update, const FSpacetimeDBEvent& Event) override; + + // Override the reducer event handler to dispatch events to the appropriate reducers + virtual void ReducerEvent(const FReducerEvent& Event) override; + + // Override the reducer event failed handler + virtual void ReducerEventFailed(const FReducerEvent& Event, const FString ErrorMessage) override; +}; + diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/CircleDecayTimerTable.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/CircleDecayTimerTable.g.h new file mode 100644 index 000000000..67734c5a8 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/CircleDecayTimerTable.g.h @@ -0,0 +1,104 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "Types/Builtins.h" +#include "ModuleBindings/Types/CircleDecayTimerType.g.h" +#include "Tables/RemoteTable.h" +#include "DBCache/WithBsatn.h" +#include "DBCache/TableHandle.h" +#include "DBCache/TableCache.h" +#include "CircleDecayTimerTable.g.generated.h" + +UCLASS(Blueprintable) +class CLIENT_UNREAL_API UCircleDecayTimerScheduledIdUniqueIndex : public UObject +{ + GENERATED_BODY() + +private: + // Declare an instance of your templated helper. + // It's private because the UObject wrapper will expose its functionality. + FUniqueIndexHelper> ScheduledIdIndexHelper; + +public: + UCircleDecayTimerScheduledIdUniqueIndex() + // Initialize the helper with the specific unique index name + : ScheduledIdIndexHelper("scheduled_id") { + } + + /** + * Finds a CircleDecayTimer by their unique scheduledid. + * @param Key The scheduledid to search for. + * @return The found FCircleDecayTimerType, or a default-constructed FCircleDecayTimerType if not found. + */ + // NOTE: Not exposed to Blueprint because uint64 types are not Blueprint-compatible + FCircleDecayTimerType Find(uint64 Key) + { + // Simply delegate the call to the internal helper + return ScheduledIdIndexHelper.FindUniqueIndex(Key); + } + + // A public setter to provide the cache to the helper after construction + // This is a common pattern when the cache might be created or provided by another system. + void SetCache(TSharedPtr> InCircleDecayTimerCache) + { + ScheduledIdIndexHelper.Cache = InCircleDecayTimerCache; + } +}; +/***/ + +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UCircleDecayTimerTable : public URemoteTable +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly) + UCircleDecayTimerScheduledIdUniqueIndex* ScheduledId; + + void PostInitialize(); + + /** Update function for circle_decay_timer table*/ + FTableAppliedDiff Update(TArray> InsertsRef, TArray> DeletesRef); + + /** Number of subscribed rows currently in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + int32 Count() const; + + /** Return all subscribed rows in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + TArray Iter() const; + + // Table Events + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnCircleDecayTimerInsert, + const FEventContext&, Context, + const FCircleDecayTimerType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams( + FOnCircleDecayTimerUpdate, + const FEventContext&, Context, + const FCircleDecayTimerType&, OldRow, + const FCircleDecayTimerType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnCircleDecayTimerDelete, + const FEventContext&, Context, + const FCircleDecayTimerType&, DeletedRow); + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnCircleDecayTimerInsert OnInsert; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnCircleDecayTimerUpdate OnUpdate; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnCircleDecayTimerDelete OnDelete; + +private: + const FString TableName = TEXT("circle_decay_timer"); + + TSharedPtr> Data; +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/CircleRecombineTimerTable.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/CircleRecombineTimerTable.g.h new file mode 100644 index 000000000..fa2584ed4 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/CircleRecombineTimerTable.g.h @@ -0,0 +1,104 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "Types/Builtins.h" +#include "ModuleBindings/Types/CircleRecombineTimerType.g.h" +#include "Tables/RemoteTable.h" +#include "DBCache/WithBsatn.h" +#include "DBCache/TableHandle.h" +#include "DBCache/TableCache.h" +#include "CircleRecombineTimerTable.g.generated.h" + +UCLASS(Blueprintable) +class CLIENT_UNREAL_API UCircleRecombineTimerScheduledIdUniqueIndex : public UObject +{ + GENERATED_BODY() + +private: + // Declare an instance of your templated helper. + // It's private because the UObject wrapper will expose its functionality. + FUniqueIndexHelper> ScheduledIdIndexHelper; + +public: + UCircleRecombineTimerScheduledIdUniqueIndex() + // Initialize the helper with the specific unique index name + : ScheduledIdIndexHelper("scheduled_id") { + } + + /** + * Finds a CircleRecombineTimer by their unique scheduledid. + * @param Key The scheduledid to search for. + * @return The found FCircleRecombineTimerType, or a default-constructed FCircleRecombineTimerType if not found. + */ + // NOTE: Not exposed to Blueprint because uint64 types are not Blueprint-compatible + FCircleRecombineTimerType Find(uint64 Key) + { + // Simply delegate the call to the internal helper + return ScheduledIdIndexHelper.FindUniqueIndex(Key); + } + + // A public setter to provide the cache to the helper after construction + // This is a common pattern when the cache might be created or provided by another system. + void SetCache(TSharedPtr> InCircleRecombineTimerCache) + { + ScheduledIdIndexHelper.Cache = InCircleRecombineTimerCache; + } +}; +/***/ + +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UCircleRecombineTimerTable : public URemoteTable +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly) + UCircleRecombineTimerScheduledIdUniqueIndex* ScheduledId; + + void PostInitialize(); + + /** Update function for circle_recombine_timer table*/ + FTableAppliedDiff Update(TArray> InsertsRef, TArray> DeletesRef); + + /** Number of subscribed rows currently in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + int32 Count() const; + + /** Return all subscribed rows in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + TArray Iter() const; + + // Table Events + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnCircleRecombineTimerInsert, + const FEventContext&, Context, + const FCircleRecombineTimerType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams( + FOnCircleRecombineTimerUpdate, + const FEventContext&, Context, + const FCircleRecombineTimerType&, OldRow, + const FCircleRecombineTimerType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnCircleRecombineTimerDelete, + const FEventContext&, Context, + const FCircleRecombineTimerType&, DeletedRow); + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnCircleRecombineTimerInsert OnInsert; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnCircleRecombineTimerUpdate OnUpdate; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnCircleRecombineTimerDelete OnDelete; + +private: + const FString TableName = TEXT("circle_recombine_timer"); + + TSharedPtr> Data; +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/CircleTable.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/CircleTable.g.h new file mode 100644 index 000000000..645dc04c5 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/CircleTable.g.h @@ -0,0 +1,141 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "Types/Builtins.h" +#include "ModuleBindings/Types/CircleType.g.h" +#include "Tables/RemoteTable.h" +#include "DBCache/WithBsatn.h" +#include "DBCache/TableHandle.h" +#include "DBCache/TableCache.h" +#include "CircleTable.g.generated.h" + +UCLASS(Blueprintable) +class CLIENT_UNREAL_API UCircleEntityIdUniqueIndex : public UObject +{ + GENERATED_BODY() + +private: + // Declare an instance of your templated helper. + // It's private because the UObject wrapper will expose its functionality. + FUniqueIndexHelper> EntityIdIndexHelper; + +public: + UCircleEntityIdUniqueIndex() + // Initialize the helper with the specific unique index name + : EntityIdIndexHelper("entity_id") { + } + + /** + * Finds a Circle by their unique entityid. + * @param Key The entityid to search for. + * @return The found FCircleType, or a default-constructed FCircleType if not found. + */ + // NOTE: Not exposed to Blueprint because uint32 types are not Blueprint-compatible + FCircleType Find(uint32 Key) + { + // Simply delegate the call to the internal helper + return EntityIdIndexHelper.FindUniqueIndex(Key); + } + + // A public setter to provide the cache to the helper after construction + // This is a common pattern when the cache might be created or provided by another system. + void SetCache(TSharedPtr> InCircleCache) + { + EntityIdIndexHelper.Cache = InCircleCache; + } +}; +/***/ + +UCLASS(Blueprintable) +class UCirclePlayerIdIndex : public UObject +{ + GENERATED_BODY() + +public: + TArray Filter(const uint32& PlayerId) const + { + TArray OutResults; + + LocalCache->FindByMultiKeyBTreeIndex>( + OutResults, + TEXT("player_id"), + MakeTuple(PlayerId) + ); + + return OutResults; + } + + void SetCache(TSharedPtr> InCache) + { + LocalCache = InCache; + } + +private: + // NOTE: Not exposed to Blueprint because some parameter types are not Blueprint-compatible + void FilterPlayerId(TArray& OutResults, const uint32& PlayerId) + { + OutResults = Filter(PlayerId); + } + + TSharedPtr> LocalCache; +}; + +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UCircleTable : public URemoteTable +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly) + UCircleEntityIdUniqueIndex* EntityId; + + UPROPERTY(BlueprintReadOnly) + UCirclePlayerIdIndex* PlayerId; + + void PostInitialize(); + + /** Update function for circle table*/ + FTableAppliedDiff Update(TArray> InsertsRef, TArray> DeletesRef); + + /** Number of subscribed rows currently in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + int32 Count() const; + + /** Return all subscribed rows in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + TArray Iter() const; + + // Table Events + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnCircleInsert, + const FEventContext&, Context, + const FCircleType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams( + FOnCircleUpdate, + const FEventContext&, Context, + const FCircleType&, OldRow, + const FCircleType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnCircleDelete, + const FEventContext&, Context, + const FCircleType&, DeletedRow); + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnCircleInsert OnInsert; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnCircleUpdate OnUpdate; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnCircleDelete OnDelete; + +private: + const FString TableName = TEXT("circle"); + + TSharedPtr> Data; +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/ConfigTable.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/ConfigTable.g.h new file mode 100644 index 000000000..3efa76559 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/ConfigTable.g.h @@ -0,0 +1,104 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "Types/Builtins.h" +#include "ModuleBindings/Types/ConfigType.g.h" +#include "Tables/RemoteTable.h" +#include "DBCache/WithBsatn.h" +#include "DBCache/TableHandle.h" +#include "DBCache/TableCache.h" +#include "ConfigTable.g.generated.h" + +UCLASS(Blueprintable) +class CLIENT_UNREAL_API UConfigIdUniqueIndex : public UObject +{ + GENERATED_BODY() + +private: + // Declare an instance of your templated helper. + // It's private because the UObject wrapper will expose its functionality. + FUniqueIndexHelper> IdIndexHelper; + +public: + UConfigIdUniqueIndex() + // Initialize the helper with the specific unique index name + : IdIndexHelper("id") { + } + + /** + * Finds a Config by their unique id. + * @param Key The id to search for. + * @return The found FConfigType, or a default-constructed FConfigType if not found. + */ + // NOTE: Not exposed to Blueprint because uint32 types are not Blueprint-compatible + FConfigType Find(uint32 Key) + { + // Simply delegate the call to the internal helper + return IdIndexHelper.FindUniqueIndex(Key); + } + + // A public setter to provide the cache to the helper after construction + // This is a common pattern when the cache might be created or provided by another system. + void SetCache(TSharedPtr> InConfigCache) + { + IdIndexHelper.Cache = InConfigCache; + } +}; +/***/ + +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UConfigTable : public URemoteTable +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly) + UConfigIdUniqueIndex* Id; + + void PostInitialize(); + + /** Update function for config table*/ + FTableAppliedDiff Update(TArray> InsertsRef, TArray> DeletesRef); + + /** Number of subscribed rows currently in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + int32 Count() const; + + /** Return all subscribed rows in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + TArray Iter() const; + + // Table Events + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnConfigInsert, + const FEventContext&, Context, + const FConfigType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams( + FOnConfigUpdate, + const FEventContext&, Context, + const FConfigType&, OldRow, + const FConfigType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnConfigDelete, + const FEventContext&, Context, + const FConfigType&, DeletedRow); + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnConfigInsert OnInsert; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnConfigUpdate OnUpdate; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnConfigDelete OnDelete; + +private: + const FString TableName = TEXT("config"); + + TSharedPtr> Data; +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/ConsumeEntityTimerTable.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/ConsumeEntityTimerTable.g.h new file mode 100644 index 000000000..f07cca059 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/ConsumeEntityTimerTable.g.h @@ -0,0 +1,104 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "Types/Builtins.h" +#include "ModuleBindings/Types/ConsumeEntityTimerType.g.h" +#include "Tables/RemoteTable.h" +#include "DBCache/WithBsatn.h" +#include "DBCache/TableHandle.h" +#include "DBCache/TableCache.h" +#include "ConsumeEntityTimerTable.g.generated.h" + +UCLASS(Blueprintable) +class CLIENT_UNREAL_API UConsumeEntityTimerScheduledIdUniqueIndex : public UObject +{ + GENERATED_BODY() + +private: + // Declare an instance of your templated helper. + // It's private because the UObject wrapper will expose its functionality. + FUniqueIndexHelper> ScheduledIdIndexHelper; + +public: + UConsumeEntityTimerScheduledIdUniqueIndex() + // Initialize the helper with the specific unique index name + : ScheduledIdIndexHelper("scheduled_id") { + } + + /** + * Finds a ConsumeEntityTimer by their unique scheduledid. + * @param Key The scheduledid to search for. + * @return The found FConsumeEntityTimerType, or a default-constructed FConsumeEntityTimerType if not found. + */ + // NOTE: Not exposed to Blueprint because uint64 types are not Blueprint-compatible + FConsumeEntityTimerType Find(uint64 Key) + { + // Simply delegate the call to the internal helper + return ScheduledIdIndexHelper.FindUniqueIndex(Key); + } + + // A public setter to provide the cache to the helper after construction + // This is a common pattern when the cache might be created or provided by another system. + void SetCache(TSharedPtr> InConsumeEntityTimerCache) + { + ScheduledIdIndexHelper.Cache = InConsumeEntityTimerCache; + } +}; +/***/ + +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UConsumeEntityTimerTable : public URemoteTable +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly) + UConsumeEntityTimerScheduledIdUniqueIndex* ScheduledId; + + void PostInitialize(); + + /** Update function for consume_entity_timer table*/ + FTableAppliedDiff Update(TArray> InsertsRef, TArray> DeletesRef); + + /** Number of subscribed rows currently in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + int32 Count() const; + + /** Return all subscribed rows in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + TArray Iter() const; + + // Table Events + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnConsumeEntityTimerInsert, + const FEventContext&, Context, + const FConsumeEntityTimerType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams( + FOnConsumeEntityTimerUpdate, + const FEventContext&, Context, + const FConsumeEntityTimerType&, OldRow, + const FConsumeEntityTimerType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnConsumeEntityTimerDelete, + const FEventContext&, Context, + const FConsumeEntityTimerType&, DeletedRow); + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnConsumeEntityTimerInsert OnInsert; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnConsumeEntityTimerUpdate OnUpdate; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnConsumeEntityTimerDelete OnDelete; + +private: + const FString TableName = TEXT("consume_entity_timer"); + + TSharedPtr> Data; +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/EntityTable.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/EntityTable.g.h new file mode 100644 index 000000000..ac15afa51 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/EntityTable.g.h @@ -0,0 +1,104 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "Types/Builtins.h" +#include "ModuleBindings/Types/EntityType.g.h" +#include "Tables/RemoteTable.h" +#include "DBCache/WithBsatn.h" +#include "DBCache/TableHandle.h" +#include "DBCache/TableCache.h" +#include "EntityTable.g.generated.h" + +UCLASS(Blueprintable) +class CLIENT_UNREAL_API UEntityEntityIdUniqueIndex : public UObject +{ + GENERATED_BODY() + +private: + // Declare an instance of your templated helper. + // It's private because the UObject wrapper will expose its functionality. + FUniqueIndexHelper> EntityIdIndexHelper; + +public: + UEntityEntityIdUniqueIndex() + // Initialize the helper with the specific unique index name + : EntityIdIndexHelper("entity_id") { + } + + /** + * Finds a Entity by their unique entityid. + * @param Key The entityid to search for. + * @return The found FEntityType, or a default-constructed FEntityType if not found. + */ + // NOTE: Not exposed to Blueprint because uint32 types are not Blueprint-compatible + FEntityType Find(uint32 Key) + { + // Simply delegate the call to the internal helper + return EntityIdIndexHelper.FindUniqueIndex(Key); + } + + // A public setter to provide the cache to the helper after construction + // This is a common pattern when the cache might be created or provided by another system. + void SetCache(TSharedPtr> InEntityCache) + { + EntityIdIndexHelper.Cache = InEntityCache; + } +}; +/***/ + +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UEntityTable : public URemoteTable +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly) + UEntityEntityIdUniqueIndex* EntityId; + + void PostInitialize(); + + /** Update function for entity table*/ + FTableAppliedDiff Update(TArray> InsertsRef, TArray> DeletesRef); + + /** Number of subscribed rows currently in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + int32 Count() const; + + /** Return all subscribed rows in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + TArray Iter() const; + + // Table Events + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnEntityInsert, + const FEventContext&, Context, + const FEntityType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams( + FOnEntityUpdate, + const FEventContext&, Context, + const FEntityType&, OldRow, + const FEntityType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnEntityDelete, + const FEventContext&, Context, + const FEntityType&, DeletedRow); + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnEntityInsert OnInsert; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnEntityUpdate OnUpdate; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnEntityDelete OnDelete; + +private: + const FString TableName = TEXT("entity"); + + TSharedPtr> Data; +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/FoodTable.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/FoodTable.g.h new file mode 100644 index 000000000..1e1893796 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/FoodTable.g.h @@ -0,0 +1,104 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "Types/Builtins.h" +#include "ModuleBindings/Types/FoodType.g.h" +#include "Tables/RemoteTable.h" +#include "DBCache/WithBsatn.h" +#include "DBCache/TableHandle.h" +#include "DBCache/TableCache.h" +#include "FoodTable.g.generated.h" + +UCLASS(Blueprintable) +class CLIENT_UNREAL_API UFoodEntityIdUniqueIndex : public UObject +{ + GENERATED_BODY() + +private: + // Declare an instance of your templated helper. + // It's private because the UObject wrapper will expose its functionality. + FUniqueIndexHelper> EntityIdIndexHelper; + +public: + UFoodEntityIdUniqueIndex() + // Initialize the helper with the specific unique index name + : EntityIdIndexHelper("entity_id") { + } + + /** + * Finds a Food by their unique entityid. + * @param Key The entityid to search for. + * @return The found FFoodType, or a default-constructed FFoodType if not found. + */ + // NOTE: Not exposed to Blueprint because uint32 types are not Blueprint-compatible + FFoodType Find(uint32 Key) + { + // Simply delegate the call to the internal helper + return EntityIdIndexHelper.FindUniqueIndex(Key); + } + + // A public setter to provide the cache to the helper after construction + // This is a common pattern when the cache might be created or provided by another system. + void SetCache(TSharedPtr> InFoodCache) + { + EntityIdIndexHelper.Cache = InFoodCache; + } +}; +/***/ + +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UFoodTable : public URemoteTable +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly) + UFoodEntityIdUniqueIndex* EntityId; + + void PostInitialize(); + + /** Update function for food table*/ + FTableAppliedDiff Update(TArray> InsertsRef, TArray> DeletesRef); + + /** Number of subscribed rows currently in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + int32 Count() const; + + /** Return all subscribed rows in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + TArray Iter() const; + + // Table Events + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnFoodInsert, + const FEventContext&, Context, + const FFoodType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams( + FOnFoodUpdate, + const FEventContext&, Context, + const FFoodType&, OldRow, + const FFoodType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnFoodDelete, + const FEventContext&, Context, + const FFoodType&, DeletedRow); + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnFoodInsert OnInsert; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnFoodUpdate OnUpdate; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnFoodDelete OnDelete; + +private: + const FString TableName = TEXT("food"); + + TSharedPtr> Data; +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/MoveAllPlayersTimerTable.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/MoveAllPlayersTimerTable.g.h new file mode 100644 index 000000000..5e81ed977 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/MoveAllPlayersTimerTable.g.h @@ -0,0 +1,104 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "Types/Builtins.h" +#include "ModuleBindings/Types/MoveAllPlayersTimerType.g.h" +#include "Tables/RemoteTable.h" +#include "DBCache/WithBsatn.h" +#include "DBCache/TableHandle.h" +#include "DBCache/TableCache.h" +#include "MoveAllPlayersTimerTable.g.generated.h" + +UCLASS(Blueprintable) +class CLIENT_UNREAL_API UMoveAllPlayersTimerScheduledIdUniqueIndex : public UObject +{ + GENERATED_BODY() + +private: + // Declare an instance of your templated helper. + // It's private because the UObject wrapper will expose its functionality. + FUniqueIndexHelper> ScheduledIdIndexHelper; + +public: + UMoveAllPlayersTimerScheduledIdUniqueIndex() + // Initialize the helper with the specific unique index name + : ScheduledIdIndexHelper("scheduled_id") { + } + + /** + * Finds a MoveAllPlayersTimer by their unique scheduledid. + * @param Key The scheduledid to search for. + * @return The found FMoveAllPlayersTimerType, or a default-constructed FMoveAllPlayersTimerType if not found. + */ + // NOTE: Not exposed to Blueprint because uint64 types are not Blueprint-compatible + FMoveAllPlayersTimerType Find(uint64 Key) + { + // Simply delegate the call to the internal helper + return ScheduledIdIndexHelper.FindUniqueIndex(Key); + } + + // A public setter to provide the cache to the helper after construction + // This is a common pattern when the cache might be created or provided by another system. + void SetCache(TSharedPtr> InMoveAllPlayersTimerCache) + { + ScheduledIdIndexHelper.Cache = InMoveAllPlayersTimerCache; + } +}; +/***/ + +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UMoveAllPlayersTimerTable : public URemoteTable +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly) + UMoveAllPlayersTimerScheduledIdUniqueIndex* ScheduledId; + + void PostInitialize(); + + /** Update function for move_all_players_timer table*/ + FTableAppliedDiff Update(TArray> InsertsRef, TArray> DeletesRef); + + /** Number of subscribed rows currently in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + int32 Count() const; + + /** Return all subscribed rows in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + TArray Iter() const; + + // Table Events + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnMoveAllPlayersTimerInsert, + const FEventContext&, Context, + const FMoveAllPlayersTimerType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams( + FOnMoveAllPlayersTimerUpdate, + const FEventContext&, Context, + const FMoveAllPlayersTimerType&, OldRow, + const FMoveAllPlayersTimerType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnMoveAllPlayersTimerDelete, + const FEventContext&, Context, + const FMoveAllPlayersTimerType&, DeletedRow); + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnMoveAllPlayersTimerInsert OnInsert; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnMoveAllPlayersTimerUpdate OnUpdate; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnMoveAllPlayersTimerDelete OnDelete; + +private: + const FString TableName = TEXT("move_all_players_timer"); + + TSharedPtr> Data; +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/PlayerTable.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/PlayerTable.g.h new file mode 100644 index 000000000..e22c9590c --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/PlayerTable.g.h @@ -0,0 +1,144 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "Types/Builtins.h" +#include "ModuleBindings/Types/PlayerType.g.h" +#include "Tables/RemoteTable.h" +#include "DBCache/WithBsatn.h" +#include "DBCache/TableHandle.h" +#include "DBCache/TableCache.h" +#include "PlayerTable.g.generated.h" + +UCLASS(Blueprintable) +class CLIENT_UNREAL_API UPlayerIdentityUniqueIndex : public UObject +{ + GENERATED_BODY() + +private: + // Declare an instance of your templated helper. + // It's private because the UObject wrapper will expose its functionality. + FUniqueIndexHelper> IdentityIndexHelper; + +public: + UPlayerIdentityUniqueIndex() + // Initialize the helper with the specific unique index name + : IdentityIndexHelper("identity") { + } + + /** + * Finds a Player by their unique identity. + * @param Key The identity to search for. + * @return The found FPlayerType, or a default-constructed FPlayerType if not found. + */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|PlayerIndex") + FPlayerType Find(FSpacetimeDBIdentity Key) + { + // Simply delegate the call to the internal helper + return IdentityIndexHelper.FindUniqueIndex(Key); + } + + // A public setter to provide the cache to the helper after construction + // This is a common pattern when the cache might be created or provided by another system. + void SetCache(TSharedPtr> InPlayerCache) + { + IdentityIndexHelper.Cache = InPlayerCache; + } +}; +/***/ + +UCLASS(Blueprintable) +class CLIENT_UNREAL_API UPlayerPlayerIdUniqueIndex : public UObject +{ + GENERATED_BODY() + +private: + // Declare an instance of your templated helper. + // It's private because the UObject wrapper will expose its functionality. + FUniqueIndexHelper> PlayerIdIndexHelper; + +public: + UPlayerPlayerIdUniqueIndex() + // Initialize the helper with the specific unique index name + : PlayerIdIndexHelper("player_id") { + } + + /** + * Finds a Player by their unique playerid. + * @param Key The playerid to search for. + * @return The found FPlayerType, or a default-constructed FPlayerType if not found. + */ + // NOTE: Not exposed to Blueprint because uint32 types are not Blueprint-compatible + FPlayerType Find(uint32 Key) + { + // Simply delegate the call to the internal helper + return PlayerIdIndexHelper.FindUniqueIndex(Key); + } + + // A public setter to provide the cache to the helper after construction + // This is a common pattern when the cache might be created or provided by another system. + void SetCache(TSharedPtr> InPlayerCache) + { + PlayerIdIndexHelper.Cache = InPlayerCache; + } +}; +/***/ + +UCLASS(BlueprintType) +class CLIENT_UNREAL_API UPlayerTable : public URemoteTable +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly) + UPlayerIdentityUniqueIndex* Identity; + + UPROPERTY(BlueprintReadOnly) + UPlayerPlayerIdUniqueIndex* PlayerId; + + void PostInitialize(); + + /** Update function for player table*/ + FTableAppliedDiff Update(TArray> InsertsRef, TArray> DeletesRef); + + /** Number of subscribed rows currently in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + int32 Count() const; + + /** Return all subscribed rows in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + TArray Iter() const; + + // Table Events + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnPlayerInsert, + const FEventContext&, Context, + const FPlayerType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams( + FOnPlayerUpdate, + const FEventContext&, Context, + const FPlayerType&, OldRow, + const FPlayerType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnPlayerDelete, + const FEventContext&, Context, + const FPlayerType&, DeletedRow); + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnPlayerInsert OnInsert; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnPlayerUpdate OnUpdate; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnPlayerDelete OnDelete; + +private: + const FString TableName = TEXT("player"); + + TSharedPtr> Data; +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/SpawnFoodTimerTable.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/SpawnFoodTimerTable.g.h new file mode 100644 index 000000000..362243f28 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/SpawnFoodTimerTable.g.h @@ -0,0 +1,104 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "Types/Builtins.h" +#include "ModuleBindings/Types/SpawnFoodTimerType.g.h" +#include "Tables/RemoteTable.h" +#include "DBCache/WithBsatn.h" +#include "DBCache/TableHandle.h" +#include "DBCache/TableCache.h" +#include "SpawnFoodTimerTable.g.generated.h" + +UCLASS(Blueprintable) +class CLIENT_UNREAL_API USpawnFoodTimerScheduledIdUniqueIndex : public UObject +{ + GENERATED_BODY() + +private: + // Declare an instance of your templated helper. + // It's private because the UObject wrapper will expose its functionality. + FUniqueIndexHelper> ScheduledIdIndexHelper; + +public: + USpawnFoodTimerScheduledIdUniqueIndex() + // Initialize the helper with the specific unique index name + : ScheduledIdIndexHelper("scheduled_id") { + } + + /** + * Finds a SpawnFoodTimer by their unique scheduledid. + * @param Key The scheduledid to search for. + * @return The found FSpawnFoodTimerType, or a default-constructed FSpawnFoodTimerType if not found. + */ + // NOTE: Not exposed to Blueprint because uint64 types are not Blueprint-compatible + FSpawnFoodTimerType Find(uint64 Key) + { + // Simply delegate the call to the internal helper + return ScheduledIdIndexHelper.FindUniqueIndex(Key); + } + + // A public setter to provide the cache to the helper after construction + // This is a common pattern when the cache might be created or provided by another system. + void SetCache(TSharedPtr> InSpawnFoodTimerCache) + { + ScheduledIdIndexHelper.Cache = InSpawnFoodTimerCache; + } +}; +/***/ + +UCLASS(BlueprintType) +class CLIENT_UNREAL_API USpawnFoodTimerTable : public URemoteTable +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly) + USpawnFoodTimerScheduledIdUniqueIndex* ScheduledId; + + void PostInitialize(); + + /** Update function for spawn_food_timer table*/ + FTableAppliedDiff Update(TArray> InsertsRef, TArray> DeletesRef); + + /** Number of subscribed rows currently in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + int32 Count() const; + + /** Return all subscribed rows in the cache */ + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") + TArray Iter() const; + + // Table Events + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnSpawnFoodTimerInsert, + const FEventContext&, Context, + const FSpawnFoodTimerType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams( + FOnSpawnFoodTimerUpdate, + const FEventContext&, Context, + const FSpawnFoodTimerType&, OldRow, + const FSpawnFoodTimerType&, NewRow); + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FOnSpawnFoodTimerDelete, + const FEventContext&, Context, + const FSpawnFoodTimerType&, DeletedRow); + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnSpawnFoodTimerInsert OnInsert; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnSpawnFoodTimerUpdate OnUpdate; + + UPROPERTY(BlueprintAssignable, Category = "SpacetimeDB Events") + FOnSpawnFoodTimerDelete OnDelete; + +private: + const FString TableName = TEXT("spawn_food_timer"); + + TSharedPtr> Data; +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleDecayTimerType.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleDecayTimerType.g.h new file mode 100644 index 000000000..998d85a4e --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleDecayTimerType.g.h @@ -0,0 +1,50 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "Types/Builtins.h" +#include "CircleDecayTimerType.g.generated.h" + +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FCircleDecayTimerType +{ + GENERATED_BODY() + + // NOTE: uint64 field not exposed to Blueprint due to non-blueprintable elements + uint64 ScheduledId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + FSpacetimeDBScheduleAt ScheduledAt; + + FORCEINLINE bool operator==(const FCircleDecayTimerType& Other) const + { + return ScheduledId == Other.ScheduledId && ScheduledAt == Other.ScheduledAt; + } + + FORCEINLINE bool operator!=(const FCircleDecayTimerType& Other) const + { + return !(*this == Other); + } +}; + +/** + * Custom hash function for FCircleDecayTimerType. + * Combines the hashes of all fields that are compared in operator==. + * @param CircleDecayTimerType The FCircleDecayTimerType instance to hash. + * @return The combined hash value. + */ +FORCEINLINE uint32 GetTypeHash(const FCircleDecayTimerType& CircleDecayTimerType) +{ + uint32 Hash = GetTypeHash(CircleDecayTimerType.ScheduledId); + Hash = HashCombine(Hash, GetTypeHash(CircleDecayTimerType.ScheduledAt)); + return Hash; +} + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_ENABLE_TARRAY(FCircleDecayTimerType); + + UE_SPACETIMEDB_STRUCT(FCircleDecayTimerType, ScheduledId, ScheduledAt); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleRecombineTimerType.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleRecombineTimerType.g.h new file mode 100644 index 000000000..dd5197710 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleRecombineTimerType.g.h @@ -0,0 +1,54 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "Types/Builtins.h" +#include "CircleRecombineTimerType.g.generated.h" + +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FCircleRecombineTimerType +{ + GENERATED_BODY() + + // NOTE: uint64 field not exposed to Blueprint due to non-blueprintable elements + uint64 ScheduledId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + FSpacetimeDBScheduleAt ScheduledAt; + + // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements + uint32 PlayerId; + + FORCEINLINE bool operator==(const FCircleRecombineTimerType& Other) const + { + return ScheduledId == Other.ScheduledId && ScheduledAt == Other.ScheduledAt && PlayerId == Other.PlayerId; + } + + FORCEINLINE bool operator!=(const FCircleRecombineTimerType& Other) const + { + return !(*this == Other); + } +}; + +/** + * Custom hash function for FCircleRecombineTimerType. + * Combines the hashes of all fields that are compared in operator==. + * @param CircleRecombineTimerType The FCircleRecombineTimerType instance to hash. + * @return The combined hash value. + */ +FORCEINLINE uint32 GetTypeHash(const FCircleRecombineTimerType& CircleRecombineTimerType) +{ + uint32 Hash = GetTypeHash(CircleRecombineTimerType.ScheduledId); + Hash = HashCombine(Hash, GetTypeHash(CircleRecombineTimerType.ScheduledAt)); + Hash = HashCombine(Hash, GetTypeHash(CircleRecombineTimerType.PlayerId)); + return Hash; +} + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_ENABLE_TARRAY(FCircleRecombineTimerType); + + UE_SPACETIMEDB_STRUCT(FCircleRecombineTimerType, ScheduledId, ScheduledAt, PlayerId); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleType.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleType.g.h new file mode 100644 index 000000000..f4169b7a3 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleType.g.h @@ -0,0 +1,63 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "ModuleBindings/Types/DbVector2Type.g.h" +#include "Types/Builtins.h" +#include "CircleType.g.generated.h" + +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FCircleType +{ + GENERATED_BODY() + + // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements + uint32 EntityId; + + // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements + uint32 PlayerId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + FDbVector2Type Direction; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + float Speed; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + FSpacetimeDBTimestamp LastSplitTime; + + FORCEINLINE bool operator==(const FCircleType& Other) const + { + return EntityId == Other.EntityId && PlayerId == Other.PlayerId && Direction == Other.Direction && Speed == Other.Speed && LastSplitTime == Other.LastSplitTime; + } + + FORCEINLINE bool operator!=(const FCircleType& Other) const + { + return !(*this == Other); + } +}; + +/** + * Custom hash function for FCircleType. + * Combines the hashes of all fields that are compared in operator==. + * @param CircleType The FCircleType instance to hash. + * @return The combined hash value. + */ +FORCEINLINE uint32 GetTypeHash(const FCircleType& CircleType) +{ + uint32 Hash = GetTypeHash(CircleType.EntityId); + Hash = HashCombine(Hash, GetTypeHash(CircleType.PlayerId)); + Hash = HashCombine(Hash, GetTypeHash(CircleType.Direction)); + Hash = HashCombine(Hash, GetTypeHash(CircleType.Speed)); + Hash = HashCombine(Hash, GetTypeHash(CircleType.LastSplitTime)); + return Hash; +} + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_ENABLE_TARRAY(FCircleType); + + UE_SPACETIMEDB_STRUCT(FCircleType, EntityId, PlayerId, Direction, Speed, LastSplitTime); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/ConfigType.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/ConfigType.g.h new file mode 100644 index 000000000..deace670b --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/ConfigType.g.h @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "ConfigType.g.generated.h" + +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FConfigType +{ + GENERATED_BODY() + + // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements + uint32 Id; + + // NOTE: uint64 field not exposed to Blueprint due to non-blueprintable elements + uint64 WorldSize; + + FORCEINLINE bool operator==(const FConfigType& Other) const + { + return Id == Other.Id && WorldSize == Other.WorldSize; + } + + FORCEINLINE bool operator!=(const FConfigType& Other) const + { + return !(*this == Other); + } +}; + +/** + * Custom hash function for FConfigType. + * Combines the hashes of all fields that are compared in operator==. + * @param ConfigType The FConfigType instance to hash. + * @return The combined hash value. + */ +FORCEINLINE uint32 GetTypeHash(const FConfigType& ConfigType) +{ + uint32 Hash = GetTypeHash(ConfigType.Id); + Hash = HashCombine(Hash, GetTypeHash(ConfigType.WorldSize)); + return Hash; +} + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_ENABLE_TARRAY(FConfigType); + + UE_SPACETIMEDB_STRUCT(FConfigType, Id, WorldSize); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/ConsumeEntityTimerType.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/ConsumeEntityTimerType.g.h new file mode 100644 index 000000000..3f1eada55 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/ConsumeEntityTimerType.g.h @@ -0,0 +1,58 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "Types/Builtins.h" +#include "ConsumeEntityTimerType.g.generated.h" + +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FConsumeEntityTimerType +{ + GENERATED_BODY() + + // NOTE: uint64 field not exposed to Blueprint due to non-blueprintable elements + uint64 ScheduledId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + FSpacetimeDBScheduleAt ScheduledAt; + + // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements + uint32 ConsumedEntityId; + + // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements + uint32 ConsumerEntityId; + + FORCEINLINE bool operator==(const FConsumeEntityTimerType& Other) const + { + return ScheduledId == Other.ScheduledId && ScheduledAt == Other.ScheduledAt && ConsumedEntityId == Other.ConsumedEntityId && ConsumerEntityId == Other.ConsumerEntityId; + } + + FORCEINLINE bool operator!=(const FConsumeEntityTimerType& Other) const + { + return !(*this == Other); + } +}; + +/** + * Custom hash function for FConsumeEntityTimerType. + * Combines the hashes of all fields that are compared in operator==. + * @param ConsumeEntityTimerType The FConsumeEntityTimerType instance to hash. + * @return The combined hash value. + */ +FORCEINLINE uint32 GetTypeHash(const FConsumeEntityTimerType& ConsumeEntityTimerType) +{ + uint32 Hash = GetTypeHash(ConsumeEntityTimerType.ScheduledId); + Hash = HashCombine(Hash, GetTypeHash(ConsumeEntityTimerType.ScheduledAt)); + Hash = HashCombine(Hash, GetTypeHash(ConsumeEntityTimerType.ConsumedEntityId)); + Hash = HashCombine(Hash, GetTypeHash(ConsumeEntityTimerType.ConsumerEntityId)); + return Hash; +} + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_ENABLE_TARRAY(FConsumeEntityTimerType); + + UE_SPACETIMEDB_STRUCT(FConsumeEntityTimerType, ScheduledId, ScheduledAt, ConsumedEntityId, ConsumerEntityId); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/DbVector2Type.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/DbVector2Type.g.h new file mode 100644 index 000000000..5eae3d926 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/DbVector2Type.g.h @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "DbVector2Type.g.generated.h" + +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FDbVector2Type +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + float X; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + float Y; + + FORCEINLINE bool operator==(const FDbVector2Type& Other) const + { + return X == Other.X && Y == Other.Y; + } + + FORCEINLINE bool operator!=(const FDbVector2Type& Other) const + { + return !(*this == Other); + } +}; + +/** + * Custom hash function for FDbVector2Type. + * Combines the hashes of all fields that are compared in operator==. + * @param DbVector2Type The FDbVector2Type instance to hash. + * @return The combined hash value. + */ +FORCEINLINE uint32 GetTypeHash(const FDbVector2Type& DbVector2Type) +{ + uint32 Hash = GetTypeHash(DbVector2Type.X); + Hash = HashCombine(Hash, GetTypeHash(DbVector2Type.Y)); + return Hash; +} + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_ENABLE_TARRAY(FDbVector2Type); + + UE_SPACETIMEDB_STRUCT(FDbVector2Type, X, Y); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/EntityType.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/EntityType.g.h new file mode 100644 index 000000000..d95e3567c --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/EntityType.g.h @@ -0,0 +1,54 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "ModuleBindings/Types/DbVector2Type.g.h" +#include "EntityType.g.generated.h" + +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FEntityType +{ + GENERATED_BODY() + + // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements + uint32 EntityId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + FDbVector2Type Position; + + // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements + uint32 Mass; + + FORCEINLINE bool operator==(const FEntityType& Other) const + { + return EntityId == Other.EntityId && Position == Other.Position && Mass == Other.Mass; + } + + FORCEINLINE bool operator!=(const FEntityType& Other) const + { + return !(*this == Other); + } +}; + +/** + * Custom hash function for FEntityType. + * Combines the hashes of all fields that are compared in operator==. + * @param EntityType The FEntityType instance to hash. + * @return The combined hash value. + */ +FORCEINLINE uint32 GetTypeHash(const FEntityType& EntityType) +{ + uint32 Hash = GetTypeHash(EntityType.EntityId); + Hash = HashCombine(Hash, GetTypeHash(EntityType.Position)); + Hash = HashCombine(Hash, GetTypeHash(EntityType.Mass)); + return Hash; +} + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_ENABLE_TARRAY(FEntityType); + + UE_SPACETIMEDB_STRUCT(FEntityType, EntityId, Position, Mass); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/FoodType.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/FoodType.g.h new file mode 100644 index 000000000..48e069952 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/FoodType.g.h @@ -0,0 +1,45 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "FoodType.g.generated.h" + +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FFoodType +{ + GENERATED_BODY() + + // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements + uint32 EntityId; + + FORCEINLINE bool operator==(const FFoodType& Other) const + { + return EntityId == Other.EntityId; + } + + FORCEINLINE bool operator!=(const FFoodType& Other) const + { + return !(*this == Other); + } +}; + +/** + * Custom hash function for FFoodType. + * Combines the hashes of all fields that are compared in operator==. + * @param FoodType The FFoodType instance to hash. + * @return The combined hash value. + */ +FORCEINLINE uint32 GetTypeHash(const FFoodType& FoodType) +{ + uint32 Hash = GetTypeHash(FoodType.EntityId); + return Hash; +} + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_ENABLE_TARRAY(FFoodType); + + UE_SPACETIMEDB_STRUCT(FFoodType, EntityId); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/MoveAllPlayersTimerType.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/MoveAllPlayersTimerType.g.h new file mode 100644 index 000000000..5bd566a94 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/MoveAllPlayersTimerType.g.h @@ -0,0 +1,50 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "Types/Builtins.h" +#include "MoveAllPlayersTimerType.g.generated.h" + +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FMoveAllPlayersTimerType +{ + GENERATED_BODY() + + // NOTE: uint64 field not exposed to Blueprint due to non-blueprintable elements + uint64 ScheduledId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + FSpacetimeDBScheduleAt ScheduledAt; + + FORCEINLINE bool operator==(const FMoveAllPlayersTimerType& Other) const + { + return ScheduledId == Other.ScheduledId && ScheduledAt == Other.ScheduledAt; + } + + FORCEINLINE bool operator!=(const FMoveAllPlayersTimerType& Other) const + { + return !(*this == Other); + } +}; + +/** + * Custom hash function for FMoveAllPlayersTimerType. + * Combines the hashes of all fields that are compared in operator==. + * @param MoveAllPlayersTimerType The FMoveAllPlayersTimerType instance to hash. + * @return The combined hash value. + */ +FORCEINLINE uint32 GetTypeHash(const FMoveAllPlayersTimerType& MoveAllPlayersTimerType) +{ + uint32 Hash = GetTypeHash(MoveAllPlayersTimerType.ScheduledId); + Hash = HashCombine(Hash, GetTypeHash(MoveAllPlayersTimerType.ScheduledAt)); + return Hash; +} + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_ENABLE_TARRAY(FMoveAllPlayersTimerType); + + UE_SPACETIMEDB_STRUCT(FMoveAllPlayersTimerType, ScheduledId, ScheduledAt); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/PlayerType.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/PlayerType.g.h new file mode 100644 index 000000000..e50121102 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/PlayerType.g.h @@ -0,0 +1,54 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "Types/Builtins.h" +#include "PlayerType.g.generated.h" + +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FPlayerType +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + FSpacetimeDBIdentity Identity; + + // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements + uint32 PlayerId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + FString Name; + + FORCEINLINE bool operator==(const FPlayerType& Other) const + { + return Identity == Other.Identity && PlayerId == Other.PlayerId && Name == Other.Name; + } + + FORCEINLINE bool operator!=(const FPlayerType& Other) const + { + return !(*this == Other); + } +}; + +/** + * Custom hash function for FPlayerType. + * Combines the hashes of all fields that are compared in operator==. + * @param PlayerType The FPlayerType instance to hash. + * @return The combined hash value. + */ +FORCEINLINE uint32 GetTypeHash(const FPlayerType& PlayerType) +{ + uint32 Hash = GetTypeHash(PlayerType.Identity); + Hash = HashCombine(Hash, GetTypeHash(PlayerType.PlayerId)); + Hash = HashCombine(Hash, GetTypeHash(PlayerType.Name)); + return Hash; +} + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_ENABLE_TARRAY(FPlayerType); + + UE_SPACETIMEDB_STRUCT(FPlayerType, Identity, PlayerId, Name); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/SpawnFoodTimerType.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/SpawnFoodTimerType.g.h new file mode 100644 index 000000000..51cc04b32 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/SpawnFoodTimerType.g.h @@ -0,0 +1,50 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#pragma once +#include "CoreMinimal.h" +#include "BSATN/UESpacetimeDB.h" +#include "Types/Builtins.h" +#include "SpawnFoodTimerType.g.generated.h" + +USTRUCT(BlueprintType) +struct CLIENT_UNREAL_API FSpawnFoodTimerType +{ + GENERATED_BODY() + + // NOTE: uint64 field not exposed to Blueprint due to non-blueprintable elements + uint64 ScheduledId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + FSpacetimeDBScheduleAt ScheduledAt; + + FORCEINLINE bool operator==(const FSpawnFoodTimerType& Other) const + { + return ScheduledId == Other.ScheduledId && ScheduledAt == Other.ScheduledAt; + } + + FORCEINLINE bool operator!=(const FSpawnFoodTimerType& Other) const + { + return !(*this == Other); + } +}; + +/** + * Custom hash function for FSpawnFoodTimerType. + * Combines the hashes of all fields that are compared in operator==. + * @param SpawnFoodTimerType The FSpawnFoodTimerType instance to hash. + * @return The combined hash value. + */ +FORCEINLINE uint32 GetTypeHash(const FSpawnFoodTimerType& SpawnFoodTimerType) +{ + uint32 Hash = GetTypeHash(SpawnFoodTimerType.ScheduledId); + Hash = HashCombine(Hash, GetTypeHash(SpawnFoodTimerType.ScheduledAt)); + return Hash; +} + +namespace UE::SpacetimeDB +{ + UE_SPACETIMEDB_ENABLE_TARRAY(FSpawnFoodTimerType); + + UE_SPACETIMEDB_STRUCT(FSpawnFoodTimerType, ScheduledId, ScheduledAt); +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/PlayerPawn.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/PlayerPawn.h new file mode 100644 index 000000000..8215e4f0e --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/PlayerPawn.h @@ -0,0 +1,61 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Pawn.h" +#include "PlayerPawn.generated.h" + +class ACircle; +struct FPlayerType; +class UCameraComponent; +class USpringArmComponent; + +UCLASS() +class CLIENT_UNREAL_API APlayerPawn : public APawn +{ + GENERATED_BODY() + +public: + APlayerPawn(); + void Initialize(FPlayerType Player); + + uint32 PlayerId = 0; + UPROPERTY(BlueprintReadWrite, Category="BH|Player") + bool bIsLocalPlayer = false; + + UPROPERTY() + TArray> OwnedCircles; + + UFUNCTION() + FString GetUsername() const; + UFUNCTION() + void OnCircleSpawned(ACircle* Circle); + UFUNCTION() + void OnCircleDeleted(ACircle* Circle); + + UFUNCTION(BlueprintCallable, Category="BH|Input") + void Split(); + UFUNCTION(BlueprintCallable, Category="BH|Input") + void Suicide(); + + uint32 TotalMass() const; + UFUNCTION(BlueprintPure, Category="BH|Player") + FVector CenterOfMass() const; + +protected: + virtual void Destroyed() override; + UPROPERTY(EditDefaultsOnly, Category="BH|Camera") + float BaseSize = 50.f; + UPROPERTY(EditDefaultsOnly, Category="BH|Camera") + float MassToSizeDivisor = 5.f; + UPROPERTY(EditDefaultsOnly, Category="BH|Camera") + float MaxMassBonus = 50.f; + UPROPERTY(EditDefaultsOnly, Category="BH|Camera") + float SplitBonus = 30.f; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly) + TObjectPtr SpringArm; + UPROPERTY(VisibleAnywhere, BlueprintReadOnly) + TObjectPtr Camera; +public: + virtual void Tick(float DeltaTime) override; +}; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/client_unreal.Build.cs b/demo/Blackholio/client-unreal/Source/client_unreal/client_unreal.Build.cs new file mode 100644 index 000000000..9b3242194 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/client_unreal.Build.cs @@ -0,0 +1,37 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class client_unreal : ModuleRules +{ + public client_unreal(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new string[] + { + "Core", + "CoreUObject", + "Engine", + "InputCore", + "EnhancedInput", + "SpacetimeDbSdk", + "Paper2D" + }); + + PrivateDependencyModuleNames.AddRange(new string[] + { + "UMG", + "SlateCore", + "Slate" + }); + + // Uncomment if you are using Slate UI + // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); + + // Uncomment if you are using online features + // PrivateDependencyModuleNames.Add("OnlineSubsystem"); + + // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true + } +} diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/client_unreal.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/client_unreal.cpp new file mode 100644 index 000000000..9b2cb8261 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/client_unreal.cpp @@ -0,0 +1,6 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "client_unreal.h" +#include "Modules/ModuleManager.h" + +IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, client_unreal, "client_unreal" ); diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/client_unreal.h b/demo/Blackholio/client-unreal/Source/client_unreal/client_unreal.h new file mode 100644 index 000000000..677c8e25b --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unreal/client_unreal.h @@ -0,0 +1,6 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + diff --git a/demo/Blackholio/client-unreal/Source/client_unrealEditor.Target.cs b/demo/Blackholio/client-unreal/Source/client_unrealEditor.Target.cs new file mode 100644 index 000000000..1e073cfc5 --- /dev/null +++ b/demo/Blackholio/client-unreal/Source/client_unrealEditor.Target.cs @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class client_unrealEditorTarget : TargetRules +{ + public client_unrealEditorTarget( TargetInfo Target) : base(Target) + { + Type = TargetType.Editor; + DefaultBuildSettings = BuildSettingsVersion.V5; + IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_6; + ExtraModuleNames.Add("client_unreal"); + } +} diff --git a/demo/Blackholio/client-unreal/client_unreal.uproject b/demo/Blackholio/client-unreal/client_unreal.uproject new file mode 100644 index 000000000..513949e4d --- /dev/null +++ b/demo/Blackholio/client-unreal/client_unreal.uproject @@ -0,0 +1,28 @@ +{ + "FileVersion": 3, + "EngineAssociation": "5.6", + "Category": "", + "Description": "", + "Modules": [ + { + "Name": "client_unreal", + "Type": "Runtime", + "LoadingPhase": "Default", + "AdditionalDependencies": [ + "Engine" + ] + } + ], + "Plugins": [ + { + "Name": "ModelingToolsEditorMode", + "Enabled": true, + "TargetAllowList": [ + "Editor" + ] + } + ], + "AdditionalPluginDirectories": [ + "../../../sdks/unreal/src" + ] +} \ No newline at end of file diff --git a/demo/Blackholio/server-csharp/generate.bat b/demo/Blackholio/server-csharp/generate.bat new file mode 100644 index 000000000..9a58365cb --- /dev/null +++ b/demo/Blackholio/server-csharp/generate.bat @@ -0,0 +1,2 @@ +spacetime generate --out-dir ../client-unity/Assets/Scripts/autogen -y --lang cs +spacetime generate --lang unrealcpp --uproject-dir ../client-unreal --project-path ./ --module-name client_unreal diff --git a/demo/Blackholio/server-csharp/generate.sh b/demo/Blackholio/server-csharp/generate.sh index b9c25552d..d21e860e0 100644 --- a/demo/Blackholio/server-csharp/generate.sh +++ b/demo/Blackholio/server-csharp/generate.sh @@ -3,3 +3,4 @@ set -euo pipefail spacetime generate --out-dir ../client-unity/Assets/Scripts/autogen --lang cs $@ +spacetime generate --lang unrealcpp --uproject-dir ../client-unreal --project-path ./ --module-name client_unreal diff --git a/demo/Blackholio/server-csharp/publish.bat b/demo/Blackholio/server-csharp/publish.bat new file mode 100644 index 000000000..d9b60d8bb --- /dev/null +++ b/demo/Blackholio/server-csharp/publish.bat @@ -0,0 +1 @@ +spacetime publish -s local -c -y blackholio \ No newline at end of file diff --git a/demo/Blackholio/server-rust/generate.bat b/demo/Blackholio/server-rust/generate.bat index 6fabe2009..9a58365cb 100644 --- a/demo/Blackholio/server-rust/generate.bat +++ b/demo/Blackholio/server-rust/generate.bat @@ -1 +1,2 @@ spacetime generate --out-dir ../client-unity/Assets/Scripts/autogen -y --lang cs +spacetime generate --lang unrealcpp --uproject-dir ../client-unreal --project-path ./ --module-name client_unreal diff --git a/demo/Blackholio/server-rust/generate.sh b/demo/Blackholio/server-rust/generate.sh index b9c25552d..d21e860e0 100755 --- a/demo/Blackholio/server-rust/generate.sh +++ b/demo/Blackholio/server-rust/generate.sh @@ -3,3 +3,4 @@ set -euo pipefail spacetime generate --out-dir ../client-unity/Assets/Scripts/autogen --lang cs $@ +spacetime generate --lang unrealcpp --uproject-dir ../client-unreal --project-path ./ --module-name client_unreal