├── Source ├── ArtilleryRuntime │ ├── Private │ │ ├── ArtilleryTimekeeper.cpp │ │ ├── ArtilleryRuntimeModule.cpp │ │ ├── ArtilleryShell.cpp │ │ ├── ArtilleryProjectileDispatch.cpp │ │ ├── UArtilleryAbilityMinimum.cpp │ │ ├── CanonicalInputStreamECS.cpp │ │ └── FArtilleryBusyWorker.cpp │ ├── Public │ │ ├── desperate-thor.gif │ │ ├── ArtilleryRuntimeModule.h │ │ ├── Systems │ │ │ ├── ArtilleryTimekeeper.h │ │ │ ├── FArtilleryBusyWorker.h │ │ │ ├── ArtilleryProjectileDispatch.h │ │ │ ├── UEnemyMachine.h │ │ │ ├── AInstancedMeshManager.h │ │ │ ├── ArtilleryBPLibs.h │ │ │ ├── FArtilleryTicklitesThread.h │ │ │ ├── UFireControlMachine.h │ │ │ └── ArtilleryDispatch.h │ │ ├── BasicTypes │ │ │ ├── FProjectileDefinitionRow.h │ │ │ ├── FArtilleryNoGuaranteeReadOnly.h │ │ │ ├── LocomotionParams.h │ │ │ ├── ConservedKey.h │ │ │ ├── FGunKey.h │ │ │ ├── FGunDefinitionRow.h │ │ │ ├── ConservedAttribute.h │ │ │ ├── ArtilleryShell.h │ │ │ └── ArtilleryCommonTypes.h │ │ ├── TIcklites │ │ │ ├── FTJumpTimer.h │ │ │ ├── FTProjectileFinalTickResolver.h │ │ │ ├── FTSphereCast.h │ │ │ ├── FTLinearVelocity.h │ │ │ ├── FTPlayerEstimatorWithForce.h │ │ │ ├── Ticklite.md │ │ │ ├── FTEntityFinalTickResolver.h │ │ │ └── FTGunFinalTickResolver.h │ │ ├── EssentialTypes │ │ │ ├── FAttributeMap.h │ │ │ ├── FRelationshipMap.h │ │ │ ├── PlayerKeyCarry.h │ │ │ ├── EAttributes.h │ │ │ ├── Ticklite.h │ │ │ └── UArtilleryAbilityMinimum.h │ │ ├── PhysicsTypes │ │ │ ├── BarrageStaticAutoMesh.h │ │ │ ├── BarrageBoxComponent.h │ │ │ ├── BarrageAutoBox.h │ │ │ ├── BarrageColliderBase.h │ │ │ └── BarragePlayerAgent.h │ │ └── TestTypes │ │ │ ├── FMockArtilleryGun.h │ │ │ ├── FMockChairCannon.h │ │ │ ├── FMockDashGun.h │ │ │ ├── BarrageGravityOnlyTester.h │ │ │ └── FMockBeamCannon.h │ └── ArtilleryRuntime.Build.cs └── ArtilleryEditor │ ├── Public │ └── ArtilleryEditorModule.h │ ├── Private │ └── ArtilleryRuntimeModule.cpp │ └── ArtilleryEditor.Build.cs ├── Resources └── Icon128.png ├── Data ├── GunData │ ├── GunClasses.csv │ ├── GunClasses.xlsm │ ├── BasePlayerLoadout.csv │ ├── BasePlayerLoadout.xlsm │ ├── AvailableGunClasses │ │ ├── M6DProperties.csv │ │ └── M6DProperties.xlsm │ ├── DefaultArtilleryAbilityProperties.csv │ └── DefaultArtilleryAbilityProperties.xlsm └── README.md ├── .gitattributes ├── README.md ├── Content ├── Chair.uasset ├── M_Chair.uasset ├── DashPreFire.uasset ├── SM_Chair.uasset ├── ChairTestActor.uasset ├── MultiChairTest.uasset ├── StaticTester.uasset ├── TestMeshes │ ├── Chair.uasset │ ├── M_Chair.uasset │ ├── SM_Chair.uasset │ ├── Shape_Pipe_180.uasset │ ├── Chassis_Quad_Wide_Lvl2.uasset │ ├── ReusableChairPrimitive.uasset │ └── TestMaterials │ │ └── M_Chair.uasset └── Chassis_Quad_Wide_Lvl2.uasset ├── Config └── DefaultArtillery.ini ├── DeterminismNotes └── DyadicTest.md ├── Artillery.uplugin ├── LICENSE └── .gitignore /Source/ArtilleryRuntime/Private/ArtilleryTimekeeper.cpp: -------------------------------------------------------------------------------- 1 | #include "ArtilleryTimekeeper.h" 2 | -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OversizedSunCoreDev/ArtilleryPrototype/HEAD/Resources/Icon128.png -------------------------------------------------------------------------------- /Data/GunData/GunClasses.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OversizedSunCoreDev/ArtilleryPrototype/HEAD/Data/GunData/GunClasses.csv -------------------------------------------------------------------------------- /Data/GunData/GunClasses.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OversizedSunCoreDev/ArtilleryPrototype/HEAD/Data/GunData/GunClasses.xlsm -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | Content/** filter=lfs diff=lfs merge=lfs -text -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Replaced by the MonoRepo! 2 | https://github.com/OversizedSunCoreDev/ArtilleryMonoRepo 3 | 4 | Hope to see you at GDC! 5 | -------------------------------------------------------------------------------- /Data/GunData/BasePlayerLoadout.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OversizedSunCoreDev/ArtilleryPrototype/HEAD/Data/GunData/BasePlayerLoadout.csv -------------------------------------------------------------------------------- /Data/GunData/BasePlayerLoadout.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OversizedSunCoreDev/ArtilleryPrototype/HEAD/Data/GunData/BasePlayerLoadout.xlsm -------------------------------------------------------------------------------- /Content/Chair.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e4eba195b2fbc4d539a0f745e9f5fa672b0b2ec999d2833c17555b308431ee8e 3 | size 2278 4 | -------------------------------------------------------------------------------- /Content/M_Chair.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:849e80235cb17ccdfe8ea7a43a56ba7ea09889bb2871b9e6dc57579e66048f96 3 | size 1425 4 | -------------------------------------------------------------------------------- /Content/DashPreFire.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e54ac5c12d9df55cffa5ae8175cc799279bfa48c7f4b64af7db0a83cc3ff80ff 3 | size 26499 4 | -------------------------------------------------------------------------------- /Content/SM_Chair.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:82e3049a8665802eaa13dd5c4113b5d2cd32193de35aba81b92be77e75ec37fd 3 | size 1437 4 | -------------------------------------------------------------------------------- /Content/ChairTestActor.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:8711fee5885e2fcec958aa320b3c3313c2dc1b4f58de1815a06330a9df24323a 3 | size 2485 4 | -------------------------------------------------------------------------------- /Content/MultiChairTest.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:8d43afb419e7e13c4f3d1c2e24e6ecc4ace2ac48fb7f8cd9dbbea3846a7e7f5b 3 | size 22209 4 | -------------------------------------------------------------------------------- /Content/StaticTester.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:5273bec0f13328615af332e8f4b9f6aa195cf509fa28eaf044fd6b8c1e0d97b9 3 | size 84274 4 | -------------------------------------------------------------------------------- /Content/TestMeshes/Chair.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:d9f198634d3a6bd5b38a239d6f64dfaf5cc13d54e8946ddfb4df6be8f435da34 3 | size 2559 4 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/desperate-thor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OversizedSunCoreDev/ArtilleryPrototype/HEAD/Source/ArtilleryRuntime/Public/desperate-thor.gif -------------------------------------------------------------------------------- /Content/Chassis_Quad_Wide_Lvl2.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:db5d75e3aa9969a5835b621eea3046e4bdfa103b35e9f5db65c9ea9bdafd06fb 3 | size 1557 4 | -------------------------------------------------------------------------------- /Content/TestMeshes/M_Chair.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:a5d13e5f0f24d39f78cdc6e861ab36e02f11b32daa8de2fa8ae86484dd720845 3 | size 1475 4 | -------------------------------------------------------------------------------- /Content/TestMeshes/SM_Chair.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:30aa19e828a9c12f79b08b8c5ce9bfeb1172c4772e73fe27c051d4397098d8d4 3 | size 100416 4 | -------------------------------------------------------------------------------- /Data/GunData/AvailableGunClasses/M6DProperties.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OversizedSunCoreDev/ArtilleryPrototype/HEAD/Data/GunData/AvailableGunClasses/M6DProperties.csv -------------------------------------------------------------------------------- /Data/GunData/AvailableGunClasses/M6DProperties.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OversizedSunCoreDev/ArtilleryPrototype/HEAD/Data/GunData/AvailableGunClasses/M6DProperties.xlsm -------------------------------------------------------------------------------- /Data/GunData/DefaultArtilleryAbilityProperties.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OversizedSunCoreDev/ArtilleryPrototype/HEAD/Data/GunData/DefaultArtilleryAbilityProperties.csv -------------------------------------------------------------------------------- /Data/GunData/DefaultArtilleryAbilityProperties.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OversizedSunCoreDev/ArtilleryPrototype/HEAD/Data/GunData/DefaultArtilleryAbilityProperties.xlsm -------------------------------------------------------------------------------- /Content/TestMeshes/Shape_Pipe_180.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e114494267476641e39602466454781c2998a531e979c28fa5dd4388ef6ec260 3 | size 86548 4 | -------------------------------------------------------------------------------- /Config/DefaultArtillery.ini: -------------------------------------------------------------------------------- 1 | [CoreRedirects] 2 | +PropertyRedirects=(OldName="/Script/ArtilleryRuntime.FireControlMachine.MySquire",NewName="/Script/ArtilleryRuntime.FireControlMachine.MyInput") -------------------------------------------------------------------------------- /Content/TestMeshes/Chassis_Quad_Wide_Lvl2.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c4152c11f1ae2aabc66f1c88e636d4a8a1a71dceec29a1337b7a4cc45d575dad 3 | size 53058 4 | -------------------------------------------------------------------------------- /Content/TestMeshes/ReusableChairPrimitive.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:98948ede34b4e4bd1752bfd1c0f4ce27a6241466bbdd4073260b214a295e3d65 3 | size 17062 4 | -------------------------------------------------------------------------------- /Content/TestMeshes/TestMaterials/M_Chair.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:cdda51fbd9219b01ec9819060d82f10c529ef898cb4fb942a952be8b3d2eaadf 3 | size 19941 4 | -------------------------------------------------------------------------------- /Data/README.md: -------------------------------------------------------------------------------- 1 | ## Hullo! Whassit? 2 | 3 | Unlike content, for us, data is defined as mergeable and programmatic. The best example here is our Gun Files, which will be compiled down to manifest tables prior to ship, but are left exposed for the prototyping phase. These are loaded as UE Data Tables. -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/ArtilleryRuntimeModule.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Modules/ModuleManager.h" 7 | 8 | class FArtilleryRuntimeModule : public IModuleInterface 9 | { 10 | public: 11 | //~IModuleInterface 12 | virtual void StartupModule() override; 13 | virtual void ShutdownModule() override; 14 | //~End of IModuleInterface 15 | }; 16 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/Systems/ArtilleryTimekeeper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ArtilleryCommonTypes.h" 3 | 4 | //REFACTOR IN PROGRESS 5 | //TODO:: CENTRALIZE AND STATICIZE TIMEKEEPING FOR ARTILLERY 6 | //RIGHT NOW, IT"S SPAGHETTIZ 7 | class ArtilleryTimekeeper 8 | { 9 | public: 10 | inline ArtilleryTime Now() 11 | { 12 | return 0; 13 | } 14 | 15 | inline ArtilleryTime GetShadowNow() 16 | { 17 | return 0; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/BasicTypes/FProjectileDefinitionRow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CoreMinimal.h" 3 | #include "CoreTypes.h" 4 | #include "Engine/DataTable.h" 5 | 6 | #include "FProjectileDefinitionRow.generated.h" 7 | 8 | USTRUCT(BlueprintType) 9 | struct FProjectileDefinitionRow : public FTableRowBase 10 | { 11 | GENERATED_USTRUCT_BODY() 12 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ProjectileDefinition) 13 | FString ProjectileDefinitionId; 14 | 15 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ProjectileDefinition) 16 | FString ProjectileMeshLocation; 17 | }; 18 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Private/ArtilleryRuntimeModule.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "ArtilleryRuntimeModule.h" 4 | 5 | #define LOCTEXT_NAMESPACE "FArtilleryRuntimeModule" 6 | 7 | void FArtilleryRuntimeModule::StartupModule() 8 | { 9 | // This code will execute after your module is loaded into memory; 10 | // the exact timing is specified in the .uplugin file per-module 11 | } 12 | 13 | void FArtilleryRuntimeModule::ShutdownModule() 14 | { 15 | // This function may be called during shutdown to clean up your module. 16 | // For modules that support dynamic reloading, we call this function before unloading the module. 17 | } 18 | 19 | #undef LOCTEXT_NAMESPACE 20 | 21 | IMPLEMENT_MODULE(FArtilleryRuntimeModule, ArtilleryRuntime) 22 | -------------------------------------------------------------------------------- /DeterminismNotes/DyadicTest.md: -------------------------------------------------------------------------------- 1 | ``` 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | int main(){ 8 | float div = 1024; 9 | double coercivediv = 1024; 10 | for(float i = 1; i <= 1024; ++i) 11 | { 12 | float base = (i/div); 13 | double test = (i/coercivediv); 14 | // bitset<32> a = bitset<32>( *reinterpret_cast (&base)); 15 | for(float ip = 1; ip <= 1024; ++ip) 16 | { 17 | float basep = (ip/div); 18 | double testp = (ip/coercivediv); 19 | float basecomp = basep*base; 20 | double testcomp = test*testp; 21 | if(testcomp != basecomp) 22 | { 23 | cout << i << " " << ip << endl; 24 | } 25 | 26 | } 27 | } 28 | } 29 | ``` 30 | 31 | So. this doesn't output anything. That's _REALLY_ interesting, actually. 32 | That means two dyadics of the form x/1024 produce a dyadic that fits in a float when 33 | multiplied once. This sounds stupid, but has some ramifications that are interesting for us. -------------------------------------------------------------------------------- /Source/ArtilleryEditor/Public/ArtilleryEditorModule.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Engine.h" 4 | 5 | #include "Kismet2/KismetEditorUtilities.h" 6 | #include "Modules/ModuleInterface.h" 7 | #include "Modules/ModuleManager.h" 8 | #include "UnrealEd.h" 9 | 10 | DECLARE_LOG_CATEGORY_EXTERN(MyGameEditor, All, All) 11 | 12 | class FArtilleryEditorModule : public IModuleInterface 13 | { 14 | public: 15 | virtual void StartupModule() override; 16 | virtual void ShutdownModule() override; 17 | private: 18 | /** 19 | * Define all of our default events for this Editor 20 | */ 21 | void PrepareAutoGeneratedDefaultEvents(); 22 | 23 | /** 24 | * Simplify Registering default events with a macro 25 | * @param Class - Name of the UObject or AActor class 26 | * @param FuncName - Name of the Event of Function to register 27 | */ 28 | #define RegisterDefaultEvent(Class, FuncName) \ 29 | (FKismetEditorUtilities::RegisterAutoGeneratedDefaultEvent(this, Class::StaticClass(), GET_FUNCTION_NAME_CHECKED(Class, FuncName))) 30 | 31 | }; -------------------------------------------------------------------------------- /Artillery.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "0.0.1.0", 5 | "FriendlyName": "Artillery", 6 | "Description": "Guns, GAS, And Steel", 7 | "Category": "Other", 8 | "CreatedBy": "Hedra", 9 | "CreatedByURL": "", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": true, 14 | "IsBetaVersion": false, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "ArtilleryRuntime", 20 | "Type": "Runtime", 21 | "LoadingPhase": "PostEngineInit" 22 | }, 23 | { 24 | "Name": "ArtilleryEditor", 25 | "Type": "Editor", 26 | "LoadingPhase": "PostEngineInit" 27 | } 28 | ], 29 | "Plugins": [ 30 | { 31 | "Name": "GameplayAbilities", 32 | "Enabled": true 33 | }, 34 | { 35 | "Name": "Bristlecone", 36 | "Enabled": true 37 | }, 38 | { 39 | "Name": "Cabling", 40 | "Enabled": true 41 | }, 42 | { 43 | "Name": "SkeletonKey", 44 | "Enabled": true 45 | }, 46 | { 47 | "Name": "Barrage", 48 | "Enabled": true 49 | } 50 | ] 51 | } -------------------------------------------------------------------------------- /Source/ArtilleryEditor/Private/ArtilleryRuntimeModule.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "ArtilleryEditorModule.h" 4 | #include "UArtilleryAbilityMinimum.h" 5 | 6 | #define LOCTEXT_NAMESPACE "FArtilleryEditorModule" 7 | 8 | void FArtilleryEditorModule::StartupModule() 9 | { 10 | // This code will execute after your module is loaded into memory; 11 | // the exact timing is specified in the .uplugin file per-module 12 | PrepareAutoGeneratedDefaultEvents(); 13 | } 14 | 15 | void FArtilleryEditorModule::ShutdownModule() 16 | { 17 | // This function may be called during shutdown to clean up your module. 18 | // For modules that support dynamic reloading, we call this function before unloading the module. 19 | FKismetEditorUtilities::UnregisterAutoBlueprintNodeCreation(this); 20 | 21 | } 22 | 23 | void FArtilleryEditorModule::PrepareAutoGeneratedDefaultEvents() 24 | { 25 | 26 | RegisterDefaultEvent(UArtilleryPerActorAbilityMinimum, K2_ActivateViaArtillery); 27 | } 28 | 29 | #undef LOCTEXT_NAMESPACE 30 | 31 | IMPLEMENT_MODULE(FArtilleryEditorModule, ArtilleryEditor) 32 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/BasicTypes/FArtilleryNoGuaranteeReadOnly.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Templates/SubclassOf.h" 7 | #include "UObject/UnrealType.h" 8 | #include "Engine/DataTable.h" 9 | #include "AttributeSet.h" 10 | #include "ArtilleryCommonTypes.h" 11 | #include 12 | #include "ArtilleryShell.h" 13 | #include "Containers/CircularBuffer.h" 14 | 15 | 16 | //See Desperate-thor.gif for more information. 17 | //Catch you in the next one, choom. 18 | 19 | //This facade offers no guarantee that it is read only. it exists to break a circular dependency in an elegant 20 | //way that also serves to cleanly facade away some quite considerabile complexity. 21 | //it's probably read only. probably. 22 | class FArtilleryNoGuaranteeReadOnly 23 | { 24 | public: 25 | virtual std::optional peek(uint64_t input) = 0; 26 | }; 27 | //See Desperate-thor.gif for more information or FArtilleryNoGuaranteeReadOnly 28 | typedef TSharedPtr FANG_PTR; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 JKurzer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | 22 | # Compiled Static libraries 23 | *.lai 24 | *.la 25 | *.a 26 | *.lib 27 | 28 | # Executables 29 | *.exe 30 | *.out 31 | *.app 32 | *.ipa 33 | 34 | # These project files can be generated by the engine 35 | *.xcodeproj 36 | *.xcworkspace 37 | *.sln 38 | *.suo 39 | *.opensdf 40 | *.sdf 41 | *.VC.db 42 | *.VC.opendb 43 | 44 | # Precompiled Assets 45 | SourceArt/**/*.png 46 | SourceArt/**/*.tga 47 | 48 | # Binary Files 49 | Binaries/* 50 | Plugins/*/Binaries/* 51 | 52 | # Builds 53 | Build/* 54 | 55 | # Whitelist PakBlacklist-.txt files 56 | !Build/*/ 57 | Build/*/** 58 | !Build/*/PakBlacklist*.txt 59 | 60 | # Don't ignore icon files in Build 61 | !Build/**/*.ico 62 | 63 | # Built data for maps 64 | *_BuiltData.uasset 65 | 66 | # Configuration files generated by the Editor 67 | Saved/* 68 | 69 | # Compiled source files for the engine to use 70 | Intermediate/* 71 | Plugins/*/Intermediate/* 72 | 73 | # Cache files for the editor to use 74 | DerivedDataCache/* 75 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/TIcklites/FTJumpTimer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Ticklite.h" 4 | #include "ArtilleryDispatch.h" 5 | #include "FArtilleryTicklitesThread.h" 6 | 7 | class FTJumpTimer : public UArtilleryDispatch::TL_ThreadedImpl 8 | { 9 | private: 10 | FSkeletonKey JumpTarget; 11 | 12 | public: 13 | FTJumpTimer() : TL_ThreadedImpl() 14 | { 15 | } 16 | 17 | FTJumpTimer(FSkeletonKey Jumper) : TL_ThreadedImpl(), JumpTarget(Jumper) 18 | { 19 | } 20 | 21 | void TICKLITE_StateReset() 22 | { 23 | } 24 | 25 | void TICKLITE_Calculate() 26 | { 27 | } 28 | 29 | void TICKLITE_Apply() { 30 | auto ticksLeftPtr = ADispatch->GetAttrib(JumpTarget, Attr::TicksTilJumpAvailable); 31 | if (!ticksLeftPtr) 32 | { 33 | return; 34 | } 35 | 36 | ticksLeftPtr->SetCurrentValue(ticksLeftPtr->GetCurrentValue() - 1); 37 | } 38 | 39 | void TICKLITE_CoreReset() { 40 | } 41 | 42 | bool TICKLITE_CheckForExpiration() { 43 | auto ticksLeftPtr = ADispatch->GetAttrib(JumpTarget, Attr::TicksTilJumpAvailable); 44 | 45 | // If there is no valid ptr, expire 46 | if (!ticksLeftPtr) 47 | { 48 | return true; 49 | } 50 | 51 | return ticksLeftPtr->GetCurrentValue() <= 0; 52 | } 53 | 54 | void TICKLITE_OnExpiration() { 55 | } 56 | }; 57 | 58 | typedef Ticklites::Ticklite TL_JumpTimer; -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/BasicTypes/LocomotionParams.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Templates/SubclassOf.h" 7 | #include "UObject/UnrealType.h" 8 | #include "Engine/DataTable.h" 9 | #include "AttributeSet.h" 10 | #include "Containers/CircularBuffer.h" 11 | #include "BristleconeCommonTypes.h" 12 | #include "ArtilleryCommonTypes.h" 13 | #include "ArtilleryShell.h" 14 | 15 | struct LocomotionParams 16 | { 17 | uint64_t time; 18 | FSkeletonKey parent; 19 | FArtilleryShell previousIndex; // may NOT be current -1. :/ 20 | FArtilleryShell currentIndex; 21 | 22 | LocomotionParams(uint64_t time, uint64_t parent, FArtilleryShell prev, FArtilleryShell cur): 23 | time(time), 24 | parent(parent), 25 | previousIndex(prev), 26 | currentIndex(cur) 27 | { 28 | } 29 | }; 30 | 31 | //this creates a stable sub-ordering that ensures deterministic sequence of operations. 32 | static bool operator<(LocomotionParams const& lhs, LocomotionParams const& rhs) 33 | { 34 | return lhs.time == rhs.time ? (lhs.time < rhs.time) : (lhs.parent < rhs.parent); 35 | } 36 | 37 | namespace Arty 38 | { 39 | typedef TArray MovementBuffer; 40 | typedef TTripleBuffer BufferedMoveEvents; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/BasicTypes/ConservedKey.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Templates/SubclassOf.h" 7 | #include "UObject/UnrealType.h" 8 | #include "Engine/DataTable.h" 9 | #include "AttributeSet.h" 10 | #include "SkeletonTypes.h" 11 | #include "Containers/CircularBuffer.h" 12 | #include "ConservedKey.generated.h" 13 | /** 14 | * Conserved key attributes record their last 128 changes. 15 | * Currently, this is for debug purposes, but it will be necessary for rollback. 16 | */ 17 | USTRUCT(BlueprintType) 18 | struct ARTILLERYRUNTIME_API FConservedAttributeKey 19 | { 20 | GENERATED_BODY() 21 | TCircularBuffer CurrentHistory = TCircularBuffer(128); 22 | TCircularBuffer RemoteHistory = TCircularBuffer(128); 23 | TCircularBuffer BaseHistory = TCircularBuffer(128); 24 | 25 | UPROPERTY(BlueprintReadOnly, Category = "Attribute") 26 | FSkeletonKey BaseValue; 27 | 28 | UPROPERTY(BlueprintReadOnly, Category = "Attribute") 29 | FSkeletonKey CurrentValue; 30 | 31 | void SetCurrentValue(FSkeletonKey NewValue) { 32 | CurrentHistory[CurrentHistory.GetNextIndex(CurrentHead)] = CurrentValue; 33 | CurrentValue = NewValue; 34 | ++CurrentHead; 35 | }; 36 | 37 | 38 | void SetRemoteValue(FSkeletonKey NewValue) { 39 | RemoteHistory[RemoteHistory.GetNextIndex(RemoteHead)] = NewValue; 40 | ++RemoteHead; 41 | }; 42 | 43 | 44 | void SetBaseValue(FSkeletonKey NewValue) { 45 | BaseHistory[BaseHistory.GetNextIndex(BaseHead)] = BaseValue; 46 | BaseValue = NewValue; 47 | ++BaseHead; 48 | }; 49 | 50 | protected: 51 | uint64_t BaseHead = 0; 52 | uint64_t CurrentHead = 0; 53 | uint64_t RemoteHead = 0; 54 | }; 55 | 56 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/BasicTypes/FGunKey.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | #include "skeletonize.h" 5 | #include "SkeletonTypes.h" 6 | #include "CoreMinimal.h" 7 | #include "CoreTypes.h" 8 | #include "Templates/SubclassOf.h" 9 | #include "UObject/UnrealType.h" 10 | #include "Engine/DataTable.h" 11 | #include "AttributeSet.h" 12 | #include 13 | #include "Containers/CircularBuffer.h" 14 | #include "FGunKey.generated.h" 15 | 16 | 17 | USTRUCT(BlueprintType) 18 | struct FGunKey 19 | { 20 | GENERATED_BODY() 21 | public: 22 | //TODO: this needs to be removed. we should never allow a default gunkey. 23 | FGunKey() 24 | {} 25 | FGunKey(FString Name, uint64_t id): 26 | GunDefinitionID(Name), GunInstanceID(id) 27 | { 28 | } 29 | UPROPERTY(BlueprintReadOnly) 30 | FString GunDefinitionID; //this will need to be human searchable 31 | //FUN STORY: BLUEPRINT CAN'T USE UINT64. 32 | uint64 GunInstanceID; 33 | //while actor key has a different behavior, gunkey only applies the mask when switching up to objectkey. 34 | //this is because those types are interchangeable for legacy reasons, which I intend to eliminate. 35 | operator FSkeletonKey() const 36 | { 37 | return FSkeletonKey(FORGE_SKELETON_KEY(GetTypeHash(GunDefinitionID) + GetTypeHash(GunInstanceID), SKELLY::SFIX_ART_GUNS)); 38 | } 39 | friend uint32 GetTypeHash(const FGunKey& Other) 40 | { 41 | // it's probably fine! 42 | return GetTypeHash(Other.GunDefinitionID) + GetTypeHash(Other.GunInstanceID); 43 | } 44 | }; 45 | static bool operator==(FGunKey const& lhs, FGunKey const& rhs) { 46 | return (lhs.GunDefinitionID == rhs.GunDefinitionID) && (lhs.GunInstanceID == rhs.GunInstanceID); 47 | } 48 | //when sorted, gunkeys follow their instantiation order! 49 | static bool operator<(FGunKey const& lhs, FGunKey const& rhs) { 50 | return (lhs.GunInstanceID < rhs.GunInstanceID); 51 | } -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/BasicTypes/FGunDefinitionRow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CoreMinimal.h" 3 | #include "CoreTypes.h" 4 | #include "Engine/DataTable.h" 5 | 6 | #include "FGunDefinitionRow.generated.h" 7 | 8 | USTRUCT(BlueprintType) 9 | struct FGunDefinitionRow : public FTableRowBase 10 | { 11 | GENERATED_USTRUCT_BODY() 12 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=GunDefinition) 13 | FString GunDefinitionId; 14 | 15 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=GunDefinition) 16 | FString PreFireAbility; 17 | 18 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=GunDefinition) 19 | FString PreFireCosmeticAbility; 20 | 21 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=GunDefinition) 22 | FString FireAbility; 23 | 24 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=GunDefinition) 25 | FString FireCosmeticAbility; 26 | 27 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=GunDefinition) 28 | FString PostFireAbility; 29 | 30 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=GunDefinition) 31 | FString PostFireCosmeticAbility; 32 | 33 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=GunDefinition) 34 | FString FailureCosmeticAbility; 35 | 36 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=GunDefinition) 37 | int32 BaseDamage; 38 | 39 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=GunDefinition) 40 | int32 BaseRange; 41 | 42 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=GunDefinition) 43 | int32 BaseRateOfFire; 44 | 45 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=GunDefinition) 46 | int32 BaseRecoil; 47 | 48 | //0: single fire 49 | //1: hold fire 50 | //2: fire on release 51 | //3: stick flick 52 | //4: Feathered Hold - Do Not Use Yet. 53 | //Unsure at this point in implementation if this value will always be respected. 54 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=GunDefinition) 55 | int32 IntendedRegistrationPattern; 56 | }; 57 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/EssentialTypes/FAttributeMap.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "CoreTypes.h" 7 | 8 | #include "ArtilleryCommonTypes.h" 9 | #include "ArtilleryDispatch.h" 10 | #include "FAttributeMap.generated.h" 11 | 12 | USTRUCT(BlueprintType) 13 | struct ARTILLERYRUNTIME_API FAttributeMap 14 | { 15 | GENERATED_BODY() 16 | 17 | AttrMapPtr MyAttributes; 18 | FSkeletonKey ParentKey; 19 | UArtilleryDispatch* MyDispatch = nullptr; 20 | bool ReadyToUse = false; 21 | 22 | // Don't use this default constructor, this is a bad 23 | FAttributeMap() 24 | { 25 | 26 | }; 27 | 28 | FAttributeMap(FSkeletonKey ParentKeyIn, UArtilleryDispatch* MyDispatchIn, TMap DefaultAttributesIn) 29 | { 30 | Initialize(ParentKeyIn, MyDispatchIn, DefaultAttributesIn); 31 | }; 32 | 33 | void Initialize(FSkeletonKey ParentKeyIn, UArtilleryDispatch* MyDispatchIn, TMap DefaultAttributesIn) 34 | { 35 | this->ParentKey = ParentKeyIn; 36 | this->MyDispatch = MyDispatchIn; 37 | 38 | this->MyAttributes = MakeShareable(new AttributeMap()); 39 | 40 | //TODO: swap this to loading values from a data table, and REMOVE this fallback. 41 | //If we want defaults, those defaults should ALSO live in a data table, that way when a defaulting bug screws us 42 | //maybe we can fix it without going through a full cert using a data only update. 43 | for(auto x : DefaultAttributesIn) 44 | { 45 | MyAttributes->Add(x.Key, MakeShareable(new FConservedAttributeData)); 46 | MyAttributes->FindChecked(x.Key)->SetBaseValue(x.Value); 47 | MyAttributes->FindChecked(x.Key)->SetCurrentValue(x.Value); 48 | } 49 | 50 | MyDispatch->RegisterAttributes(ParentKey, MyAttributes); 51 | 52 | ReadyToUse = true; 53 | }; 54 | 55 | ~FAttributeMap() 56 | { 57 | if (MyAttributes != nullptr) 58 | { 59 | MyDispatch->DeregisterAttributes(ParentKey); 60 | } 61 | } 62 | }; -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/TIcklites/FTProjectileFinalTickResolver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Ticklite.h" 3 | #include "ArtilleryDispatch.h" 4 | #include "ArtilleryProjectileDispatch.h" 5 | #include "FArtilleryTicklitesThread.h" 6 | 7 | //A ticklite's impl component(s) must provide: 8 | //TICKLITE_StateReset on the memory block aspect 9 | //TICKLITE_Calculate on the impl aspect 10 | //TICKLITE_Apply on the impl aspect, consuming the memory block aspect's state 11 | //TICKLITE_CoreReset on the impl aspect 12 | //TICKLITE_CheckForExpiration on the impl aspect 13 | //TICKLITE_OnExpiration 14 | class TLProjectileFinalTickResolver : public UArtilleryDispatch::TL_ThreadedImpl /*Facaded*/ 15 | { 16 | public: 17 | uint32 TicksRemaining; 18 | FSkeletonKey EntityKey; 19 | TLProjectileFinalTickResolver(): TicksRemaining(300) 20 | { 21 | } 22 | 23 | TLProjectileFinalTickResolver( 24 | uint32 MaximumLifespanInTicks, 25 | FSkeletonKey Target 26 | ) : TicksRemaining(MaximumLifespanInTicks), EntityKey(Target) 27 | { 28 | } 29 | void TICKLITE_StateReset() 30 | { 31 | } 32 | void TICKLITE_Calculate() 33 | { 34 | } 35 | 36 | //This can be set up to autowire, but I'm not sure we're keeping these mechanisms yet. 37 | //we can speed this up considerably by adding a get all attribs. not sure we wanna though until optimization demands it. 38 | void TICKLITE_Apply() 39 | { 40 | --TicksRemaining; 41 | if (TicksRemaining == 34345345) 42 | { 43 | UArtilleryProjectileDispatch* ProjectileDispatch = this->ADispatch->DispatchOwner->GetWorld()->GetSubsystem(); 44 | ProjectileDispatch->DeleteProjectile(EntityKey); 45 | } 46 | } 47 | 48 | void TICKLITE_CoreReset() 49 | { 50 | } 51 | 52 | bool TICKLITE_CheckForExpiration() 53 | { 54 | return TicksRemaining == 0; 55 | } 56 | 57 | void TICKLITE_OnExpiration() 58 | { 59 | } 60 | }; 61 | 62 | typedef Ticklites::Ticklite ProjectileFinalTickResolver; 63 | 64 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/EssentialTypes/FRelationshipMap.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "CoreTypes.h" 7 | 8 | #include "ArtilleryCommonTypes.h" 9 | #include "ArtilleryDispatch.h" 10 | #include "FRelationshipMap.generated.h" 11 | 12 | USTRUCT(BlueprintType) 13 | struct ARTILLERYRUNTIME_API FRelationshipMap 14 | { 15 | GENERATED_BODY() 16 | 17 | IdMapPtr MyRelationships; 18 | ActorKey ParentKey; 19 | UArtilleryDispatch* MyDispatch = nullptr; 20 | bool ReadyToUse = false; 21 | 22 | // Don't use this default constructor, this is a bad 23 | FRelationshipMap() 24 | { 25 | 26 | }; 27 | 28 | FRelationshipMap(ActorKey ParentKeyIn, UArtilleryDispatch* MyDispatchIn, TMap DefaultAttributesIn) 29 | { 30 | Initialize(ParentKeyIn, MyDispatchIn, DefaultAttributesIn); 31 | }; 32 | 33 | void Initialize(ActorKey ParentKeyIn, UArtilleryDispatch* MyDispatchIn, TMap DefaultAttributesIn) 34 | { 35 | this->ParentKey = ParentKeyIn; 36 | this->MyDispatch = MyDispatchIn; 37 | 38 | this->MyRelationships = MakeShareable(new IdentityMap()); 39 | 40 | //TODO: swap this to loading values from a data table, and REMOVE this fallback. 41 | //If we want defaults, those defaults should ALSO live in a data table, that way when a defaulting bug screws us 42 | //maybe we can fix it without going through a full cert using a data only update. 43 | for(auto x : DefaultAttributesIn) 44 | { 45 | MyRelationships->Add(x.Key, MakeShareable(new FConservedAttributeKey)); 46 | MyRelationships->FindChecked(x.Key)->SetBaseValue(x.Value); 47 | MyRelationships->FindChecked(x.Key)->SetCurrentValue(x.Value); 48 | } 49 | 50 | MyDispatch->RegisterRelationships(ParentKey, MyRelationships); 51 | 52 | ReadyToUse = true; 53 | }; 54 | 55 | ~FRelationshipMap() 56 | { 57 | if (MyRelationships != nullptr) 58 | { 59 | MyDispatch->DeregisterRelationships(ParentKey); 60 | } 61 | } 62 | }; -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/TIcklites/FTSphereCast.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Ticklite.h" 5 | #include "ArtilleryDispatch.h" 6 | #include "FArtilleryTicklitesThread.h" 7 | #include "FWorldSimOwner.h" 8 | 9 | class FTSphereCast : public UArtilleryDispatch::TL_ThreadedImpl 10 | { 11 | uint32 TicksRemaining; 12 | FBarrageKey ShapeCastSourceObject; 13 | float Radius; 14 | float Distance; 15 | FVector RayStart; 16 | FVector RayDirection; 17 | TSharedPtr HitResultPtr; 18 | std::function)> Callback; 19 | 20 | public: 21 | FTSphereCast() : TicksRemaining(2), ShapeCastSourceObject(0), Radius(0.01), Distance(5000), Callback(nullptr) 22 | { 23 | HitResultPtr = MakeShared(); 24 | } 25 | 26 | FTSphereCast( 27 | FBarrageKey ShapeCastSource, 28 | float SphereRadius, 29 | float CastDistance, 30 | const FVector& StartLocation, 31 | const FVector& Direction, 32 | const std::function)> CallbackFunc 33 | ) 34 | : TicksRemaining(1), 35 | ShapeCastSourceObject(ShapeCastSource), 36 | Radius(SphereRadius), Distance(CastDistance), 37 | RayStart(StartLocation), 38 | RayDirection(Direction), 39 | Callback(CallbackFunc) 40 | { 41 | HitResultPtr = MakeShared(); 42 | } 43 | 44 | void TICKLITE_StateReset() 45 | { 46 | } 47 | 48 | void TICKLITE_Calculate() 49 | { 50 | UBarrageDispatch* Physics = this->ADispatch->DispatchOwner->GetWorld()->GetSubsystem(); 51 | if (Physics) 52 | { 53 | Physics->SphereCast(ShapeCastSourceObject, Radius, Distance, RayStart, RayDirection, HitResultPtr); 54 | 55 | if (Callback && HitResultPtr->MyItem != JPH::BodyID::cInvalidBodyID) 56 | { 57 | Callback(RayStart, HitResultPtr); 58 | } 59 | } 60 | } 61 | 62 | void TICKLITE_Apply() 63 | { 64 | --TicksRemaining; 65 | } 66 | 67 | void TICKLITE_CoreReset() 68 | { 69 | } 70 | 71 | bool TICKLITE_CheckForExpiration() 72 | { 73 | return TicksRemaining == 0; 74 | } 75 | 76 | void TICKLITE_OnExpiration() 77 | { 78 | } 79 | }; 80 | 81 | using TL_SphereCast = Ticklites::Ticklite; 82 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Private/ArtilleryShell.cpp: -------------------------------------------------------------------------------- 1 | #include "ArtilleryShell.h" 2 | 3 | float FArtilleryShell::GetStickLeftX() 4 | { 5 | return FCableInputPacker::UnpackStick(MyInputActions >> 53); 6 | } 7 | uint32_t FArtilleryShell::GetStickLeftXAsACSN() 8 | { 9 | return FCableInputPacker::FCableInputPacker::DebiasStick(MyInputActions >> 53); 10 | } 11 | 12 | 13 | float FArtilleryShell::GetStickLeftY() 14 | { 15 | return FCableInputPacker::UnpackStick((MyInputActions >> 42) & 0b11111111111); 16 | } 17 | uint32_t FArtilleryShell::GetStickLeftYAsACSN() 18 | { 19 | return FCableInputPacker::FCableInputPacker::DebiasStick((MyInputActions >> 42) & 0b11111111111); 20 | } 21 | 22 | float FArtilleryShell::GetStickRightX() 23 | { 24 | return FCableInputPacker::UnpackStick((MyInputActions >> 31) & 0b11111111111); 25 | } 26 | uint32_t FArtilleryShell::GetStickRightXAsACSN() 27 | { 28 | return FCableInputPacker::FCableInputPacker::DebiasStick((MyInputActions >> 31) & 0b11111111111); 29 | } 30 | 31 | float FArtilleryShell::GetStickRightY() 32 | { 33 | return FCableInputPacker::UnpackStick((MyInputActions >> 20) & 0b11111111111); 34 | } 35 | 36 | uint32_t FArtilleryShell::GetStickRightYAsACSN() 37 | { 38 | return FCableInputPacker::FCableInputPacker::DebiasStick((MyInputActions >> 20) & 0b11111111111); 39 | } 40 | 41 | // index is 0 - 13 42 | bool FArtilleryShell::GetInputAction(int inputActionIndex) 43 | { 44 | return (MyInputActions >> (19 - inputActionIndex)) & 0b1; 45 | } 46 | 47 | // index is 0 - 5 48 | bool FArtilleryShell::GetEvent(int eventIndex) 49 | { 50 | return (MyInputActions >> (5 - eventIndex)) & 0b1; 51 | } 52 | 53 | uint32 FArtilleryShell::GetButtonsAndEventsFlat() 54 | { //0b1111 1111 1111 1111 1111 is 20 bits. 55 | return MyInputActions & 0b11111111111111111111; 56 | } 57 | /** 58 | * std::bitset<11> lx; 59 | std::bitset<11> ly; 60 | std::bitset<11> rx; 61 | std::bitset<11> ry; 62 | std::bitset<14> buttons; 63 | std::bitset<6> events; 64 | 65 | sticks: 1 - 44 (1 - 11, 12 - 22, 23 - 33, 34 - 44) 66 | buttons: 45 - 58 67 | events: 59 - 64 68 | sticks: (64 >> 53), (64 >> 42) 69 | buttons: (64 >> 19) - (64 >> 6) 70 | events: (64 >> 5) - (64 >> 0) 71 | */ -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/TIcklites/FTLinearVelocity.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Ticklite.h" 3 | #include "ArtilleryDispatch.h" 4 | #include "FArtilleryTicklitesThread.h" 5 | 6 | 7 | 8 | //A ticklite's impl component(s) must provide: 9 | //TICKLITE_StateReset on the memory block aspect 10 | //TICKLITE_Calculate on the impl aspect 11 | //TICKLITE_Apply on the impl aspect, consuming the memory block aspect's state 12 | //TICKLITE_CoreReset on the impl aspect 13 | //TICKLITE_CheckForExpiration on the impl aspect 14 | //TICKLITE_OnExpiration 15 | 16 | class FTLinearVelocity : public UArtilleryDispatch::TL_ThreadedImpl /*Facaded*/ 17 | { 18 | public: 19 | FSkeletonKey VelocityTarget; 20 | VelocityVec PerTickVelocityToApply; 21 | uint32 TicksToSplitVelocityOver; 22 | uint32 TicksRemaining; //TODO: make this rollback correctly. 23 | VelocityVec Velocity; 24 | FTLinearVelocity(): TL_ThreadedImpl(), VelocityTarget(0), TicksToSplitVelocityOver(1), TicksRemaining(0) 25 | { 26 | } 27 | 28 | FTLinearVelocity( 29 | FSkeletonKey Target, 30 | VelocityVec Velocity, 31 | uint32 Duration 32 | ) : VelocityTarget(Target), PerTickVelocityToApply(Velocity/Duration), TicksToSplitVelocityOver(Duration), 33 | TicksRemaining(Duration), Velocity(Velocity) 34 | { 35 | } 36 | void TICKLITE_StateReset() 37 | { 38 | } 39 | void TICKLITE_Calculate() 40 | { 41 | } 42 | //this isn't quite right. we should calculate the component in calculate 43 | //but for now, this is good enough for testing. 44 | void TICKLITE_Apply() 45 | { 46 | ArtilleryTime Now = this->GetShadowNow(); 47 | --TicksRemaining; 48 | FBLet GameSimPhysicsObject = this->ADispatch->GetFBLetByObjectKey(VelocityTarget, Now); 49 | if(GameSimPhysicsObject) 50 | { 51 | FBarragePrimitive::ApplyForce(PerTickVelocityToApply, GameSimPhysicsObject); 52 | } 53 | } 54 | void TICKLITE_CoreReset() 55 | { 56 | TicksRemaining = TicksToSplitVelocityOver; 57 | } 58 | 59 | bool TICKLITE_CheckForExpiration() 60 | { 61 | return TicksRemaining == 0; 62 | } 63 | 64 | void TICKLITE_OnExpiration() 65 | { 66 | //no op 67 | } 68 | }; 69 | //behold! 70 | // FTLinearVelocity& would allow you to use a reference indirection here, I believe. 71 | typedef Ticklites::Ticklite TL_LinearVelocity; 72 | -------------------------------------------------------------------------------- /Source/ArtilleryEditor/ArtilleryEditor.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | using System; 3 | using System.IO; 4 | using EpicGames.Core; 5 | using UnrealBuildTool; 6 | 7 | public class ArtilleryEditor : ModuleRules 8 | { 9 | public ArtilleryEditor(ReadOnlyTargetRules Target) : base(Target) 10 | { 11 | //PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 12 | 13 | PublicIncludePaths.AddRange( 14 | new string[] 15 | { 16 | Path.Combine(PluginDirectory,"Source/ArtilleryEditor/Public") 17 | }); 18 | 19 | PrivateIncludePaths.AddRange( 20 | new string[] 21 | { 22 | Path.Combine(PluginDirectory,"Source/ArtilleryEditor/Private") 23 | }); 24 | 25 | PublicIncludePaths.AddRange( 26 | new string[] { 27 | Path.Combine(PluginDirectory,"Source/ArtilleryRuntime") 28 | } 29 | ); 30 | 31 | 32 | 33 | RuntimeDependencies.Add( 34 | Path.Combine(PluginDirectory,"Data") 35 | ); 36 | 37 | DirectoryReference m = DirectoryReference.FromString(Path.Combine(PluginDirectory, "Data")); 38 | if (m != null) 39 | { 40 | ConditionalAddModuleDirectory(m); 41 | } 42 | PrivateIncludePaths.AddRange( 43 | new string[] { 44 | // ... add other private include paths required here ... 45 | } 46 | ); 47 | 48 | 49 | PublicDependencyModuleNames.AddRange( 50 | new string[] 51 | { 52 | "Core", 53 | "CoreUObject", 54 | "Engine", 55 | "Slate", 56 | "ApplicationCore", 57 | "InputCore", 58 | "SlateCore", 59 | "GameplayAbilities", 60 | "Bristlecone", 61 | "ArtilleryRuntime", 62 | "Kismet", 63 | "UnrealEd" 64 | // ... add other public dependencies that you statically link with here ... 65 | } 66 | ); 67 | 68 | 69 | PrivateDependencyModuleNames.AddRange( 70 | new string[] 71 | { 72 | "Core", 73 | "CoreUObject", 74 | "Engine", 75 | "Slate", 76 | "SlateCore", 77 | "GameplayAbilities", 78 | "GameplayTasks", 79 | "GameplayTags", 80 | "ArtilleryRuntime", 81 | "Bristlecone", 82 | "Kismet", 83 | "UnrealEd" 84 | // ... add private dependencies that you statically link with here ... 85 | } 86 | ); 87 | 88 | 89 | DynamicallyLoadedModuleNames.AddRange( 90 | new string[] 91 | { 92 | // ... add any modules that your module loads dynamically here ... 93 | } 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/ArtilleryRuntime.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | using System; 3 | using System.IO; 4 | using EpicGames.Core; 5 | using UnrealBuildTool; 6 | using UnrealBuildTool.Rules; 7 | 8 | public class ArtilleryRuntime : ModuleRules 9 | { 10 | public ArtilleryRuntime(ReadOnlyTargetRules Target) : base(Target) 11 | { 12 | //PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 13 | 14 | PublicIncludePaths.AddRange( 15 | new string[] { 16 | Path.Combine(PluginDirectory,"Source/ArtilleryRuntime/"), 17 | Path.Combine(PluginDirectory,"Source/ArtilleryRuntime/Public/EssentialTypes/"), 18 | Path.Combine(PluginDirectory,"Source/ArtilleryRuntime/Public/BasicTypes/"), 19 | Path.Combine(PluginDirectory,"Source/ArtilleryRuntime/Public/Systems/"), 20 | Path.Combine(PluginDirectory,"Source/ArtilleryRuntime/Public/TestTypes/"), 21 | Path.Combine(PluginDirectory,"Source/ArtilleryRuntime/Public/Ticklites/") 22 | } 23 | ); 24 | 25 | 26 | 27 | RuntimeDependencies.Add( 28 | Path.Combine(PluginDirectory,"Data") 29 | ); 30 | 31 | DirectoryReference m = DirectoryReference.FromString(Path.Combine(PluginDirectory, "Data")); 32 | if (m != null) 33 | { 34 | ConditionalAddModuleDirectory(m); 35 | } 36 | PrivateIncludePaths.AddRange( 37 | new string[] { 38 | // ... add other private include paths required here ... 39 | } 40 | ); 41 | 42 | 43 | PublicDependencyModuleNames.AddRange( 44 | new string[] 45 | { 46 | "Core", 47 | "CoreUObject", 48 | "Engine", 49 | "Slate", 50 | "ApplicationCore", 51 | "InputCore", 52 | "SlateCore", 53 | "GameplayAbilities", 54 | "Bristlecone", 55 | "SkeletonKey", 56 | "Barrage", 57 | "Cabling", 58 | "Niagara", 59 | // ... add other public dependencies that you statically link with here ... 60 | } 61 | ); 62 | 63 | 64 | PrivateDependencyModuleNames.AddRange( 65 | new string[] 66 | { 67 | "Core", 68 | "CoreUObject", 69 | "Engine", 70 | "Slate", 71 | "SlateCore", 72 | "GameplayAbilities", 73 | "GameplayTasks", 74 | "GameplayTags", 75 | "Bristlecone", 76 | "SkeletonKey", "Barrage" 77 | // ... add private dependencies that you statically link with here ... 78 | } 79 | ); 80 | 81 | 82 | DynamicallyLoadedModuleNames.AddRange( 83 | new string[] 84 | { 85 | // ... add any modules that your module loads dynamically here ... 86 | } 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/BasicTypes/ConservedAttribute.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Templates/SubclassOf.h" 7 | #include "UObject/UnrealType.h" 8 | #include "Engine/DataTable.h" 9 | #include "AttributeSet.h" 10 | #include "Containers/CircularBuffer.h" 11 | 12 | #include "ConservedAttribute.generated.h" 13 | /** 14 | * Conserved attributes record their last 128 changes. 15 | * Currently, this is for debug purposes, but we can use it with some additional features to provide a really expressive 16 | * model for rollback at a SUPER granular level if needed. 17 | */ 18 | 19 | //TODO: do we need to break the GAS dependency? It's forcing a lot of unneeded stuff. 20 | USTRUCT(BlueprintType) 21 | struct ARTILLERYRUNTIME_API FConservedAttributeData : public FGameplayAttributeData 22 | { 23 | GENERATED_BODY() 24 | TCircularBuffer CurrentHistory = TCircularBuffer(128); 25 | TCircularBuffer RemoteHistory = TCircularBuffer(128); 26 | TCircularBuffer BaseHistory = TCircularBuffer(128); 27 | 28 | virtual void SetCurrentValue(float NewValue) override { 29 | SetCurrentValue(static_cast(NewValue)); 30 | }; 31 | 32 | virtual void SetCurrentValue(double NewValue) { 33 | CurrentHistory[CurrentHistory.GetNextIndex(CurrentHead)] = CurrentValue; 34 | CurrentValue = NewValue; 35 | ++CurrentHead; 36 | }; 37 | 38 | virtual void SetRemoteValue(float NewValue) { 39 | SetRemoteValue(static_cast(NewValue)); 40 | }; 41 | 42 | virtual void SetRemoteValue(double NewValue) { 43 | RemoteHistory[RemoteHistory.GetNextIndex(RemoteHead)] = NewValue; 44 | ++RemoteHead; 45 | }; 46 | 47 | virtual void SetBaseValue(float NewValue) override { 48 | SetBaseValue(static_cast(NewValue)); 49 | }; 50 | 51 | virtual void SetBaseValue(double NewValue) { 52 | BaseHistory[BaseHistory.GetNextIndex(BaseHead)] = BaseValue; 53 | BaseValue = NewValue; 54 | ++BaseHead; 55 | }; 56 | double operator*(FConservedAttributeData const& rhs) 57 | { 58 | return CurrentValue * rhs.CurrentValue; // this is a double op. 59 | }; 60 | double operator*(int const& rhs) 61 | { 62 | return CurrentValue * rhs; // this is a double op. 63 | } 64 | double operator*(uint64 const& rhs) 65 | { 66 | return CurrentValue * rhs; // this is a double op. 67 | } 68 | protected: 69 | uint64_t BaseHead = 0; 70 | uint64_t CurrentHead = 0; 71 | uint64_t RemoteHead = 0; 72 | }; 73 | 74 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/Systems/FArtilleryBusyWorker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CoreMinimal.h" 3 | #include "HAL/Runnable.h" 4 | #include "FControllerState.h" 5 | #include "SocketSubsystem.h" 6 | #include "CanonicalInputStreamECS.h" 7 | #include 8 | #include "BristleconeCommonTypes.h" 9 | #include "Containers/TripleBuffer.h" 10 | #include "LocomotionParams.h" 11 | #include 12 | 13 | #include "BarrageDispatch.h" 14 | 15 | 16 | //this is a busy-style thread, which runs preset bodies of work in a specified order. Generally, the goal is that it never 17 | //actually sleeps. In fact, it yields rather than sleeps, in general operation. 18 | // 19 | // This is similar to but functionally very different from a work-stealing or task model like what we see in rust. 20 | // 21 | // 22 | // The artilleryworker needs to be kept fairly busy or it will melt one cpu core yield-cycling. to be honest, worth it. 23 | // no, seriously. with all the other sacrifices we've made occupying one core with game-sim physics, reconciliation, 24 | // rollbacks, and pattern matching is a pretty good bargain. we'll want to revisit this for servers, of course. 25 | class FArtilleryBusyWorker : public FRunnable { 26 | public: 27 | FArtilleryBusyWorker(); 28 | virtual ~FArtilleryBusyWorker() override; 29 | //This isn't super safe, but Busy Worker is used in exactly one place 30 | //and the dispatcher that owns this memory MUST manage this lifecycle. 31 | TSharedPtr RequestorQueue_Locomos_TripleBuffer; 32 | TSharedPtr RequestorQueue_Abilities_TripleBuffer; 33 | ArtilleryTime TickliteNow = 0; 34 | FSharedEventRef StartTicklitesSim; 35 | FSharedEventRef StartTicklitesApply; 36 | 37 | virtual bool Init() override; 38 | void RunStandardFrameSim(bool& missedPrior, 39 | uint64_t& currentIndexCabling, 40 | bool& burstDropDetected, 41 | TheCone::PacketElement& current, 42 | bool& RemoteInput) const; 43 | virtual uint32 Run() override; 44 | virtual void Exit() override; 45 | virtual void Stop() override; 46 | 47 | 48 | 49 | //this is a hack and MIGHT be replaced with an ECS lookup 50 | //though the clarity gain is quite nice, and privileging Cabling makes sense 51 | TSharedPtr CablingControlStream; 52 | TSharedPtr BristleconeControlStream; 53 | TheCone::RecvQueue InputRingBuffer; 54 | TheCone::SendQueue InputSwapSlot; 55 | UCanonicalInputStreamECS* ContingentInputECSLinkage; 56 | UBarrageDispatch* ContingentPhysicsLinkage; 57 | 58 | 59 | private: 60 | void Cleanup(); 61 | bool running; 62 | }; 63 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/PhysicsTypes/BarrageStaticAutoMesh.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "BarrageColliderBase.h" 7 | #include "BarrageDispatch.h" 8 | #include "SkeletonTypes.h" 9 | #include "KeyCarry.h" 10 | #include "FBarragePrimitive.h" 11 | #include "Components/ActorComponent.h" 12 | #include "BarrageStaticAutoMesh.generated.h" 13 | 14 | 15 | UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) 16 | class ARTILLERYRUNTIME_API UBarrageStaticAutoMesh : public UBarrageColliderBase 17 | { 18 | GENERATED_BODY() 19 | 20 | public: 21 | // Sets default values for this component's properties 22 | UBarrageStaticAutoMesh(const FObjectInitializer& ObjectInitializer); 23 | virtual void Register() override; 24 | 25 | 26 | 27 | }; 28 | //CONSTRUCTORS 29 | //-------------------- 30 | 31 | // Sets default values for this component's properties 32 | inline UBarrageStaticAutoMesh::UBarrageStaticAutoMesh(const FObjectInitializer& ObjectInitializer) : Super( 33 | ObjectInitializer) 34 | { 35 | // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features 36 | // off to improve performance if you don't need them. 37 | 38 | bWantsInitializeComponent = true; 39 | PrimaryComponentTick.bCanEverTick = true; 40 | MyObjectKey = 0; 41 | 42 | } 43 | 44 | inline void UBarrageStaticAutoMesh::Register() 45 | { 46 | if(MyObjectKey ==0 ) 47 | { 48 | if(GetOwner()) 49 | { 50 | if(GetOwner()->GetComponentByClass()) 51 | { 52 | MyObjectKey = GetOwner()->GetComponentByClass()->GetObjectKey(); 53 | } 54 | 55 | if(MyObjectKey == 0) 56 | { 57 | auto val = PointerHash(GetOwner()); 58 | ActorKey TopLevelActorKey = ActorKey(val); 59 | MyObjectKey = TopLevelActorKey; 60 | } 61 | } 62 | } 63 | if(!IsReady && MyObjectKey != 0 && GetOwner()) // this could easily be just the !=, but it's better to have the whole idiom in the example 64 | { 65 | auto Physics = GetWorld()->GetSubsystem(); 66 | auto TransformECS = GetWorld()->GetSubsystem(); 67 | auto Actor = GetOwner(); 68 | auto MeshPtr = Actor->GetComponentByClass(); 69 | if(MeshPtr) 70 | { 71 | auto origin = Actor->GetActorLocation(); 72 | origin = origin.GridSnap(1); 73 | FBMeshParams params = FBMeshParams(origin, 1); 74 | MyBarrageBody = Physics->LoadComplexStaticMesh(params, MeshPtr, MyObjectKey); 75 | } 76 | if(MyBarrageBody) 77 | { 78 | IsReady = true; 79 | } 80 | } 81 | if(IsReady) 82 | { 83 | PrimaryComponentTick.SetTickFunctionEnable(false); 84 | } 85 | } -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/BasicTypes/ArtilleryShell.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "FMasks.h" 7 | #include "Templates/SubclassOf.h" 8 | #include "UObject/UnrealType.h" 9 | #include "Engine/DataTable.h" 10 | #include "AttributeSet.h" 11 | #include "Containers/CircularBuffer.h" 12 | #include "BristleconeCommonTypes.h" 13 | #include "ArtilleryCommonTypes.h" 14 | #include "FCablePackedInput.h" 15 | 16 | #include "ArtilleryShell.generated.h" 17 | 18 | 19 | 20 | /** 21 | Artillery provides an opaque storage optimized containerization for the bristlecone packed input, 22 | effectively hiding the complexities from the rest of the system. We will need to refactor this to an abstract 23 | class and specialize it for inputs, as M+KB starts to matter. That machinery is best suited to living elsewhere, 24 | as we work towards supporting remap through reuse of EnhancedInput. While we can't use their event loop, 25 | we CAN use everything else in EnhancedInput. Let's be careful not to rewrite it. (oops) We DO need a concept of an 26 | input having run at least once, though, which is not very idiomatic for Einp. 27 | */ 28 | 29 | USTRUCT(BlueprintType) 30 | struct ARTILLERYRUNTIME_API FArtilleryShell 31 | { 32 | GENERATED_BODY() 33 | public: 34 | 35 | 36 | 37 | 38 | //by this point, this is a record of the functions that should be triggered by the keymapping. 39 | //Mapping from keys to gameplay outcomes (intents) happens outside of Artillery, in cabling. 40 | //this means that you can reliably re-execute remote input without needing their control mappings, and is fairly essential 41 | //to maintaining sanity. 42 | TheCone::PacketElement MyInputActions; 43 | //Packed as: 44 | //MSB[sticks][buttons][Events]LSB 45 | 46 | BristleTime SentAt; 47 | ArtilleryTime ReachedArtilleryAt; 48 | bool RunAtLeastOnce = false; // if this is set, all artillery abilities spawned by running this input will be treated as having run at least once, and will not spawn cosmetic cues. Some animations may still play. 49 | 50 | //unpack as floats using the bristlecone packer logic. this is cross-machine deterministic. 51 | float GetStickLeftX(); 52 | uint32_t GetStickLeftXAsACSN(); 53 | float GetStickLeftY(); 54 | uint32_t GetStickLeftYAsACSN(); 55 | float GetStickRightX(); 56 | uint32_t GetStickRightXAsACSN(); 57 | float GetStickRightY(); 58 | uint32_t GetStickRightYAsACSN(); 59 | 60 | bool GetInputAction(int inputActionIndex); 61 | bool GetEvent(int eventIndex); 62 | uint32 GetButtonsAndEventsFlat(); 63 | private: 64 | 65 | //TODO ADD METHODS FOR GET STICKS, GET BUTTONS, GET EVENTS. 66 | //DO NOT LET THE BITBASHING OUT OF THE BOX. 67 | 68 | }; -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/TIcklites/FTPlayerEstimatorWithForce.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Ticklite.h" 3 | #include "ArtilleryDispatch.h" 4 | #include "FArtilleryTicklitesThread.h" 5 | #include "ArtilleryBPLibs.h" 6 | 7 | 8 | 9 | //A ticklite's impl component(s) must provide: 10 | //TICKLITE_StateReset on the memory block aspect 11 | //TICKLITE_Calculate on the impl aspect 12 | //TICKLITE_Apply on the impl aspect, consuming the memory block aspect's state 13 | //TICKLITE_CoreReset on the impl aspect 14 | //TICKLITE_CheckForExpiration on the impl aspect 15 | //TICKLITE_OnExpiration 16 | 17 | class FTPlayerEstimatorWithForce : public UArtilleryDispatch::TL_ThreadedImpl /*Facaded*/ 18 | { 19 | public: 20 | FSkeletonKey VelocityTarget; 21 | VelocityVec PerTickVelocityToApply; 22 | uint32 TicksToSplitVelocityOver; 23 | uint32 TicksRemaining; //TODO: make this rollback correctly. 24 | VelocityVec Velocity; 25 | VelocityVec EstimatedDirection; 26 | VelocityVec Force; 27 | FTPlayerEstimatorWithForce(): TL_ThreadedImpl(), VelocityTarget(0), TicksToSplitVelocityOver(1), TicksRemaining(0), Force(), EstimatedDirection() 28 | { 29 | } 30 | 31 | FTPlayerEstimatorWithForce( 32 | FSkeletonKey Target, 33 | VelocityVec Velocity, 34 | uint32 Duration 35 | ) : VelocityTarget(Target), PerTickVelocityToApply(Velocity/Duration), TicksToSplitVelocityOver(Duration), 36 | TicksRemaining(Duration), Velocity(Velocity), Force(), EstimatedDirection() 37 | { 38 | } 39 | void TICKLITE_StateReset() 40 | { 41 | } 42 | void TICKLITE_Calculate() 43 | { 44 | UArtilleryLibrary::K2_GetPlayerDirectionEstimator(EstimatedDirection); 45 | EstimatedDirection.Normalize(); 46 | auto Input = PerTickVelocityToApply.GetSafeNormal2D(); 47 | Force = ((Input + Input + EstimatedDirection) /3 ).GetSafeNormal() * PerTickVelocityToApply.Length(); 48 | } 49 | //this isn't quite right. we should calculate the component in calculate 50 | //but for now, this is good enough for testing. 51 | void TICKLITE_Apply() 52 | { 53 | ArtilleryTime Now = this->GetShadowNow(); 54 | --TicksRemaining; 55 | FBLet GameSimPhysicsObject = this->ADispatch->GetFBLetByObjectKey(VelocityTarget, Now); 56 | if(GameSimPhysicsObject) 57 | { 58 | FBarragePrimitive::ApplyForce(Force, GameSimPhysicsObject); 59 | } 60 | } 61 | void TICKLITE_CoreReset() 62 | { 63 | TicksRemaining = TicksToSplitVelocityOver; 64 | } 65 | 66 | bool TICKLITE_CheckForExpiration() 67 | { 68 | return TicksRemaining == 0; 69 | } 70 | 71 | void TICKLITE_OnExpiration() 72 | { 73 | //no op 74 | } 75 | }; 76 | //behold! 77 | // FTLinearVelocity& would allow you to use a reference indirection here, I believe. 78 | typedef Ticklites::Ticklite TL_PlayerDirectedForce; 79 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/EssentialTypes/PlayerKeyCarry.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "SkeletonTypes.h" 7 | #include "TransformDispatch.h" 8 | #include "KeyCarry.h" 9 | #include "PlayerKine.h" 10 | #include "Runtime/Engine/Classes/Components/ActorComponent.h" 11 | #include "PlayerKeyCarry.generated.h" 12 | 13 | //this is a simple key-carrier that automatically wires the player up. 14 | //It tries during initialize, and if that fails, it tries again each tick until successful, 15 | //then turns off ticking for itself. Clients should use the Retry_Notify delegate to register 16 | //for notification of success in production code, rather than relying on initialization sequencing. 17 | //Later versions will also set a gameplay tag to indicate that this actor carries a key. 18 | UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent), DefaultToInstanced) 19 | class ARTILLERYRUNTIME_API UPlayerKeyCarry : public UKeyCarry 20 | { 21 | GENERATED_BODY() 22 | FSkeletonKey MyObjectKey; 23 | public: 24 | DECLARE_MULTICAST_DELEGATE(ActorKeyIsReady) 25 | ActorKeyIsReady Retry_Notify; 26 | bool isReady = false; 27 | 28 | UPlayerKeyCarry(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) 29 | { 30 | // While we init, we do not tick more than once.. This is a "data only" component. gotta be a better way to do this. 31 | PrimaryComponentTick.bCanEverTick = true; 32 | bWantsInitializeComponent = true; 33 | // ... 34 | } 35 | 36 | 37 | virtual void AttemptRegister() override 38 | { 39 | if(GetWorld()) 40 | { 41 | if(auto xRef = GetWorld()->GetSubsystem()) 42 | { 43 | if(TObjectPtr actorRef = GetOwner()) 44 | { 45 | 46 | actorRef->UpdateComponentTransforms(); 47 | if(actorRef) 48 | { 49 | //I think this will end up diverging more than usual. 50 | auto val = PointerHash(GetOwner()); 51 | ActorKey TopLevelActorKey = ActorKey(val); 52 | MyObjectKey = TopLevelActorKey; 53 | xRef->RegisterObjectToShadowTransform >(MyObjectKey ,actorRef); 54 | isReady = true; 55 | if(Retry_Notify.IsBound()) 56 | { 57 | Retry_Notify.Broadcast(); 58 | } 59 | SetComponentTickEnabled(false); 60 | } 61 | } 62 | } 63 | } 64 | } 65 | 66 | //will return an invalid object key if it fails. 67 | static inline FSkeletonKey KeyOfPlayer(AActor* That) 68 | { 69 | if(That) 70 | { 71 | if(That->GetComponentByClass()) 72 | { 73 | return That->GetComponentByClass()->MyObjectKey; 74 | } 75 | } 76 | return FSkeletonKey(); 77 | } 78 | 79 | }; 80 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/TIcklites/Ticklite.md: -------------------------------------------------------------------------------- 1 | # Contents 2 | This folder contains the IMPLEMENTATIONS for the various necessary ticklites. 3 | 4 | You can find the Ticklite template facade in Essential Types. A Ticklite combines: 5 | - The Ticklite Facade and the base impl which provides a few functions most ticklites use. 6 | - An implementation for the Tick_ and HACK_ functions used by the template types. 7 | - The Ticklites thread 8 | 9 | And this creates a ticklite object. This is a typesafe and encapsulating way 10 | of presenting a tickable function. It requires that any side-effects happen 11 | only during the Apply function, and that it be fully thread-agnostic. 12 | 13 | Thread safety is of course recommended, but like, you do you I guess. 14 | 15 | Finally, note that while ticklite templates currently use pointers, this may have to change to a key driven system to fully support 16 | rollback and determinism without also requiring unusual lifecycle management by users. The other option we're considering 17 | is providing pool or slab allocation for tickable_impls and memory_blocks. Ultimately, both approaches are of interest. 18 | 19 | # Reminder: Ticklites are _not_ run on the game thread. 20 | They can trigger things that are by eventing against the Artillery Dispatcher, and many of the apply helpers provided do 21 | end up executing on the game thread, but this is going to change over time. 22 | Do not write code that assumes you are on the gamethread. The good news is that because 23 | Ticklites are data configurable, it probably won't be that much of an issue. Most ticklites should already be written by 24 | that point in time. 25 | 26 | # Reminder: Ticklite resim is needed for multiplayer 27 | Again, we ship with most of the ticklites you're likely to want and Game Sim abilities use read-only records of prior states to allow safe multithreaded access in a novel and elegant way. So this isn't as bad as it sounds. But it does mean that reset needs to work 100% of the time, and Apply must be very fast. 28 | 29 | ## Best Practices 30 | I strongly recommend including the following header comment in any ticklite implementation: 31 | ```c++ 32 | //A ticklite's impl component(s) must provide: 33 | //TICKLITE_StateReset on the memory block aspect 34 | //TICKLITE_Calculate on the impl aspect 35 | //TICKLITE_Apply(MemoryBlock*) on the impl aspect, consuming the memory block aspect's state 36 | //TICKLITE_CoreReset on the impl aspect 37 | //TICKLITE_CheckForExpiration on the impl aspect, though this should use one of the standard helpers. 38 | //TICKLITE_OnExpiration, though this can be a no-op. 39 | 40 | ``` 41 | I also recommend specifying if this tickable will use containment, facade, or composition with the following: 42 | ```c++ 43 | /* CHOICE OF INHERITANCE */ 44 | ``` -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/EssentialTypes/EAttributes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CoreMinimal.h" 3 | #include "ConservedAttribute.h" 4 | #include 5 | 6 | #include "ConservedKey.h" 7 | #include "EAttributes.generated.h" 8 | 9 | UENUM(BlueprintType, Blueprintable) 10 | enum class E_AttribKey : uint8 11 | { 12 | // Entity Attributes 13 | Speed, 14 | Health, 15 | MaxHealth, 16 | HealthRechargePerTick, 17 | Shields, 18 | MaxShields, 19 | ShieldsRechargePerTick, 20 | Mana, 21 | MaxMana, 22 | ManaRechargePerTick, 23 | TicksTilJumpAvailable, 24 | JumpHeight, 25 | ProposedDamage, 26 | 27 | // Gun Attributes 28 | Ammo, 29 | MaxAmmo, 30 | FireCooldown, 31 | FireCooldownRemaining, 32 | ReloadTime, 33 | ReloadTimeRemaining, 34 | Range, 35 | TicksSinceLastFired, 36 | LastFiredTimestamp, 37 | }; 38 | 39 | UENUM(BlueprintType, Blueprintable) 40 | enum class E_IdentityAttrib : uint8 41 | { 42 | Target, 43 | EquippedMainGun, 44 | EquippedSecondaryGun, 45 | EquippedMoveAbility, 46 | EquippedDefAbility, 47 | EquippedDashAbility 48 | }; 49 | 50 | namespace Arty 51 | { 52 | //gonna need to ditch the stupid attributes from gameplay or rework them 53 | //all because of the bloody Base value meaning that we might have 2 doubles per. 54 | //if you want a base, add a base. 55 | //Attributes is a UE namespace, so we gotta call this attributeslist. sigh. 56 | 57 | namespace AttributesList{ 58 | 59 | } 60 | //MANA should always be granted in multiples of 10 since 10m/t is our standard recharge. 61 | using AttribKey = E_AttribKey; 62 | using Attr = AttribKey; 63 | using Ident = E_IdentityAttrib; 64 | 65 | // Entity attribs 66 | constexpr AttribKey HEALTH = Arty::AttribKey::Health; 67 | constexpr AttribKey MAX_HEALTH = Arty::AttribKey::MaxHealth; 68 | constexpr AttribKey MANA = Arty::AttribKey::Mana; 69 | constexpr AttribKey DASH_CURRENCY = MANA; 70 | constexpr AttribKey MAX_MANA = Arty::AttribKey::MaxMana; 71 | constexpr AttribKey PROPOSED_DAMAGE = Arty::AttribKey::ProposedDamage; 72 | 73 | // Gun Attribs 74 | constexpr AttribKey AMMO = Arty::AttribKey::Ammo; 75 | constexpr AttribKey MAX_AMMO = Arty::AttribKey::MaxAmmo;\ 76 | constexpr AttribKey COOLDOWN = Arty::AttribKey::FireCooldown; 77 | constexpr AttribKey COOLDOWN_REMAINING = Arty::AttribKey::FireCooldownRemaining; 78 | constexpr AttribKey RELOAD = Arty::AttribKey::ReloadTime; 79 | constexpr AttribKey RELOAD_REMAINING = Arty::AttribKey::ReloadTimeRemaining; 80 | constexpr AttribKey TICKS_SINCE_GUN_LAST_FIRED = Arty::AttribKey::TicksSinceLastFired; 81 | typedef TSharedPtr AttrPtr; 82 | typedef TSharedPtr IdentPtr; 83 | typedef TMap AttributeMap; 84 | typedef TMap IdentityMap; 85 | typedef TSharedPtr AttrMapPtr; 86 | typedef TSharedPtr IdMapPtr; 87 | 88 | 89 | } 90 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/TIcklites/FTEntityFinalTickResolver.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "Ticklite.h" 4 | #include "ArtilleryDispatch.h" 5 | #include "FArtilleryTicklitesThread.h" 6 | 7 | //A ticklite's impl component(s) must provide: 8 | //TICKLITE_StateReset on the memory block aspect 9 | //TICKLITE_Calculate on the impl aspect 10 | //TICKLITE_Apply on the impl aspect, consuming the memory block aspect's state 11 | //TICKLITE_CoreReset on the impl aspect 12 | //TICKLITE_CheckForExpiration on the impl aspect 13 | //TICKLITE_OnExpiration 14 | class TLEntityFinalTickResolver : public UArtilleryDispatch::TL_ThreadedImpl /*Facaded*/ 15 | { 16 | public: 17 | FSkeletonKey EntityKey; 18 | TLEntityFinalTickResolver(): TL_ThreadedImpl() 19 | { 20 | } 21 | 22 | TLEntityFinalTickResolver( 23 | FSkeletonKey Target 24 | ) : TL_ThreadedImpl(), EntityKey(Target) 25 | { 26 | } 27 | void TICKLITE_StateReset() 28 | { 29 | } 30 | void TICKLITE_Calculate() 31 | { 32 | } 33 | 34 | 35 | void RechargeClamp(AttrPtr bindH, AttribKey Max, AttribKey Current) 36 | { 37 | if(bindH != nullptr && bindH->GetCurrentValue() > 0) 38 | { 39 | auto bindHMax = TL_ThreadedImpl::ADispatch->GetAttrib(EntityKey, Max); 40 | auto bindHCur = TL_ThreadedImpl::ADispatch->GetAttrib(EntityKey, Current); 41 | if( 42 | (bindHMax != nullptr && bindHMax->GetCurrentValue() > 0) && 43 | (bindHCur != nullptr)) //note that current does not check 0. lmao. it used to. 44 | { 45 | auto clamped = std::min(bindH->GetCurrentValue() + bindHCur->GetCurrentValue(), bindHMax->GetCurrentValue()); 46 | bindHCur->SetCurrentValue(clamped); 47 | } 48 | } 49 | } 50 | 51 | //This can be set up to autowire, but I'm not sure we're keeping these mechanisms yet. 52 | //we can speed this up considerably by adding a get all attribs. not sure we wanna though until optimization demands it. 53 | void TICKLITE_Apply() 54 | { 55 | //factor the get attr down to the impl. 56 | auto ManaRecharge = TL_ThreadedImpl::ADispatch->GetAttrib(EntityKey, Attr::ManaRechargePerTick); 57 | auto ShieldRecharge = TL_ThreadedImpl::ADispatch->GetAttrib(EntityKey, Attr::ShieldsRechargePerTick); 58 | auto HealthRecharge = TL_ThreadedImpl::ADispatch->GetAttrib(EntityKey, Attr::HealthRechargePerTick); 59 | 60 | RechargeClamp(HealthRecharge, Attr::MaxHealth, Attr::Health); 61 | RechargeClamp(ShieldRecharge, Attr::MaxShields, Attr::Shields); 62 | RechargeClamp(ManaRecharge, Attr::MaxMana, Attr::Mana); 63 | } 64 | 65 | void TICKLITE_CoreReset() 66 | { 67 | } 68 | 69 | bool TICKLITE_CheckForExpiration() 70 | { 71 | return false; //add check for aliveness of ya owner, factor that down. 72 | } 73 | 74 | void TICKLITE_OnExpiration() 75 | { 76 | //no op 77 | } 78 | }; 79 | 80 | typedef Ticklites::Ticklite EntityFinalTickResolver; 81 | 82 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/EssentialTypes/Ticklite.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ArtilleryCommonTypes.h" 3 | 4 | namespace Ticklites 5 | { 6 | 7 | //conserved attributes mean that we always have a shadow copy ready. 8 | // a successful Calculate function should reference the attribute not by most recent, but by exact index. 9 | //good support for this isn't in yet, but during our early work, the conserved attributes will still 10 | //protect us from partial memory commits and similar that might cause truly weird bugs. 11 | //instead, right now, we just run tickables "across" ticks right now. 12 | //This conforms with our measure, cut, fit, finish policy, as this is still in the _measure_ phase. 13 | 14 | template 15 | struct Ticklite : public TicklitePrototype 16 | { 17 | using Ticklite_Impl = YourImplementation; 18 | YourImplementation Core; 19 | virtual void CalculateTickable() 20 | override 21 | { 22 | Core.TICKLITE_StateReset(); 23 | //as always, the use of keys over references will make rollback far far easier. 24 | //when performing operations here, do not expect floating point accuracy above 16 ulps. 25 | //If you do, you will get fucked sooner or later. I guarantee it. 26 | Core.TICKLITE_Calculate(); 27 | } 28 | 29 | virtual void ApplyTickable() 30 | override 31 | { 32 | Core.TICKLITE_Apply(); 33 | } 34 | 35 | virtual void ReturnToPool() 36 | override 37 | { 38 | Core.TICKLITE_CoreReset(); 39 | Core.TICKLITE_StateReset(); 40 | } 41 | 42 | //expiration will likely get factored out into a delegate or pushed into the TL_Impl 43 | //to help ensure that we don't end up with 20 million tickables, each of which expires in a slightly different way. 44 | virtual bool ShouldExpireTickable() override 45 | { 46 | return Core.TICKLITE_CheckForExpiration(); 47 | } 48 | 49 | //expiration will likely get factored out into a delegate or pushed into the TL_Impl 50 | //to help ensure that we don't end up with 20 million tickables, each of which expires in a slightly different way. 51 | virtual void OnExpireTickable() 52 | override 53 | { 54 | return Core.TICKLITE_OnExpiration(); 55 | } 56 | 57 | virtual ~Ticklite() 58 | override 59 | { 60 | } 61 | 62 | //You may wish to implement a ticklite that uses reference counting. 63 | //this DOES NOT. Ticklites and their implementations are assumed to have different lifecycles, 64 | //with the ticklite either dying at the same time, or dying first. Anything besides this will cause bugs. 65 | //this can be optimized to remove the copy operation with a little work. once I'm sure this is the FINAL FORM 66 | //I'll do the work. 67 | Ticklite(Ticklite_Impl ImplInstance) 68 | { 69 | Core = ImplInstance; 70 | } 71 | }; 72 | } 73 | namespace Arty 74 | { 75 | typedef TPair, TicklitePhase> StampLiteRequest; 76 | typedef TArray< TSharedPtr> TickliteGroup; 77 | typedef TCircularQueue TickliteRequests; 78 | typedef TSharedPtr> TickliteBuffer; 79 | } 80 | 81 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/Systems/ArtilleryProjectileDispatch.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Subsystems/WorldSubsystem.h" 7 | #include "AInstancedMeshManager.h" 8 | #include "ArtilleryCommonTypes.h" 9 | #include "ArtilleryDispatch.h" 10 | #include "FProjectileDefinitionRow.h" 11 | #include "ArtilleryProjectileDispatch.generated.h" 12 | 13 | /** 14 | * This is the Artillery subsystem that manages the lifecycle of projectiles using only SkeletonKeys rather than UE5 15 | * actors. This is done by instancing projectiles through AInstancedMeshManager actors rather than creating an actor 16 | * per projectile which greatly reduces computational load as all projectiles with the same model can be managed by 17 | * Artillery + TickLites rather than through the heavy and expensive UE5 Actor system. 18 | * 19 | * This does mean that a lot of UE5 default functionality associated with Actors won't and don't work with Artillery 20 | * Projectiles, but this is intended. Artillery Projectiles should be managed through this subsystem and through the 21 | * ticklites that are assigned to them (also through this subsystem). This subsystem is responsible for basic default 22 | * behavior of projectiles that Artillery supports, but additional behavior can be added to Artillery Projectiles by 23 | * way of attaching custom TickLites to them. 24 | * 25 | */ 26 | 27 | namespace Arty 28 | { 29 | DECLARE_MULTICAST_DELEGATE(OnArtilleryProjectilesActivated); 30 | } 31 | 32 | UCLASS() 33 | class ARTILLERYRUNTIME_API UArtilleryProjectileDispatch : public UWorldSubsystem 34 | { 35 | GENERATED_BODY() 36 | 37 | protected: 38 | static inline UArtilleryProjectileDispatch* SelfPtr = nullptr; 39 | public: 40 | OnArtilleryProjectilesActivated BindToArtilleryProjectilesActivated; 41 | 42 | protected: 43 | virtual void Initialize(FSubsystemCollectionBase& Collection) override; 44 | virtual void OnWorldBeginPlay(UWorld& InWorld) override; 45 | virtual void Deinitialize() override; 46 | 47 | UDataTable* ProjectileDefinitions; 48 | TSharedPtr>> ManagerKeyToMeshManagerMapping; 49 | TSharedPtr>> ProjectileKeyToMeshManagerMapping; 50 | TSharedPtr>> ProjectileNameToMeshManagerMapping; 51 | 52 | public: 53 | virtual void PostInitialize() override; 54 | 55 | FProjectileDefinitionRow* GetProjectileDefinitionRow(const FName ProjectileDefinitionId); 56 | FSkeletonKey CreateProjectileInstance(const FName ProjectileDefinitionId, const FTransform& WorldTransform, const FVector3d& MuzzleVelocity, const bool IsSensor); 57 | void DeleteProjectile(const FSkeletonKey Target); 58 | TWeakObjectPtr GetProjectileMeshManagerByManagerKey(const FSkeletonKey ManagerKey); 59 | TWeakObjectPtr GetProjectileMeshManagerByProjectileKey(const FSkeletonKey ProjectileKey); 60 | 61 | void OnBarrageContactAdded(const BarrageContactEvent& ContactEvent); 62 | 63 | }; 64 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/TIcklites/FTGunFinalTickResolver.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "Ticklite.h" 4 | #include "ArtilleryDispatch.h" 5 | #include "FArtilleryTicklitesThread.h" 6 | 7 | //A ticklite's impl component(s) must provide: 8 | //TICKLITE_StateReset on the memory block aspect 9 | //TICKLITE_Calculate on the impl aspect 10 | //TICKLITE_Apply on the impl aspect, consuming the memory block aspect's state 11 | //TICKLITE_CoreReset on the impl aspect 12 | //TICKLITE_CheckForExpiration on the impl aspect 13 | //TICKLITE_OnExpiration 14 | class TLGunFinalTickResolver : public UArtilleryDispatch::TL_ThreadedImpl /*Facaded*/ 15 | { 16 | public: 17 | FSkeletonKey EntityKey; 18 | TLGunFinalTickResolver(): TL_ThreadedImpl() 19 | { 20 | } 21 | 22 | TLGunFinalTickResolver( 23 | FSkeletonKey Target 24 | ) : TL_ThreadedImpl(), EntityKey(Target) 25 | { 26 | } 27 | void TICKLITE_StateReset() 28 | { 29 | } 30 | void TICKLITE_Calculate() 31 | { 32 | } 33 | 34 | void TICKLITE_Apply() 35 | { 36 | // handle gun cooldown (fire rate) 37 | auto GunCooldownRemaining = TL_ThreadedImpl::ADispatch->GetAttrib(EntityKey, COOLDOWN_REMAINING); 38 | 39 | // Reduce cd remaining by one tick, flooring at 0. 40 | if (GunCooldownRemaining.IsValid()) 41 | { 42 | auto CurrCooldownValue = GunCooldownRemaining->GetCurrentValue(); 43 | if (CurrCooldownValue > 0.f) { 44 | GunCooldownRemaining->SetCurrentValue(std::max(CurrCooldownValue - 1, 0.f)); 45 | } 46 | } 47 | 48 | // handle reload 49 | auto CurrentAmmo = TL_ThreadedImpl::ADispatch->GetAttrib(EntityKey, AMMO); 50 | auto MaxAmmo = TL_ThreadedImpl::ADispatch->GetAttrib(EntityKey, MAX_AMMO); 51 | auto ReloadTime = TL_ThreadedImpl::ADispatch->GetAttrib(EntityKey, RELOAD); 52 | auto ReloadRemaining = TL_ThreadedImpl::ADispatch->GetAttrib(EntityKey, RELOAD_REMAINING); 53 | if (MaxAmmo.IsValid() && CurrentAmmo.IsValid()) 54 | { 55 | auto CurrentAmmoValue = CurrentAmmo->GetCurrentValue(); 56 | auto MaxAmmoValue = MaxAmmo->GetCurrentValue(); 57 | auto ReloadTimeValue = ReloadTime->GetCurrentValue(); 58 | auto ReloadRemainingValue = ReloadRemaining->GetCurrentValue(); 59 | if (MaxAmmoValue > 0.f) 60 | { 61 | // Only process reload system if this gun uses ammo + ammo is at zero 62 | 63 | if (ReloadRemainingValue > -1.f) 64 | { 65 | // Tick reload timer if its greater than -1, stopping at -1 66 | ReloadRemaining->SetCurrentValue(ReloadRemainingValue - 1); 67 | } 68 | 69 | if (ReloadRemainingValue == 0) 70 | { 71 | // Complete the reload 72 | CurrentAmmo->SetCurrentValue(MaxAmmoValue); 73 | } 74 | 75 | if (CurrentAmmoValue <= 0.f && ReloadRemainingValue <= -1.f) 76 | { 77 | // If we are out of ammo and the reload remaining is negative, start the reload 78 | ReloadRemaining->SetCurrentValue(ReloadTimeValue); 79 | } 80 | } 81 | } 82 | } 83 | 84 | void TICKLITE_CoreReset() 85 | { 86 | } 87 | 88 | bool TICKLITE_CheckForExpiration() 89 | { 90 | return false; //add check for aliveness of ya owner, factor that down. 91 | } 92 | 93 | void TICKLITE_OnExpiration() 94 | { 95 | //no op 96 | } 97 | }; 98 | 99 | typedef Ticklites::Ticklite GunFinalTickResolver; 100 | 101 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/PhysicsTypes/BarrageBoxComponent.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "BarrageColliderBase.h" 7 | #include "BarrageDispatch.h" 8 | #include "SkeletonTypes.h" 9 | #include "KeyCarry.h" 10 | #include "FBarragePrimitive.h" 11 | #include "Components/ActorComponent.h" 12 | #include "BarrageBoxComponent.generated.h" 13 | 14 | 15 | UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) 16 | class ARTILLERYRUNTIME_API UBarrageBoxComponent : public UBarrageColliderBase 17 | { 18 | GENERATED_BODY() 19 | 20 | public: 21 | // Sets default values for this component's properties 22 | UPROPERTY(EditAnywhere,BlueprintReadWrite) 23 | int XDiam = 30; 24 | UPROPERTY(EditAnywhere,BlueprintReadWrite) 25 | int YDiam = 30; 26 | UPROPERTY(EditAnywhere,BlueprintReadWrite) 27 | int ZDiam = 20; 28 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 29 | float OffsetCenterToMatchBoundedShapeX = 0; 30 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 31 | float OffsetCenterToMatchBoundedShapeY = 0; 32 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 33 | float OffsetCenterToMatchBoundedShapeZ = 0; 34 | UBarrageBoxComponent(const FObjectInitializer& ObjectInitializer); 35 | virtual void Register() override; 36 | }; 37 | 38 | //CONSTRUCTORS 39 | //-------------------- 40 | //do not invoke the default constructor unless you have a really good plan. in general, let UE initialize your components. 41 | 42 | // Sets default values for this component's properties 43 | inline UBarrageBoxComponent::UBarrageBoxComponent(const FObjectInitializer& ObjectInitializer) : Super( 44 | ObjectInitializer) 45 | { 46 | // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features 47 | // off to improve performance if you don't need them. 48 | 49 | bWantsInitializeComponent = true; 50 | PrimaryComponentTick.bCanEverTick = true; 51 | MyObjectKey = 0; 52 | 53 | } 54 | //KEY REGISTER, initializer, and failover. 55 | //---------------------------------- 56 | 57 | inline void UBarrageBoxComponent::Register() 58 | { 59 | if(MyObjectKey == 0) 60 | { 61 | if(GetOwner()) 62 | { 63 | if(GetOwner()->GetComponentByClass()) 64 | { 65 | MyObjectKey = GetOwner()->GetComponentByClass()->GetObjectKey(); 66 | } 67 | 68 | if(MyObjectKey == 0) 69 | { 70 | auto val = PointerHash(GetOwner()); 71 | ActorKey TopLevelActorKey = ActorKey(val); 72 | MyObjectKey = TopLevelActorKey; 73 | } 74 | } 75 | } 76 | if(!IsReady && MyObjectKey != 0) // this could easily be just the !=, but it's better to have the whole idiom in the example 77 | { 78 | auto Physics = GetWorld()->GetSubsystem(); 79 | auto TransformECS = GetWorld()->GetSubsystem(); 80 | 81 | auto params = FBarrageBounder::GenerateBoxBounds(GetOwner()->GetActorLocation(), XDiam, YDiam ,ZDiam, 82 | FVector3d(OffsetCenterToMatchBoundedShapeX, OffsetCenterToMatchBoundedShapeY, OffsetCenterToMatchBoundedShapeZ)); 83 | MyBarrageBody = Physics->CreatePrimitive(params, MyObjectKey, Layers::MOVING); 84 | //TransformECS->RegisterObjectToShadowTransform(MyObjectKey, const_cast*>(&GetOwner()->GetTransform())); 85 | if(MyBarrageBody) 86 | { 87 | IsReady = true; 88 | } 89 | } 90 | if(IsReady) 91 | { 92 | PrimaryComponentTick.SetTickFunctionEnable(false); 93 | } 94 | } -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/PhysicsTypes/BarrageAutoBox.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "BarrageColliderBase.h" 7 | #include "BarrageDispatch.h" 8 | #include "SkeletonTypes.h" 9 | #include "KeyCarry.h" 10 | #include "FWorldSimOwner.h" 11 | #include "Components/ActorComponent.h" 12 | #include "BarrageAutoBox.generated.h" 13 | 14 | 15 | UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent), DefaultToInstanced ) 16 | class ARTILLERYRUNTIME_API UBarrageAutoBox : public UBarrageColliderBase 17 | { 18 | GENERATED_BODY() 19 | 20 | public: 21 | // Sets default values for this component's properties 22 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 23 | bool isMovable = true; 24 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 25 | float OffsetCenterToMatchBoundedShapeX = 0; 26 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 27 | float OffsetCenterToMatchBoundedShapeY = 0; 28 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 29 | float OffsetCenterToMatchBoundedShapeZ = 0; 30 | UBarrageAutoBox(const FObjectInitializer& ObjectInitializer); 31 | virtual void Register() override; 32 | }; 33 | 34 | //CONSTRUCTORS 35 | //-------------------- 36 | //do not invoke the default constructor unless you have a really good plan. in general, let UE initialize your components. 37 | 38 | // Sets default values for this component's properties 39 | inline UBarrageAutoBox::UBarrageAutoBox(const FObjectInitializer& ObjectInitializer) : Super( 40 | ObjectInitializer) 41 | { 42 | // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features 43 | // off to improve performance if you don't need them. 44 | 45 | bWantsInitializeComponent = true; 46 | PrimaryComponentTick.bCanEverTick = true; 47 | MyObjectKey = 0; 48 | 49 | } 50 | 51 | //KEY REGISTER, initializer, and failover. 52 | //---------------------------------- 53 | 54 | inline void UBarrageAutoBox::Register() 55 | { 56 | if(MyObjectKey ==0 ) 57 | { 58 | if(GetOwner()) 59 | { 60 | if(GetOwner()->GetComponentByClass()) 61 | { 62 | MyObjectKey = GetOwner()->GetComponentByClass()->GetObjectKey(); 63 | } 64 | 65 | if(MyObjectKey == 0) 66 | { 67 | auto val = PointerHash(GetOwner()); 68 | ActorKey TopLevelActorKey = ActorKey(val); 69 | MyObjectKey = TopLevelActorKey; 70 | } 71 | } 72 | } 73 | if(!IsReady && MyObjectKey != 0 && GetOwner()) // this could easily be just the !=, but it's better to have the whole idiom in the example 74 | { 75 | auto Physics = GetWorld()->GetSubsystem(); 76 | auto TransformECS = GetWorld()->GetSubsystem(); 77 | auto AnyMesh = GetOwner()->GetComponentByClass(); 78 | auto Boxen = AnyMesh->GetLocalBounds(); 79 | // Multiply by the scale factor, then multiply by 2 since mesh bounds is radius not diameter 80 | auto extents = Boxen.BoxExtent * AnyMesh->BoundsScale * 2; 81 | 82 | auto params = FBarrageBounder::GenerateBoxBounds(GetOwner()->GetActorLocation(),extents.X, extents.Y, extents.Z, 83 | FVector3d(OffsetCenterToMatchBoundedShapeX, OffsetCenterToMatchBoundedShapeY, extents.Z/2)); 84 | MyBarrageBody = Physics->CreatePrimitive(params, MyObjectKey, isMovable ? Layers::MOVING : Layers::NON_MOVING); 85 | //TransformECS->RegisterObjectToShadowTransform(MyObjectKey, const_cast*>(&GetOwner()->GetTransform())); 86 | if(MyBarrageBody) 87 | { 88 | AnyMesh->WakeRigidBody(); 89 | IsReady = true; 90 | AnyMesh->SetSimulatePhysics(false); 91 | } 92 | } 93 | if(IsReady) 94 | { 95 | PrimaryComponentTick.SetTickFunctionEnable(false); 96 | } 97 | } -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/PhysicsTypes/BarrageColliderBase.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "BarrageDispatch.h" 7 | #include "SkeletonTypes.h" 8 | #include "KeyCarry.h" 9 | #include "FBarragePrimitive.h" 10 | #include "Components/ActorComponent.h" 11 | #include "BarrageColliderBase.generated.h" 12 | 13 | 14 | UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) 15 | class UBarrageColliderBase : public UActorComponent 16 | { 17 | GENERATED_BODY() 18 | 19 | public: 20 | // Sets default values for this component's properties 21 | UBarrageColliderBase(const FObjectInitializer& ObjectInitializer); 22 | virtual void InitializeComponent() override; 23 | FBLet MyBarrageBody = nullptr; 24 | 25 | virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; 26 | 27 | FSkeletonKey MyObjectKey; 28 | bool IsReady = false; 29 | virtual void BeforeBeginPlay(FSkeletonKey TransformOwner); 30 | 31 | //Colliders must override this. 32 | virtual void Register(); 33 | 34 | virtual void OnDestroyPhysicsState() override; 35 | // Called when the game starts 36 | virtual void BeginPlay() override; 37 | 38 | 39 | 40 | // Called every frame 41 | virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; 42 | 43 | 44 | }; 45 | 46 | //CONSTRUCTORS 47 | //-------------------- 48 | //do not invoke the default constructor unless you have a really good plan. in general, let UE initialize your components. 49 | 50 | // Sets default values for this component's properties 51 | inline UBarrageColliderBase::UBarrageColliderBase(const FObjectInitializer& ObjectInitializer) : Super( 52 | ObjectInitializer) 53 | { 54 | // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features 55 | // off to improve performance if you don't need them. 56 | 57 | PrimaryComponentTick.bCanEverTick = true; 58 | 59 | bWantsInitializeComponent = true; 60 | MyObjectKey = 0; 61 | 62 | } 63 | //--------------------------------- 64 | 65 | 66 | inline void UBarrageColliderBase::InitializeComponent() 67 | { 68 | Super::InitializeComponent(); 69 | } 70 | 71 | //SETTER: Unused example of how you might set up a registration for an arbitrary key. 72 | inline void UBarrageColliderBase::BeforeBeginPlay(FSkeletonKey TransformOwner) 73 | { 74 | MyObjectKey = TransformOwner; 75 | } 76 | 77 | //Colliders must override this. 78 | inline void UBarrageColliderBase::Register() 79 | { 80 | PrimaryComponentTick.SetTickFunctionEnable(false); 81 | } 82 | 83 | 84 | inline void UBarrageColliderBase::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) 85 | { 86 | Super::TickComponent(DeltaTime, TickType, ThisTickFunction); 87 | Register();// ... 88 | } 89 | 90 | // Called when the game starts 91 | inline void UBarrageColliderBase::BeginPlay() 92 | { 93 | Super::BeginPlay(); 94 | Register(); 95 | } 96 | 97 | //TOMBSTONERS 98 | 99 | inline void UBarrageColliderBase::OnDestroyPhysicsState() 100 | { 101 | Super::OnDestroyPhysicsState(); 102 | if(GetWorld()) 103 | { 104 | auto Physics = GetWorld()->GetSubsystem(); 105 | if(Physics && MyBarrageBody) 106 | { 107 | Physics->SuggestTombstone(MyBarrageBody); 108 | MyBarrageBody.Reset(); 109 | } 110 | } 111 | } 112 | 113 | inline void UBarrageColliderBase::EndPlay(const EEndPlayReason::Type EndPlayReason) 114 | { 115 | Super::EndPlay(EndPlayReason); 116 | if(GetWorld()) 117 | { 118 | auto Physics = GetWorld()->GetSubsystem(); 119 | if(Physics && MyBarrageBody) 120 | { 121 | Physics->SuggestTombstone(MyBarrageBody); 122 | MyBarrageBody.Reset(); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/Systems/UEnemyMachine.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | #include "CoreTypes.h" 5 | #include "CoreMinimal.h" 6 | #include "AbilitySystemComponent.h" 7 | 8 | #include "ArtilleryDispatch.h" 9 | #include "FAttributeMap.h" 10 | #include "TransformDispatch.h" 11 | #include "UEnemyMachine.generated.h" 12 | 13 | // So. This probably should share some functionality with UFireControlMachine, but I don't want to open that can of worms yet. 14 | UCLASS() 15 | class ARTILLERYRUNTIME_API UEnemyMachine : public UAbilitySystemComponent 16 | { 17 | GENERATED_BODY() 18 | 19 | public: 20 | UArtilleryDispatch* MyDispatch; 21 | UTransformDispatch* TransformDispatch; 22 | ActorKey ParentKey; 23 | 24 | // Me enemy, me want gun too 25 | // but me no implement now because me limit scope 26 | // TSet MyGuns; 27 | 28 | //The direct presence of attributes in this way is likely obsoleted by switching to inheriting rather than friending 29 | //the UAS component, but I'm not totally sure. 30 | TSharedPtr MyAttributes; 31 | 32 | bool Usable = false; 33 | 34 | //IF YOU DO NOT CALL THIS FROM THE GAMETHREAD, YOU WILL HAVE A BAD TIME. 35 | ActorKey CompleteRegistrationByActorParent(TMap Attributes) 36 | { 37 | //these are initialized earlier under all intended orderings, but we cannot ensure that this function will be called correctly 38 | //so we should do what we can to foolproof things. As long as the world subsystems are up, force-updating 39 | //here will either: 40 | //work correctly 41 | //fail fast 42 | MyDispatch = GetWorld()->GetSubsystem(); 43 | TransformDispatch = GetWorld()->GetSubsystem(); 44 | 45 | // Make a key yo 46 | auto keyHash = PointerHash(GetOwner()); 47 | UE_LOG(LogTemp, Warning, TEXT("EnemyMachine Parented: %d"), keyHash); 48 | ParentKey = ActorKey(keyHash); 49 | Usable = true; 50 | 51 | // TODO: I have no guns and I am sad :( Give me gun in future 52 | 53 | MyAttributes = MakeShareable(new FAttributeMap(ParentKey, MyDispatch, Attributes)); 54 | 55 | UE_LOG(LogTemp, Warning, TEXT("Enemy Mana: %f"), MyDispatch->GetAttrib(ParentKey, Attr::Mana)->GetCurrentValue()); 56 | 57 | return ParentKey; 58 | } 59 | 60 | void InitializeComponent() override 61 | { 62 | Super::InitializeComponent(); 63 | //we rely on attribute replication, which I think is borderline necessary, but I wonder if we should use effect replication. 64 | //historically, relying on gameplay effect replication has led to situations where key state was not managed through effects. 65 | //for OUR situation, where we have few attributes and many effects, huge amounts of effects are likely not interesting for us to replicate. 66 | ReplicationMode = EGameplayEffectReplicationMode::Minimal; 67 | }; 68 | 69 | //this happens post init but pre begin play, and the world subsystems should exist by this point. 70 | //we use this to help ensure that if the actor's begin play triggers first, things will be set correctly 71 | //I've left the same code in begin play as a fallback. 72 | void ReadyForReplication() override 73 | { 74 | Super::ReadyForReplication(); 75 | MyDispatch = GetWorld()->GetSubsystem(); 76 | } 77 | 78 | //on components, begin play can fire twice, because we aren't allowed to have nice things. 79 | //This can cause it to fire BEFORE the actor's begin play fires, which leaves you with 80 | //very few good options. the bool Usable helps control this. 81 | //This is, ironically, not a problem in actual usage, only testing, for us. 82 | void BeginPlay() override 83 | { 84 | Super::BeginPlay(); 85 | MyDispatch = GetWorld()->GetSubsystem(); 86 | }; 87 | 88 | virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override 89 | { 90 | Super::OnComponentDestroyed(bDestroyingHierarchy); 91 | }; 92 | }; -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/TestTypes/FMockArtilleryGun.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "CoreTypes.h" 7 | #include 8 | #include "FGunKey.h" 9 | #include "GameplayEffectTypes.h" 10 | #include "GameplayEffect.h" 11 | #include "Abilities/GameplayAbility.h" 12 | #include "UArtilleryAbilityMinimum.h" 13 | #include "FArtilleryGun.h" 14 | #include "FMockArtilleryGun.generated.h" 15 | 16 | 17 | 18 | 19 | /** 20 | * This class will be a data-driven instance of a gun that encapsulates a generic structured ability, 21 | * then exposes bindings for the phases of that ability as a component to be bound as specific gameplay abilities. 22 | * 23 | * 24 | * Artillery gun is a not a UObject. This allows us to safely interact with it off the game thread. 25 | * Triggering the abilities is likely a no-go off the thread, but we can modify the attributes as needed. 26 | * This allows us to do some very powerful stuff to ensure that we always have the most up to date data. 27 | * Some dark things. 28 | * 29 | * Ultimately, we'll need something like https://github.com/facebook/folly/blob/main/folly/concurrency/ConcurrentHashMap.h 30 | * if we want to get serious about this. 31 | */ 32 | USTRUCT(BlueprintType) 33 | struct ARTILLERYRUNTIME_API FMockArtilleryGun : public FArtilleryGun 34 | { 35 | GENERATED_BODY() 36 | 37 | public: 38 | // this can be handed into abilities. 39 | friend class UArtilleryPerActorAbilityMinimum; 40 | 41 | //this just sets the gunkey. 42 | //this mock doesn't use the delegate chaining, because it doesn't use abilities. 43 | FMockArtilleryGun(const FGunKey& KeyFromDispatch) 44 | { 45 | MyGunKey = KeyFromDispatch; 46 | }; 47 | 48 | 49 | virtual void PreFireGun( 50 | const FGameplayAbilitySpecHandle Handle, 51 | const FGameplayAbilityActorInfo* ActorInfo, 52 | const FGameplayAbilityActivationInfo ActivationInfo, 53 | const FGameplayEventData* TriggerEventData = nullptr, 54 | bool RerunDueToReconcile = false, 55 | int DallyFramesToOmit = 0) 56 | override 57 | { 58 | if (GEngine) 59 | { 60 | GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Yellow, FString::Printf(TEXT("Mock Gun Fired. Is reconcile? %hs"), RerunDueToReconcile ? "true" : "false")); 61 | } 62 | }; 63 | 64 | virtual void FireGun( 65 | FArtilleryStates OutcomeStates, 66 | int DallyFramesToOmit, 67 | const FGameplayAbilityActorInfo* ActorInfo, 68 | const FGameplayAbilityActivationInfo ActivationInfo, 69 | bool RerunDueToReconcile, 70 | const FGameplayEventData* TriggerEventData, 71 | FGameplayAbilitySpecHandle Handle) 72 | override 73 | { 74 | throw; 75 | }; 76 | 77 | virtual void PostFireGun( 78 | FArtilleryStates OutcomeStates, 79 | int DallyFramesToOmit, 80 | const FGameplayAbilityActorInfo* ActorInfo, 81 | const FGameplayAbilityActivationInfo ActivationInfo, 82 | bool RerunDueToReconcile, 83 | const FGameplayEventData* TriggerEventData, 84 | FGameplayAbilitySpecHandle Handle) 85 | override 86 | { 87 | throw; 88 | }; 89 | 90 | virtual bool Initialize( 91 | const FGunKey& KeyFromDispatch, 92 | const bool MyCodeWillHandleKeys, 93 | UArtilleryPerActorAbilityMinimum* PF = nullptr, 94 | UArtilleryPerActorAbilityMinimum* PFC = nullptr, 95 | UArtilleryPerActorAbilityMinimum* F = nullptr, 96 | UArtilleryPerActorAbilityMinimum* FC = nullptr, 97 | UArtilleryPerActorAbilityMinimum* PtF = nullptr, 98 | UArtilleryPerActorAbilityMinimum* PtFc = nullptr, 99 | UArtilleryPerActorAbilityMinimum* FFC = nullptr) 100 | override 101 | { 102 | return ARTGUN_MACROAUTOINIT(MyCodeWillHandleKeys); 103 | } 104 | 105 | FMockArtilleryGun() 106 | { 107 | MyGunKey = Default; 108 | } 109 | private: 110 | //Our debug value remains M6D. 111 | static const inline FGunKey Default = FGunKey("M6D", UINT64_MAX); 112 | 113 | 114 | }; 115 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/TestTypes/FMockChairCannon.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "ArtilleryProjectileDispatch.h" 5 | #include "FArtilleryGun.h" 6 | #include "FTSphereCast.h" 7 | #include "UArtilleryAbilityMinimum.h" 8 | 9 | #include "FMockChairCannon.generated.h" 10 | 11 | // I'mma firing my... chairs? Look I didn't have rockets ok 12 | USTRUCT(BlueprintType) 13 | struct ARTILLERYRUNTIME_API FMockChairCannon : public FArtilleryGun 14 | { 15 | GENERATED_BODY() 16 | public: 17 | friend class UArtilleryPerActorAbilityMinimum; 18 | 19 | FMockChairCannon(const FGunKey& KeyFromDispatch, int MaxAmmoIn, int FirerateIn, int ReloadTimeIn) 20 | { 21 | MyGunKey = KeyFromDispatch; 22 | MaxAmmo = MaxAmmoIn; 23 | Firerate = FirerateIn; 24 | ReloadTime = ReloadTimeIn; 25 | 26 | MyDispatch = nullptr; 27 | MyProjectileDispatch = nullptr; 28 | }; 29 | 30 | FMockChairCannon() : Super() 31 | { 32 | MyGunKey = Default; 33 | MaxAmmo = 10; 34 | Firerate = 60; 35 | ReloadTime = 150; 36 | 37 | MyDispatch = nullptr; 38 | MyProjectileDispatch = nullptr; 39 | }; 40 | 41 | virtual bool Initialize( 42 | const FGunKey& KeyFromDispatch, 43 | const bool MyCodeWillHandleKeys, 44 | UArtilleryPerActorAbilityMinimum* PF = nullptr, 45 | UArtilleryPerActorAbilityMinimum* PFC = nullptr, 46 | UArtilleryPerActorAbilityMinimum* F = nullptr, 47 | UArtilleryPerActorAbilityMinimum* FC = nullptr, 48 | UArtilleryPerActorAbilityMinimum* PtF = nullptr, 49 | UArtilleryPerActorAbilityMinimum* PtFc = nullptr, 50 | UArtilleryPerActorAbilityMinimum* FFC = nullptr) override 51 | { 52 | return ARTGUN_MACROAUTOINIT(MyCodeWillHandleKeys); 53 | } 54 | 55 | virtual void PreFireGun( 56 | const FGameplayAbilitySpecHandle Handle, 57 | const FGameplayAbilityActorInfo* ActorInfo, 58 | const FGameplayAbilityActivationInfo ActivationInfo, 59 | const FGameplayEventData* TriggerEventData = nullptr, 60 | bool RerunDueToReconcile = false, 61 | int DallyFramesToOmit = 0) override 62 | { 63 | AttrPtr CooldownRemainingPtr = MyDispatch->GetAttrib(MyGunKey, COOLDOWN_REMAINING); 64 | AttrPtr AmmoRemainingPtr = MyDispatch->GetAttrib(MyGunKey, AMMO); 65 | if (!CooldownRemainingPtr.IsValid() || CooldownRemainingPtr->GetCurrentValue() > 0.f) 66 | { 67 | // Cooldown not up yet! 68 | return; 69 | } 70 | 71 | if (!AmmoRemainingPtr.IsValid() || AmmoRemainingPtr->GetCurrentValue() <= 0.f) 72 | { 73 | // No ammo! 74 | return; 75 | } 76 | FireGun(Fired, 0, ActorInfo, ActivationInfo, false, TriggerEventData, Handle); 77 | }; 78 | 79 | virtual void FireGun( 80 | FArtilleryStates OutcomeStates, 81 | int DallyFramesToOmit, 82 | const FGameplayAbilityActorInfo* ActorInfo, 83 | const FGameplayAbilityActivationInfo ActivationInfo, 84 | bool RerunDueToReconcile, 85 | const FGameplayEventData* TriggerEventData, 86 | FGameplayAbilitySpecHandle Handle) override 87 | { 88 | if (PlayerCameraComponent.IsValid() && FiringPointComponent.IsValid()) 89 | { 90 | FVector StartLocation = PlayerCameraComponent->GetComponentLocation() + FVector(-10.0f, 0.0f, 0.0f); 91 | FRotator Rotation = PlayerCameraComponent->GetRelativeRotation(); 92 | 93 | FSkeletonKey ChairKey = GWorld->GetSubsystem()->CreateProjectileInstance(TEXT("ChairRocket"), PlayerCameraComponent->GetComponentTransform(), Rotation.Vector() * 1200, true); 94 | 95 | PostFireGun(Fired, 0, ActorInfo, ActivationInfo, false, TriggerEventData, Handle); 96 | } 97 | } 98 | 99 | virtual void PostFireGun( 100 | FArtilleryStates OutcomeStates, 101 | int DallyFramesToOmit, 102 | const FGameplayAbilityActorInfo* ActorInfo, 103 | const FGameplayAbilityActivationInfo ActivationInfo, 104 | bool RerunDueToReconcile, 105 | const FGameplayEventData* TriggerEventData, 106 | FGameplayAbilitySpecHandle Handle) override 107 | { 108 | AttrPtr AmmoPtr = MyDispatch->GetAttrib(MyGunKey, AMMO); 109 | if (AmmoPtr.IsValid()) 110 | { 111 | AmmoPtr->SetCurrentValue(AmmoPtr->GetCurrentValue() - 1); 112 | } 113 | AttrPtr CooldownPtr = MyDispatch->GetAttrib(MyGunKey, COOLDOWN); 114 | AttrPtr CooldownRemainingPtr = MyDispatch->GetAttrib(MyGunKey, COOLDOWN_REMAINING); 115 | if (CooldownPtr.IsValid() && CooldownRemainingPtr.IsValid()) 116 | { 117 | CooldownRemainingPtr->SetCurrentValue(CooldownPtr->GetCurrentValue()); 118 | } 119 | 120 | MyDispatch->GetAttrib(MyGunKey, TICKS_SINCE_GUN_LAST_FIRED)->SetCurrentValue(0.f); 121 | MyDispatch->GetAttrib(MyGunKey, AttribKey::LastFiredTimestamp)->SetCurrentValue(static_cast(MyDispatch->GetShadowNow())); 122 | }; 123 | 124 | private: 125 | static const inline FGunKey Default = FGunKey("ChairCannon", UINT64_MAX); 126 | }; -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/TestTypes/FMockDashGun.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "CoreTypes.h" 7 | #include 8 | 9 | #include "ArtilleryBPLibs.h" 10 | #include "ArtilleryDispatch.h" 11 | #include "AssetTypeCategories.h" 12 | #include "FGunKey.h" 13 | #include "GameplayEffectTypes.h" 14 | #include "GameplayEffect.h" 15 | #include "Abilities/GameplayAbility.h" 16 | #include "UArtilleryAbilityMinimum.h" 17 | #include "FArtilleryGun.h" 18 | #include "FTLinearVelocity.h" 19 | #include "FTPlayerEstimatorWithForce.h" 20 | #include "GameFramework/MovementComponent.h" 21 | #include "FMockDashGun.generated.h" 22 | 23 | /** 24 | * This test class encapsulates a dash. 25 | * Finally. 26 | */ 27 | USTRUCT(BlueprintType) 28 | struct ARTILLERYRUNTIME_API FMockDashGun : public FArtilleryGun 29 | { 30 | GENERATED_BODY() 31 | 32 | public: 33 | // this can be handed into abilities. 34 | friend class UArtilleryPerActorAbilityMinimum; 35 | UArtilleryDispatch* MyDispatch; 36 | 37 | 38 | //this just sets the gunkey. 39 | //this mock doesn't use the delegate chaining, because it doesn't use abilities. 40 | FMockDashGun(const FGunKey& KeyFromDispatch) 41 | { 42 | MyGunKey = KeyFromDispatch; 43 | }; 44 | 45 | 46 | virtual void PreFireGun( 47 | const FGameplayAbilitySpecHandle Handle, 48 | const FGameplayAbilityActorInfo* ActorInfo, 49 | const FGameplayAbilityActivationInfo ActivationInfo, 50 | const FGameplayEventData* TriggerEventData = nullptr, 51 | bool RerunDueToReconcile = false, 52 | int DallyFramesToOmit = 0) 53 | override 54 | { 55 | UE_LOG(LogTemp, Warning, TEXT("FMockDashGun prefire")); 56 | auto bind = MyDispatch->GetAttrib(MyProbableOwner,DASH_CURRENCY); 57 | if(bind != nullptr && bind->GetCurrentValue() > 1200) 58 | { 59 | bind->SetCurrentValue(bind->GetCurrentValue() - 1200.0); 60 | FireGun(FArtilleryStates::Fired, 0, ActorInfo, ActivationInfo, false, TriggerEventData , Handle); 61 | } 62 | }; 63 | 64 | virtual void FireGun( 65 | FArtilleryStates OutcomeStates, 66 | int DallyFramesToOmit, 67 | const FGameplayAbilityActorInfo* ActorInfo, 68 | const FGameplayAbilityActivationInfo ActivationInfo, 69 | bool RerunDueToReconcile, 70 | const FGameplayEventData* TriggerEventData, 71 | FGameplayAbilitySpecHandle Handle) 72 | override 73 | { 74 | UE_LOG(LogTemp, Warning, TEXT("FMockDashGun fire")); 75 | //todo: fix parametrica. RequestAdd should probably set the Anchor. tbh, atm, anchor is always the thread but 76 | FBLet GameSimPhysicsObject = this->MyDispatch->GetFBLetByObjectKey(MyProbableOwner, this->MyDispatch->GetShadowNow()); 77 | // TODO: Maybe direct this towards movement directional instead of current velocity? 78 | auto ScaledVelocityContinuation = FBarragePrimitive::GetVelocity(GameSimPhysicsObject).GetSafeNormal() * 1000; 79 | FVector ForwardInitial; 80 | UArtilleryLibrary::SimpleEstimator(ForwardInitial); 81 | 82 | FTPlayerEstimatorWithForce temp = 83 | FTPlayerEstimatorWithForce( 84 | MyProbableOwner, 85 | VelocityVec(ForwardInitial.X * 7, ForwardInitial.Y * 7, 0), 86 | 10 87 | ); 88 | 89 | MyDispatch->RequestAddTicklite( 90 | MakeShareable(new TL_PlayerDirectedForce(temp)), Early); 91 | PostFireGun(FArtilleryStates::Fired, 0, ActorInfo, ActivationInfo, false, TriggerEventData, Handle); 92 | } 93 | 94 | virtual void PostFireGun( 95 | FArtilleryStates OutcomeStates, 96 | int DallyFramesToOmit, 97 | const FGameplayAbilityActorInfo* ActorInfo, 98 | const FGameplayAbilityActivationInfo ActivationInfo, 99 | bool RerunDueToReconcile, 100 | const FGameplayEventData* TriggerEventData, 101 | FGameplayAbilitySpecHandle Handle) 102 | override 103 | { 104 | UE_LOG(LogTemp, Warning, TEXT("FMockDashGun post")); 105 | }; 106 | 107 | virtual bool Initialize( 108 | const FGunKey& KeyFromDispatch, 109 | const bool MyCodeWillHandleKeys, 110 | UArtilleryPerActorAbilityMinimum* PF = nullptr, 111 | UArtilleryPerActorAbilityMinimum* PFC = nullptr, 112 | UArtilleryPerActorAbilityMinimum* F = nullptr, 113 | UArtilleryPerActorAbilityMinimum* FC = nullptr, 114 | UArtilleryPerActorAbilityMinimum* PtF = nullptr, 115 | UArtilleryPerActorAbilityMinimum* PtFc = nullptr, 116 | UArtilleryPerActorAbilityMinimum* FFC = nullptr) 117 | override 118 | { 119 | MyDispatch = GWorld->GetSubsystem(); 120 | return ARTGUN_MACROAUTOINIT(MyCodeWillHandleKeys); 121 | } 122 | 123 | FMockDashGun() 124 | { 125 | MyGunKey = Default; 126 | } 127 | private: 128 | //Our debug value remains M6D. 129 | static const inline FGunKey Default = FGunKey("M6D", UINT64_MAX); 130 | }; 131 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/Systems/AInstancedMeshManager.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "FWorldSimOwner.h" 7 | #include "GameFramework/Actor.h" 8 | #include "UEnemyMachine.h" 9 | #include "SwarmKine.h" 10 | #include "EPhysicsLayer.h" 11 | #include "AInstancedMeshManager.generated.h" 12 | 13 | UCLASS() 14 | class ARTILLERYRUNTIME_API AInstancedMeshManager : public AActor 15 | { 16 | GENERATED_BODY() 17 | 18 | //UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Artillery, meta = (AllowPrivateAccess = "true")) 19 | USwarmKineManager* SwarmKineManager; 20 | 21 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Artillery, meta = (AllowPrivateAccess = "true")) 22 | UArtilleryDispatch* MyDispatch; 23 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Artillery, meta = (AllowPrivateAccess = "true")) 24 | UTransformDispatch* TransformDispatch; 25 | 26 | uint32 instances_generated; 27 | 28 | virtual void BeginPlay() override 29 | { 30 | Super::BeginPlay(); 31 | 32 | if(!IsDefaultSubobject()) 33 | { 34 | MyDispatch = GetWorld()->GetSubsystem(); 35 | TransformDispatch = GetWorld()->GetSubsystem(); 36 | // No Chaos for you! 37 | SwarmKineManager->SetEnableGravity(false); 38 | SwarmKineManager->SetSimulatePhysics(false); 39 | SwarmKineManager->DestroyPhysicsState(); 40 | 41 | // Make a key yo 42 | auto keyHash = PointerHash(this); 43 | UE_LOG(LogTemp, Warning, TEXT("AInstancedMeshManager Parented: %d"), keyHash); 44 | MyKey = ActorKey(keyHash); 45 | Usable = true; 46 | } 47 | } 48 | 49 | public: 50 | bool Usable = false; 51 | 52 | AInstancedMeshManager() 53 | { 54 | SwarmKineManager = CreateDefaultSubobject("SwarmKineManager"); 55 | SwarmKineManager->bDisableCollision = true; 56 | MyDispatch = nullptr; 57 | TransformDispatch = nullptr; 58 | } 59 | 60 | ActorKey GetMyKey() const 61 | { 62 | return MyKey; 63 | }; 64 | 65 | void SetStaticMesh(UStaticMesh* Mesh) 66 | { 67 | SwarmKineManager->SetStaticMesh(Mesh); 68 | } 69 | 70 | UFUNCTION(BlueprintCallable, Category = Instance) 71 | FSkeletonKey CreateNewInstance(const FTransform& WorldTransform, const FVector& MuzzleVelocity, const EPhysicsLayer Layer, bool IsSensor = false) 72 | { 73 | return CreateNewInstance(WorldTransform, MuzzleVelocity, static_cast(Layer), IsSensor); 74 | } 75 | FSkeletonKey CreateNewInstance(const FTransform& WorldTransform, const FVector3d& MuzzleVelocity, const uint16_t Layer, bool IsSensor = false) 76 | { 77 | FPrimitiveInstanceId NewInstanceId = SwarmKineManager->AddInstanceById(WorldTransform, true); 78 | // TODO: Does this make a good hash? Can we hash collide? 79 | // TODO: Oh god this definitely birthday problems at some point but I don't know how else to get a unique hash since the instances rotate around and reuse the same memory 80 | auto hash = PointerHash(SwarmKineManager, ++instances_generated); 81 | UE_LOG(LogTemp, Warning, TEXT("NewInstanceId: %i"), hash); 82 | FSkeletonKey NewInstanceKey = FSkeletonKey(hash); 83 | 84 | SwarmKineManager->AddToMap(NewInstanceId, NewInstanceKey); 85 | 86 | // TODO: can't use the BarrageColliderBase set of types, so in-lining the barrage setup code. Is this what we want long-term? 87 | auto Physics = GetWorld()->GetSubsystem(); 88 | auto TransformECS = GetWorld()->GetSubsystem(); 89 | auto AnyMesh = SwarmKineManager->GetStaticMesh(); 90 | auto Boxen = AnyMesh->GetBoundingBox(); 91 | auto extents = Boxen.GetExtent() * 2; 92 | 93 | auto params = FBarrageBounder::GenerateBoxBounds(WorldTransform.GetLocation(), extents.X, extents.Y, extents.Z, 94 | FVector3d(0, 0, extents.Z/2)); 95 | 96 | FBLet MyBarrageBody = Physics->CreatePrimitive(params, NewInstanceKey, Layer, IsSensor); 97 | FBarragePrimitive::SetVelocity(MuzzleVelocity, MyBarrageBody); 98 | 99 | TransformDispatch->RegisterObjectToShadowTransform(NewInstanceKey, SwarmKineManager); 100 | 101 | MyDispatch->REGISTER_PROJECTILE_FINAL_TICK_RESOLVER(100, NewInstanceKey); 102 | 103 | return NewInstanceKey; 104 | } 105 | 106 | // THIS MUST BE CALLED OR ELSE THE MAPPINGS WILL KEEP THE LIVE REFERENCE 4EVA 107 | 108 | void CleanupInstance(const FSkeletonKey Target) 109 | { 110 | // TODO: Not sure how if this cleans up the FBLet in Jolt 111 | // Tombstones don't seem to do anything? UBarrageDispatch::Entomb is never called 112 | auto Physics = GetWorld()->GetSubsystem(); 113 | Physics->SuggestTombstone(Physics->GetShapeRef(Target)); 114 | SwarmKineManager->CleanupInstance(Target); 115 | TransformDispatch->ReleaseKineByKey(Target); 116 | } 117 | 118 | private: 119 | ActorKey MyKey; 120 | }; -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/TestTypes/BarrageGravityOnlyTester.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "BarrageDispatch.h" 7 | #include "SkeletonTypes.h" 8 | #include "KeyCarry.h" 9 | #include "FBarragePrimitive.h" 10 | #include "Components/ActorComponent.h" 11 | #include "BarrageGravityOnlyTester.generated.h" 12 | 13 | 14 | UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) 15 | class UBarrageGravityOnlyTester : public UActorComponent 16 | { 17 | GENERATED_BODY() 18 | 19 | public: 20 | // Sets default values for this component's properties 21 | UBarrageGravityOnlyTester(); 22 | UBarrageGravityOnlyTester(const FObjectInitializer& ObjectInitializer); 23 | FBLet MyBarrageBody = nullptr; 24 | 25 | virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; 26 | 27 | FSkeletonKey MyObjectKey; 28 | bool IsReady = false; 29 | virtual void BeforeBeginPlay(FSkeletonKey TransformOwner); 30 | void Register(); 31 | 32 | virtual void OnDestroyPhysicsState() override; 33 | // Called when the game starts 34 | virtual void BeginPlay() override; 35 | 36 | // Called every frame 37 | virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; 38 | 39 | 40 | }; 41 | 42 | //CONSTRUCTORS 43 | //-------------------- 44 | //do not invoke the default constructor unless you have a really good plan. in general, let UE initialize your components. 45 | inline UBarrageGravityOnlyTester::UBarrageGravityOnlyTester() 46 | { 47 | PrimaryComponentTick.bCanEverTick = true; 48 | MyObjectKey = 0; 49 | } 50 | // Sets default values for this component's properties 51 | inline UBarrageGravityOnlyTester::UBarrageGravityOnlyTester(const FObjectInitializer& ObjectInitializer) : Super( 52 | ObjectInitializer) 53 | { 54 | // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features 55 | // off to improve performance if you don't need them. 56 | 57 | PrimaryComponentTick.bCanEverTick = true; 58 | MyObjectKey = 0; 59 | 60 | } 61 | //--------------------------------- 62 | 63 | //SETTER: Unused example of how you might set up a registration for an arbitrary key. 64 | inline void UBarrageGravityOnlyTester::BeforeBeginPlay(FSkeletonKey TransformOwner) 65 | { 66 | MyObjectKey = TransformOwner; 67 | } 68 | 69 | //KEY REGISTER, initializer, and failover. 70 | //---------------------------------- 71 | 72 | inline void UBarrageGravityOnlyTester::Register() 73 | { 74 | if(MyObjectKey ==0 ) 75 | { 76 | if(GetOwner()) 77 | { 78 | if(GetOwner()->GetComponentByClass()) 79 | { 80 | MyObjectKey = GetOwner()->GetComponentByClass()->GetObjectKey(); 81 | } 82 | 83 | if(MyObjectKey == 0) 84 | { 85 | auto val = PointerHash(GetOwner()); 86 | ActorKey TopLevelActorKey = ActorKey(val); 87 | MyObjectKey = TopLevelActorKey; 88 | } 89 | } 90 | } 91 | if(!IsReady && MyObjectKey != 0) // this could easily be just the !=, but it's better to have the whole idiom in the example 92 | { 93 | auto Physics = GetWorld()->GetSubsystem(); 94 | auto TransformECS = GetWorld()->GetSubsystem(); 95 | auto params = FBarrageBounder::GenerateBoxBounds(GetOwner()->GetActorLocation(), 30, 30 ,20); 96 | MyBarrageBody = Physics->CreatePrimitive(params, MyObjectKey, Layers::MOVING); 97 | //TransformECS->RegisterObjectToShadowTransform(MyObjectKey, const_cast*>(&GetOwner()->GetTransform())); 98 | if(MyBarrageBody) 99 | { 100 | IsReady = true; 101 | } 102 | } 103 | if(IsReady) 104 | { 105 | PrimaryComponentTick.SetTickFunctionEnable(false); 106 | } 107 | } 108 | 109 | 110 | inline void UBarrageGravityOnlyTester::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) 111 | { 112 | Super::TickComponent(DeltaTime, TickType, ThisTickFunction); 113 | Register();// ... 114 | } 115 | 116 | // Called when the game starts 117 | inline void UBarrageGravityOnlyTester::BeginPlay() 118 | { 119 | Super::BeginPlay(); 120 | Register(); 121 | } 122 | 123 | //TOMBSTONERS 124 | 125 | inline void UBarrageGravityOnlyTester::OnDestroyPhysicsState() 126 | { 127 | Super::OnDestroyPhysicsState(); 128 | if(GetWorld()) 129 | { 130 | auto Physics = GetWorld()->GetSubsystem(); 131 | if(Physics && MyBarrageBody) 132 | { 133 | Physics->SuggestTombstone(MyBarrageBody); 134 | MyBarrageBody.Reset(); 135 | } 136 | } 137 | } 138 | 139 | inline void UBarrageGravityOnlyTester::EndPlay(const EEndPlayReason::Type EndPlayReason) 140 | { 141 | Super::EndPlay(EndPlayReason); 142 | if(GetWorld()) 143 | { 144 | auto Physics = GetWorld()->GetSubsystem(); 145 | if(Physics && MyBarrageBody) 146 | { 147 | Physics->SuggestTombstone(MyBarrageBody); 148 | MyBarrageBody.Reset(); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Private/ArtilleryProjectileDispatch.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "ArtilleryProjectileDispatch.h" 4 | #include "BarrageDispatch.h" 5 | 6 | void UArtilleryProjectileDispatch::Initialize(FSubsystemCollectionBase& Collection) 7 | { 8 | Super::Initialize(Collection); 9 | // TODO: Can we find and autoload the datatable, or do 10 | ProjectileDefinitions = Cast(StaticLoadObject(UDataTable::StaticClass(), nullptr, TEXT("DataTable'/Game/DataTables/ProjectileDefinitions.ProjectileDefinitions'"))); 11 | ManagerKeyToMeshManagerMapping = MakeShareable(new TMap>()); 12 | ProjectileKeyToMeshManagerMapping = MakeShareable(new TMap>()); 13 | ProjectileNameToMeshManagerMapping = MakeShareable(new TMap>()); 14 | SelfPtr = this; 15 | UE_LOG(LogTemp, Warning, TEXT("ArtilleryProjectileDispatch:Subsystem: Online")); 16 | } 17 | 18 | void UArtilleryProjectileDispatch::PostInitialize() 19 | { 20 | Super::PostInitialize(); 21 | } 22 | 23 | void UArtilleryProjectileDispatch::OnWorldBeginPlay(UWorld& InWorld) 24 | { 25 | Super::OnWorldBeginPlay(InWorld); 26 | UBarrageDispatch* BarrageDispatch = GetWorld()->GetSubsystem(); 27 | BarrageDispatch->OnBarrageContactAddedDelegate.AddUObject(this, &UArtilleryProjectileDispatch::OnBarrageContactAdded); 28 | } 29 | 30 | void UArtilleryProjectileDispatch::Deinitialize() 31 | { 32 | Super::Deinitialize(); 33 | ManagerKeyToMeshManagerMapping->Empty(); 34 | ProjectileKeyToMeshManagerMapping->Empty(); 35 | ProjectileNameToMeshManagerMapping->Empty(); 36 | } 37 | 38 | FProjectileDefinitionRow* UArtilleryProjectileDispatch::GetProjectileDefinitionRow(const FName ProjectileDefinitionId) 39 | { 40 | if (ProjectileDefinitions != nullptr) 41 | { 42 | FProjectileDefinitionRow* FoundRow = ProjectileDefinitions->FindRow(ProjectileDefinitionId, TEXT("ProjectileTableLibrary")); 43 | return FoundRow; 44 | } 45 | return nullptr; 46 | } 47 | 48 | FSkeletonKey UArtilleryProjectileDispatch::CreateProjectileInstance(const FName ProjectileDefinitionId, const FTransform& WorldTransform, const FVector3d& MuzzleVelocity, const bool IsSensor) 49 | { 50 | auto MeshManagerPtr = ProjectileNameToMeshManagerMapping->Find(ProjectileDefinitionId); 51 | 52 | if (!MeshManagerPtr || !MeshManagerPtr->IsValid()) 53 | { 54 | if (const FProjectileDefinitionRow* ProjectileDefinition = GetProjectileDefinitionRow(ProjectileDefinitionId)) 55 | { 56 | if (UStaticMesh* StaticMeshPtr = Cast(StaticLoadObject(UStaticMesh::StaticClass(), nullptr, *ProjectileDefinition->ProjectileMeshLocation))) 57 | { 58 | AInstancedMeshManager* NewMeshManager = GetWorld()->SpawnActor(); 59 | NewMeshManager->SetStaticMesh(StaticMeshPtr); 60 | ManagerKeyToMeshManagerMapping->Add(NewMeshManager->GetMyKey(), NewMeshManager); 61 | ProjectileNameToMeshManagerMapping->Add(ProjectileDefinitionId, NewMeshManager); 62 | } 63 | } 64 | } 65 | 66 | MeshManagerPtr = ProjectileNameToMeshManagerMapping->Find(ProjectileDefinitionId); 67 | 68 | if(MeshManagerPtr) 69 | { 70 | auto MeshManager = *MeshManagerPtr; 71 | if (MeshManager.IsValid()) 72 | { 73 | FSkeletonKey NewProjectileKey = MeshManager->CreateNewInstance(WorldTransform, MuzzleVelocity, Layers::PROJECTILE, IsSensor); 74 | ProjectileKeyToMeshManagerMapping->Add(NewProjectileKey, MeshManager); 75 | return NewProjectileKey; 76 | } 77 | } 78 | 79 | UE_LOG(LogTemp, Error, TEXT("Could not find or load projectile instance manager with id %s"), *ProjectileDefinitionId.ToString()); 80 | return FSkeletonKey(); 81 | } 82 | 83 | void UArtilleryProjectileDispatch::DeleteProjectile(const FSkeletonKey Target) 84 | { 85 | TWeakObjectPtr MeshManager; 86 | bool FoundKey = ProjectileKeyToMeshManagerMapping->RemoveAndCopyValue(Target, MeshManager); 87 | if (FoundKey && MeshManager.IsValid()) 88 | { 89 | UE_LOG(LogTemp, Warning, TEXT("DELETING")); 90 | MeshManager->CleanupInstance(Target); 91 | ProjectileKeyToMeshManagerMapping->Remove(Target); 92 | } 93 | } 94 | 95 | TWeakObjectPtr UArtilleryProjectileDispatch::GetProjectileMeshManagerByManagerKey(const FSkeletonKey ManagerKey) 96 | { 97 | if (auto ManagerRefRef = ManagerKeyToMeshManagerMapping->Find(ManagerKey)) 98 | { 99 | return *ManagerRefRef; 100 | } 101 | 102 | return nullptr; 103 | } 104 | 105 | TWeakObjectPtr UArtilleryProjectileDispatch::GetProjectileMeshManagerByProjectileKey(const FSkeletonKey ProjectileKey) 106 | { 107 | if (auto ManagerRefRef = ProjectileKeyToMeshManagerMapping->Find(ProjectileKey)) 108 | { 109 | return *ManagerRefRef; 110 | } 111 | 112 | return nullptr; 113 | } 114 | 115 | void UArtilleryProjectileDispatch::OnBarrageContactAdded(const BarrageContactEvent& ContactEvent) 116 | { 117 | // We only care if one of the entities is a projectile 118 | if (ContactEvent.IsEitherEntityAProjectile()) 119 | { 120 | auto ProjectileKey = ContactEvent.ContactEntity1.bIsProjectile ? ContactEvent.ContactEntity1.ContactKey : ContactEvent.ContactEntity2.ContactKey; 121 | auto EntityHitKey = ContactEvent.ContactEntity1.bIsProjectile ? ContactEvent.ContactEntity2.ContactKey : ContactEvent.ContactEntity1.ContactKey; 122 | 123 | UArtilleryDispatch* ArtilleryDispatch = GetWorld()->GetSubsystem(); 124 | UTransformDispatch* TransformDispatch = GetWorld()->GetSubsystem(); 125 | 126 | // TODO: make action based on projectile configuration, not just 100 health damage 127 | AttrPtr HitObjectHealthPtr = ArtilleryDispatch->GetAttrib(EntityHitKey, HEALTH); 128 | 129 | if (HitObjectHealthPtr.IsValid()) 130 | { 131 | HitObjectHealthPtr->SetCurrentValue(HitObjectHealthPtr->GetCurrentValue() - 100); 132 | } 133 | 134 | auto HitActor = TransformDispatch->GetAActorByObjectKey(EntityHitKey); 135 | if (HitActor.IsValid()) 136 | { 137 | UE_LOG(LogTemp, Warning, TEXT("Hit a: %s"), *HitActor->GetFullName()); 138 | } 139 | 140 | DeleteProjectile(ProjectileKey); 141 | } 142 | 143 | } -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Private/UArtilleryAbilityMinimum.cpp: -------------------------------------------------------------------------------- 1 | #include "UArtilleryAbilityMinimum.h" 2 | #include "Engine/BlueprintGeneratedClass.h" 3 | #include "Engine/Engine.h" 4 | #include "AbilitySystemGlobals.h" 5 | #include "AbilitySystemComponent.h" 6 | #include "Abilities/Tasks/AbilityTask.h" 7 | #include "Components/SkeletalMeshComponent.h" 8 | #include "GameplayCue_Types.h" 9 | #include "GameFramework/CharacterMovementComponent.h" 10 | #include "GameFramework/Character.h" 11 | #include "Misc/DataValidation.h" 12 | #include "UObject/Package.h" 13 | #include "UFireControlMachine.h" 14 | 15 | //The K2 function paths for ending abilities are a little confusing, so I've excerpted the original code below. 16 | //We don't override it atm, so this is how you should currently expect Artillery abilities to behave just like UGAs. 17 | //Here's the call path that UGameplayAbility exposes to blueprint which calls end, commit, and cancel. 18 | /* 19 | bool UGameplayAbility::K2_CommitAbility() 20 | { 21 | check(CurrentActorInfo); 22 | return CommitAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo); 23 | } 24 | 25 | bool UGameplayAbility::K2_CommitAbilityCooldown(bool BroadcastCommitEvent, bool ForceCooldown) 26 | { 27 | check(CurrentActorInfo); 28 | if (BroadcastCommitEvent) 29 | { 30 | UAbilitySystemComponent* const AbilitySystemComponent = GetAbilitySystemComponentFromActorInfo_Checked(); 31 | AbilitySystemComponent->NotifyAbilityCommit(this); 32 | } 33 | return CommitAbilityCooldown(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, ForceCooldown); 34 | } 35 | 36 | bool UGameplayAbility::K2_CommitAbilityCost(bool BroadcastCommitEvent) 37 | { 38 | check(CurrentActorInfo); 39 | if (BroadcastCommitEvent) 40 | { 41 | UAbilitySystemComponent* const AbilitySystemComponent = GetAbilitySystemComponentFromActorInfo_Checked(); 42 | AbilitySystemComponent->NotifyAbilityCommit(this); 43 | } 44 | return CommitAbilityCost(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo); 45 | }*/ 46 | 47 | 48 | //This routes to K2_ActivateViaArtillery_Implementation for C++ or K2_ActivateViaArtillery for blueprint. 49 | // in event data, you can expect 50 | // Instigator(nullptr) 51 | // Target(nullptr) 52 | //to provide actorkeys that will allow you to access artillery if they are gamesim objects. 53 | //If you need additional keys at the start of the ability, it may unfortunately be necessary to write your ability partially or fully as an artillery gun. 54 | void UArtilleryPerActorAbilityMinimum::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) 55 | { 56 | 57 | if (TriggerEventData) 58 | { 59 | K2_ActivateViaArtillery(*ActorInfo, *TriggerEventData, MyGunKey); 60 | } 61 | else if (bHasBlueprintActivateFromEvent) 62 | { 63 | UE_LOG(LogTemp, Warning, TEXT("Artillery Ability %s expects event data but none is being supplied. Only ActivateViaArtillery will be called."), *GetName()); 64 | constexpr bool bReplicateEndAbility = false; 65 | constexpr bool bWasCancelled = true; 66 | EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled); 67 | } 68 | else 69 | { 70 | UE_LOG(LogTemp, Warning, TEXT("Artillery Ability %s expects to be activated by FArtilleryGun and routes to ActivateViaArtillery. Either override this or try again with a valid Blueprint Graph implemented and an event."), *GetName()); 71 | constexpr bool bReplicateEndAbility = false; 72 | constexpr bool bWasCancelled = true; 73 | EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled); 74 | // Per the original code from UGA, we may need to expand this significantly. 75 | 76 | // if (!CommitAbility(Handle, ActorInfo, ActivationInfo)) 77 | // { 78 | // constexpr bool bReplicateEndAbility = true; 79 | // constexpr bool bWasCancelled = true; 80 | // EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled); 81 | // } 82 | } 83 | } 84 | 85 | void UArtilleryPerActorAbilityMinimum::PreActivate(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, FOnGameplayAbilityEnded::FDelegate* OnGameplayAbilityEndedDelegate, const FGameplayEventData* TriggerEventData) 86 | { 87 | //right now, we do EXACTLY AND ONLY what UGA does. This is going to get less and less true. 88 | Super::PreActivate(Handle, ActorInfo, ActivationInfo, OnGameplayAbilityEndedDelegate, TriggerEventData); 89 | } 90 | 91 | void UArtilleryPerActorAbilityMinimum::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) 92 | { 93 | Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled); 94 | FArtilleryStates HowDidItGo = bWasCancelled ? FArtilleryStates::Fired : FArtilleryStates::Canceled ; 95 | GunBinder.Execute(HowDidItGo, AvailableDallyFrames, ActorInfo, ActivationInfo); 96 | } 97 | 98 | bool UArtilleryPerActorAbilityMinimum::CommitAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, OUT FGameplayTagContainer* OptionalRelevantTags) 99 | { 100 | 101 | return Super::CommitAbility(Handle, ActorInfo, ActivationInfo, OptionalRelevantTags); 102 | } 103 | 104 | 105 | FGameplayEffectContextHandle UArtilleryPerActorAbilityMinimum::GetContextFromOwner(FGameplayAbilityTargetDataHandle OptionalTargetData) const 106 | { 107 | return Super::GetContextFromOwner(OptionalTargetData); 108 | } 109 | 110 | FGameplayEffectContextHandle UArtilleryPerActorAbilityMinimum::MakeEffectContext(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo) const 111 | { 112 | return Super::MakeEffectContext(Handle, ActorInfo); 113 | } 114 | 115 | void UArtilleryPerActorAbilityMinimum::CancelAbility(const FGameplayAbilitySpecHandle Handle, 116 | const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, 117 | bool bReplicateCancelAbility) 118 | { 119 | Super::CancelAbility(Handle, ActorInfo, ActivationInfo, bReplicateCancelAbility); 120 | } 121 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Private/CanonicalInputStreamECS.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "CanonicalInputStreamECS.h" 5 | 6 | void UCanonicalInputStreamECS::Initialize(FSubsystemCollectionBase& Collection) 7 | { 8 | Super::Initialize(Collection); 9 | UE_LOG(LogTemp, Warning, TEXT("Artillery::CanonicalInputStream is Online.")); 10 | } 11 | 12 | void UCanonicalInputStreamECS::OnWorldBeginPlay(UWorld& InWorld) 13 | { 14 | if ([[maybe_unused]] const UWorld* World = InWorld.GetWorld()) { 15 | UE_LOG(LogTemp, Warning, TEXT("Artillery::CanonicalInputStream is Operational")); 16 | MySquire = GetWorld()->GetSubsystem(); 17 | } 18 | SelfPtr =this; 19 | } 20 | 21 | void UCanonicalInputStreamECS::Deinitialize() 22 | { 23 | SelfPtr = nullptr; 24 | UE_LOG(LogTemp, Warning, TEXT("Artillery::CanonicalInputStream is Shutting Down.")); 25 | Super::Deinitialize(); 26 | } 27 | 28 | ActorKey UCanonicalInputStreamECS::ActorByStream(InputStreamKey Stream) 29 | { 30 | return StreamToActorMapping->FindRef(Stream); 31 | } 32 | 33 | InputStreamKey UCanonicalInputStreamECS::StreamByActor(ActorKey Actor) 34 | { 35 | return ActorToStreamMapping->FindRef(Actor); 36 | } 37 | 38 | void UCanonicalInputStreamECS::Tick(float DeltaTime) 39 | { 40 | } 41 | 42 | TStatId UCanonicalInputStreamECS::GetStatId() const 43 | { 44 | RETURN_QUICK_DECLARE_CYCLE_STAT(UCanonicalInputStreamECS, STATGROUP_Tickables); 45 | } 46 | 47 | 48 | //OH NO. HIDDEN ORDERING DEPENDENCY EMERGED! if this gets called AFTER getstreamforplayer, which can happen because we now handle 49 | //the CDO correctly, we can end up in a situation where things just sort of flywheel off to hell because there's not actually any stream 50 | //constructs extant yet. 51 | TSharedPtr UCanonicalInputStreamECS::getNewStreamConstruct( PlayerKey ByPlayerConcept) 52 | { 53 | 54 | TSharedPtr ManagedStream = MakeShareable( 55 | new FConservedInputStream(this, ByPlayerConcept) //using++ vs ++would be wrong here. inc then ret. 56 | ); 57 | auto BifurcateOwnership = new TSharedPtr(ManagedStream); 58 | //fun fucking story, this was working by ACCIDENT because we were somehow ZEROING OUT the pointers, causing things to JUST BARELY map. 59 | //here we go again. 60 | SessionPlayerToStreamMapping->Add(ByPlayerConcept, ManagedStream->MyKey);// 61 | StreamKeyToStreamMapping->Add(ManagedStream->MyKey, *BifurcateOwnership);//This is the key driver for the ordering problem 62 | return ManagedStream; 63 | } 64 | 65 | 66 | InputStreamKey UCanonicalInputStreamECS::GetStreamForPlayer(PlayerKey ThisPlayer) 67 | { 68 | //TODO: this can actually fail if the start up sequence happens in a really unusual order. 69 | return SessionPlayerToStreamMapping->FindChecked(ThisPlayer); 70 | } 71 | 72 | TSharedPtr UCanonicalInputStreamECS::GetStream(InputStreamKey StreamKey) const 73 | { 74 | const auto SP = StreamKeyToStreamMapping->FindRef(StreamKey); 75 | return SP; // creates a copy. 76 | } 77 | 78 | bool UCanonicalInputStreamECS::registerPattern( IPM::CanonPattern ToBind, 79 | FActionPatternParams FCM_Owner_ActorParams) 80 | { 81 | if ( 82 | #ifndef LOCALISCODEDSPECIAL 83 | FCM_Owner_ActorParams.MyInputStream == APlayer::CABLE || 84 | #endif // !LOCALISCODEDSPECIAL 85 | StreamKeyToStreamMapping->Contains(FCM_Owner_ActorParams.MyInputStream)) 86 | { 87 | auto thisInputStream = StreamKeyToStreamMapping->Find(FCM_Owner_ActorParams.MyInputStream)->Get(); 88 | if (thisInputStream->MyPatternMatcher->AllPatternBinds.Contains(ToBind->getName())) 89 | { 90 | //names are never removed. sets are only added to or removed from. 91 | thisInputStream->MyPatternMatcher->AllPatternBinds.Find(ToBind->getName())->Get()->Add(FCM_Owner_ActorParams); 92 | 93 | } 94 | else 95 | { 96 | thisInputStream->MyPatternMatcher->Names.Add(ToBind->getName()); 97 | thisInputStream->MyPatternMatcher->AllPatternsByName.Add(ToBind->getName(), ToBind); 98 | TSharedPtr> newSet = MakeShareable < TSet>(new TSet()); 99 | newSet.Get()->Add(FCM_Owner_ActorParams); 100 | thisInputStream->MyPatternMatcher->AllPatternBinds.Add(ToBind->getName(), newSet); 101 | } 102 | 103 | return true; 104 | } 105 | return false; 106 | } 107 | 108 | bool UCanonicalInputStreamECS::removePattern(IPM::CanonPattern ToBind, FActionPatternParams FCM_Owner_ActorParams) 109 | { 110 | if ( 111 | #ifndef LOCALISCODEDSPECIAL 112 | FCM_Owner_ActorParams.MyInputStream == APlayer::CABLE || 113 | #endif // !LOCALISCODEDSPECIAL 114 | StreamKeyToStreamMapping->Contains(FCM_Owner_ActorParams.MyInputStream)) 115 | { 116 | auto thisInputStream = StreamKeyToStreamMapping->Find(FCM_Owner_ActorParams.MyInputStream)->Get(); 117 | if (thisInputStream->MyPatternMatcher->AllPatternBinds.Contains(ToBind->getName())) 118 | { 119 | //names are never removed. sets are only added to or removed from. 120 | auto pinSharedPtr = thisInputStream->MyPatternMatcher->AllPatternBinds.Find(ToBind->getName()); 121 | 122 | if (pinSharedPtr->Get()->Contains(FCM_Owner_ActorParams)) 123 | { 124 | auto remId = pinSharedPtr->Get()->FindId(FCM_Owner_ActorParams); 125 | pinSharedPtr->Get()->Remove(remId); 126 | return true; 127 | } 128 | } 129 | 130 | } 131 | return false; 132 | } 133 | TPair UCanonicalInputStreamECS::RegisterKeysToParentActorMapping(AActor* parent, FireControlKey MachineKey, bool IsActorForLocalPlayer) 134 | { 135 | //todo, registration goes here. 136 | auto val = PointerHash(parent); 137 | UE_LOG(LogTemp, Warning, TEXT("FCM Parented: %d"), val); 138 | ActorKey ParentKey(val); 139 | LocalActorToFireControlMapping->Add(ParentKey, MachineKey); 140 | 141 | //this is a hack. this is such a hack. oh god. 142 | if(IsActorForLocalPlayer) 143 | { 144 | #if UE_BUILD_SHIPPING != 0 145 | throw; //I told you not to ship this without checking. 146 | #endif 147 | //this relies on a really ugly hack using the ENUM. do not ship this without being sure you want to. 148 | InputStreamKey LocalKey = GetStreamForPlayer(APlayer::CABLE); 149 | StreamToActorMapping->Add(LocalKey, ParentKey); //ONE OF THE TWO THINGS IS WRONG NOW, CONGRATS, HERO. 150 | ActorToStreamMapping->Add(ParentKey, LocalKey); 151 | return TPair(ParentKey, LocalKey); 152 | } 153 | else 154 | { 155 | throw; 156 | } 157 | 158 | } 159 | 160 | 161 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/BasicTypes/ArtilleryCommonTypes.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | /** 5 | * Component for managing input streams in an ECS-like way, where any controller can request any stream. 6 | */ 7 | 8 | 9 | #include "FActionBitMask.h" 10 | #include "BristleconeCommonTypes.h" 11 | #include "Containers/TripleBuffer.h" 12 | #include "Skeletonize.h" 13 | #include "SkeletonTypes.h" 14 | 15 | namespace Arty 16 | { 17 | enum APlayer 18 | { 19 | CABLE = 1, 20 | ECHO = 2, 21 | TWO = 20, 22 | THREE = 30, 23 | FOUR = 40, 24 | FIVE = 50, 25 | SIX = 60, 26 | SEVEN = 70, 27 | EIGHT = 80 28 | }; 29 | typedef TheCone::PacketElement INNNNCOMING; 30 | typedef uint32_t InputStreamKey; 31 | typedef APlayer PlayerKey; 32 | typedef uint32_t FireControlKey; 33 | 34 | 35 | } 36 | 37 | #include "CoreMinimal.h" 38 | #include "Templates/SubclassOf.h" 39 | #include "UObject/UnrealType.h" 40 | #include "Engine/DataTable.h" 41 | #include "AttributeSet.h" 42 | #include 43 | #include "Containers/CircularBuffer.h" 44 | #include "FGunKey.h" 45 | #include //if you remove this, you will have a very bad time. 46 | 47 | namespace Arty 48 | { 49 | typedef uint64_t ArtilleryDataSetKey; 50 | typedef ArtilleryDataSetKey ADSKey; 51 | typedef uint64_t TickliteKey; 52 | //this must use the same type as actor keys and artillery object keys like projectile or mesh 53 | 54 | DECLARE_DELEGATE(CalculateTicklite); 55 | //performs the actual data transformations. 56 | DECLARE_DELEGATE(ApplyTicklite); 57 | //resets any data related to apply 58 | DECLARE_DELEGATE(ResetTicklike) 59 | DECLARE_DELEGATE_TwoParams(NormalTicklite, ActorKey, ADSKey); 60 | 61 | 62 | // direct use of FINAL_TICK_RESOLVE is strictly prohibited and will break everything. 63 | // it is possible in modern C++ to create a 2nd enum that hides recharge. 64 | // I would like to not do this. please don't use FINAL_TICK_RESOLVE directly. 65 | // FINAL_TICK_RESOLVE is ONLY for FTFinalTickResolver or a subclass and is reserved 66 | // for gameplay implementation of tick-resolving actions like finalizing damage 67 | // or applying queued effects. 68 | enum TicklitePhase 69 | { 70 | Early = 1, 71 | Normal = 2, 72 | Late = 4, 73 | FINAL_TICK_RESOLVE = 2048 74 | }; 75 | 76 | 77 | enum TickliteCadence 78 | { 79 | Critical = 1, 80 | Tick = 2, 81 | Lite = 8, 82 | Slow = 32 83 | }; 84 | 85 | struct TicklikeMemoryBlock 86 | { 87 | TickliteCadence Cadence = TickliteCadence::Lite; 88 | TicklitePhase RunGroup = TicklitePhase::Normal; 89 | ArtilleryTime MadeStamp = 0; 90 | }; 91 | struct TicklitePrototype : TicklikeMemoryBlock 92 | { 93 | virtual void CalculateTickable() = 0; 94 | virtual bool ShouldExpireTickable() = 0; 95 | 96 | 97 | 98 | //This trigger effects and this is borderline necessary for some semantics, like popping a poison tag off 99 | //when an effect ends. where possible, we should prefer ArtilleryEvents for this, but it won't always be viable. 100 | //use your best judgment. 101 | virtual void OnExpireTickable() = 0; 102 | virtual void ApplyTickable() = 0; 103 | virtual void ReturnToPool() = 0; 104 | 105 | virtual ~TicklitePrototype() 106 | { 107 | }; 108 | }; 109 | 110 | //You might notice Ticklite is the name used in implementation, but you might derive other ticklikes. 111 | //In fact, this class exists as what is, effectively, a poor man's trait. This will be deprecated eventually 112 | //if we don't find any uses for it, and the ticklite template will supersede it, but I think that won't happen. 113 | 114 | 115 | 116 | typedef TArray> EventBuffer; 117 | typedef TTripleBuffer BufferedEvents; 118 | 119 | //Ever see the motto of the old naval railgun project? I won't spoil it for you. 120 | typedef FVector3d VelocityVec; 121 | typedef TTuple VelocityEvent; 122 | 123 | typedef TCircularQueue VelocityStack; 124 | typedef TSharedPtr VelocityEP; //event pump, if you must know. 125 | } 126 | 127 | //PATH TO DATA TABLES 128 | //constexpr const FString GunsManifests = ""; 129 | //TODO: ALWAYS customize this to the sample-rate you select for cabling. ALWAYS. Or your game will feel Real Bad. 130 | constexpr int ArtilleryInputSearchWindow = TheCone::BristleconeSendHertz; 131 | constexpr const inline int ArtilleryHoldSweepBack = 5; // this is literally the sin within the beast. 132 | constexpr const inline int ArtilleryFlickSweepBack = 15; // And this is no better. 133 | 134 | //But this is pretty good. Here it is, the most magical constant I've written. 135 | //we use the Cabling Integerized Sticks. Normally, we turn them into floats. 136 | //But squares of floats are a good way to blow your bit corruption limits for 137 | //FP rounding error. So here, we actually use the sqrMag of the debiased ints. 138 | //It works just the same. It's distance across a metric space, number of discretized 139 | //positions away from center on an axis. So we need a magnitude boundary to start 140 | //a stick flick detection from. This is that. 141 | //This is 2*(975*975) 142 | constexpr const inline uint32_t ArtilleryMagicFlickBoundary = 1901250; 143 | //these should LIKELY be the same, but I could see an argument than this might need to be a little smaller? 144 | //Tune as needed. we actually maintain a surprisingly finegrained degree of control here. 145 | constexpr const inline uint32_t ArtilleryMagicMinimumFlickDistanceRequired = 1901250; 146 | using namespace Arty; 147 | struct FActionPatternParams 148 | { 149 | public: 150 | //this specifies a parametric bind's "preference" which will need to become an int for priority. 151 | //and if the binding consumes the input or passes it through. 152 | // an example is that we WILL want to say that holding down the trigger should be fired before 153 | // single press. actually, we might do pattern-priority rather than anything else. 154 | // hard to say. there is a trick here that could let us handle firing a diff ability if the ability is on cool down but I'm not borrowing trouble. 155 | bool preferToMatch = false; 156 | bool consumeInput = true; 157 | bool defaultBehavior = false; 158 | bool FiresCosmetics = false; 159 | 160 | FGunKey ToFire; //IT WAS NOT A MISTAKE. I AM A GENIUS. 161 | FActionBitMask ToSeek; 162 | InputStreamKey MyInputStream; 163 | FireControlKey MyOrigin; 164 | FActionPatternParams(const FActionBitMask ToSeek_In, FireControlKey MyOrigin_In, InputStreamKey MyInputStream_In, FGunKey Fireable) : 165 | ToSeek(ToSeek_In), MyInputStream(MyInputStream_In), MyOrigin(MyOrigin_In) 166 | { 167 | ToFire = Fireable; 168 | }; 169 | 170 | friend uint32 GetTypeHash(const FActionPatternParams& Other) 171 | { 172 | // it's probably fine! 173 | return GetTypeHash(Other.ToFire) + GetTypeHash(Other.ToSeek); 174 | } 175 | 176 | }; 177 | 178 | static bool operator==(FActionPatternParams const& lhs, FActionPatternParams const& rhs) { 179 | return lhs.ToFire == rhs.ToFire; 180 | } 181 | 182 | 183 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/TestTypes/FMockBeamCannon.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "FArtilleryGun.h" 5 | #include "FTSphereCast.h" 6 | #include "UArtilleryAbilityMinimum.h" 7 | 8 | #include "NiagaraFunctionLibrary.h" 9 | #include "NiagaraComponent.h" 10 | 11 | #include "FMockBeamCannon.generated.h" 12 | 13 | #define SCREEN_MESSAGE(str) if (GEngine) \ 14 | { \ 15 | GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Yellow, FString::Printf(TEXT(str))); \ 16 | } 17 | 18 | /** 19 | * Well THIS test class encapsulates a beam cannon! 20 | * 21 | */ 22 | USTRUCT(BlueprintType) 23 | struct ARTILLERYRUNTIME_API FMockBeamCannon : public FArtilleryGun 24 | { 25 | GENERATED_BODY() 26 | 27 | public: 28 | friend class UArtilleryPerActorAbilityMinimum; 29 | 30 | // Gun parameters 31 | float Range; 32 | 33 | // Beam effect 34 | UPROPERTY() 35 | UNiagaraSystem* Beam; 36 | 37 | FMockBeamCannon(const FGunKey& KeyFromDispatch, int MaxAmmoIn, int FirerateIn, int ReloadTimeIn, float BeamGunRange) 38 | { 39 | MyGunKey = KeyFromDispatch; 40 | MaxAmmo = MaxAmmoIn; 41 | Firerate = FirerateIn; 42 | ReloadTime = ReloadTimeIn; 43 | MyDispatch = nullptr; 44 | 45 | Range = BeamGunRange; 46 | Beam = nullptr; 47 | }; 48 | 49 | FMockBeamCannon() : Super() 50 | { 51 | MyDispatch = nullptr; 52 | 53 | MyGunKey = Default; 54 | MaxAmmo = 100; 55 | Firerate = 2; 56 | ReloadTime = 150; 57 | Range = 5000.0f; 58 | Beam = nullptr; 59 | }; 60 | 61 | virtual bool Initialize( 62 | const FGunKey& KeyFromDispatch, 63 | const bool MyCodeWillHandleKeys, 64 | UArtilleryPerActorAbilityMinimum* PF = nullptr, 65 | UArtilleryPerActorAbilityMinimum* PFC = nullptr, 66 | UArtilleryPerActorAbilityMinimum* F = nullptr, 67 | UArtilleryPerActorAbilityMinimum* FC = nullptr, 68 | UArtilleryPerActorAbilityMinimum* PtF = nullptr, 69 | UArtilleryPerActorAbilityMinimum* PtFc = nullptr, 70 | UArtilleryPerActorAbilityMinimum* FFC = nullptr) override 71 | { 72 | Beam = LoadObject(nullptr, TEXT("/Game/Blueprints/BeamCannon/BeamSystem.BeamSystem"), nullptr, LOAD_None, nullptr); 73 | check(Beam != nullptr); 74 | 75 | return ARTGUN_MACROAUTOINIT(MyCodeWillHandleKeys); 76 | } 77 | 78 | virtual void PreFireGun( 79 | const FGameplayAbilitySpecHandle Handle, 80 | const FGameplayAbilityActorInfo* ActorInfo, 81 | const FGameplayAbilityActivationInfo ActivationInfo, 82 | const FGameplayEventData* TriggerEventData = nullptr, 83 | bool RerunDueToReconcile = false, 84 | int DallyFramesToOmit = 0) override 85 | { 86 | AttrPtr CooldownRemainingPtr = MyDispatch->GetAttrib(MyGunKey, COOLDOWN_REMAINING); 87 | AttrPtr AmmoRemainingPtr = MyDispatch->GetAttrib(MyGunKey, AMMO); 88 | if (!CooldownRemainingPtr.IsValid() || CooldownRemainingPtr->GetCurrentValue() > 0.f) 89 | { 90 | // Cooldown not up yet! 91 | return; 92 | } 93 | 94 | if (!AmmoRemainingPtr.IsValid() || AmmoRemainingPtr->GetCurrentValue() <= 0.f) 95 | { 96 | // No ammo! 97 | return; 98 | } 99 | FireGun(Fired, 0, ActorInfo, ActivationInfo, false, TriggerEventData, Handle); 100 | }; 101 | 102 | virtual void FireGun( 103 | FArtilleryStates OutcomeStates, 104 | int DallyFramesToOmit, 105 | const FGameplayAbilityActorInfo* ActorInfo, 106 | const FGameplayAbilityActivationInfo ActivationInfo, 107 | bool RerunDueToReconcile, 108 | const FGameplayEventData* TriggerEventData, 109 | FGameplayAbilitySpecHandle Handle) override 110 | { 111 | if (PlayerCameraComponent.IsValid() && FiringPointComponent.IsValid()) 112 | { 113 | UE_LOG(LogTemp, Warning, TEXT("firing")); 114 | FVector StartLocation = PlayerCameraComponent->GetComponentLocation() + FVector(-10.0f, 0.0f, 0.0f); 115 | FRotator Rotation = PlayerCameraComponent->GetRelativeRotation(); 116 | 117 | UBarrageDispatch* Physics = MyDispatch->GetWorld()->GetSubsystem(); 118 | FBLet OwnerFiblet = Physics->GetShapeRef(MyProbableOwner); 119 | 120 | FTSphereCast temp = FTSphereCast( 121 | OwnerFiblet->KeyIntoBarrage, 122 | 0.01f, 123 | Range, 124 | StartLocation, 125 | Rotation.Vector(), 126 | // TODO: This possibly horribly fails when trying to synchronize network state 127 | // Not sure what pattern we want to enforce for hit reg callbacks though, so this temporarily works 128 | std::bind(&FMockBeamCannon::ResolveHit, this, std::placeholders::_1, std::placeholders::_2)); 129 | 130 | MyDispatch->RequestAddTicklite(MakeShareable(new TL_SphereCast(temp)), Early); 131 | 132 | // Fire particles 133 | UNiagaraComponent* BeamComp = UNiagaraFunctionLibrary::SpawnSystemAttached( 134 | Beam, 135 | FiringPointComponent.Get(), 136 | NAME_None, 137 | FVector(0.f), 138 | FRotator::ZeroRotator, 139 | EAttachLocation::Type::SnapToTarget, 140 | true, 141 | true, 142 | ENCPoolMethod::AutoRelease); 143 | BeamComp->SetVariablePosition(FName("Beam_End"), Rotation.Vector() * Range); 144 | 145 | PostFireGun(Fired, 0, ActorInfo, ActivationInfo, false, TriggerEventData, Handle); 146 | } 147 | } 148 | 149 | void ResolveHit(UE::Math::TVector RayStart, TSharedPtr HitResultFromTicklite) const 150 | { 151 | // DrawDebugLine(MyDispatch->GetWorld(), RayStart, HitResultFromTicklite->Location, FColor::Blue, false, 5.0f, 0, 1.0f); 152 | UBarrageDispatch* Physics = MyDispatch->GetWorld()->GetSubsystem(); 153 | FBarrageKey HitBarrageKey = Physics->GenerateBarrageKeyFromBodyId( 154 | static_cast(HitResultFromTicklite->MyItem)); 155 | FBLet HitObjectFiblet = Physics->GetShapeRef(HitBarrageKey); 156 | 157 | FSkeletonKey ObjectKey = HitObjectFiblet->KeyOutOfBarrage; 158 | AttrPtr HitObjectHealthPtr = MyDispatch->GetAttrib(ObjectKey, HEALTH); 159 | 160 | if (HitObjectHealthPtr.IsValid()) 161 | { 162 | HitObjectHealthPtr->SetCurrentValue(HitObjectHealthPtr->GetCurrentValue() - 5); 163 | } 164 | }; 165 | 166 | virtual void PostFireGun( 167 | FArtilleryStates OutcomeStates, 168 | int DallyFramesToOmit, 169 | const FGameplayAbilityActorInfo* ActorInfo, 170 | const FGameplayAbilityActivationInfo ActivationInfo, 171 | bool RerunDueToReconcile, 172 | const FGameplayEventData* TriggerEventData, 173 | FGameplayAbilitySpecHandle Handle) override 174 | { 175 | AttrPtr AmmoPtr = MyDispatch->GetAttrib(MyGunKey, AMMO); 176 | if (AmmoPtr.IsValid()) 177 | { 178 | AmmoPtr->SetCurrentValue(AmmoPtr->GetCurrentValue() - 1); 179 | } 180 | AttrPtr CooldownPtr = MyDispatch->GetAttrib(MyGunKey, COOLDOWN); 181 | AttrPtr CooldownRemainingPtr = MyDispatch->GetAttrib(MyGunKey, COOLDOWN_REMAINING); 182 | if (CooldownPtr.IsValid() && CooldownRemainingPtr.IsValid()) 183 | { 184 | CooldownRemainingPtr->SetCurrentValue(CooldownPtr->GetCurrentValue()); 185 | } 186 | 187 | MyDispatch->GetAttrib(MyGunKey, TICKS_SINCE_GUN_LAST_FIRED)->SetCurrentValue(0.f); 188 | MyDispatch->GetAttrib(MyGunKey, AttribKey::LastFiredTimestamp)->SetCurrentValue(static_cast(MyDispatch->GetShadowNow())); 189 | }; 190 | 191 | private: 192 | static const inline FGunKey Default = FGunKey("Laser", UINT64_MAX); 193 | }; 194 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/Systems/ArtilleryBPLibs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ArtilleryDispatch.h" 3 | #include "CanonicalInputStreamECS.h" 4 | #include "PhysicsTypes/BarragePlayerAgent.h" 5 | #include "ArtilleryBPLibs.generated.h" 6 | 7 | UCLASS(meta=(ScriptName="InputSystemLibrary")) 8 | class ARTILLERYRUNTIME_API UInputECSLibrary : public UBlueprintFunctionLibrary 9 | { 10 | GENERATED_BODY() 11 | public: 12 | static void GetHistoricalInputs(TArray& Inputs, int Count) 13 | { 14 | auto ptr = UCanonicalInputStreamECS::SelfPtr; 15 | if(ptr) 16 | { 17 | auto streamkey = ptr->GetStreamForPlayer(PlayerKey::CABLE); 18 | auto sptr = ptr->GetStream(streamkey); 19 | for(int i = 0; i <= Count; ++i) 20 | { 21 | auto input = sptr.Get()->peek( sptr->GetHighestGuaranteedInput()-i); 22 | Inputs.Add(input.has_value() ? input.value() : FArtilleryShell()); 23 | } 24 | } 25 | } 26 | 27 | UFUNCTION(BlueprintPure, meta = (ScriptName = "Get15PlayerInputs", DisplayName = "Get Last 15 of Local Player's Inputs", WorldContext = "WorldContextObject", HidePin = "WorldContextObject"), Category="Artillery|Inputs") 28 | static void K2_Get15LocalHistoricalInputs(UObject* WorldContextObject, TArray &Inputs) 29 | { 30 | GetHistoricalInputs(Inputs, 15); 31 | } 32 | }; 33 | 34 | UCLASS(meta=(ScriptName="AbilitySystemLibrary")) 35 | class ARTILLERYRUNTIME_API UArtilleryLibrary : public UBlueprintFunctionLibrary 36 | { 37 | GENERATED_BODY() 38 | public: 39 | UFUNCTION(BlueprintCallable, meta = (ScriptName = "GetAttribute", DisplayName = "Get Attribute Of", ExpandBoolAsExecs="bFound"), Category="Artillery|Attributes") 40 | static float K2_GetAttrib(FSkeletonKey Owner, E_AttribKey Attrib, bool& bFound) 41 | { 42 | bFound = false; 43 | return implK2_GetAttrib(Owner,Attrib, bFound); 44 | } 45 | 46 | static float implK2_GetAttrib(FSkeletonKey Owner, E_AttribKey Attrib, bool& bFound) 47 | { 48 | 49 | bFound = false; 50 | if(UArtilleryDispatch::SelfPtr) 51 | { 52 | if(UArtilleryDispatch::SelfPtr->GetAttrib( Owner, Attrib)) 53 | { 54 | bFound = true; 55 | return UArtilleryDispatch::SelfPtr->GetAttrib( Owner, Attrib)->GetCurrentValue(); 56 | } 57 | } 58 | return NAN; 59 | } 60 | UFUNCTION(BlueprintCallable, meta = (ScriptName = "GetRelatedKey", DisplayName = "Get Related Key From", ExpandBoolAsExecs="bFound"), Category="Artillery|Keys") 61 | static FSkeletonKey K2_GetIdentity(FSkeletonKey Owner, E_IdentityAttrib Attrib, bool& bFound) 62 | { 63 | 64 | bFound = false; 65 | return implK2_GetIdentity(Owner, Attrib, bFound); 66 | } 67 | 68 | static FSkeletonKey implK2_GetIdentity(FSkeletonKey Owner, E_IdentityAttrib Attrib, bool& bFound) 69 | { 70 | 71 | bFound = false; 72 | if(UArtilleryDispatch::SelfPtr) 73 | { 74 | auto ident = UArtilleryDispatch::SelfPtr->GetIdent( Owner, Attrib); 75 | if(ident) 76 | { 77 | bFound = true; 78 | return ident->CurrentValue; 79 | } 80 | } 81 | return FSkeletonKey(); 82 | } 83 | 84 | UFUNCTION(BlueprintCallable, meta = (ScriptName = "GetPlayerRelatedKey", DisplayName = "Get Local Player's Related Key", WorldContext = "WorldContextObject", HidePin = "WorldContextObject", ExpandBoolAsExecs="bFound"), Category="Artillery|Keys") 85 | static FSkeletonKey K2_GetPlayerIdentity(UObject* WorldContextObject, E_IdentityAttrib Attrib, bool& bFound) 86 | { 87 | 88 | bFound = false; 89 | auto ptr = WorldContextObject->GetWorld()->GetSubsystem(); 90 | if(ptr) 91 | { 92 | auto streamkey = ptr->GetStreamForPlayer(PlayerKey::CABLE); 93 | auto key = ptr->ActorByStream(streamkey); 94 | if(key) 95 | { 96 | return implK2_GetIdentity(key, Attrib, bFound); 97 | } 98 | } 99 | bFound = false; 100 | return FSkeletonKey(); 101 | } 102 | 103 | UFUNCTION(BlueprintCallable, meta = (ScriptName = "GetThisActorAttribute", DisplayName = "Get My Actor's Attribute", DefaultToSelf = "Actor", HidePin = "Actor", ExpandBoolAsExecs="bFound"), Category="Artillery|Attributes") 104 | static float K2_GetMyAttrib(AActor *Actor, E_AttribKey Attrib, bool& bFound) 105 | { 106 | 107 | bFound = false; 108 | auto ptr = Actor->GetComponentByClass(); 109 | if(ptr) 110 | { 111 | if(FSkeletonKey key = ptr->GetObjectKey()) 112 | { 113 | return implK2_GetAttrib(key, Attrib, bFound); 114 | } 115 | } 116 | bFound = false; 117 | return NAN; 118 | } 119 | 120 | UFUNCTION(BlueprintCallable, meta = (ScriptName = "GetPlayerAttribute", DisplayName = "Get Local Player's Attribute", WorldContext = "WorldContextObject", HidePin = "WorldContextObject", ExpandBoolAsExecs="bFound"), Category="Artillery|Attributes") 121 | static float K2_GetPlayerAttrib(UObject* WorldContextObject, E_AttribKey Attrib, bool& bFound) 122 | { 123 | bFound = false; 124 | auto ptr = WorldContextObject->GetWorld()->GetSubsystem(); 125 | if(ptr) 126 | { 127 | auto streamkey = ptr->GetStreamForPlayer(PlayerKey::CABLE); 128 | auto key = ptr->ActorByStream(streamkey); 129 | if(key) 130 | { 131 | return implK2_GetAttrib(key, Attrib, bFound); 132 | } 133 | } 134 | bFound = false; 135 | return NAN; 136 | } 137 | //DEPRECATED 138 | //TODO: This needs to be replaced by GetPlayerBarrageAgent(PlayerKey) 139 | static TObjectPtr GetLocalPlayerBarrageAgent() 140 | { 141 | if (UTransformDispatch::SelfPtr && UArtilleryDispatch::SelfPtr && UCanonicalInputStreamECS::SelfPtr) 142 | { 143 | if (CurrentPlayerAgent && CurrentPlayerAgent->IsValidLowLevelFast() && CurrentPlayerAgent->GetOwner()-> 144 | IsActorTickEnabled()) 145 | { 146 | return CurrentPlayerAgent; 147 | } 148 | 149 | CurrentPlayerAgent = nullptr; 150 | auto local = UCanonicalInputStreamECS::SelfPtr->GetStreamForPlayer(PlayerKey::CABLE); 151 | if (local != 0) 152 | { 153 | auto playerkey = UCanonicalInputStreamECS::SelfPtr->ActorByStream(local); 154 | TObjectPtr SecretName = UTransformDispatch::SelfPtr->GetAActorByObjectKey(playerkey).Get(); 155 | if (SecretName) 156 | { 157 | CurrentPlayerAgent = SecretName->GetComponentByClass(); 158 | return CurrentPlayerAgent; 159 | } 160 | } 161 | } 162 | return nullptr; 163 | } 164 | 165 | //DEPRECATED 166 | //TODO: This needs to be replaced by GetPlayerVectors(Forward, Right, PlayerKey) 167 | static void GetLocalPlayerVectors(FVector& Forward, FVector& Right) 168 | { 169 | if(UTransformDispatch::SelfPtr && UArtilleryDispatch::SelfPtr && UCanonicalInputStreamECS::SelfPtr) 170 | { 171 | auto local = GetLocalPlayerBarrageAgent(); 172 | if(local && local->IsValidLowLevelFast()) 173 | { 174 | Forward = local->Chaos_LastGameFrameForwardVector(); 175 | Right = local->Chaos_LastGameFrameRightVector(); 176 | } 177 | } 178 | } 179 | 180 | UFUNCTION(BlueprintPure, meta = (ScriptName = "GetPlayerVectors", DisplayName = "Get Local Player's Attribute"), Category="Artillery|Character") 181 | static void K2_GetLocalPlayerVectors(FVector& Forward, FVector& Right) 182 | { 183 | GetLocalPlayerVectors(Forward, Right); 184 | } 185 | 186 | static void SimpleEstimator(FVector& Forwardish, double Counter = 15) 187 | { 188 | FVector Right; 189 | GetLocalPlayerVectors(Forwardish, Right); 190 | TArray In; 191 | UInputECSLibrary::GetHistoricalInputs(In, Counter); 192 | double accumulateX = 0; 193 | double accumulateY = 0; 194 | for(auto& shell : In) 195 | { 196 | accumulateX += shell.GetStickLeftX(); 197 | accumulateY += shell.GetStickLeftY(); 198 | } 199 | accumulateX = accumulateX/Counter; 200 | accumulateY = accumulateY/Counter; 201 | //for serious work, replace this. 202 | accumulateX += In[0].GetStickLeftX(); 203 | accumulateX += In[0].GetStickLeftX(); 204 | accumulateX += In[0].GetStickLeftX(); 205 | accumulateY += In[0].GetStickLeftY(); 206 | accumulateY += In[0].GetStickLeftY(); 207 | accumulateY += In[0].GetStickLeftY(); 208 | accumulateX = accumulateX/4.0; 209 | accumulateY = accumulateY/4.0; 210 | auto bind = GetLocalPlayerBarrageAgent(); 211 | if(bind) 212 | { 213 | auto moveX = accumulateX * bind->Acceleration * Right; 214 | auto moveY = accumulateY * bind->Acceleration * Forwardish; 215 | Forwardish = moveX + moveY; 216 | } 217 | } 218 | 219 | UFUNCTION(BlueprintPure, meta = (ScriptName = "GetPlayerDirectionEstimator", DisplayName = "Get Local Player's Direction Estimator"), Category="Artillery|Character") 220 | static void K2_GetPlayerDirectionEstimator(FVector& Forward) 221 | { 222 | SimpleEstimator(Forward, 15); 223 | } 224 | 225 | private: 226 | static inline TObjectPtr CurrentPlayerAgent = nullptr; 227 | }; -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/PhysicsTypes/BarragePlayerAgent.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "BarrageColliderBase.h" 7 | #include "BarrageDispatch.h" 8 | #include "SkeletonTypes.h" 9 | #include "KeyCarry.h" 10 | #include "FBarragePrimitive.h" 11 | #include "Components/ActorComponent.h" 12 | #include "Components/CapsuleComponent.h" 13 | #include "BarragePlayerAgent.generated.h" 14 | 15 | 16 | UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) 17 | class ARTILLERYRUNTIME_API UBarragePlayerAgent : public UBarrageColliderBase 18 | { 19 | GENERATED_BODY() 20 | 21 | //This leans HARD on the collider base but retains more uniqueness than the others. 22 | public: 23 | using Caps = 24 | UE::Geometry::FCapsule3d; 25 | // Sets default values for this component's properties 26 | UPROPERTY() 27 | double radius; 28 | UPROPERTY() 29 | double extent; 30 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Movement, meta=(ClampMin="0", UIMin="0")) 31 | float TurningBoost = 1.1; 32 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Movement) 33 | float MaxVelocity = 600; 34 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Movement) 35 | float Deceleration = 200; 36 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Movement) 37 | float Acceleration = 200; 38 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Movement) 39 | float AirAcceleration = 7; 40 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Movement) 41 | float JumpImpulse = 1000; 42 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Movement) 43 | float WallJumpImpulse = 500; 44 | 45 | 46 | 47 | [[nodiscard]] FVector Chaos_LastGameFrameRightVector() const 48 | { 49 | return CHAOS_LastGameFrameRightVector.IsNearlyZero() ? FVector::RightVector : CHAOS_LastGameFrameRightVector; 50 | } 51 | 52 | [[nodiscard]] FVector Chaos_LastGameFrameForwardVector() const 53 | { 54 | return CHAOS_LastGameFrameForwardVector.IsNearlyZero() ? FVector::ForwardVector : CHAOS_LastGameFrameForwardVector ; 55 | } 56 | 57 | UBarragePlayerAgent(const FObjectInitializer& ObjectInitializer); 58 | virtual void Register() override; 59 | void AddForce(float Duration); 60 | void ApplyRotation(float Duration, FQuat4f Rotation); 61 | void AddOneTickOfForce(FVector3d Force); 62 | // Kludge for now until we double-ify everything 63 | void AddOneTickOfForce(FVector3f Force); 64 | FVector3f GetVelocity(); 65 | FVector3f GetGroundNormal(); 66 | bool IsOnGround(); 67 | // Called when the game starts 68 | virtual void BeginPlay() override; 69 | 70 | // Called every frame 71 | virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; 72 | 73 | void ApplyAimFriction(const ActorKey& ActorsKey, const FVector3d& ActorLocation, const FVector3d& Direction, FVector2d& OutAimVector); 74 | 75 | protected: 76 | UPROPERTY(BlueprintReadOnly) 77 | FVector CHAOS_LastGameFrameRightVector = FVector::ZeroVector; 78 | UPROPERTY(BlueprintReadOnly) 79 | FVector CHAOS_LastGameFrameForwardVector = FVector::ZeroVector; 80 | 81 | private: 82 | // Currently targeted object 83 | FBLet TargetFiblet; 84 | TWeakObjectPtr TargetPtr; 85 | }; 86 | 87 | //CONSTRUCTORS 88 | //-------------------- 89 | //do not invoke the default constructor unless you have a really good plan. in general, let UE initialize your components. 90 | 91 | // Sets default values for this component's properties 92 | inline UBarragePlayerAgent::UBarragePlayerAgent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) 93 | { 94 | // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features 95 | // off to improve performance if you don't need them. 96 | 97 | PrimaryComponentTick.bCanEverTick = true; 98 | MyObjectKey = 0; 99 | } 100 | 101 | inline FVector3f UBarragePlayerAgent::GetVelocity() 102 | { 103 | return FBarragePrimitive::GetVelocity(MyBarrageBody); 104 | } 105 | 106 | inline FVector3f UBarragePlayerAgent::GetGroundNormal() 107 | { 108 | return FBarragePrimitive::GetCharacterGroundNormal(MyBarrageBody); 109 | } 110 | 111 | inline bool UBarragePlayerAgent::IsOnGround() 112 | { 113 | return FBarragePrimitive::IsCharacterOnGround(MyBarrageBody); 114 | } 115 | //KEY REGISTER, initializer, and failover. 116 | //---------------------------------- 117 | 118 | inline void UBarragePlayerAgent::Register() 119 | { 120 | if(MyObjectKey ==0 ) 121 | { 122 | if(GetOwner()) 123 | { 124 | if(GetOwner()->GetComponentByClass()) 125 | { 126 | MyObjectKey = GetOwner()->GetComponentByClass()->GetObjectKey(); 127 | } 128 | 129 | if(MyObjectKey == 0) 130 | { 131 | auto val = PointerHash(GetOwner()); 132 | ActorKey TopLevelActorKey = ActorKey(val); 133 | MyObjectKey = TopLevelActorKey; 134 | } 135 | } 136 | } 137 | if(!IsReady && MyObjectKey != 0 && !GetOwner()->GetActorLocation().ContainsNaN()) // this could easily be just the !=, but it's better to have the whole idiom in the example 138 | { 139 | auto Physics = GetWorld()->GetSubsystem(); 140 | auto params = FBarrageBounder::GenerateCharacterBounds(GetOwner()->GetActorLocation(), radius, extent, MaxVelocity); 141 | MyBarrageBody = Physics->CreatePrimitive(params, MyObjectKey, Layers::MOVING); 142 | 143 | if(MyBarrageBody) 144 | { 145 | IsReady = true; 146 | } 147 | } 148 | } 149 | 150 | inline void UBarragePlayerAgent::AddForce(float Duration) 151 | { 152 | //I'll be back for youuuu. 153 | throw; 154 | } 155 | 156 | inline void UBarragePlayerAgent::ApplyRotation(float Duration, FQuat4f Rotation) 157 | { 158 | //I'll be back for youuuu. 159 | throw; 160 | } 161 | 162 | inline void UBarragePlayerAgent::AddOneTickOfForce(FVector3d Force) 163 | { 164 | FBarragePrimitive::ApplyForce(Force, MyBarrageBody); 165 | } 166 | 167 | inline void UBarragePlayerAgent::AddOneTickOfForce(FVector3f Force) 168 | { 169 | FBarragePrimitive::ApplyForce(FVector3d(Force.X, Force.Y, Force.Z), MyBarrageBody); 170 | } 171 | 172 | // Called when the game starts 173 | inline void UBarragePlayerAgent::BeginPlay() 174 | { 175 | Super::BeginPlay(); 176 | Register(); 177 | } 178 | 179 | inline void UBarragePlayerAgent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) 180 | { 181 | Super::TickComponent(DeltaTime, TickType, ThisTickFunction); 182 | if(!IsReady) 183 | { 184 | Register();// ... 185 | } 186 | 187 | CHAOS_LastGameFrameRightVector = GetOwner()->GetActorRightVector(); 188 | CHAOS_LastGameFrameForwardVector = GetOwner()->GetActorForwardVector(); 189 | 190 | } 191 | 192 | inline void UBarragePlayerAgent::ApplyAimFriction( 193 | const ActorKey& ActorsKey, 194 | const FVector3d& ActorLocation, 195 | const FVector3d& Direction, 196 | FVector2d& OutAimVector) 197 | { 198 | double TotalFriction = 1.0f; 199 | 200 | UBarrageDispatch* Physics = GetWorld()->GetSubsystem(); 201 | check(Physics); 202 | 203 | FBLet MyFiblet = Physics->GetShapeRef(ActorsKey); 204 | check(MyFiblet); // The actor calling this sure as hell better be allocated already 205 | 206 | TSharedPtr HitObjectResult = MakeShared(); 207 | Physics->SphereCast( 208 | MyFiblet->KeyIntoBarrage, 209 | 0.01f, 210 | 1000.0f, // Hard-coding range for now until we determine how we want to handle range on this 211 | ActorLocation, 212 | Direction, 213 | HitObjectResult); 214 | 215 | FBarrageKey HitBarrageKey = Physics->GetBarrageKeyFromFHitResult(HitObjectResult); 216 | 217 | // Determine if we've changed targets 218 | if (HitBarrageKey != 0) 219 | { 220 | if (!TargetFiblet.IsValid() || HitBarrageKey != TargetFiblet->KeyIntoBarrage) 221 | { 222 | TargetFiblet = Physics->GetShapeRef(HitBarrageKey); 223 | check(TargetFiblet.IsValid()); 224 | 225 | UTransformDispatch* TransformDispatch = GetWorld()->GetSubsystem(); 226 | check(TransformDispatch); 227 | 228 | FBLet PotentialTargetFiblet = Physics->GetShapeRef(HitBarrageKey); 229 | TWeakObjectPtr PotentialNewTarget = TransformDispatch->GetAActorByObjectKey(TargetFiblet->KeyOutOfBarrage); 230 | if (PotentialNewTarget.IsValid() && PotentialNewTarget.Get()->Tags.Contains(FName("enemy"))) 231 | { 232 | TargetPtr = PotentialNewTarget; 233 | } 234 | } 235 | } 236 | else 237 | { 238 | TargetFiblet.Reset(); 239 | TargetPtr.Reset(); 240 | } 241 | 242 | if (TargetPtr.IsValid()) 243 | { 244 | // TODO - determine if aim vector is moving towards or away from a friction point 245 | TotalFriction = 0.5f; 246 | } 247 | 248 | UE_LOG(LogTemp, Warning, TEXT("Target found, applying friction to reticle ('%f')"), TotalFriction); 249 | OutAimVector *= TotalFriction; 250 | } 251 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/Systems/FArtilleryTicklitesThread.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CoreMinimal.h" 3 | #include "HAL/Runnable.h" 4 | #include 5 | 6 | //this is a busy-style thread, which runs preset bodies of work in a specified order. Generally, the goal is that it never 7 | //actually sleeps. In fact, it only ever waits on the Artillery busy thread. 8 | // 9 | // This is similar to but functionally very different from a work-stealing or task model like what we see in rust. 10 | // This thread runs ticklites, which are simple functions that satisfy the following properties: 11 | // They are order insensitive. Surprisingly, most things are. 12 | // They do not run the tick they are applied. 13 | // They operate only on gameplay tags, abilities, and transforms. 14 | // Anything that would normally be a ObjectPtr, SharedPtr, or Reference of any kind should be replaced by a key unless you are absolutely sure that object has a match-duration lifecycle. 15 | // This is because expired ticklikes may be reactivated during rollback, as one would expect. Because we reverse attribute and tag _state_, those will be _ticked_ as normal.s 16 | // They modify only gameplay tags and abilities as of this writing. Transforms may become possible with Jolt. 17 | // In the interim, we will support velocity change events submitted to the busyworker. This may be the permanent system. 18 | // In general, if it cannot be accessed through Artillery, Thistle, or Sunflower, do not use it. 19 | // The gameplay tags and abilities they operate on are owned and managed by the artillery system. 20 | // They run on this thread, rather than the gameplay thread. 21 | // They have either a duration, a lifecycle owner, or are created by a gameplay experience that serves as their lifecycle owner.* 22 | // 23 | // * This is not enforced. 24 | // 25 | // Unlike OnTick, which can't make even rudimentary guarantees about when or how often it will run, 26 | // Ticklikes run on the artillery tick rather than on the UE frame. They also run on this thread, 27 | // rather than the gamethread. FInally, and most importantly Ticklikes can be attached to anything, 28 | // not just actors. this allows one ticklike to move multiple projectiles, affect multiple actors, 29 | // or do things like batch process conditions such as frozen. This sounds like a performance optimization, 30 | // and in many ways it is, but it also creates a far easier usage semantic than gameplay effects which often struggle 31 | // despite being nominally more flexible. 32 | // 33 | // It also means they must be written in C++ for the time being. Hopefully, we can relax this restriction. 34 | // Fortunately, because they are NOT assets, it is trivial to make them data driven. This means we need quite few 35 | // of them overall, and that they can be easily instanced and referenced from blueprint. 36 | // 37 | // Finally, Ticklikes may be used to trigger the firing of ArtilleryGuns on the next frame and 38 | // any complex ticklike should probably work this way during prototyping. It does mean the earliest 39 | // possible firing is 2 frames after application, but at 120ticks per second, that's 16ms. 40 | // 41 | // As a result of being unable to fire the frame they are added, and unable to fire guns on the frame they fire, 42 | // the fastest cadence we allow a ticklite to be checked at is 2. Cadences are aligned for entire groups. 43 | // Do not rely on cadence to ensure ordering. 44 | // 45 | // If ordering is mandatory, absolutely mandatory, start by using Phase. If that's not a strong enough guarantee, 46 | // consider using either an ArtilleryAutoGun or triggering an ArtilleryGun from your ticklite. In general, though, 47 | // effects like thorns should apply in the last phase. Phases are intended to order ticklikes relative to other ticklikes. 48 | // Any additional ordering benefits they provide should be considered UB for the time being, and should not be relied on. 49 | // 50 | // Good luck, and may the force be with you. 51 | template 52 | class FArtilleryTicklitesWorker : public FRunnable { 53 | 54 | //This isn't super safe but like busy worker, ticklites only runs in one spot. 55 | friend class UArtilleryDispatch; 56 | ArtilleryTime LocalNow; 57 | 58 | static const int GroupCount = 4; 59 | TickliteGroup ExecutionGroups[GroupCount]; 60 | 61 | 62 | 63 | 64 | protected: 65 | TickliteBuffer QueuedAdds; 66 | 67 | TSharedPtr TickliteAdd(TSharedPtr AllocatedTL, TicklitePhase Group) 68 | { 69 | switch (Group) 70 | { 71 | case TicklitePhase::Early : 72 | { 73 | ExecutionGroups[0].Add(AllocatedTL); 74 | return AllocatedTL; 75 | } 76 | case TicklitePhase::Normal : 77 | { 78 | ExecutionGroups[1].Add( AllocatedTL); 79 | return AllocatedTL; 80 | } 81 | case TicklitePhase::Late : 82 | { 83 | ExecutionGroups[2].Add(AllocatedTL); 84 | return AllocatedTL; 85 | } 86 | case TicklitePhase::FINAL_TICK_RESOLVE : 87 | { 88 | ExecutionGroups[3].Add(AllocatedTL); 89 | return AllocatedTL; 90 | } 91 | } 92 | return nullptr; 93 | } 94 | //we may be able to remove sim or move it outside the run loop. I don't think there's anything wrong with simulating 95 | //as fast as we can, and it buys us a lot of perf time by not sleeping the thread until it's apply time. 96 | FSharedEventRef StartTicklitesSim; 97 | //Apply is necessary. 98 | FSharedEventRef StartTicklitesApply; 99 | public: 100 | //Templating here is used to both make reparenting easier if needed later and to simplify our dependency tree 101 | UDispatch* DispatchOwner; 102 | TOptional GetCopyOfShadowTransform(FSkeletonKey Target, ArtilleryTime Now) 103 | { 104 | return DispatchOwner->GetTransformShadowByObjectKey(Target, Now); 105 | } 106 | 107 | FBLet GetFBLetByObjectKey(FSkeletonKey Target, ArtilleryTime Now) 108 | { 109 | return DispatchOwner->GetFBLetByObjectKey(Target, Now); 110 | } 111 | FArtilleryTicklitesWorker(): LocalNow(0), DispatchOwner(nullptr), running(false) 112 | { 113 | QueuedAdds = MakeShareable(new TickliteRequests(128)); 114 | } 115 | 116 | void RequestAddTicklite(TSharedPtr ToAdd, TicklitePhase Group) 117 | { 118 | QueuedAdds->Enqueue(StampLiteRequest(ToAdd, Group)); 119 | } 120 | 121 | inline ArtilleryTime GetShadowNow() 122 | const 123 | { 124 | return DispatchOwner->GetShadowNow(); 125 | } 126 | 127 | inline AttrPtr GetAttrib(FSkeletonKey Target, AttribKey Attr) 128 | { 129 | return DispatchOwner->GetAttrib(Target, Attr); 130 | } 131 | 132 | virtual ~FArtilleryTicklitesWorker() override 133 | { 134 | UE_LOG(LogTemp, Display, TEXT("Artillery: Destructing SimTicklites thread.")); 135 | }; 136 | virtual bool QueueRollback() 137 | { 138 | //rollback is not implemented yet, but works by removing ticklikes added after the rollback's timestamp. 139 | //then adding back in any expired ticklikes that should be revived, clearing the current tick, and beginning resim. 140 | //Implementing this will not be easy, but it will suck a lot less than trying to do this with gameplay effects. 141 | //This is one reason we advocate STRONGLY for the use of KEYS over references, as references to memmory location 142 | //are not durable across rollbacks. 143 | throw; 144 | } 145 | 146 | virtual bool Init() override 147 | { 148 | LocalNow = 0; 149 | UE_LOG(LogTemp, Display, TEXT("Artillery: Booting SimTicklites thread.")); 150 | running = true; 151 | return true; 152 | 153 | } 154 | //TODO: ADD NULL GUARDS OR COPY. PREFER GUARD. 155 | void ApplyINE(TSharedPtr& x) 156 | { 157 | 158 | if( x->ShouldExpireTickable()) 159 | { 160 | //TODO: swap from arrays to slab or true pool? 161 | //Can't implement until we're sure that they _tick_ 162 | } 163 | else 164 | { 165 | x->ApplyTickable(); 166 | } 167 | } 168 | 169 | //TODO: ADD NULL GUARDS OR COPY. PREFER GUARD. 170 | void CalcINE(TSharedPtr& x) 171 | { 172 | if( x->ShouldExpireTickable()) 173 | { 174 | //TODO: swap from arrays to slab or true pool? 175 | //Can't implement until we're sure that they _tick_ 176 | } 177 | else 178 | { 179 | x->CalculateTickable(); 180 | } 181 | } 182 | 183 | //adding cadence is going to be quite annoying. 184 | virtual uint32 Run() override 185 | { 186 | StartTicklitesSim->Wait(); 187 | DispatchOwner->ThreadSetup(); 188 | while(running) { 189 | 190 | for(auto& Group : ExecutionGroups) 191 | { 192 | for(auto Tickable : Group) 193 | { 194 | CalcINE(Tickable); 195 | } 196 | } 197 | //if we have any ticklite requests, perform their calculations here and then 198 | //add them. 199 | //TODO: Reassess 12/10/24 200 | //this may cause consistency issues during resim, as artillery guns are fired on the main thread 201 | //which is not cadence-locked to the artillery threads. however, during resim, I believe this can be 202 | //resolved with the ticklite's add timestamp. and until we have resim, this is a non-issue. 203 | while(!QueuedAdds->IsEmpty()) 204 | { 205 | const StampLiteRequest AddTup = *QueuedAdds->Peek(); 206 | auto ptr = TickliteAdd(AddTup.Key, AddTup.Value); 207 | if(ptr) 208 | { 209 | CalcINE(ptr); 210 | } 211 | QueuedAdds->Dequeue(); 212 | } 213 | 214 | StartTicklitesApply->Wait(); 215 | StartTicklitesApply->Reset(); // we can run long on sim, not on apply. 216 | 217 | 218 | for (auto& Group : ExecutionGroups) 219 | { 220 | //this is just to make it clearer, 0 works just as well. 221 | int finalsize = Group.IsEmpty() ? -1 : Group.Num(); 222 | for(int index = 0; index < finalsize;) 223 | { 224 | //either a ticklite expires, and the count remaining drops by one, or we process it and move to next. 225 | if(Group[index]->ShouldExpireTickable()) 226 | { 227 | Group[index]->OnExpireTickable(); 228 | //TODO good chance we must save the ticklites from older frames that have expired if we want any hope at determinism 229 | //TODO THIS VIOLATES ORDERING. ...kinda. it's complicated. look, you almost certainly don't want it here. 230 | //we probably need to use sorted array anyway. 231 | Group.RemoveAtSwap(index, EAllowShrinking::No); //Determinism risk 232 | 233 | --finalsize;//hohoho. merry nothingmas. 234 | } 235 | else 236 | { 237 | Group[index]->ApplyTickable(); 238 | index++; 239 | } 240 | } 241 | } 242 | } 243 | 244 | return 0; 245 | } 246 | 247 | virtual void Exit() override 248 | { 249 | UE_LOG(LogTemp, Display, TEXT("Artillery: Exiting SimTicklites thread.")); 250 | running = false; 251 | Cleanup(); 252 | } 253 | 254 | virtual void Stop() override 255 | { 256 | FRunnable::Stop(); 257 | } 258 | 259 | 260 | 261 | 262 | 263 | private: 264 | void Cleanup() 265 | { 266 | running = false; 267 | }; 268 | bool running; 269 | }; 270 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Private/FArtilleryBusyWorker.cpp: -------------------------------------------------------------------------------- 1 | #include "FArtilleryBusyWorker.h" 2 | #include "ArtilleryDispatch.h" 3 | 4 | #include "BarrageDispatch.h" 5 | #include "Containers/TripleBuffer.h" 6 | 7 | FArtilleryBusyWorker::FArtilleryBusyWorker() : RequestorQueue_Abilities_TripleBuffer(nullptr), running(false) 8 | { 9 | UE_LOG(LogTemp, Display, TEXT("Artillery:BusyWorker: Constructing Artillery")); 10 | } 11 | 12 | FArtilleryBusyWorker::~FArtilleryBusyWorker() 13 | { 14 | UE_LOG(LogTemp, Display, TEXT("Artillery:BusyWorker: Destructing Artillery")); 15 | } 16 | 17 | bool FArtilleryBusyWorker::Init() 18 | { 19 | //you cannot reorder these. it is a magic ordering put in place for a hack. 20 | 21 | UE_LOG(LogTemp, Display, TEXT("Artillery:BusyWorker: Initializing Artillery thread")); 22 | running = true; 23 | return true; 24 | } 25 | 26 | void FArtilleryBusyWorker::RunStandardFrameSim(bool& missedPrior, uint64_t& currentIndexCabling, 27 | bool& burstDropDetected, TheCone::PacketElement& current, 28 | bool& RemoteInput) const 29 | { 30 | //this is an odd thing to do, I know, but we have some book-keeping we want to reserve for each code path. 31 | //once this settles a little, I'll refactor, but I'm going to end up reworking this next weekend. 32 | if (InputRingBuffer != nullptr && !InputRingBuffer.Get()->IsEmpty()) 33 | { 34 | while (InputRingBuffer != nullptr && !InputRingBuffer.Get()->IsEmpty()) 35 | { 36 | const TheCone::Packet_tpl* packedInput = InputRingBuffer.Get()->Peek(); 37 | auto indexInput = packedInput->GetCycleMeta() + 3; //faster than 3xabs or a branch. 38 | { 39 | //unlike the old design, we use an array of inputs from first -> current 40 | //so we want to add oldest first, then next, then next. 41 | //we'll need to amend this to handle correct defaulting of missing input, 42 | //which we can detect by both cycle skips and arrival window misses. 43 | //we then need a way, during rollbacks, to perform the rewrite. 44 | //right now, we just wait until we get the remote input. 45 | if (missedPrior) 46 | { 47 | if (burstDropDetected) 48 | { 49 | // 50 | BristleconeControlStream->Add( 51 | *((TheCone::Packet_tpl*)(packedInput))->GetPointerToElement((indexInput - 2) % 3), 52 | ((TheCone::Packet_tpl*)(packedInput))->GetTransferTime()); 53 | } 54 | BristleconeControlStream->Add( 55 | *((TheCone::Packet_tpl*)(packedInput))->GetPointerToElement((indexInput - 1) % 3), 56 | ((TheCone::Packet_tpl*)(packedInput))->GetTransferTime() 57 | ); 58 | } 59 | BristleconeControlStream->Add( 60 | *((TheCone::Packet_tpl*)(packedInput))->GetPointerToElement(indexInput % 3), 61 | ((TheCone::Packet_tpl*)(packedInput))->GetTransferTime()); 62 | 63 | RemoteInput = true; //we check for empty at the start of the while. no need to check again. 64 | InputRingBuffer.Get()->Dequeue(); 65 | } 66 | } 67 | 68 | if (RemoteInput == true) 69 | { 70 | missedPrior = false; 71 | burstDropDetected = false; 72 | } 73 | else 74 | { 75 | if (burstDropDetected) 76 | { 77 | //add rolling average switch-over here 78 | } 79 | if (missedPrior) 80 | { 81 | burstDropDetected = true; 82 | } 83 | missedPrior = true; 84 | } 85 | } 86 | else if (InputSwapSlot != nullptr && !InputSwapSlot.Get()->IsEmpty()) 87 | { 88 | //though it's probably more elegant and faster to index over the control streams 89 | while (InputSwapSlot != nullptr && !InputSwapSlot.Get()->IsEmpty()) 90 | { 91 | current = *InputSwapSlot.Get()->Peek(); 92 | CablingControlStream->Add(current); 93 | 94 | InputSwapSlot.Get()->Dequeue(); 95 | } 96 | } 97 | else 98 | { 99 | //---------------------------------- 100 | //if we got nothing, repeat prior. 101 | //0000000000000000000000000000000000 102 | 103 | CablingControlStream->Add(CablingControlStream->get(CablingControlStream->highestInput-1)->MyInputActions, TickliteNow); 104 | } 105 | #define ARTILLERY_FIRE_CONTROL_MACHINE_HANDLING (false) 106 | //First, locomotions are pushed. Patterns run here. The thread queues the locomotions and fires. 107 | //the dispatch fires guns via the machines on the gamethread. 108 | 109 | //Pattern matchers match, set events, and then those events are handed to the dispatch for now. 110 | //gradually, we'll be able to run more and more of them on this thread, freeing us from the tyranny. 111 | //do not refactor to auto. it will break. 112 | MovementBuffer& refDangerous_LifeCycleManaged_Loco_TripleBuffered 113 | = RequestorQueue_Locomos_TripleBuffer->GetWriteBuffer(); 114 | 115 | //Per input stream, run their patterns here. god in heaven. 116 | EventBuffer& refDangerous_LifeCycleManaged_Abilities_TripleBuffered 117 | = RequestorQueue_Abilities_TripleBuffer->GetWriteBuffer(); 118 | 119 | if(currentIndexCabling < CablingControlStream->highestInput) 120 | { 121 | //today's sin is PRIDE, bigbird! 122 | for (int i = currentIndexCabling; i < CablingControlStream->highestInput; ++i) 123 | { 124 | //TODO: does this leak memory? 125 | auto actor = CablingControlStream->GetActorByInputStream(); 126 | if (actor) 127 | { 128 | refDangerous_LifeCycleManaged_Loco_TripleBuffered.Add( 129 | LocomotionParams( 130 | CablingControlStream->peek(i)->SentAt, 131 | actor, 132 | *CablingControlStream->peek(i - 1), 133 | *CablingControlStream->peek(i) 134 | ) 135 | ); 136 | 137 | CablingControlStream->MyPatternMatcher->runOneFrameWithSideEffects( 138 | true, 139 | 0, 140 | 0, 141 | i, 142 | refDangerous_LifeCycleManaged_Abilities_TripleBuffered 143 | ); // this looks wrong but I'm pretty sure it ain' since we reserve highest. 144 | } 145 | //even if this doesn't get played for some reason, this is the last chance we've got to make a 146 | //truly informed decision about the matter. By the time we reach the dispatch system, that chance is gone. 147 | //Better to skip a cosmetic once in a while than crash the game. 148 | CablingControlStream->get(CablingControlStream->highestInput - 1)->RunAtLeastOnce = true; 149 | } 150 | } 151 | refDangerous_LifeCycleManaged_Loco_TripleBuffered.Sort(); 152 | refDangerous_LifeCycleManaged_Abilities_TripleBuffered.Sort(); 153 | if (RequestorQueue_Abilities_TripleBuffer->IsDirty() == false) 154 | { 155 | RequestorQueue_Abilities_TripleBuffer->SwapWriteBuffers(); 156 | } 157 | if (RequestorQueue_Locomos_TripleBuffer->IsDirty() == false) 158 | { 159 | RequestorQueue_Locomos_TripleBuffer->SwapWriteBuffers(); 160 | } 161 | } 162 | 163 | uint32 FArtilleryBusyWorker::Run() 164 | { 165 | UE_LOG(LogTemp, Display, TEXT("Artillery:BusyWorker: Running Artillery thread")); 166 | if (RequestorQueue_Abilities_TripleBuffer == nullptr) 167 | { 168 | #ifdef UE_BUILD_SHIPPING 169 | return -1; 170 | #else 171 | throw; // this is a BUG. A BAD ONE. 172 | #endif 173 | } 174 | bool missedPrior = false; 175 | uint64_t currentIndexCabling = 0; 176 | uint64_t currentIndexBristlecone = 0; 177 | bool burstDropDetected = false; 178 | bool sent = false; 179 | //TODO: remember why this needs to be an int. Overflow would take a match running for 1000 hours. 180 | //if you wanna use this for a really long lived session, you'll need to fix it. 181 | int seqNumber = 0; 182 | //Hi! Jake here! Reminding you that this will CYCLE 183 | //That's known. Isn't that fun? :) Don't reorder these, by the way. 184 | uint32_t LastIncrementWindow = ContingentInputECSLinkage->Now(); 185 | uint32_t lsbTime = ContingentInputECSLinkage->Now(); 186 | constexpr uint32_t sampleHertz = TheCone::CablingSampleHertz; 187 | constexpr uint32_t sendHertz = LongboySendHertz; 188 | constexpr uint32_t sendHertzFactor = sampleHertz / sendHertz; 189 | constexpr uint32_t Period = 1000000 / sampleHertz; //swap to microseconds. standardizing. 190 | 191 | constexpr auto Step = std::chrono::milliseconds(Period / 2000); 192 | 193 | //we can now start the sim. we latch only on the apply step. 194 | StartTicklitesSim->Trigger(); 195 | //we are started by Artillery Dispatch, but we can't use it in the .h file to avoid dependencies. 196 | //so we know it's live, but we don't take a ref to it until this point. 197 | //we only use it for GrantFeed, but it's important that we start abiding by separation of concerns 198 | //where we can, so we're trying to hide the barrage dependency here in a sense. We can't fully, but. 199 | auto ArtilleryDispatch = ContingentInputECSLinkage->GetWorld()->GetSubsystem(); 200 | ArtilleryDispatch->ThreadSetup(); 201 | timeBeginPeriod(2); 202 | while (running) 203 | { 204 | if (!sent && 205 | ( 206 | InputRingBuffer != nullptr && !InputRingBuffer.Get()->IsEmpty() 207 | || seqNumber % sendHertzFactor == 0 //last chance. Not good. 208 | ) 209 | ) 210 | { 211 | currentIndexCabling = CablingControlStream->highestInput; 212 | currentIndexBristlecone = BristleconeControlStream->highestInput; 213 | TheCone::PacketElement current = 0; 214 | bool RemoteInput = false; 215 | RunStandardFrameSim(missedPrior, currentIndexCabling, burstDropDetected, current, RemoteInput); 216 | /* 217 | * 218 | * Jolt will go here? No point in updating if we need to reconcile first. 219 | * Note: We also have Iris performing intermittent state stomps to recover from more serious desyncs. 220 | * Ultimately, rollback can never solve everything. The window's just get too wide. 221 | */ 222 | 223 | sent = true; 224 | TickliteNow = ContingentInputECSLinkage->Now(); // this updates ONCE PER CYCLE. ONCE. THIS IS INTENDED. 225 | 226 | ArtilleryDispatch->RunLocomotions(); 227 | //such a simple thing, after all this work. 228 | ContingentPhysicsLinkage->StackUp(); 229 | StartTicklitesApply->Trigger(); 230 | ContingentPhysicsLinkage->StepWorld(TickliteNow); 231 | } 232 | 233 | //unlike cabling, we do our time keeping HERE. It may be worth switching cabling to also follow this. 234 | //though if we end up using frameworks where the poll isn't free, we'll get dorked for doing it this way. 235 | //Increment window is still used to ensure we have at least two milliseconds to run, though. 236 | if ((LastIncrementWindow + Period) <= lsbTime) 237 | { 238 | LastIncrementWindow = lsbTime; 239 | if ((seqNumber % sendHertzFactor) == 0) 240 | { 241 | sent = false; 242 | } 243 | ++seqNumber; 244 | } 245 | std::this_thread::sleep_for(Step); 246 | lsbTime = ContingentInputECSLinkage->Now(); 247 | } 248 | timeEndPeriod(2); 249 | UE_LOG(LogTemp, Display, TEXT("Artillery:BusyWorker: Run Ended.")); 250 | return 0; 251 | } 252 | 253 | void FArtilleryBusyWorker::Exit() 254 | { 255 | UE_LOG(LogTemp, Display, TEXT("ARTILLERY OFFLINE.")); 256 | Cleanup(); 257 | } 258 | 259 | void FArtilleryBusyWorker::Stop() 260 | { 261 | UE_LOG(LogTemp, Display, TEXT("Artillery:BusyWorker: Stopping Artillery thread.")); 262 | Cleanup(); 263 | } 264 | 265 | 266 | void FArtilleryBusyWorker::Cleanup() 267 | { 268 | running = false; 269 | } 270 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/EssentialTypes/UArtilleryAbilityMinimum.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include 7 | 8 | #include "GameplayEffectTypes.h" 9 | 10 | #include "Abilities/GameplayAbility.h" 11 | #include "GameplayAbilitySpecHandle.h" 12 | 13 | #include "Abilities/GameplayAbilityTypes.h" 14 | #include "GameplayEffect.h" 15 | #include "FGunKey.h" 16 | 17 | #include "GameplayTaskOwnerInterface.h" 18 | #include "GameplayTask.h" 19 | #include "UArtilleryAbilityMinimum.generated.h" 20 | 21 | /** 22 | * The phases of an Artillery ability are 23 | * 24 | * [Try: fail, start] 25 | * Prefire 26 | * Prefire Cosmetic 27 | * 28 | * [Activate] 29 | * Fire 30 | * Fire Cosmetic 31 | * 32 | * [Outro] 33 | * PostFire 34 | * PostFire Cosmetic 35 | * 36 | * 37 | * Either Commit OR end may be called. EACH of these is an instance, per actor, of UArtilleryPerActorAbilityMinimum. 38 | * This is somewhat unusual but allows us vast flexibility in how we lay out abilities. 39 | * 40 | * You'll note there's also a gun key here. While these are instanced abilities, the creation and storage of non-attribute 41 | * state is strictly prohibited. Doing so may break abruptly, and without explanation, in a myriad of ways. It's not supported. 42 | * 43 | * This means that we'll need to be thoughtful about how we use gameplay tags, so we may need to relax this rule. 44 | * Rollbacks with gameplay tags and longterm maintenance are both pretty nightmarish, though, so I'd need a solid arg. 45 | * 46 | * Otoh, looman likes them, and that's often a good sign. Technically, we could create a delta-tracking tag container. 47 | * It's not the worst idea. 48 | * 49 | * Like in the UGameplayAbility class it derives from... 50 | * // ---------------------------------------------------------------------------------------------------------------- 51 | // 52 | // The important functions: 53 | // 54 | // CanActivateAbility() - const function to see if ability is activatable. Callable by UI etc 55 | // 56 | // TryActivateAbility() - Attempts to activate the ability. Calls CanActivateAbility(). Input events can call this directly. 57 | // - Also handles instancing-per-execution logic and replication/prediction calls. 58 | // 59 | // CallActivateAbility() - Protected, non virtual function. Does some boilerplate 'pre activate' stuff, then calls ActivateAbility() 60 | // 61 | // ActivateAbility() - What the abilities *does*. This is what child classes want to override. 62 | // 63 | // CommitAbility() - Commits reources/cooldowns etc. ActivateAbility() must call this! 64 | // 65 | // CancelAbility() - Interrupts the ability (from an outside source). 66 | // 67 | // EndAbility() - The ability has ended. This is intended to be called by the ability to end itself. 68 | // 69 | // ---------------------------------------------------------------------------------------------------------------- 70 | 71 | The K2 functions are wrappers that expose native behavior to the BP graph, and serve a few other functions. 72 | Check out https://medium.com/trunk-of-code/how-to-easily-change-default-events-to-fit-your-needs-38e87cec16f0 73 | */ 74 | 75 | enum FArtilleryStates 76 | { 77 | Fired, Canceled, CanceledAfterCommit 78 | }; 79 | 80 | 81 | 82 | DECLARE_DELEGATE_FourParams(FArtilleryAbilityStateAlert, FArtilleryStates, int, const FGameplayAbilityActorInfo*, const FGameplayAbilityActivationInfo); 83 | 84 | 85 | UCLASS(BlueprintType) 86 | class ARTILLERYRUNTIME_API UArtilleryPerActorAbilityMinimum : public UGameplayAbility 87 | { 88 | 89 | GENERATED_BODY() 90 | 91 | friend struct FArtilleryGun; 92 | 93 | public: 94 | //As you can see, they all call through to commit ability. 95 | 96 | FArtilleryAbilityStateAlert GunBinder; 97 | //ALMOST EVERYTHING THAT IS INTERESTING HAPPENS HERE RIGHT NOW. 98 | //ONLY ATTRIBUTES ARE REPLICATED. _AGAIN_. ONLY ATTRIBUTES ARE REPLICATED. 99 | UArtilleryPerActorAbilityMinimum(const FObjectInitializer& ObjectInitializer) 100 | : Super(ObjectInitializer), MyGunKey() 101 | { 102 | NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalOnly; 103 | ReplicationPolicy = EGameplayAbilityReplicationPolicy::ReplicateNo; 104 | InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor; 105 | }; 106 | 107 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Latency Hiding") 108 | int AvailableDallyFrames = 0; 109 | 110 | 111 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Gun") 112 | FGunKey MyGunKey; 113 | /* 114 | // Add the Ability's tags to the given GameplayEffectSpec. This is likely to be overridden per project. 115 | virtual void ApplyAbilityTagsToGameplayEffectSpec(FGameplayEffectSpec& Spec, FGameplayAbilitySpec* AbilitySpec) const; 116 | //this will likelybe where we set up how all artillery abilities behave regarding this. 117 | */ 118 | 119 | 120 | 121 | /*/* 122 | 123 | The following should likely be overridden differently for cosmetic and non-cosmetic ability base-classes. 124 | //Invoke a gameplay cue on the ability owner 125 | UFUNCTION(BlueprintCallable, Category = Ability, meta = (GameplayTagFilter = "GameplayCue"), DisplayName = "Execute GameplayCue On Owner", meta = (ScriptName = "ExecuteGameplayCue")) 126 | virtual void K2_ExecuteGameplayCue(FGameplayTag GameplayCueTag, FGameplayEffectContextHandle Context); 127 | 128 | //Invoke a gameplay cue on the ability owner, with extra parameters 129 | UFUNCTION(BlueprintCallable, Category = Ability, meta = (GameplayTagFilter = "GameplayCue"), DisplayName = "Execute GameplayCueWithParams On Owner", meta = (ScriptName = "ExecuteGameplayCueWithParams")) 130 | virtual void K2_ExecuteGameplayCueWithParams(FGameplayTag GameplayCueTag, const FGameplayCueParameters& GameplayCueParameters); 131 | 132 | //Adds a persistent gameplay cue to the ability owner. Optionally will remove if ability ends 133 | UFUNCTION(BlueprintCallable, Category = Ability, meta = (GameplayTagFilter = "GameplayCue"), DisplayName = "Add GameplayCue To Owner", meta = (ScriptName = "AddGameplayCue")) 134 | virtual void K2_AddGameplayCue(FGameplayTag GameplayCueTag, FGameplayEffectContextHandle Context, bool bRemoveOnAbilityEnd = true); 135 | 136 | // Adds a persistent gameplay cue to the ability owner. Optionally will remove if ability ends 137 | UFUNCTION(BlueprintCallable, Category = Ability, meta = (GameplayTagFilter = "GameplayCue"), DisplayName = "Add GameplayCueWithParams To Owner", meta = (ScriptName = "AddGameplayCueWithParams")) 138 | virtual void K2_AddGameplayCueWithParams(FGameplayTag GameplayCueTag, const FGameplayCueParameters& GameplayCueParameter, bool bRemoveOnAbilityEnd = true); 139 | 140 | // Removes a persistent gameplay cue from the ability owner 141 | UFUNCTION(BlueprintCallable, Category = Ability, meta = (GameplayTagFilter = "GameplayCue"), DisplayName = "Remove GameplayCue From Owner", meta = (ScriptName = "RemoveGameplayCue")) 142 | virtual void K2_RemoveGameplayCue(FGameplayTag GameplayCueTag); 143 | 144 | */ 145 | 146 | //here are a few others we'll likely NEED to override. 147 | ///** Returns the time in seconds remaining on the currently active cooldown. */ 148 | //UFUNCTION(BlueprintCallable, Category = Ability) 149 | //float GetCooldownTimeRemaining() const; 150 | //virtual float GetCooldownTimeRemaining(const FGameplayAbilityActorInfo* ActorInfo) const; 151 | //virtual const FGameplayTagContainer* GetCooldownTags() const; 152 | //virtual bool DoesAbilitySatisfyTagRequirements(const UAbilitySystemComponent& AbilitySystemComponent, const FGameplayTagContainer* SourceTags = nullptr, const FGameplayTagContainer* TargetTags = nullptr, OUT FGameplayTagContainer* OptionalRelevantTags = nullptr) const; 153 | //virtual bool IsBlockingOtherAbilities() const; 154 | 155 | /** 156 | * THIS IS SUPERSEDED BY ACTIVATEBYARTILLERY. 157 | */ 158 | virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override; 159 | 160 | //InstancingPolicy is ALWAYS EGameplayAbilityInstancingPolicy::NonInstanced for artillery abilities. 161 | //Storing state outside of tags and game simulation attributes will not be replicated and will cause bugs during rollback. 162 | //Only implementation graphs in THIS function are called by artillery. Anything else will be ignored. 163 | //You MUST call end ability and commit ability as appropriate, or execution will not continue. 164 | //Prefire should use commit\end vs. cancel to signal if execution should continue, but all abilities can. 165 | UFUNCTION(BlueprintNativeEvent, Category = Ability, DisplayName = "Artillery Ability Implementation", meta=(ScriptName = "ArtilleryActivation")) 166 | void K2_ActivateViaArtillery(const FGameplayAbilityActorInfo& ActorInfo, const FGameplayEventData& Event, const FGunKey& MyGun); 167 | 168 | //Default behavior, override to use C++! 169 | virtual void K2_ActivateViaArtillery_Implementation(const FGameplayAbilityActorInfo& ActorInfo,const FGameplayEventData& Event, const FGunKey& MyGun) 170 | { 171 | } 172 | 173 | /** Do boilerplate init stuff and then call ActivateAbility */ 174 | //You get a much better sense of how this flows looking at the 175 | virtual void PreActivate(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, FOnGameplayAbilityEnded::FDelegate* OnGameplayAbilityEndedDelegate, const FGameplayEventData* TriggerEventData = nullptr) override; 176 | 177 | //HEADS UP. END ABILITY FOR ARTILLERY DOES NOT REPLICATE ANYTHING. IT DOES NOT CLEAR TIMERS OR ASYNC BECAUSE 178 | //YOU ARE NOT SUPPOSED TO USE THEM IN ARTILLERY. IF YOU DO, THEY WILL FAIL ONCE ROLLBACK IS IMPLEMENTED. 179 | //MAY GOD HAVE MERCY ON OUR SOULS. 180 | virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override; 181 | 182 | virtual bool CommitAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, OUT FGameplayTagContainer* OptionalRelevantTags = nullptr) override; 183 | 184 | /** Generates a GameplayEffectContextHandle from our owner and an optional TargetData.*/ 185 | FGameplayEffectContextHandle GetContextFromOwner(FGameplayAbilityTargetDataHandle OptionalTargetData) const override; 186 | 187 | /** Returns an effect context, given a specified actor info */ 188 | FGameplayEffectContextHandle MakeEffectContext(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo) const override; 189 | 190 | virtual void CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility) override; 191 | 192 | private: 193 | 194 | 195 | //these have no function in the Artillery ability sequence. 196 | void InputPressed(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) override {}; 197 | void InputReleased(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) override {}; 198 | /** Called from AbilityTask_WaitConfirmCancel to handle input confirming */ 199 | void OnWaitingForConfirmInputBegin() override {}; 200 | void OnWaitingForConfirmInputEnd() override {}; 201 | 202 | /** Ability sequences, and artillery in general, make use of only cosmetic events and cues where possible. */ 203 | void SendGameplayEvent(FGameplayTag EventTag, FGameplayEventData Payload) override {}; 204 | 205 | //TODO: Make sure this is actually obsolete. 206 | FOnGameplayAbilityEnded OnGameplayAbilityEnded; 207 | 208 | /** Notification that the ability has ended with data on how it was ended */ 209 | FGameplayAbilityEndedDelegate OnGameplayAbilityEndedWithData; 210 | 211 | /** Notification that the ability is being cancelled. Called before OnGameplayAbilityEnded. */ 212 | FOnGameplayAbilityCancelled OnGameplayAbilityCancelled; 213 | }; 214 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/Systems/UFireControlMachine.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | #include "CoreTypes.h" 5 | #include "CoreMinimal.h" 6 | #include "AbilitySystemComponent.h" 7 | #include "AttributeSet.h" 8 | #include 9 | 10 | #include "GameplayEffectTypes.h" 11 | #include "GameplayEffect.h" 12 | 13 | #include "GameplayAbilitySpec.h" 14 | #include "GameplayAbilitySpecHandle.h" 15 | #include "FArtilleryGun.h" 16 | #include "Abilities/GameplayAbility.h" 17 | #include "CanonicalInputStreamECS.h" 18 | #include "ArtilleryDispatch.h" 19 | #include 20 | #include "ArtilleryCommonTypes.h" 21 | #include "FAttributeMap.h" 22 | #include "FMockArtilleryGun.h" 23 | #include "FMockBeamCannon.h" 24 | #include "FMockChairCannon.h" 25 | #include "FMockDashGun.h" 26 | #include "TransformDispatch.h" 27 | #include "Components/ActorComponent.h" 28 | #include "UFireControlMachine.generated.h" 29 | 30 | //dynamic constructed statemachine for matching patterns in action records to triggering abilities. 31 | //extends the Ability System Component to remove even more boiler plate and smoothly integrate 32 | //Artillery's threaded input model. 33 | 34 | //The fire control machine manages activation patterns. 35 | //it does not interact directly with input or run the patterns itself. 36 | // 37 | // Patterns are always run by the artillery worker thread. events generated by pattern success flow through Dispatch. 38 | // 39 | //Each Fire Control Machine manages attributes and abilities, with all abilities being ordered into ArtilleryGuns. 40 | //This additional constraint, combined with the pattern system, allows us to better build abilities that are general 41 | //but NOT abstract. Our goal is to reduce boilerplate and improve ease of use, not necessarily increase code reuse. 42 | //We HOPE that will happen, but bear this set of priorities in mind while dealing with Guns and FireControl. 43 | 44 | //As a final word of warning, it has been our extensive experience that generic is better than general, and concrete 45 | //is better than abstract. The urge towards pattern-before-problem programming is deadly. 46 | UCLASS() 47 | class ARTILLERYRUNTIME_API UFireControlMachine : public UAbilitySystemComponent 48 | { 49 | GENERATED_BODY() 50 | 51 | public: 52 | static inline int orderInInitialize = 0; 53 | UCanonicalInputStreamECS* MyInput; 54 | UArtilleryDispatch* MyDispatch; 55 | UTransformDispatch* TransformDispatch; 56 | ActorKey ParentKey; 57 | //this needs to be replicated in iris, interestin'ly. 58 | TSet MyGuns; 59 | 60 | //The direct presence of attributes in this way is likely obsoleted by switching to inheriting rather than friending 61 | //the UAS component, but I'm not totally sure. 62 | TSharedPtr MyAttributes; 63 | 64 | FireControlKey MyKey; 65 | bool Usable = false; 66 | 67 | /** Who to route replication through if ReplicationProxyEnabled (if this returns null, when ReplicationProxyEnabled, we wont replicate) */ 68 | //this is the tooling used in part by NPP's implementation, and we should consider using it as well to integrate with Iris and constrain 69 | //replication to attributes only. 70 | //virtual IAbilitySystemReplicationProxyInterface* GetReplicationInterface(); 71 | 72 | 73 | // These properties, and the fact that they are NOT the same, is important for understanding the model of the original 74 | // ability system component. Note that this means by default, this component has no structural model for multi actor 75 | // groups that might share abilities, such as enemy squads. While this sounds like a corner case, it is not. 76 | // /** The actor that owns this component logically */ 77 | // UPROPERTY(ReplicatedUsing = OnRep_OwningActor) 78 | // TObjectPtr OwnerActor; 79 | // 80 | // /** The actor that is the physical representation used for abilities. Can be NULL */ 81 | // UPROPERTY(ReplicatedUsing = OnRep_OwningActor) 82 | // TObjectPtr AvatarActor; 83 | 84 | /*/** Will be called from GiveAbility or from OnRep. Initializes events (triggers and inputs) with the given ability #1# 85 | virtual void OnGiveAbility(FGameplayAbilitySpec& AbilitySpec); 86 | 87 | /** Will be called from RemoveAbility or from OnRep. Unbinds inputs with the given ability #1# 88 | virtual void OnRemoveAbility(FGameplayAbilitySpec& AbilitySpec); 89 | 90 | /** Called from ClearAbility, ClearAllAbilities or OnRep. Clears any triggers that should no longer exist. #1# 91 | void CheckForClearedAbilities(); 92 | 93 | /** Cancel a specific ability spec #1# 94 | virtual void CancelAbilitySpec(FGameplayAbilitySpec& Spec, UGameplayAbility* Ignore); 95 | 96 | /** Creates a new instance of an ability, storing it in the spec #1# 97 | virtual UGameplayAbility* CreateNewInstanceOfAbility(FGameplayAbilitySpec& Spec, const UGameplayAbility* Ability);*/ 98 | 99 | 100 | /**Found this in the AbilitySystemComponent.h: 101 | * 102 | * The abilities we can activate. 103 | * -This will include CDOs for non instanced abilities and per-execution instanced abilities. 104 | * -Actor-instanced abilities will be the actual instance (not CDO) 105 | * 106 | * This array is not vital for things to work. It is a convenience thing for 'giving abilities to the actor'. But abilities could also work on things 107 | * without an AbilitySystemComponent. For example an ability could be written to execute on a StaticMeshActor. As long as the ability doesn't require 108 | * instancing or anything else that the AbilitySystemComponent would provide, then it doesn't need the component to function. 109 | */ 110 | 111 | //it's really not clear how vital granting and removing abilities is. getting my arms around this system is still a lot. 112 | 113 | //******************************************************************************************* 114 | //patterns are run in ArtilleryBusyWorker. Search for ARTILLERY_FIRE_CONTROL_MACHINE_HANDLING 115 | //******************************************************************************************* 116 | 117 | //IF YOU DO NOT CALL THIS FROM THE GAMETHREAD, YOU WILL HAVE A BAD TIME. 118 | void pushPatternToRunner(IPM::CanonPattern ToBind, PlayerKey InputStreamByPlayer, FActionBitMask ToSeek, FGunKey ToFire) 119 | { 120 | FActionPatternParams myParams = FActionPatternParams(ToSeek, MyKey, InputStreamByPlayer, ToFire); 121 | MyInput->registerPattern(ToBind, myParams); 122 | Arty::FArtilleryFireGunFromDispatch Inbound; 123 | Inbound.BindUObject(this, &UFireControlMachine::FireGun); 124 | MyDispatch->RegisterReady(ToFire, Inbound); 125 | MyGuns.Add(ToFire); 126 | }; 127 | 128 | //IF YOU DO NOT CALL THIS FROM THE GAMETHREAD, YOU WILL HAVE A BAD TIME. 129 | void popPatternFromRunner(FActionPattern* ToBind, PlayerKey InputStreamByPlayer, FActionBitMask ToSeek, FGunKey ToFire) 130 | { 131 | FActionPatternParams myParams = FActionPatternParams(ToSeek, MyKey, InputStreamByPlayer, ToFire); 132 | MyInput->removePattern(ToBind, myParams); 133 | MyDispatch->Deregister(ToFire); 134 | MyGuns.Remove(ToFire); 135 | 136 | 137 | }; 138 | 139 | 140 | void AddTestGun(Intents::Intent BindIntent, FArtilleryGun* Gun, IPM::CanonPattern Pattern) 141 | { 142 | FActionBitMask IntentBitPattern; 143 | IntentBitPattern.buttons = BindIntent; 144 | Gun->UpdateProbableOwner(ParentKey); 145 | Gun->Initialize(Gun->MyGunKey, false); 146 | auto key = MyDispatch->RegisterExistingGun(Gun, ParentKey); 147 | pushPatternToRunner(Pattern, APlayer::CABLE, IntentBitPattern, key); 148 | } 149 | 150 | //IF YOU DO NOT CALL THIS FROM THE GAMETHREAD, YOU WILL HAVE A BAD TIME. 151 | ActorKey CompleteRegistrationByActorParent(bool IsLocalPlayerCharacter, 152 | FArtilleryRunLocomotionFromDispatch LocomotionFromActor, 153 | TMap Attributes) 154 | { 155 | //these are initialized earlier under all intended orderings, but we cannot ensure that this function will be called correctly 156 | //so we should do what we can to foolproof things. As long as the world subsystems are up, force-updating 157 | //here will either: 158 | //work correctly 159 | //fail fast 160 | MyInput = GetWorld()->GetSubsystem(); 161 | MyDispatch = GetWorld()->GetSubsystem(); 162 | TransformDispatch = GetWorld()->GetSubsystem(); 163 | TPair Parent = MyInput->RegisterKeysToParentActorMapping(GetOwner(), MyKey, true); 164 | ParentKey = Parent.Key; 165 | MyDispatch->RegisterLocomotion(ParentKey, LocomotionFromActor); 166 | Usable = true; 167 | if(MyGuns.IsEmpty() && Usable) 168 | { 169 | auto dummy = new FMockArtilleryGun(FGunKey("Dummy", 1)); 170 | AddTestGun(Intents::A, dummy, IPM::GPress); 171 | auto dash = new FMockDashGun(FGunKey("DummyDash", 2)); 172 | AddTestGun(Intents::B, dash, IPM::GPerPress); 173 | auto beam = new FMockBeamCannon(FGunKey("DummyBeam", 3), 200, 4, 150, 5000.0f); 174 | beam->UpdateProbableOwner(ParentKey); 175 | AddTestGun(Intents::RTrigger, beam, IPM::GPress); 176 | auto chairs = new FMockChairCannon(FGunKey("ChairCannon", 4), 10, 20, 150); 177 | AddTestGun(Intents::LTrigger, chairs, IPM::GPerPress); 178 | } 179 | MyAttributes = MakeShareable(new FAttributeMap(ParentKey, MyDispatch, Attributes)); 180 | 181 | UE_LOG(LogTemp, Warning, TEXT("FCM Mana: %f"), MyDispatch->GetAttrib(ParentKey, Attr::Mana)->GetCurrentValue()); 182 | 183 | return ParentKey; 184 | 185 | //right now, we can push all our patterns here as well, and we can use a static set of patterns for 186 | //each of our fire control machines. you can basically think of a fire control machine as a full set 187 | //of related abilities, their attributes, and similar required to, you know, actually fire a gun. 188 | //it is also the gas component, if you're using gas. 189 | //There's a bit more blueprint exposure work to do here as a result. 190 | #ifdef CONTROL_TEST_MODE 191 | 192 | 193 | #endif 194 | 195 | }; 196 | 197 | //it is strongly recommended that you understand 198 | // FGameplayAbilitySpec and FGameplayAbilitySpecDef, as well as Handle. 199 | // I'm deferring the solve for how we use them for now, in a desperate effort to 200 | // make sure we can preserve as much of the ability framework as possible 201 | // but spec management is going to be mission critical for determinism 202 | void FireGun(TSharedPtr Gun, bool InputAlreadyUsedOnce) 203 | { 204 | //frig. 205 | FGameplayAbilitySpec BackboneFiring = BuildAbilitySpecFromClass( 206 | (Gun->Prefire).GetClass(), 207 | 0, 208 | -1 209 | ); 210 | FGameplayAbilitySpecHandle FireHandle = BackboneFiring.Handle; 211 | Gun->PreFireGun( 212 | FireHandle, 213 | AbilityActorInfo.Get(), 214 | FGameplayAbilityActivationInfo(EGameplayAbilityActivationMode::Authority)); 215 | }; 216 | 217 | void InitializeComponent() override 218 | { 219 | Super::InitializeComponent(); 220 | MyKey = UFireControlMachine::orderInInitialize++; 221 | //we rely on attribute replication, which I think is borderline necessary, but I wonder if we should use effect replication. 222 | //historically, relying on gameplay effect replication has led to situations where key state was not managed through effects. 223 | //for OUR situation, where we have few attributes and many effects, huge amounts of effects are likely not interesting for us to replicate. 224 | ReplicationMode = EGameplayEffectReplicationMode::Minimal; 225 | }; 226 | //this happens post init but pre begin play, and the world subsystems should exist by this point. 227 | //we use this to help ensure that if the actor's begin play triggers first, things will be set correctly 228 | //I've left the same code in begin play as a fallback. 229 | void ReadyForReplication() override 230 | { 231 | Super::ReadyForReplication(); 232 | MyInput = GetWorld()->GetSubsystem(); 233 | MyDispatch = GetWorld()->GetSubsystem(); 234 | } 235 | 236 | //on components, begin play can fire twice, because we aren't allowed to have nice things. 237 | //This can cause it to fire BEFORE the actor's begin play fires, which leaves you with 238 | //very few good options. the bool Usable helps control this. 239 | //This is, ironically, not a problem in actual usage, only testing, for us. 240 | void BeginPlay() override 241 | { 242 | Super::BeginPlay(); 243 | MyInput = GetWorld()->GetSubsystem(); 244 | MyDispatch = GetWorld()->GetSubsystem(); 245 | }; 246 | 247 | virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override 248 | { 249 | Super::OnComponentDestroyed(bDestroyingHierarchy); 250 | for (FGunKey Gun : MyGuns) 251 | { 252 | MyDispatch->Deregister(Gun); // emergency deregister. 253 | } 254 | }; 255 | }; 256 | -------------------------------------------------------------------------------- /Source/ArtilleryRuntime/Public/Systems/ArtilleryDispatch.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Subsystems/WorldSubsystem.h" 7 | #include "UBristleconeWorldSubsystem.h" 8 | #include "UCablingWorldSubsystem.h" 9 | #include "ArtilleryCommonTypes.h" 10 | #include "Containers/TripleBuffer.h" 11 | #include "FArtilleryBusyWorker.h" 12 | #include "LocomotionParams.h" 13 | #include "ConservedAttribute.h" 14 | #include "FArtilleryTicklitesThread.h" 15 | #include "KeyCarry.h" 16 | #include "TransformDispatch.h" 17 | #include "PhysicsTypes/BarragePlayerAgent.h" 18 | #include "ArtilleryDispatch.generated.h" 19 | 20 | 21 | /** 22 | * This is the backbone of artillery, and along with the ArtilleryGuns and UFireControlMachines, 23 | * this is most of what any other code will see of artillery in C++. Blueprints will mostly not even see that 24 | * as they are already encapsulated in the artillerygun's ability sequencer. Abilities merely need to be uninstanced, 25 | * only modify attributes tracked in Artillery, and use artillery bp nodes where possible. 26 | * 27 | * No unusual additional serialization or similar is required for determinism. I hope. 28 | * 29 | * For an organized view of the code, start by understanding that most work is done outside the gamethread. 30 | * Bristlecone handles network inputs, Cabling handles keymapping and local inputs. Artillery handles outcomes. 31 | * 32 | * Here's the core rule: 33 | * 34 | * Core gameplay simulation happens in the ArtilleryBusyWorker at about 120hz. 35 | * Anything that cannot cause the core gameplay sim to become desyncrhonized happens in the game thread. 36 | * Surprisingly, this means _most things flow through UE normally and happen on the game thread & render threads._ 37 | * 38 | * Our goal is that cosmetic ability components, animation, IK, particle systems, cloth, and really, almost everything 39 | * happens in UE normally. Rollbacks don't happen in the broader UE system at all, and in fact, most of UE acts like its 40 | * running in a pretty simple Iris push config. 41 | * 42 | * Artillery just pushes transform and ability updates upward as needed. That's a big just, but it really is a narrow scope. 43 | * 44 | * Iris does normal replication on a slow cadence as a fall back and to provide attribute sync reassurances. 45 | */ 46 | struct FArtilleryGun; 47 | namespace Arty 48 | { 49 | DECLARE_MULTICAST_DELEGATE(OnArtilleryActivated); 50 | 51 | DECLARE_DELEGATE_TwoParams(FArtilleryFireGunFromDispatch, 52 | TSharedPtr Gun, 53 | bool InputAlreadyUsedOnce); 54 | 55 | //returns true if-and-only-if the duration of the input intent was exhausted. 56 | DECLARE_DELEGATE_RetVal_FourParams(bool, 57 | FArtilleryRunLocomotionFromDispatch, 58 | FArtilleryShell PreviousMovement, 59 | FArtilleryShell Movement, 60 | bool RunAtLeastOnce, 61 | bool Smear); 62 | 63 | typedef FArtilleryTicklitesWorker TickliteWorker; 64 | } 65 | 66 | class UCanonicalInputStreamECS; 67 | UCLASS() 68 | class ARTILLERYRUNTIME_API UArtilleryDispatch : public UTickableWorldSubsystem 69 | { 70 | GENERATED_BODY() 71 | 72 | friend class FArtilleryBusyWorker; 73 | friend class FArtilleryTicklitesWorker; 74 | friend class UCanonicalInputStreamECS; 75 | friend class UArtilleryLibrary; 76 | protected: 77 | static inline UArtilleryDispatch* SelfPtr = nullptr; 78 | 79 | public: 80 | 81 | OnArtilleryActivated BindToArtilleryActivated; 82 | 83 | inline ArtilleryTime GetShadowNow() 84 | const 85 | { 86 | return ArtilleryAsyncWorldSim.TickliteNow; 87 | }; 88 | void REGISTER_ENTITY_FINAL_TICK_RESOLVER(ActorKey Self); 89 | void REGISTER_PROJECTILE_FINAL_TICK_RESOLVER(uint32 MaximumLifespanInTicks, FSkeletonKey Self); 90 | void REGISTER_GUN_FINAL_TICK_RESOLVER(FGunKey Self); 91 | void INITIATE_JUMP_TIMER(FSkeletonKey Self); 92 | 93 | //Forwarding for the TickliteThread. 94 | TOptional GetTransformShadowByObjectKey(FSkeletonKey Target, ArtilleryTime Now) 95 | { 96 | if(GetWorld()) 97 | { 98 | auto TransformECSPillar = GetWorld()->GetSubsystem(); 99 | if(TransformECSPillar) 100 | { 101 | return TransformECSPillar->CopyOfTransformByObjectKey(Target); 102 | } 103 | } 104 | return TOptional(); 105 | } 106 | 107 | FBLet GetFBLetByObjectKey(FSkeletonKey Target, ArtilleryTime Now) 108 | { 109 | if(GetWorld()) 110 | { 111 | auto PhysicsECSPillar = GetWorld()->GetSubsystem(); 112 | if(PhysicsECSPillar) 113 | { 114 | return PhysicsECSPillar->GetShapeRef(Target); 115 | } 116 | } 117 | return FBLet(); 118 | } 119 | 120 | //forwarding for TickliteThread 121 | //TEMPORARILY REVIVED! HAHAHAHAHA! Speed over elegaerngz 122 | bool ApplyShadowTransforms() 123 | { 124 | if(GetWorld()) 125 | { 126 | auto TransformECSPillar = GetWorld()->GetSubsystem(); 127 | if(TransformECSPillar) 128 | { 129 | return TransformECSPillar->ApplyTransformUpdates>(TransformUpdateQueue); 130 | } 131 | } 132 | return false; 133 | } 134 | 135 | //Executes necessary preconfiguration for threads owned by this dispatch. Likely going to be factored into the 136 | //dispatch API so that we can use stronger type guarantees throughout our codebase. 137 | //Called FROM the thread being set up. 138 | void ThreadSetup() 139 | { 140 | auto PhysicsECSPillar = GetWorld()->GetSubsystem(); 141 | if(PhysicsECSPillar) 142 | { 143 | PhysicsECSPillar->GrantFeed(); 144 | } 145 | } 146 | 147 | protected: 148 | virtual void Initialize(FSubsystemCollectionBase& Collection) override; 149 | virtual void OnWorldBeginPlay(UWorld& InWorld) override; 150 | virtual void Deinitialize() override; 151 | TSharedPtr< JOLT::FWorldSimOwner> HoldOpen; 152 | 153 | 154 | //this is the underlying function mapping we use to queue up Gun Activations. 155 | //These will eventually need a complete and consistent ordering to ensure determinism. 156 | //copy op is intentional but may be unneeded. assess before revising signature. 157 | //TODO: assess if this needs to be a multimap. I think it needs to NOT be. 158 | TSharedPtr< TMap> GunToFiringFunctionMapping; 159 | TSharedPtr RequestorQueue_Abilities_TripleBuffer; 160 | 161 | //This is more straightforward than the guns problem. 162 | //We can actually map this quite directly. 163 | TSharedPtr< TMap> ActorToLocomotionMapping; 164 | 165 | // NOTTODO: It's built! 166 | TSharedPtr> AttributeSetToDataMapping; 167 | 168 | TSharedPtr> IdentSetToDataMapping; 169 | TSharedPtr TransformUpdateQueue; 170 | public: 171 | virtual void PostInitialize() override; 172 | 173 | protected: 174 | 175 | 176 | //todo: convert conserved attribute to use a timestamp for versioning to create a true temporal shadowstack. 177 | AttrMapPtr GetAttribSetShadowByObjectKey( 178 | FSkeletonKey Target, ArtilleryTime Now) const; 179 | 180 | IdMapPtr GetIdSetShadowByObjectKey( 181 | FSkeletonKey Target, ArtilleryTime Now) const; 182 | TSharedPtr RequestorQueue_Locomos_TripleBuffer; 183 | 184 | static inline long long TotalFirings = 0; //2024 was rough. 185 | virtual void Tick(float DeltaTime) override; 186 | virtual TStatId GetStatId() const override; 187 | FGunKey GetGun(FString GunDefinitionID, ActorKey ProbableOwner); 188 | //fully specifying the type is necessary to prevent spurious warnings in some cases. 189 | TSharedPtr>> ActionsToOrder; 190 | //These two are the backbone of the Artillery gun lifecycle. 191 | TSharedPtr< TMap>> GunByKey; 192 | TMultiMap> PooledGuns; 193 | 194 | 195 | /** 196 | * Wil hold the configuration for the gun definitions 197 | */ 198 | TObjectPtr GunDefinitionsManifest; 199 | //TODO: This is a dummy function and should be replaced NLT 10/20/24. 200 | //It loads from the PROJECT directory. This cannot ship, but will work for all purposes at the moment. 201 | //Note: https://www.reddit.com/r/unrealengine/comments/160mjkx/how_reliable_and_scalable_are_the_data_tables/ 202 | void LoadGunData(); 203 | 204 | TSharedPtr>> ActionsToReconcile; 205 | 206 | void QueueFire(FGunKey Key, ArtilleryTime Time); 207 | 208 | void QueueResim(FGunKey Key, ArtilleryTime Time); 209 | 210 | //the separation of tick and frame is inspired by the Serious Engine and others. 211 | //In fact, it's pretty common to this day, with Unity also using a similar model. 212 | //However, our particular design is running fast relative to most games except quake. 213 | void RunGuns(); 214 | void RunLocomotions(); 215 | void RunGunFireTimers(); 216 | void CheckFutures(); 217 | //The current start of the tick boundary that ticklites should run on. this allows the ticklites 218 | //to run in frozen time. 219 | //******************************** 220 | //DUMMY. USED BY RECONCILE AND RERUN. 221 | //Here to demonstrate that we actually queue normal and rerun separately. 222 | //We can't risk intermingling them, which should never happen, but... 223 | //c'mon. Seriously. you wanna find that bug? 224 | //******************************** 225 | void RERunGuns(); 226 | void RERunLocomotions(); 227 | 228 | public: 229 | typedef FArtilleryTicklitesWorker FTicklitesWorker; 230 | struct TL_ThreadedImpl 231 | { 232 | //Each class generated gets a unique static. Each kind of dispatcher will get a unique class. 233 | //TODO: If you run more than one of the parent threads, this gets unsafe. We don't so... 234 | //As is, it saves a huge amount of memory and indirection costs. 235 | static inline FTicklitesWorker* ADispatch = nullptr; 236 | 237 | TL_ThreadedImpl() 238 | { 239 | if(ADispatch == nullptr) 240 | { 241 | throw; // dawg, you tryin' allocate shit against a thread that ain' there. 242 | } 243 | } 244 | 245 | ArtilleryTime GetShadowNow() 246 | { 247 | return ADispatch->GetShadowNow(); 248 | } 249 | }; 250 | 251 | //DUMMY FOR NOW. 252 | //TODO: IMPLEMENT THE GUNMAP FROM INSTANCE UNTO CLASS 253 | //TODO: REMEMBER TO SAY AMMO A BUNCH 254 | void RequestAddTicklite(TSharedPtr ToAdd, TicklitePhase Group) 255 | { 256 | ArtilleryTicklitesWorker_LockstepToWorldSim.RequestAddTicklite(ToAdd, Group); 257 | } 258 | FGunKey GetGun(FString GunDefinitionID, FireControlKey MachineKey); 259 | FGunKey RegisterExistingGun(FArtilleryGun* toBind, ActorKey ProbableOwner) const; 260 | bool ReleaseGun(FGunKey Key, FireControlKey MachineKey); 261 | 262 | //TODO: convert to object key to allow the grand dance of the mesh primitives. 263 | AttrPtr GetAttrib(FSkeletonKey Owner, E_AttribKey Attrib); 264 | IdentPtr GetIdent(FSkeletonKey Owner, Ident Attrib); 265 | 266 | void RegisterReady(FGunKey Key, FArtilleryFireGunFromDispatch Machine) 267 | { 268 | GunToFiringFunctionMapping->Add(Key, Machine); 269 | } 270 | void RegisterLocomotion(ActorKey Key, FArtilleryRunLocomotionFromDispatch Machine) 271 | { 272 | ActorToLocomotionMapping->Add(Key, Machine); 273 | } 274 | void Deregister(FGunKey Key) 275 | { 276 | auto holdopen = GunToFiringFunctionMapping; 277 | if(holdopen && holdopen.IsValid()) 278 | { 279 | GunToFiringFunctionMapping->Remove(Key); 280 | } 281 | //TODO: add the rest of the wipe here? 282 | } 283 | void RegisterAttributes(FSkeletonKey in, AttrMapPtr Attributes) 284 | { 285 | AttributeSetToDataMapping->Add(in, Attributes); 286 | } 287 | void RegisterRelationships(FSkeletonKey in, IdMapPtr Relationships) 288 | { 289 | IdentSetToDataMapping->Add(in, Relationships); 290 | } 291 | void DeregisterAttributes(FSkeletonKey in) 292 | { 293 | AttributeSetToDataMapping->Remove(in); 294 | } 295 | void DeregisterRelationships(FSkeletonKey in) 296 | { 297 | IdentSetToDataMapping->Remove(in); 298 | } 299 | 300 | std::atomic_bool UseNetworkInput; 301 | bool missedPrior = false; 302 | bool burstDropDetected = false; 303 | 304 | private: 305 | static inline long long monotonkey = 0; 306 | //If you're trying to figure out how artillery works, read the busy worker knowing it's a single thread coming off of Dispatch. 307 | //this handles input from bristlecone, patching it into streams from the CanonicalInputStreamECS (ACIS), using the ACIS to perform mappings, 308 | //and processing those streams using the pattern matcher. right now, we also expect it to own rollback and jolt when that's implemented. 309 | // 310 | // 311 | // This is quite a lot and for performance or legibility reasons there is a good chance that this thread will 312 | //need to be split up. On the other hand, we want each loop iteration to run for around 2-4 milliseconds. 313 | //this is quite a bit. 314 | FArtilleryBusyWorker ArtilleryAsyncWorldSim; 315 | //This runs the tickables alongside the worldsim, ticking each time the worldsim ticks 316 | //but NOT using locks. So the processing is totally independent. This is extremely fast, 317 | //extremely powerful, and the reason why we don't use ticklites when we don't need to. 318 | //it's dangerous as __________ _____________________ _ _________. 319 | 320 | TickliteWorker ArtilleryTicklitesWorker_LockstepToWorldSim; 321 | TUniquePtr WorldSim_Thread; 322 | TUniquePtr WorldSim_Ticklites_Thread; 323 | FSharedEventRef StartTicklitesSim; 324 | FSharedEventRef StartTicklitesApply; 325 | }; 326 | 327 | --------------------------------------------------------------------------------