├── Resources └── Icon128.png ├── README.md ├── LICENSE ├── Source ├── Squirrel │ ├── Private │ │ ├── SquirrelModule.cpp │ │ ├── Squirrel.cpp │ │ └── SquirrelNoise5.hpp │ ├── Squirrel.Build.cs │ └── Public │ │ └── Squirrel.h └── SquirrelEditor │ ├── SquirrelEditor.Build.cs │ └── Private │ ├── Customizations │ ├── SquirrelStateCustomization.h │ └── SquirrelStateCustomization.cpp │ └── SquirrelEditorModule.cpp ├── Squirrel.uplugin └── .gitignore /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Drakynfly/SquirrelUE/HEAD/Resources/Icon128.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SquirrelUE 2 | 3 | A simple plugin that integrates SquirrelNoise5 into an Unreal type. 4 | 5 | The file `SquirrelNoise5.hpp` is a modified version of this: 6 | https://github.com/EDKarlsson/go-squirrelnoise/blob/main/docs/SquirrelNoise5.hpp 7 | 8 | Discord: [![Discord](https://img.shields.io/discord/996247217314738286.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/AAk9yNwKk8) (Drakynfly's Plugins) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /Source/Squirrel/Private/SquirrelModule.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Guy (Drakynfly) Lundvall. All Rights Reserved. 2 | 3 | #include "Modules/ModuleManager.h" 4 | 5 | #define LOCTEXT_NAMESPACE "SquirrelModule" 6 | 7 | class FSquirrelModule final : public IModuleInterface 8 | { 9 | public: 10 | virtual void StartupModule() override; 11 | virtual void ShutdownModule() override; 12 | }; 13 | 14 | void FSquirrelModule::StartupModule() 15 | { 16 | } 17 | 18 | void FSquirrelModule::ShutdownModule() 19 | { 20 | } 21 | 22 | #undef LOCTEXT_NAMESPACE 23 | 24 | IMPLEMENT_MODULE(FSquirrelModule, Squirrel) -------------------------------------------------------------------------------- /Source/Squirrel/Squirrel.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Guy (Drakynfly) Lundvall. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class Squirrel : ModuleRules 6 | { 7 | public Squirrel(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicDependencyModuleNames.AddRange( 12 | new [] 13 | { 14 | "Core" 15 | } 16 | ); 17 | 18 | PrivateDependencyModuleNames.AddRange( 19 | new [] 20 | { 21 | "CoreUObject", 22 | "Engine" 23 | } 24 | ); 25 | } 26 | } -------------------------------------------------------------------------------- /Source/SquirrelEditor/SquirrelEditor.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Guy (Drakynfly) Lundvall. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class SquirrelEditor : ModuleRules 6 | { 7 | public SquirrelEditor(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicDependencyModuleNames.AddRange( 12 | new [] 13 | { 14 | "Core", 15 | "Squirrel" 16 | } 17 | ); 18 | 19 | PrivateDependencyModuleNames.AddRange( 20 | new [] 21 | { 22 | "CoreUObject", 23 | "Engine", 24 | "Slate", 25 | "SlateCore", 26 | } 27 | ); 28 | } 29 | } -------------------------------------------------------------------------------- /Squirrel.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "Squirrel", 6 | "Description": "A simple plugin that integrates SquirrelNoise5 into an Unreal type", 7 | "Category": "RNG", 8 | "CreatedBy": "Guy (Drakynfly) Lundvall", 9 | "CreatedByURL": "https://github.com/Drakynfly", 10 | "SupportURL": "https://discord.gg/AAk9yNwKk8", 11 | "DocsURL": "", 12 | "MarketplaceURL": "", 13 | "CanContainContent": false, 14 | "IsBetaVersion": false, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "Squirrel", 20 | "Type": "Runtime", 21 | "LoadingPhase": "Default" 22 | }, 23 | { 24 | "Name": "SquirrelEditor", 25 | "Type": "Editor", 26 | "LoadingPhase": "Default" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /Source/SquirrelEditor/Private/Customizations/SquirrelStateCustomization.h: -------------------------------------------------------------------------------- 1 | // Copyright Guy (Drakynfly) Lundvall. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "IPropertyTypeCustomization.h" 6 | 7 | class FSquirrelStateCustomization : public IPropertyTypeCustomization 8 | { 9 | public: 10 | static TSharedRef MakeInstance(); 11 | 12 | virtual void CustomizeHeader(TSharedRef PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) override; 13 | virtual void CustomizeChildren(TSharedRef PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils) override {} 14 | 15 | private: 16 | FReply OnRandomizeClicked(); 17 | 18 | TSharedPtr Position; 19 | }; -------------------------------------------------------------------------------- /Source/SquirrelEditor/Private/SquirrelEditorModule.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Guy (Drakynfly) Lundvall. All Rights Reserved. 2 | 3 | #include "PropertyEditorModule.h" 4 | #include "Modules/ModuleManager.h" 5 | #include "Squirrel.h" 6 | #include "Customizations/SquirrelStateCustomization.h" 7 | 8 | #define LOCTEXT_NAMESPACE "SquirrelEditorModule" 9 | 10 | class FSquirrelEditorModule : public IModuleInterface 11 | { 12 | public: 13 | virtual void StartupModule() override; 14 | virtual void ShutdownModule() override; 15 | }; 16 | 17 | void FSquirrelEditorModule::StartupModule() 18 | { 19 | FPropertyEditorModule& Module = FModuleManager::Get().LoadModuleChecked("PropertyEditor"); 20 | 21 | Module.RegisterCustomPropertyTypeLayout(FSquirrelState::StaticStruct()->GetFName(), 22 | FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FSquirrelStateCustomization::MakeInstance)); 23 | } 24 | 25 | void FSquirrelEditorModule::ShutdownModule() 26 | { 27 | if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) 28 | { 29 | FPropertyEditorModule& Module = FModuleManager::Get().GetModuleChecked("PropertyEditor"); 30 | Module.UnregisterCustomPropertyTypeLayout(FSquirrelState::StaticStruct()->GetFName()); 31 | } 32 | } 33 | 34 | #undef LOCTEXT_NAMESPACE 35 | 36 | IMPLEMENT_MODULE(FSquirrelEditorModule, SquirrelEditor) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Rider C++ 5 | .idea/ 6 | Plugins/Developer/RiderLink/ 7 | 8 | # Visual Studio 2015 database file 9 | *.VC.db 10 | 11 | # Compiled Object files 12 | *.slo 13 | *.lo 14 | *.o 15 | *.obj 16 | 17 | # Precompiled Headers 18 | *.gch 19 | *.pch 20 | 21 | # Compiled Dynamic libraries 22 | *.so 23 | *.dylib 24 | *.dll 25 | 26 | # Fortran module files 27 | *.mod 28 | 29 | # Compiled Static libraries 30 | *.lai 31 | *.la 32 | *.a 33 | *.lib 34 | 35 | # Executables 36 | *.exe 37 | *.out 38 | *.app 39 | *.ipa 40 | 41 | # These project files can be generated by the engine 42 | *.xcodeproj 43 | *.xcworkspace 44 | *.sln 45 | *.suo 46 | *.opensdf 47 | *.sdf 48 | *.VC.db 49 | *.VC.opendb 50 | 51 | # Precompiled Assets 52 | SourceArt/**/*.png 53 | SourceArt/**/*.tga 54 | 55 | # Binary Files 56 | Binaries/* 57 | Plugins/*/Binaries/* 58 | 59 | # Builds 60 | Build/* 61 | 62 | # Whitelist PakBlacklist-.txt files 63 | !Build/*/ 64 | Build/*/** 65 | !Build/*/PakBlacklist*.txt 66 | 67 | # Don't ignore icon files in Build 68 | !Build/**/*.ico 69 | 70 | # Built data for maps 71 | *_BuiltData.uasset 72 | 73 | # Configuration files generated by the Editor 74 | Saved/* 75 | 76 | # Compiled source files for the engine to use 77 | Intermediate/* 78 | Plugins/*/Intermediate/* 79 | 80 | # Cache files for the editor to use 81 | DerivedDataCache/* 82 | -------------------------------------------------------------------------------- /Source/SquirrelEditor/Private/Customizations/SquirrelStateCustomization.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Guy (Drakynfly) Lundvall. All Rights Reserved. 2 | 3 | #include "SquirrelStateCustomization.h" 4 | 5 | #include "DetailWidgetRow.h" 6 | #include "Squirrel.h" 7 | 8 | #define LOCTEXT_NAMESPACE "SquirrelStateCustomization" 9 | 10 | TSharedRef FSquirrelStateCustomization::MakeInstance() 11 | { 12 | return MakeShared(); 13 | } 14 | 15 | void FSquirrelStateCustomization::CustomizeHeader(TSharedRef PropertyHandle, 16 | FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) 17 | { 18 | Position = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FSquirrelState, Position)); 19 | check(Position) 20 | 21 | const TSharedRef PropertyWidget = Position->CreatePropertyNameWidget(); 22 | PropertyWidget->SetEnabled(PropertyHandle->IsEditable()); 23 | 24 | HeaderRow 25 | .NameContent() 26 | [ 27 | PropertyWidget 28 | ] 29 | .ValueContent() 30 | [ 31 | SNew(SHorizontalBox) 32 | + SHorizontalBox::Slot() 33 | [ 34 | Position->CreatePropertyValueWidget() 35 | ] 36 | + SHorizontalBox::Slot() 37 | .AutoWidth() 38 | [ 39 | SNew(SBox) 40 | .HAlign(HAlign_Center) 41 | .VAlign(VAlign_Center) 42 | .WidthOverride(22) 43 | .HeightOverride(22) 44 | .ToolTipText(LOCTEXT("RandomizeButtonTooltip", "Generate a new random position")) 45 | [ 46 | SNew(SButton) 47 | .IsEnabled(PropertyHandle->IsEditable()) 48 | .ButtonStyle(FAppStyle::Get(), "SimpleButton") 49 | .OnClicked(this, &FSquirrelStateCustomization::OnRandomizeClicked) 50 | .ContentPadding(0) 51 | .IsFocusable(true) 52 | [ 53 | SNew(SImage) 54 | // @todo custom/better icon 55 | .Image(FAppStyle::GetBrush("Icons.PlusCircle")) 56 | .ColorAndOpacity(FSlateColor::UseForeground()) 57 | ] 58 | ] 59 | ] 60 | ]; 61 | } 62 | 63 | FReply FSquirrelStateCustomization::OnRandomizeClicked() 64 | { 65 | check(Position) 66 | 67 | FSquirrelState* DataPtr; 68 | Position->GetValueData(reinterpret_cast(DataPtr)); 69 | 70 | if (DataPtr) 71 | { 72 | DataPtr->RandomizeState(); 73 | } 74 | 75 | TArray Outers; 76 | Position->GetOuterObjects(Outers); 77 | for (UObject* Outer : Outers) 78 | { 79 | // Notify the object that is has been modified so that undo/redo works. 80 | Outer->Modify(); 81 | } 82 | 83 | return FReply::Handled(); 84 | } 85 | 86 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Source/Squirrel/Public/Squirrel.h: -------------------------------------------------------------------------------- 1 | // Copyright Guy (Drakynfly) Lundvall. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "UObject/Object.h" 6 | #include "Subsystems/EngineSubsystem.h" 7 | 8 | #include "Squirrel.generated.h" 9 | 10 | /* 11 | * WARNING: 12 | * READ BEFORE MAKING ANY CHANGES THIS FILE: 13 | * This file generates seeded random numbers for 14 | * game code. Any changes made here may affect 15 | * generation such that existing seeds no longer 16 | * function as they previously did. Only make 17 | * changes to this file if you are aware of this, 18 | * understand what you are doing, or don't care! 19 | */ 20 | 21 | DECLARE_LOG_CATEGORY_EXTERN(LogSquirrel, Log, All) 22 | 23 | USTRUCT(BlueprintType) 24 | struct SQUIRREL_API FSquirrelState 25 | { 26 | GENERATED_BODY() 27 | 28 | /** Location in the noise. Use this to "scrub" generation forward and backward. */ 29 | UPROPERTY(BlueprintReadOnly, EditInstanceOnly, Category = "SquirrelState") 30 | int32 Position = 0; 31 | 32 | #if WITH_EDITOR 33 | void RandomizeState(); 34 | #endif 35 | }; 36 | 37 | namespace Squirrel 38 | { 39 | namespace Impl 40 | { 41 | // Direct access to calling SquirrelNoise5 42 | [[nodiscard]] uint32 SquirrelNoise5(int32& Position, uint32 Seed); 43 | } 44 | 45 | // Use SquirrelNoise to mangle two values together. 46 | SQUIRREL_API [[nodiscard]] uint32 HashCombine(int32 A, int32 B); 47 | 48 | uint32 GetGlobalSeed(); 49 | 50 | void SetGlobalSeed(uint32 Seed); 51 | 52 | template < 53 | typename T 54 | UE_REQUIRES(TIsIntegral::Value) 55 | > 56 | [[nodiscard]] T Next(FSquirrelState& State) 57 | { 58 | if constexpr (sizeof(T) >= 4) 59 | { 60 | return static_cast(Impl::SquirrelNoise5(State.Position, GetGlobalSeed())); 61 | } 62 | else 63 | { 64 | return static_cast(Impl::SquirrelNoise5(State.Position, GetGlobalSeed()) % TNumericLimits::Max()); 65 | } 66 | } 67 | 68 | template <> 69 | [[nodiscard]] inline bool Next(FSquirrelState& State) 70 | { 71 | return !!(Impl::SquirrelNoise5(State.Position, GetGlobalSeed()) % 2); 72 | } 73 | 74 | SQUIRREL_API int32 NextInt32(FSquirrelState& State, const int32 Max); 75 | 76 | SQUIRREL_API int32 NextInt32InRange(FSquirrelState& State, const int32 Min, const int32 Max); 77 | 78 | SQUIRREL_API double NextReal(FSquirrelState& State); 79 | 80 | SQUIRREL_API double NextRealInRange(FSquirrelState& State, const double Min, const double Max); 81 | 82 | /** 83 | * Roll for a deterministic chance of an event occurring. 84 | * 85 | * @param State Squirrel position 86 | * @param Roll The resulting value from the roll. Will always be a value between 0 and 100 87 | * @param Chance The percentage chance for the event to occur. Roll must meet or exceed this to succeed. 88 | * @param RollModifier A modifier to adjust the likelihood of the occurence. Must be a value between -100 and 100 89 | * @return True if the event should occur 90 | */ 91 | SQUIRREL_API [[nodiscard]] bool RollChance(FSquirrelState& State, double& Roll, const double Chance, const double RollModifier); 92 | 93 | /** 94 | * Round a float to an int with a chanced result, where the result is determined by the decimal. 95 | * Example: Value = 3.25 has a 25% chance to return 4 and a 75% chance to return 3. 96 | */ 97 | SQUIRREL_API [[nodiscard]] int32 RoundWithWeightByFraction(FSquirrelState& State, double Value); 98 | } 99 | 100 | /** 101 | * A noise-based random number generator using SquirrelNoise5 102 | */ 103 | UCLASS(BlueprintType, EditInlineNew, CollapseCategories) 104 | class SQUIRREL_API USquirrel final : public UObject 105 | { 106 | GENERATED_BODY() 107 | 108 | public: 109 | USquirrel(); 110 | 111 | virtual void PostInitProperties() override; 112 | 113 | FSquirrelState& GetState() { return State; } 114 | 115 | UFUNCTION(BlueprintCallable, Category = "Squirrel") 116 | void Jump(const int32 NewPosition); 117 | 118 | UFUNCTION(BlueprintCallable, Category = "Squirrel") 119 | int32 GetPosition() const; 120 | 121 | UFUNCTION(BlueprintCallable, Category = "Squirrel") 122 | int32 NextInt32(const int32 Max); 123 | 124 | UFUNCTION(BlueprintCallable, Category = "Squirrel") 125 | int32 NextInt32InRange(const int32 Min, const int32 Max); 126 | 127 | UFUNCTION(BlueprintCallable, Category = "Squirrel") 128 | bool NextBool(); 129 | 130 | UFUNCTION(BlueprintCallable, Category = "Squirrel") 131 | double NextReal(); 132 | 133 | UFUNCTION(BlueprintCallable, Category = "Squirrel") 134 | double NextRealInRange(const double Min, const double Max); 135 | 136 | /** 137 | * Roll for a deterministic chance of an event occurring. 138 | * 139 | * @param Roll The resulting value from the roll. Will always be a value between 0 and 100 140 | * @param Chance The percentage chance for the event to occur. Roll must meet or exceed this to succeed. 141 | * @param RollModifier A modifier to adjust the likelihood of the occurence. Must be a value between -100 and 100 142 | * @return True if the event should occur 143 | */ 144 | UFUNCTION(BlueprintCallable, Category = "Squirrel") 145 | bool RollChance(double& Roll, const double Chance, const double RollModifier); 146 | 147 | /** 148 | * Round a float to an int with a chanced result, where the result is determined by the decimal. 149 | * Example: Value = 3.25 has a 25% chance to return 4 and a 75% chance to return 3. 150 | */ 151 | UFUNCTION(BlueprintCallable, Category = "Squirrel") 152 | int32 RoundWithWeightByFraction(double Value); 153 | 154 | protected: 155 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Squirrel") 156 | FSquirrelState State; 157 | }; 158 | 159 | 160 | /** 161 | * Combines the global seed and subsystem state into an easily serialized struct. 162 | */ 163 | USTRUCT(BlueprintType) 164 | struct FSquirrelWorldState 165 | { 166 | GENERATED_BODY() 167 | 168 | // The world's seed. This is the static value that seeds the game. 169 | UPROPERTY() 170 | uint32 GlobalSeed = 0; 171 | 172 | // The position of the Squirrel Subsystem. 173 | UPROPERTY() 174 | FSquirrelState RuntimeState; 175 | }; 176 | 177 | 178 | /* 179 | * This subsystem's primary responsibility to provide seeded positions for new USquirrels generated at runtime. 180 | */ 181 | UCLASS() 182 | class SQUIRREL_API USquirrelSubsystem : public UEngineSubsystem 183 | { 184 | GENERATED_BODY() 185 | 186 | friend USquirrel; 187 | 188 | public: 189 | virtual void Initialize(FSubsystemCollectionBase& Collection) override; 190 | virtual void Deinitialize() override; 191 | 192 | protected: 193 | // Get a new position for a Squirrel that is created during gameplay. 194 | int32 NewPosition(); 195 | 196 | public: 197 | UFUNCTION(BlueprintCallable, Category = "Squirrel") 198 | int64 GetGlobalSeed() const; 199 | 200 | UFUNCTION(BlueprintCallable, Category = "Squirrel") 201 | void SetGlobalSeed(int64 NewSeed); 202 | 203 | UFUNCTION(BlueprintCallable, Category = "Squirrel") 204 | FSquirrelWorldState SaveWorldState() const; 205 | 206 | UFUNCTION(BlueprintCallable, Category = "Squirrel") 207 | void LoadGameState(FSquirrelWorldState State); 208 | 209 | private: 210 | UPROPERTY(Transient) 211 | FSquirrelState RuntimePositionsSquirrel; 212 | }; -------------------------------------------------------------------------------- /Source/Squirrel/Private/Squirrel.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Guy (Drakynfly) Lundvall. All Rights Reserved. 2 | 3 | #include "Squirrel.h" 4 | #include "SquirrelNoise5.hpp" 5 | 6 | /* 7 | * WARNING: 8 | * READ BEFORE MAKING ANY CHANGES THIS FILE: 9 | * This file generates seeded random numbers for 10 | * game code. Any changes made here may affect 11 | * generation such that existing seeds no longer 12 | * function as they previously did. Only make 13 | * changes to this file if you are aware of this, 14 | * understand what you are doing, or don't care! 15 | */ 16 | 17 | DEFINE_LOG_CATEGORY(LogSquirrel) 18 | 19 | #define LOCTEXT_NAMESPACE "Squirrel" 20 | 21 | namespace Squirrel 22 | { 23 | // The master seed used to set the game world to a consistent state that can be returned to. 24 | static uint32 GWorldSeed = 0; 25 | 26 | namespace Impl 27 | { 28 | uint32 SquirrelNoise5(int32& Position, const uint32 Seed) 29 | { 30 | return ::SquirrelNoise5(Position++, Seed); 31 | } 32 | } 33 | 34 | namespace Math 35 | { 36 | // The purpose of this function is to generate a random value in the full range of its type. 37 | // RandRange functions like that of FMath can only generate values in the range of ( Min/2+1 -> Max/2 ). 38 | template 39 | constexpr T MaxRand(RNG Engine) 40 | { 41 | union 42 | { 43 | T Total; 44 | uint8 Pieces[sizeof(T)]; 45 | } Value; 46 | 47 | for (size_t i = 0; i < sizeof Value.Pieces; i++) 48 | { 49 | Value.Pieces[i] = Engine(); 50 | } 51 | 52 | return Value.Total; 53 | } 54 | 55 | // constexpr version of FMath::Ceil 56 | constexpr int64 SqCeil(const double Value) 57 | { 58 | const int64 Int = static_cast(Value); 59 | return Value > Int ? Int + 1 : Int; 60 | } 61 | 62 | // constexpr version of FMath::Floor 63 | constexpr int64 SqFloor(const double Value) 64 | { 65 | const int64 Int = static_cast(Value); 66 | return Value < Int ? Int - 1 : Int; 67 | } 68 | } 69 | 70 | uint32 HashCombine(int32 A, const int32 B) 71 | { 72 | return Impl::SquirrelNoise5(A, B); 73 | } 74 | 75 | uint32 GetGlobalSeed() 76 | { 77 | return GWorldSeed; 78 | } 79 | 80 | void SetGlobalSeed(const uint32 Seed) 81 | { 82 | GWorldSeed = Seed; 83 | } 84 | 85 | int32 NextInt32(FSquirrelState& State, const int32 Max) 86 | { 87 | return Max > 0 ? FMath::Min(FMath::TruncToInt(NextReal(State) * static_cast(Max)), Max - 1) : 0; 88 | } 89 | 90 | int32 NextInt32InRange(FSquirrelState& State, const int32 Min, const int32 Max) 91 | { 92 | // @todo Min and Max must only cover *half* the int32 range, or it will cause an overflow in (Max - Min) 93 | // look into alternate ways to generate random values that don't have this limit 94 | 95 | const int32 Range = (Max - Min) + 1; 96 | return Min + NextInt32(State, Range); 97 | } 98 | 99 | double NextReal(FSquirrelState& State) 100 | { 101 | return Get1dNoiseZeroToOne(State.Position++, GWorldSeed); 102 | } 103 | 104 | double NextRealInRange(FSquirrelState& State, const double Min, const double Max) 105 | { 106 | // @todo Min and Max must only cover *half* the double range, or it will cause an overflow in (Max - Min) 107 | // look into alternate ways to generate random values that don't have this limit 108 | 109 | return Min + (Max - Min) * NextReal(State); 110 | } 111 | 112 | bool RollChance(FSquirrelState& State, double& Roll, const double Chance, const double RollModifier) 113 | { 114 | if (ensure((Chance >= 0.0) && (Chance <= 100.0)) || 115 | ensure((RollModifier >= -100.0) && (RollModifier <= 100.0))) 116 | { 117 | UE_LOG(LogSquirrel, Warning, TEXT("Bad inputs passed to USquirrel::RollChance - Chance: %f, Modifier: %f"), Chance, RollModifier); 118 | } 119 | 120 | Roll = NextRealInRange(State, 0.0, 100.0 - RollModifier) + RollModifier; 121 | return Roll >= Chance; 122 | } 123 | 124 | int32 RoundWithWeightByFraction(FSquirrelState& State, const double Value) 125 | { 126 | const double Whole = Math::SqFloor(Value); 127 | const double Remainder = Value - Whole; 128 | 129 | // If the Remainder equals to 0.f then always return the whole number. 130 | if (Remainder <= 0.0) 131 | { 132 | return Whole; 133 | } 134 | 135 | // Otherwise, return the whole number plus the random weighted bool. 136 | return Whole + (Remainder >= NextReal(State)); 137 | } 138 | } 139 | 140 | #if WITH_EDITOR 141 | 142 | void FSquirrelState::RandomizeState() 143 | { 144 | // It is allowable and expected to get a non-seeded random value in the editor, hence using 'Rand' here. 145 | Position = Squirrel::Math::MaxRand(FMath::Rand); 146 | } 147 | #endif 148 | 149 | USquirrel::USquirrel() 150 | { 151 | } 152 | 153 | void USquirrel::PostInitProperties() 154 | { 155 | Super::PostInitProperties(); 156 | 157 | if (!IsTemplate()) 158 | { 159 | if (const UWorld* World = GEngine->GetWorldFromContextObject(GetTypedOuter(), EGetWorldErrorMode::ReturnNull)) 160 | { 161 | // At runtime, Squirrels should be given random (but still seeded) positions 162 | if (World->HasBegunPlay()) 163 | { 164 | if (USquirrelSubsystem* Subsystem = GEngine->GetEngineSubsystem()) 165 | { 166 | State.Position = Subsystem->NewPosition(); 167 | } 168 | } 169 | #if WITH_EDITOR 170 | // In the editor, they should be given a new one in any-case 171 | else if (GIsEditor) 172 | { 173 | State.RandomizeState(); 174 | } 175 | #endif 176 | } 177 | } 178 | } 179 | 180 | void USquirrel::Jump(const int32 NewPosition) 181 | { 182 | State.Position = NewPosition; 183 | } 184 | 185 | int32 USquirrel::GetPosition() const 186 | { 187 | return State.Position; 188 | } 189 | 190 | int32 USquirrel::NextInt32(const int32 Max) 191 | { 192 | return Squirrel::NextInt32(State, Max); 193 | } 194 | 195 | int32 USquirrel::NextInt32InRange(const int32 Min, const int32 Max) 196 | { 197 | return Squirrel::NextInt32InRange(State, Min, Max); 198 | } 199 | 200 | bool USquirrel::NextBool() 201 | { 202 | return Squirrel::Next(State); 203 | } 204 | 205 | double USquirrel::NextReal() 206 | { 207 | return Squirrel::NextReal(State); 208 | } 209 | 210 | double USquirrel::NextRealInRange(const double Min, const double Max) 211 | { 212 | return Squirrel::NextRealInRange(State, Min, Max); 213 | } 214 | 215 | bool USquirrel::RollChance(double& Roll, const double Chance, const double RollModifier) 216 | { 217 | return Squirrel::RollChance(State, Roll, Chance, RollModifier); 218 | } 219 | 220 | int32 USquirrel::RoundWithWeightByFraction(const double Value) 221 | { 222 | return Squirrel::RoundWithWeightByFraction(State, Value); 223 | } 224 | 225 | void USquirrelSubsystem::Initialize(FSubsystemCollectionBase& Collection) 226 | { 227 | Super::Initialize(Collection); 228 | 229 | #if WITH_EDITOR 230 | if (GIsEditor) 231 | { 232 | RuntimePositionsSquirrel.RandomizeState(); 233 | } 234 | #endif 235 | } 236 | 237 | void USquirrelSubsystem::Deinitialize() 238 | { 239 | Super::Deinitialize(); 240 | } 241 | 242 | int32 USquirrelSubsystem::NewPosition() 243 | { 244 | return Squirrel::Next(RuntimePositionsSquirrel); 245 | } 246 | 247 | int64 USquirrelSubsystem::GetGlobalSeed() const 248 | { 249 | return Squirrel::GetGlobalSeed(); 250 | } 251 | 252 | void USquirrelSubsystem::SetGlobalSeed(const int64 NewSeed) 253 | { 254 | Squirrel::SetGlobalSeed(NewSeed); 255 | } 256 | 257 | FSquirrelWorldState USquirrelSubsystem::SaveWorldState() const 258 | { 259 | return FSquirrelWorldState{Squirrel::GetGlobalSeed(), RuntimePositionsSquirrel }; 260 | } 261 | 262 | void USquirrelSubsystem::LoadGameState(const FSquirrelWorldState State) 263 | { 264 | Squirrel::SetGlobalSeed(State.GlobalSeed); 265 | RuntimePositionsSquirrel = State.RuntimeState; 266 | } 267 | 268 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Source/Squirrel/Private/SquirrelNoise5.hpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------------------------- 2 | // SquirrelNoise5.hpp 3 | // 4 | #pragma once 5 | 6 | 7 | ///////////////////////////////////////////////////////////////////////////////////////////////// 8 | // SquirrelNoise5 - Squirrel's Raw Noise utilities (version 5) 9 | // 10 | // This code is made available under the Creative Commons attribution 3.0 license (CC-BY-3.0 US): 11 | // Attribution in source code comments (even closed-source/commercial code) is sufficient. 12 | // License summary and text available at: https://creativecommons.org/licenses/by/3.0/us/ 13 | // 14 | // These noise functions were written by Squirrel Eiserloh as a cheap and simple substitute for 15 | // the [sometimes awful] bit-noise sample code functions commonly found on the web, many of which 16 | // are hugely biased or terribly patterned, e.g. having bits which are on (or off) 75% or even 17 | // 100% of the time (or are excessively overkill/slow for our needs, such as MD5 or SHA). 18 | // 19 | // Note: This is work in progress; not all functions have been tested. Use at your own risk. 20 | // Please report any bugs, issues, or bothersome cases to SquirrelEiserloh at gmail.com. 21 | // 22 | // The following functions are all based on a simple bit-noise hash function which returns an 23 | // unsigned integer containing 32 reasonably-well-scrambled bits, based on a given (signed) 24 | // integer input parameter (position/index) and [optional] seed. Kind of like looking up a 25 | // value in an infinitely large [non-existent] table of previously rolled random numbers. 26 | // 27 | // These functions are deterministic and random-access / order-independent (i.e. state-free), 28 | // so they are particularly well-suited for use in smoothed/fractal/simplex/Perlin noise 29 | // functions and out-of-order (or or-demand) procedural content generation (i.e. that mountain 30 | // village is the same whether you generated it first or last, ahead of time or just now). 31 | // 32 | // The N-dimensional variations simply hash their multidimensional coordinates down to a single 33 | // 32-bit index and then proceed as usual, so while results are not unique they should 34 | // (hopefully) not seem locally predictable or repetitive. 35 | // 36 | ///////////////////////////////////////////////////////////////////////////////////////////////// 37 | 38 | using FSquirrelReal = double; 39 | 40 | //----------------------------------------------------------------------------------------------- 41 | // Raw pseudorandom noise functions (random-access / deterministic). Basis of all other noise. 42 | // 43 | constexpr uint32 Get1dNoiseUint(int32 Index, uint32 Seed=0); 44 | constexpr uint32 Get2dNoiseUint(int32 IndexX, int32 IndexY, uint32 Seed=0); 45 | constexpr uint32 Get3dNoiseUint(int32 IndexX, int32 IndexY, int32 IndexZ, uint32 Seed=0); 46 | constexpr uint32 Get4dNoiseUint(int32 IndexX, int32 IndexY, int32 IndexZ, int32 IndexT, uint32 Seed=0); 47 | 48 | //----------------------------------------------------------------------------------------------- 49 | // Same functions, mapped to floats in [0,1] for convenience. 50 | // 51 | constexpr FSquirrelReal Get1dNoiseZeroToOne(int32 Index, uint32 Seed=0); 52 | constexpr FSquirrelReal Get2dNoiseZeroToOne(int32 IndexX, int32 IndexY, uint32 Seed=0); 53 | constexpr FSquirrelReal Get3dNoiseZeroToOne(int32 IndexX, int32 IndexY, int32 IndexZ, uint32 Seed=0); 54 | constexpr FSquirrelReal Get4dNoiseZeroToOne(int32 IndexX, int32 IndexY, int32 IndexZ, int32 IndexT, uint32 Seed=0); 55 | 56 | //----------------------------------------------------------------------------------------------- 57 | // Same functions, mapped to floats in [-1,1] for convenience. 58 | // 59 | constexpr FSquirrelReal Get1dNoiseNegOneToOne(int32 Index, uint32 Seed=0); 60 | constexpr FSquirrelReal Get2dNoiseNegOneToOne(int32 IndexX, int32 IndexY, uint32 Seed=0); 61 | constexpr FSquirrelReal Get3dNoiseNegOneToOne(int32 IndexX, int32 IndexY, int32 IndexZ, uint32 Seed=0); 62 | constexpr FSquirrelReal Get4dNoiseNegOneToOne(int32 IndexX, int32 IndexY, int32 IndexZ, int32 IndexT, uint32 Seed=0); 63 | 64 | 65 | ///////////////////////////////////////////////////////////////////////////////////////////////// 66 | // Inline function definitions below 67 | ///////////////////////////////////////////////////////////////////////////////////////////////// 68 | 69 | //----------------------------------------------------------------------------------------------- 70 | // Fast hash of an int32 into a different (unrecognizable) uint32. 71 | // 72 | // Returns an unsigned integer containing 32 reasonably-well-scrambled bits, based on the hash 73 | // of a given (signed) integer input parameter (position/index) and [optional] seed. Kind of 74 | // like looking up a value in an infinitely large table of previously generated random numbers. 75 | // 76 | // I call this particular approach SquirrelNoise5 (5th iteration of my 1D raw noise function). 77 | // 78 | // Many thanks to Peter Schmidt-Nielsen whose outstanding analysis helped identify a weakness 79 | // in the SquirrelNoise3 code I originally used in my GDC 2017 talk, "Noise-based RNG". 80 | // Version 5 avoids a noise repetition found in version 3 at extremely high position values 81 | // caused by a lack of influence by some of the high input bits onto some of the low output bits. 82 | // 83 | // The revised SquirrelNoise5 function ensures all input bits affect all output bits, and to 84 | // (for me) a statistically acceptable degree. I believe the worst-case here is in the amount 85 | // of influence input position bit #30 has on output noise bit #0 (49.99%, vs. 50% ideal). 86 | // 87 | constexpr uint32 SquirrelNoise5(const int32 Position, const uint32 Seed) 88 | { 89 | constexpr uint32 SQ5_BIT_NOISE1 = 0xd2a80a3f; // 11010010101010000000101000111111 90 | constexpr uint32 SQ5_BIT_NOISE2 = 0xa884f197; // 10101000100001001111000110010111 91 | constexpr uint32 SQ5_BIT_NOISE3 = 0x6C736F4B; // 01101100011100110110111101001011 92 | constexpr uint32 SQ5_BIT_NOISE4 = 0xB79F3ABB; // 10110111100111110011101010111011 93 | constexpr uint32 SQ5_BIT_NOISE5 = 0x1b56c4f5; // 00011011010101101100010011110101 94 | 95 | uint32 MangledBits = static_cast(Position); 96 | MangledBits *= SQ5_BIT_NOISE1; 97 | MangledBits += Seed; 98 | MangledBits ^= MangledBits >> 9; 99 | MangledBits += SQ5_BIT_NOISE2; 100 | MangledBits ^= MangledBits >> 11; 101 | MangledBits *= SQ5_BIT_NOISE3; 102 | MangledBits ^= MangledBits >> 13; 103 | MangledBits += SQ5_BIT_NOISE4; 104 | MangledBits ^= MangledBits >> 15; 105 | MangledBits *= SQ5_BIT_NOISE5; 106 | MangledBits ^= MangledBits >> 17; 107 | return MangledBits; 108 | } 109 | 110 | #define ONE_OVER_MAX_UINT (1.0 / static_cast(TNumericLimits::Max())) 111 | #define ONE_OVER_MAX_INT (1.0 / static_cast(TNumericLimits::Max())) 112 | #define PRIME1 198491317 // Large prime number with non-boring bits 113 | #define PRIME2 6542989 // Large prime number with distinct and non-boring bits 114 | #define PRIME3 357239 // Large prime number with distinct and non-boring bits 115 | 116 | constexpr uint32 Get1dNoiseUint(const int32 Index, const uint32 Seed) 117 | { 118 | return SquirrelNoise5(Index, Seed); 119 | } 120 | 121 | constexpr uint32 Get2dNoiseUint(const int32 IndexX, const int32 IndexY, const uint32 Seed) 122 | { 123 | return SquirrelNoise5(IndexX + PRIME1 * IndexY, Seed); 124 | } 125 | 126 | constexpr uint32 Get3dNoiseUint(const int32 IndexX, const int32 IndexY, const int32 IndexZ, const uint32 Seed) 127 | { 128 | return SquirrelNoise5(IndexX + PRIME1 * IndexY + PRIME2 * IndexZ, Seed); 129 | } 130 | 131 | constexpr uint32 Get4dNoiseUint(const int32 IndexX, const int32 IndexY, const int32 IndexZ, const int32 IndexT, const uint32 Seed) 132 | { 133 | return SquirrelNoise5(IndexX + PRIME1 * IndexY + PRIME2 * IndexZ + PRIME3 * IndexT, Seed); 134 | } 135 | 136 | constexpr FSquirrelReal Get1dNoiseZeroToOne(const int32 Index, const uint32 Seed) 137 | { 138 | return ONE_OVER_MAX_UINT * static_cast(SquirrelNoise5(Index, Seed)); 139 | } 140 | 141 | constexpr FSquirrelReal Get2dNoiseZeroToOne(const int32 IndexX, const int32 IndexY, const uint32 Seed) 142 | { 143 | return ONE_OVER_MAX_UINT * static_cast(Get2dNoiseUint(IndexX, IndexY, Seed)); 144 | } 145 | 146 | constexpr FSquirrelReal Get3dNoiseZeroToOne(const int32 IndexX, const int32 IndexY, const int32 IndexZ, const uint32 Seed) 147 | { 148 | return ONE_OVER_MAX_UINT * static_cast(Get3dNoiseUint(IndexX, IndexY, IndexZ, Seed)); 149 | } 150 | 151 | constexpr FSquirrelReal Get4dNoiseZeroToOne(const int32 IndexX, const int32 IndexY, const int32 IndexZ, const int32 IndexT, const uint32 Seed) 152 | { 153 | return ONE_OVER_MAX_UINT * static_cast(Get4dNoiseUint(IndexX, IndexY, IndexZ, IndexT, Seed)); 154 | } 155 | 156 | constexpr FSquirrelReal Get1dNoiseNegOneToOne(const int32 Index, const uint32 Seed) 157 | { 158 | return ONE_OVER_MAX_INT * static_cast(static_cast(SquirrelNoise5(Index, Seed))); 159 | } 160 | 161 | constexpr FSquirrelReal Get2dNoiseNegOneToOne(const int32 IndexX, const int32 IndexY, const uint32 Seed) 162 | { 163 | return ONE_OVER_MAX_INT * static_cast(static_cast(Get2dNoiseUint(IndexX, IndexY, Seed))); 164 | } 165 | 166 | constexpr FSquirrelReal Get3dNoiseNegOneToOne(const int32 IndexX, const int32 IndexY, const int32 IndexZ, const uint32 Seed) 167 | { 168 | return ONE_OVER_MAX_INT * static_cast(static_cast(Get3dNoiseUint(IndexX, IndexY, IndexZ, Seed))); 169 | } 170 | 171 | constexpr FSquirrelReal Get4dNoiseNegOneToOne(const int32 IndexX, const int32 IndexY, const int32 IndexZ, const int32 IndexT, const uint32 Seed) 172 | { 173 | return ONE_OVER_MAX_INT * static_cast(static_cast(Get4dNoiseUint(IndexX, IndexY, IndexZ, IndexT, Seed))); 174 | } --------------------------------------------------------------------------------